/*
  Knox Field Console
  ------------------
  Visual language for the operator-facing pages (sandbox settings, server manager).

  The reference is 1993 civil-defence and industrial control equipment, which is where
  Project Zomboid's own world lives: painted olive-charcoal panels, silkscreened labels,
  amber indicator lamps, stamped red for anything you cannot take back. The ground is
  deliberately a warm olive near-black rather than neutral #000, and amber is rationed to
  live state and current values only, so it reads as an indicator rather than a theme.

  Everything is scoped under .knox so it cannot leak into the MudBlazor pages.
*/

:root {
    /* ground */
    --ash: #14150f;
    --panel: #1e201a;
    --panel-hi: #262922;
    --rule: #33362b;

    /* ink */
    --bone: #e8e4d6;
    --moss: #8a9070;
    --faint: #5a5f4a;

    /* signal */
    --amber: #e5a32b;
    --rust: #b04226;
    --live: #9ccc65;

    --label: "Barlow Condensed", "Arial Narrow", sans-serif;
    --data: "IBM Plex Mono", ui-monospace, Consolas, monospace;
    --prose: Roboto, system-ui, sans-serif;

    /*
      The brand block's width, and nothing else's since the nav rail was deleted. Kept fixed
      rather than shrink-to-fit so the bar's left edge does not shift as the state word beside
      it changes between "Running" and "Not installed".
    */
    --rail-w: 232px;

    /*
      The app bar is the only element that spans the whole width, so every other sticky offset
      is measured from it. Page mastheads park directly beneath it, and the in-page rails and
      consoles park beneath those — hence one token for each, rather than the 80px that used to
      be written by hand in four places and was wrong in two of them.
    */
    --bar-h: 46px;

    /*
      Raised from 74px on 2026-07-28: the masthead gained a back link above the eyebrow when the
      nav rail was deleted. It is a min-height, so a head that overflows still looks right — but
      --below-head is what everything sticky measures from, so an understated value here parks
      the console and the sandbox rail underneath the masthead they are supposed to sit below.
    */
    --head-h: 92px;
    --below-head: calc(var(--bar-h) + var(--head-h));

    /*
      One height for every control that can sit beside another in an action strip. The parts
      use different fonts — search and selects are --data, buttons are --label at a different
      size — so matching padding never lined them up; only a shared height does.
    */
    --control-h: 2.25rem;

    /* Header strips carry a label and at most one control, and sit inside another surface. */
    --control-h-sm: 1.75rem;
}

body {
    background: var(--ash);
}

/*
  The marker class. Anything wearing it opts into the console palette, including the fragments
  this app renders inside MudBlazor's overlay — see ServerCommandConfirmDialog.

  It carries **no layout**, and must not. It used to also set `min-height: 100vh`, which was
  right for the one element it started on and wrong the moment a dialog used it: the confirm
  dialog's action row became 1000px tall, pushed the dialog past the viewport, and squeezed
  .mud-dialog-content to zero height — so the confirmation asked you to restart the server
  without showing you the sentence explaining what that does. Full height is a property of the
  page surface, so it lives on .knox-main.
*/
.knox {
    background: var(--ash);
    color: var(--bone);
}

/* ---- app shell ---------------------------------------------------------- */

/*
  Three regions, and each one answers a different question:

    bar   — what is true everywhere (which machine, is it up, what needs me)
    rail  — where can I go, and what am I working on
    main  — the thing I came here to do

  One column since the nav rail was deleted on 2026-07-28. It was two: a fixed 232px of
  destinations that could not say which of them needed attention, and charged that width to
  every screen forever. The dashboard does that job and says more.
*/
.knox-shell {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: var(--bar-h) minmax(0, 1fr);
    grid-template-areas:
        "bar"
        "main";
    min-height: 100vh;
    background: var(--ash);
    color: var(--bone);
}

/* ---- app bar ------------------------------------------------------------- */

.knox-bar {
    grid-area: bar;
    position: sticky;
    top: 0;
    z-index: 40;
    display: flex;
    align-items: stretch;
    background: #101109;
    border-bottom: 1px solid var(--rule);
}

/* Exactly one rail wide, so the panel seam runs unbroken from the bar to the floor. */
.knox-bar__brand {
    display: flex;
    flex-direction: column;
    justify-content: center;
    flex: none;
    width: var(--rail-w);
    box-sizing: border-box;
    padding: 0 1.25rem;
    border-right: 1px solid var(--rule);
    text-decoration: none;
}

.knox-brand__mark {
    display: block;
    font-family: var(--label);
    font-size: 1.15rem;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    line-height: 1;
    color: var(--bone);
}

.knox-brand__sub {
    display: block;
    font-family: var(--data);
    font-size: 0.55rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--faint);
    margin-top: 0.2rem;
}

/*
  The status readout. This is the one fact that used to cost a page navigation to learn, and
  it is the first thing an operator wants on every screen.
*/
.knox-bar__status {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0 1.1rem;
    min-width: 0;
}

/*
  The beacon lamp at bar scale. The bar carries the same .knox-beacon--running / --crashed
  classes the server page uses, so the lit states come from one set of rules; only the size
  differs.
*/
.knox-bar__lamp {
    width: 10px;
    height: 10px;
}

.knox-bar__state {
    font-family: var(--label);
    font-size: 0.9rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--moss);
    white-space: nowrap;
}

.knox-bar--running .knox-bar__state { color: var(--live); }
.knox-bar--crashed .knox-bar__state { color: #d4522f; }
.knox-bar--warn .knox-bar__state    { color: var(--amber); }

/* Secondary facts, hairline-separated the way a panel legend plate is engraved. */
.knox-bar__facts {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    font-family: var(--data);
    font-size: 0.7rem;
    color: var(--faint);
    white-space: nowrap;
    overflow: hidden;
}

.knox-bar__fact {
    padding-left: 0.85rem;
    border-left: 1px solid var(--rule);
}

/*
  Standing condition, not an event: config is on disk that the running server has not read, and
  it stays true until somebody restarts. Amber and clickable, unlike the build number beside it,
  because there is something to go and do about it.
*/
.knox-bar__fact--warn {
    color: var(--amber);
    text-decoration: none;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.knox-bar__fact--warn:hover {
    color: var(--bone);
}

/*
  The positioning context for both bar panels, so they hang from the right edge of the window
  rather than from the right edge of whichever icon opened them — a panel anchored to the bell
  stops short of the account switch and reads as misaligned.
*/
.knox-bar__tools {
    display: flex;
    align-items: stretch;
    margin-left: auto;
    position: relative;
}

.knox-bar__tools > .knox-drop {
    position: static;
}

/*
  A bank of panel switches: square, hairline-divided, the same height as the bar. Round icon
  buttons would be the only round thing in the app.
*/
.knox-icon {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--bar-h);
    flex: none;
    background: none;
    border: none;
    border-left: 1px solid var(--rule);
    color: var(--moss);
    cursor: pointer;
    transition: color 120ms ease, background 120ms ease;
}

.knox-icon:hover,
.knox-icon[aria-expanded="true"] {
    color: var(--bone);
    background: rgba(232, 228, 214, 0.05);
}

.knox-icon[aria-expanded="true"] {
    color: var(--amber);
}

.knox-icon svg {
    width: 18px;
    height: 18px;
    display: block;
}

/* The count, stamped rather than pilled — nothing else here has a radius. */
.knox-icon__badge {
    position: absolute;
    top: 7px;
    right: 6px;
    min-width: 14px;
    padding: 0 2px;
    box-sizing: border-box;
    font-family: var(--data);
    font-size: 0.58rem;
    font-weight: 600;
    line-height: 14px;
    text-align: center;
    color: #1a1508;
    background: var(--moss);
}

.knox-icon__badge--warn  { background: var(--amber); }
.knox-icon__badge--alarm { background: var(--rust); color: #f5e3dd; }

/*
  The nav rail lived here — .knox-nav, __list, __item, __item.active, __num — and was deleted
  on 2026-07-28 along with the second grid column. Do not bring back a persistent list of
  destinations: the dashboard is the navigation, and the reason is written up in
  docs/05-ui-shell.md.
*/

.knox-main {
    min-width: 0;
    display: flex;
    flex-direction: column;
    /* The page fills the window even when it holds one short panel. Here, not on .knox — that
       class is worn by dialog fragments too, which must not be a screen tall. */
    min-height: calc(100vh - var(--bar-h));
}

/* ---- the bench: what am I working on, and is it on the server? ---------- */

/*
  The working list used to have a dock in the app bar — .knox-benchbar and the .knox-bench--live
  / --drifted lamp states. Both went with the nav rail on 2026-07-28: the picker moved to the
  dashboard panel it picks for, and the state now reads as a .knox-chip beside that panel's
  legend. What is left here is the one class the bar still uses.
*/
.knox-bench__count {
    font-family: var(--data);
    font-size: 0.7rem;
    color: var(--faint);
    white-space: nowrap;
}

/* ---- shared atoms ------------------------------------------------------- */

.knox-eyebrow {
    font-family: var(--label);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--moss);
}

.knox-title {
    font-family: var(--label);
    font-size: 1.5rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--bone);
    margin: 0;
    line-height: 1.1;
}

