@import url('tokens.css');

/* ============================================================================
   AppShell — the in-process web shell frame (ADR 0031). A Blazor layout
   over the nav pages: title bar + 64-px rail + status bar + content.
   The surface ramp (app < rail/frame < content < card) uses the canonical --color-* surface
   tokens. Raw px is used ONLY for structural
   dimensions (rail width, band heights, dot sizes); every colour / font-size / radius
   / spacing is a var(--…) token. shell- prefix (no collision with the dash- or dnav- ones).
   ============================================================================ */
.shell-root {
    display: flex;
    flex-direction: column;
    height: 100vh;
    overflow: hidden;
    background: var(--color-bg);
}

/* ---- Title bar -------------------------------------------------- */
.shell-titlebar {
    flex: 0 0 auto;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 var(--space-4);
    background: var(--color-frame);
    border-bottom: 1px solid var(--color-stroke-subtle);
}

/* Hosted (Avalonia/CEF shell): the OS draws the window's title bar (caption + min/max/close), so the
   web title bar is dropped and its theme + user-chip controls are SEATED into the window's top-right
   corner as a piece of shell frame, just below the OS caption buttons (Windows host). A remote browser
   lacks .host-shell and keeps the full title bar above. The .shell-fullscreen rule (later in this sheet)
   still hides the cluster in the dashboard's fullscreen mode. */
.host-shell .shell-titlebar { display: contents; }     /* drop the bar box and reclaim its height */
.host-shell .shell-brand { display: none; }

/* The corner dock: same matte --color-frame as the rail + status bar, flush to the top and right
   window edges, squared outward and rounded only on the one INNER corner — mirroring the content
   panel's top-left --radius-lg so the content reads as one panel nested inside a frame. A
   hairline on just the two inner edges draws the seam, like the seams under the status bar and beside
   the rail. No shadow, no blur: it belongs to the frame, it does not float above it. Rail (left) +
   this dock (top-right) + status bar (bottom) are three cuts of one continuous frame. */
.host-shell .shell-titlebar-right {
    position: fixed;
    top: 0;
    right: 0;
    z-index: var(--z-popover);
    padding: var(--space-2) var(--space-3) var(--space-2) var(--space-4);
    background: var(--color-frame);
    border-left: 1px solid var(--color-stroke-subtle);
    border-bottom: 1px solid var(--color-stroke-subtle);
    border-bottom-left-radius: var(--radius-lg);
}

.shell-brand {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
}

.shell-brand-mark { width: 18px; height: 18px; flex: 0 0 auto; }

.shell-brand-name {
    font-size: var(--font-size-card-title);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    letter-spacing: -0.01em;  /* wordmark treatment: the brand's slight negative tracking (brand.md) */
}

.shell-titlebar-right {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex: 0 0 auto;
}

/* ---- Theme keys ------------------------------------------------
   The station's theme (ADR 0030 §8) as ONE segmented control rather than three loose buttons: a
   choice among three is not three commands, and the shape has to say so before the marks do. The
   keys sit in a bordered group that lets the frame show through, and the picked one is raised out
   of it onto the card surface — which lifts in BOTH themes (white over the light frame, a lighter
   grey over the near-black one), where the page's well tone would land the wrong way round on this
   band. The group stands 30px tall, the height the chip and the frame icon buttons hold, so the
   whole cluster lines up on one baseline. */
.shell-theme-choice {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-1);
    border: 1px solid var(--color-stroke);
    border-radius: var(--radius-md);
}

.shell-theme-key {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 24px;
    padding: 0;
    background: transparent;
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: transform var(--dur-press) var(--ease-out);
}

/* The glyph carries its own ink (.ui-glyph), so a key's states have to reach it directly — the
   same way the rail tints its own marks. Idle is the secondary ink the glyph already wears. */
.shell-theme-key:hover { background: var(--color-overlay-hover); }
.shell-theme-key:hover .ui-glyph { color: var(--color-text); }
.shell-theme-key:active { transform: scale(0.97); }

