/* ==========================================================================
   NIST Tech ISP -- blue and white theme
   isp.nisttech.com

   No webfonts and no CDN: the Content-Security-Policy in config/security.php
   is `default-src 'self'`, so anything loaded from outside this origin is
   blocked. The type system below is built from fonts already on the device.

   Type has two roles:
     - proportional for anything a person wrote (labels, prose, buttons)
     - monospace for anything the network produced (IPs, MACs, room numbers,
       throughput, session IDs)
   That split is the point. Operators scan columns of machine data, and
   fixed-width digits make a mistyped octet visible at a glance.
   ========================================================================== */

:root {
    /* -- Blue and white ---------------------------------------------------- */
    --paper:        #F4F7FB;  /* page, a cool white rather than a grey       */
    --surface:      #FFFFFF;  /* cards and panels                            */
    --surface-sunk: #EEF3FA;  /* table headers, inset wells                  */

    --ink:          #0C1E33;  /* primary text, near-black navy               */
    --ink-soft:     #35506E;  /* secondary text                              */
    --ink-faint:    #6B84A3;  /* captions, placeholders                      */

    --line:         #DCE6F2;  /* hairlines                                   */
    --line-strong:  #BFD1E8;

    --blue:         #0A5BD3;  /* primary action                              */
    --blue-hover:   #0847A8;
    --blue-deep:    #06306E;  /* headers, the darkest blue in the system     */
    --blue-tint:    #E8F0FE;  /* selected rows, quiet badges                 */
    --blue-glow:    rgba(10, 91, 211, .22);

    /* -- Link states. Named after what an operator sees on a faceplate. --- */
    --up:           #0E7C5A;
    --up-tint:      #DFF3EB;
    --warn:         #B45309;
    --warn-tint:    #FBF0E0;
    --down:         #C42B1C;
    --down-tint:    #FBE6E4;
    --idle:         #A8BCD4;

    /* -- Type -------------------------------------------------------------- */
    --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
            "Helvetica Neue", Arial, sans-serif;
    --mono: ui-monospace, SFMono-Regular, "SF Mono", "Cascadia Mono",
            "Roboto Mono", Menlo, Consolas, monospace;

    --step--1: .8125rem;
    --step-0:  .9375rem;
    --step-1:  1.0625rem;
    --step-2:  1.375rem;
    --step-3:  1.875rem;
    --step-4:  2.5rem;

    /* -- Space and shape --------------------------------------------------- */
    --gap-1: .375rem;
    --gap-2: .75rem;
    --gap-3: 1.25rem;
    --gap-4: 2rem;
    --gap-5: 3rem;

    --radius:    4px;
    --radius-lg: 8px;

    --shadow-card: 0 1px 2px rgba(12, 30, 51, .05);
    --shadow-lift: 0 4px 16px rgba(12, 30, 51, .10);

    --bar-height: 4px;   /* the link bar, see .link-bar */
}

/* ==========================================================================
   Reset
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    background: var(--paper);
    color: var(--ink);
    font-family: var(--sans);
    font-size: var(--step-0);
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3, p, figure { margin: 0; }

h1, h2, h3 {
    font-weight: 650;
    letter-spacing: -.014em;
    line-height: 1.2;
}

h1 { font-size: var(--step-3); }
h2 { font-size: var(--step-2); }
h3 { font-size: var(--step-1); }

a { color: var(--blue); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--blue-hover); }

button, input, select, textarea { font: inherit; color: inherit; }

/* Keyboard focus must always be visible. Operators work this UI by tab. */
:focus-visible {
    outline: 2px solid var(--blue);
    outline-offset: 2px;
    border-radius: 2px;
}

/* ==========================================================================
   Machine data
   Anything the network produced rather than a person typed.
   ========================================================================== */

.mono,
.ip, .mac, .room, .rate, .code, .uuid {
    font-family: var(--mono);
    font-variant-numeric: tabular-nums;
    font-size: .9em;
    letter-spacing: -.01em;
}

.ip, .mac, .uuid { color: var(--ink-soft); }

.room {
    font-weight: 600;
    color: var(--ink);
}

.code {
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
}