.knox-mono {
    font-family: var(--data);
    font-variant-numeric: tabular-nums;
}

/* Buttons are stamped metal: square, silkscreened, no gradient. */
/*
  Worn by <button> and by <a> alike — the dashboard's panels navigate as often as they act, and
  a link that looks like a button must not be a link that sits half a line higher than one.
  inline-flex + centring is what makes the two agree once a control strip sets an explicit
  height; text-decoration: none is what stops the anchor underlining itself.
*/
.knox-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: var(--label);
    font-size: 0.875rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    text-decoration: none;
    color: var(--bone);
    background: var(--panel-hi);
    border: 1px solid var(--rule);
    padding: 0.5rem 1.1rem;
    cursor: pointer;
    transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
}

.knox-btn:hover:not(:disabled) {
    background: #2f3229;
    border-color: var(--moss);
}

.knox-btn:disabled {
    color: #5a5f4a;
    cursor: not-allowed;
}

.knox-btn--go {
    background: var(--amber);
    border-color: var(--amber);
    color: #1a1508;
}

.knox-btn--go:hover:not(:disabled) {
    background: #f2b23c;
    border-color: #f2b23c;
    color: #1a1508;
}

.knox-btn--go:disabled {
    background: var(--panel-hi);
    border-color: var(--rule);
}

/*
  Hazard striping — the one place the page raises its voice. Two grades, same pattern, because
  the difference between them is what the operator needs to weigh:

    rust  = players are left unable to connect, and nothing brings them back but you.
    amber = players are dropped, and it comes back on its own.

  Backup earns amber and looked routine until 2026-07-28. LinuxGSM's `stoponbackup` defaults to
  "on", so `./pzserver backup` stops the server, archives, and starts it again — a full restart
  with a three-second printed countdown and nothing to confirm. It was sitting unstriped beside
  Monitor in the group explicitly marked as not hazardous.
*/
.knox-btn--stop,
.knox-btn--cycle {
    background-image: repeating-linear-gradient(
        -45deg,
        transparent 0 6px,
        var(--hazard-stripe) 6px 12px
    );
}

.knox-btn--stop {
    --hazard-stripe: rgba(176, 66, 38, 0.22);
    border-color: var(--rust);
    color: #f0c4b8;
    background-color: #2a1712;
}

.knox-btn--stop:hover:not(:disabled) {
    background-color: #3a1e16;
    border-color: #d4522f;
}

.knox-btn--cycle {
    --hazard-stripe: rgba(229, 163, 43, 0.20);
    border-color: var(--amber);
    color: #f0dcb0;
    background-color: #2a2210;
}

.knox-btn--cycle:hover:not(:disabled) {
    background-color: #3a2f16;
    border-color: #f0b93c;
}

.knox :focus-visible,
.knox-bar :focus-visible {
    outline: 2px solid var(--amber);
    outline-offset: 2px;
}

/*
  The router focuses the page <h1> on every navigation so screen readers announce the new page.
  That is right, but the h1 is not interactive, and the rule above was drawing a live amber
  actuator box around the page title on every single page load — the title read as a control
  you could press. Announce it, do not ring it.
*/
.knox h1:focus,
.knox h1:focus-visible,
.knox-title:focus,
.knox-title:focus-visible {
    outline: none;
}

/* Keyboard-only jump past the app bar. Off-screen until it is focused. */
.knox-skip {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 60;
    font-family: var(--label);
    font-size: 0.85rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: #1a1508;
    background: var(--amber);
    padding: 0.5rem 1rem;
}

.knox-skip:focus {
    left: 0;
}

/*
  A factual state chip: square, level, quiet. Distinct from .knox-stamp, which is rotated and
  rust and means "this is not right yet" — the settings page was using the stamp to report a
  key had saved successfully, so a good outcome was rendered in the alarm colour.
*/
.knox-chip {
    font-family: var(--data);
    font-size: 0.68rem;
    letter-spacing: 0.06em;
    color: var(--moss);
    border: 1px solid var(--rule);
    padding: 0.2rem 0.5rem;
    white-space: nowrap;
}

.knox-chip--ok {
    color: var(--live);
    border-color: rgba(156, 204, 101, 0.35);
}

.knox-chip--warn {
    color: var(--amber);
    border-color: rgba(229, 163, 43, 0.4);
}

/* ---- drop panels: annunciator, account, page overflow -------------------- */

/*
  All three are <details>. The browser gives a disclosure keyboard support, Escape-to-close and
  correct expanded state for free; the alternative was a click-outside handler in JS per panel.
  Same idiom the Workshop tag filter already uses.
*/
.knox-drop {
    position: relative;
    display: flex;
}

.knox-drop > summary {
    list-style: none;
}

.knox-drop > summary::marker { content: ""; }
.knox-drop > summary::-webkit-details-marker { display: none; }

.knox-drop__panel {
    position: absolute;
    z-index: 50;
    top: 100%;
    right: 0;
    width: 24rem;
    max-width: calc(100vw - 1rem);
    max-height: calc(100vh - var(--bar-h) - 1rem);
    overflow-y: auto;
    background: var(--panel);
    border: 1px solid var(--moss);
    box-shadow: 0 14px 36px rgba(0, 0, 0, 0.6);
}

.knox-drop__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.6rem 0.9rem;
    border-bottom: 1px solid var(--rule);
    background: #101109;
}

/*
  ---- the annunciator ------------------------------------------------------

  Borrowed wholesale from the alarm panel on the equipment this app is dressed as: a stack of
  engraved legend windows, dark until their condition trips. Each lit cell states the condition
  in a stamped caption, says in one sentence what it means, and offers the one place you would
  go to clear it.

  Quiet conditions are not drawn as dead cells — a wall of unlit legends is clutter. They are
  named once, in a line at the foot, so an empty panel reads as "checked, all clear" rather
  than "nothing is being watched".
*/
.knox-cell {
    display: block;
    padding: 0.7rem 0.9rem 0.75rem;
    border-bottom: 1px solid var(--rule);
    border-left: 3px solid var(--moss);
    text-decoration: none;
}

.knox-cell--warn  { border-left-color: var(--amber); }
.knox-cell--alarm { border-left-color: var(--rust); }

a.knox-cell:hover {
    background: var(--panel-hi);
}

.knox-cell__legend {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--label);
    font-size: 0.8rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--moss);
}

.knox-cell--warn  .knox-cell__legend { color: var(--amber); }
.knox-cell--alarm .knox-cell__legend { color: #d4522f; }

/* One lamp per cell, lit to the cell's own level — the same lamp used everywhere else. */
.knox-cell__lamp {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex: none;
    background: var(--moss);
}

.knox-cell--warn .knox-cell__lamp {
    background: var(--amber);
    box-shadow: 0 0 6px rgba(229, 163, 43, 0.7);
}

.knox-cell--alarm .knox-cell__lamp {
    background: var(--rust);
    box-shadow: 0 0 7px rgba(176, 66, 38, 0.85);
    animation: knox-pulse 1.4s ease-in-out infinite;
}

.knox-cell__detail {
    font-family: var(--prose);
    font-size: 0.8rem;
    line-height: 1.45;
    color: var(--bone);
    margin: 0.3rem 0 0;
}

.knox-cell__go {
    display: inline-block;
    font-family: var(--label);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--amber);
    margin-top: 0.4rem;
}

.knox-clear {
    padding: 1.4rem 0.9rem;
    text-align: center;
}

.knox-clear__mark {
    font-family: var(--label);
    font-size: 1.05rem;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--live);
}