/* The picked face hangs off the same attribute assistive technology reads, so the picture and the
   announcement can never disagree — and both follow the STORED preference, which is what keeps the
   mark on System while System resolves to light or dark. */
.shell-theme-key[aria-pressed="true"] {
    background: var(--color-surface);
    box-shadow: var(--shadow-card);
}
.shell-theme-key[aria-pressed="true"] .ui-glyph { color: var(--color-text); }

/* The global focus ring is pulled inside the key: at this pitch an outward ring would cross the
   neighbouring key and the group's own border. */
.shell-theme-key:focus-visible { outline-offset: calc(-1 * var(--focus-ring-width)); }

/* Touch (remote tablets, no hover): these keys are adjacent, so they cannot each take the isolated
   44px square the lone frame buttons take below — three overlapping zones would leave the middle
   key unreachable. Each key claims the full height of the bar instead and stops at its own edge, so
   no two zones ever cover the same pixel, and the compact visual box is unchanged. */
@media (hover: none) {
    .shell-theme-key { position: relative; }
    .shell-theme-key::after {
        content: "";
        position: absolute;
        top: 50%;
        left: 50%;
        width: 100%;
        height: 44px;
        transform: translate(-50%, -50%);
    }
}

/* Hairline separator between the theme keys and the user chip — two distinct-stakes actions
   (choose the theme vs. open the sign-in dialog), so a toolbar divider (plus the --space-3 gap on
   each side) keeps them from being mis-clicked. 18px tall to echo the rail's active marker. */
.shell-titlebar-sep {
    flex: 0 0 auto;
    width: 1px;
    height: 18px;
    background: var(--color-stroke-subtle);
}

/* Generic compact frame icon button (snackbar / feed dismiss). */
.shell-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    background: transparent;
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
    padding: 0;
    color: var(--color-text-secondary);
    transition: transform var(--dur-press) var(--ease-out);
}
.shell-icon-btn:hover { background: var(--color-overlay-hover); color: var(--color-text); }

/* Touch (remote tablets, no hover): a 44px centered hit area (WCAG 2.5.5) without changing
   the compact 30px visual box. These frame icon buttons (snackbar / feed dismiss)
   are isolated, so the expanded tap zones don't overlap each other. */
@media (hover: none) {
    .shell-icon-btn { position: relative; }
    .shell-icon-btn::after {
        content: "";
        position: absolute;
        top: 50%;
        left: 50%;
        width: 44px;
        height: 44px;
        transform: translate(-50%, -50%);
    }
}
.shell-icon-btn:active { transform: scale(0.97); }

/* User chip — the title-bar button that opens the switch-user dialog (UserSwitchDialog). */
.shell-userchip-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    height: 30px;
    padding: 0 var(--space-2);
    background: transparent;
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
    color: var(--color-text-secondary);
    transition: transform var(--dur-press) var(--ease-out);
}
.shell-userchip-btn:hover { background: var(--color-overlay-hover); color: var(--color-text); }
.shell-userchip-btn:active { transform: scale(0.97); }

.shell-userchip-name {
    font-size: var(--font-size-description);
    font-weight: var(--font-weight-medium);
    max-width: 130px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Signed-in dot on the title-bar person glyph — connection identity for this circuit, separate
   from the exceptional license-attention mark on the Account rail entry. */
.shell-userchip-avatar { position: relative; display: inline-flex; }
.shell-userchip-dot {
    position: absolute;
    top: -2px;
    right: -3px;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--color-neutral);
    border: 1px solid var(--color-frame);
}
.shell-userchip-dot.on { background: var(--color-success); }

/* View login (ADR 0029/0030 §7) — the router's NotAuthorized surface for a remote circuit that
   still owes remote access: a full-viewport opaque backdrop (it must cover the shell frame behind
   it) with a centered station instrument face — an engraved header strip (brand mark + wordmark)
   over the shared sign-in body (SignInPanel: operator roster + PIN keypad). The roster / keypad /
   token styles are shared with the switch-user dialog and live in shared.css. */
