/* Poshtibano Control Panel
   Dark-first, RTL-first. Layout uses logical properties (inline-start/end) so the same rules
   serve the default RTL layout and an LTR deployment without a second stylesheet. */

:root {
    --bg: #0b0f16;
    --bg-soft: #121826;
    --panel: #151c2b;
    --panel-2: #1b2334;
    --line: #263149;
    --text: #e8edf7;
    --text-soft: #9aa7c0;
    --accent: #4f8cff;
    --accent-soft: #1d2c4d;
    --ok: #35c88a;
    --warn: #f0b429;
    --bad: #f2685f;
    /* Peer session ids: one hue per side of the pair, so an id can be attributed to the agent or
       the controller by colour without reading the column header. */
    --sid-agent: #56c8ff;
    --sid-controller: #c08cff;
    --radius: 14px;
    --radius-sm: 9px;
    --shadow: 0 1px 2px rgba(0, 0, 0, .35), 0 8px 24px rgba(0, 0, 0, .18);
    /* Scrollbar tones. Declared once and built from --text-soft / --accent, so they resolve
       against whichever theme is active instead of needing a light-theme duplicate: a muted
       thumb at rest, the theme's own accent while the surface is in use. The track is
       transparent, which is what makes the pill read as floating on the surface. */
    --scroll-thumb: color-mix(in oklab, var(--text-soft) 38%, transparent);
    --scroll-thumb-hover: color-mix(in oklab, var(--accent) 55%, transparent);
    --mono: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace;
}

html[data-theme="light"] {
    --bg: #f4f6fb;
    --bg-soft: #ffffff;
    --panel: #ffffff;
    --panel-2: #f7f9fd;
    --line: #dfe5f0;
    --text: #16203a;
    --text-soft: #62708c;
    --accent: #2f6bdb;
    --accent-soft: #e6eefc;
    --ok: #12915f;
    --warn: #a86a00;
    --bad: #c0392f;
    --sid-agent: #0b5f96;
    --sid-controller: #6b46c1;
    --shadow: 0 1px 2px rgba(16, 32, 64, .06), 0 8px 24px rgba(16, 32, 64, .06);
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    min-height: 100%;
}

body {
    background: var(--bg);
    color: var(--text);
    font: 15px/1.55 "Segoe UI", system-ui, -apple-system, "Noto Sans", "Vazirmatn", sans-serif;
}