.knox-watching {
    font-family: var(--data);
    font-size: 0.65rem;
    line-height: 1.5;
    color: var(--faint);
    padding: 0.6rem 0.9rem 0.7rem;
    border-top: 1px solid var(--rule);
    margin: 0;
}

/* ---- account panel ------------------------------------------------------- */

.knox-facts {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 0.35rem 0.9rem;
    padding: 0.8rem 0.9rem;
    margin: 0;
}

.knox-facts dt {
    font-family: var(--label);
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--faint);
}

.knox-facts dd {
    font-family: var(--data);
    font-size: 0.75rem;
    color: var(--bone);
    margin: 0;
    overflow-wrap: anywhere;
}

.knox-drop__links {
    display: flex;
    flex-direction: column;
    border-top: 1px solid var(--rule);
}

.knox-drop__link {
    font-family: var(--label);
    font-size: 0.85rem;
    font-weight: 500;
    letter-spacing: 0.08em;
    color: var(--moss);
    text-decoration: none;
    background: none;
    border: none;
    border-bottom: 1px solid var(--rule);
    padding: 0.55rem 0.9rem;
    text-align: left;
    cursor: pointer;
}

.knox-drop__link:last-child {
    border-bottom: none;
}

.knox-drop__link:hover {
    color: var(--bone);
    background: var(--panel-hi);
}

.knox-drop__link:disabled {
    color: var(--faint);
    cursor: not-allowed;
    background: none;
}

/*
  Destructive, inside the overflow where it belongs. No hazard striping here — the stripes are
  for a button sitting in the open, and this one already costs a deliberate second click.
*/
.knox-drop__link--danger {
    color: #d4522f;
}

.knox-drop__link--danger:hover {
    color: #f0c4b8;
    background: #2a1712;
}

/* ---- control strips ------------------------------------------------------ */

/*
  A row of controls: the browse filter bar, the actions in a page masthead, a form's buttons.
  Everything directly inside one is forced to --control-h so inputs, selects and buttons line
  up on both edges.

  Scoped to these containers rather than applied to .knox-btn / .knox-field globally, because
  the sandbox ledger reuses .knox-field in a few hundred stacked rows where a 2.25rem control
  would double the length of the page. Those keep their own compact sizing.
*/
.knox-controls {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
}

/* A search box in a control strip is one item in a row, not a full-width block on its own. */
.knox-controls > .knox-search {
    margin: 0;
    flex: 1 1 18rem;
    min-width: 0;
    width: auto;
}

.knox-filters > .knox-search,
.knox-filters > .knox-field,
.knox-filters > .knox-btn,
.knox-filters > .knox-multi > .knox-multi__summary,
.knox-head__actions > .knox-search,
.knox-head__actions > .knox-field,
.knox-head__actions > .knox-btn,
.knox-controls > .knox-search,
.knox-controls > .knox-field,
.knox-controls > .knox-btn,
.knox-panel__body > .knox-btn,
.knox-panel__body > .knox-field,
.knox-pager > .knox-btn {
    box-sizing: border-box;
    height: var(--control-h);
    padding-top: 0;
    padding-bottom: 0;
}

/*
  Header strips. Same idea, one size down: these sit inside a panel that already has its own
  frame, and a full-height control in them reads as a second toolbar. This replaces an inline
  `style="padding:.15rem .55rem"` that was doing the job by hand on one button in ServerManager.
*/
.knox-panel__head > .knox-btn,
.knox-panel__head > .knox-field,
.knox-pane__head > .knox-search,
.knox-pane__head > .knox-field,
.knox-pane__head > .knox-btn {
    box-sizing: border-box;
    height: var(--control-h-sm);
    padding-top: 0;
    padding-bottom: 0;
    font-size: 0.75rem;
}

/* Buttons centre their own label; inputs and selects need telling on some engines. */
.knox-filters > .knox-btn,
.knox-head__actions > .knox-btn,
.knox-controls > .knox-btn,
.knox-panel__body > .knox-btn,
.knox-panel__head > .knox-btn,
.knox-pane__head > .knox-btn,
.knox-pager > .knox-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* ---- masthead ----------------------------------------------------------- */

.knox-head {
    position: sticky;
    top: var(--bar-h);
    z-index: 5;
    display: flex;
    align-items: center;
    gap: 1.5rem;
    flex-wrap: wrap;
    box-sizing: border-box;
    min-height: var(--head-h);
    padding: 0.85rem 1.5rem;
    background: var(--ash);
    border-bottom: 1px solid var(--rule);
}

.knox-head__id {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
}

/*
  The way back to the dashboard. Deliberately the quietest thing in the masthead — it is the
  exit, not the subject. Sits above the eyebrow so the reading order is where-I-came-from,
  what-kind-of-page, which-page.
*/
.knox-head__back {
    font-family: var(--label);
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--faint);
    text-decoration: none;
    width: fit-content;
}

.knox-head__back:hover {
    color: var(--amber);
}

.knox-head__actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-left: auto;
}

/*
  Page actions, ranked. Mod lists was five buttons of identical weight in a row, one of them
  Delete — no reading of that strip tells you which one you came to press, and the destructive
  one was as easy to hit as the rest. The primary action stays out; everything else folds into
  the overflow, and anything destructive lives only in there.
*/
.knox-head__more > summary {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--control-h);
    height: var(--control-h);
    box-sizing: border-box;
    color: var(--moss);
    background: var(--panel-hi);
    border: 1px solid var(--rule);
    cursor: pointer;
}

.knox-head__more > summary:hover,
.knox-head__more[open] > summary {
    color: var(--bone);
    border-color: var(--moss);
}

.knox-head__more .knox-drop__panel {
    width: 15rem;
}

/* Unsaved work is a rubber stamp, not a badge. */
.knox-stamp {
    font-family: var(--label);
    font-size: 0.8rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--rust);
    border: 2px solid var(--rust);
    padding: 0.2rem 0.6rem;
    transform: rotate(-1.5deg);
}

/* ---- body grid ---------------------------------------------------------- */

.knox-body {
    display: grid;
    grid-template-columns: 220px minmax(0, 1fr);
    gap: 0;
    align-items: start;
}

.knox-rail {
    position: sticky;
    top: var(--below-head);
    display: flex;
    flex-direction: column;
    border-right: 1px solid var(--rule);
    padding: 1rem 0;
    max-height: calc(100vh - var(--below-head));
    overflow-y: auto;
}

.knox-rail__item {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    font-family: var(--label);
    font-size: 0.95rem;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--moss);
    background: none;
    border: none;
    border-left: 3px solid transparent;
    padding: 0.45rem 1.25rem;
    text-align: left;
    cursor: pointer;
    transition: color 120ms ease, border-color 120ms ease;
}

.knox-rail__item:hover {
    color: var(--bone);
}

/*
  Reused by the dashboard's preset roster, where an entry can be unpickable — a preset whose mod
  list was deleted has nothing to switch to. The sandbox rail never disables one, so this arrived
  with that second use.
*/
.knox-rail__item:disabled {
    color: var(--faint);
    cursor: not-allowed;
}

.knox-rail__item:disabled:hover {
    color: var(--faint);
}

.knox-rail__item[aria-current="true"] {
    color: var(--amber);
    border-left-color: var(--amber);
    background: linear-gradient(90deg, rgba(229, 163, 43, 0.08), transparent);
}

.knox-rail__count {
    font-family: var(--data);
    font-size: 0.7rem;
    color: #5a5f4a;
}

.knox-ledger {
    padding: 0 1.5rem 4rem;
    min-width: 0;
}

/*
  A single-form page. Full-bleed is right for a grid of cards or a two-pane transfer, and wrong
  for one panel holding one input — that rendered as a strip across a 1600px screen with the
  rest of the viewport empty beneath it.
*/
.knox-column {
    max-width: 52rem;
    padding: 1.5rem;
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
}

/* Wide enough for a mod description or a bot transcript, still short of full bleed. */
.knox-column--wide {
    max-width: 68rem;
}

/*
  Notices carry their own margin for the full-bleed pages that drop one between sections. In a
  column the gap already exists, and the extra margin inset them a further 1.5rem so a notice
  and the panel under it did not share a left edge.
*/
.knox-column > .knox-notice {
    margin: 0;
    max-width: none;
}

/* ---- mod detail ---------------------------------------------------------- */