.view-login-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--color-bg);
}

.view-login-card {
    width: min(380px, calc(100vw - 2 * var(--space-4)));
    background: var(--color-surface);
    border: 1px solid var(--color-stroke);
    border-radius: var(--radius-lg);
    overflow: hidden;                 /* clip the header strip to the rounded corners */
    box-shadow: var(--shadow-card);
}

/* Engraved header strip — the recessed instrument band carrying the brand identity. It is a band
   INSIDE a card, so it takes the well tone rather than --color-frame: the frame is the shell's own
   chrome (rail / title / status), and wearing it here would claim this strip is part of the window. */
.view-login-header {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5);
    background: var(--color-surface-alt);
    border-bottom: 1px solid var(--color-stroke);
}

.view-login-mark { width: 18px; height: 18px; flex: 0 0 auto; }

.view-login-wordmark {
    font-size: var(--font-size-card-title);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    letter-spacing: -0.01em;          /* the brand's slight negative tracking (brand.md) */
}

.view-login-tag {
    margin-left: auto;
    font-size: var(--font-size-eyebrow);
    font-weight: var(--font-weight-medium);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-text-secondary);
}

.view-login-body {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding: var(--space-5);
}

.view-login-subtitle {
    font-size: var(--font-size-body);
    color: var(--color-text-secondary);
}

/* Framed refusal (ADR 0035) — on the published demonstration the router draws this surface inside
   the shell layout, so it stops covering the viewport and becomes the page's own content: the
   title bar and the rail stay on screen around it, and the card keeps its centering. */
.view-login-backdrop.framed {
    position: static;
    z-index: auto;
    min-height: 100%;
    padding: var(--space-6);
    background: transparent;
}

/* The way off a page this role does not open. It sits under the roster, centered and secondary:
   the card's business is signing in, and this is only the door back to a screen that does open. */
.view-login-back { align-self: center; }

/* ---- Stopped-runtime band --------------------------------------------------
   The single announcement that the runtime is stopped, a frame band above the rail and the
   content so every route carries it on both hosts. Soft amber, not alarm red: the consequence is
   real (nothing is being read) but a restart is one click and unlimited, so the surface informs
   without frightening. Everything is left-aligned — on the hosted shell the fixed theme + chip
   dock occupies the top-right corner, and the band reserves that structural width for it. */
.shell-runtime-band {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
    padding: var(--space-3) var(--space-5);
    background: var(--color-caution-bg);
    border-bottom: 1px solid var(--color-caution);
}

.host-shell .shell-runtime-band { padding-right: 240px; }  /* clear the fixed corner dock */

.shell-runtime-band-title {
    font-size: var(--font-size-description);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
}

.shell-runtime-band-detail,
.shell-runtime-band-instruction {
    font-size: var(--font-size-description);
    color: var(--color-text-secondary);
}

/* The restart stays NEUTRAL inside the amber band, at rest and on hover: it is safe, immediate and
   unlimited, so nothing about it may read as the thing to avoid. Fill and hover are the shared
   default button's — opaque, so the band never shows through — and only the metrics are local: the
   band's height, and the weight of the one offered action. */
.shell-runtime-restart {
    min-height: 28px;
    padding-inline: var(--space-4);
    font-weight: var(--font-weight-semibold);
}

/* ---- Body: rail + content -------------------------------------------------- */
.shell-body {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
}

/* ---- Navigation rail ------------------------------------------- */
.shell-rail {
    flex: 0 0 64px;
    width: 64px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    background: var(--color-frame);
    padding: var(--space-2) 0;
    overflow-y: auto;
    overflow-x: hidden;
}

.shell-rail-top,
.shell-rail-footer {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: 0 var(--space-2);
}