h1 { font-size: 1.5rem; margin: 0 0 .35rem; letter-spacing: -.01em; }
h2 { font-size: 1.05rem; margin: 0; }
p { margin: 0 0 .6rem; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.mono { font-family: var(--mono); font-size: .85em; }

/* A peer session id is Latin data shown inside a possibly-RTL table: keep it LTR and isolated so
   "1-234-567-890" is never reordered by the surrounding bidi context. */
.sid { font-family: var(--mono); font-size: .85em; direction: ltr; unicode-bidi: isolate; word-break: break-all; }
.sid-agent { color: var(--sid-agent); }
.sid-controller { color: var(--sid-controller); }
.muted { color: var(--text-soft); }
.small { font-size: .82rem; }
.spacer { height: .8rem; }
.spacer-inline { display: inline-block; width: .5rem; }
.num { text-align: end; }

/* ── shell ─────────────────────────────────────────────────────────────── */

.shell {
    --sidebar-width: 262px;
    position: relative;
    display: grid;
    grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
    min-height: 100vh;
}

/* The sidebar itself is sticky at 100vh, but its navigation can be taller than the viewport. The
   overflowing links and footer would then paint over the page background with no rail behind them.
   This strip follows the shell's full height (and the current rail width) instead of the sidebar's
   one-viewport box, so the surface and divider stay continuous. */
.shell::before {
    content: "";
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    inline-size: var(--sidebar-width);
    background: var(--bg-soft);
    border-inline-end: 1px solid var(--line);
    z-index: 0;
    pointer-events: none;
}

.sidebar {
    background: var(--bg-soft);
    padding: 1.1rem .9rem;
    display: flex;
    flex-direction: column;
    gap: 1.1rem;
    position: sticky;
    top: 0;
    height: 100vh;
    /* A compact group's flyout leaves the rail and sits above the topbar/content. */
    z-index: 20;
}

.brand { display: flex; align-items: center; gap: .7rem; padding-inline: .35rem; }

/* The mark is the app's own artwork (Poshtibano.UI/Icon): a cream-and-orange disc that reads on both
   themes, so it needs no tile behind it. 36px keeps the row close to the old 40px tile's height, so
   nothing below the brand shifts. Empty alt because the wordmark sits right beside it. */
.brand-mark { flex: none; width: 36px; height: 36px; }
.brand-mark img { width: 100%; height: 100%; display: block; }

.brand-text { display: flex; flex-direction: column; line-height: 1.2; }
.brand-text span { color: var(--text-soft); font-size: .8rem; }

.nav { display: flex; flex-direction: column; gap: .2rem; }

.nav-link {
    display: flex; align-items: center; gap: .6rem;
    padding: .58rem .7rem;
    border-radius: var(--radius-sm);
    color: var(--text);
    font-weight: 500;
}

.nav-link:hover { background: var(--panel-2); text-decoration: none; }
.nav-link.active { background: var(--accent-soft); color: var(--accent); }
.nav-glyph { opacity: .75; width: 1.1em; text-align: center; }
.nav-link-text { min-inline-size: 0; }

/* The sidebar's two levels, made unmistakable. Design-system guidance for side navigation is
   consistent here: section headers are small uppercase labels at ~60% contrast, separated by
   16–24px; sub-items are 13–14px and indented 16–24px under a guide line; and no more than three
   levels are ever shown. (Fluent 2 Nav, Queensland/Apple side-navigation guidance, and the common
   admin-sidebar pattern all describe the same treatment.) Without the guide line and the type
   step, a nested page reads as just another top-level destination.

   Two levels are all this catalogue has — group and page — so the hierarchy is expressed in the
   type scale, indentation and the rule, never by inventing a third disclosure. */
.nav-group {
    display: flex;
    flex-direction: column;
    gap: .15rem;
    margin-top: 1.15rem;
}

/* The first group follows the standalone /profile link; it needs less separation than one group
   from the next. */
.nav-group:first-of-type { margin-top: .55rem; }

.nav-group-head {
    display: flex; align-items: center; gap: .4rem;
    inline-size: 100%;
    padding: .35rem .55rem .3rem;
    border: 0; border-radius: var(--radius-sm); background: none; cursor: pointer;
    color: var(--text-soft);
    font: inherit; font-size: .74rem; font-weight: 600;
    text-transform: uppercase; letter-spacing: .08em;
    text-align: start;
}

.nav-group-head:hover { color: var(--text); background: var(--panel-2); }
.nav-caret { flex: none; width: .9rem; font-size: .7rem; opacity: .65; text-align: center; }

/* The guide line is the depth cue that survives scanning: it starts at the section label and runs
   beside every child, so a page is visibly inside something rather than beside it. Logical
   properties keep the indent and the line on the inline-start edge in both LTR and RTL. */
.nav-group-body {
    display: flex;
    flex-direction: column;
    gap: .1rem;
    margin-inline-start: .85rem;
    padding-inline-start: .55rem;
    border-inline-start: 1px solid var(--line);
}

/* Subordinate, not disabled: 13.5px against the 15px top-level links, softened until hover/current. */
.nav-group-body .nav-link {
    padding-block: .38rem;
    padding-inline: .55rem;
    color: var(--text-soft);
    font-size: .9rem;
    font-weight: 500;
}

.nav-group-body .nav-link:hover { color: var(--text); background: var(--panel-2); }

.nav-group-body .nav-link.active {
    color: var(--accent);
    background: var(--accent-soft);
    font-weight: 600;
}

.nav-group-body .nav-glyph { width: 1.05em; font-size: .95em; opacity: .6; }
.nav-group-body .nav-link.active .nav-glyph { opacity: 1; }

/* Orientation when the eye is on the content, not the section: the group whose child is current
   says so too. `:has` degrades to the ordinary header where it is unsupported. */
.nav-group:has(> .nav-group-body .nav-link.active) > .nav-group-head { color: var(--text); }
.nav-group:has(> .nav-group-body .nav-link.active) > .nav-group-head::after {
    content: "";
    inline-size: .35rem;
    block-size: .35rem;
    border-radius: 50%;
    background: var(--accent);
    margin-inline-start: auto;
}

/* The compact rail's group menu. The rail is selected by panel.js when the viewport is below
   1280px and no wider choice is stored, or when the operator has pinned compact at any width; the
   icon glyph only appears in that mode. In RTL the flyout naturally opens to the inline-end (left of
   a right-hand rail). */
.nav-group-glyph { display: none; }
.nav-group-flyout {
    display: none;
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 100%;
    z-index: 60;
    min-inline-size: 12.75rem;
    max-inline-size: 17rem;
    max-block-size: calc(100vh - 2rem);
    overflow: auto;
    padding: .4rem;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
}

.nav-flyout-title {
    padding: .3rem .55rem .45rem;
    color: var(--text-soft);
    font-size: .72rem;
    font-weight: 650;
    letter-spacing: .06em;
    text-transform: uppercase;
}

.nav-group-flyout .nav-link {
    justify-content: flex-start;
    padding: .4rem .55rem;
    color: var(--text-soft);
    font-size: .88rem;
}

.nav-group-flyout .nav-link:hover { color: var(--text); background: var(--panel-2); }
.nav-group-flyout .nav-link.active { color: var(--accent); background: var(--accent-soft); font-weight: 600; }
.nav-group-flyout .nav-glyph { width: 1.05em; opacity: .65; }
.nav-group-flyout .nav-link.active .nav-glyph { opacity: 1; }

.nav-flyout-scrim {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 45;
    background: transparent;
}

.sidebar-foot { margin-top: auto; padding: .6rem .35rem; border-top: 1px solid var(--line); }
.sidebar-foot-info { display: flex; flex-direction: column; gap: .15rem; }

.sidebar-toggle {
    display: flex;
    align-items: center;
    gap: .55rem;
    inline-size: 100%;
    margin-bottom: .45rem;
    padding: .45rem .5rem;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--text-soft);
    font: inherit;
    font-size: .82rem;
    text-align: start;
    cursor: pointer;
}