.knox-mod-head {
    display: flex;
    align-items: flex-start;
    gap: 1.25rem;
    padding: 1rem;
    min-width: 0;
}

.knox-mod-head__shot {
    width: 220px;
    max-width: 45%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    background: var(--ash);
    border: 1px solid var(--rule);
    flex: none;
}

.knox-mod-head__by {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    min-width: 0;
}

.knox-mod-head__by img {
    margin-top: 0.35rem;
    max-width: 100%;
}

/* Dependency rows are links, so they need the anchor reset the plain rows do not. */
a.knox-mod {
    text-decoration: none;
}

a.knox-mod:hover .knox-mod__name {
    color: var(--amber);
}

.description {
    font-family: var(--prose);
    font-size: 0.875rem;
    line-height: 1.6;
    color: var(--bone);
    overflow-wrap: anywhere;
}

.description a {
    color: var(--amber);
}

/* ---- bot transcript ------------------------------------------------------ */

.knox-turn {
    padding: 0.7rem 1rem 0.8rem;
    border-bottom: 1px solid var(--rule);
    border-left: 3px solid transparent;
}

/* Who is speaking, as an edge colour — quicker to scan than reading each label. */
.knox-turn--you   { border-left-color: var(--rule); }
.knox-turn--bot   { border-left-color: var(--moss); }
.knox-turn--error { border-left-color: var(--rust); }

.knox-turn__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0 0.7rem;
    font-family: var(--data);
    font-size: 0.65rem;
    color: var(--faint);
}

.knox-turn__who {
    font-family: var(--label);
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--moss);
}

.knox-turn--error .knox-turn__who {
    color: #d4522f;
}

.knox-turn__text {
    font-family: var(--prose);
    font-size: 0.875rem;
    line-height: 1.5;
    color: var(--bone);
    white-space: pre-wrap;
    margin: 0.25rem 0 0;
}

.knox-turn .knox-controls {
    margin-top: 0.6rem;
}

/* ---- search ------------------------------------------------------------- */

.knox-search {
    display: block;
    width: 100%;
    font-family: var(--data);
    font-size: 0.9rem;
    color: var(--bone);
    background: var(--panel);
    border: 1px solid var(--rule);
    border-left: 3px solid var(--moss);
    padding: 0.6rem 0.9rem;
    margin: 1.25rem 0 0.5rem;
}

.knox-search::placeholder {
    color: #5a5f4a;
}

/* ---- setting row -------------------------------------------------------- */

/*
  One setting. The identifying line reads left to right as a sentence —
  name, value, what the value means — so the eye never has to cross the row.
*/
.knox-row {
    padding: 0.5rem 0 0.55rem 0.7rem;
    border-bottom: 1px solid var(--rule);
    border-left: 3px solid transparent;
}

.knox-row--dirty {
    border-left-color: var(--rust);
    background: linear-gradient(90deg, rgba(176, 66, 38, 0.07), transparent 60%);
}

.knox-row__head {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0 0.55rem;
}

.knox-row__label {
    font-family: var(--label);
    font-size: 1rem;
    font-weight: 500;
    letter-spacing: 0.01em;
    color: var(--bone);
}

.knox-row__value {
    font-family: var(--data);
    font-size: 0.85rem;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    color: var(--amber);
}

.knox-row--dirty .knox-row__value {
    color: var(--rust);
}

.knox-row__name {
    font-family: var(--label);
    font-size: 0.85rem;
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--moss);
}

.knox-row__where {
    font-family: var(--data);
    font-size: 0.7rem;
    color: #5a5f4a;
    margin-left: auto;
}

.knox-row__desc {
    font-family: var(--prose);
    font-size: 0.78rem;
    line-height: 1.4;
    color: var(--moss);
    max-width: 78ch;
    margin: 0.1rem 0 0;
}

/* Prose that is reporting a tripped condition rather than explaining a control. */
.knox-row__desc--warn {
    color: var(--bone);
    border-left: 3px solid var(--amber);
    padding-left: 0.7rem;
}

.knox-row__desc--warn strong {
    color: var(--amber);
    font-family: var(--label);
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.knox-row__control {
    margin-top: 0.45rem;
}

.knox-row__factory {
    font-family: var(--data);
    font-size: 0.7rem;
    color: #5a5f4a;
    margin-top: 0.3rem;
}

/* A value that was already outside its bounds when the file was read. Not an alarm — nothing
   is broken, and it may well be the mod's own comment that is wrong. */
.knox-row__factory--warn {
    color: var(--warn);
}

/* ---- the dial: enumerated stops ----------------------------------------- */

.knox-dial {
    display: flex;
    flex-wrap: wrap;
    /* Row gap leaves room for the factory-default tick that hangs below a stop. */
    gap: 10px 2px;
}

.knox-dial__stop {
    position: relative;
    font-family: var(--label);
    font-size: 0.78rem;
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--moss);
    background: var(--panel);
    border: 1px solid var(--rule);
    padding: 0.22rem 0.6rem;
    cursor: pointer;
    transition: color 120ms ease, background 120ms ease;
}

.knox-dial__stop:hover {
    color: var(--bone);
    background: var(--panel-hi);
}

.knox-dial__stop[aria-pressed="true"] {
    color: #1a1508;
    background: var(--amber);
    border-color: var(--amber);
}

.knox-row--dirty .knox-dial__stop[aria-pressed="true"] {
    background: var(--rust);
    border-color: var(--rust);
    color: #f5e3dd;
}

/* The factory default gets a tick beneath it, so drift from vanilla is always visible. */
.knox-dial__stop--factory::after {
    content: "";
    position: absolute;
    left: 50%;
    bottom: -5px;
    width: 5px;
    height: 5px;
    transform: translateX(-50%) rotate(45deg);
    background: var(--moss);
}

/* ---- the dial: long enumerations and free numbers ----------------------- */

.knox-field {
    font-family: var(--data);
    font-size: 0.82rem;
    color: var(--bone);
    background: var(--panel);
    border: 1px solid var(--rule);
    padding: 0.3rem 0.6rem;
    min-width: 11rem;
}

.knox-field:hover {
    border-color: var(--moss);
}

select.knox-field {
    cursor: pointer;
}

.knox-field--wide {
    width: 100%;
}

.knox-slider {
    display: flex;
    align-items: center;
    gap: 1rem;
}

.knox-slider input[type="range"] {
    flex: 1;
    accent-color: var(--amber);
    background: transparent;
    cursor: pointer;
}

.knox-row--dirty .knox-slider input[type="range"] {
    accent-color: var(--rust);
}

.knox-slider .knox-field {
    min-width: 7rem;
    max-width: 9rem;
    text-align: right;
}

/* ---- boolean: two-position switch --------------------------------------- */

.knox-switch {
    display: inline-flex;
    border: 1px solid var(--rule);
}

.knox-switch__pos {
    font-family: var(--label);
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--moss);
    background: var(--panel);
    border: none;
    padding: 0.22rem 0.95rem;
    cursor: pointer;
    transition: color 120ms ease, background 120ms ease;
}

.knox-switch__pos:hover {
    color: var(--bone);
}

.knox-switch__pos[aria-pressed="true"] {
    color: #1a1508;
    background: var(--amber);
}

.knox-row--dirty .knox-switch__pos[aria-pressed="true"] {
    background: var(--rust);
    color: #f5e3dd;
}

/* ---- notices ------------------------------------------------------------ */

.knox-notice {
    border: 1px solid var(--rule);
    border-left: 3px solid var(--moss);
    background: var(--panel);
    padding: 1rem 1.25rem;
    margin: 1.5rem;
    max-width: 60rem;
}

.knox-notice--alert {
    border-left-color: var(--rust);
}

.knox-notice p {
    font-family: var(--prose);
    font-size: 0.875rem;
    line-height: 1.5;
    color: var(--moss);
    margin: 0.4rem 0 0;
}

.knox-notice code {
    font-family: var(--data);
    font-size: 0.82rem;
    color: var(--bone);
    background: var(--ash);
    padding: 0.1rem 0.35rem;
    word-break: break-all;
}

/* Pre-apply findings. One line per problem, kind first so the list scans vertically. */
.knox-issues {
    list-style: none;
    margin: 0.6rem 0 0;
    padding: 0;
}