.shell-rail-item {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-3) 0;
    border: none;
    border-radius: var(--radius-md);
    background: transparent;
    cursor: pointer;
    color: var(--color-text-secondary);
    transition: background var(--dur-quick) var(--ease-out), color var(--dur-quick) var(--ease-out);
}
.shell-rail-item:hover {
    background: var(--color-overlay-hover);
    color: var(--color-text);
}
.shell-rail-item:hover .ui-glyph,
.shell-rail-item:hover .shell-rail-brand-mark,
.shell-rail-item:hover .shell-rail-label { color: var(--color-text); }

/* Selected wins over hover: accent bar + tinted icon, tertiary fill. */
.shell-rail-item.active {
    background: var(--color-surface);
    color: var(--color-text);
}
.shell-rail-item.active::before {
    content: '';
    position: absolute;
    left: 0;
    top: 50%;
    height: 18px;
    transform: translateY(-50%);
    width: 3px;
    border-radius: var(--radius-pill);
    background: var(--color-accent);
}
.shell-rail-item.active .ui-glyph,
.shell-rail-item.active .shell-rail-brand-mark { color: var(--color-accent-text); }
.shell-rail-item.active .shell-rail-label { color: var(--color-text); }

.shell-rail-glyph-wrap { position: relative; display: inline-flex; }

/* The Account rail item's brand G (inline SVG, not a font glyph). Like .ui-glyph it carries no own
   colour: stroke:currentColor inherits the rail colour rules above, so it tints with the item
   (secondary idle, text on hover, accent when active). */
.shell-rail-brand-mark { width: 24px; height: 24px; display: block; }

/* The rail is 64 px wide whatever the station's language, and the same entry is "Agent" in one and
   "Agentenzugriff" in another. A word with no space in it has nowhere to break, so it ran past the
   item and the rail, which hides its overflow, cut it at BOTH ends. It breaks inside the word
   instead — at a hyphenation point where the language has one, since the page declares its lang,
   and anywhere otherwise — and the two lines come out even rather than leaving one character alone
   on the second, which is what the ideographic labels did. */
.shell-rail-label {
    font-size: var(--font-size-micro);
    color: var(--color-text-secondary);
    text-align: center;
    line-height: 1.1;
    max-width: 100%;
    hyphens: auto;
    overflow-wrap: anywhere;
    text-wrap: balance;
}

/* Account license attention: absent while healthy/neutral, a static ! only for an actionable
   billing or seat condition. The button owns the accessible diagnosis; this glyph is decorative. */
.shell-license-attention {
    position: absolute;
    top: -5px;
    right: -7px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    border: 1px solid var(--color-frame);
    font-size: 11px;
    font-weight: var(--font-weight-semibold);
    line-height: 12px;
    text-align: center;
}
.shell-license-attention.caution {
    color: var(--color-on-caution);
    background: var(--color-caution);
}
.shell-license-attention.critical {
    color: var(--color-text-on-accent);
    background: var(--color-critical-fill);
}

/* Validation broken-reference count: a caution pill on the rail glyph's top-right. The ink is the
   one caution states for itself, which is what makes the number readable on it: white on this
   yellow is 2.6:1 in the light theme, and the pill beside it (.shell-license-attention.caution)
   already reads its own pair. */
.shell-rail-count {
    position: absolute;
    top: -5px;
    right: -8px;
    min-width: 15px;
    height: 15px;
    padding: 0 3px;
    border-radius: 8px;
    background: var(--color-caution);
    border: 1px solid var(--color-frame);
    color: var(--color-on-caution);
    font-size: var(--font-size-micro);
    font-weight: var(--font-weight-semibold);
    line-height: 13px;
    text-align: center;
}

/* ---- Content region ------------------------------------------------- */
.shell-content {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    position: relative;
    display: grid;
    grid-template-rows: auto minmax(0, 1fr);
    overflow: hidden;
    background: var(--color-bg);
    border-top-left-radius: var(--radius-lg);
    border-left: 1px solid var(--color-stroke);
    border-top: 1px solid var(--color-stroke);
}