.sidebar-toggle:hover { background: var(--panel-2); color: var(--text); }
.sidebar-toggle-glyph { flex: none; inline-size: 1.1em; text-align: center; font-size: 1rem; }
.sidebar-toggle-compact { display: none; }

.content { display: flex; flex-direction: column; min-width: 0; }

.topbar {
    display: flex; align-items: center; justify-content: space-between; gap: 1rem;
    padding: .8rem 1.4rem;
    background: var(--bg-soft);
    border-bottom: 1px solid var(--line);
    position: sticky; top: 0; z-index: 5;
}

.topbar-title { color: var(--text-soft); font-size: .88rem; }
.topbar-actions { display: flex; align-items: center; gap: .6rem; }

.page { padding: 1.4rem; display: flex; flex-direction: column; gap: 1rem; }

/* ── controls ──────────────────────────────────────────────────────────── */

.btn {
    appearance: none;
    border: 1px solid var(--line);
    background: var(--panel-2);
    color: var(--text);
    padding: .45rem .85rem;
    border-radius: var(--radius-sm);
    font: inherit;
    font-size: .88rem;
    cursor: pointer;
    /* A button is one control, so its label is one line and its border is one box. In a narrow table
       column — the Overview's hub-error rows, the connection timeline's log links — a wrapping label
       turned one button into what read as two: an inline anchor draws its border around EACH line box,
       so "Server" over "logs" came out as two stacked pills. `inline-flex` gives the control a single
       box whatever happens, `nowrap` keeps the label on one line, and the cell then takes the width the
       label needs — the neighbouring text column is the one that wraps. */
    display: inline-flex;
    align-items: center;
    white-space: nowrap;
}

.btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn.ghost { background: transparent; }

/* Table-row actions stack their own links, and `.btn`'s nowrap above is what keeps
   each label on one line: a two-line "Controller / log" reads as two buttons. */

.field { display: flex; flex-direction: column; gap: .3rem; font-size: .82rem; color: var(--text-soft); }
.field.inline { flex-direction: row; align-items: center; gap: .5rem; }
.field.check { flex-direction: row; align-items: center; gap: .5rem; color: var(--text); }

.field input, .field select, .field textarea {
    background: var(--panel);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    padding: .42rem .55rem;
    font: inherit;
    font-size: .9rem;
    min-width: 0;
    /* A select asks for its widest option's width; capping it keeps the control inside the card
       whatever the locale's labels say. The list itself is drawn by the browser and cannot be styled,
       which is why the long sentences live under the control instead (ConnectionSettings.razor). */
    max-width: 100%;
}

.field input:focus, .field select:focus, .field textarea:focus { outline: 2px solid var(--accent); outline-offset: 0; }

.field-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: .8rem 1rem;
    align-items: end;
}

/* One row per choice, radio first (/connection-settings ICE policy). A <select> popup is sized and
   drawn by the browser — it cannot be wrapped, styled, or kept inside the card — and there it clipped
   its own option text, so the page lays the list out itself. Logical properties keep it right in both
   directions: the radio column sits on the inline-start edge whatever dir is on <html>. */

/* Capped, not stretched: four short choices across 1570px of card read as empty stripes. */
.field.policy-list { gap: .4rem; max-inline-size: 46rem; }
.policy-list > span { margin-bottom: .1rem; }

.policy-option {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: baseline;
    column-gap: .55rem;
    padding: .5rem .65rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--panel-2);
    cursor: pointer;
}

.policy-option:hover { border-color: var(--accent); }
.policy-option.selected { border-color: var(--accent); background: var(--accent-soft); }