.knox-issue {
    font-family: var(--data);
    font-size: 0.78rem;
    line-height: 1.5;
    color: var(--moss);
    padding: 0.3rem 0 0.3rem 0.7rem;
    border-left: 2px solid var(--rule);
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
}

.knox-issue__text {
    flex: 1 1 auto;
    min-width: 0;
}

/* Pushed to the far edge so a row of findings reads as prose with an action at the end, not as
   a line of buttons. Several findings have none — refetching the same files fixes nothing. */
.knox-issue__act {
    flex: none;
    align-self: center;
    font-size: 0.62rem;
    padding: 0.25rem 0.6rem;
}

.knox-issue--error {
    color: var(--bone);
    border-left-color: var(--rust);
}

.knox-issue__kind {
    font-family: var(--label);
    font-size: 0.62rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--faint);
    flex: none;
    width: 9rem;
}

.knox-issue--error .knox-issue__kind {
    color: var(--rust);
}

.knox-empty {
    font-family: var(--label);
    font-size: 1rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: #5a5f4a;
    padding: 3rem 0;
    text-align: center;
}

/* ---- dashboard ----------------------------------------------------------- */

/*
  The dashboard is the navigation and the working surface, and it is read as a rack: top to
  bottom in descending order of "does this need me". Synoptic band, then what needs you beside
  power, then the preset you are working on.

  That ordering is the whole design. The old rail listed six destinations of identical weight and
  could not say which of them mattered today, so every destination now hangs off the panel that
  operates it — there is no list of links on this page and there must not be one again.

  No masthead above it either. Every other page has one because it has to name itself and offer
  a way back; this is home, and a 92px band reading DASHBOARD under a bar already saying KNOX
  bought nothing and pushed the band down by that much.
*/
.knox-dash {
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
    padding: 0 1.5rem 2rem;
}

/*
  The signature: a band that is itself a lamp.

  Not a panel. Full-bleed, no border but the lit edge, and it takes the state colour as a wash
  across the whole strip rather than confining it to a 20px dot. That is what an annunciator
  plate does and nothing else in this app does it at this scale — which is right, because the
  one question this page exists to answer is "do I need to do something", and the largest
  element on it should answer that before any reading happens.

  The quiet state is genuinely quiet: olive on olive, no wash. An indicator that shouts when
  everything is fine is one nobody reads when it isn't.
*/
.knox-synoptic {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    flex-wrap: wrap;
    margin: 1.5rem -1.5rem 0;
    padding: 1.4rem 1.5rem;
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
    border-left: 4px solid #3a3d30;
    background: var(--panel);
}

.knox-synoptic--ok {
    border-left-color: var(--live);
    background: linear-gradient(90deg, rgba(156, 204, 101, 0.07), transparent 60%), var(--panel);
}

.knox-synoptic--warn {
    border-left-color: var(--amber);
    background: linear-gradient(90deg, rgba(229, 163, 43, 0.1), transparent 60%), var(--panel);
}

.knox-synoptic--alarm {
    border-left-color: var(--rust);
    background: linear-gradient(90deg, rgba(176, 66, 38, 0.14), transparent 60%), var(--panel);
}

.knox-synoptic__text {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    min-width: 0;
    flex: 1;
}

/* The largest type in the app. It is one word or two, and it is the answer. */
.knox-synoptic__state {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    font-family: var(--label);
    font-size: 2.1rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    line-height: 1;
    color: var(--moss);
    margin: 0;
}

.knox-synoptic--ok    .knox-synoptic__state { color: var(--live); }
.knox-synoptic--warn  .knox-synoptic__state { color: var(--amber); }
.knox-synoptic--alarm .knox-synoptic__state { color: #d4522f; }

/*
  The consequence, not a restatement of the state. "Running" above; "restart pending, settings
  written 12m ago" here. When nothing is wrong this says what the server is actually serving,
  because that is the next thing you would want to know.
*/
.knox-synoptic__note {
    font-family: var(--prose);
    font-size: 0.9rem;
    line-height: 1.4;
    color: var(--moss);
    margin: 0;
    max-width: 62ch;
}

.knox-synoptic__note strong {
    color: var(--bone);
    font-weight: 500;
}

/* Right-hand readouts. Secondary to the sentence, which is why they are small and mono. */
.knox-synoptic__readouts {
    display: flex;
    gap: 2rem;
    flex-wrap: wrap;
}

.knox-synoptic__readout {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

.knox-synoptic__value {
    font-family: var(--data);
    font-size: 1.25rem;
    color: var(--bone);
    line-height: 1;
}

.knox-synoptic__label {
    font-family: var(--label);
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--faint);
}

/*
  Sits inside the readouts, not past them: the numbers keep the right edge they are read against.
  Explicit height because the band is not one of the control strips that size their children, and
  an unsized .knox-btn beside a two-line readout grows to match it.
*/
.knox-synoptic__go {
    box-sizing: border-box;
    height: var(--control-h);
    padding-top: 0;
    padding-bottom: 0;
    margin-left: auto;
}

/*
  There was a Power panel here, beside the alerts. It never matched their height — "all clear"
  and three buttons are not the same amount of content — and that mismatch was the symptom
  rather than the fault: power is not a peer of the alerts, and the dashboard is not where you
  cycle the server. The band now carries one link to the page that does, and the commands live
  on that page with the console they print to. Removed 2026-07-28.
*/

/* A panel whose body is a list of annunciator cells rather than a row of controls. */
.knox-dash__cells {
    display: flex;
    flex-direction: column;
}

/*
  There was a `.knox-plate` here: a row of seven links across the foot of the dashboard, legended
  "Go to". It was the deleted nav rail rebuilt smaller and lower, which is exactly what the
  dashboard was meant to replace, and while it existed no panel above it had to carry a way
  anywhere. Deleted 2026-07-28. Do not reintroduce it — see docs/05-ui-shell.md.
*/

/* ---- the working panel: a roster beside the preset it opens -------------- */

/*
  Hairline gaps rather than borders, the same trick `.knox-panes` uses: one background showing
  through a 1px grid gap draws every divider, including the ones between nested halves, without
  a border on each child that then has to be cancelled at the edges.
*/
.knox-dash__work {
    display: grid;
    grid-template-columns: 15rem minmax(0, 1fr);
    gap: 1px;
    background: var(--rule);
    border-top: 1px solid var(--rule);
}

.knox-dash__roster,
.knox-dash__open {
    background: var(--ash);
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* Pushes the roster's own actions to the foot, so it lines up with the pane beside it. */
.knox-dash__rosterlist {
    display: flex;
    flex-direction: column;
    padding: 0.5rem 0;
    flex: 1;
}

.knox-dash__halves {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 1px;
    background: var(--rule);
    flex: 1;
}

.knox-dash__half {
    background: var(--ash);
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* The mods each half's actions sit below, so the two halves stay the same height. */
.knox-dash__half > .knox-panel__body {
    margin-top: auto;
}

.knox-dash__modlist {
    display: flex;
    flex-direction: column;
}

.knox-dash__more {
    font-family: var(--label);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--moss);
    text-decoration: none;
    padding: 0.5rem 0.75rem;
}

.knox-dash__more:hover {
    color: var(--amber);
}

.knox-dash__note {
    font-family: var(--prose);
    font-size: 0.8rem;
    line-height: 1.5;
    color: var(--faint);
    margin: 0;
    padding: 0.85rem 1rem;
    max-width: 46ch;
}

/* The same sentence sharing a control strip with a button, so it must not add a second box. */
.knox-dash__note--inline {
    padding: 0;
    align-self: center;
}

/* The thing the panel is about, named beside the eyebrow that says what kind of thing it is. */
.knox-panel__name {
    font-family: var(--label);
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    color: var(--bone);
    margin-right: auto;
}

/* ---- server manager ----------------------------------------------------- */

.knox-deck {
    display: grid;
    grid-template-columns: 320px minmax(0, 1fr);
    gap: 1.5rem;
    padding: 1.5rem;
    align-items: start;
}

.knox-stack {
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
    min-width: 0;
}

.knox-panel {
    background: var(--panel);
    border: 1px solid var(--rule);
}

.knox-panel__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.6rem 1rem;
    border-bottom: 1px solid var(--rule);
}

.knox-panel__body {
    padding: 1rem;
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
}

/*
  A panel body is a wrapping row of buttons by default, which is right for the power controls
  and wrong for anything with prose or a form in it — those come out side by side. This is the
  column variant. It replaces two inline `style="display:block"` patches.
*/
.knox-panel__body--stack {
    flex-direction: column;
    align-items: stretch;
}

.knox-panel__body--stack > p {
    margin: 0;
    max-width: 60ch;
}

/* A second group of actions in the same panel, ruled off from the routine ones above it. */
.knox-panel__body--divided {
    border-top: 1px solid var(--rule);
}

/* The status beacon: an indicator lamp, lit only when the server is actually up. */
.knox-beacon {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 1.25rem 1rem;
}

.knox-beacon__lamp {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #3a3d30;
    border: 1px solid var(--rule);
    flex: none;
}

.knox-beacon__text {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
}

.knox-beacon__state {
    font-family: var(--label);
    font-size: 1.4rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    line-height: 1;
    color: var(--moss);
}

.knox-beacon--running .knox-beacon__lamp {
    background: #7cb342;
    border-color: #9ccc65;
    box-shadow: 0 0 10px rgba(124, 179, 66, 0.65);
    animation: knox-pulse 2.4s ease-in-out infinite;
}

.knox-beacon--running .knox-beacon__state {
    color: #9ccc65;
}

.knox-beacon--crashed .knox-beacon__lamp {
    background: var(--rust);
    border-color: #d4522f;
    box-shadow: 0 0 10px rgba(176, 66, 38, 0.65);
}

.knox-beacon--crashed .knox-beacon__state {
    color: #d4522f;
}

.knox-beacon--warn .knox-beacon__lamp {
    background: var(--amber);
    border-color: var(--amber);
    box-shadow: 0 0 10px rgba(229, 163, 43, 0.55);
}

.knox-beacon--warn .knox-beacon__state {
    color: var(--amber);
}

/*
  Something the app asked for is still happening. Amber already says "not joinable"; the pulse
  is what separates a transition from a steady state — Starting and Stopping are both amber and
  only one of them is the operator's own doing arriving.
*/
.knox-beacon--busy .knox-beacon__lamp {
    animation: knox-pulse 1s ease-in-out infinite;
}

@keyframes knox-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.55; }
}