/* The page roots assume the full viewport (height:100vh); inside the shell
   they must fill the content region instead. Constrain them to 100% so the title +
   status bands keep their space and the page scrolls within. */
.shell-content > .ui-page,
.shell-content > .ui-md,
.shell-content > .log-page,
.shell-content > .nf-page,
.shell-content > .dnav-page {
    grid-row: 2;
    min-height: 0;
    height: 100%;
    border-top-left-radius: var(--radius-lg);
}

/* The paused page: what stands where a route's page would be while the runtime is stopped. It is
   NOT a second warning — the band above the frame says it once — so it stays quiet: no tint, no
   border, just a still mark and the explanation centred in the empty content region. */
.shell-runtime-paused {
    grid-row: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-7);
    overflow: auto;
}

/* Composed on the shared .ui-empty-state (centring, gap, the muted glyph ink), so this card only
   sets its measure and the type each line carries. */
.shell-runtime-paused-card {
    max-width: 46ch;
}

.shell-runtime-paused-title {
    margin: 0;
    font-size: var(--font-size-card-title);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
}

.shell-runtime-paused-body {
    margin: 0;
    font-size: var(--font-size-body);
    line-height: 1.5;
    color: var(--color-text-secondary);
}

.shell-runtime-paused-note {
    margin: 0;
    font-size: var(--font-size-description);
    color: var(--color-text-secondary);
}

/* ---- Snackbar host --------------------------------------------- */
/* The stack keeps a few cards at once and the rest wait behind one card that counts them, so the
   height below is the belt for an unusually tall card rather than the rule: it holds the stack
   inside the content area instead of letting it run off the top with the commands of the oldest
   cards on it. column-reverse fills from the anchor upwards, so what a bound clips is the far end,
   and the counting card leads the list precisely so it is never the one clipped. Nothing here
   scrolls: a command reached by scrolling in a flood is a command not reached. */
.shell-snackbars {
    position: absolute;
    right: var(--space-7);
    bottom: var(--space-8);
    display: flex;
    flex-direction: column-reverse;
    gap: var(--space-3);
    max-width: 460px;
    max-height: calc(100% - var(--space-8) - var(--space-7));
    overflow: hidden;
    z-index: var(--z-toast);
}

.shell-snackbar {
    display: flex;
    align-items: stretch;
    background: var(--color-surface);
    border: 1px solid var(--color-stroke);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-popover);
    overflow: hidden;
}

.shell-snackbar-accent {
    flex: 0 0 4px;
    background: var(--color-accent);
}
.shell-snackbar-warning .shell-snackbar-accent { background: var(--color-caution); }
.shell-snackbar-error .shell-snackbar-accent { background: var(--color-critical); }

.shell-snackbar-main {
    flex: 1 1 auto;
    min-width: 0;
    padding: var(--space-4) var(--space-3) var(--space-4) var(--space-4);
}

.shell-snackbar-title {
    font-size: var(--font-size-small);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    margin-bottom: var(--space-1);
}

.shell-snackbar-msg {
    font-size: var(--font-size-small);
    color: var(--color-text-secondary);
    line-height: 1.4;
}

.shell-snackbar-actions {
    display: flex;
    gap: var(--space-2);
    margin-top: var(--space-3);
}
.shell-snackbar-action { padding: var(--space-1) var(--space-3); }

.shell-snackbar-dismiss { align-self: flex-start; margin: var(--space-2) var(--space-2) 0 0; }

/* ---- Status bar ------------------------------------------------ */
.shell-statusbar {
    flex: 0 0 auto;
    height: 32px;
    display: flex;
    align-items: center;
    gap: var(--space-6);
    padding: 0 var(--space-5);
    background: var(--color-frame);
    border-top: 1px solid var(--color-stroke-subtle);
}