/* Beats ".field input" (the text-field skin) without touching it: a radio is not a text box. */
.policy-option input[type="radio"] {
    background: none;
    border: 0;
    padding: 0;
    margin: .15rem 0 0;
    inline-size: 1rem;
    block-size: 1rem;
    align-self: start;
    accent-color: var(--accent);
}

.policy-option .policy-name { color: var(--text); }
.policy-option .policy-hint { grid-column: 2; color: var(--text-soft); font-size: .8rem; line-height: 1.45; }

/* ── layout blocks ─────────────────────────────────────────────────────── */

.page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.page-actions { display: flex; align-items: flex-end; gap: .5rem; flex-wrap: wrap; }

/* The connection page's single save bar. That page writes one hub_settings row, so one bar after both editable
   cards states the scope once — a Save inside a card would save more than the card shows, and the card with no
   button (the log answer) would look unsaveable. */
/* ".card.save-bar", not ".save-bar": the card rule's border shorthand comes later in this file and would
   reset the accent edge back to the ordinary 1px line. */
.card.save-bar {
    display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
    border-top: 2px solid var(--accent-soft);
}
.save-bar-text { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.save-bar .page-actions { align-items: center; }
.save-bar .kv { flex-basis: 100%; }

.card {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 1rem 1.1rem;
    box-shadow: var(--shadow);
    min-width: 0;
}

.card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; margin-bottom: .8rem; }

/* The one-time access code the owner reads out ("Show code" on /access-codes and /access-requests).
   The digits are the whole purpose of that moment, so they get a block of their own rather than a line
   in a paragraph — at the size the person on the other end can read across a room, in the mono face
   that keeps 0/O and 1/l apart. `user-select: all` makes a failed clipboard still copyable by hand. */
.otp-card { max-inline-size: 40rem; }
.otp-card h2 { margin-bottom: .25rem; }
.otp-card > p { margin-bottom: 0; }
.otp-value {
    margin: 1.1rem 0 .85rem;
    padding: 1.15rem 1rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--panel-2);
    color: var(--text);
    font-family: var(--mono);
    font-size: clamp(2rem, 5vw, 2.75rem);
    font-weight: 700;
    line-height: 1.15;
    /* The trailing letter-space would push the run off-centre; the indent gives it back. */
    letter-spacing: .35em;
    text-indent: .35em;
    text-align: center;
    user-select: all;
}
.otp-foot { display: flex; align-items: center; justify-content: space-between; gap: .8rem; flex-wrap: wrap; }

/* Two codes minted in one visit are two separate cards; without this they touch, because the page
   root is not a `.page` flex stack on every layout. */
.otp-card + .otp-card { margin-top: 1rem; }
.split { display: grid; grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); gap: 1rem; align-items: start; }

/* The administrators screen's page-permission grid (docs/method-call-flow/05-admin-panel.html §19).
   One row per page in catalogue order, so the list reads like the sidebar it grants. */
.page-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: .2rem 1rem; margin-bottom: .6rem; }
.page-grid .field.check { font-size: .86rem; }

/* The inline name/email/password editor on an account row, separated from the facts above it. */
.edit-account { border-top: 1px solid var(--line); margin-top: .9rem; padding-top: .8rem; }
.edit-account h3 { font-size: .92rem; margin: 0 0 .6rem; }

.empty {
    padding: 1rem;
    border: 1px dashed var(--line);
    border-radius: var(--radius-sm);
    color: var(--text-soft);
    text-align: center;
}

.alert { border-radius: var(--radius-sm); padding: .65rem .8rem; margin-bottom: .8rem; border: 1px solid; }
.alert.error { background: rgba(242, 104, 95, .12); border-color: var(--bad); }
.alert.warn { background: rgba(240, 180, 41, .12); border-color: var(--warn); }
/* The panel's own confirmations — an action that succeeded, or the one-time password-reset code.
   It shares the .alert box with the two failures so a page reports both the same way. */
.alert.success { background: rgba(53, 200, 138, .12); border-color: var(--ok); }

.health { display: inline-flex; align-items: center; gap: .5rem; font-size: .86rem; padding: .35rem .7rem; border-radius: 999px; border: 1px solid var(--line); background: var(--panel-2); }
.health .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-soft); }
.health.ok .dot { background: var(--ok); }
.health.stale { border-color: var(--warn); }
.health.stale .dot { background: var(--warn); }
.health.down { border-color: var(--bad); }
.health.down .dot { background: var(--bad); }

/* ── KPIs ──────────────────────────────────────────────────────────────── */

.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(185px, 1fr)); gap: .8rem; }

.kpi {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: .85rem .95rem;
    box-shadow: var(--shadow);
}

.kpi.warn { border-color: var(--warn); }
.kpi-label { color: var(--text-soft); font-size: .8rem; }
.kpi-value { font-size: 1.45rem; font-weight: 650; letter-spacing: -.02em; margin: .15rem 0; }
.kpi-hint { color: var(--text-soft); font-size: .78rem; }