/* Vitals: the numbers you check before doing anything else. */
.knox-vitals {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    border-top: 1px solid var(--rule);
}

.knox-vital {
    padding: 0.75rem 1rem;
    border-right: 1px solid var(--rule);
    min-width: 0;
}

.knox-vital:last-child {
    border-right: none;
}

.knox-vital__label {
    display: block;
    font-family: var(--label);
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--faint);
}

.knox-vital__value {
    display: block;
    font-family: var(--data);
    font-size: 1rem;
    color: var(--bone);
    margin-top: 0.2rem;
    overflow-wrap: break-word;
}

.knox-vital__value--dim {
    color: var(--faint);
}

.knox-vital__value--big {
    font-size: 1.5rem;
    color: var(--amber);
}

/* A second, quieter fact under a vital — the container's uptime under the server's. Not a
   value in its own right, so it must not read as one. */
.knox-vital__note {
    display: block;
    font-family: var(--data);
    font-size: 0.75rem;
    color: var(--faint);
    margin-top: 0.15rem;
}

.knox-console {
    display: flex;
    flex-direction: column;
    height: calc(100vh - var(--below-head) - 1.5rem);
    position: sticky;
    top: var(--below-head);
}

/* Sending a command to the game console. LinuxGSM supports it; nothing exposed it before. */
.knox-send {
    display: flex;
    gap: 0;
    border-top: 1px solid var(--rule);
}

.knox-send__prompt {
    font-family: var(--data);
    font-size: 0.85rem;
    color: var(--amber);
    padding: 0.55rem 0.5rem 0.55rem 0.85rem;
    background: #0e0f0a;
    user-select: none;
}

.knox-send input {
    flex: 1;
    min-width: 0;
    font-family: var(--data);
    font-size: 0.85rem;
    color: var(--bone);
    background: #0e0f0a;
    border: none;
    padding: 0.55rem 0.5rem;
}

.knox-send input::placeholder {
    color: var(--faint);
}

.knox-send input:disabled {
    color: var(--faint);
}

.knox-send button {
    border: none;
    border-left: 1px solid var(--rule);
}

.knox-console__out {
    flex: 1;
    overflow-y: auto;
    margin: 0;
    padding: 1rem;
    font-family: var(--data);
    font-size: 0.8rem;
    line-height: 1.5;
    color: #c8c8b4;
    background: #0e0f0a;
    white-space: pre-wrap;
    word-break: break-all;
}

.knox-lamp-sm {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--moss);
    flex: none;
}

.knox-lamp-sm--live {
    background: var(--amber);
    box-shadow: 0 0 6px rgba(229, 163, 43, 0.8);
    animation: knox-pulse 1.2s ease-in-out infinite;
}

/* ---- workshop browse ---------------------------------------------------- */

/*
  Sticks directly under the masthead, on the same token every other sticky strip uses. The
  search is how you get a different set of cards, and it was scrolling away with the cards it
  controls — so changing the search meant scrolling back up to find it.

  z-index below the masthead's, so the two never fight; they do not overlap anyway, because
  --below-head is exactly where the masthead ends.
*/
.knox-filters {
    position: sticky;
    top: var(--below-head);
    z-index: 4;
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    align-items: center;
    padding: 1rem 1.5rem;
    background: var(--ash);
    border-bottom: 1px solid var(--rule);
}

.knox-filters .knox-search {
    margin: 0;
    flex: 1 1 18rem;
    min-width: 0;
}

/* ---- multi-select filter ------------------------------------------------- */

/*
  Tags are a multi-select — Steam ANDs them, and narrowing by "Build 42" *and* "Vehicles" is
  the main reason to touch this bar at all. A native <select multiple> renders as a permanent
  tall listbox, which cannot sit in a single-line strip, so this is a <details> disclosure
  holding checkboxes. <details> gets keyboard support and Escape-to-close from the browser;
  the alternative was a click-outside handler in JS for no gain.
*/
.knox-multi {
    position: relative;
}

.knox-multi__summary {
    display: inline-flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    cursor: pointer;
    user-select: none;
    list-style: none;
}

/*
  The default disclosure triangle, replaced with a caret matching the native selects beside it.
  All three rules are needed: engines disagree about where a summary's marker lives, and
  getting only two of them leaves a stray triangle. ::marker is the modern one — Chrome keeps
  drawing it even when `display` is no longer list-item.
*/
.knox-multi__summary::marker {
    content: "";
}

.knox-multi__summary::-webkit-details-marker {
    display: none;
}

.knox-multi__summary::after {
    content: "";
    flex: none;
    width: 0.4rem;
    height: 0.4rem;
    border-right: 1px solid var(--moss);
    border-bottom: 1px solid var(--moss);
    transform: rotate(45deg) translateY(-0.1rem);
}

.knox-multi[open] > .knox-multi__summary {
    border-color: var(--moss);
    color: var(--amber);
}

.knox-multi__panel {
    position: absolute;
    z-index: 20;
    top: calc(100% + 2px);
    left: 0;
    min-width: 100%;
    max-height: 17rem;
    overflow-y: auto;
    background: var(--panel);
    border: 1px solid var(--moss);
    padding: 0.3rem 0;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.55);
}

.knox-multi__opt {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.32rem 0.75rem;
    font-family: var(--data);
    font-size: 0.8rem;
    color: var(--bone);
    white-space: nowrap;
    cursor: pointer;
}

.knox-multi__opt:hover {
    background: var(--panel-hi);
}

.knox-multi__opt input {
    accent-color: var(--amber);
    cursor: pointer;
}

.knox-multi__clear {
    display: block;
    width: 100%;
    margin-top: 0.3rem;
    padding: 0.35rem 0.75rem;
    border: none;
    border-top: 1px solid var(--rule);
    background: none;
    font-family: var(--label);
    font-size: 0.72rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    text-align: left;
    color: var(--moss);
    cursor: pointer;
}

.knox-multi__clear:hover:not(:disabled) {
    color: var(--bone);
    background: var(--panel-hi);
}

.knox-multi__clear:disabled {
    color: var(--faint);
    cursor: not-allowed;
}

/*
  Cards are separated by real space, not a hairline. At 1px the action strip at the bottom of
  one card read as a header on the card below it.
*/
.knox-cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
    gap: 1.25rem;
    padding: 1.25rem 1.5rem;
}