/* ==========================================================================
   The link bar -- the signature element
   A strip of segments modelled on the LED bank across a switch faceplate.
   Each segment is one uplink or access point. It is the first thing on the
   page because it is the first thing an operator looks at on the hardware.
   ========================================================================== */

.link-bar {
    display: flex;
    gap: 2px;
    height: var(--bar-height);
    border-radius: var(--bar-height);
    overflow: hidden;
    background: var(--line);
}

.link-bar__seg {
    flex: 1 1 0;
    background: var(--idle);
    transition: background .4s ease;
}

.link-bar__seg[data-state="up"]      { background: var(--up); }
.link-bar__seg[data-state="warn"]    { background: var(--warn); }
.link-bar__seg[data-state="down"]    { background: var(--down); }
.link-bar__seg[data-state="unknown"] { background: var(--idle); }

/* The health poll runs once a minute. The pulse is that poll made visible,
   not decoration -- when it stops, polling has stopped. */
.link-bar--live .link-bar__seg[data-state="up"] {
    animation: poll 60s linear infinite;
}

@keyframes poll {
    0%, 96%  { opacity: 1; }
    98%      { opacity: .45; }
    100%     { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .link-bar--live .link-bar__seg { animation: none; }
    *, *::before, *::after {
        animation-duration: .001ms !important;
        transition-duration: .001ms !important;
    }
}

/* ==========================================================================
   Layout
   ========================================================================== */

.shell {
    display: grid;
    grid-template-columns: 232px 1fr;
    min-height: 100vh;
}

.sidebar {
    background: var(--blue-deep);
    color: #fff;
    padding: var(--gap-3) 0;
    display: flex;
    flex-direction: column;
    gap: var(--gap-3);
}

.sidebar__brand {
    padding: 0 var(--gap-3);
    display: flex;
    flex-direction: column;
    gap: var(--gap-1);
}

.sidebar__name {
    font-size: var(--step-1);
    font-weight: 650;
    letter-spacing: -.01em;
}

.sidebar__host {
    font-family: var(--mono);
    font-size: var(--step--1);
    color: rgba(255, 255, 255, .58);
}

.nav { display: flex; flex-direction: column; }

.nav__label {
    padding: var(--gap-2) var(--gap-3) var(--gap-1);
    font-size: .6875rem;
    font-weight: 650;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, .45);
}

.nav__item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--gap-2);
    padding: .5rem var(--gap-3);
    color: rgba(255, 255, 255, .82);
    text-decoration: none;
    border-left: 3px solid transparent;
}

.nav__item:hover {
    background: rgba(255, 255, 255, .07);
    color: #fff;
}

.nav__item[aria-current="page"] {
    background: rgba(255, 255, 255, .12);
    border-left-color: #fff;
    color: #fff;
    font-weight: 600;
}

.nav__count {
    font-family: var(--mono);
    font-size: var(--step--1);
    color: rgba(255, 255, 255, .6);
}

.main { min-width: 0; display: flex; flex-direction: column; }

.topbar {
    background: var(--surface);
    border-bottom: 1px solid var(--line);
    padding: var(--gap-3) var(--gap-4);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--gap-3);
    flex-wrap: wrap;
}

.topbar__title { display: flex; flex-direction: column; gap: 2px; }

.topbar__eyebrow {
    font-size: var(--step--1);
    color: var(--ink-faint);
}

.content {
    padding: var(--gap-4);
    display: flex;
    flex-direction: column;
    gap: var(--gap-3);
    max-width: 1280px;
    width: 100%;
}

.grid {
    display: grid;
    gap: var(--gap-3);
    grid-template-columns: repeat(auto-fit, minmax(216px, 1fr));
}

/* ==========================================================================
   Cards
   ========================================================================== */

.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card);
    overflow: hidden;
}

.card__head {
    padding: var(--gap-2) var(--gap-3);
    border-bottom: 1px solid var(--line);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--gap-2);
}

.card__title {
    font-size: var(--step-0);
    font-weight: 650;
}

.card__body { padding: var(--gap-3); }

/* A single figure with its label. The number carries the weight; the label
   stays small and quiet so a wall of these stays scannable. */
.stat { padding: var(--gap-3); }