/* ── tables ────────────────────────────────────────────────────────────── */

.table { width: 100%; border-collapse: collapse; background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.table th, .table td { padding: .55rem .7rem; text-align: start; vertical-align: top; border-bottom: 1px solid var(--line); }
.table thead th { background: var(--panel-2); font-size: .78rem; text-transform: uppercase; letter-spacing: .04em; color: var(--text-soft); font-weight: 600; white-space: nowrap; }
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr:hover { background: var(--panel-2); }
.table.compact th, .table.compact td { padding: .42rem .6rem; }
.table .num { text-align: end; font-variant-numeric: tabular-nums; }
.row-open { background: rgba(240, 180, 41, .08); }
.row-alert { background: rgba(242, 104, 95, .07); }
.chip.bad { color: var(--bad); border-color: var(--bad); }
.event-detail { max-width: 48rem; overflow-wrap: anywhere; margin-top: .25rem; }

.finding-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 1rem; }
.finding-count { font-size: 1.3rem; font-weight: 650; font-variant-numeric: tabular-nums; }
.finding.warn { border-color: var(--warn); }
.finding.bad { border-color: var(--bad); }
.finding.good .finding-count { color: var(--ok); }
.finding.warn .finding-count { color: var(--warn); }
.finding.bad .finding-count { color: var(--bad); }

.samples, .notes, .endpoints { margin: .5rem 0 0; padding-inline-start: 1.1rem; display: flex; flex-direction: column; gap: .25rem; }
.endpoints { list-style: none; padding-inline-start: 0; }

/* ── chips ─────────────────────────────────────────────────────────────── */

.chip { display: inline-block; padding: .12rem .5rem; border-radius: 999px; font-size: .76rem; border: 1px solid var(--line); background: var(--panel-2); white-space: nowrap; }
.chip.ok { color: var(--ok); border-color: var(--ok); }
.chip.warn { color: var(--warn); border-color: var(--warn); }
.chip.open { color: var(--accent); border-color: var(--accent); }
.chip.muted-chip { color: var(--text-soft); }

/* ── pager ─────────────────────────────────────────────────────────────── */

.pager { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .6rem .2rem; flex-wrap: wrap; }
.pager-info { display: flex; align-items: baseline; gap: .35rem; }
.pager-controls { display: flex; align-items: center; gap: .5rem; }

/* ── charts (dependency-free) ──────────────────────────────────────────── */