.knox-card {
    display: flex;
    flex-direction: column;
    background: var(--panel);
    border: 1px solid var(--rule);
    min-width: 0;
    transition: border-color 120ms ease;
}

.knox-card:hover {
    border-color: var(--moss);
}

.knox-card__shot {
    display: block;
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    background: var(--ash);
    border: none;
    border-bottom: 1px solid var(--rule);
    padding: 0;
    cursor: pointer;
}

/* Already on disk is a fact about the mod, not an action — so it sits on the image. */
.knox-card__ondisk {
    position: absolute;
    top: 0;
    right: 0;
    font-family: var(--label);
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--bone);
    background: rgba(20, 21, 15, 0.85);
    border-left: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
    padding: 0.15rem 0.4rem;
}

.knox-card__body {
    padding: 0.65rem 0.8rem 0.8rem;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    flex: 1;
}

.knox-card__title {
    font-family: var(--label);
    font-size: 1rem;
    font-weight: 500;
    letter-spacing: 0.02em;
    color: var(--bone);
    text-decoration: none;
    line-height: 1.2;
}

.knox-card__title:hover {
    color: var(--amber);
}

.knox-card__by {
    font-family: var(--data);
    font-size: 0.68rem;
    color: var(--faint);
}

.knox-card__tags {
    display: flex;
    flex-wrap: wrap;
    gap: 3px;
    margin-top: auto;
    padding-top: 0.4rem;
}

.knox-tag {
    font-family: var(--label);
    font-size: 0.68rem;
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--moss);
    border: 1px solid var(--rule);
    padding: 0.05rem 0.35rem;
}

/* Build compatibility is the tag that decides whether a mod is usable at all. */
.knox-tag--build {
    color: var(--amber);
    border-color: rgba(229, 163, 43, 0.45);
}

.knox-tag--build-old {
    color: var(--faint);
    border-color: var(--rule);
}

/*
  One action per card. Inside the body with space beneath it, so it belongs to this card
  rather than looking like a header bolted onto the next one.
*/
.knox-card__act {
    margin-top: 0.55rem;
    width: 100%;
    font-size: 0.8rem;
    padding: 0.38rem 0.5rem;
    text-align: center;
}

.knox-card__state {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    margin-top: 0.55rem;
    font-family: var(--label);
    font-size: 0.8rem;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--live);
    border: 1px solid rgba(156, 204, 101, 0.3);
    padding: 0.38rem 0.5rem;
}

.knox-card__state--waiting {
    color: var(--amber);
    border-color: rgba(229, 163, 43, 0.35);
}

.knox-pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 1rem;
}

.knox-pager__at {
    font-family: var(--data);
    font-size: 0.8rem;
    color: var(--moss);
    padding: 0 0.5rem;
}

/* ---- two-pane transfer (mod list curation) ------------------------------ */

.knox-panes {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 1px;
    background: var(--rule);
    border-top: 1px solid var(--rule);
    min-height: calc(100vh - var(--below-head));
}

/*
  Three columns: which list, the pool, and the list. Nested rather than flat — `pair` is one grid
  child holding the two mod panes — so the page folds inward-out, exactly like the dashboard's
  working panel: the two mod panes stack first because each needs its own width to read, and the
  roster only gives up its 15rem once nothing else fits beside it.
*/
.knox-panes--roster {
    grid-template-columns: 15rem minmax(0, 1fr);
}

.knox-panes__pair {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 1px;
    background: var(--rule);
    min-width: 0;
}

.knox-pane {
    background: var(--ash);
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/*
  Sticky, the same treatment .knox-rail gets on the sandbox page — and here it is load-bearing
  rather than a nicety. The pane beside it is a couple of hundred mods long and grows the page
  rather than scrolling inside itself, so a roster that stretched to match would put New list and
  Import a thousand pixels below the fold: the buttons this whole rework exists to surface.

  align-self:start is what gives sticky somewhere to move; grid children stretch by default. The
  1px gap still draws the divider full height, because the divider is the grid's background
  showing through rather than a border on this element.
*/
.knox-pane--roster {
    position: sticky;
    top: var(--below-head);
    align-self: start;
    max-height: calc(100vh - var(--below-head));
}

/* Scrolls on its own, and pushes New list / Import to the foot so they line up with the panes. */
.knox-pane__roster {
    display: flex;
    flex-direction: column;
    padding: 0.5rem 0;
    flex: 1;
    overflow-y: auto;
    min-height: 0;
}

.knox-pane__head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.7rem 1rem;
    border-bottom: 1px solid var(--rule);
    background: var(--panel);
    flex-wrap: wrap;
}

.knox-pane__head .knox-search {
    margin: 0;
    flex: 1 1 10rem;
    min-width: 0;
}

/*
  Downloads in progress. Amber and quiet — deliberately not .knox-stamp, which is the rust,
  rotated, something-is-wrong mark. A download arriving is the system working.
*/
.knox-pane__wait {
    font-family: var(--label);
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--amber);
}

.knox-pane__list {
    overflow-y: auto;
    flex: 1;
}

.knox-mod {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.4rem 0.75rem;
    border-bottom: 1px solid var(--rule);
    min-width: 0;
}

.knox-mod:hover {
    background: var(--panel);
}

.knox-mod__shot {
    width: 40px;
    height: 24px;
    object-fit: cover;
    background: var(--panel-hi);
    flex: none;
}

.knox-mod__text {
    min-width: 0;
    flex: 1;
    display: flex;
    flex-direction: column;
}

.knox-mod__name {
    font-family: var(--prose);
    font-size: 0.82rem;
    color: var(--bone);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.knox-mod__id {
    font-family: var(--data);
    font-size: 0.65rem;
    color: var(--faint);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
  A Workshop item asked for and not yet on disk. Dimmed and with no arrow, because there is
  nothing to move onto a list until it lands — at which point this row is replaced by however
  many mods came out of it.
*/
.knox-mod--waiting .knox-mod__name {
    color: var(--faint);
}

.knox-mod__wait {
    font-family: var(--label);
    font-size: 0.65rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--amber);
}

.knox-mod__act {
    font-family: var(--data);
    font-size: 0.95rem;
    line-height: 1;
    color: var(--moss);
    background: var(--panel-hi);
    border: 1px solid var(--rule);
    padding: 0.15rem 0.5rem;
    cursor: pointer;
    flex: none;
}

.knox-mod__act:hover {
    color: var(--bone);
    border-color: var(--moss);
}

.knox-mod__act:disabled {
    color: var(--faint);
    border-color: var(--rule);
    cursor: not-allowed;
}

/*
  Throws files away, so it does not read as the moss-green arrow beside it. Dim until hovered —
  it sits on every row of a long list, and a column of red would drown out the one action that
  is actually the point of the pane.
*/
.knox-mod__act--danger {
    color: var(--faint);
}

.knox-mod__act--danger:hover {
    color: #f0c4b8;
    border-color: #d4522f;
}

/*
  ---- MudBlazor surfaces ---------------------------------------------------

  Dialogs, snackbars, tooltips and popovers render into an overlay at the end of <body>,
  outside .knox, so these selectors are deliberately unscoped. The MudTheme in MainLayout
  already carries the palette and the square corners; what is left here is the chrome the
  theme does not reach — dialog headers, the stamped-metal look on buttons, and giving the
  snackbar an accent edge instead of a coloured slab.
*/

.mud-dialog {
    border: 1px solid var(--rule);
    box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6);
}

.mud-dialog-title {
    font-family: var(--label);
    font-size: 1.15rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--bone);
    border-bottom: 1px solid var(--rule);
    padding-bottom: 0.75rem;
}

.mud-dialog-content {
    padding-top: 1.25rem;
}

.mud-dialog-actions {
    border-top: 1px solid var(--rule);
    padding: 0.75rem 1.5rem;
    gap: 0.5rem;
}

/*
  A dialog whose actions are plain .knox-btn rather than MudButtons, so a confirm can wear the
  same hazard stripe as the button that opened it. MudBlazor lays its own actions out; this
  restores the row when the content is one element of ours.
*/
.knox-dialog__acts {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    width: 100%;
    /* It wears .knox for the text colour; the surface underneath is the dialog's, not a page's,
       so the slab of --ash that came with it painted a visible rectangle beside the buttons. */
    background: transparent;
}