/* ---- Action feed — the elastic middle of the status bar ----------
   The slot is what used to be the plain spacer, so the left indicator group and the clock keep
   the exact places they hold when nothing has happened. min-width:0 lets the chip ellipsise
   instead of pushing the clock; the entry is right-aligned so results always appear at the same
   edge, immediately left of the clock. */
.shell-status-feed {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    justify-content: flex-end;
}

.shell-feed {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
    max-width: 100%;
    padding: 2px var(--space-3);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    font-size: var(--font-size-description);
    color: var(--color-text-secondary);
}

.shell-feed-text {
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* A failure is the one entry that waits: tinted, marked, and kept until it is dismissed or
   replaced. The tint is the only semantic colour the bar gains — no fill, no flashing. */
.shell-feed.error {
    color: var(--color-critical);
    border-color: var(--color-critical);
    font-weight: var(--font-weight-semibold);
}

/* The one follow-up an entry may offer ("Removed X · Undo"). It reads as the next word of the
   sentence, not as a second control in the bar: no chrome, the entry's own type (controls
   inherit it; base.css), the accent colour, and a middot that binds it to the outcome it
   answers. The separator is an inline-block so the link underline stops at the verb instead
   of running through it. */
.shell-feed-action {
    flex: 0 0 auto;
    background: transparent;
    border: none;
    padding: 0;
    cursor: pointer;
    color: var(--color-accent-text);
    text-decoration: underline;
    text-underline-offset: 2px;
    white-space: nowrap;
}

.shell-feed-action::before {
    content: "·";
    display: inline-block;
    margin-right: var(--space-2);
    color: var(--color-text-secondary);
}

.shell-feed-action:hover { color: var(--color-text); }

.shell-feed-dismiss {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    color: inherit;
}
.shell-feed-dismiss:hover { color: inherit; }

/* Every indicator on the bar shrinks rather than pushing the clock and the version off the end: a
   flex item's default min-width is its content, so without min-width:0 a long device name in the
   health line simply overflowed and the shell's overflow:hidden cut off everything after it. The
   feed beside them already behaved this way; now the whole bar does. */
.shell-status-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 0;
    color: var(--color-text-secondary);
}
.shell-status-btn:hover { color: var(--color-text); }

.shell-status-text {
    min-width: 0;
    font-size: var(--font-size-description);
    color: var(--color-text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.shell-alarm-critical .shell-status-text { color: var(--color-critical); }
.shell-alarm-caution .shell-status-text { color: var(--color-caution); }
.shell-alarm-attention .shell-status-text { color: var(--color-accent-text); }

.shell-recording.held .shell-status-text { color: var(--color-caution); }

/* The new-release mark: quiet by construction. It carries the bar's own secondary ink like every
   other indicator, and a release with important fixes takes the caution tone in the same mark
   rather than growing a second, louder one. */
.shell-update-important .shell-status-text { color: var(--color-caution); }

.shell-license {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
}

/* An indicator with nowhere to go: the same line and the same spacing as the pressable one, drawn
   as plain text for an identity whose role does not open the page the press would lead to. The bar
   still says what it knows; it only stops offering a door that would answer with a sign-in card. */
.shell-status-still {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
}

/* The stopped state's one status-bar message, standing where health/alarm would be. It is a
   link, not a warning: underlined on hover so its lead-somewhere role is discoverable. */
.shell-status-lead:hover .shell-status-text {
    color: var(--color-accent-text);
    text-decoration: underline;
}

.shell-clock {
    font-size: var(--font-size-description);
    color: var(--color-text-secondary);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;  /* second-by-second clock — hold the column still */
}

.shell-version {
    font-size: var(--font-size-eyebrow);
    color: var(--color-text-secondary);
    white-space: nowrap;
}

/* ---- Fullscreen: hide title/rail; the selected Dashboard decides whether the one
   existing status bar stays in normal flow below the canvas. --------------------- */
.shell-fullscreen .shell-titlebar,
.shell-fullscreen .shell-rail { display: none; }

.shell-fullscreen:not(.shell-fullscreen-statusbar) .shell-statusbar { display: none; }

.shell-fullscreen .shell-content {
    border: none;
    border-radius: 0;
}
.shell-fullscreen .shell-content > .dnav-page { border-radius: 0; }

/* ---- Circuit-loss banner (App.razor #blazor-error-ui) ---------------------- */
/* Hidden until blazor.web.js shows it when the circuit dies. Calm consequence
   surface: a quiet bottom band that informs and offers the reload. */
#blazor-error-ui {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 3000;
    padding: var(--space-2) var(--space-4);
    background: var(--color-surface);
    color: var(--color-text);
    border-top: 1px solid var(--color-stroke);
    font-size: var(--font-size-description);
    box-shadow: var(--shadow-sheet);
}

#blazor-error-ui .reload {
    margin-left: var(--space-3);
    color: var(--color-accent-text);
}