.chart { min-width: 0; }
.bars { display: grid; grid-template-columns: repeat(var(--bar-count), minmax(0, 1fr)); gap: 2px; align-items: end; height: 190px; }
.bar-col { display: flex; flex-direction: column; justify-content: flex-end; height: 100%; min-width: 0; }
.bar-rail { flex: 1; display: flex; align-items: flex-end; }
.bar { width: 100%; min-height: 2px; border-radius: 4px 4px 0 0; background: linear-gradient(180deg, var(--accent), #7c5cff); }
.bar-label { font-size: .62rem; color: var(--text-soft); text-align: center; padding-top: .25rem; white-space: nowrap; overflow: hidden; }
.chart-caption { margin-top: .5rem; }

.stacked-bar { display: flex; height: 14px; border-radius: 999px; overflow: hidden; border: 1px solid var(--line); }
.stacked-slice { height: 100%; }
.s0 { background: #4f8cff; } .s1 { background: #7c5cff; } .s2 { background: #35c88a; }
.s3 { background: #f0b429; } .s4 { background: #f2685f; } .s5 { background: #4bc8d8; }

.legend { list-style: none; margin: .7rem 0 0; padding: 0; display: flex; flex-direction: column; gap: .3rem; }
.legend li { display: flex; align-items: center; gap: .5rem; font-size: .85rem; }
.legend-label { flex: 1; }
.swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }

/* ── definition lists ──────────────────────────────────────────────────── */

.kv { display: grid; grid-template-columns: minmax(120px, auto) minmax(0, 1fr); gap: .35rem .9rem; margin: 0; }
.kv dt { color: var(--text-soft); font-size: .82rem; }
.kv dd { margin: 0; overflow-wrap: anywhere; }

/* A row-level toggle that must read as text, not as a second control: the actions column already
   carries the links that leave the page, and a third button-shaped item competes with them. */
.link-btn {
    appearance: none;
    border: 0;
    padding: 0;
    background: none;
    color: var(--text-soft);
    font: inherit;
    font-size: .82rem;
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}

.link-btn:hover { color: var(--accent); }

/* The numbers a verdict fired on (docs/session-problems-troubleshooting.md §2). Wrapped, not scrolled:
   a horizontal scrollbar inside a table cell hides the field that explains the verdict. */
.evidence {
    margin: 0;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    font-family: var(--mono, monospace);
    font-size: .8rem;
    color: var(--text-soft);
}

/* The expanded "Why" row belongs to the row above it, so it sits flat against it. */
.attempt-detail td { background: var(--panel-2); }

/* One verdict on a session page: signature, severity, cause, length. A flex row with wrapping, because
   the four items are optional except two and the longest (the timestamp) must fall to a second line
   rather than squeeze the signature. */
.verdict {
    display: flex;
    align-items: center;
    gap: .6rem;
    flex-wrap: wrap;
    padding: .55rem 0;
}

.verdict + .verdict { border-top: 1px solid var(--line); }

/* ── device log reader ────────────────────────────────────────────────── */

/* A log file is Latin/LTR text whatever language the panel speaks, so the reader pins the
   direction instead of inheriting an RTL layout (which would reorder punctuation per line). */
.log-view { background: var(--bg-soft); border: 1px solid var(--line); border-radius: var(--radius-sm);
    padding: .85rem 1rem; margin: 0; max-height: 62vh; overflow: auto; direction: ltr; text-align: start;
    font-family: var(--mono); font-size: .82rem; line-height: 1.55; white-space: pre; tab-size: 4; }
.log-view.wrap { white-space: pre-wrap; overflow-wrap: anywhere; }

/* ── filters ───────────────────────────────────────────────────────────── */

.filters { background: var(--panel-2); }

/* ── scroll affordances ────────────────────────────────────────────────────────────────────────
   One scrollbar for every surface in the panel that scrolls: the navigation rail, the compact
   rail's group flyout, and the log reader. A rounded 6px pill, inset by a transparent border so
   it floats inside the surface instead of hugging its edge — muted at rest, accent while the
   surface is in use, the same change of weight a row makes on hover. Colours come from the theme
   tokens, so light and dark need no separate rules.

   The pill needs ::-webkit-scrollbar. Modern Chromium keeps honouring that ONLY while it has not
   been given the standard scrollbar-width/scrollbar-color — set both and the pseudo-elements are
   ignored and the platform's own square bar comes back. The standard properties are therefore
   scoped to engines that have no such pseudo-element (Firefox), where they are the only lever
   there is: themed and thin, but square, because that engine cannot round a thumb. */
:is(.nav, .nav-group-flyout, .log-view) {
    /* Reserve the strip whether or not it is needed, so content does not jump sideways the first
       time a surface overflows (a rail gaining a group, a log growing past 62vh). */
    scrollbar-gutter: stable;
}

:is(.nav, .nav-group-flyout, .log-view)::-webkit-scrollbar {
    inline-size: 6px;
    block-size: 6px;
}

:is(.nav, .nav-group-flyout, .log-view)::-webkit-scrollbar-track,
:is(.nav, .nav-group-flyout, .log-view)::-webkit-scrollbar-corner {
    background: transparent;
}

:is(.nav, .nav-group-flyout, .log-view)::-webkit-scrollbar-thumb {
    border: 2px solid transparent;
    border-radius: 999px;
    background-color: var(--scroll-thumb);
    background-clip: content-box;
    transition: background-color .16s ease;
}

/* Reaching into the surface is "in use": the thumb takes the accent. */
:is(.nav, .nav-group-flyout, .log-view):hover::-webkit-scrollbar-thumb {
    background-color: var(--scroll-thumb-hover);
}

/* No ::-webkit-scrollbar here (Firefox): fall back to the standard properties. */
@supports not selector(::-webkit-scrollbar) {
    :is(.nav, .nav-group-flyout, .log-view) {
        scrollbar-width: thin;
        scrollbar-color: var(--scroll-thumb) transparent;
    }

    /* scrollbar-color is not animatable, so the hover change is a step — still one step, and the
       alternative is leaving the bar grey on an engine we cannot style further. */
    :is(.nav, .nav-group-flyout, .log-view):hover {
        scrollbar-color: var(--scroll-thumb-hover) transparent;
    }
}

/* One viewport, one scroll region. The sticky rail is exactly 100vh tall, so a navigation list
   taller than the window used to keep painting its rows — and their hover/active fill — past the
   rail's own box and over the divider, which is what made the last group look broken on a short
   window (the strip behind was already fixed with .shell::before; the interactive surface was
   not). Scrolling the nav inside the rail contains both. Compact mode is excluded on purpose: a
   group flyout is meant to leave the rail, and clipping the nav would clip it. */
@media (min-width: 901px) {
    html:not([data-sidebar="compact"]) .nav {
        flex: 1 1 auto;
        min-block-size: 0;
        overflow-y: auto;
        overscroll-behavior: contain;
        /* Room for the focus ring of the last row, which a scroll container would otherwise clip. */
        padding-block-end: .25rem;
    }
}

@media (min-width: 901px) {
    /* The checkbox for the compact rail is the root data attribute, not this media query: the
       script owns the 1280px default and the explicit override. This query only keeps the existing
       wrapping navigation below 901px, where a horizontal top strip is the right layout. */
    html[data-sidebar="compact"] .shell { --sidebar-width: 76px; }
    html[data-sidebar="compact"] .sidebar { padding-inline: .6rem; align-items: center; gap: .8rem; }
    html[data-sidebar="compact"] .brand { inline-size: 100%; justify-content: center; padding-inline: 0; }
    html[data-sidebar="compact"] .brand-text { display: none; }

    html[data-sidebar="compact"] .nav { inline-size: 100%; align-items: center; }
    html[data-sidebar="compact"] .nav > .nav-link { inline-size: 100%; justify-content: center; padding-inline: 0; }
    html[data-sidebar="compact"] .nav > .nav-link .nav-link-text,
    html[data-sidebar="compact"] .nav-group-label { display: none; }

    html[data-sidebar="compact"] .nav-group { position: relative; inline-size: 100%; margin-top: .65rem; }
    html[data-sidebar="compact"] .nav-group:first-of-type { margin-top: .35rem; }
    html[data-sidebar="compact"] .nav-group-head { inline-size: 100%; justify-content: center; padding: .6rem 0; }
    html[data-sidebar="compact"] .nav-caret { display: none; }
    html[data-sidebar="compact"] .nav-group-glyph { display: block; font-size: 1.15rem; line-height: 1; opacity: .8; }
    html[data-sidebar="compact"] .nav-group:has(> .nav-group-body .nav-link.active) > .nav-group-head::after { display: none; }
    html[data-sidebar="compact"] .nav-group-body { display: none; }

    /* A flyout is opened by the group button, not by hover: a clicked page must be able to close
       the menu immediately, while a hover-only panel would stay under the pointer after navigation. */
    html[data-sidebar="compact"] .nav-group.flyout-open > .nav-group-flyout { display: block; }
    html[data-sidebar="compact"] .nav-flyout-scrim { display: block; }

    /* The bottom group opens upwards, so the last section is not cut off by the viewport. */
    html[data-sidebar="compact"] .nav-group:last-child .nav-group-flyout { inset-block-start: auto; inset-block-end: 0; }

    html[data-sidebar="compact"] .sidebar-foot { inline-size: 100%; padding: .6rem 0; }
    html[data-sidebar="compact"] .sidebar-foot-info { display: none; }
    html[data-sidebar="compact"] .sidebar-toggle { justify-content: center; margin-bottom: 0; padding: .55rem 0; }
    html[data-sidebar="compact"] .sidebar-toggle-text { display: none; }
}

@media (max-width: 900px) {
    .shell { --sidebar-width: 0px; grid-template-columns: 1fr; }
    .shell::before { display: none; }
    .sidebar { position: static; height: auto; border-inline-end: 0; border-bottom: 1px solid var(--line); }
    .nav { flex-direction: row; flex-wrap: wrap; }
    .sidebar-foot { display: none; }
    .page, .topbar { padding-inline: .9rem; }
}

/* ── stats surfaces (docs/admin-stats-plan.md §7) ──────────────────────────────────────────────── */

/* The weekday × hour grid. Empty cells are drawn, not omitted: "nothing at 3 a.m." is information. */
.heatmap { display: grid; gap: 3px; }
.heatmap-row { display: grid; grid-template-columns: 30px repeat(24, 1fr); gap: 3px; align-items: center; }
.heatmap-day { font-family: var(--mono); font-size: .68rem; color: var(--text-soft); }
.heatmap-cell {
    display: block; height: 13px; border-radius: 3px;
    background-color: var(--panel-2);
    background-image: linear-gradient(rgba(79, 140, 255, var(--a, 0)), rgba(79, 140, 255, var(--a, 0)));
}
.heatmap-hours span { font-family: var(--mono); font-size: .62rem; color: var(--text-soft); }

/* A ranked bar: the ORDER is the answer, so the bar carries it and the number confirms it. */
.ranked { display: grid; gap: .4rem; }
.ranked-row { display: grid; grid-template-columns: minmax(0, 1fr) 110px 46px; gap: .6rem; align-items: center; }
.ranked-name { font-size: .85rem; color: var(--text-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ranked-track { height: 7px; border-radius: 4px; background: var(--panel-2); overflow: hidden; }
.ranked-track i { display: block; height: 100%; border-radius: 4px; background: linear-gradient(90deg, var(--accent), var(--sid-controller)); }
.ranked-value { font-family: var(--mono); font-size: .75rem; text-align: end; }

/* A trend inside a table row: the axis is omitted, the tooltip is not. */
.sparkline { display: block; width: 120px; height: 24px; }

/* A duration histogram: buckets, not a mean. */
.histogram { display: flex; align-items: flex-end; gap: .55rem; height: 120px; }
.histogram-col { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; height: 100%; gap: .25rem; }
.histogram-bar { display: block; width: 100%; border-radius: 4px 4px 0 0; background: linear-gradient(180deg, var(--accent), var(--accent-soft)); min-height: 2px; }
.histogram-count { font-family: var(--mono); font-size: .68rem; color: var(--text-soft); }
.histogram-label { font-family: var(--mono); font-size: .66rem; color: var(--text-soft); }

/* One stacked bar per week: the shape of a machine's health over time. */
.health-weeks { display: grid; gap: .55rem; }
.health-week { display: grid; grid-template-columns: 42px 1fr; gap: .6rem; align-items: center; }
.health-week-label { font-family: var(--mono); font-size: .7rem; color: var(--text-soft); }

/* A single-series area: one unit, one shape. */
.area-chart { display: block; width: 100%; height: 140px; }

/* A queue against the gate it is measured by (send gates): two series, one unit, so "at the gate"
   becomes a shape. The gate is dashed and muted — reference, not a second measurement. */
.gate-chart { display: block; width: 100%; height: 110px; margin-top: .45rem; }
.gate-chart-axis { stroke: var(--line); stroke-width: 1; }
.gate-chart-buffered { fill: none; stroke: var(--accent); stroke-width: 1.8; stroke-linejoin: round; }
.gate-chart-gate { fill: none; stroke: var(--warn); stroke-width: 1.2; stroke-dasharray: 4 3; opacity: .8; }
.gate-chart-legend { display: flex; align-items: center; gap: .4rem; margin: .2rem 0 0; }
.gate-chart-key { display: inline-block; width: 14px; border-top: 2px solid var(--accent); }
.gate-chart-key.gate { border-top-color: var(--warn); border-top-style: dashed; }

/* ── Updates page (docs/auto-update-plan.md §16) ──────────────────────────────
   The uploader's own band. A drop target that does not change on dragover has
   been missed by every person who ever tried it, so the class is toggled by
   update-upload.js and only the border moves. */
.dropzone {
    margin: .8rem 0;
    padding: 1.1rem;
    border: 1px dashed var(--line);
    border-radius: var(--radius-sm);
    color: var(--text-soft);
    text-align: center;
    transition: border-color .12s, color .12s;
}

.dropzone.over { border-color: var(--accent); color: var(--accent); }

/* The release's single bar. The fill's width is a percentage the script sets, so the browser does
   the layout and the shape stays the same in both themes. */
.upload-track {
    height: 6px;
    margin: 1rem 0 .35rem;
    border-radius: 999px;
    background: var(--accent-soft);
    overflow: hidden;
}

.upload-fill {
    width: 0;
    height: 100%;
    border-radius: 999px;
    background: linear-gradient(90deg, var(--accent), #7c5cff);
    transition: width .15s linear;
}

#updOverallText { margin: 0 0 .2rem; }

#updList { margin: .4rem 0 .8rem; padding-inline-start: 1.1rem; }
#updList li { font-size: .82rem; }
#updDiff ul { margin: .3rem 0 .8rem; padding-inline-start: 0; list-style: none; }
#updDiff .field.check { font-size: .82rem; }

/* The review a draft in progress shows before it is committed: indented under its row, because it
   belongs to one upload and must not read as a second section of the page. */
.review { margin: .5rem 0 .2rem; padding-inline-start: .9rem; border-inline-start: 2px solid var(--line); }
.review ul { margin: .3rem 0 .7rem; padding-inline-start: 1rem; }
.review li { font-size: .82rem; }

/* ── Templates page ───────────────────────────────────────────────────────────
   The expanded row is a SUNKEN surface, so it reads as part of its row on both
   themes. An earlier inline fallback (#f8fafc) painted a white slab in dark mode. */
.template-row-body {
    background: var(--bg);
    border-top: 1px solid var(--line);
}

.template-row-body .table.compact { background: transparent; }

/* A transient confirmation. role="status" + aria-live="polite" announces it without
   moving focus, and the page clears it after a few seconds (see TemplatesPage.razor). */
.toast {
    position: fixed;
    inset-inline-end: 1.25rem;
    inset-block-end: 1.25rem;
    z-index: 60;
    max-width: min(26rem, calc(100vw - 2.5rem));
    padding: .7rem .9rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--bg-soft);
    box-shadow: 0 10px 28px rgba(0, 0, 0, .22);
    font-size: .9rem;
    line-height: 1.5;
}