/* Match the flat, bordered buttons the pages use rather than MudBlazor's raised ones. */
.mud-dialog-actions .mud-button-root,
.mud-snackbar .mud-button-root {
    border: 1px solid var(--rule);
    box-shadow: none;
}

.mud-dialog-actions .mud-button-filled:not(.mud-button-filled-primary) {
    background-color: var(--panel-hi);
    color: var(--bone);
}

.mud-dialog-actions .mud-button-filled:not(.mud-button-filled-primary):hover {
    background-color: #2f3229;
    border-color: var(--moss);
}

.mud-dialog-actions .mud-button-filled-primary {
    border-color: var(--amber);
}

/* Inputs read as recessed panels, the same as .knox-field. */
.mud-input.mud-input-outlined .mud-input-outlined-border {
    border-color: var(--rule);
}

.mud-input-control input,
.mud-input-control textarea {
    font-family: var(--data);
    font-size: 0.85rem;
}

.mud-input-label {
    font-family: var(--label);
    letter-spacing: 0.06em;
}

/* A left accent edge instead of a saturated slab — same idiom as .knox-notice. */
.mud-snackbar {
    border: 1px solid var(--rule);
    border-left-width: 3px;
    background-color: var(--panel) !important;
    color: var(--bone) !important;
    font-family: var(--prose);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55);
}

.mud-snackbar.mud-alert-filled-success { border-left-color: var(--live); }
.mud-snackbar.mud-alert-filled-error   { border-left-color: var(--rust); }
.mud-snackbar.mud-alert-filled-warning { border-left-color: var(--amber); }
.mud-snackbar.mud-alert-filled-info    { border-left-color: var(--moss); }

.mud-snackbar .mud-snackbar-content-message {
    font-size: 0.85rem;
}

.mud-tooltip {
    font-family: var(--prose);
    font-size: 0.78rem;
    background-color: #0e0f0a;
    color: var(--bone);
    border: 1px solid var(--rule);
    max-width: 32ch;
}

.mud-popover {
    border: 1px solid var(--rule);
    background-color: var(--panel);
    box-shadow: 0 12px 34px rgba(0, 0, 0, 0.6);
}

.mud-list-item:hover {
    background-color: var(--panel-hi);
}

.mud-alert {
    border: 1px solid var(--rule);
    border-left-width: 3px;
}

.mud-progress-linear .mud-progress-linear-bar {
    background-color: var(--amber);
}

/* ---- the sign-in gate ---------------------------------------------------- */

/*
  The one screen reached before signing in, so it gets none of the shell: no app bar, no live
  server readings, nothing that says what this instance is running. Brand, one panel, centred.
*/
.knox-gate {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.4rem;
    padding: 2rem 1rem 4rem;
}

.knox-gate__mark {
    text-align: center;
}

.knox-gate__body {
    width: 100%;
    max-width: 30rem;
}

.knox-gate__panel {
    width: 100%;
}

/* Shown by AuthorizeRouteView while the cookie is being read. Rarely visible; never blank. */
.knox-gate__wait {
    font-family: var(--data);
    font-size: 0.75rem;
    color: var(--faint);
    padding: 2rem;
}

/* ---- the access list ---------------------------------------------------- */

/*
  One row per Steam account allowed in. Its own block rather than a reused .knox-mod row: that
  one reserves a 40px thumbnail column, and there is no picture of an account here.
*/
.knox-access {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--rule);
}

.knox-access__row {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.5rem 0.7rem;
    border-bottom: 1px solid var(--rule);
}

.knox-access__row:last-child {
    border-bottom: none;
}

.knox-access__text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1;
}

.knox-access__name {
    font-family: var(--label);
    font-size: 0.95rem;
    font-weight: 500;
    letter-spacing: 0.04em;
    color: var(--bone);
    overflow-wrap: anywhere;
}

.knox-access__id {
    font-family: var(--data);
    font-size: 0.65rem;
    color: var(--faint);
}

.knox-access__when {
    font-family: var(--data);
    font-size: 0.65rem;
    color: var(--moss);
    white-space: nowrap;
}

/* Dimmed until hover, like the download-delete affordance it mirrors. */
.knox-access__act {
    font-family: var(--data);
    font-size: 1rem;
    line-height: 1;
    color: var(--faint);
    background: none;
    border: 1px solid transparent;
    padding: 0.15rem 0.4rem;
    cursor: pointer;
}

.knox-access__act:hover:not(:disabled) {
    color: var(--rust);
    border-color: var(--rust);
}

.knox-access__act:disabled {
    cursor: not-allowed;
    opacity: 0.4;
}

/* The sign-out form in the account menu — a form, so it needs the padding a link inherits. */
.knox-drop__form {
    padding: 0.7rem 0.9rem;
    border-top: 1px solid var(--rule);
}

/* Monospace where a fact is an identifier rather than a word. */
.knox-facts__mono {
    font-family: var(--data);
    font-size: 0.7rem;
}

/* ---- responsive --------------------------------------------------------- */

@media (max-width: 1100px) {
    .knox-panes {
        grid-template-columns: minmax(0, 1fr);
    }

    /*
      The working panel and the mod lists page both fold inward-out: the two panes stack first,
      because each needs its own width to read, and the roster only gives up its column once
      there is no room for a 15rem list beside anything.

      .knox-panes--roster has to be restated here — the .knox-panes rule above is in this same
      block at the same specificity, so the modifier outside it does not win.
    */
    .knox-dash__halves,
    .knox-panes__pair {
        grid-template-columns: minmax(0, 1fr);
    }

    .knox-panes--roster {
        grid-template-columns: 15rem minmax(0, 1fr);
    }
}

@media (max-width: 820px) {
    .knox-dash__work,
    .knox-panes--roster {
        grid-template-columns: minmax(0, 1fr);
    }

    /* Stacked, a roster is a short list at the top rather than a full-height column. */
    .knox-dash__rosterlist,
    .knox-pane__roster {
        flex: none;
    }

    /* Nothing to stick to once it is a band across the top. */
    .knox-pane--roster {
        position: static;
        max-height: none;
    }
}

/*
  The bar is one row and stays one row, so as it narrows it sheds content in order of how much
  each part earns its space: the build number and the restart chip first, then the state word.
  The lamp is the last thing standing — it is the one fact you cannot get from a narrow screen
  without navigating, and the tools beside it are how you navigate.

  It sheds one thing less than it used to: the working-list dock that used to sit between the
  status and the tools is on the dashboard now.
*/
@media (max-width: 1250px) {
    /*
      The build number goes; the restart chip does not. One is a fact you can read on two other
      pages, the other is a standing hazard with nothing else announcing it at this width.
    */
    .knox-bar__fact:not(.knox-bar__fact--warn) {
        display: none;
    }
}

@media (max-width: 820px) {
    .knox-bar__state {
        display: none;
    }
}

@media (max-width: 900px) {
    /*
      The shell is already one column at every width, so there is nothing to collapse here any
      more — this query used to fold the rail into a horizontal strip and reflow the grid to
      three rows. What is left is everything that stops being worth pinning on a small screen.
    */
    .knox-bar__brand {
        width: auto;
        min-width: 0;
    }

    /* Nothing is pinned any more, so page mastheads and the console stop competing to stick. */
    .knox-head {
        position: static;
    }

    .knox-console {
        position: static;
    }

    .knox-body,
    .knox-deck {
        grid-template-columns: minmax(0, 1fr);
    }

    .knox-rail {
        position: static;
        flex-direction: row;
        flex-wrap: wrap;
        max-height: none;
        border-right: none;
        border-bottom: 1px solid var(--rule);
        padding: 0.5rem 1rem;
    }

    .knox-rail__item {
        border-left: none;
        border-bottom: 3px solid transparent;
        padding: 0.4rem 0.7rem;
    }

    .knox-rail__item[aria-current="true"] {
        border-left-color: transparent;
        border-bottom-color: var(--amber);
        background: none;
    }

    .knox-ledger {
        padding: 0 1rem 3rem;
    }

    .knox-row {
        grid-template-columns: minmax(0, 1fr);
    }

    .knox-row__readout {
        grid-column: 1;
        grid-row: auto;
    }

    .knox-console {
        height: 70vh;
    }
}

@media (prefers-reduced-motion: reduce) {
    .knox *,
    .knox *::after {
        animation: none !important;
        transition: none !important;
    }
}