.stat__label {
    font-size: var(--step--1);
    color: var(--ink-faint);
    text-transform: uppercase;
    letter-spacing: .06em;
    font-weight: 600;
}

.stat__value {
    font-family: var(--mono);
    font-size: var(--step-4);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    letter-spacing: -.03em;
    line-height: 1.1;
    margin-top: var(--gap-1);
}

.stat__unit {
    font-size: var(--step-1);
    color: var(--ink-faint);
    font-weight: 500;
}

.stat__note {
    font-size: var(--step--1);
    color: var(--ink-soft);
    margin-top: var(--gap-1);
}

/* ==========================================================================
   Status
   ========================================================================== */

.pill {
    display: inline-flex;
    align-items: center;
    gap: .375rem;
    padding: .125rem .5rem;
    border-radius: 999px;
    font-size: var(--step--1);
    font-weight: 600;
    line-height: 1.6;
    white-space: nowrap;
}

.pill::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    flex: none;
}

.pill--up      { background: var(--up-tint);    color: var(--up); }
.pill--warn    { background: var(--warn-tint);  color: var(--warn); }
.pill--down    { background: var(--down-tint);  color: var(--down); }
.pill--info    { background: var(--blue-tint);  color: var(--blue); }
.pill--idle    { background: var(--surface-sunk); color: var(--ink-faint); }

/* ==========================================================================
   Tables
   ========================================================================== */

.table-wrap { overflow-x: auto; }

table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--step-0);
}

thead th {
    background: var(--surface-sunk);
    text-align: left;
    padding: .5rem var(--gap-3);
    font-size: var(--step--1);
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--ink-soft);
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
}

tbody td {
    padding: .625rem var(--gap-3);
    border-bottom: 1px solid var(--line);
    vertical-align: middle;
}

tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--blue-tint); }

td.num, th.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* ==========================================================================
   Forms
   ========================================================================== */

.field {
    display: flex;
    flex-direction: column;
    gap: var(--gap-1);
    margin-bottom: var(--gap-3);
}

.field__label {
    font-size: var(--step--1);
    font-weight: 650;
    color: var(--ink-soft);
}

.field__hint {
    font-size: var(--step--1);
    color: var(--ink-faint);
}

.input {
    width: 100%;
    padding: .5625rem .75rem;
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    transition: border-color .15s, box-shadow .15s;
}

.input::placeholder { color: var(--ink-faint); }

.input:focus {
    outline: none;
    border-color: var(--blue);
    box-shadow: 0 0 0 3px var(--blue-glow);
}

.input[aria-invalid="true"] {
    border-color: var(--down);
}

.input--code {
    font-family: var(--mono);
    font-size: var(--step-2);
    letter-spacing: .22em;
    text-align: center;
    text-transform: uppercase;
}

/* ==========================================================================
   Buttons
   ========================================================================== */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .5rem;
    padding: .5625rem 1.125rem;
    border: 1px solid transparent;
    border-radius: var(--radius);
    font-weight: 600;
    cursor: pointer;
    text-decoration: none;
    transition: background .15s, border-color .15s, color .15s;
}