#blazor-error-ui .dismiss {
    float: right;
    cursor: pointer;
    color: var(--color-text-secondary);
}

/* ---- Reconnection surface (App.razor #components-reconnect-modal) ---------
   THE `components-reconnect-*` NAMES BELOW ARE THE BLAZOR FRAMEWORK'S (blazor.web.js ·
   UserSpecifiedDisplay), not ours: because the host page declares the element the framework
   looks for, the framework drives it and toggles exactly ONE state class on it per state —
   show (+ retrying while a countdown runs), paused, resume-failed, failed, rejected, hide.
   Nothing here matches until it does, so the surface is inert while the connection holds. Every
   state that means the connection is gone must therefore appear twice below: once in the rule
   that raises the surface (a state missing there leaves a frozen page with no explanation on it)
   and once in the rule that shows its block (missing there, an empty card). On a framework
   upgrade, re-read UserSpecifiedDisplay for these names.

   Blocking by design: the page behind is stale the moment the station is out of reach. This is a
   different event from the stopped runtime, and deliberately the same voice — the station named
   as the computer running Ganter Lab, and the reassurance that nothing recorded is lost. */
#components-reconnect-modal {
    display: none;
    position: fixed;
    inset: 0;
    /* One step above the circuit-loss banner: a dead circuit raises both, and this is the one
       that carries the whole answer. */
    z-index: 3001;
    align-items: center;
    justify-content: center;
    padding: var(--space-6);
    background: var(--color-scrim);
}

#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-paused,
#components-reconnect-modal.components-reconnect-resume-failed,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
    display: flex;
}

.reconnect-card {
    width: min(420px, 92vw);
    padding: var(--space-7);
    background: var(--color-surface);
    border: 1px solid var(--color-stroke);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-popover);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-5);
    text-align: center;
}

/* One mark for every state, in the neutral empty-state ink: it names the subject (the
   connection) once, and the words carry which state this is. */
.reconnect-mark {
    font-size: 44px;
    color: var(--color-text-disabled);
}

.reconnect-state {
    display: none;
    flex-direction: column;
    align-items: center;
    gap: var(--space-4);
}

.components-reconnect-show .reconnect-when-trying,
.components-reconnect-failed .reconnect-when-failed,
.components-reconnect-paused .reconnect-when-paused,
.components-reconnect-resume-failed .reconnect-when-paused,
.components-reconnect-rejected .reconnect-when-rejected {
    display: flex;
}

/* The extra line a failed resume adds to the on-hold block. */
.reconnect-resume-note { display: none; }
.components-reconnect-resume-failed .reconnect-resume-note { display: block; }

.reconnect-title {
    font-size: var(--font-size-section);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
}

.reconnect-body {
    font-size: var(--font-size-body);
    line-height: 1.5;
    color: var(--color-text-secondary);
}

/* The only proof the screen is still working at it, so it recedes by size alone and keeps the
   readable secondary ink — held still so the digits don't jitter as they count. */
.reconnect-progress {
    font-size: var(--font-size-description);
    color: var(--color-text-secondary);
    font-variant-numeric: tabular-nums;
}

.reconnect-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-3);
}