.btn--primary {
    background: var(--blue);
    color: #fff;
}
.btn--primary:hover:not(:disabled) { background: var(--blue-hover); color: #fff; }

.btn--quiet {
    background: var(--surface);
    border-color: var(--line-strong);
    color: var(--ink);
}
.btn--quiet:hover:not(:disabled) { border-color: var(--blue); color: var(--blue); }

.btn--danger {
    background: var(--surface);
    border-color: var(--down);
    color: var(--down);
}
.btn--danger:hover:not(:disabled) { background: var(--down); color: #fff; }

.btn--block { width: 100%; }

.btn:disabled { opacity: .55; cursor: not-allowed; }

.btn--busy { position: relative; color: transparent !important; }

.btn--busy::after {
    content: "";
    position: absolute;
    width: 15px;
    height: 15px;
    border: 2px solid rgba(255, 255, 255, .4);
    border-top-color: #fff;
    border-radius: 50%;
    animation: spin .7s linear infinite;
}

.btn--quiet.btn--busy::after { border-color: var(--line-strong); border-top-color: var(--blue); }

@keyframes spin { to { transform: rotate(360deg); } }

/* ==========================================================================
   Messages
   Errors say what happened and what to do. They do not apologise.
   ========================================================================== */

.notice {
    display: flex;
    gap: var(--gap-2);
    padding: var(--gap-2) var(--gap-3);
    border-radius: var(--radius);
    border: 1px solid;
    font-size: var(--step-0);
}

.notice--error { background: var(--down-tint); border-color: #F0B8B2; color: #8A1F14; }
.notice--warn  { background: var(--warn-tint); border-color: #EFCF9E; color: #7A3A06; }
.notice--info  { background: var(--blue-tint); border-color: #BBD3F7; color: var(--blue-deep); }
.notice--ok    { background: var(--up-tint);   border-color: #A9DCC8; color: #08543C; }

.notice__title { font-weight: 650; display: block; }

[hidden] { display: none !important; }

/* An empty screen is an invitation to act, so it always carries the action. */
.empty {
    padding: var(--gap-5) var(--gap-3);
    text-align: center;
    color: var(--ink-faint);
}

.empty__title {
    color: var(--ink);
    font-weight: 650;
    font-size: var(--step-1);
    margin-bottom: var(--gap-1);
}

.skeleton {
    background: linear-gradient(90deg, var(--surface-sunk) 25%, #F7FAFE 50%, var(--surface-sunk) 75%);
    background-size: 400% 100%;
    animation: shimmer 1.4s ease infinite;
    border-radius: var(--radius);
    color: transparent;
}

@keyframes shimmer { to { background-position: -400% 0; } }

/* ==========================================================================
   Centred single-card pages: sign in, and the guest portal
   ========================================================================== */

.gate {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: var(--gap-3);
    background:
        radial-gradient(circle at 15% 0%, #E6EFFC 0%, transparent 48%),
        radial-gradient(circle at 88% 100%, #E9F1FD 0%, transparent 46%),
        var(--paper);
}

.gate__card {
    width: 100%;
    max-width: 400px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lift);
    overflow: hidden;
}

/* The card's top edge is the link bar. On the sign-in screen it is the only
   thing on the page that reports on the network, which is the whole point of
   signing in. */
.gate__card > .link-bar { border-radius: 0; height: var(--bar-height); }

.gate__body { padding: var(--gap-4) var(--gap-3) var(--gap-3); }

.gate__brand {
    text-align: center;
    margin-bottom: var(--gap-4);
}

.gate__brand h1 {
    font-size: var(--step-2);
    margin-bottom: 2px;
}

.gate__host {
    font-family: var(--mono);
    font-size: var(--step--1);
    color: var(--ink-faint);
}

.gate__foot {
    padding: var(--gap-2) var(--gap-3);
    background: var(--surface-sunk);
    border-top: 1px solid var(--line);
    font-size: var(--step--1);
    color: var(--ink-faint);
    text-align: center;
}

/* Tabs, used by the portal to switch between room and voucher sign-in. */
.tabs {
    display: flex;
    gap: var(--gap-1);
    background: var(--surface-sunk);
    padding: 3px;
    border-radius: var(--radius);
    margin-bottom: var(--gap-3);
}

.tabs__btn {
    flex: 1;
    padding: .4375rem;
    border: 0;
    border-radius: 3px;
    background: transparent;
    color: var(--ink-soft);
    font-weight: 600;
    font-size: var(--step--1);
    cursor: pointer;
}

.tabs__btn[aria-selected="true"] {
    background: var(--surface);
    color: var(--blue);
    box-shadow: var(--shadow-card);
}

/* ==========================================================================
   Responsive
   ========================================================================== */

@media (max-width: 860px) {
    .shell { grid-template-columns: 1fr; }

    .sidebar {
        flex-direction: row;
        align-items: center;
        overflow-x: auto;
        padding: var(--gap-2) var(--gap-2);
        gap: var(--gap-2);
    }

    .sidebar__brand { padding: 0 var(--gap-1); }
    .sidebar__host { display: none; }
    .nav { flex-direction: row; }
    .nav__label { display: none; }

    .nav__item {
        border-left: 0;
        border-bottom: 3px solid transparent;
        white-space: nowrap;
        padding: .375rem .625rem;
    }

    .nav__item[aria-current="page"] { border-bottom-color: #fff; }
    .nav__count { display: none; }

    .content, .topbar { padding: var(--gap-3); }
}

@media print {
    .sidebar, .topbar, .btn { display: none; }
    .card { border-color: #999; box-shadow: none; }
    body { background: #fff; }
}

/* =============================================================================
   Views, toolbars and pagination
   Added with the hash router: the console previously rendered only the
   overview, so the sidebar links had no targets to reveal.
   ========================================================================== */

/* View containers are toggled with the `hidden` attribute. They deliberately
   carry no display property of their own, because any value here would
   outrank the user-agent `[hidden] { display: none }` rule and leave every
   view permanently visible. */
.view > * + * { margin-top: var(--gap-3); }

.toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .625rem;
    margin-bottom: 1rem;
}

.toolbar__grow { flex: 1 1 12rem; min-width: 0; }

.toolbar .input { margin: 0; }

.select {
    appearance: none;
    font: inherit;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: .5rem 2rem .5rem .75rem;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath fill='%236b7280' d='M2 4.5 6 8.5 10 4.5z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right .6rem center;
    background-size: .75rem;
}

.select:focus-visible,
.toolbar .input:focus-visible {
    outline: 2px solid var(--blue);
    outline-offset: 1px;
}

.btn--sm { padding: .35rem .625rem; font-size: .8125rem; }

td.actions { white-space: nowrap; text-align: right; }
td.actions .btn + .btn { margin-left: .375rem; }

.pager {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: .75rem;
    padding: .75rem 1rem;
    border-top: 1px solid var(--line);
}

.pager__state { color: var(--ink-soft); font-size: .8125rem; }

.codes {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
    gap: .5rem;
    max-height: 18rem;
    overflow: auto;
    padding: .875rem;
    background: var(--surface-sunk, #f8fafc);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

.codes span {
    font-family: var(--mono);
    font-size: .875rem;
    letter-spacing: .04em;
}

.catalogue { display: grid; gap: .625rem; }

.catalogue__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: .75rem .875rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

.catalogue__name { font-weight: 600; }
.catalogue__desc { color: var(--ink-soft); font-size: .8125rem; margin-top: .125rem; }

/* Visually hidden but announced: the action columns have no visible header. */
.sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* =============================================================================
   Modal
   Router add/edit needs eighteen fields; prompt() is not adequate for that.
   ========================================================================== */

.modal {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: var(--gap-4);
    overflow-y: auto;
    background: rgba(15, 23, 42, .55);
}

.modal[hidden] { display: none; }

.modal__card {
    width: 100%;
    max-width: 46rem;
    margin: auto;
    background: var(--surface);
    border-radius: var(--radius);
    box-shadow: 0 20px 45px rgba(15, 23, 42, .25);
}

.modal__card--wide { max-width: 60rem; }

.modal__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--line);
}

.modal__title { font-weight: 600; font-size: 1.0625rem; }

.modal__close {
    border: 0;
    background: none;
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    color: var(--ink-soft);
    padding: 0 .25rem;
}

.modal__close:hover { color: var(--ink); }

.modal__body { padding: 1.25rem; }

.modal__foot {
    display: flex;
    justify-content: flex-end;
    gap: .625rem;
    padding: 1rem 1.25rem;
    border-top: 1px solid var(--line);
}

/* Two columns on anything wider than a phone; the notes field spans both. */
.form-grid {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}

.form-grid .field--wide { grid-column: 1 / -1; }

.form-section {
    grid-column: 1 / -1;
    margin-top: .5rem;
    padding-top: .875rem;
    border-top: 1px solid var(--line);
    font-size: .8125rem;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

.form-grid .field:first-child .form-section,
.form-section:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

.check { display: flex; align-items: center; gap: .5rem; }
.check input { width: 1rem; height: 1rem; }

.detail {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: .5rem 1.25rem;
    font-size: .875rem;
}

.detail dt { color: var(--ink-soft); }
.detail dd { margin: 0; font-family: var(--mono); }

.tabs__panel { padding-top: 1rem; }
