/* Skylace — design tokens + base components.

   Field-command / Cabinet-Office design language (2.155.0+). Walnut +
   brass at night; paper + brass by day. Theme is selected by the
   `data-theme` attribute on the `<html>` element — `dark` is the
   default (also rendered when the attribute is absent so a bare doc
   still reads correctly), `light` switches in the Cabinet-Office
   daylight palette. The chrome's brass-switch toggle writes the
   `skylace-theme` cookie + flips the attribute; the server reads the
   cookie on every render so there's no flash of unstyled palette.

   Single source of truth for every Skylace UI surface. Inlined by the
   orchestrator into server-rendered HTML; copied verbatim by deploy-cli
   to /var/www/skylace/skylace-tokens.css for the static landing page.
   Edit one file; every surface updates on next deploy.

   ─── Vocabulary ───────────────────────────────────────────────────
   - --ground-{0..4} : the room. Outer ground → page → raised surface
     (panel body) → hover/nested → input wells. Maps to walnut0..4 in
     dark and cream0/cream1/paper/paper2/paperWell in light.
   - --brass{,-hi,-lo,-rim,-dim} : the primary accent. Buttons,
     nameplates, eyebrow leaders, focus rings. Hi/Lo are gradient
     stops for the engraved-brass look.
   - --cabinet{,-hi} : affirmative green ("active duty"). On-air dots,
     ok banners, success states.
   - --oxblood{,-hi} : danger red. Destructive buttons, error
     banners, bad pills, stamps (RESTRICTED / EYES ONLY).
   - --royal{,-dim} : intelligence-corps slate blue. Paused state,
     secondary tint.
   - --text-{1..4} : body text on the active ground. text-1 is full
     strength, text-4 is hairline-faint.
   - --hair : 1px rule between fields, between crumbs, the panel's
     internal seam.
   - --display : the "showy" colour for Cinzel chrome titles.
     Vellum-warm on dark, ink-dark on light.
   - --r-{0..3} : radii. Skylace's design is *polished, not playful* —
     2-4px max. The legacy --panel-radius is aliased to --r-1 so
     existing views auto-pick up the flatter shape.
   ─────────────────────────────────────────────────────────────────── */

/* ─── Self-hosted webfonts ──────────────────────────────────────
   Fonts ship with the orchestrator (packages/orchestrator/templates/
   fonts/) and are deployed to /var/www/skylace/skylace-fonts/ by
   deploy-cli's `ensureSkylaceFonts` step. Each per-VPS nginx vhost
   serves /skylace-fonts/* directly from disk (location block in
   nginx-base.conf.tmpl, included via skylace-common.conf). Locked
   in 2.157.0 — no third-party dependency in the critical-path render.
   ─────────────────────────────────────────────────────────────── */

/* Cinzel */
@font-face {
  font-family: 'Cinzel';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/cinzel-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Cinzel';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/cinzel-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Cinzel';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/skylace-fonts/cinzel-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Cinzel';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/skylace-fonts/cinzel-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Cinzel';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/skylace-fonts/cinzel-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Cinzel';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/skylace-fonts/cinzel-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* EB Garamond */
@font-face {
  font-family: 'EB Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-italic-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-italic-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-italic-greek-ext.woff2') format('woff2');
  unicode-range: U+1F00-1FFF;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-italic-greek.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-italic-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-italic-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-italic-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-greek-ext.woff2') format('woff2');
  unicode-range: U+1F00-1FFF;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-greek.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-greek-ext.woff2') format('woff2');
  unicode-range: U+1F00-1FFF;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-greek.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/eb-garamond-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Inter Tight */
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-greek-ext.woff2') format('woff2');
  unicode-range: U+1F00-1FFF;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-greek.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-greek-ext.woff2') format('woff2');
  unicode-range: U+1F00-1FFF;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-greek.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-greek-ext.woff2') format('woff2');
  unicode-range: U+1F00-1FFF;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-greek.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-greek-ext.woff2') format('woff2');
  unicode-range: U+1F00-1FFF;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-greek.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/skylace-fonts/inter-tight-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* JetBrains Mono */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-greek.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-greek.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-greek.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/skylace-fonts/jetbrains-mono-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}


/* ─── DARK THEME (canonical / default) ─────────────────────────── */

:root {
  /* Grounds — deep walnut-navy, layered. */
  --ground-0: #080a10;
  --ground-1: #0c0f17;
  --ground-2: #13171f;
  --ground-3: #1a1f29;
  --ground-4: #232934;

  /* Brass — warm gold accent. */
  --brass:     #c9a557;
  --brass-hi:  #e3c887;
  --brass-lo:  #8b7236;
  --brass-rim: rgba(201, 165, 87, 0.32);
  --brass-dim: rgba(201, 165, 87, 0.16);

  /* Brilliant gold — for medal / star / award-style emphasis where
     muted brass reads too washed-out against the walnut ground. The
     default-arsenal star toggle uses this; future "active/award"
     glyphs can pull the same token. */
  --gold-bright: #ffd700;

  /* Royal — secondary slate blue (Wedgwood / Intelligence Corps). */
  --royal:     #5681aa;
  --royal-dim: rgba(86, 129, 170, 0.22);

  /* Affirmative — Cabinet green. */
  --cabinet:    #2f5a3f;
  --cabinet-hi: #4a8060;

  /* Danger — oxblood. */
  --oxblood:    #7a2030;
  --oxblood-hi: #a52a3d;

  /* Paper tones — used on dossier sheets / vellum surfaces over dark. */
  --vellum:     #e8dcc4;
  --manila:     #c9a878;
  --paper-ink:  #1a1610;

  /* Body text on walnut. */
  --text-1: #d8d4cb;
  --text-2: rgba(216, 212, 203, 0.74);
  --text-3: rgba(216, 212, 203, 0.60);
  --text-4: rgba(216, 212, 203, 0.34);
  --hair:   rgba(216, 212, 203, 0.10);
  --hair-2: rgba(216, 212, 203, 0.18);

  /* Display — Cinzel chrome titles, dossier covers. */
  --display: var(--vellum);

  /* ── Back-compat aliases for pre-reskin class library ──────────
     The bg-/line-/accent-/st- token names are referenced from
     every view file; pointing them at the new vocabulary means
     panels / pills / buttons / alerts pick up the new palette
     without per-file edits. */
  --bg-0: var(--ground-0);
  --bg-1: var(--ground-2);
  --bg-2: var(--ground-3);
  --bg-3: var(--ground-4);
  --bg-4: var(--ground-3);

  --line-1: var(--hair);
  --line-2: var(--hair-2);
  --line-3: rgba(216, 212, 203, 0.28);

  --accent:      var(--brass);
  --accent-2:    var(--brass-lo);
  --accent-glow: rgba(201, 165, 87, 0.30);
  --accent-fg:   #1a1208;

  --panel-group-bg: rgba(201, 165, 87, 0.04);

  --st-active:     var(--cabinet-hi);
  --st-active-bg:  rgba(74, 128, 96, 0.14);
  --st-running:    var(--brass-hi);
  --st-running-bg: rgba(227, 200, 135, 0.10);
  --st-deploying:    var(--brass);
  --st-deploying-bg: rgba(201, 165, 87, 0.12);
  --st-blocked:    var(--oxblood-hi);
  --st-blocked-bg: rgba(165, 42, 61, 0.14);
  --st-idle:       var(--text-3);
  --st-idle-bg:    rgba(216, 212, 203, 0.04);
  --st-paused:     var(--royal);
  --st-paused-bg:  var(--royal-dim);

  /* 3.X — semantic fail / error red. 17+ view-file references
     to var(--fail) shipped under the assumption this token was
     defined; it wasn't, so every site fell back to inherited
     text colour. Pointing it at the existing oxblood-hi shade
     keeps the colour family coherent with the danger-button
     and st-blocked rails. Mirrored in the light theme below
     with the deeper oxblood for day-mode legibility. */
  --fail: var(--oxblood-hi);

  /* Fonts. */
  --font-display: "Cinzel", "Trajan Pro", "Optima", serif;
  --font-body:    "Inter Tight", "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-serif:   "EB Garamond", "Caslon", "Baskerville", Georgia, serif;
  --font-mono:    "JetBrains Mono", "IBM Plex Mono", ui-monospace, "Menlo", monospace;

  /* 3.166.1 — chrome-pinned dimensions. The page chrome's
     `<header>` (`position: sticky; top: 0; z-index: 10`) and
     `.bottom-cluster` (`position: sticky; bottom: 0; z-index: 5`)
     occupy these heights at the viewport edges; sticky widgets
     INSIDE `<main>` (page-designer's `header` / `footer` zones,
     introduced 3.160.0) offset their own `top` / `bottom` by these
     values so they pin BELOW the chrome header / ABOVE the chrome
     bottom-cluster rather than overlapping. Single source of truth
     for chrome dimensions; revisit when chrome padding /
     min-height changes. Values match `header`'s
     `padding: 0.3125rem 1.375rem; min-height: 3rem` (≈ 3.625rem
     vertical extent) and `.skylace-foot`'s
     `padding: 0.75rem 2rem` + one line of body text (≈ 2.75rem).
     Pages with a teleprinter ribbon inside the bottom-cluster
     (Squad detail) are taller; the variable is the conservative
     baseline for the common case. */
  --page-topnav-height: 3.625rem;
  --page-bottom-cluster-height: 2.75rem;

  /* Radii — polished, not playful. */
  --r-0: 0;
  --r-1: 2px;
  --r-2: 4px;
  --r-3: 6px;
  --panel-radius: var(--r-1);

  /* Root font-size dial — drives the rem scale for every UI surface.
     132% (2.162.0+) gives the whole UI a more legible base — every
     rem-based size inherits the bump. Respects the user's browser
     font-size for accessibility. Pre-2.162.0 this was 110%; bumped
     20% on operator feedback that body text was reading too small
     against the new walnut+brass chrome density.

     Dial convention (codified 2.213.0 audit pass): every property
     that should scale with the dial uses `rem`. That covers font-
     size, padding, margin, gap, top/right/bottom/left positioning,
     and width/height on chrome elements that read alongside text
     (cap-badges, theme-switch knob, user-menu avatar, small icons).
     `px` is reserved for properties that genuinely shouldn't scale:
     border widths ≤2px (hairlines), box-shadow geometry, focus-ring
     widths, container max/min-widths (those participate in viewport
     breakpoints, not the dial), grid-template min-widths, and SVG
     attribute widths on decorative pattern fills. Inline-styled
     chrome in admin-*-views.ts follows the same rule.
     This ship — every dial value cut by 10% so the whole UI reads
     denser without per-element retuning. Desktop 132% → 119%; the
     three mobile breakpoints follow proportionally (108→97, 92→83,
     80→72, 75→68). */
  font-size: 119%;

  /* Native form controls + scrollbars render in the dark palette by
     default. Chrome on a light system theme otherwise renders the
     focus ring + selection highlight against a black input bg. */
  color-scheme: dark;
}

/* ─── LIGHT THEME · Cabinet Office by daylight ─────────────────── */

:root[data-theme="light"] {
  --ground-0: #e3d8bd;
  --ground-1: #ebe2cc;
  --ground-2: #fbf6e8;
  --ground-3: #f4eed5;
  --ground-4: #e8dfc1;

  --brass:     #8b6e1f;
  --brass-hi:  #a48538;
  --brass-lo:  #5e4912;
  --brass-rim: rgba(139, 110, 31, 0.32);
  --brass-dim: rgba(139, 110, 31, 0.18);

  /* Brilliant gold — light theme companion. #ffd700 stays
     readable on the cream ground; a slightly darker amber would
     bleed into the brass tones, losing the "this is special" pop. */
  --gold-bright: #e8a800;

  --royal:     #3a6786;
  --royal-dim: rgba(58, 103, 134, 0.22);

  --cabinet:    #234d36;
  --cabinet-hi: #356f4f;

  --oxblood:    #6a1a26;
  --oxblood-hi: #8b2536;

  --vellum:     #fbf6e8;
  --manila:     #b88b3a;
  --paper-ink:  #1a1610;

  --text-1: #1a1610;
  --text-2: rgba(26, 22, 16, 0.78);
  --text-3: rgba(26, 22, 16, 0.62);
  --text-4: rgba(26, 22, 16, 0.42);
  --hair:   rgba(26, 22, 16, 0.12);
  --hair-2: rgba(26, 22, 16, 0.20);

  --display: var(--paper-ink);

  --bg-0: var(--ground-0);
  --bg-1: var(--ground-2);
  --bg-2: var(--ground-3);
  --bg-3: var(--ground-4);
  --bg-4: var(--ground-3);

  --line-1: var(--hair);
  --line-2: var(--hair-2);
  --line-3: rgba(26, 22, 16, 0.30);

  --accent:      var(--brass);
  --accent-2:    var(--brass-lo);
  --accent-glow: rgba(139, 110, 31, 0.22);
  --accent-fg:   #1a1208;

  --panel-group-bg: rgba(139, 110, 31, 0.05);

  --st-active:     var(--cabinet);
  --st-active-bg:  rgba(35, 77, 54, 0.10);
  --st-running:    var(--brass);
  --st-running-bg: rgba(139, 110, 31, 0.10);
  --st-deploying:    var(--brass);
  --st-deploying-bg: rgba(139, 110, 31, 0.10);
  --st-blocked:    var(--oxblood);
  --st-blocked-bg: rgba(106, 26, 38, 0.10);
  --st-idle:       var(--text-3);
  --st-idle-bg:    rgba(26, 22, 16, 0.04);
  --st-paused:     var(--royal);
  --st-paused-bg:  var(--royal-dim);

  /* See dark-theme block above — deeper oxblood here for legibility
     against the cream daylight backgrounds. */
  --fail: var(--oxblood);

  color-scheme: light;
}

/* Alert text colours need saturated mid-shades on light theme — the
   dark-theme pastels (#86efac etc.) are pastel-on-pastel-tint and
   unreadable against a light banner bg. */
:root[data-theme="light"] .alert.ok        { color: #15803d; }
:root[data-theme="light"] .alert.ok-banner { color: #15803d; }
:root[data-theme="light"] .alert.info      { color: #1d4ed8; }
:root[data-theme="light"] .alert.warn      { color: #a16207; }
:root[data-theme="light"] .alert.bad       { color: #b91c1c; }

/* Decorative backdrops: dot-grid + binary-bg need black speckles on
   light, white on dark. */
:root[data-theme="light"] .dot-grid {
  background-image: radial-gradient(circle at 1px 1px, rgba(26,22,16,0.08) 1px, transparent 1px);
}
:root[data-theme="light"] .binary-bg {
  background-image:
    repeating-linear-gradient(0deg, transparent 0 19px, rgba(26,22,16,0.04) 19px 20px),
    repeating-linear-gradient(90deg, transparent 0 19px, rgba(26,22,16,0.04) 19px 20px);
}

/* ─── Browser autofill override ────────────────────────────────────
   Chrome paints autofill bg/text with !important on internal
   pseudo-states. A long inset box-shadow forces our bg; the
   -webkit-text-fill-color forces our text colour past the override;
   the 5000s transition outruns Chrome's animation. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active,
textarea:-webkit-autofill,
select:-webkit-autofill {
  -webkit-text-fill-color: var(--text-1);
  -webkit-box-shadow: 0 0 0 1000px var(--ground-4) inset;
  caret-color: var(--text-1);
  transition: background-color 5000s ease-in-out 0s;
}

/* ─── Reset + body root ──────────────────────────────────────── */

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

html, body {
  margin: 0;
  padding: 0;
  color: var(--text-1);
  font-family: var(--font-body);
  font-size: 0.875rem;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  font-feature-settings: "ss01", "cv11";
  /* 2.191.0 — clip any horizontal overflow at the document root.
     Without this, body's `display: flex` cross-axis stretch makes
     header + footer extend to whatever the widest descendant ends
     up being (a long Cinzel title block, a fixed-width vellum
     sheet, a wide table) — so on a 320–414 px phone where one of
     those exceeds the viewport, the chrome rows visibly extend
     past the right edge, looking like they "don't span the page
     properly." Clipping at root caps every descendant to the
     viewport's cross axis; the wide element stays in the DOM but
     can't push the page wider. `overflow-x: clip` (Chrome 90+ /
     Safari 16+ / Firefox 81+) doesn't create a new block-formatting
     context, so it doesn't break `position: sticky` the way
     `overflow-x: hidden` can. */
  overflow-x: clip;
}

/* Page ground carries the walnut-grain motif as a layered gradient —
   no raster, < 1 KB on the wire. The 0.3s transition makes the
   day/night toggle feel like the room itself dimming. */
body {
  min-height: 100vh;
  /* 2.182.1 — flex-column body + flex:1 main pin the shared
     `.skylace-foot` to the viewport bottom on short pages (e.g. an
     empty dashboard) while letting `<main>` grow naturally with
     content on long pages. Login surfaces already used this shape
     locally; promoting it here means every signed-in dashboard
     surface inherits the same behaviour. */
  display: flex;
  flex-direction: column;
  background:
    radial-gradient(ellipse 120% 100% at 30% -10%, rgba(201,165,87,0.06), transparent 60%),
    radial-gradient(ellipse 100% 80% at 80% 120%, rgba(86,129,170,0.05), transparent 65%),
    linear-gradient(180deg, var(--ground-2) 0%, var(--ground-1) 100%);
  background-attachment: fixed;
  transition: background .3s ease, color .3s ease;
}
body > main { flex: 1 1 auto; }
:root[data-theme="light"] body {
  background:
    radial-gradient(ellipse 110% 90% at 30% 0%, rgba(255,255,255,0.6), transparent 65%),
    radial-gradient(ellipse 100% 80% at 80% 100%, rgba(139,110,31,0.06), transparent 60%),
    repeating-linear-gradient(91deg, rgba(0,0,0,0.012) 0 1px, transparent 1px 6px),
    linear-gradient(180deg, var(--ground-1) 0%, var(--ground-0) 100%);
}

a { color: var(--brass-hi); text-decoration: none; }
a:hover { color: var(--brass); text-decoration: underline; }
:root[data-theme="light"] a { color: var(--brass); }
:root[data-theme="light"] a:hover { color: var(--brass-lo); }

code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  background: var(--bg-2);
  border: 1px solid var(--line-1);
  padding: 0.0625rem 0.375rem;
  border-radius: var(--r-1);
  color: var(--text-1);
}

pre {
  background: var(--bg-2);
  border: 1px solid var(--line-1);
  color: var(--text-1);
  padding: 0.625rem 0.75rem;
  border-radius: var(--r-2);
  font-family: var(--font-mono);
  font-size: 0.7813rem;
  overflow-x: auto;
}
pre code { background: transparent; border: 0; padding: 0; color: inherit; }

/* ─── Decorative motif utilities ───────────────────────────────── */

/* Apply to any surface that wants the walnut-grain warm bias under
   it (cover panels, masthead). The body already carries this; use
   the utility only when you need it on a contained element. */
.walnut-grain {
  background-image:
    radial-gradient(ellipse 120% 100% at 30% -10%, rgba(201,165,87,0.06), transparent 60%),
    radial-gradient(ellipse 100% 80% at 80% 120%, rgba(86,129,170,0.05), transparent 65%),
    linear-gradient(180deg, var(--ground-2) 0%, var(--ground-1) 100%);
}
:root[data-theme="light"] .walnut-grain {
  background-image:
    radial-gradient(ellipse 110% 90% at 30% 0%, rgba(255,255,255,0.6), transparent 65%),
    radial-gradient(ellipse 100% 80% at 80% 100%, rgba(139,110,31,0.06), transparent 60%),
    linear-gradient(180deg, var(--ground-2) 0%, var(--ground-3) 100%);
}

/* Tooled-leather desk surface — for briefing tables / muster-roll
   backdrops / operational maps. Cabinet-green base + diagonal
   crosshatch + radial gloss. */
.leather-inlay {
  background-image:
    radial-gradient(ellipse 80% 60% at 50% 50%, rgba(255,255,255,0.04), transparent 70%),
    repeating-linear-gradient(45deg, rgba(0,0,0,0.04) 0 1px, transparent 1px 4px),
    repeating-linear-gradient(-45deg, rgba(0,0,0,0.04) 0 1px, transparent 1px 4px),
    linear-gradient(135deg, #2a4e3a 0%, #1c3528 100%);
  box-shadow:
    inset 0 0 0 1px rgba(0,0,0,0.6),
    inset 0 1px 0 rgba(255,255,255,0.04),
    inset 0 0 24px rgba(0,0,0,0.5);
  border-radius: var(--r-2);
}
:root[data-theme="light"] .leather-inlay {
  background-image:
    radial-gradient(ellipse 80% 60% at 50% 50%, rgba(255,255,255,0.10), transparent 70%),
    repeating-linear-gradient(45deg, rgba(0,0,0,0.05) 0 1px, transparent 1px 4px),
    repeating-linear-gradient(-45deg, rgba(0,0,0,0.05) 0 1px, transparent 1px 4px),
    linear-gradient(135deg, #506a55 0%, #3a5340 100%);
  box-shadow:
    inset 0 0 0 1px rgba(0,0,0,0.4),
    inset 0 1px 0 rgba(255,255,255,0.18),
    inset 0 0 24px rgba(0,0,0,0.2);
}

/* Vellum dossier sheet — for high-stakes forms (Login, Commission,
   Mission objective). Off-white gradient + drop shadow + inset sheen,
   reads as paper on a desk. The slight tilt is per-instance via
   `style="transform: rotate(-0.4deg)"`; the class only handles the
   surface material. */
.vellum {
  background: linear-gradient(180deg, #ebe2cc 0%, #ddd0b3 100%);
  color: var(--paper-ink);
  box-shadow:
    0 1px 0 rgba(255,255,255,0.6) inset,
    0 -1px 0 rgba(0,0,0,0.10) inset,
    0 8px 24px rgba(0,0,0,0.5),
    0 2px 6px rgba(0,0,0,0.3);
  border-radius: var(--r-1);
}
:root[data-theme="light"] .vellum {
  background: linear-gradient(180deg, #f0e6c8 0%, #e3d6ac 100%);
  box-shadow:
    0 1px 0 rgba(255,255,255,0.7) inset,
    0 -1px 0 rgba(0,0,0,0.08) inset,
    0 6px 18px rgba(0,0,0,0.10),
    0 2px 4px rgba(0,0,0,0.06);
}

/* OS-map contour underlay — for hero panels (Mission cover, Squad
   dossier header). Repeats; opacity differs per theme. The svg
   data-URI ships inline; no extra request. */
.contour-map {
  position: absolute; inset: 0;
  background-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='600' height='400' viewBox='0 0 600 400'><g fill='none' stroke='%23c9a557' stroke-opacity='0.10' stroke-width='0.6'><path d='M0,210 C60,200 120,230 200,225 S360,180 460,200 S600,240 600,235'/><path d='M0,180 C70,170 140,195 220,190 S380,150 480,168 S600,200 600,198'/><path d='M0,240 C50,235 130,265 210,258 S380,220 480,238 S600,275 600,270'/><path d='M0,150 C80,140 160,165 240,160 S400,120 500,135 S600,165 600,165'/><path d='M0,275 C40,272 120,300 200,295 S370,260 470,278 S600,310 600,305'/><path d='M0,120 C90,108 180,135 260,130 S420,92 520,108 S600,135 600,135'/><path d='M0,310 C60,308 140,335 220,330 S390,300 490,315 S600,340 600,338'/><ellipse cx='180' cy='220' rx='28' ry='8'/><ellipse cx='180' cy='220' rx='40' ry='14'/><ellipse cx='420' cy='200' rx='22' ry='6'/><ellipse cx='420' cy='200' rx='36' ry='12'/><ellipse cx='420' cy='200' rx='52' ry='18'/></g></svg>");
  background-repeat: repeat;
  opacity: 0.5;
  pointer-events: none;
  z-index: 0;
}
:root[data-theme="light"] .contour-map { opacity: 0.18; filter: sepia(0.6); }

/* ─── Decorative speckle backdrops (legacy aliases) ──────────── */

.dot-grid {
  position: absolute; inset: 0;
  background-image: radial-gradient(circle at 1px 1px, rgba(216,212,203,0.06) 1px, transparent 1px);
  background-size: 22px 22px;
  pointer-events: none;
  z-index: 0;
}
.binary-bg {
  position: absolute; inset: 0;
  background-image:
    repeating-linear-gradient(0deg, transparent 0 19px, rgba(216,212,203,0.020) 19px 20px),
    repeating-linear-gradient(90deg, transparent 0 19px, rgba(216,212,203,0.020) 19px 20px);
  pointer-events: none;
  z-index: 0;
}

/* ─── Skylace wordmark — Cinzel + brass crest ──────────────────
   The old "chrome + tint" gradient mark retired in 2.155.0; the
   wordmark now reads as a Cinzel serif inscription. The icosahedron
   brass crest sits inline before the text; both are wrapped in a
   single `.skylace-mark` anchor that's the home-link affordance. */
.skylace-mark {
  display: inline-flex;
  align-items: center;
  /* 3.29 — gap floored at the ≤1024-tier rendered px so the
     crest-to-wordmark spacing doesn't collapse under the narrower
     root dial tiers (≤720 / ≤480 / ≤360). Letter-spacing is
     em-relative so it tracks the floored font-size automatically. */
  gap: max(9.7px, 0.625rem);
  font-family: var(--font-display);
  font-weight: 600;
  /* 3.29 — operator-flagged "the Skylace logo at 15.52px is
     perfect; at 12.45px it looks shit". 15.52px is the ≤1024-
     tier rendered size (1rem * 97% dial); narrower tiers used
     to drop the wordmark to 12.45 / 10.8 / 10.2 px via the root
     dial + the ≤720 explicit `font-size: 0.9375rem` override.
     `max(15.52px, 1rem)` pins the wordmark at the ≤1024 rendered
     size as the floor at every narrower viewport, while the
     default (>1024) tier still renders at 16px (max wins the
     larger value). Same floor pattern applies to every other
     header chrome rule below (crest, theme-switch, help-button,
     user-avatar) so the masthead no longer scales below the
     "perfect" reading the operator named. */
  font-size: max(15.52px, 1rem);
  letter-spacing: 0.26em;
  line-height: 1;
  color: var(--display);
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  transition: color .3s ease;
}
.skylace-mark:hover { text-decoration: none; color: var(--brass-hi); }
:root[data-theme="light"] .skylace-mark:hover { color: var(--brass); }

/* The icosahedron brass crest. Used inline in the masthead +
   anywhere else the Skylace identity appears. Two sizes by class.
   Sizes in rem so the global font-size dial scales them with the
   wordmark. */
.skylace-crest {
  display: inline-block;
  /* 3.29 — floor at the ≤1024-tier rendered size (29.1px =
     1.875rem * 97% dial). See `.skylace-mark` for the rationale. */
  width: max(29.1px, 1.875rem);
  height: max(29.1px, 1.875rem);
  flex-shrink: 0;
  vertical-align: middle;
}
.skylace-crest.lg { width: 4rem; height: 4rem; }

/* Legacy `.skylace-mark .layer` was the chrome+tint two-layer.
   Hide it gracefully if any view still emits it; the wordmark
   already renders correctly without the layered fallback. */
.skylace-mark .layer { display: none; }

/* ─── Shared utility classes ─────────────────────────────────── */

.kbd, .mono { font-family: var(--font-mono); }

.uppercase-label {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-3);
}

/* Eyebrow — the design's signature small-caps mono label that
   leads every section ("OPERATIONAL · LAST 24H"). Wider tracking
   + dimmer colour than .uppercase-label. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font: 500 0.625rem/1 var(--font-mono);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--text-3);
}
.eyebrow.brass { color: var(--brass-hi); }
:root[data-theme="light"] .eyebrow.brass { color: var(--brass); }
.eyebrow::before {
  content: "";
  width: 0.875rem;
  height: 1px;
  background: var(--brass-rim);
}
/* Object-name link variant — used wherever a parent / sibling
   entity name appears as plain text in chrome (eyebrows, dossier
   trade-row values, dossier-sheet rows). Inherits the surface's
   colour + tracking so the visual remains the same at rest; an
   underline appears on hover/focus so the affordance is
   discoverable. 3.26.0 generalised from the 2.228.2
   `.eyebrow-link` (then eyebrow-only) once the principle was
   applied across detail-page parent-object names. */
.entity-link {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color .15s;
}
.entity-link:hover,
.entity-link:focus-visible {
  border-bottom-color: currentColor;
}

/* Display-tier headings — Cinzel chrome. Used for page hero
   titles, dossier covers, regimental name plates. */
h1.display, h2.display {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: 0.12em;
  line-height: 1.04;
  margin: 0;
  color: var(--display);
  text-transform: uppercase;
}
h1.display { font-size: 2.25rem; }
h2.display { font-size: 1.625rem; }

.muted { color: var(--text-3); }
.num { font-variant-numeric: tabular-nums; text-align: right; }

/* Serif prose — debrief notes, communiqué body. */
.prose-serif,
.serif-italic {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 1.1em;
  color: var(--text-2);
}

/* ─── Panels (raised surfaces / cards) ────────────────────────
   Walnut on dark, paper on light. The bevel + brass hairline + lit-
   from-above sheen is built from box-shadow layers; the brass-rim
   border is the visible edge. */

.panel {
  position: relative;
  background: linear-gradient(180deg, var(--ground-2) 0%, var(--ground-1) 100%);
  border: 1px solid var(--brass-rim);
  border-radius: var(--panel-radius);
  padding: 1.125rem;
  margin-bottom: 1.125rem;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.04),
    inset 0 -1px 0 rgba(0,0,0,0.35),
    inset 0 0 0 1px rgba(0,0,0,0.20),
    0 1px 0 rgba(255,255,255,0.02);
  transition: background .3s ease, border-color .3s ease, box-shadow .3s ease;
}
:root[data-theme="light"] .panel {
  background: linear-gradient(180deg, var(--ground-2) 0%, var(--ground-3) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.7),
    inset 0 -1px 0 rgba(0,0,0,0.04),
    0 1px 2px rgba(0,0,0,0.04),
    0 6px 14px rgba(0,0,0,0.04);
}

/* Affirmative / Restricted / Royal panel accents — change the
   brass rim to cabinet / oxblood / royal for tonally-tagged
   panels (Comms · ON AIR, Restricted · EYES ONLY etc.). */
.panel.accent-cabinet { border-color: rgba(74,128,96,0.32); }
.panel.accent-oxblood { border-color: rgba(165,42,61,0.32); }
.panel.accent-royal   { border-color: rgba(86,129,170,0.32); }
:root[data-theme="light"] .panel.accent-cabinet { border-color: rgba(35,77,54,0.32); }
:root[data-theme="light"] .panel.accent-oxblood { border-color: rgba(106,26,38,0.32); }
:root[data-theme="light"] .panel.accent-royal   { border-color: rgba(58,103,134,0.32); }

/* `.is-probing` — transient dim-and-collect state. Applied to
   any panel / pane while a server-render-driven widget is
   reattempting a failing upstream read inside the grace-period
   budget; the element dims, blocks interaction, and shows a
   `.probing-indicator` pulse so the operator sees motion rather
   than a contradictory error banner. Removed (via class toggle
   or innerHTML swap) once the poll resolves to live data, or
   transitioned to a visible alert if the probe budget elapses.
   3.294.0 introduced the hook scoped to `.panel.is-probing`;
   3.302.0 dropped the `.panel.` prefix so the second adopter
   (operator-chat's transcript pane, which dims independently of
   its outer panel so the composer stays interactive) can reuse
   the same rule without a parallel CSS hook. */
.is-probing {
  opacity: 0.55;
  pointer-events: none;
  transition: opacity 0.18s ease;
}
.is-probing .probing-indicator {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font: 500 0.6875rem/1 var(--font-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
  margin: 0.375rem 0;
}
.is-probing .probing-indicator::before {
  content: "";
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: currentColor;
  animation: skylace-pulse-dot 1.6s ease-in-out infinite;
}
:root[data-theme="light"] .is-probing .probing-indicator::before {
  animation: none;
  box-shadow: none;
}

/* Elevated panel chrome — applied to Squad Info on elevated
   Squads (Command Squad in HQ Mission today; future Company-
   altitude Squads inherit). Layers a fuller brass border with a
   subtle inner glow + gilt-leaf corner ornaments to mark the
   panel as Company-altitude infrastructure. The base `.panel`
   styling carries through (gradient, padding, radius); this
   override thickens the rim, deepens the brass tone, adds a
   warm shadow, and stamps a `★` rosette in each corner via
   `::before` / `::after` pseudo-elements on a wrapper. */
.panel--elevated {
  border: 1.5px solid var(--brass);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.05),
    inset 0 -1px 0 rgba(0,0,0,0.35),
    inset 0 0 0 1px var(--brass-dim),
    0 0 0 1px var(--brass-rim),
    0 0 12px var(--brass-rim);
  background:
    /* gilt corner rosettes — top-left + top-right + bottom-left + bottom-right */
    radial-gradient(circle at 0.5rem 0.5rem,           var(--brass-hi) 1px, transparent 2.5px),
    radial-gradient(circle at calc(100% - 0.5rem) 0.5rem,           var(--brass-hi) 1px, transparent 2.5px),
    radial-gradient(circle at 0.5rem calc(100% - 0.5rem),           var(--brass-hi) 1px, transparent 2.5px),
    radial-gradient(circle at calc(100% - 0.5rem) calc(100% - 0.5rem), var(--brass-hi) 1px, transparent 2.5px),
    linear-gradient(180deg, var(--ground-2) 0%, var(--ground-1) 100%);
  background-repeat: no-repeat;
}
:root[data-theme="light"] .panel--elevated {
  border-color: var(--brass-lo);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.7),
    inset 0 -1px 0 rgba(0,0,0,0.04),
    inset 0 0 0 1px var(--brass-dim),
    0 0 0 1px var(--brass-rim),
    0 2px 8px rgba(139,110,31,0.18);
  background:
    radial-gradient(circle at 0.5rem 0.5rem,           var(--brass) 1px, transparent 2.5px),
    radial-gradient(circle at calc(100% - 0.5rem) 0.5rem,           var(--brass) 1px, transparent 2.5px),
    radial-gradient(circle at 0.5rem calc(100% - 0.5rem),           var(--brass) 1px, transparent 2.5px),
    radial-gradient(circle at calc(100% - 0.5rem) calc(100% - 0.5rem), var(--brass) 1px, transparent 2.5px),
    linear-gradient(180deg, var(--ground-2) 0%, var(--ground-3) 100%);
  background-repeat: no-repeat;
}

.panel-2 {
  background: var(--bg-2);
  border: 1px solid var(--line-1);
  border-radius: var(--panel-radius);
}

.panel h2 {
  margin: 0 0 0.875rem;
  font-family: var(--font-body);
  font-size: 0.875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--display);
}

/* Panel header row (.panel-header): title left, actions pinned
   top-right. Used by every panel that exposes a `+ X` button.
   `flex-wrap: wrap` on the row lets the `.actions` cluster drop
   onto a second line at narrow viewports; without it, the H2's
   `min-width: auto` default (= content min-width) plus
   `.actions { flex-shrink: 0 }` combine to push the button cluster
   off the right edge of the panel on mobile. `min-width: 0` on
   the heading lets the title flex-shrink to fit alongside actions
   for as long as both can share a row, then wraps below when the
   row gives up. */
.panel-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.875rem;
}
.panel-header h1,
.panel-header h2 {
  margin: 0;
  min-width: 0;
  flex: 1 1 auto;
}
.panel-header .actions {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  flex-shrink: 0;
}

/* Panel-group: visually clusters a head panel and its sub-panels
   into one block. Left brass stripe pins the cluster; nested
   `.panel` children lose their own border + background.

   This ship — horizontal padding migrated off the group and
   onto each child panel. The group itself now has zero horizontal
   padding, so each child fills the full width from the brass
   left-stripe to the right brass border; children carry the
   1.125rem inset themselves so content keeps the same visual
   breathing room. Two visible effects:
   1. In light theme the per-panel cream background (which leaks
      through the intended `.panel-group > .panel { background:
      transparent }` rule because `:root[data-theme="light"]
      .panel`'s 0,2,1 specificity beats the override's 0,2,0)
      now extends edge-to-edge instead of floating with a gap.
   2. The hairline separators between sibling panels now run
      brass-to-brass rather than stopping short of the group's
      side borders. */
.panel-group {
  border: 1px solid var(--hair-2);
  border-left: 3px solid var(--brass);
  background: var(--panel-group-bg);
  border-radius: var(--panel-radius);
  padding: 0.25rem 0;
  margin-bottom: 1.125rem;
}
.panel-group > .panel {
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 0.875rem 1.125rem;
  margin: 0;
  border-bottom: 1px solid var(--hair);
  box-shadow: none;
}
.panel-group > .panel:last-child { border-bottom: none; }
.panel-group > .panel h3 {
  margin: 0 0 0.75rem;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--display);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* ─── Buttons — engraved brass ────────────────────────────────
   The button family reads as machined brass on walnut: gradient
   fill + inset highlight/shadow + textShadow. Four kinds:
     - .primary  — engraved brass, the default action.
     - .ghost    — transparent + brass rim, for Cancel.
     - .danger   — oxblood gradient, destructive.
     - (default) — quiet, transparent + hair border.
   Sizes via .sm / .lg modifiers. */

button, .button-link {
  --btn-bg: transparent;
  --btn-fg: var(--text-1);
  --btn-bd: var(--hair);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  height: 2.125rem;
  padding: 0 0.875rem;
  border-radius: var(--r-1);
  border: 1px solid var(--btn-bd);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font: 600 0.75rem/1 var(--font-body);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s, transform 0.06s, filter 0.15s;
  white-space: nowrap;
  user-select: none;
  text-decoration: none;
}
button:hover, .button-link:hover {
  border-color: var(--brass-rim);
  color: var(--display);
  text-decoration: none;
}
button:active, .button-link:active { transform: translateY(0.5px); }
button:focus-visible, .button-link:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 2px;
}
button:disabled, .button-link[aria-disabled="true"] { opacity: 0.40; cursor: not-allowed; }

/* Engraved-brass primary. */
button.primary, .button-link.primary {
  --btn-fg: #1a1208;
  --btn-bd: var(--brass);
  background: linear-gradient(180deg, var(--brass-hi) 0%, var(--brass) 55%, var(--brass-lo) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.45),
    inset 0 -1px 0 rgba(0,0,0,0.45),
    0 1px 0 rgba(0,0,0,0.4);
  text-shadow: 0 1px 0 rgba(255,255,255,0.25);
}
button.primary:hover, .button-link.primary:hover {
  filter: brightness(1.08);
  color: #1a1208;
}
button.primary:active, .button-link.primary:active {
  filter: brightness(0.92);
  transform: translateY(1px);
}
/* Light-mode brass tokens are darker (#a48538 → #8b6e1f → #5e4912)
   than dark-mode brass (#e3c887 → #c9a557 → #8b7236), and the
   engraved-medallion primary depends on a bright gradient under
   dark text for the engraved-into-metal effect. 3.40.2 first tried
   inverting to cream text on dark brass; contrast crashed at the
   gradient top (cream on #a48538 ≈ 3:1) and the base hover rule's
   filter: brightness(1.08) lifted the top further, masking the
   problem on hover only. Reusing the dark-mode brass gradient for
   the medallion in light mode too (the brass is the same metal
   regardless of page background) keeps the dark-text-on-bright-
   brass contrast the engraving was tuned for. */
:root[data-theme="light"] button.primary,
:root[data-theme="light"] .button-link.primary {
  --btn-bd: #c9a557;
  background: linear-gradient(180deg, #e3c887 0%, #c9a557 55%, #8b7236 100%);
}

/* Ghost — outlined Cancel. */
button.ghost, .button-link.ghost {
  --btn-bg: transparent;
  --btn-bd: var(--brass-rim);
  color: var(--text-1);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.03);
}
button.ghost:hover, .button-link.ghost:hover {
  border-color: var(--brass);
  color: var(--display);
  background: transparent;
}

/* Danger — oxblood. */
button.danger, .button-link.danger {
  --btn-fg: var(--vellum);
  --btn-bd: var(--oxblood);
  background: linear-gradient(180deg, var(--oxblood-hi) 0%, var(--oxblood) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.18),
    inset 0 -1px 0 rgba(0,0,0,0.45);
  text-shadow: 0 1px 0 rgba(0,0,0,0.4);
}
button.danger:hover, .button-link.danger:hover {
  filter: brightness(1.10);
  color: var(--vellum);
}

button.sm, .button-link.sm { height: 1.75rem; padding: 0 0.625rem; font-size: 0.6875rem; }
button.lg, .button-link.lg { height: 2.75rem; padding: 0 1.375rem; font-size: 0.8125rem; }

/* ─── Inputs — dossier entry fields ──────────────────────────
   Recessed well bg, hair border, no visible outline. Focus state
   raises the brass rim. */

input[type=text], input[type=email], input[type=password], input[type=url],
input[type=number], input[type=search], select, textarea {
  display: block;
  width: 100%;
  height: 2.25rem;
  padding: 0 0.75rem;
  background: var(--ground-4);
  border: 1px solid var(--hair);
  border-radius: var(--r-1);
  color: var(--text-1);
  font: 400 0.875rem/1.4 var(--font-body);
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
  box-shadow: inset 0 1px 1px rgba(0,0,0,0.30);
}
:root[data-theme="light"] input[type=text],
:root[data-theme="light"] input[type=email],
:root[data-theme="light"] input[type=password],
:root[data-theme="light"] input[type=url],
:root[data-theme="light"] input[type=number],
:root[data-theme="light"] input[type=search],
:root[data-theme="light"] select,
:root[data-theme="light"] textarea {
  box-shadow: inset 0 1px 1px rgba(0,0,0,0.06);
}
input::placeholder, textarea::placeholder { color: var(--text-4); font-style: italic; font-size: 1.1em; }
input:hover, select:hover, textarea:hover { border-color: var(--brass-rim); }
input:focus, select:focus, textarea:focus {
  border-color: var(--brass);
  box-shadow:
    inset 0 1px 1px rgba(0,0,0,0.30),
    0 0 0 3px var(--brass-dim);
  background: var(--ground-3);
}
:root[data-theme="light"] input:focus,
:root[data-theme="light"] select:focus,
:root[data-theme="light"] textarea:focus {
  box-shadow:
    inset 0 1px 1px rgba(0,0,0,0.06),
    0 0 0 3px var(--brass-dim);
  background: var(--ground-2);
}

textarea { height: auto; padding: 0.625rem 0.75rem; resize: vertical; min-height: 5rem; }

select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><path d='M2 4l4 4 4-4' stroke='%23c9a557' stroke-width='1.4' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 0.625rem center;
  padding-right: 1.875rem;
}
select option { background: var(--ground-2); color: var(--text-1); }

label {
  display: block;
  font-size: 0.8125rem;
  color: var(--text-2);
}

.field-label {
  display: block;
  font: 500 0.625rem/1 var(--font-mono);
  letter-spacing: 0.20em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: 0.375rem;
}

/* Italic-serif text sizes bumped in 2.171.0: the previous 0.75rem
   tier read too small against the 2.162.0 font-size-dial bump
   (132%), so .field-hint / .hint / .form-help / etc. all stepped up
   one notch. See WEBSITE_STYLE_GUIDE §"Typography — italic-serif
   tier" for the full table. */
.field-hint, .hint { font-family: var(--font-serif); font-style: italic; font-size: 0.9625rem; color: var(--text-3); margin-top: 0.375rem; }
/* 2.178.0 — field-error rebalanced for prominence. The pre-2.178
   styling (0.7188rem, plain colour, no glyph) read as a quiet
   footnote against the help-text below it; operators missed it.
   Now: weighty body-size text, a warning glyph, a tinted background
   panel with a left-rail in oxblood, and the corresponding input
   picks up a red border via `.has-error input` further down. */
.field-error, .field-err {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin-top: 0.5rem;
  padding: 0.5rem 0.75rem;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.35;
  color: var(--oxblood-hi);
  background: var(--st-blocked-bg, rgba(165,42,61,0.10));
  border: 1px solid rgba(165,42,61,0.32);
  border-left: 3px solid var(--oxblood-hi);
  border-radius: var(--r-1, 6px);
}
.field-error-glyph {
  flex: 0 0 auto;
  font-size: 1rem;
  line-height: 1;
  margin-top: 1px;
}
.field-error-text { flex: 1 1 auto; }
:root[data-theme="light"] .field-error,
:root[data-theme="light"] .field-err {
  color: var(--oxblood);
  background: rgba(165,42,61,0.06);
  border-color: rgba(165,42,61,0.40);
  border-left-color: var(--oxblood);
}
/* Mark the input inside a label that paired with a field-error
   (`has-error` is stamped by the admin-form-fields renderers). */
.has-error input,
.has-error textarea,
.has-error select {
  border-color: var(--oxblood-hi);
  box-shadow: 0 0 0 1px var(--oxblood-hi) inset;
}
:root[data-theme="light"] .has-error input,
:root[data-theme="light"] .has-error textarea,
:root[data-theme="light"] .has-error select {
  border-color: var(--oxblood);
  box-shadow: 0 0 0 1px var(--oxblood) inset;
}
.form-help { font-family: var(--font-serif); font-style: italic; font-size: 1.0625rem; color: var(--text-3); margin: 0.25rem 0 0; }

/* 2.178.0 — `Couldn't save` banner upgrade. Same colour vocab as
   `.alert.bad` but with the warning glyph, bolder copy, and a tad
   more padding so it stops being mistaken for an info line. */
.form-error-banner {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.875rem 1rem;
  margin: 0 0 1.125rem;
  font-size: 0.9375rem;
  line-height: 1.4;
}
.form-error-banner-glyph {
  flex: 0 0 auto;
  font-size: 1.25rem;
  line-height: 1;
  margin-top: 0.125rem;
}
.form-error-banner-body { flex: 1 1 auto; }
.form-error-banner-body strong {
  font-weight: 700;
  letter-spacing: 0.01em;
}

/* ─── 3.X — Top-of-form error banner + toast container ───────
   Massive top-of-page banner rendered ABOVE the form's H2 on a
   validation-fail re-render. Replaces the pre-3.X pattern where
   Edit forms passed a single inline `editError` rendered next to
   the field that caused it — operators routinely missed that
   styling (witness: the 3.235.0 reserved-callsign reject the
   operator only spotted by squinting at small text). The toast
   is the peripheral-vision nudge that pairs with it; both fire
   together on every validation-fail re-render. */
.form-feedback-banner {
  display: flex;
  align-items: flex-start;
  gap: 0.875rem;
  padding: 1.25rem 1.375rem;
  margin: 0 0 1.5rem;
  font-size: 1.0625rem;
  line-height: 1.45;
  border-radius: var(--r-2, 8px);
  border: 2px solid;
  border-left-width: 6px;
}
/* Night-mode text colour matches the sibling .alert.bad convention
   (further down this file) — bright red tint over the dark walnut
   ground. The pre-fix `--oxblood-hi` (#a52a3d, dark red) on
   `--ground-0` (#080a10, near-black) cleared ~3.5:1 contrast,
   below WCAG AA for normal text. The accent (border + glyph)
   stays oxblood-hi so the brand colour is preserved on the
   chrome; only the readable body text shifts to the bright tint. */
.form-feedback-banner--error {
  color: #fca5a5;
  background: rgba(165, 42, 61, 0.12);
  border-color: var(--oxblood-hi);
}
.form-feedback-banner--error .form-feedback-banner-glyph {
  color: var(--oxblood-hi);
}
:root[data-theme="light"] .form-feedback-banner--error {
  color: var(--oxblood);
  background: rgba(165, 42, 61, 0.08);
  border-color: var(--oxblood);
}
:root[data-theme="light"] .form-feedback-banner--error .form-feedback-banner-glyph {
  color: var(--oxblood);
}
.form-feedback-banner-glyph {
  flex: 0 0 auto;
  font-size: 1.75rem;
  line-height: 1;
  margin-top: 0.125rem;
}
.form-feedback-banner-body { flex: 1 1 auto; min-width: 0; }
.form-feedback-banner-title {
  display: block;
  font-weight: 700;
  font-size: 1.1875rem;
  letter-spacing: 0.01em;
  margin-bottom: 0.25rem;
}
.form-feedback-banner-msg {
  margin: 0.25rem 0 0;
  font-weight: 500;
}
.form-feedback-banner-fields {
  margin: 0.5rem 0 0;
  padding-left: 1.25rem;
  font-size: 0.95rem;
  font-weight: 500;
}
.form-feedback-banner-fields li { margin-top: 0.25rem; }

/* Toast container — fixed top-center of the viewport. Stacks
   newer toasts on top (column-reverse on a top-anchored stack
   puts the most-recent append at the topmost visual position),
   so the operator sees the most-recent action first.
   `pointer-events: none` on the container lets clicks through
   the empty space below the stack while each toast itself
   re-enables them for the dismiss button. */
.skylace-toast-container {
  position: fixed;
  top: 1.25rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 9999;
  display: flex;
  flex-direction: column-reverse;
  gap: 0.625rem;
  width: min(28rem, calc(100vw - 2rem));
  pointer-events: none;
}
.skylace-toast {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  padding: 0.875rem 2.5rem 0.875rem 1rem;
  border-radius: var(--r-2, 8px);
  font-size: 0.9625rem;
  line-height: 1.4;
  font-weight: 500;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.32), 0 2px 6px rgba(0, 0, 0, 0.2);
  border: 1px solid;
  border-left-width: 4px;
  pointer-events: auto;
  animation: skylace-toast-in 0.18s ease-out;
}
.skylace-toast--dismissing {
  animation: skylace-toast-out 0.22s ease-in forwards;
}
.skylace-toast--success {
  color: #e6f4e0;
  background: #2f5d2f;
  border-color: #5a8c5a;
}
:root[data-theme="light"] .skylace-toast--success {
  color: #1d3e1d;
  background: #e9f5e3;
  border-color: #3a7c3a;
}
.skylace-toast--failure {
  color: #fff1f2;
  background: var(--oxblood-hi);
  border-color: #e2536a;
}
:root[data-theme="light"] .skylace-toast--failure {
  color: #fff;
  background: var(--oxblood);
  border-color: var(--oxblood-hi);
}
.skylace-toast-title {
  font-weight: 700;
  letter-spacing: 0.01em;
  margin-right: 0.25rem;
}
.skylace-toast-message { flex: 1 1 auto; min-width: 0; }
.skylace-toast-close {
  position: absolute;
  top: 0.375rem;
  right: 0.5rem;
  background: transparent;
  border: 0;
  color: inherit;
  font-size: 1.25rem;
  line-height: 1;
  padding: 0.25rem 0.375rem;
  cursor: pointer;
  opacity: 0.75;
}
.skylace-toast-close:hover { opacity: 1; }
@keyframes skylace-toast-in {
  from { transform: translateY(-8px); opacity: 0; }
  to   { transform: translateY(0); opacity: 1; }
}
@keyframes skylace-toast-out {
  from { transform: translateX(0); opacity: 1; }
  to   { transform: translateX(16px); opacity: 0; }
}

/* ─── Form actions row ──────────────────────────────────────── */

.form-actions {
  display: flex;
  gap: 0.625rem;
  justify-content: flex-end;
  border-top: 1px solid var(--hair);
  padding-top: 0.875rem;
  margin-top: 1.125rem;
}

/* ─── Status pills (canonical) ────────────────────────────────
   The design's "standing pills" (Active duty / Standing by / On
   comms / Detained / Withdrawn / Deploying) plus the legacy is-*
   aliases used by existing views. */

.pill {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 0.1875rem 0.625rem;
  border-radius: var(--r-1);
  border: 1px solid currentColor;
  white-space: nowrap;
  font-weight: 500;
}
.pill .dot {
  width: 0.375rem; height: 0.375rem;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 6px currentColor;
}

.pill.is-active   { color: var(--st-active);    background: var(--st-active-bg); }
.pill.is-running  { color: var(--st-running);   background: var(--st-running-bg); }
.pill.is-deploying{ color: var(--st-deploying); background: var(--st-deploying-bg); }
.pill.is-blocked  { color: var(--st-blocked);   background: var(--st-blocked-bg); }
.pill.is-idle     { color: var(--st-idle);      background: var(--st-idle-bg); }
.pill.is-paused   { color: var(--st-paused);    background: var(--st-paused-bg); }

/* Legacy admin pill aliases — bridged to status colours. */
.pill.ok   { color: var(--st-active);    background: var(--st-active-bg);    border-color: var(--st-active); }
.pill.warn { color: var(--st-deploying); background: var(--st-deploying-bg); border-color: var(--st-deploying); }
.pill.bad  { color: var(--st-blocked);   background: var(--st-blocked-bg);   border-color: var(--st-blocked); }

.pill.role-super-admin   { color: var(--brass-hi);   background: rgba(227,200,135,0.10); border-color: var(--brass); }
.pill.role-company-admin { color: var(--royal);      background: var(--royal-dim);       border-color: var(--royal); }
.pill.role-user          { color: var(--text-2);     background: var(--bg-3);            border-color: var(--hair-2); }
:root[data-theme="light"] .pill.role-super-admin { color: var(--brass-lo); background: rgba(139,110,31,0.10); border-color: var(--brass); }

/* Pulse on dots for the "live" states. Dark theme only; the design
   reserves animation for the lit-room feel. */
@keyframes skylace-pulse-dot {
  0%, 100% { box-shadow: 0 0 0 0 currentColor; opacity: 1; }
  50%      { box-shadow: 0 0 0 3px transparent; opacity: 0.6; }
}
.pill.is-active .dot, .pill.is-running .dot, .pill.is-deploying .dot {
  animation: skylace-pulse-dot 1.6s ease-in-out infinite;
}
:root[data-theme="light"] .pill .dot { animation: none; box-shadow: none; }

/* ─── Standing pill — the design's explicit "operative readiness"
   token. Used on operative cards (Squad detail's muster roll) and
   the Joe dossier cover. */
.standing-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  font: 500 0.625rem/1 var(--font-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 0.3125rem 0.5625rem 0.3125rem 0.5rem;
  border-radius: var(--r-1);
  border: 1px solid currentColor;
  white-space: nowrap;
}
.standing-pill .dot {
  width: 0.375rem; height: 0.375rem;
  border-radius: 50%;
  background: currentColor;
}
.standing-pill.active   { color: var(--cabinet-hi); background: rgba(74,128,96,0.14); }
.standing-pill.oncomms  { color: var(--brass-hi);   background: rgba(201,165,87,0.10); }
.standing-pill.standby  { color: var(--text-2);     background: rgba(216,212,203,0.06); }
.standing-pill.detained { color: var(--oxblood-hi); background: rgba(165,42,61,0.14); }
.standing-pill.withdrawn{ color: var(--text-4);     background: rgba(0,0,0,0.18); }
.standing-pill.deploying{ color: var(--royal);      background: rgba(86,129,170,0.18); }
:root[data-theme="light"] .standing-pill.active   { color: var(--cabinet); background: rgba(35,77,54,0.10); }
:root[data-theme="light"] .standing-pill.oncomms  { color: var(--brass);   background: rgba(139,110,31,0.10); }
:root[data-theme="light"] .standing-pill.detained { color: var(--oxblood); background: rgba(106,26,38,0.10); }
:root[data-theme="light"] .standing-pill.withdrawn{ color: var(--text-4);  background: rgba(0,0,0,0.05); }
:root[data-theme="light"] .standing-pill.deploying{ color: var(--royal);   background: rgba(58,103,134,0.12); }
.standing-pill.active .dot, .standing-pill.oncomms .dot {
  box-shadow: 0 0 6px currentColor;
  animation: skylace-pulse-dot 1.8s steps(1, end) infinite;
}
:root[data-theme="light"] .standing-pill .dot { animation: none; box-shadow: none; }

/* ─── Nameplate — engraved brass label ────────────────────────
   For callsign engraving on operative cards, dossier covers, badge
   inscriptions. Three sizes. */
.nameplate {
  display: inline-block;
  background: linear-gradient(180deg, var(--brass-hi) 0%, var(--brass) 50%, var(--brass-lo) 100%);
  color: #1a1208;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.6875rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  padding: 0.375rem 0.75rem;
  border-radius: var(--r-1);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.45),
    inset 0 -1px 0 rgba(0,0,0,0.45),
    0 1px 0 rgba(0,0,0,0.4);
  text-shadow: 0 1px 0 rgba(255,255,255,0.25);
  white-space: nowrap;
  line-height: 1;
}
.nameplate.sm { font-size: 0.59375rem; letter-spacing: 0.14em; padding: 0.25rem 0.5rem; }
.nameplate.lg { font-size: 0.8125rem;  letter-spacing: 0.22em; padding: 0.5rem 0.875rem; }

/* Day-mode pins the gradient to the dark-palette brass tones — the
   day-theme `--brass*` tokens are calibrated as accent colours
   against the cream paper bg, so they darken to deep olive-brown.
   The nameplate is the brass surface itself, not an accent on it,
   so dark text on day-theme brass renders nearly invisible. Polished
   brass should look polished in either room lighting.
   Re-stating `color` here is load-bearing for `<a class="nameplate">`
   (muster-roll callsign chip): the day-mode link rule
   `:root[data-theme="light"] a { color: var(--brass); }` further up
   has specificity (0,0,2,1), which beats `.nameplate`'s bare (0,0,1,0)
   and was repainting the engraving in brass mid-tone. Pinning color
   inside this scoped block raises specificity to (0,0,3,0) so the
   dark engraving wins on anchors too. */
:root[data-theme="light"] .nameplate {
  background: linear-gradient(180deg, #e3c887 0%, #c9a557 50%, #8b7236 100%);
  color: #1a1208;
}

/* ─── Stamp — red-ink diagonal stamp ──────────────────────────
   Decorative tag for RESTRICTED / EYES ONLY / TOP SECRET /
   ACKNOWLEDGED. Apply `style="transform: rotate(-8deg)"` for the
   skewed-stamped look (varies per instance). */
.stamp {
  display: inline-block;
  font: 700 0.75rem/1 var(--font-display);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--oxblood-hi);
  padding: 0.375rem 0.75rem;
  border: 2px solid var(--oxblood-hi);
  box-shadow: inset 0 0 0 1px var(--oxblood-hi);
  filter: contrast(1.1);
  opacity: 0.92;
  transform: rotate(-8deg);
  white-space: nowrap;
}
.stamp.cabinet { color: var(--cabinet-hi); border-color: var(--cabinet-hi); box-shadow: inset 0 0 0 1px var(--cabinet-hi); }
.stamp.brass   { color: var(--brass-hi);   border-color: var(--brass-hi);   box-shadow: inset 0 0 0 1px var(--brass-hi); }
:root[data-theme="light"] .stamp         { color: var(--oxblood); border-color: var(--oxblood); box-shadow: inset 0 0 0 1px var(--oxblood); opacity: 0.85; }
:root[data-theme="light"] .stamp.cabinet { color: var(--cabinet); border-color: var(--cabinet); box-shadow: inset 0 0 0 1px var(--cabinet); }
:root[data-theme="light"] .stamp.brass   { color: var(--brass);   border-color: var(--brass);   box-shadow: inset 0 0 0 1px var(--brass); }

/* ─── Cap-badge — brass roundel ───────────────────────────────
   Used for directive insignia on operative cards + Squad dossier
   covers. Outer brass ring + inner dark well + glyph slot. The
   inner content slot (an SVG glyph or text) sits in `.cap-badge-inner`. */
.cap-badge {
  display: inline-flex;
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 50%;
  background: radial-gradient(circle at 30% 30%, var(--brass-hi) 0%, var(--brass) 35%, var(--brass-lo) 100%);
  box-shadow:
    inset 0 0 0 1px rgba(255,255,255,0.15),
    inset 0 -2px 4px rgba(0,0,0,0.45),
    inset 0 2px 2px rgba(255,255,255,0.25),
    0 1px 2px rgba(0,0,0,0.6);
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.cap-badge.lg { width: 4.25rem; height: 4.25rem; }
.cap-badge.sm { width: 2.25rem; height: 2.25rem; }
.cap-badge-inner {
  width: calc(100% - 0.5rem);
  height: calc(100% - 0.5rem);
  border-radius: 50%;
  background: radial-gradient(circle at 35% 35%, #2a2114 0%, #0c0906 100%);
  box-shadow: inset 0 0 0 1px rgba(0,0,0,0.6), inset 0 1px 1px rgba(255,255,255,0.05);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--brass-hi);
  font-family: var(--font-display);
  font-size: 0.875rem;
  letter-spacing: 0.04em;
}

/* User-dossier cap-badge variant (3.X.0+). The operator-uploaded
   avatar `<img>` had no sizing rules — natural resolution +
   top-left default origin meant tall portraits hung out the
   bottom of the badge. Constrain to fill the inner dark-well
   + cover-crop + clip to the circle so the avatar always reads
   as a centered round portrait. The initials roundel fallback
   inherits flex-centering from .cap-badge-inner; this rule
   just bumps the type up so initials fill the larger
   .cap-badge.lg slot the User cover uses. */
.user-cover-badge-inner {
  overflow: hidden;
}
.user-cover-badge-inner > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  border-radius: 50%;
  display: block;
}
.user-cover-initials {
  font-family: var(--font-display);
  font-size: 1.375rem;
  letter-spacing: 0.04em;
  color: var(--brass-hi);
}

/* ─── Insignia staged-choice editor (2.230.0) ──────────────────
   Every entity (Company / Mission / Squad / Joe) /edit surface
   uses a staged-choice pattern: the current insignia renders inline
   in the entity's info panel via `.insignia-edit` (stage + Change
   button + hidden `insignia` form field), and the Change button
   opens a modal `<dialog>` carrying the 4 tiles + OK / Cancel. OK
   stages the choice (writes to the hidden input + flips the visible
   SVG); Save on the enclosing main edit form commits, Cancel on
   either layer discards. Pre-2.230.0 Squad used a Squad-specific
   class soup (`.squad-insignia-*`) — generalised here so the same
   chrome reads across all four entities.

   `.insignia-stage` is the visible-SVG slot. It renders all four
   variants as `.insignia-staged` siblings; the stage's
   `data-insignia="<key>"` attribute selects which one is visible
   via the [data-insignia] sibling-attr selectors below. The 16
   key/variant combinations are listed exhaustively (one per entity
   × 4 = 16 selectors) so the visible SVG flips correctly regardless
   of which entity rendered the editor. */
.insignia-edit {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.375rem;
}
.insignia-stage {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.insignia-stage .insignia-staged {
  display: none;
}
/* Company variants */
.insignia-stage[data-insignia="eagle"]      .insignia-staged[data-insignia="eagle"],
.insignia-stage[data-insignia="crown"]      .insignia-staged[data-insignia="crown"],
.insignia-stage[data-insignia="roundel"]    .insignia-staged[data-insignia="roundel"],
.insignia-stage[data-insignia="iron-cross"] .insignia-staged[data-insignia="iron-cross"],
/* Mission variants */
.insignia-stage[data-insignia="star"]       .insignia-staged[data-insignia="star"],
.insignia-stage[data-insignia="swords"]     .insignia-staged[data-insignia="swords"],
.insignia-stage[data-insignia="bullseye"]   .insignia-staged[data-insignia="bullseye"],
.insignia-stage[data-insignia="compass"]    .insignia-staged[data-insignia="compass"],
/* Squad variants */
.insignia-stage[data-insignia="chevrons"]   .insignia-staged[data-insignia="chevrons"],
.insignia-stage[data-insignia="pennant"]    .insignia-staged[data-insignia="pennant"],
.insignia-stage[data-insignia="shield"]     .insignia-staged[data-insignia="shield"],
.insignia-stage[data-insignia="wreath"]     .insignia-staged[data-insignia="wreath"],
/* Joe variants */
.insignia-stage[data-insignia="wings"]      .insignia-staged[data-insignia="wings"],
.insignia-stage[data-insignia="chevron"]    .insignia-staged[data-insignia="chevron"],
.insignia-stage[data-insignia="star-bars"]  .insignia-staged[data-insignia="star-bars"],
.insignia-stage[data-insignia="pip"]        .insignia-staged[data-insignia="pip"] {
  display: inline-flex;
}
.insignia-change-btn {
  font: 500 0.6875rem/1 var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: var(--ground-2);
  border: 1px solid var(--brass-rim);
  border-radius: var(--r-1);
  color: var(--text-1);
  padding: 0.375rem 0.75rem;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.insignia-change-btn:hover {
  border-color: var(--brass);
  background: var(--ground-3);
}

.insignia-dialog {
  border: 1px solid var(--brass-rim);
  border-radius: var(--r-2);
  background: var(--ground-1);
  color: var(--text-1);
  padding: 0;
  max-width: 32rem;
  width: 90vw;
  box-shadow: 0 12px 32px rgba(0,0,0,0.4);
}
.insignia-dialog::backdrop {
  background: rgba(8, 10, 16, 0.65);
}
.insignia-dialog-body {
  padding: 1.25rem 1.25rem 1rem;
}
.insignia-dialog-body h3 {
  margin: 0 0 0.25rem;
  font: 500 0.9375rem/1.2 var(--font-display);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--brass-hi);
}
.insignia-dialog-hint {
  margin: 0 0 1rem;
  font: 400 0.75rem/1.4 var(--font-body);
  color: var(--text-3);
}
.insignia-dialog-tiles {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.5rem;
  margin: 0 0 1rem;
}
.insignia-dialog-tile {
  /* 2.230.2 — explicit resets for the global `button` styles
     (height: 2.125rem; line-height/font; white-space: nowrap;
     letter-spacing). Without these, the 34px-tall global button
     box clipped the 56px SVG and forced label-only height — the
     symptom the operator reported with "where are the images?".
     Letter-spacing + uppercase live on the label span instead. */
  background: var(--ground-2);
  border: 1px solid var(--brass-rim);
  border-radius: var(--r-1);
  padding: 0.625rem 0.5rem 0.75rem;
  height: auto;
  min-height: 0;
  white-space: normal;
  letter-spacing: normal;
  text-transform: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.375rem;
  transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
}
.insignia-dialog-tile:hover {
  border-color: var(--brass);
}
.insignia-dialog-tile.is-active {
  background: var(--brass-dim);
  border-color: var(--brass);
  box-shadow: inset 0 1px 3px rgba(0,0,0,0.25);
}
.insignia-dialog-tile svg {
  width: 3.5rem;
  height: 3.5rem;
  display: block;
}
.insignia-dialog-tile-label {
  font: 400 0.625rem/1 var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-2);
}
.insignia-dialog-tile.is-active .insignia-dialog-tile-label {
  color: var(--text-1);
}
.insignia-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
}
.insignia-dialog-actions button {
  font: 500 0.75rem/1 var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.5rem 1rem;
  border-radius: var(--r-1);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.insignia-dialog-cancel {
  background: transparent;
  border: 1px solid var(--brass-rim);
  color: var(--text-2);
}
.insignia-dialog-cancel:hover {
  border-color: var(--brass);
  color: var(--text-1);
}
.insignia-dialog-ok {
  background: var(--brass);
  border: 1px solid var(--brass);
  color: #0c0906;
}
.insignia-dialog-ok:hover {
  background: var(--brass-hi);
  border-color: var(--brass-hi);
}

/* ─── Squad-detail / Joe-dossier primitives (2.158.0+) ─────
   The "briefing-room" surface vocabulary: the dossier cover hero,
   the muster-roll table, per-operative cards, loadout 6-slot
   patches, fuel meters. Cross-cutting — these primitives are
   shared by Squad / Joe / Mission / Company detail surfaces
   as the reskin lands per-surface. */

/* Dossier cover — the hero panel at the top of every entity-detail
   surface. Cap-badge on the left + identity column + gauges column +
   actions column. Brass-rim panel with contour-map underlay on top
   for the "field-command" atmospheric. */
.dossier-cover {
  position: relative;
  overflow: hidden;
  padding: 1.375rem 1.625rem;
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 1.75rem;
  align-items: center;
  /* 3.X — make the cover a containment context so the responsive
     stacking rules below can use `@container` queries based on the
     cover's OWN width, not the viewport. The pre-3.X
     `@media (max-width: 720px)` rule was viewport-keyed; at
     mid-width viewports (or when the page chrome embeds the
     cover in a narrower column) the cap-badge + actions cluster
     would squeeze the centre (1fr) column to nothing, and the
     name h2 + parent-ref prose wrapped one character per line.
     Operator-flagged on the Squad detail page at ~900-1000px
     viewport. */
  container-type: inline-size;
  container-name: dossier-cover;
}
.dossier-cover > * { position: relative; z-index: 1; }
/* 2.159.1 — the contour-map underlay stays position:absolute so it
   doesn't participate in grid auto-flow. Pre-2.159.1 the `> *` rule
   above silently overrode `.contour-map`'s `position: absolute` (same
   specificity, later rule won), turning the underlay into a grid
   item that consumed col 1 and pushed identity + gauges + actions
   right (and wrapped actions onto a second row). The `> .contour-
   map` selector is more specific than `> *`, so its `position:
   absolute` wins back. */
.dossier-cover > .contour-map { position: absolute; z-index: 0; }
.dossier-cover-identity {
  display: flex;
  align-items: center;
  gap: 1.125rem;
  min-width: 0;
}
.dossier-cover-name {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  min-width: 0;
}
.dossier-cover-name h1,
.dossier-cover-name h2 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.625rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--display);
  line-height: 1.05;
  /* 3.X — `word-break: break-word` (deprecated alias for
     `overflow-wrap: anywhere`) was breaking the name heading on
     every character when the centre column got squeezed (operator-
     typed callsigns of repeated symbols like `/////` rendered
     one character per line). `overflow-wrap: break-word` is the
     modern conservative — only break inside a word as a last
     resort when there's NO word-break position available, instead
     of breaking at any character. */
  overflow-wrap: break-word;
  word-break: normal;
}
.dossier-cover-name h1 input,
.dossier-cover-name h2 input {
  /* Inline-edit-mode swap: the h2 becomes an <input>. Keep the
     heading typography (font, letter-spacing, uppercase) so the
     field doesn't shrink visually when edit mode kicks in, but
     give it an obvious editable-field affordance — operators read
     the pre-3.27 transparent bg + dashed brass-rim border as
     static heading text, not an input. The bg matches the
     .profile-edit-input ship (#313a4d dark / #c8b988 light) for
     site-wide consistency; the solid hair border + inset shadow
     match the global input[type=text] chrome so the heading slot
     lines up with the sibling Website / nickname / description
     inputs in the same form. */
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: var(--display);
  background: #313a4d;
  border: 1px solid var(--hair);
  border-radius: var(--r-1);
  padding: 0.25rem 0.5rem;
  width: 100%;
  max-width: 26.25rem;
  height: auto;
  box-shadow: inset 0 1px 1px rgba(0,0,0,0.30);
}
:root[data-theme="light"] .dossier-cover-name h1 input,
:root[data-theme="light"] .dossier-cover-name h2 input {
  background: #c8b988;
  box-shadow: inset 0 1px 1px rgba(0,0,0,0.06);
}
.dossier-cover-name h1 input:focus,
.dossier-cover-name h2 input:focus {
  border-color: var(--brass);
  box-shadow:
    inset 0 1px 1px rgba(0,0,0,0.30),
    0 0 0 3px var(--brass-dim);
}
:root[data-theme="light"] .dossier-cover-name h1 input:focus,
:root[data-theme="light"] .dossier-cover-name h2 input:focus {
  box-shadow:
    inset 0 1px 1px rgba(0,0,0,0.06),
    0 0 0 3px var(--brass-dim);
}
.dossier-cover-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.625rem 0.875rem;
}
.dossier-cover-prose {
  font: 400 0.9625rem/1.45 var(--font-serif);
  font-style: italic;
  color: var(--text-2);
  /* Long unbreakable URLs (typically the Company's website link)
     would push the parent flex row past the viewport on mobile —
     `overflow-wrap: anywhere` lets the URL token break mid-string
     so the prose line never escapes its column. `min-width: 0`
     gives the prose span room to shrink as a flex child rather
     than being sized by its min-content. */
  min-width: 0;
  overflow-wrap: anywhere;
}
.dossier-cover-prose a { overflow-wrap: anywhere; word-break: break-word; }
.dossier-cover-gauges {
  display: flex;
  gap: 1.375rem;
  /* 2.228.2 — align gauges to the top so labels share a baseline
     and the Cinzel values below them sit at the same Y across the
     row. Pre-2.228.2 the parent was `align-items: center`, which
     made gauges with a sub-line (e.g. "Spend today $0.00 / no cap")
     taller and shifted the value up relative to neighbours without
     a sub. Operator-flagged: Spend Today's value sat above the
     Operatives + Daemon values. With flex-start, labels align; the
     sub-line for one gauge just appears below its value, never
     pushing the value off the shared baseline. */
  align-items: flex-start;
}
.dossier-cover-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.5rem;
}

/* Joe dossier cover variant — wider identity column (no big
   gauges column), action cluster on the right. Trade row carries
   the meta KV inline. */
.dossier-cover.dossier-cover-joe {
  /* 2.159.1 — 2-column grid (identity at 1fr stretches across the
     remaining width, actions hugs the right). Pre-2.159.1 this was
     `1fr auto auto` paired with an empty `<div>` placeholder
     between identity and actions; the empty placeholder + the
     contour-map-takes-grid-space bug combined to wrap actions
     onto a second row. */
  grid-template-columns: 1fr auto;
  align-items: stretch;
}
.dossier-cover-joe .dossier-cover-identity {
  align-items: flex-start;
}
.dossier-cover-joe .dossier-cover-name {
  gap: 0.625rem;
}

/* Trade row — the inline meta line on the Joe cover: trade
   (cap-badge + directive name), detailed (squad + tenure days),
   AI key. Vertical brass hairline separators between fields. */
.dossier-trade-row {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  flex-wrap: wrap;
  margin-top: 0.25rem;
}
.dossier-trade-field {
  display: flex;
  flex-direction: column;
  gap: 0.1875rem;
  min-width: 0;
}
.dossier-trade-label {
  font: 400 0.5625rem/1 var(--font-mono);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-4);
}
.dossier-trade-value {
  font: 500 0.8125rem/1.2 var(--font-body);
  color: var(--text-1);
  word-break: break-word;
}
.dossier-trade-value.mono {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
}
.dossier-trade-sep {
  width: 1px;
  height: 1.75rem;
  background: var(--hair);
  flex-shrink: 0;
}

/* Big portrait variant — used by the Joe dossier cover. Wider +
   taller than the muster-roll thumbnail; the brass frame is more
   prominent because this surface is *the* Joe's identity card. */
.operative-portrait.big {
  width: 6rem;
  height: 7rem;
  border-width: 1px;
  border-color: var(--brass);
}

/* 3.282 — small portrait variant for chat-row / transcript-row
   contexts (per-thread walkie page renders one tile per message,
   sized down from the 4.5rem muster-roll default to a 2.25rem
   chip). Aspect 7:8 matches the silhouette viewBox; the
   dossier-no overlay is suppressed at this size for legibility
   (the host row already labels the sender on the meta line). */
.operative-portrait.sm {
  width: 2.25rem;
  height: 2.625rem;
}

/* 3.X — vertical stack wrapping the dossier-cover portrait + any
   below-portrait fragment (the edit-mode REGENERATE button). The
   stack sizes to the portrait's width (6rem) so an attached button
   below it spans the same width and reads as one visual unit. In
   read mode the stack carries only the portrait; in edit mode it
   carries portrait + slim REGENERATE strip glued to the bottom
   edge. */
.dossier-portrait-stack {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  width: 6rem;
  flex-shrink: 0;
  /* 3.X — establish an inline-size container so the
     REGENERATE strip below can size its font in cqi units
     and stay fitted as the stack shrinks (6rem desktop →
     4.5rem mobile per the responsive rule further down). */
  container-type: inline-size;
}

/* Slim REGENERATE strip glued under the dossier portrait in edit
   mode. Matches the portrait's brass frame so the two elements
   read as one identity card. Negative top-margin closes the
   1px gap between portrait border and button border so they
   share an edge. Centered uppercase Cinzel for the dossier
   aesthetic. Font scales with the stack via cqi (parent declares
   `container-type: inline-size`): cqi multiplier 9 keeps the
   value below the 0.625rem max at every breakpoint (true linear
   scaling, no clamp-flat-tops on the desktop side) and tracks
   down to ~0.45rem at the narrowest ≤360px tier. Min 0.4375rem
   guards the legibility floor under aggressive root font dials.
   The wide ≤480px stack is only ~52px in real pixels once
   `:root { font-size: 72% }` bites, so the font has to drop to
   ~5px there. Letter-spacing also tightened (0.18em → 0.10em):
   without it the 9-letter-gap × 0.18em (≈1.6em fixed-fraction
   of the budget) consumed the entire mobile width regardless
   of how small the font went; the wide Cinzel cap-glyph
   advance already dominates the per-letter budget so the
   reduced tracking still reads as dossier-spaced typography. */
.dossier-regen-btn {
  display: block;
  width: 100%;
  height: 1.3rem;
  min-height: 1.3rem;
  margin: -1px 0 0;
  padding: 0.0625rem 0;
  font-family: var(--font-display, "Cinzel", serif);
  font-size: clamp(0.4375rem, 9cqi, 0.625rem);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  text-align: center;
  color: var(--vellum);
  background: linear-gradient(180deg, var(--oxblood-hi) 0%, var(--oxblood) 100%);
  border: 1px solid var(--oxblood);
  border-radius: 0 0 var(--r-1) var(--r-1);
  cursor: pointer;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.35);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.10),
    inset 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.dossier-regen-btn:hover {
  filter: brightness(1.10);
}
.dossier-regen-btn:focus-visible {
  outline: 2px solid var(--brass-hi);
  outline-offset: 1px;
}
.dossier-regen-btn[disabled] {
  cursor: progress;
  opacity: 0.65;
}

/* 3.X (roll-the-dice) — dice icon button rendered inline with the
   Character + Role <label>s on the Joe edit page. Slim brass-rim
   chip carrying the U+2684 die-face-4 glyph; one click farms off
   to Enigma for an AI-generated preamble that the page-side JS
   pastes into the matching textarea (no page reload). Hidden in
   read-mode (the button itself isn't emitted unless canManage); the
   styling here is for the edit-mode render. */
.joe-dice-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.625rem;
  height: 1.625rem;
  margin-left: 0.375rem;
  padding: 0;
  font-size: 1.125rem;
  line-height: 1;
  color: var(--brass-hi);
  background: linear-gradient(180deg, var(--ground-3) 0%, var(--ground-2) 100%);
  border: 1px solid var(--brass-rim);
  border-radius: var(--r-1, 4px);
  cursor: pointer;
  vertical-align: -0.25rem;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.05),
    inset 0 -1px 0 rgba(0, 0, 0, 0.20);
  transition: filter 120ms ease, transform 120ms ease;
}
.joe-dice-btn:hover {
  filter: brightness(1.18);
}
.joe-dice-btn:active {
  transform: scale(0.94);
}
.joe-dice-btn:focus-visible {
  outline: 2px solid var(--brass-hi);
  outline-offset: 2px;
}
.joe-dice-btn[disabled] {
  cursor: progress;
  opacity: 0.55;
  filter: none;
}
:root[data-theme="light"] .joe-dice-btn {
  color: var(--brass);
  background: linear-gradient(180deg, var(--vellum) 0%, var(--ground-2) 100%);
  border-color: var(--brass);
}

/* Joe loadout grid — wider tiles than the muster-roll's 30px
   loadout patches. 3-column auto-flow on wide; collapses to 2 / 1
   on narrower viewports. Each card carries the kit-patch glyph +
   the toolset entry label + an in-arsenal status pill + an
   optional configure link.
*/
.joe-loadout-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 0.625rem;
}
.joe-loadout-card {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.5rem;
  padding: 0.75rem 0.75rem 0.625rem;
  background: linear-gradient(180deg, var(--ground-2) 0%, var(--ground-1) 100%);
  border: 1px solid var(--brass-rim);
  border-radius: var(--r-1);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.04),
    inset 0 -1px 0 rgba(0,0,0,0.35);
  min-width: 0;
}
:root[data-theme="light"] .joe-loadout-card {
  background: linear-gradient(180deg, var(--ground-2) 0%, var(--ground-3) 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.6), 0 1px 2px rgba(0,0,0,0.04);
}
.joe-loadout-card.not-in-arsenal {
  border-color: rgba(165,42,61,0.32);
}
:root[data-theme="light"] .joe-loadout-card.not-in-arsenal {
  border-color: rgba(106,26,38,0.32);
}
.joe-loadout-card-head {
  display: flex;
  align-items: center;
  gap: 0.625rem;
}
.joe-loadout-card-label {
  flex: 1;
  font: 500 0.8125rem/1.2 var(--font-mono);
  color: var(--display);
  letter-spacing: 0.02em;
  word-break: break-all;
}
.joe-loadout-card-instance {
  display: block;
  font: 400 0.625rem/1.2 var(--font-mono);
  letter-spacing: 0.06em;
  color: var(--text-4);
  margin-top: 0.125rem;
  text-transform: uppercase;
}
/* 2.216.0 — the status pill is now also the toggle button. The
   selector covers both shapes (read-only span when canManage is
   off; submit button inside the pill-form when on); the rules apply
   to both, with the button variant getting cursor + hover for
   discoverability. */
.joe-loadout-card-status {
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  font: 500 0.5625rem/1 var(--font-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 0.1875rem 0.4375rem;
  border-radius: var(--r-1);
  border: 1px solid currentColor;
  white-space: nowrap;
  align-self: flex-start;
}
button.joe-loadout-card-status {
  background: transparent;
  cursor: pointer;
  transition: background-color 0.12s, box-shadow 0.12s;
  font-family: inherit;
}
button.joe-loadout-card-status:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--accent-glow, rgba(201,165,87,0.35));
}
.joe-loadout-card-status.ok      { color: var(--cabinet-hi); background: rgba(74,128,96,0.10); }
.joe-loadout-card-status.warn    { color: var(--oxblood-hi); background: rgba(165,42,61,0.10); }
button.joe-loadout-card-status.ok:hover     { background: rgba(74,128,96,0.20); }
button.joe-loadout-card-status.warn:hover   { background: rgba(165,42,61,0.20); }
:root[data-theme="light"] .joe-loadout-card-status.ok   { color: var(--cabinet); background: rgba(35,77,54,0.08); }
:root[data-theme="light"] .joe-loadout-card-status.warn { color: var(--oxblood); background: rgba(106,26,38,0.08); }
:root[data-theme="light"] button.joe-loadout-card-status.ok:hover   { background: rgba(35,77,54,0.16); }
:root[data-theme="light"] button.joe-loadout-card-status.warn:hover { background: rgba(106,26,38,0.16); }
.joe-loadout-card-pill-form {
  margin: 0;
  padding: 0;
  align-self: flex-start;
  display: inline-flex;
}
.joe-loadout-card-configure {
  display: inline-flex;
  align-self: flex-start;
  font: 500 0.6875rem/1 var(--font-body);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--brass-hi);
  text-decoration: none;
  padding: 0.125rem 0;
}
.joe-loadout-card-configure:hover { color: var(--brass); text-decoration: underline; }
:root[data-theme="light"] .joe-loadout-card-configure { color: var(--brass); }
/* Disabled entries get a muted card body so the operator sees at a
   glance which toolsets are paused, in addition to the orange pill.
   Kit-patch + controls stay full-strength so the action affordances
   remain readable. */
.joe-loadout-card.entry-disabled .joe-loadout-card-label {
  opacity: 0.55;
}
.joe-loadout-card-warn-note {
  font: 400 0.89375rem/1.45 var(--font-serif);
  font-style: italic;
  color: var(--text-3);
}
/* 3.X (toolset-advisories) — operator-facing warnings a paired toolset
   computes against the live entity tuple + its config (e.g. dispatch-
   box's leader-locked-out check). Walnut/brass warn tone matches the
   `.joe-loadout-card-status.warn` pill family; stacked list under the
   card body so multi-advisory cases read cleanly. The card itself
   gains a `.has-advisories` flag for an optional outer border tint. */
.joe-loadout-card.has-advisories {
  border-color: rgba(201, 165, 87, 0.45);
}
:root[data-theme="light"] .joe-loadout-card.has-advisories {
  border-color: rgba(139, 110, 31, 0.45);
}
.joe-loadout-card-advisories {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}
.joe-loadout-card-advisory {
  display: flex;
  flex-direction: column;
  gap: 0.1875rem;
  padding: 0.4375rem 0.5rem;
  background: rgba(201, 165, 87, 0.10);
  border-left: 3px solid var(--brass);
  border-radius: var(--r-1);
}
:root[data-theme="light"] .joe-loadout-card-advisory {
  background: rgba(139, 110, 31, 0.08);
}
.joe-loadout-card-advisory-message {
  font: 400 0.8125rem/1.4 var(--font-body);
  color: var(--text-2);
}
.joe-loadout-card-advisory-hint {
  font: 400 0.6875rem/1.35 var(--font-body);
  letter-spacing: 0.02em;
  color: var(--text-3);
  font-style: italic;
}
.joe-loadout-card-configure-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1rem;
  height: 1rem;
  margin-left: 0.3125rem;
  padding: 0 0.3125rem;
  border-radius: 999px;
  background: var(--brass);
  color: var(--ground-1);
  font: 600 0.625rem/1 var(--font-mono);
  letter-spacing: 0;
  vertical-align: middle;
}
:root[data-theme="light"] .joe-loadout-card-configure-badge {
  background: var(--brass);
  color: var(--ground-1);
}

/* Mission objective communiqué (2.160.0+). Sits below the dossier
   cover on Mission detail; eyebrow + serif-italic prose body, no
   panel chrome of its own (it rides the parent panel's body
   region). */
.mission-objective {
  margin: 0 0 0.875rem;
  padding: 0.75rem 0.875rem;
  background: rgba(201, 165, 87, 0.04);
  border-left: 3px solid var(--brass);
  border-radius: var(--r-1);
}
:root[data-theme="light"] .mission-objective {
  background: rgba(139, 110, 31, 0.06);
}
.mission-objective-body {
  margin-top: 0.375rem;
  font: 400 1.03125rem/1.5 var(--font-serif);
  font-style: italic;
  color: var(--display);
}

/* Squad tile grid (2.160.0+). Used on Mission detail's Squads
   panel + Company detail's Missions panel. Auto-fill 300px-min
   grid; tiles are anchors so the whole card is clickable.
   `minmax(min(300px, 100%), 1fr)` is the canonical fix for the
   auto-fill/minmax overflow trap: on narrow viewports where the
   container is < 300px, the `300px` raw minimum would force each
   column to 300px and push the grid wider than its parent
   (visible on a 360 px phone where the Company-detail panel's
   inner area squeezes below 300 px after main/panel padding
   compounds at the 80% root font-size dial). Wrapping the
   minimum in `min(300px, 100%)` caps the floor at the container's
   own width so the grid can never demand more horizontal room
   than it has. */
.entity-tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
  gap: 0.75rem;
}
.entity-tile {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  padding: 0.875rem;
  background: linear-gradient(180deg, var(--ground-2) 0%, var(--ground-1) 100%);
  border: 1px solid var(--brass-rim);
  border-radius: var(--r-1);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.04),
    inset 0 -1px 0 rgba(0,0,0,0.35);
  text-decoration: none;
  color: inherit;
  transition: box-shadow .15s, border-color .15s;
  min-width: 0;
}
:root[data-theme="light"] .entity-tile {
  background: linear-gradient(180deg, var(--ground-2) 0%, var(--ground-3) 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.6), 0 1px 2px rgba(0,0,0,0.04);
}
.entity-tile:hover {
  border-color: var(--brass);
  text-decoration: none;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.04),
    inset 0 -1px 0 rgba(0,0,0,0.35),
    0 0 0 1px var(--brass);
}
.entity-tile-head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
}
.entity-tile-id {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
}
.entity-tile-name {
  font: 500 1.0625rem/1 var(--font-display);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--display);
  word-break: break-word;
}
.entity-tile-meta {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.entity-tile-count {
  font: 500 0.6875rem/1 var(--font-mono);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--text-3);
}
.entity-tile-repo {
  font: 500 0.75rem/1.4 var(--font-mono);
  color: var(--text-2);
  word-break: break-all;
}
.entity-tile-branch {
  color: var(--text-4);
}

/* Muster roll — leather-inlay container holding operative cards.
   The cards stack vertically; each card is its own panel-shaped
   row. */
.muster-roll {
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  /* 3.X — make the muster-roll a containment context so the
     `.operative-card` inside it can use `@container` queries to
     stack based on the roll's OWN width, not the viewport. The
     Squad detail page's 3-col layout activates around 1100px,
     where the muster column compresses to ~600px — too narrow
     for the operative-card's desktop 5-col grid. Pre-3.X the
     card stacked on `@media (max-width: 720px)`, which left a
     ~720-1100px gap where the page was in 3-col but the card
     was still trying to render in desktop mode + overflowing. */
  container-type: inline-size;
  container-name: muster-roll;
}
.muster-roll-empty {
  padding: 1.75rem 1.125rem;
  text-align: center;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 1.1em;
  color: var(--vellum);
}

/* Operative card — one Joe in the muster roll. Top row reads
   left-to-right (portrait + identity + loadout + actions). The
   tasking line gets its own bottom row spanning the full card
   width so longer addendum text wraps horizontally instead of
   collapsing the 1fr column to one character per line when the
   muster roll is in a narrower viewport (2.226.4+ — operator-
   reported "way too tall" on the Squad detail page).
   Pre-2.226.4 the card was a single 5-col flex-grid; the 1fr
   tasking track's min-content sized to the longest unbreakable
   word, but `auto` neighbours could still squeeze it past that. */
.operative-card {
  display: grid;
  grid-template-columns: auto auto auto 1fr auto;
  grid-template-areas:
    "portrait identity loadout .       actions"
    "tasking  tasking  tasking tasking tasking";
  column-gap: 1rem;
  row-gap: 0.625rem;
  align-items: center;
  padding: 0.875rem;
  background: linear-gradient(180deg, var(--ground-2) 0%, var(--ground-1) 100%);
  border: 1px solid var(--brass-rim);
  border-radius: var(--r-1);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.04),
    inset 0 -1px 0 rgba(0,0,0,0.35);
  transition: box-shadow .15s, border-color .15s;
}
.operative-portrait { grid-area: portrait; }
.operative-identity { grid-area: identity; align-self: center; }
.loadout-grid { grid-area: loadout; }
.operative-actions { grid-area: actions; }
.operative-tasking { grid-area: tasking; }
:root[data-theme="light"] .operative-card {
  background: linear-gradient(180deg, var(--ground-2) 0%, var(--ground-3) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.6),
    0 1px 2px rgba(0,0,0,0.04);
}
.operative-card:hover {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.04),
    inset 0 -1px 0 rgba(0,0,0,0.35),
    0 0 0 1px var(--brass-rim);
}
.operative-card.highlight {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.04),
    inset 0 -1px 0 rgba(0,0,0,0.35),
    0 0 0 1px var(--brass);
}

/* Operative portrait — silhouette container. Inline SVG silhouette
   deterministic per callsign; the joe-portraits FOLLOWUPS ship
   will swap to real B&W vintage photos.

   3.296 — `display: block` + `text-decoration: none` + `color:
   inherit` so the same class can host either a `<div>` (legacy
   decorative) or an `<a>` (3.296 clickable variant) without
   layout drift. `a.operative-portrait` gets a brass focus ring
   so keyboard nav surfaces the click target; the inherited
   pointer cursor is honest about clickability. */
.operative-portrait {
  display: block;
  width: 4.5rem;
  height: 5.375rem;
  border-radius: var(--r-1);
  border: 1px solid var(--brass-rim);
  background: linear-gradient(180deg, #1a1f29 0%, #0a0c12 100%);
  box-shadow:
    inset 0 0 0 1px rgba(0,0,0,0.4),
    inset 0 0 18px rgba(0,0,0,0.5);
  overflow: hidden;
  position: relative;
  flex-shrink: 0;
  text-decoration: none;
  color: inherit;
}
a.operative-portrait:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 2px;
}
:root[data-theme="light"] .operative-portrait {
  background: linear-gradient(180deg, #ede4cf 0%, #d4c7a0 100%);
}
.operative-portrait-dossier-no {
  position: absolute;
  top: 0.25rem;
  left: 0.375rem;
  font: 400 0.5rem/1 var(--font-mono);
  letter-spacing: 0.06em;
  color: var(--text-4);
}
.operative-portrait svg { width: 100%; height: 100%; display: block; }
/* 2.165.0 — generated B&W vintage portrait img variant. Covers the
   frame edge-to-edge; object-fit: cover crops to fill at any aspect
   ratio so the centered head + shoulders Replicate produces always
   anchors visibly. Slight desaturation + grain overlay would be a
   future polish — today the image carries the aesthetic via the
   locked prompt template. The dossier-no overlay sits on top via
   its own absolute positioning, untouched by the img layer. */
.operative-portrait-img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  object-position: center top;
}

.operative-identity {
  display: flex;
  flex-direction: column;
  gap: 0.4375rem;
  min-width: 0;
  align-items: flex-start;
}
/* 2.228.3 — Joe insignia + callsign sit in one horizontal row
   inside the operative card identity column. Badge ~22px, callsign
   to the right; the rest of `.operative-identity` (standing pill +
   directive line) stacks underneath the row as before. */
.operative-identity-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  row-gap: 0.3125rem;
  column-gap: 0.4375rem;
  min-width: 0;
  max-width: 100%;
}
.operative-identity-row > svg {
  flex-shrink: 0;
  display: block;
}
.operative-identity-directive {
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  font: 400 0.625rem/1 var(--font-mono);
  letter-spacing: 0.08em;
  color: var(--text-3);
  text-transform: lowercase;
}

/* Loadout grid — the 6-slot kit-patch grid showing the Joe's
   equipped toolsets. */
.loadout-grid {
  display: grid;
  grid-template-columns: repeat(3, 1.875rem);
  grid-template-rows: repeat(2, 1.875rem);
  gap: 0.25rem;
  align-self: center;
}
.kit-patch {
  width: 1.875rem;
  height: 1.875rem;
  border-radius: var(--r-1);
  background: linear-gradient(180deg, #2a2114 0%, #14100a 100%);
  box-shadow: inset 0 0 0 1px var(--brass-rim), inset 0 1px 0 rgba(255,255,255,0.05);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--brass-hi);
  font: 600 0.5625rem/1 var(--font-mono);
  letter-spacing: 0;
  text-transform: uppercase;
  overflow: hidden;
  cursor: default;
}
.kit-patch.empty {
  background: repeating-linear-gradient(45deg, var(--ground-2) 0 4px, var(--ground-1) 4px 8px);
  box-shadow: inset 0 0 0 1px var(--hair);
  opacity: 0.45;
}
:root[data-theme="light"] .kit-patch.empty {
  background: repeating-linear-gradient(45deg, var(--ground-4) 0 4px, var(--ground-3) 4px 8px);
}

/* Operative tasking — italic serif prose for the "current tasking"
   line. The fuel bar sits below as a thin gauge of remaining
   budget / quota / freshness. */
.operative-tasking {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  justify-content: center;
  min-width: 0;
}
.operative-tasking-body {
  font: 400 0.9625rem/1.45 var(--font-serif);
  font-style: italic;
  color: var(--display);
  /* 2.226.2 — `word-break: break-word` (legacy alias) was letting the
     CSS-grid `1fr` track shrink to a single character per line when
     the centre column got squeezed by the auto-sized neighbours
     (portrait + identity + loadout + actions) at viewports between
     ~800-1024px. `overflow-wrap: break-word` only wraps long words
     at the point of overflow — min-content stays at the longest word
     width, so the column can't collapse below a sensible minimum.
     Operator-reported: "Standing by · no operator addendum" reading
     as one character per row in the muster roll. */
  overflow-wrap: break-word;
}
.operative-tasking-meta {
  font: 400 0.625rem/1 var(--font-mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-4);
}

/* 3.X — Squad Leader role pill inside the muster-roll operative
   card's identity row. Brass-bordered, brass-tinted background,
   strong contrast in both themes. Sits next to the callsign so
   the operator scans down the roster and instantly picks out
   the Leader without squinting. Replaces the pre-3.X
   `.operative-tasking-meta` text rendering (`· SQUAD LEADER ·`
   in 10px var(--text-4) — invisible in night mode, operator-
   reported). */
.operative-leader-badge {
  display: inline-flex;
  align-items: center;
  font: 700 0.6875rem/1 var(--font-sans);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 0.25rem 0.5rem;
  border-radius: 999px;
  border: 1px solid var(--brass);
  background: linear-gradient(180deg,
    rgba(208, 168, 84, 0.20) 0%,
    rgba(208, 168, 84, 0.32) 100%);
  color: var(--brass-hi);
  white-space: nowrap;
  flex: 0 0 auto;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    0 1px 1px rgba(0, 0, 0, 0.18);
}
:root[data-theme="light"] .operative-leader-badge {
  border-color: var(--brass);
  background: linear-gradient(180deg,
    rgba(168, 128, 50, 0.16) 0%,
    rgba(168, 128, 50, 0.26) 100%);
  color: #6b4d12;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.4),
    0 1px 1px rgba(0, 0, 0, 0.06);
}

.operative-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: center;
  gap: 0.375rem;
}

/* Fuel bar — horizontal meter under tasking. */
.fuel-bar {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  max-width: 13.75rem;
}
.fuel-bar-label {
  font: 400 0.5625rem/1 var(--font-mono);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-4);
  flex-shrink: 0;
}
.fuel-bar-track {
  flex: 1;
  height: 0.3125rem;
  background: var(--ground-0);
  border-radius: 1px;
  box-shadow: inset 0 1px 1px rgba(0,0,0,0.4);
  overflow: hidden;
}
.fuel-bar-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--brass), var(--brass-hi));
}
.fuel-bar-fill.cabinet { background: linear-gradient(90deg, var(--cabinet-hi), var(--cabinet)); }
.fuel-bar-fill.oxblood { background: linear-gradient(90deg, var(--oxblood-hi), var(--oxblood)); }
.fuel-bar-value {
  font: 500 0.625rem/1 var(--font-mono);
  color: var(--text-1);
  min-width: 2rem;
  text-align: right;
}

/* 3.X — operative-card stacking is now driven by the muster-roll
   container's width via `@container muster-roll`, not the
   viewport. The Squad-detail page's 3-col layout activates around
   1100px viewport and compresses the muster column to ~600px;
   the pre-3.X `@media (max-width: 720px)` rule left an awkward
   720-1100px window where the page was in 3-col layout but the
   card was still rendering desktop side-by-side, overflowing into
   neighbouring grid columns. Threshold 640px chosen as the floor
   the desktop 5-col grid needs (portrait 4.5rem + identity ~12rem
   + loadout ~5.25rem + actions ~5rem + 3×1rem gaps ≈ 550-600px).
   Wraps every operative-card-related rule so portrait + tasking
   + actions reflow together. */
/* Mid-width (420-640px): the desktop 5-col layout doesn't fit
   but there's still room on row 1 to keep the loadout grid to
   the right of identity. Operator-flagged: the prior 1-stage
   stack pushed loadout to a full-width row below identity,
   leaving a big empty void to the right of the portrait. */
@container muster-roll (max-width: 640px) {
  .operative-card {
    grid-template-columns: auto 1fr auto;
    grid-template-areas:
      "portrait identity loadout"
      "tasking  tasking  tasking"
      "actions  actions  actions";
    gap: 0.625rem 0.875rem;
  }
  .operative-portrait { grid-area: portrait; width: 3.5rem; height: 4.25rem; }
  .operative-identity { grid-area: identity; }
  .loadout-grid { grid-area: loadout; align-self: center; }
  .operative-tasking { grid-area: tasking; }
  .operative-actions { grid-area: actions; flex-direction: row; }
}

/* Very narrow (≤420px): row 1 can no longer carry portrait +
   identity + loadout together — loadout drops to its own row
   below the portrait/identity pair. */
@container muster-roll (max-width: 420px) {
  .operative-card {
    grid-template-columns: auto 1fr;
    grid-template-areas:
      "portrait identity"
      "loadout  loadout"
      "tasking  tasking"
      "actions  actions";
  }
  .loadout-grid { grid-area: loadout; align-self: start; }
}

/* 3.X — dossier-cover stacking is now driven by the cover's own
   width via `@container dossier-cover`, not the viewport. The
   pre-3.X `@media (max-width: 720px)` rule fired on viewport
   width, which left a wide gap where the page chrome wasn't yet
   in mobile mode but the cover's centre 1fr column had been
   compressed to nothing by the cap-badge + actions cluster —
   the name h2 + parent-ref prose wrapped one character per line.
   Operator-flagged 2026-05-21 on the Squad detail page at
   ~900-1000px viewport. */
@container dossier-cover (max-width: 720px) {
  .dossier-cover { grid-template-columns: 1fr; gap: 1rem; padding: 1rem 1.125rem; }
  /* Joe variant carries its own `grid-template-columns: 1fr auto`
     override at (0,2,0) specificity that beats the (0,1,0) base
     rule above even inside this container query — without re-
     asserting here the Joe cover stayed 2-col (96px portrait +
     name) on mobile, squeezing the name column to ~170 CSS-px.
     Original issue flagged 2026-05-17. */
  .dossier-cover.dossier-cover-joe { grid-template-columns: 1fr; }
  .dossier-cover-actions { flex-direction: row; align-items: flex-start; flex-wrap: wrap; }
  /* Gauges land in a 2-col grid so they pair off cleanly instead
     of wrapping into a ragged single-row ribbon. */
  .dossier-cover-gauges {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.75rem 1rem;
    align-items: start;
  }
}

/* ─── Alerts (banners) ─────────────────────────────────────── */

.alert {
  padding: 0.625rem 0.875rem;
  border-radius: var(--r-1);
  margin-bottom: 1rem;
  font-size: 0.875rem;
  border: 1px solid;
  border-left-width: 3px;
}
.alert.ok   { background: var(--st-active-bg);   border-color: rgba(74,128,96,0.32);   border-left-color: var(--cabinet-hi); color: #86efac; }
.alert.info { background: rgba(86,129,170,0.10); border-color: rgba(86,129,170,0.32);  border-left-color: var(--royal);      color: #93c5fd; }
.alert.warn { background: rgba(201,165,87,0.10); border-color: rgba(201,165,87,0.32);  border-left-color: var(--brass);      color: #fcd34d; }
.alert.bad  { background: var(--st-blocked-bg);  border-color: rgba(165,42,61,0.32);   border-left-color: var(--oxblood-hi); color: #fca5a5; }
.alert.ok-banner { background: var(--st-active-bg); border-color: rgba(74,128,96,0.32); border-left-color: var(--cabinet-hi); color: #86efac; }

/* ─── Tables ─────────────────────────────────────────────────── */

table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 0.8125rem;
  background: transparent;
  box-shadow: none;
}
th {
  text-align: left;
  font: 500 0.625rem/1.2 var(--font-mono);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-3);
  padding: 0.625rem 0.75rem;
  border-bottom: 1px solid var(--hair);
  background: rgba(0,0,0,0.10);
  font-weight: 500;
}
:root[data-theme="light"] th { background: rgba(0,0,0,0.02); }
td {
  padding: 0.75rem;
  border-bottom: 1px solid var(--hair);
  color: var(--text-2);
  font-size: 0.8125rem;
  vertical-align: top;
}
tr:hover td { background: rgba(201,165,87,0.04); color: var(--text-1); }
:root[data-theme="light"] tr:hover td { background: rgba(139,110,31,0.04); }
tr:last-child td { border-bottom: none; }
td.strong { color: var(--text-1); font-weight: 500; }

.jobs td.notes {
  overflow-wrap: anywhere;
  word-break: break-word;
  max-width: 28ch;
}
.jobs {
  display: block;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* 3.X — Bulk-action + filter toolbar above the api-jobs table.
   Sits inside the panel but outside the polled host div, so the
   2s table swap doesn't reset the operator's filter selections or
   the select-all checkbox state. Layout: inline-flex wrap, 0.75rem
   gap. `.job-bulk-delete-btn[disabled]` desaturates so the
   operator's eye doesn't read it as an active CTA before any row
   is checked. */
.job-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  margin: 0.5rem 0 0.75rem;
  font-size: 0.75rem;
}
.job-toolbar .job-filter-field,
.job-toolbar .job-select-all-field {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  color: var(--text-2);
}
.job-toolbar select {
  font-size: 0.75rem;
}
.job-toolbar .job-bulk-delete-form {
  display: inline-flex;
  margin: 0;
}
.job-toolbar .job-bulk-delete-btn {
  font-size: 0.75rem;
}
.job-toolbar .job-bulk-delete-btn[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
}
.job-toolbar .job-match-count {
  color: var(--text-3);
  font-size: 0.6875rem;
  margin-left: auto;
}
.jobs .job-check-col {
  width: 1.5rem;
  text-align: center;
  padding-left: 0.25rem;
  padding-right: 0.25rem;
}
.jobs .job-check-col input[type="checkbox"] {
  margin: 0;
  vertical-align: middle;
}

/* Canonical horizontal-scroll wrapper for wide tables. Use as
   `<div class="table-scroll"><table>…</table></div>` — the wrapper
   establishes the scroll container, while the inner table keeps
   its native `display: table` so the column algorithm works
   normally. Operator-confirmed via the debug-page probe (3.62.x
   stages 6→7) that `.jobs`'s `display: block; overflow-x: auto`
   alone doesn't reliably scroll on Brave Android — the
   block-level table can't function as a scroll container the way
   a plain `<div>` can, and the wide cell content pushes the
   surrounding panel-group past the viewport edge instead of
   scrolling internally. Wrapping the table in a plain block
   scroller is the canonical responsive-table pattern. */
.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  max-width: 100%;
}
.table-scroll > table {
  display: table;
  width: max-content;
  min-width: 100%;
  white-space: nowrap;
}

/* ─── Stats grid ─────────────────────────────────────────────── */

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.75rem;
}

.stat {
  background: var(--bg-2);
  border: 1px solid var(--hair);
  padding: 0.75rem 0.875rem;
  border-radius: var(--r-1);
}
.stat .label {
  font: 500 0.625rem/1 var(--font-mono);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-3);
}
.stat .value {
  font-size: 1.625rem;
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--display);
  margin-top: 0.375rem;
  font-variant-numeric: tabular-nums;
}
.stat .value.zero { color: var(--text-4); font-weight: 400; }

/* Gauge — Cinzel number + sub-line (used on dossier covers). */
.gauge { display: inline-flex; flex-direction: column; gap: 0.25rem; }
.gauge .gauge-label { font: 500 0.59375rem/1 var(--font-mono); letter-spacing: 0.22em; text-transform: uppercase; color: var(--text-3); }
.gauge .gauge-value { font: 500 1.625rem/1 var(--font-display); letter-spacing: 0.04em; color: var(--display); margin-top: 0.25rem; font-variant-numeric: tabular-nums; }
.gauge .gauge-value.brass   { color: var(--brass-hi); }
.gauge .gauge-value.cabinet { color: var(--cabinet-hi); }
.gauge .gauge-sub { font: 400 0.859375rem/1.2 var(--font-serif); font-style: italic; color: var(--text-3); margin-top: 0.25rem; }
:root[data-theme="light"] .gauge .gauge-value.brass   { color: var(--brass); }
:root[data-theme="light"] .gauge .gauge-value.cabinet { color: var(--cabinet); }

/* ─── Header / masthead ──────────────────────────────────────
   The chrome's top strip. Walnut/paper backplate, brass hairline
   bottom border. Holds the crest+wordmark, a divider, the
   "FIELD COMMAND CONSOLE" subtitle, the breadcrumb path, and
   right-aligned the brass-switch toggle + user menu. */

header {
  background: linear-gradient(180deg, var(--ground-2) 0%, var(--ground-1) 100%);
  border-bottom: 1px solid var(--brass-rim);
  color: var(--text-1);
  /* 2.229.1 — header is a 2-row wrapping flex. Row 1 carries the
     wordmark + masthead-tag + spacer + theme-switch + user-menu
     (fixed natural-height); row 2 carries the breadcrumb (full
     width via `flex: 0 0 100%` + `order: 1`). Pre-2.229.1 the
     header was a single-row flex `height: 4rem` and the breadcrumb
     fought for inline space with the right cluster; on narrow
     viewports an @media flipped it to wrap-mode but the row-1 then
     grew vertically because the wrapped breadcrumb pushed the
     theme-switch off the top edge. Wrap-always is the simpler
     contract and lets `FIELD COMMAND CONSOLE` stay visible at
     every viewport. */
  /* This ship — header tightened further: vertical padding
     0.3125rem → 0.1875rem (3px top + bottom) and row-gap
     0.1875rem → 0.0625rem (1px between row-1 cluster and the
     breadcrumb row). Combined with the `.crumb` padding drop
     below, the breadcrumb now sits as a tight band against the
     row-1 cluster + the brass hairline below. min-height stays
     2.5rem as the "don't collapse on empty-breadcrumb pages"
     floor — the row-1 wordmark naturally exceeds it. */
  /* 3.29 — header element's own layout properties floored at the
     ≤1024-tier rendered px (97% root dial). Pre-3.29 the
     @media (max-width: 720px) block re-declared the entire row
     layout (`padding: 0.5rem 0.75rem; min-height: 2.75rem;
     column-gap: 0.5rem`) for tap-target sizing, then the root
     dial scaled those down further at narrower tiers — operator-
     flagged via computed-property dump: GOOD@733px shows
     `column-gap: 15.52px, padding: 2.91px 21.34px, min-height:
     38.8px` while BAD@700px shows `column-gap: 6.64px, padding:
     6.64px 9.96px, min-height: 36.52px` (different rule, smaller
     content, AND the same JUMP at the 720 breakpoint that the
     operator wants gone). Floors lock the header's row layout
     at the GOOD values at every viewport ≤1024px; the @media
     720/480 row overrides retire entirely. */
  padding: max(2.91px, 0.1875rem) max(21.34px, 1.375rem);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* 3.30 — column-gap tightened from `max(15.52px, 1rem)` to
     `max(6px, 0.375rem)`. Operator-flagged: at narrow viewports
     the right cluster (`?` help button + day/night brass-switch +
     user-menu avatar) wraps to its own row earlier than needed.
     Pre-3.30 the 15.52px column-gap between every row-1 sibling
     consumed ~62px of inline width across 4 inter-item gaps for
     a regular user (or ~93px across 6 gaps for a super-admin
     once the GOOD/BAD debug chips ride too). Tightening to ~6px
     buys back ~38px (regular) / ~57px (super-admin) of header
     real-estate, pushing the wrap point ~40-60px smaller in
     viewport width. The spacer (`flex: 1`) still pushes the
     wordmark left and the cluster right; only the inter-item
     gaps within the cluster (and between wordmark + cluster
     after the spacer collapses) tighten. */
  column-gap: max(6px, 0.375rem);
  row-gap: max(0.97px, 0.0625rem);
  min-height: max(38.8px, 2.5rem);
  /* 3.29 — inherited body font-size floors at the ≤1024-tier
     rendered px (13.58px = 0.875rem * 97% dial — the operator's
     GOOD-state computed value). Locks the header subtree's
     em-relative descendants (h1 letter-spacing, breadcrumb
     em-relative children, etc) at GOOD proportions. */
  font-size: max(13.58px, 0.875rem);
  /* This ship — sticky-top so the masthead + breadcrumb stay
     pinned to the viewport top as the operator scrolls main.
     Body remains the scroll container (no scroll-container flip)
     so `<dialog>`'s native top-layer + position:absolute drop-
     downs keep their existing stacking contract.
     `z-index: 10` is one tier above the `.bottom-cluster`'s
     `z-index: 5` — same-z-index stacking contexts resolve on
     DOM order, and bottom-cluster comes later in the DOM, so
     equal z would let bottom-cluster paint over the user-menu
     drop-down when the menu reaches past the centre of the
     viewport on a short window. Bumping header to 10 keeps the
     menu (whose `position:absolute` z-index:50 is contained in
     the header's stacking context) above the bottom-cluster
     visually. */
  position: sticky;
  top: 0;
  z-index: 10;
  box-shadow: 0 1px 0 rgba(255,255,255,0.04), 0 2px 12px rgba(0,0,0,0.4);
  transition: background .3s, border-color .3s, box-shadow .3s;
}
:root[data-theme="light"] header {
  background: linear-gradient(180deg, var(--ground-2) 0%, var(--ground-3) 100%);
  box-shadow: 0 1px 0 rgba(255,255,255,0.5), 0 2px 8px rgba(0,0,0,0.04);
}
header h1 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--display);
}
header .sub { color: var(--text-3); font-size: 0.8125rem; }

/* Vertical divider between the wordmark and the subtitle / breadcrumb. */
header .masthead-divider {
  width: 1px;
  /* 3.29 — floor at the ≤1024-tier rendered px so the divider
     keeps its visual weight at narrower viewports. */
  height: max(21.34px, 1.375rem);
  background: var(--brass-rim);
  margin: 0 max(5.82px, 0.375rem);
  flex-shrink: 0;
}

header .masthead-tag {
  /* 3.29 — floor at the ≤1024-tier rendered px (10.67px =
     0.6875rem * 97% dial). Hidden at ≤720 by the existing
     @media rule — the floor only kicks in if a future ship
     widens the tag's visibility breakpoint. */
  font: 400 max(10.67px, 0.6875rem)/1 var(--font-mono);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-3);
  white-space: nowrap;
}

/* 2.171.0 — chromeless surfaces (pre-auth, public landing, 404) use
   `renderChromeBanner` from admin-chrome.ts, which lays out the same
   wordmark + brass-switch theme toggle as the signed-in chrome but
   without a breadcrumb in the middle. `.masthead-spacer` fills the
   space the breadcrumb would otherwise claim (it carries `flex: 1`
   on signed-in surfaces) so the theme-switch still pins to the
   right edge. Mirrors `header nav.breadcrumb { flex: 1; min-width: 0 }`. */
header .masthead-spacer {
  flex: 1;
  min-width: 0;
}

/* ─── Breadcrumb (in header) — file-path style ────────────────
   `// crumb / crumb / current` in monospace; brass slashes; the
   current segment in brass-hi. Type subtitle renders above the
   label in tiny mono. */

header nav.breadcrumb {
  display: flex;
  align-items: center;
  gap: max(3.88px, 0.25rem);
  /* 2.229.1 — breadcrumb font-size is JS-controlled via the
     `--bc-fs` custom property so it shrinks to fit the row width.
     Fallback 0.75rem matches the pre-2.229.1 default if the inline
     auto-fit script doesn't run (e.g. operator with JS disabled).
     Letter-spacing scales with the font via `em` so tight crumbs
     stay tight at small sizes.
     3.29 — fallback floored at the ≤1024-tier rendered px (11.64px
     = 0.75rem * 97% dial) for parity with the rest of the header
     chrome. The JS auto-fit script can still shrink past this
     when the breadcrumb actually overflows; the floor only
     applies when `--bc-fs` is unset (no-JS / pre-paint). */
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: var(--bc-fs, max(11.64px, 0.75rem));
  line-height: 1;
  letter-spacing: 0.02em;
  /* Own-row layout: full width, drops below the row-1 wordmark +
     tag + theme-switch + user-menu. `order: 1` so the breadcrumb
     wraps to a NEW row after the right cluster regardless of its
     document position. */
  flex: 0 0 100%;
  width: 100%;
  order: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
}
header nav.breadcrumb::before {
  content: "//";
  color: var(--text-4);
  margin-right: 0.25rem;
  font-weight: 500;
}
header nav.breadcrumb .crumb {
  color: var(--text-2);
  text-decoration: none;
  padding: 0;
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 1.05;
  white-space: nowrap;
  transition: color .15s;
}
header nav.breadcrumb a.crumb:hover { color: var(--brass-hi); text-decoration: none; }
:root[data-theme="light"] header nav.breadcrumb a.crumb:hover { color: var(--brass); }
header nav.breadcrumb .crumb.current { color: var(--brass-hi); font-weight: 600; }
:root[data-theme="light"] header nav.breadcrumb .crumb.current { color: var(--brass); }
header nav.breadcrumb .crumb-label {
  font-family: var(--font-display);
  /* 2.229.1 — em-relative so the breadcrumb auto-fit script's
     `--bc-fs` cascades down. Label sized 1.08em above the parent
     so the Cinzel name reads as the primary slug + the mono type
     line sits under it at ~0.67em. */
  font-size: 1.08em;
  letter-spacing: 0.04em;
  text-transform: none;
}
header nav.breadcrumb .crumb-type {
  font-family: var(--font-mono);
  font-size: 0.67em;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--text-4);
  margin-bottom: 1px;
}
header nav.breadcrumb .crumb-sep {
  color: var(--brass-rim);
  user-select: none;
  align-self: center;
  font-size: 1.16em;
  margin: 0 0.125rem;
}

/* ─── Brass-switch theme toggle ───────────────────────────────
   The masthead's day/night affordance. Renders as a `<button>`
   with track + sun glyph + moon glyph + sliding brass knob.
   Clicking flips `data-theme` + writes the `skylace-theme` cookie
   (handled by inline JS in admin-chrome.ts). */

.theme-switch {
  display: inline-flex;
  align-items: center;
  /* 3.29 — gap + padding floored at the ≤1024-tier rendered px
     so the inner label / track / label rhythm doesn't collapse
     at narrower root dial tiers. See `.skylace-mark` for the
     rationale. */
  gap: max(7.76px, 0.5rem);
  /* This ship — vertical padding dropped to 0 and the inner
     track shrunk from 1.75rem to 1.5rem (see `.theme-switch-
     track` below). The outer pill is now ~26px tall (24px track
     + 2px border) instead of ~34px, leaving the user-menu's
     2rem avatar as the tallest right-cluster element rather
     than the day/night pill. Horizontal padding unchanged so
     the label gap rhythm stays. */
  padding: 0 max(7.76px, 0.5rem) 0 max(9.7px, 0.625rem);
  background: var(--ground-3);
  border: 1px solid var(--brass-rim);
  border-radius: 99px;
  transition: all .3s;
  flex-shrink: 0;
}
:root[data-theme="light"] .theme-switch { background: var(--ground-3); }
.theme-switch-label {
  /* 3.29 — NIGHT / DAY label floored at the ≤1024-tier rendered
     size (9.7px = 0.625rem * 97% dial). */
  font: 500 max(9.7px, 0.625rem)/1 var(--font-mono);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-4);
  transition: color .3s;
  white-space: nowrap;
  user-select: none;
}
.theme-switch-label.is-active { color: var(--brass-hi); }
:root[data-theme="light"] .theme-switch-label.is-active { color: var(--brass); }

.theme-switch-track {
  position: relative;
  /* 3.29 — floor at the ≤1024-tier rendered size (54.32px ×
     23.28px = 3.5rem × 1.5rem * 97% dial).
     3.30 — `min-height: 0` overrides the generic `@media
     (max-width: 720px) { button, .button-link { min-height:
     2.375rem } }` mobile-tier rule. Pre-3.30 the track inflated
     to 31.54px (2.375rem * 83% dial) at ≤720px while the rest of
     the floored chrome held at the GOOD-state sizes — operator-
     flagged via the new GOOD/BAD debug buttons (3.301.0). The
     `<button>`-element track was the last regression channel: the
     generic mobile button min-height was winning over `height:
     max(23.28px, 1.5rem)` because min-height beats height when
     min > height. `box-sizing: border-box` pins the outer size
     to the explicit 1.5rem regardless of border (same pattern as
     `.help-button` per 3.290.4). Same fix could apply
     transitively to `.theme-switch-knob` but the knob is a
     `<span>` not a `<button>`, so the generic mobile-button rule
     never matched it — its position centres via `top: 50%` +
     `translateY(-50%)` against the track, so once the track
     stops inflating the knob centres correctly too. */
  width: max(54.32px, 3.5rem);
  height: max(23.28px, 1.5rem);
  min-height: 0;
  box-sizing: border-box;
  border-radius: 0.75rem;
  border: 1px solid var(--brass-rim);
  cursor: pointer;
  padding: 0;
  background: linear-gradient(180deg, var(--ground-3) 0%, var(--ground-1) 100%);
  box-shadow: inset 0 1px 1px rgba(0,0,0,0.6);
  transition: background .3s, box-shadow .3s;
  flex-shrink: 0;
}
:root[data-theme="light"] .theme-switch-track {
  background: linear-gradient(180deg, #e0d6b8 0%, #c8baa0 100%);
  box-shadow: inset 0 1px 1px rgba(0,0,0,0.10);
}
.theme-switch-track:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
.theme-switch-glyph {
  position: absolute;
  /* 2.230.1 — vertical centre via translateY so the glyph sits on
     the track's centerline at every dial tier. Pre-2.230.1 the
     `top: 0.375rem` value was tuned for the 100% root; at the 80%
     dial (≤480px) the track's 1px border (which doesn't scale with
     rem) drifted the glyph visibly off-centre. */
  top: 50%;
  transform: translateY(-50%);
  /* 3.29 — floor at the ≤1024-tier rendered size (13.58px =
     0.875rem * 97% dial). */
  width: max(13.58px, 0.875rem);
  height: max(13.58px, 0.875rem);
  pointer-events: none;
  transition: opacity .3s;
}
.theme-switch-moon { left: max(6.79px, 0.4375rem); opacity: 0.75; }
.theme-switch-sun  { right: max(6.79px, 0.4375rem); opacity: 0.40; }
:root[data-theme="light"] .theme-switch-moon { opacity: 0.45; }
:root[data-theme="light"] .theme-switch-sun  { opacity: 0.85; }

.theme-switch-knob {
  position: absolute;
  /* 2.230.1 — same translateY-centre fix as the glyphs above.
     Operator-flagged: at the smallest dial tier (80% root, <=480px)
     the yellow blob lost its vertical centre. With `top: 0.125rem`
     the knob's bottom-gap was ~2× its top-gap because the track's
     1px border (px, not rem) ate proportionally more space at
     smaller dials. translateY pins the knob centre to the track
     centre regardless of how the rem-vs-px math shakes out. */
  top: 50%;
  /* 3.29 — knob position + size floored at the ≤1024-tier
     rendered px (1.94 / 17.46 / 8.73 / 34.92 px). The `left`
     position tracks the track-edge inset, so it floors at the
     same factor. */
  left: max(1.94px, 0.125rem);
  width: max(17.46px, 1.125rem);
  height: max(17.46px, 1.125rem);
  border-radius: max(8.73px, 0.5625rem);
  background: radial-gradient(circle at 35% 30%, #f0d99a 0%, var(--brass) 45%, var(--brass-lo) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.55),
    inset 0 -2px 3px rgba(0,0,0,0.4),
    0 1px 2px rgba(0,0,0,0.5);
  transform: translateY(-50%);
  /* `left` transitions; `transform` stays static so the
     vertical centre never animates. */
  transition: left .35s cubic-bezier(.4, 0, .2, 1);
  pointer-events: none;
}
:root[data-theme="light"] .theme-switch-knob { left: max(34.92px, 2.25rem); }

/* ─── User menu (account dropdown) ─────────────────────────── */

header .user-menu {
  position: relative;
  flex-shrink: 0;
}
header .user-menu summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
}
header .user-menu summary::-webkit-details-marker { display: none; }
header .user-menu summary::marker { display: none; }
header .user-menu .user-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 3.29 — avatar size + initials font floored at the ≤1024-
     tier rendered px (31.04px = 2rem * 97% dial; 10.67px =
     0.6875rem * 97% dial). See `.skylace-mark` for the
     rationale — keep the right-cluster proportionate at every
     viewport below the operator-named "perfect" threshold. */
  width: max(31.04px, 2rem);
  height: max(31.04px, 2rem);
  border-radius: 50%;
  background: linear-gradient(180deg, var(--brass-hi), var(--brass-lo));
  color: #1a1208;
  font-family: var(--font-display);
  font-size: max(10.67px, 0.6875rem);
  font-weight: 600;
  letter-spacing: 0.04em;
  border: 1px solid var(--brass-rim);
  user-select: none;
  object-fit: cover;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.4),
    inset 0 -1px 0 rgba(0,0,0,0.5);
  transition: filter .15s, border-color .15s;
}
header .user-menu .user-avatar-img {
  background: var(--ground-3);
  box-shadow: none;
  /* 2.230.1 — `<img>` is a replaced element, so the `inline-flex`
     on the `.user-avatar` class doesn't centre its raster content
     the way it does for the span / initials variant. Force `block`
     so the image fills the 2rem box as a clean rectangle, and
     `vertical-align: middle` so the inline baseline doesn't push
     the box down a hair relative to the theme-switch beside it.
     Operator-flagged: "v-align centre that avatar image too". */
  display: block;
  vertical-align: middle;
}
header .user-menu summary:hover .user-avatar,
header .user-menu summary:focus-visible .user-avatar {
  filter: brightness(1.08);
  border-color: var(--brass);
  outline: none;
}
header .user-menu[open] .user-avatar { filter: brightness(1.08); border-color: var(--brass); }

header .user-menu-panel {
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  min-width: 15rem;
  background: linear-gradient(180deg, var(--ground-2) 0%, var(--ground-1) 100%);
  border: 1px solid var(--brass-rim);
  border-radius: var(--r-1);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.04),
    0 10px 32px rgba(0,0,0,0.5);
  padding: 0.5rem 0;
  z-index: 50;
  display: flex;
  flex-direction: column;
  gap: 0;
}
:root[data-theme="light"] header .user-menu-panel {
  background: linear-gradient(180deg, var(--ground-2) 0%, var(--ground-3) 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.6), 0 8px 22px rgba(0,0,0,0.10);
}
header .user-menu-identity {
  padding: 0.625rem 0.875rem 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}
header .user-menu-email {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--text-1);
  word-break: break-all;
  letter-spacing: 0.02em;
}
header .user-menu-role { line-height: 1; }
header .user-menu-sep {
  height: 1px;
  background: var(--brass-rim);
  margin: 0.25rem 0;
}
header .user-menu-links { display: flex; flex-direction: column; }
header .user-menu-links a {
  display: block;
  padding: 0.5rem 0.875rem;
  color: var(--text-2);
  font: 500 0.75rem/1 var(--font-body);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  transition: background .15s, color .15s;
}
header .user-menu-links a:hover,
header .user-menu-links a:focus-visible {
  background: rgba(201,165,87,0.08);
  color: var(--brass-hi);
  outline: none;
}
:root[data-theme="light"] header .user-menu-links a:hover,
:root[data-theme="light"] header .user-menu-links a:focus-visible {
  background: rgba(139,110,31,0.08);
  color: var(--brass);
}
header .user-menu-links a[aria-current="page"] { color: var(--brass-hi); }
:root[data-theme="light"] header .user-menu-links a[aria-current="page"] { color: var(--brass); }
/* 3.30 — theme-switch slot inside the user-menu panel. The
   brass-switch toggle moved here from the masthead row to free
   ~150px of inline width upstream; the toggle pill is centred
   in its own slot above the sign-out footer. Slot wraps with
   the same horizontal padding as the links row so the toggle
   sits flush with the panel's content rhythm. */
header .user-menu-theme {
  display: flex;
  justify-content: center;
  padding: 0.5rem 0.875rem;
}

header .user-menu-footer {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.375rem 0.875rem 0.5rem;
}
header .user-menu-footer form { margin: 0; flex: 1 1 auto; }
header .user-menu-footer form button[type="submit"] {
  width: 100%;
  height: 1.75rem;
}

/* ─── Main / content area ─────────────────────────────────── */

main {
  max-width: 1200px;
  margin: 0 auto;
  padding: 1.5rem 1.75rem;
}
/* 2.221.0 — wide variant for surfaces whose native layout is a
   3-col grid (briefing-room Squad detail).
   2.223.1 — operator review on a 1080p screen: 1600px still left
   ~160px gutters each side. Drop the explicit max-width cap and
   trim horizontal padding to 0.75rem so the briefing grid uses
   the full viewport with only a hairline gutter. The chrome above
   (masthead / breadcrumb) widens with it; both are single-row
   flex layouts with a spacer so they handle any width gracefully.
   This ship — cap at `100%` (was `none`). body is a flex column
   with `align-items: stretch`, so the flex line's cross size is
   the max-content of any item; the Squad detail's `.teleprinter`
   ribbon has `flex-shrink: 0` ticks (each `max-width: 32rem`)
   whose sum exceeds the viewport once a handful of events are on
   the wire, and with `max-width: none` main was growing to
   accommodate (measured at 3343px on a 1910px viewport — operator
   reported needing 67% zoom to fit). `100%` clamps main to body
   (= viewport, since html/body carry `overflow-x: clip`), at
   which point the teleprinter's existing `overflow-x: auto`
   kicks in and scrolls internally as designed. */
/* `width: 100%` is load-bearing alongside `max-width: 100%`. body is
   `display: flex; flex-direction: column` and the base `main` rule
   above sets `margin: 0 auto`. When a flex-column child has auto
   cross-axis margins, the child's cross-size is determined by its
   content (max-content), then the auto margins absorb the rest as
   centring gutters — `max-width: 100%` does NOT force stretch. On
   surfaces whose content max-content happens to exceed the viewport
   (Squad detail with its long teleprinter, Company detail with wide
   missions/metrics tables), the page renders edge-to-edge anyway —
   which is why the bug hid here for 13 minor versions. On surfaces
   whose content max-content is narrower than the viewport (Mission
   detail with a thin centre Squads panel + empty right rail under
   the system default), `<main>` collapses to ~max-content + padding,
   leaving the operator with a ~300px brass-coloured letterbox each
   side. `width: 100%` pins the cross-size explicitly, the auto
   margins have no free space to absorb, and the page renders
   full-width regardless of inner content shape. */
main.page-wide {
  width: 100%;
  max-width: 100%;
  padding-left: 0.75rem;
  padding-right: 0.75rem;
}

/* Page-level breadcrumb (legacy — admin tree views use this).
   The header's nav.breadcrumb is the canonical one. */
.breadcrumb {
  font: 400 0.75rem/1.4 var(--font-mono);
  color: var(--text-3);
  margin-bottom: 0.875rem;
  letter-spacing: 0.02em;
}
.breadcrumb a { color: var(--text-2); }
.breadcrumb a:hover { color: var(--brass-hi); }

/* ─── Empty state ─────────────────────────────────────────── */

.empty {
  color: var(--text-3);
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 1.1em;
  padding: 1.125rem 0;
}

/* ─── Footer ──────────────────────────────────────────────── */

.footer {
  margin-top: 1.5rem;
  font: 400 0.625rem/1 var(--font-mono);
  color: var(--text-4);
  text-align: center;
  letter-spacing: 0.20em;
  text-transform: uppercase;
}

/* This ship — pinned-chrome bottom cluster. Wraps the optional
   pre-footer slot (`opts.bottomStickyHtml` — Squad detail's live
   teleprinter ribbon is the first caller) plus the always-present
   `.skylace-foot`. `position: sticky; bottom: 0` glues the
   cluster to the viewport bottom as `<main>` scrolls between it
   and the sticky `<header>`. `z-index: 5` matches the header
   (both above briefing-grid panel shadows, both below the user-
   menu drop-down at z-index 50). The cluster carries its own
   opaque background so main's content doesn't bleed through
   from underneath; `border-top` echoes the header's `border-
   bottom` for visual symmetry. The teleprinter's existing
   `border-top` / `.skylace-foot`'s `border-top` are both
   suppressed inside the cluster (they'd compound visually
   against the cluster's own border). */
.bottom-cluster {
  position: sticky;
  bottom: 0;
  z-index: 5;
  background: linear-gradient(180deg, var(--ground-1) 0%, var(--ground-2) 100%);
  border-top: 1px solid var(--brass-rim);
  box-shadow: 0 -2px 12px rgba(0,0,0,0.4), 0 -1px 0 rgba(255,255,255,0.04);
  /* Flex column so a pre-footer slot stacks above the footer
     without margin shenanigans. */
  display: flex;
  flex-direction: column;
}
:root[data-theme="light"] .bottom-cluster {
  background: linear-gradient(180deg, var(--ground-3) 0%, var(--ground-2) 100%);
  box-shadow: 0 -2px 8px rgba(0,0,0,0.06), 0 -1px 0 rgba(255,255,255,0.5);
}
/* The teleprinter + footer each carry their own top border in
   the standalone shape; inside the cluster the cluster's own
   border-top is the single source of separation between main +
   the pinned cluster, so suppress the descendants' borders.
   This ship — also zero out the teleprinter's standalone
   `margin-top: 1rem` (which used to separate it from
   briefing-grid content above) — inside the cluster the
   margin reads as a slab of whitespace between the cluster's
   golden top border and the teleprinter's text. */
.bottom-cluster > .teleprinter,
.bottom-cluster > .skylace-foot {
  border-top-color: transparent;
}
.bottom-cluster > .teleprinter {
  margin-top: 0;
}

/* 2.182.0 — shared page footer (`.skylace-foot`) shipped by
   `renderSkylaceFooter` in admin-chrome.ts. Both palettes covered:
   the dark variant uses `--text-2` for the body + `--text-4` for
   separators, so the row stays readable on the walnut ground (the
   pre-2.182 hardcoded `rgba(26,22,16,0.66)` was near-black on cream
   but invisible on the dark theme). Sits flush at the bottom of the
   page; no margin-top because the wrapping `<body>` already shapes
   the layout. */
.skylace-foot {
  flex: 0 0 auto;
  padding: 0.75rem 2rem;
  border-top: 1px solid var(--brass-rim);
  background: var(--ground-2);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  font: 400 0.625rem/1 var(--font-mono);
  color: var(--text-2);
  letter-spacing: 0.08em;
  position: relative;
  z-index: 2;
}
.skylace-foot a {
  color: inherit;
  text-decoration: none;
  transition: color .15s;
}
.skylace-foot a[href]:hover { color: var(--brass-hi); }
:root[data-theme="light"] .skylace-foot a[href]:hover { color: var(--brass); }
/* 2.188.0 — the © Sky Elemental attribution opens skyelemental.com
   in a new tab; a quiet brass underline marks it as a link without
   shouting (the right-cluster Home/Help/Status/Privacy row stays
   plain because the colon `·` separators already signal "link
   cluster" by typography). */
.skylace-foot-attribution {
  color: var(--brass-hi);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
:root[data-theme="light"] .skylace-foot-attribution { color: var(--brass); }
.skylace-foot-list {
  display: flex;
  align-items: center;
  gap: 0.625rem;
}
.skylace-foot-list .sep { color: var(--text-4); }
:root[data-theme="light"] .skylace-foot {
  background: var(--ground-1);
  border-top-color: rgba(26,22,16,0.08);
  color: rgba(26,22,16,0.78);
}
:root[data-theme="light"] .skylace-foot-list .sep {
  color: rgba(26,22,16,0.42);
}
@media (max-width: 520px) {
  /* 2.191.0 — narrow viewports: the attribution row + Home/Help/
     Status/Privacy link cluster combined exceed ~380 px, which
     pushes past most phones. Allow the two halves to wrap +
     centre-align so they stack readably (attribution on row 1,
     link row centred underneath on row 2). */
  .skylace-foot {
    padding: 0.625rem 0.875rem;
    flex-wrap: wrap;
    justify-content: center;
    text-align: center;
    row-gap: 0.375rem;
    column-gap: 0.875rem;
  }
  .skylace-foot-list { flex-wrap: wrap; justify-content: center; gap: 0.375rem 0.625rem; }
}

/* ─── Teleprinter ribbon (Squad detail, etc.) ─────────────── */

.teleprinter {
  flex: 0 0 auto;
  padding: 0.625rem 2rem;
  background: var(--ground-0);
  border-top: 1px solid var(--brass-rim);
  display: flex;
  align-items: center;
  gap: 1rem;
  font: 400 0.6875rem/1.4 var(--font-mono);
  color: var(--text-3);
  overflow: hidden;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.03), inset 0 -1px 0 rgba(0,0,0,0.4);
}
.teleprinter-tag {
  color: var(--brass-hi);
  letter-spacing: 0.18em;
  font-weight: 600;
  white-space: nowrap;
  flex-shrink: 0;
}
:root[data-theme="light"] .teleprinter-tag { color: var(--brass); }
.teleprinter-tick {
  display: inline-flex;
  align-items: baseline;
  gap: 0.375rem;
  white-space: nowrap;
}
.teleprinter-tick-when { color: var(--text-4); }
.teleprinter-tick-who  { color: var(--brass-hi); font-weight: 600; }
.teleprinter-tick-what { color: var(--text-1); }
.teleprinter-sep { color: var(--brass-rim); margin: 0 0.375rem; user-select: none; }

/* ─── Mobile responsive ──────────────────────────────────── */

/* 2.196.0 — scale the root font-size dial down on mobile. The
   default `html { font-size: 132% }` was set 2.162.0+ for the
   walnut+brass desktop reading distance; on a phone held closer
   to the face, every Cinzel display heading (2.25rem ≈ 47 px) and
   panel body (0.875rem ≈ 18 px) reads oversized. Stepping the dial
   down at the breakpoints lets every rem-based size cascade to a
   phone-appropriate scale without per-element overrides. The
   header / panel / button tweaks below build on top of the dial
   shift — proportions stay coherent, absolute sizes shrink. */
/* 2.203.1 — selectors switched from `html` to `:root` so the
   mobile dial actually wins the cascade. The default 132% dial
   is set inside the giant `:root { ... }` token block (line ~645)
   — `:root` has pseudo-class specificity (0,1,0) which beats
   `html`'s element specificity (0,0,1). 2.196.0–2.202.1 set the
   media-query rules on `html`, which `:root` quietly overrode —
   operator-reported "ZERO change on Brave on OnePlus 8" was the
   symptom. Matching the selector closes the gap. */
@media (max-width: 1024px) { :root { font-size: 97%; } }
@media (max-width: 720px) {
  :root { font-size: 83%; }
  /* `width: 100vw` is the missing piece that finally contained
     a mobile-overflow bug that survived five targeted ship
     attempts (3.51 → 3.66). Under `display: flex; flex-direction:
     column` on body, main is a flex item that should stretch
     cross-axis to body's clientWidth via `align-items: stretch` —
     but on Brave Android (verified via the
     `/c/<co>/debug?stage=7` overlay) main was rendering at
     ~418 px on a 384 px viewport even with `min-width: 0`
     applied. The `align-items: stretch` constraint somehow
     wasn't holding; descendants with `width: 100%` then
     stretched with main, propagating the apparent overflow to
     every panel-shaped child. Forcing `width: 100vw` +
     `max-width: 100vw` sidesteps the flex sizing and pins main
     to the viewport. Combined with the existing `overflow-x:
     hidden`, any descendant that would overflow gets clipped at
     the main boundary instead of leaking up the chain. */
  main {
    padding: 0.75rem;
    overflow-x: hidden;
    width: 100vw;
    max-width: 100vw;
    box-sizing: border-box;
  }
  /* 2.229.1 — the default `header` rule now wraps + drops the
     breadcrumb to its own row at every viewport, so the mobile
     override only adjusts the row-1 padding / sizing.
     2.229.2 — hide the masthead-tag at <=720px (not <=480px the
     2.229.1 ship attempted). Between 480 and 720 the row-1
     cluster [wordmark + tag + spacer + theme-switch + user-menu]
     overflowed the row at every realistic tablet width: the
     `Field command console` mono-caps line wants ~270px on its
     own at the 92% dial, and combined with the wordmark + right
     cluster it crossed 480px+ thresholds. flex-wrap then split
     the right cluster off onto its own row, making the day/night
     box appear to "grow in height" (operator-flagged). Tag is
     the most expendable element — desktop (>720) keeps it;
     tablet + phone drop it. */
  /* 3.29 — pre-3.29 `header { padding: 0.5rem 0.75rem; min-height:
     2.75rem; column-gap: 0.5rem }` re-declared the row layout at
     ≤720 for tap-target sizing; combined with the 83% root dial
     it dropped column-gap to 6.64px, padding to 6.64×9.96px, and
     min-height to 36.52px — a visible jump at the 720 breakpoint
     vs the GOOD-state values one px higher (15.52 / 2.91×21.34 /
     38.8). The base `header` rule now floors all four properties
     at the GOOD values, so this override is retired and the
     header element's own layout is identical at every viewport
     ≤1024px. The tap-target rule below (`button, .button-link
     { min-height: 2.375rem }`) is unchanged — that's body
     content sizing, not header chrome. */
  header .masthead-tag, header .masthead-divider { display: none; }
  header .user-menu-panel { right: 0; left: auto; }
  /* 2.196.0 — keep the Night/Day labels (the new mobile dial fits
     them comfortably).
     3.29 — pre-3.29 a `.skylace-crest { width: 1.375rem }` + `.skylace-mark
     { font-size: 0.9375rem; letter-spacing: 0.20em; gap: 0.375rem }`
     pair shrunk the wordmark to 12.45px on the 83% phone dial —
     operator-flagged "the whole header looks shit". The base rules
     now floor at the ≤1024-tier rendered px via `max()`, so those
     explicit shrinks aren't needed (and were actively fighting the
     floor). The header chrome holds its ≤1024-tier proportions at
     every viewport below; body content keeps shrinking via the
     root dial as before. */
  .panel { padding: 0.75rem; margin-bottom: 0.75rem; }
  .panel h2 { font-size: 0.8125rem; }
  .panel-group { padding: 0.25rem 0; margin-bottom: 0.75rem; }
  .panel-group > .panel { padding: 0.625rem 0.75rem; }
  .panel-group > .panel h3 { font-size: 0.8125rem; }
  table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; white-space: nowrap; }
  th, td { padding: 0.5rem 0.625rem; }
  input[type=text], input[type=email], input[type=password], input[type=url],
  input[type=number], input[type=search], textarea, select { width: 100%; min-width: 0; box-sizing: border-box; }
  button, .button-link { padding: 0.5rem 0.75rem; font-size: 0.75rem; min-height: 2.375rem; }
  .grid { grid-template-columns: 1fr; gap: 0.625rem; }
  .stat .value { font-size: 1.125rem; }
  .breadcrumb { font-size: 0.6875rem; line-height: 1.4; }
  pre { font-size: 0.6875rem; padding: 0.5rem 0.625rem; }
  pre code { white-space: pre-wrap; word-break: break-word; }
  h1.display { font-size: 1.375rem; }
  h2.display { font-size: 1.125rem; }
  .teleprinter { padding: 0.5rem 0.875rem; flex-wrap: wrap; gap: 0.5rem; }
}

/* 2.196.0 — narrow phone tier. 2.202.0 — dial pushed to 88% so
   the whole UI reads phone-appropriate (operator feedback was
   that the 95% tier still felt oversized on a real phone — large
   wordmark, chunky display headings dominating above-the-fold). */
@media (max-width: 480px) {
  :root { font-size: 72%; }
  /* 2.229.2 — masthead-tag now hides at the parent 720px tier;
     phone-tier used to tighten the header padding further.
     3.29 — that further tighten retires: the base `header` rule
     floors padding + column-gap at the GOOD-state values, so the
     header element's own layout is now identical at every
     viewport ≤1024px (operator-flagged "stop applying BAD"
     when the row crosses the 720 / 480 breakpoints). */
  main { padding: 0.625rem; }
  .panel { padding: 0.625rem; }
  h1.display { font-size: 1.125rem; }
  h2.display { font-size: 0.9375rem; }
  /* Narrow tier: stack the identity column so the cap-badge /
     big portrait sits ABOVE the name + meta row rather than
     hogging the left edge while the name column wraps mid-word.
     Applies to every dossier-cover variant — the wider cap-badge
     primitives (Squad/Mission/Company cap-badge, Joe big
     portrait) all benefit from the same vertical stack. */
  .dossier-cover { padding: 0.875rem 1rem; gap: 0.75rem; }
  .dossier-cover-identity {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.625rem;
  }
  /* Big portrait shrinks at the narrow tier — 96×112 was tuned for
     the desktop hero; at sub-480 it leaves the rest of the row no
     room. 72×84 keeps the brass-rim affordance legible without
     dominating the panel. The wrapping portrait-stack tracks the
     portrait's width so the edit-mode REGENERATE button glued
     under the portrait stays flush with the photo above it
     (pre-3.281 the stack stayed at the desktop 6rem and the button
     overhung the portrait by 1.5rem on mobile). */
  .operative-portrait.big { width: 4.5rem; height: 5.25rem; }
  .dossier-portrait-stack { width: 4.5rem; }
  /* 3.X (joe-mobile-callsign) — Joe dossier-cover overrides the
     identity-stacks-to-column rule above. The Joe's `big` portrait
     at 4.5rem wide is narrow enough that the callsign reads
     comfortably alongside it on mobile (unlike the wider cap-badge
     primitives used by Squad / Mission / Company dossiers, which
     keep the vertical stack from the rule above). Operator-flagged:
     stacking the photo above the name on mobile wasted the
     vertical room the callsign needed to read as the dominant
     element on the Joe page. Callsign h2 bumped to 2.25rem (vs the
     desktop 1.625rem) so it stays heroic at the 72% root-dial; the
     identity row stretches across the panel width with the name
     column flexing to fill the space next to the portrait. */
  .dossier-cover-joe .dossier-cover-identity {
    flex-direction: row;
    align-items: flex-start;
    gap: 0.75rem;
  }
  .dossier-cover-joe .dossier-cover-name {
    flex: 1 1 auto;
    min-width: 0;
  }
  .dossier-cover-joe .dossier-cover-name h1,
  .dossier-cover-joe .dossier-cover-name h2 {
    font-size: 2.25rem;
  }
  /* Trade row's vertical hairlines look like floating dividers when
     the row wraps onto two lines on a narrow viewport — hide them
     and let the field-column shape do the visual separation. */
  .dossier-trade-row { gap: 0.625rem 0.75rem; }
  .dossier-trade-sep { display: none; }
}

/* 2.203.0 — extra-narrow phone tier (sub-iPhone-SE). Pushes the
   dial one more notch so genuinely small viewports (240–360 px,
   Android Go devices, etc.) stop feeling chunky.

   2.230.3 — drop the theme-switch + user-menu at the ≤360 tier.
   Operator-reported "smallest day/night is larger than the medium"
   — the rem-driven sizing math should make the smallest tier the
   smallest visually, but a layout / wrap artefact pushed the
   right-cluster chrome wider than at 480-720. Hiding both at the
   extra-narrow tier removes the dispute; profile + sign-out stay
   reachable on any viewport ≥361 (rotate to landscape, or use a
   tablet). Wordmark + masthead + breadcrumb keep their full
   readable layout. */
@media (max-width: 360px) {
  :root { font-size: 68%; }
  /* 3.29 — drop the help button at the same tier as the
     theme-switch + user-menu (precedent: 2.230.3). On extra-
     narrow phones the row-1 cluster needs to collapse all the
     way down to the wordmark + breadcrumb; help is still
     reachable on any viewport ≥361 (rotate, or use a tablet).
     3.30 — `header .theme-switch` retired from this hide rule:
     the toggle moved into the user-menu panel, so hiding the
     user-menu already takes the toggle with it. Pre-3.30 the
     selector targeted the masthead-row instance; post-3.30
     there's no masthead-row instance to hide (the chromeless
     pre-auth `renderChromeBanner` still emits the toggle in-
     row, but that surface is outside the signed-in chrome
     this @media rule applies to). */
  header .user-menu,
  header .help-button { display: none; }
}

/* ─── Scrollbars ─────────────────────────────────────────── */

/* Thumb uses --text-4 (auto-inverting low-emphasis ink): in dark
   mode it renders as semi-transparent vellum against the deep
   walnut track; in light mode as semi-transparent paper-ink
   against the parchment track. Either way the thumb sits visibly
   one step away from the track instead of melting into it (which
   the previous --ground-3-on-ground-0 mapping did in light mode,
   where ground-3 is paler than ground-0). */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--text-4); border-radius: 5px; }
::-webkit-scrollbar-thumb:hover { background: var(--brass-rim); }
::-webkit-scrollbar-track { background: var(--ground-0); }

/* ─── Keyframes ─────────────────────────────────────────── */

@keyframes sky-brass-pulse {
  0%, 100% { box-shadow: inset 0 0 0 1px rgba(201,165,87,0.40), 0 0 0 0 rgba(201,165,87,0.0); }
  50%      { box-shadow: inset 0 0 0 1px rgba(227,200,135,0.85), 0 0 12px 1px rgba(201,165,87,0.25); }
}
@keyframes sky-radar-sweep {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
@keyframes sky-blink {
  0%, 49%, 100% { opacity: 1; }
  50%, 99%      { opacity: 0.3; }
}
@keyframes sky-teleprinter {
  from { width: 0; }
  to   { width: 100%; }
}

.sky-pulse  { animation: sky-brass-pulse 3.6s ease-in-out infinite; }
.sky-radar  { animation: sky-radar-sweep 6s linear infinite; transform-origin: center; }
.sky-blink  { animation: sky-blink 1.8s steps(1, end) infinite; }
.sky-teleprinter {
  display: inline-block;
  overflow: hidden;
  white-space: nowrap;
  animation: sky-teleprinter 1.2s steps(40, end);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; }
  .sky-pulse, .sky-radar, .sky-blink, .sky-teleprinter, .pill .dot, .standing-pill .dot { animation: none !important; }
}

/* ─── Job status page · "Signals room" stage rows (3.16.0) ──
   Each config-worker stage renders as one row: pip (state)
   + label + substep + duration + a thin progress bar that
   pulses for the current stage and fills cabinet-green / oxblood
   for completed / failed stages. The polling client (inline JS
   on /jobs/<id>) mutates `.stage-{pending,running,ok,fail}` on
   each .stage-row in place — no full re-renders.  Visual
   reference: Signals room "VPS · regimental hosts" panel +
   HostBar from the reskin design canvas.  */
.job-status-panel { display: flex; flex-direction: column; gap: 1rem; }
.job-status-header { display: flex; flex-direction: column; gap: 0.5rem; }
.job-status-eyebrow { font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.14em; }
.job-status-title { margin: 0; font-family: var(--font-mono); font-size: 1.125rem; color: var(--text-1); letter-spacing: 0.03em; word-break: break-all; }
.job-status-meta { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
.job-status-pill-slot { display: inline-flex; }
.job-status-counter {
  font-family: var(--font-mono); font-size: 0.8125rem; color: var(--text-2);
  letter-spacing: 0.04em; font-variant-numeric: tabular-nums;
}

.job-stage-list { display: flex; flex-direction: column; gap: 0.4375rem; }
.stage-row {
  display: grid;
  grid-template-columns: 0.75rem 1fr;
  gap: 0.625rem;
  align-items: start;
  padding: 0.5625rem 0.75rem;
  background: var(--ground-3);
  border: 1px solid var(--hair-2);
  border-left: 3px solid var(--text-4);
  border-radius: 2px;
  transition: border-left-color 0.18s ease, background 0.18s ease;
}
:root[data-theme="light"] .stage-row { background: rgba(255,255,255,0.5); border-color: var(--hair); }
.stage-row.stage-pending { border-left-color: var(--text-4); opacity: 0.78; }
.stage-row.stage-running { border-left-color: var(--brass); background: linear-gradient(90deg, rgba(196,154,86,0.07), transparent 60%); }
.stage-row.stage-ok      { border-left-color: var(--cabinet); }
.stage-row.stage-fail    { border-left-color: var(--oxblood); }
/* Phase B (3.X.0): stages that didn't run on this dispatch. The
   handler chose not to execute them (vps flag off, payload field
   absent, etc.) — distinct from pending (waiting to run) and fail
   (ran and threw). Pip + bar muted, name struck through. */
.stage-row.stage-skipped { border-left-color: var(--text-4); opacity: 0.55; }
.stage-row.stage-skipped .stage-pip { background: var(--text-4); box-shadow: none; }
.stage-row.stage-skipped .stage-name { color: var(--text-3); text-decoration: line-through; text-decoration-thickness: 1px; text-decoration-color: var(--text-4); }
.stage-row.stage-skipped .stage-bar-fill { width: 4%; background: var(--text-4); opacity: 0.3; }

.stage-pip {
  width: 0.4375rem; height: 0.4375rem; border-radius: 50%;
  margin-top: 0.375rem; background: var(--text-4);
  box-shadow: 0 0 0 transparent;
  transition: background 0.18s ease, box-shadow 0.18s ease;
}
.stage-row.stage-running .stage-pip { background: var(--brass-hi); box-shadow: 0 0 6px rgba(196,154,86,0.45); animation: sky-blink 1.4s steps(1, end) infinite; }
.stage-row.stage-ok .stage-pip      { background: var(--cabinet); box-shadow: 0 0 5px rgba(74,128,96,0.4); }
.stage-row.stage-fail .stage-pip    { background: var(--oxblood); box-shadow: 0 0 5px rgba(165,42,61,0.45); }

.stage-body { display: flex; flex-direction: column; gap: 0.375rem; min-width: 0; }
.stage-line {
  display: flex; align-items: baseline; gap: 0.625rem; flex-wrap: wrap;
  font-family: var(--font-mono); font-size: 0.8125rem; color: var(--text-1);
  letter-spacing: 0.02em;
}
.stage-name { font-weight: 500; }
.stage-row.stage-pending .stage-name { color: var(--text-3); }
.stage-substep {
  flex: 1 1 auto; min-width: 0;
  font-family: var(--font-serif); font-style: italic; font-size: 0.89375rem;
  color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.stage-row.stage-running .stage-substep::before {
  content: "· ";
  color: var(--brass-hi);
}
.stage-duration {
  font-family: var(--font-mono); font-size: 0.6875rem; color: var(--text-3);
  letter-spacing: 0.04em; font-variant-numeric: tabular-nums; flex-shrink: 0;
}

.stage-bar {
  position: relative; height: 4px; border-radius: 1px; overflow: hidden;
  background: rgba(0,0,0,0.12);
}
:root[data-theme="light"] .stage-bar { background: rgba(0,0,0,0.08); }
.stage-bar-fill {
  position: absolute; left: 0; top: 0; bottom: 0;
  width: 0%; background: var(--text-4);
  transition: width 0.25s ease, background 0.18s ease;
}
.stage-row.stage-ok      .stage-bar-fill { width: 100%; background: var(--cabinet); }
.stage-row.stage-fail    .stage-bar-fill { width: 100%; background: var(--oxblood); }
.stage-row.stage-pending .stage-bar-fill { width: 4%; background: var(--text-4); opacity: 0.5; }
.stage-row.stage-running .stage-bar-fill {
  width: 100%;
  background: linear-gradient(90deg, transparent 0%, var(--brass-hi) 50%, transparent 100%);
  background-size: 200% 100%;
  animation: stage-bar-sweep 1.6s linear infinite;
}
@keyframes stage-bar-sweep {
  from { background-position:  100% 0; }
  to   { background-position: -100% 0; }
}

.stage-details {
  margin: 0.25rem 0 0;
  padding: 0.5rem 0.625rem;
  background: rgba(165,42,61,0.06);
  border: 1px solid rgba(165,42,61,0.18);
  border-radius: 2px;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--text-2);
  white-space: pre-wrap;
  word-break: break-word;
}

.job-error {
  padding: 0.625rem 0.75rem;
  background: rgba(165,42,61,0.08);
  border: 1px solid rgba(165,42,61,0.28);
  border-left: 3px solid var(--oxblood);
  border-radius: 2px;
  font-family: var(--font-serif); font-size: 0.875rem; color: var(--text-1);
}
.job-error pre {
  margin: 0.375rem 0 0;
  font-family: var(--font-mono); font-size: 0.75rem; color: var(--text-2);
  white-space: pre-wrap; word-break: break-word;
}
.job-back { margin: 0; font-family: var(--font-mono); font-size: 0.8125rem; color: var(--brass-hi); letter-spacing: 0.04em; }
.job-back a { font-family: var(--font-mono); font-size: 0.8125rem; color: var(--brass-hi); letter-spacing: 0.04em; }

/* ─── Terminal job page · actions row (3.X.0) ─────────────
   Container for the back + forward affordances at the bottom
   of the job-status panel. On success the operator's primary
   CTA is forward (the brass `View <Entity>` button); back is
   the muted text link. Pre-3.X.0 the page only carried a
   back link, which assumed the operator's next move was
   always "return to where I was". Once a job creates or
   edits an object, the operator usually wants to navigate
   to that object's page next. The forward slot only renders
   when the `forwardLinkForJob` helper returns a destination
   for the kind + payload + output. */
.job-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 1rem;
  margin: 0.5rem 0 0;
  flex-wrap: wrap;
}
.job-forward-slot { display: inline-flex; }
.job-forward-slot:empty { display: none; }
.job-forward {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-family: var(--font-mono);
  font-size: 0.875rem;
  letter-spacing: 0.03em;
}

.standing-pill.standing-brass { color: var(--brass-hi); border-color: rgba(196,154,86,0.4); background: rgba(196,154,86,0.08); }
.standing-pill.standing-royal { color: var(--royal); border-color: rgba(86,129,170,0.4); background: rgba(86,129,170,0.08); }
.standing-pill.standing-brass .dot { background: var(--brass-hi); box-shadow: 0 0 5px rgba(196,154,86,0.45); }
.standing-pill.standing-royal .dot { background: var(--royal); box-shadow: 0 0 5px rgba(86,129,170,0.4); }

/* ─── Establish Enigma form (2.225.1) ──────────────────────
   Mobile-friendly variant of the establish-HQ ceremony panel.
   Pre-2.225.1 the renderer used inline styles with hard min-widths
   (form: min-width 360px) and a fixed 10rem cap-badge — both blew
   out the 360px-wide mobile viewport. Class-ified so the panel
   collapses gracefully on narrow screens. */
.establish-enigma-panel {
  text-align: center;
  padding: 2rem 1.25rem;
}
.establish-enigma-badge {
  display: flex;
  justify-content: center;
  margin-bottom: 1rem;
}
/* Cap-badge override — the SVG emits width/height in rem (size/16).
   On mobile, override to fit the viewport: 30vw with a 10rem
   ceiling so a 1920px desktop still gets the full 10rem mark. */
.establish-enigma-badge svg {
  width: min(10rem, 30vw);
  height: min(10rem, 30vw);
}
.establish-enigma-title {
  font-family: var(--font-display, 'Cinzel', serif);
  letter-spacing: 0.1em;
  margin: 0 0 0.375rem 0;
}
.establish-enigma-prose {
  font-family: var(--font-serif, 'EB Garamond', serif);
  font-style: italic;
  font-size: 1.1em;
  color: var(--text-2, #c9c2b1);
  margin: 0 auto 1.5rem auto;
  max-width: 56ch;
  line-height: 1.55;
}
.establish-enigma-error {
  padding: 0.625rem 0.875rem;
  margin: 0 auto 1rem auto;
  max-width: 30rem;
  background: rgba(165, 42, 61, 0.15);
  border-left: 3px solid var(--oxblood-hi, #a52a3d);
  color: var(--text-1, #e8dcc4);
  font-size: 0.875rem;
  text-align: left;
}
.establish-enigma-form {
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  margin: 0 auto;
  width: 100%;
  max-width: 30rem;
  text-align: left;
}
.establish-enigma-field {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}
.establish-enigma-field select {
  padding: 0.625rem 0.75rem;
  font-size: 0.9375rem;
  width: 100%;
}
.establish-enigma-help {
  font-size: 0.825rem;
  color: var(--text-3, #8b8b8b);
  font-style: italic;
}
.establish-enigma-actions {
  display: flex;
  gap: 0.625rem;
  justify-content: flex-end;
  margin-top: 0.5rem;
  flex-wrap: wrap;
}
/* Narrow viewports: stack actions full-width (Cancel above Submit
   reads naturally — the destructive-feeling primary is below) so
   the submit copy isn't truncated against the panel edge. */
@media (max-width: 480px) {
  .establish-enigma-panel { padding: 1.5rem 0.875rem; }
  .establish-enigma-actions { flex-direction: column-reverse; align-items: stretch; }
  .establish-enigma-actions .button { width: 100%; text-align: center; }
}

/* ─── Company Enigma panel (post-activation strip on Company detail) ─
   Renders between the Company dossier cover and the Missions list:
   wax-seal cap-badge + ENIGMA nameplate + "Visit Enigma →" /
   "Begin setup →" CTA cluster. Class-ified for mobile collapse —
   the pre-class inline-styled flex row had no `flex-wrap`, so on a
   360px viewport the unshrinkable cap-badge + 2 button cluster
   pushed the panel past the right edge of the screen. Same pattern
   the establish-enigma-panel (above) settled on. */

.company-hq-panel {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1.375rem;
  padding: 1.125rem 1.375rem;
}
.company-hq-badge {
  flex: 0 0 auto;
}
.company-hq-meta {
  flex: 1 1 14rem;
  min-width: 0;
}
.company-hq-meta-eyebrow {
  font-family: var(--font-display, 'Cinzel', serif);
  font-weight: 600;
  letter-spacing: 0.32em;
  font-size: 0.8125rem;
  color: var(--brass-hi, #e3c887);
  text-transform: uppercase;
}
/* 3.X.0 — HQ panel's title slot. The HQ widget now reads as a
   sibling of the Mission detail page's dossier-cover: the
   panel title IS the HQ Mission's own name (linked through to
   the Mission detail), not a separate "Enigma" eyebrow. */
.company-hq-meta-title {
  font-family: var(--font-display, 'Cinzel', serif);
  font-weight: 600;
  font-size: 1.4rem;
  line-height: 1.1;
  margin: 0;
  color: var(--text-1, #e8dcc4);
  letter-spacing: 0.02em;
}
.company-hq-meta-title a.entity-link {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px dotted transparent;
  transition: color 0.15s, border-color 0.15s;
}
.company-hq-meta-title a.entity-link:hover {
  color: var(--brass-hi, #e3c887);
  border-bottom-color: var(--brass-rim);
}
.company-hq-meta-tagline {
  font-family: var(--font-serif, 'EB Garamond', serif);
  font-style: italic;
  font-size: 1.05em;
  color: var(--text-2, #c9c2b1);
  margin-top: 0.25rem;
}
.company-hq-actions {
  /* 3.X.0 — always take a full row beneath the badge + meta so
     the action buttons never compete with the meta block for
     horizontal space. Pre-this-ship the cluster was `flex: 0 0
     auto` (intrinsic width), which let the cluster overflow the
     parent column on a narrow page-designer layout. Forcing
     `1 1 100%` + `min-width: 0` keeps the buttons within the
     panel; `flex-wrap: wrap` lets individual buttons reflow when
     the combined natural width exceeds one row. */
  flex: 1 1 100%;
  min-width: 0;
  display: flex;
  gap: 0.625rem;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-start;
}

/* ─── Q-Branch Arsenal (3.12.0; hero re-skinned 3.28.2) ─────
   Per-Company + per-Mission roll-up of every registered toolset.
   The hero panel uses the shared `dossier-cover` chrome (cap-
   badge + brass-linked Object name + gauges + cover-ops cluster)
   so it reads as a sibling of the Mission / Squad / Company
   detail pages. Cards carry one toolset each with usage stats +
   placeholder telemetry + stub action buttons (Configure / Audit /
   Issue). Theming inherits the global walnut-brass-dark /
   Cabinet-Office-light tokens — no hard-coded hex. */

/* P1 (3.12.0) grid + per-card chrome retired in 3.X (arsenal-p2)
   alongside the renderer body. The cluster + accordion-tile
   chrome that replaced it lives further down (search
   `.arsenal-cluster`). The `.arsenal-empty` rule survives for the
   "No toolsets registered" placeholder. */
.arsenal-empty { padding: 2rem 1.5rem; text-align: center; }

.arsenal-audit { padding: 1.25rem 1.5rem; }
.arsenal-audit-header { margin-bottom: 0.875rem; }
.arsenal-audit-disclaimer {
  font: 400 0.89375rem/1.45 var(--font-serif, 'EB Garamond', serif);
  font-style: italic;
  color: var(--text-3);
  margin: 0.25rem 0 0;
}
.arsenal-audit-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.arsenal-audit-row {
  display: grid;
  grid-template-columns: auto auto 1fr;
  gap: 0.75rem;
  align-items: baseline;
  padding: 0.5rem 0;
  border-top: 1px solid var(--hair);
  font: 400 0.875rem/1.45 var(--font-serif, 'EB Garamond', serif);
  color: var(--text-2);
}
.arsenal-audit-row:first-child { border-top: none; padding-top: 0; }
.arsenal-audit-ts {
  font: 500 0.75rem/1 var(--font-mono, 'JetBrains Mono', monospace);
  color: var(--text-3);
  letter-spacing: 0.04em;
}
.arsenal-audit-verb {
  font: 600 0.6875rem/1 var(--font-display, 'Cinzel', serif);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  padding: 0.1875rem 0.4375rem;
  border-radius: var(--r-1);
  border: 1px solid var(--brass-rim);
  color: var(--brass-hi);
  background: transparent;
}
.arsenal-audit-verb-recall {
  border-color: var(--hair);
  color: var(--text-3);
}
.arsenal-audit-verb-scope {
  border-color: var(--oxblood-hi, var(--brass-rim));
  color: var(--oxblood-hi, var(--brass-hi));
}
.arsenal-audit-body code {
  font: 500 0.8125rem/1.2 var(--font-mono, 'JetBrains Mono', monospace);
  background: var(--ground-3);
  color: var(--text-1);
  padding: 0.0625rem 0.3125rem;
  border-radius: 3px;
}

@media (max-width: 640px) {
  .arsenal-audit-row { grid-template-columns: 1fr; gap: 0.25rem; }
}

/* Hero ops cluster — the bottom-right corner of the dossier cover
   that carries the operations buttons ("Brief", "Commission Joe").
   Sits below the existing dossier-cover-actions icon row. */
.dossier-cover-ops {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: flex-end;
  margin-top: 0.625rem;
}
.cover-btn {
  font: 500 0.6875rem/1 var(--font-display);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  padding: 0.625rem 0.9375rem;
  border-radius: var(--r-1);
  border: 1px solid var(--brass-rim);
  cursor: pointer;
  background: transparent;
  color: var(--text-2);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  transition: background .15s, border-color .15s, color .15s;
}
.cover-btn:hover {
  border-color: var(--brass);
  color: var(--display);
  background: var(--ground-2);
}
.cover-btn.primary {
  background: var(--brass);
  color: var(--ground-1);
  border-color: var(--brass-hi);
}
.cover-btn.primary:hover {
  background: var(--brass-hi);
  color: var(--ground-1);
}
:root[data-theme="light"] .cover-btn:not(.primary) { background: var(--ground-3); }
:root[data-theme="light"] .cover-btn:not(.primary):hover { background: var(--ground-2); }
:root[data-theme="light"] .cover-btn.primary { color: var(--ground-1); }

/* Dossier sheet — the vellum "paper file" card in the left column.
   Cream/ivory background, dashed brass internal divider, mono-caps
   file label, kv rows, italic standing-orders block. The contour-map
   underlay rides at the same opacity as the dossier cover for
   atmospheric continuity. */
.dossier-sheet {
  position: relative;
  background:
    radial-gradient(ellipse at top left, #f4ead2 0%, #ebe0c2 70%, #d6c498 100%);
  color: #2a210f;
  border: 1px solid #8c6f3a;
  border-radius: var(--r-2);
  padding: 1rem 1.125rem 1.125rem;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.55),
    inset 0 -1px 0 rgba(0,0,0,0.12),
    0 1px 2px rgba(0,0,0,0.35);
  overflow: hidden;
}
:root[data-theme="light"] .dossier-sheet {
  background:
    radial-gradient(ellipse at top left, #fbf6e6 0%, #f1e8cd 70%, #ddc99e 100%);
}
.dossier-sheet > * { position: relative; z-index: 1; }
.dossier-sheet-stamp {
  position: absolute;
  top: 0.625rem;
  right: 0.875rem;
  transform: rotate(6deg);
  transform-origin: top right;
  z-index: 2;
}
.dossier-sheet-eyebrow {
  font: 400 0.59375rem/1 var(--font-mono);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #5a4a2a;
}
.dossier-sheet-title {
  font: 600 1.0625rem/1.1 var(--font-display);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #2a210f;
  margin: 0.5rem 0 0;
}
.dossier-rows {
  margin-top: 0.875rem;
  display: grid;
  gap: 0.5rem;
}
.dossier-row {
  display: grid;
  grid-template-columns: minmax(0, 7rem) minmax(0, 1fr);
  gap: 0.5rem;
  align-items: baseline;
}
.dossier-row-label {
  font: 400 0.625rem/1 var(--font-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #7a6a4a;
}
.dossier-row-value {
  font: 500 0.8125rem/1.3 var(--font-body);
  color: #2a210f;
  text-align: right;
  word-break: break-word;
}
.dossier-row-value.mono {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
}
.dossier-row-value .joe-tag {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #7a6a4a;
  margin-left: 0.5rem;
}
/* 3.483 (squad-dossier-notes) — operator scratch block on the
   dossier sheet. Both branches (editable textarea / read-only
   <pre>) share the same mono token + size as `.dossier-row-
   value.mono` so the visual texture of the Field-notes block
   matches what the retired codebase rows used. The textarea
   drops the input field's default chrome (border, focus ring,
   scroll padding) so the surface reads as part of the vellum
   sheet, not a form input. The empty-state <p> is the read-
   only-no-content placeholder. */
.dossier-field-notes-block {
  margin-top: 0.875rem;
}
.dossier-field-notes {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin: 0;
  padding: 0.375rem 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  line-height: 1.45;
  color: #2a210f;
  background: rgba(254,248,232,0.55);
  border: 1px dashed rgba(60,40,15,0.22);
  border-radius: 2px;
  resize: vertical;
  min-height: 5rem;
  white-space: pre-wrap;
  word-break: break-word;
}
.dossier-field-notes:focus {
  outline: none;
  border-color: rgba(60,40,15,0.55);
  background: rgba(254,248,232,0.85);
}
pre.dossier-field-notes {
  overflow-x: auto;
}
.dossier-field-notes-empty {
  font: 400 0.75rem/1.45 var(--font-serif);
  font-style: italic;
  color: #7a6a4a;
  margin: 0;
}
.dossier-field-notes-status {
  margin-top: 0.25rem;
  font: 500 0.59375rem/1 var(--font-mono);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #7a6a4a;
  min-height: 0.75rem;
}
.dossier-field-notes-status[data-status-kind="saving"] {
  color: #7a6a4a;
}
.dossier-field-notes-status[data-status-kind="saved"] {
  color: #4a6a3a;
}
.dossier-field-notes-status[data-status-kind="error"] {
  color: #8a3a2a;
}
:root[data-theme="light"] .dossier-field-notes {
  background: rgba(254,248,232,0.75);
}
.dossier-divider {
  border: 0;
  border-top: 1px dashed rgba(60,40,15,0.28);
  margin: 0.875rem 0 0;
}
.dossier-standing-orders {
  margin-top: 0.875rem;
}
.dossier-standing-orders p {
  font: 400 0.928125rem/1.45 var(--font-serif);
  font-style: italic;
  color: #2a210f;
  margin: 0.5rem 0 0;
}
.dossier-standing-orders p.empty {
  color: #7a6a4a;
}
.dossier-sheet .pill {
  background: rgba(60,40,15,0.10);
  border-color: rgba(60,40,15,0.20);
  color: #2a210f;
}

/* Spend block in the left column — sits under the dossier sheet
   as a panel-shaped row. Shares the .panel chrome but with the
   Today numbers laid out as a 2x2 mini-grid for at-a-glance read. */
.spend-block-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem 1rem;
  margin-top: 0.625rem;
}
.spend-block-cell {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
}
.spend-block-cell .gauge-label {
  font: 500 0.59375rem/1 var(--font-mono);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text-3);
}
.spend-block-cell .gauge-value {
  font: 500 1.125rem/1 var(--font-display);
  letter-spacing: 0.04em;
  color: var(--display);
  font-variant-numeric: tabular-nums;
}
.spend-block-cell .gauge-sub {
  font: 400 0.75625rem/1.2 var(--font-serif);
  font-style: italic;
  color: var(--text-4);
}

/* Chatter panel (3.X) — the right column's activity rollup across
   every chatTranscript-flagged toolset (walkie-talkie + operator-
   chat today). Each .chatter-row is a clickable <a> wrapping a
   glyph + body. The body's head row carries the thread label, the
   toolset name, the carrying-Joe callsign for operator-chat rows,
   and a status pill for degraded fetches; the body's snippet row
   carries the latest-message preview (operator-chat only); the
   body's meta row carries message count + relative timestamp.
   Replaces the pre-3.X Comms panel (.freq-row / .comms-list)
   whose channel-inventory shape filtered on replyChannel-flagged
   RPCs. */
.chatter-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.chatter-row {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  padding: 0.5rem 0.625rem;
  background: var(--ground-2);
  border: 1px solid var(--hair);
  border-radius: var(--r-1);
  color: inherit;
  text-decoration: none;
  transition: background-color 80ms ease, border-color 80ms ease;
}
:root[data-theme="light"] .chatter-row { background: var(--ground-3); }
.chatter-row:hover {
  background: var(--ground-1);
  border-color: var(--brass-lo);
}
:root[data-theme="light"] .chatter-row:hover { background: var(--ground-2); }
.chatter-row:focus-visible {
  outline: 2px solid var(--brass-hi);
  outline-offset: 2px;
}
.chatter-row:active {
  background: var(--ground-3);
}
:root[data-theme="light"] .chatter-row:active { background: var(--ground-1); }
.chatter-glyph {
  font-size: 0.9375rem;
  line-height: 1.2;
  flex-shrink: 0;
  /* The glyph sits slightly heavier than the label baseline so the
     visual weight reads with the meta row's metrics. */
  padding-top: 0.0625rem;
  opacity: 0.85;
}
.chatter-body {
  display: flex;
  flex-direction: column;
  gap: 0.1875rem;
  min-width: 0;
  flex: 1;
}
.chatter-row-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4375rem;
}
.chatter-label {
  font: 500 0.78125rem/1.1 var(--font-mono);
  letter-spacing: 0.02em;
  color: var(--display);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 0 1 auto;
}
.chatter-toolset {
  font: 400 0.59375rem/1 var(--font-mono);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-3);
  flex-shrink: 0;
}
.chatter-joe {
  font: 400 0.6875rem/1 var(--font-serif);
  font-style: italic;
  color: var(--text-3);
  flex-shrink: 0;
}
.chatter-snippet {
  font: 400 0.78125rem/1.35 var(--font-serif);
  color: var(--text-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chatter-meta {
  font: 400 0.65rem/1 var(--font-mono);
  letter-spacing: 0.04em;
  color: var(--text-4);
}
.chatter-status-pill {
  display: inline-block;
  padding: 0.0625rem 0.4375rem;
  border: 1px solid var(--hair);
  border-radius: var(--r-pill, 9999px);
  font: 400 0.5625rem/1 var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-3);
  background: var(--ground-3);
}
:root[data-theme="light"] .chatter-status-pill { background: var(--ground-2); }
.chatter-status-restricted,
.chatter-status-error,
.chatter-status-fetch-error {
  color: var(--oxblood-hi, #c44);
  border-color: var(--oxblood-lo, #a33);
}
/* 3.514 — unread badge on chatter rows. Brass-tinted pill with
   the unread count; renders next to the chat label when > 0.
   Sized to read as a count, not a notification dot — operators
   want to see "1 unread" vs "12 unread" before clicking. */
.chatter-unread-badge {
  display: inline-block;
  min-width: 1.25rem;
  padding: 0.0625rem 0.4375rem;
  border-radius: var(--r-pill, 9999px);
  font: 700 0.6875rem/1.1 var(--font-mono);
  letter-spacing: 0.04em;
  text-align: center;
  color: var(--text-1);
  background: var(--brass-2, #3a2f1a);
  border: 1px solid var(--brass-hi, #e3c887);
  flex-shrink: 0;
}
.chatter-panel .empty {
  font: 400 0.89375rem/1.4 var(--font-serif);
  font-style: italic;
  color: var(--text-3);
}

/* Arsenal cabinet — the right column's CTA panel routing operators
   to the new four-altitude Arsenal page at `${squadBase}/arsenal`.
   3.X (arsenal-p1) replaced the pre-3.X kit-module-tiles grid with
   a one-line stat + "Open Arsenal →" button-link; the
   `.kit-module*` / `.arsenal-cabinet-grid` / `.kit-module-sub.needs-config`
   classes retired alongside the renderer body in the same ship.
   Only the empty-state rule survives. */
.arsenal-cabinet .empty {
  font: 400 0.89375rem/1.4 var(--font-serif);
  font-style: italic;
  color: var(--text-3);
}

/* Arsenal page (P2) — cluster sections + per-toolset accordion
   tiles. 3.X (arsenal-p2) replaced the flat grid + per-card stub
   chrome with a cluster-grouped layout: each of the six canonical
   `ToolsetCategory` values gets its own brass-eyebrow section, and
   per-toolset tiles use a native `<details>` accordion (cap-badge +
   name + equipped-gauge collapsed; description + reverse-view Joe
   roster expanded). At Joe altitude tiles dim when unequipped so
   the "what does this Joe carry" reading is glanceable. */
.arsenal-cluster {
  margin: 1rem 0;
}
.arsenal-cluster + .arsenal-cluster { margin-top: 1.25rem; }
.arsenal-cluster-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin: 0 0 0.5rem;
  padding: 0 0.125rem;
}
.arsenal-cluster-count {
  font: 400 0.6875rem/1 var(--font-mono);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-4);
}
.arsenal-cluster-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
  gap: 0.625rem;
}

/* Tile (one per toolset). Native <details>; the open/close toggle
   is keyboard-accessible + screen-reader-announced for free. */
.arsenal-tile {
  background: var(--ground-2);
  border: 1px solid var(--hair);
  border-radius: var(--r-1);
  overflow: hidden;
  transition: border-color .15s;
}
.arsenal-tile:hover { border-color: var(--brass-rim); }
.arsenal-tile[open] { border-color: var(--brass-rim); }
:root[data-theme="light"] .arsenal-tile { background: var(--ground-3); }
.arsenal-tile-unequipped {
  opacity: 0.55;
}
.arsenal-tile-unequipped:hover,
.arsenal-tile-unequipped[open] {
  opacity: 1;
}

.arsenal-tile-summary {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.625rem 0.75rem;
  cursor: pointer;
  list-style: none;
}
.arsenal-tile-summary::-webkit-details-marker { display: none; }
.arsenal-tile-summary::marker { content: ""; }

.arsenal-cap-badge {
  width: 2rem;
  height: 2rem;
  border-radius: var(--r-1);
  background: linear-gradient(180deg, #2a2114 0%, #14100a 100%);
  box-shadow: inset 0 0 0 1px var(--brass-rim);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--brass-hi);
  font: 600 0.6875rem/1 var(--font-mono);
  letter-spacing: 0;
  text-transform: uppercase;
  flex-shrink: 0;
}
.arsenal-tile-equipped .arsenal-cap-badge {
  box-shadow: inset 0 0 0 1px var(--brass-hi);
}
.arsenal-tile-identity {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.0625rem;
}
.arsenal-tile-name {
  font: 600 0.9375rem/1.2 var(--font-display, 'Cinzel', serif);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--display);
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
/* 3.X.0 (star-after-name) — inline row containing the toolset name +
   the star-default toggle, sitting at the top of the identity column.
   The name flexes to fill (`flex: 1`) so the star pins to the right
   edge of the row; ellipsis on the h3 absorbs long names without
   shoving the star off-screen. */
.arsenal-tile-name-row {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  min-width: 0;
}
.arsenal-tile-name-row .arsenal-tile-name {
  flex: 1;
}
.arsenal-tile-id {
  font: 400 0.625rem/1 var(--font-mono);
  letter-spacing: 0.08em;
  color: var(--text-4);
}
.arsenal-tile-id code {
  font: inherit;
  color: inherit;
  background: none;
  padding: 0;
}
.arsenal-tile-gauge {
  font: 600 0.6875rem/1 var(--font-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  flex-shrink: 0;
  padding: 0.25rem 0.4375rem;
  border-radius: var(--r-1);
  border: 1px solid var(--hair);
}
.arsenal-tile-gauge.brass {
  color: var(--brass-hi);
  border-color: var(--brass-rim);
}
.arsenal-tile-gauge.muted {
  color: var(--text-4);
}
.arsenal-tile-chevron {
  font: 400 1.125rem/1 var(--font-display, 'Cinzel', serif);
  color: var(--brass-rim);
  flex-shrink: 0;
  transition: transform .15s;
}
.arsenal-tile[open] .arsenal-tile-chevron { transform: rotate(90deg); }

/* 3.X.0 (default-arsenal S3 → star-after-name) — star toggle on
   every Company / Mission / Squad arsenal tile (suppressed at Joe
   altitude + on multi-instance toolsets — see
   `renderArsenalTileDefaultStar`). Renders INLINE inside the
   `<summary>`, in `.arsenal-tile-name-row` right after the toolset
   name, so the operator can see and click the star whether the
   tile is open or closed. The button carries
   `onclick="event.stopPropagation()"` so the click doesn't bubble
   to the parent `<summary>`'s native disclosure toggle. Glyph:
   ★ (solid, brass) when default, ☆ (outline, muted) when ghosted;
   hover-fades the ghost state up so the star reads "interactive"
   even when off. */
.arsenal-star-form {
  margin: 0;
  flex-shrink: 0;
  line-height: 1;
}
.arsenal-star {
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0.125rem 0.25rem;
  font: 400 1.125rem/1 var(--font-display, 'Cinzel', serif);
  line-height: 1;
  min-height: 0;
  height: auto;
  transition: color .15s, transform .1s;
}
.arsenal-star:hover {
  transform: scale(1.15);
}
.arsenal-star.is-default {
  /* Brilliant gold — earlier `var(--brass-rim)` (alpha-32 brass)
     read too muted to convey "this is the active state".
     `--gold-bright` is medal-yellow against the walnut dark
     ground + readable amber against the cream light ground. */
  color: var(--gold-bright);
  text-shadow: 0 0 6px var(--gold-bright);
}
.arsenal-star.is-ghosted {
  color: var(--text-4);
  opacity: 0.4;
}
.arsenal-star.is-ghosted:hover {
  /* Hover preview of the active state — show the operator the
     gold tone they'd flip to before they commit. */
  color: var(--gold-bright);
  opacity: 0.85;
}

.arsenal-tile-body {
  padding: 0.5rem 0.75rem 0.75rem;
  border-top: 1px solid var(--hair);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.arsenal-tile-use-case {
  font: 500 0.8125rem/1.4 var(--font-serif);
  color: var(--text-1);
  margin: 0;
}
.arsenal-tile-description {
  font: 400 0.78125rem/1.5 var(--font-serif);
  font-style: italic;
  color: var(--text-3);
  margin: 0;
}
.arsenal-tile-roster {
  margin-top: 0.25rem;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--hair);
}
.arsenal-tile-roster .empty {
  font: 400 0.78125rem/1.4 var(--font-serif);
  font-style: italic;
  color: var(--text-4);
  margin: 0.25rem 0 0;
}
.arsenal-tile-roster-list,
.arsenal-tile-instances {
  list-style: none;
  margin: 0.25rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}
.arsenal-tile-roster-list li,
.arsenal-tile-instances li {
  font: 400 0.78125rem/1.4 var(--font-mono);
  color: var(--text-2);
}
/* 3.X (Arsenal equip-modal) — per-instance row chrome at Joe
   altitude. Each instance row carries the verbatim loadout entry,
   a Configure button (opens the per-entry edit modal), and a
   Remove form. The Configure button's chrome lives inline in
   admin-arsenal-views' modal-shell style block. */
.arsenal-instance-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.arsenal-instance-row code {
  font: inherit;
  color: var(--brass-hi);
  background: none;
  padding: 0;
}
.arsenal-instance-row-form { margin: 0; }
.arsenal-instance-row-btn {
  font: 500 0.625rem/1 var(--font-display, 'Cinzel', serif);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 0.25rem 0.5rem;
}
/* 3.X (Arsenal P5.1) — aggregate-altitude multi-instance roster
   row. Lists every Joe in scope with a Configure → link to their
   arsenal page. Same row layout as the singleton bulk-equip rows
   (pip + callsign + state + action) but the action is a brass
   link, not a checkbox + submit. */
.arsenal-aggregate-roster-list {
  list-style: none;
  margin: 0.25rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}
.arsenal-aggregate-roster-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3125rem 0.5rem;
  border: 1px solid transparent;
  border-radius: var(--r-1);
  font: 400 0.78125rem/1.2 var(--font-mono);
  color: var(--text-2);
  transition: background-color .12s, border-color .12s;
}
.arsenal-aggregate-roster-row:hover { border-color: var(--hair); }
.arsenal-aggregate-roster-link {
  color: var(--brass-hi);
  text-decoration: none;
  font: 500 0.6875rem/1 var(--font-display, 'Cinzel', serif);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  flex-shrink: 0;
}
.arsenal-aggregate-roster-link:hover { text-decoration: underline; }
.arsenal-tile-roster-link {
  color: var(--brass-hi);
  text-decoration: none;
}
.arsenal-tile-roster-link:hover { text-decoration: underline; }
.arsenal-tile-roster-entries {
  font: 400 0.71875rem/1 var(--font-mono);
  color: var(--text-4);
}
.arsenal-tile-roster-entries code {
  font: inherit;
  color: inherit;
  background: none;
  padding: 0;
}

/* Arsenal P3 — Squad-altitude multi-select bulk equip/unequip form.
   Replaces the read-only reverse-view roster on singleton-toolset
   tiles at Squad altitude. Native label+checkbox pattern: click
   anywhere on the row toggles the (visually hidden) checkbox, which
   in turn flips the row's selected highlight via :has(:checked). */
.arsenal-bulk-equip {
  margin: 0.25rem 0 0;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--hair);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.arsenal-bulk-list {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}
.arsenal-bulk-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3125rem 0.5rem;
  border: 1px solid transparent;
  border-radius: var(--r-1);
  cursor: pointer;
  font: 400 0.78125rem/1.2 var(--font-mono);
  color: var(--text-2);
  transition: background-color .12s, border-color .12s;
}
.arsenal-bulk-row:hover { border-color: var(--hair); }
.arsenal-bulk-row input[type="checkbox"] {
  /* Visually hide the native checkbox but keep it focus-reachable +
     screen-reader-announced. The label is the click target. */
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
.arsenal-bulk-row:has(input[type="checkbox"]:checked) {
  background: rgba(195, 159, 89, 0.16);
  border-color: var(--brass-rim);
}
.arsenal-bulk-row:has(input[type="checkbox"]:focus-visible) {
  outline: 2px solid var(--brass-rim);
  outline-offset: 2px;
}
.arsenal-bulk-pip {
  font: 400 0.625rem/1 var(--font-mono);
  width: 0.875rem;
  text-align: center;
  flex-shrink: 0;
}
.arsenal-bulk-pip.equipped { color: var(--brass-hi); }
.arsenal-bulk-pip.empty { color: var(--text-4); }
.arsenal-bulk-callsign {
  flex: 1;
  min-width: 0;
  color: var(--text-1);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.arsenal-bulk-state {
  font: 400 0.625rem/1 var(--font-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-4);
  flex-shrink: 0;
}
.arsenal-bulk-actions {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-top: 0.25rem;
}
.arsenal-bulk-btn {
  font: 600 0.6875rem/1 var(--font-display, 'Cinzel', serif);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--brass-rim);
  border-radius: var(--r-1);
  background: transparent;
  color: var(--brass-hi);
  cursor: pointer;
  transition: background-color .12s, border-color .12s;
}
.arsenal-bulk-btn:hover {
  background: rgba(195, 159, 89, 0.12);
  border-color: var(--brass-hi);
}
.arsenal-bulk-btn-unequip {
  color: var(--oxblood-hi, var(--brass-hi));
  border-color: var(--oxblood-hi, var(--brass-rim));
}
.arsenal-bulk-btn-unequip:hover {
  background: rgba(165, 42, 61, 0.12);
}
.arsenal-bulk-help {
  margin: 0;
  font: 400 0.71875rem/1.4 var(--font-serif);
  font-style: italic;
  color: var(--text-4);
}

/* P4 — Mission / Company bulk-equip roster groups rows by Squad
   with a sub-header (callsign + per-Squad equipped count). Each
   group sits in its own `<div class="arsenal-bulk-squad-group">`
   with a brass-eyebrow header above the row list. */
.arsenal-bulk-groups {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
}
.arsenal-bulk-squad-group {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.arsenal-bulk-squad-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.1875rem 0.5rem;
  border-bottom: 1px solid var(--hair);
}
.arsenal-bulk-squad-callsign {
  font: 600 0.6875rem/1 var(--font-display, 'Cinzel', serif);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--brass-hi);
}
.arsenal-bulk-squad-count {
  font: 400 0.625rem/1 var(--font-mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-4);
}

/* Multi-instance tile note at Squad altitude — read-only roster +
   a brass-link to the legacy /toolsets/<id> per-toolset config
   page. The bulk-equip flow can't multiplex per-instance config
   across N Joes, so multi-instance toolsets route around the
   form entirely. */
.arsenal-tile-multi-instance-note {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--hair);
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}
.arsenal-tile-multi-instance-note p {
  margin: 0;
  font: 400 0.71875rem/1.4 var(--font-serif);
  color: var(--text-4);
}
.arsenal-tile-multi-instance-note a {
  align-self: flex-start;
}

/* Teleprinter — full-width ribbon at the bottom of the page. Walnut0
   base, brass top hairline, mono ticks separated by brass bullets.
   Horizontally scrollable on narrow viewports so the latest few
   events always read end-to-end. */
.teleprinter {
  margin-top: 1rem;
  padding: 0.625rem 1.5rem;
  background: var(--ground-0);
  border-top: 1px solid var(--brass-rim);
  border-bottom: 1px solid var(--brass-rim);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.03),
    inset 0 -1px 0 rgba(0,0,0,0.4);
  display: flex;
  align-items: center;
  gap: 1rem;
  font: 400 0.6875rem/1.4 var(--font-mono);
  color: var(--text-3);
  /* This ship — `overflow: hidden` (was `overflow-x: auto`).
     Ops-console ticker mental model: newest streams in from the
     right edge, older content slides off left under viewport
     pressure. The poller width-prunes from the left as content
     types in on the right so the ribbon visually anchors to
     its right edge — a scrollbar would let the operator scroll
     to "old" content, breaking the live-tail UX. */
  overflow: hidden;
  white-space: nowrap;
}
:root[data-theme="light"] .teleprinter { background: var(--ground-4); }
.teleprinter-head {
  font: 600 0.6875rem/1 var(--font-display);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--brass-hi);
  flex-shrink: 0;
}
.teleprinter-empty {
  font: 400 0.825rem/1.2 var(--font-serif);
  font-style: italic;
  color: var(--text-4);
}
.tick {
  display: inline-flex;
  align-items: baseline;
  gap: 0.375rem;
  flex-shrink: 0;
}
.tick-ts { color: var(--text-4); }
.tick-who { color: var(--brass-hi); font-weight: 600; }
.tick-what { color: var(--text-1); overflow: hidden; text-overflow: ellipsis; max-width: 32rem; }
.tick-sep { color: var(--brass-rim); margin-left: 0.375rem; }
.tick:last-child .tick-sep { display: none; }
/* This ship — live teleprinter ribbon arrival animation. The
   inline poller appends a new tick with `tick--typing` then walks
   the inner spans character-by-character via setTimeout; the
   typewriter IS the arrival cue (the previous ship's brass-glow
   keyframe is retired — operator feedback was the glow felt
   ornamental on top of the typewriter, which already pulls the
   eye to the right edge). A blinking brass cursor (via ::after)
   anchors to the tick while it's typing; the class drops once
   the last character lands and the cursor disappears. Honours
   `prefers-reduced-motion` via the existing global rule. */
.tick.tick--typing::after {
  content: "▍";
  color: var(--brass-hi);
  margin-left: 0.125rem;
  animation: tick-cursor-blink 700ms steps(2, start) infinite;
}
@keyframes tick-cursor-blink {
  0%, 50%  { opacity: 1; }
  51%, 100% { opacity: 0; }
}

/* ─── Commission form chrome (2.224.0+) ────────────────────────
   Vellum-dossier form framing for the `+ Joe` commissioning
   surface (`renderCreateJoeForm`). The three sibling create
   forms (Mission / Squad / Company) are expected to land on the
   same chrome in a follow-up extraction — keep these rules
   surface-neutral (no `joe` specifics) so they're reusable. */

/* Outer 2-col grid: vellum form left, service-record-preview rail
   right. Drops to a single column at ≤960 px so the rail stacks
   beneath the form on tablet / phone. */
.commission-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 22rem;
  gap: 1.375rem;
  align-items: start;
}
@media (max-width: 960px) {
  .commission-grid { grid-template-columns: minmax(0, 1fr); }
}

/* The vellum sheet that carries the commissioning form proper.
   Slight tilt cancelled (we want a calm form surface, not the
   stamped-paper drama of the 404 page); generous padding so the
   form fields don't crowd the eyebrow / title. */
.commission-vellum {
  position: relative;
  padding: 1.5rem 1.625rem 1.75rem;
}
/* DRAFT stamp pinned top-right. Negative right margin lets the
   stamp's rotation overhang the vellum edge slightly, matching the
   design's intentional just-stamped feel. */
.commission-stamp-slot {
  position: absolute;
  top: 1.125rem;
  right: 1.25rem;
  pointer-events: none;
}
.commission-eyebrow {
  font: 500 0.625rem/1 var(--font-mono);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(90, 74, 42, 0.85);
}
.commission-title {
  font: 500 1.625rem/1.05 var(--font-display);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #2a210f;
  margin: 0.625rem 0 0;
}
.commission-prose {
  font: 400 0.9625rem/1.5 var(--font-serif);
  font-style: italic;
  color: rgba(42, 33, 15, 0.78);
  margin: 0.5rem 0 0;
  max-width: 38rem;
}

/* Inner 2-col grid for the per-section sheets. Each section is its
   own card so the form reads as a sequence of dossier sub-sheets
   rather than a single tall scroll. Collapses to single-column at
   narrow widths. */
.commission-sections-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem 1.125rem;
  margin-top: 1.375rem;
}
@media (max-width: 640px) {
  .commission-sections-grid { grid-template-columns: minmax(0, 1fr); }
}
.commission-section {
  background: rgba(255, 255, 255, 0.45);
  border: 1px solid rgba(0, 0, 0, 0.10);
  border-radius: 2px;
  padding: 0.875rem 1rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  /* Each section's labels and inputs need the form-tokens.css
     palette but on a cream background; force ink-colour text so
     the dark-mode rule that paints labels in --text-2 doesn't
     wash out on the vellum. */
  color: #2a210f;
}
.commission-section-full { grid-column: 1 / -1; }
.commission-section-head {
  font: 500 0.78125rem/1 var(--font-display);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #2a210f;
  border-bottom: 1px solid rgba(0, 0, 0, 0.10);
  padding-bottom: 0.5rem;
  margin: 0 0 0.25rem;
}
.commission-section label {
  display: block;
  font: 500 0.6875rem/1.2 var(--font-mono);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: rgba(42, 33, 15, 0.72);
}
.commission-section label > input[type=text],
.commission-section label > input[type=email],
.commission-section label > input[type=url],
.commission-section label > input[type=number],
.commission-section label > input[type=password],
.commission-section label > select,
.commission-section label > textarea {
  margin-top: 0.375rem;
  background: rgba(255, 255, 255, 0.72);
  border-color: rgba(0, 0, 0, 0.18);
  color: #2a210f;
  font-family: var(--font-body);
  text-transform: none;
  letter-spacing: normal;
  font-size: 0.8125rem;
  font-weight: 400;
}
.commission-section label > textarea {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 1.1em;
  line-height: 1.5;
}
.commission-section label > input:focus,
.commission-section label > select:focus,
.commission-section label > textarea:focus {
  background: rgba(255, 255, 255, 0.95);
  border-color: var(--brass);
}
.commission-section .form-help {
  font: 400 0.825rem/1.4 var(--font-serif);
  font-style: italic;
  color: rgba(42, 33, 15, 0.62);
  margin: 0.375rem 0 0;
  text-transform: none;
  letter-spacing: normal;
}
.commission-section fieldset {
  border: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
}
.commission-section fieldset > legend {
  font: 500 0.6875rem/1.2 var(--font-mono);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: rgba(42, 33, 15, 0.72);
  padding: 0;
}

/* Right rail: vertical stack of preview panels + the action row.
   Reuses the dark-mode .panel chrome for the panels themselves
   (the brass-rim card already reads as "intel surface" in the
   reskin language), so no per-rail panel rule needed. */
.commission-rail {
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  min-width: 0;
}
.commission-preview-identity {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  min-width: 0;
}
.commission-preview-name {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.commission-preview-role {
  font: 400 0.75625rem/1.4 var(--font-serif);
  font-style: italic;
  color: var(--text-3);
}
.commission-preview-meta {
  margin-top: 0.875rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--hair);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.625rem 0.875rem;
}
.commission-preview-field {
  display: flex;
  flex-direction: column;
  gap: 0.1875rem;
  min-width: 0;
}
.commission-preview-field-label {
  font: 500 0.5625rem/1 var(--font-mono);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-4);
}
.commission-preview-field-value {
  font: 500 0.75rem/1.2 var(--font-body);
  color: var(--text-2);
  overflow: hidden;
  text-overflow: ellipsis;
}
.commission-preview-field-value.mono {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.02em;
}

/* Action row: pinned to the bottom of the rail, right-aligned per
   WEBSITE_STYLE_GUIDE §5 (primary on the right, cancel on the
   left). On narrow viewports where the rail stacks below the form,
   this row sits beneath the rail's preview panels in the same
   visual position. */
.commission-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 0.625rem;
  margin-top: auto;
  padding-top: 0.5rem;
}

/* Generate-photograph row (2.228.0). Sits at the bottom of the
   "Service record · preview" panel, beneath the KV meta grid. The
   button reuses the standard brass `button.primary` chrome (no per-
   button override); the row carries a hint paragraph and a hidden
   status slot the JS uses for "generating…" / error messages. The
   disabled state is the browser default (low opacity, not-allowed
   cursor) — visible but inert, with the hint pointing operators at
   `/image-provider`. */
.commission-preview-photoaction {
  margin-top: 1rem;
  padding-top: 0.875rem;
  border-top: 1px solid var(--hair);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  align-items: flex-start;
}
.commission-generate-photo-hint {
  font: 400 0.825rem/1.4 var(--font-serif);
  font-style: italic;
  color: var(--text-3);
  margin: 0;
}
.commission-generate-photo-status {
  font: 500 0.6875rem/1.3 var(--font-mono);
  letter-spacing: 0.04em;
  color: var(--text-2);
  margin: 0;
  padding: 0.375rem 0.625rem;
  background: var(--ground-2);
  border-radius: var(--r-1);
  border: 1px solid var(--hair);
  width: 100%;
  box-sizing: border-box;
}
.commission-generate-photo-status.bad {
  color: var(--oxblood-hi);
  border-color: rgba(165, 42, 61, 0.32);
  background: rgba(165, 42, 61, 0.08);
}
:root[data-theme="light"] .commission-generate-photo-status.bad {
  color: var(--oxblood);
  background: rgba(165, 42, 61, 0.06);
}

/* ─── Help system (3.288) ───────────────────────────────────────
   Per-page contextual help. The `?` button sits in the masthead
   immediately left of the day/night brass-switch; click opens the
   shared viewer-mode dialog (admin-confirm-dialog.ts) with the
   server-rendered markdown content for the page in view. The
   dialog chrome already handles light/dark via :root[data-theme]; we
   add the help-content typography + the inline shortcode primitives
   (callouts, action buttons, kbd, badges, crest, error chip). */

.help-button {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 3.29 — lock the size at every viewport. The generic
     `button { min-height: 2.375rem }` mobile-tier rule (≤720px)
     was inflating the help circle to ~38px on phones while the
     adjacent `.theme-switch` pill stayed at its intended 1.5rem
     (24px) — operator-flagged "stop dicking over the header
     when we go smaller". `min-height: 0` overrides the generic
     mobile rule + `box-sizing: border-box` pins the outer size
     to the explicit 1.5rem regardless of border / padding so
     the help button matches the theme-switch's visual weight at
     every tier.
     3.29 follow-up — same px-floor pattern as the rest of the
     header chrome (max(23.28px, 1.5rem) and max(14.55px,
     0.9375rem)) so the `?` keeps the operator-named "perfect"
     ≤1024-tier rendered size at narrower viewports rather than
     scaling down with the root dial. */
  width: max(23.28px, 1.5rem);
  height: max(23.28px, 1.5rem);
  min-height: 0;
  box-sizing: border-box;
  border-radius: 50%;
  border: 1px solid var(--brass-rim);
  background: linear-gradient(180deg, var(--ground-3) 0%, var(--ground-1) 100%);
  color: var(--brass-hi);
  font: 600 max(14.55px, 0.9375rem)/1 var(--font-display, 'Cinzel', serif);
  letter-spacing: 0;
  padding: 0;
  cursor: pointer;
  flex-shrink: 0;
  transition: color .2s, background .2s, box-shadow .2s, transform .15s;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.06);
}
.help-button:hover {
  color: var(--brass);
  background: linear-gradient(180deg, var(--ground-2) 0%, var(--ground-0) 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.10), 0 0 0 2px rgba(201,165,87,0.18);
}
.help-button:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 2px;
}
:root[data-theme="light"] .help-button {
  background: linear-gradient(180deg, #e0d6b8 0%, #c8baa0 100%);
  color: var(--brass);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.45);
}
:root[data-theme="light"] .help-button:hover {
  background: linear-gradient(180deg, #d8ccab 0%, #bdad91 100%);
  color: var(--brass-hi);
}

/* Help content typography inside the viewer dialog. The dialog's
   `[data-mode="view"]` selectors already loosen body styling for
   richer composition; .help-content overlays per-help rules so the
   markdown emits read as documentation rather than confirm-body
   italic prose. */
.help-content {
  font: 400 0.9375rem/1.55 var(--font-serif, 'EB Garamond', serif);
  color: var(--text-2);
}
.help-content h1, .help-content h2, .help-content h3, .help-content h4 {
  font-family: var(--font-display, 'Cinzel', serif);
  letter-spacing: 0.04em;
  color: var(--display, var(--text-1));
  margin: 1.25rem 0 0.5rem;
}
.help-content h1 { font-size: 1.125rem; margin-top: 0; }
.help-content h2 { font-size: 1rem; }
.help-content h3 {
  font-size: 0.8125rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--brass-hi);
}
.help-content h4 { font-size: 0.875rem; }
.help-content p {
  font-style: italic;
  margin: 0 0 0.75rem;
}
.help-content ul, .help-content ol {
  margin: 0 0 0.875rem 0;
  padding-left: 1.25rem;
}
.help-content li { margin: 0.25rem 0; }
.help-content code {
  font: 500 0.8125rem/1.3 var(--font-mono, monospace);
  background: var(--ground-3);
  padding: 0.125rem 0.375rem;
  border-radius: var(--r-1, 4px);
  color: var(--text-1);
}
.help-content pre {
  background: var(--ground-3);
  border: 1px solid var(--hair);
  border-radius: var(--r-1, 4px);
  padding: 0.625rem 0.75rem;
  overflow-x: auto;
  margin: 0 0 0.875rem;
}
.help-content pre code {
  background: transparent;
  padding: 0;
  font-size: 0.8125rem;
}
.help-content a {
  color: var(--brass-hi);
  text-decoration: underline;
  text-decoration-color: var(--brass-rim);
}
.help-content a:hover { text-decoration-color: var(--brass); }
.help-content hr {
  border: 0;
  border-top: 1px solid var(--hair);
  margin: 1.25rem 0;
}
.help-content strong { color: var(--text-1); }

/* Inline cap-badge — small, sits on the baseline */
.help-content .help-badge {
  display: inline-block;
  width: 1.25rem;
  height: 1.25rem;
  vertical-align: -0.25rem;
  margin: 0 0.125rem;
}
.help-content .help-crest {
  display: inline-block;
  width: 1.5rem;
  height: 1.5rem;
  vertical-align: -0.3rem;
  margin: 0 0.125rem;
}

/* Inline kbd */
.help-kbd {
  display: inline-block;
  font: 600 0.75rem/1 var(--font-mono, monospace);
  padding: 0.1875rem 0.375rem;
  border: 1px solid var(--brass-rim);
  border-bottom-width: 2px;
  border-radius: var(--r-1, 4px);
  background: var(--ground-3);
  color: var(--text-1);
  vertical-align: 0.0625rem;
}

/* Inline button-preview chip ({{action}} / {{nav}}) */
.btn--inline-preview {
  display: inline-block;
  font: 500 0.6875rem/1 var(--font-display, 'Cinzel', serif);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 0.3125rem 0.625rem;
  border-radius: var(--r-1, 4px);
  background: var(--brass);
  color: var(--ground-1);
  border: 1px solid var(--brass-hi);
  vertical-align: 0.0625rem;
  text-decoration: none;
}
.btn--inline-preview.is-link:hover {
  background: var(--brass-hi);
  text-decoration: none;
}

/* Callouts — tip (brass), warn (oxblood), note (royal) */
.help-callout {
  margin: 0.875rem 0;
  padding: 0.75rem 0.875rem 0.75rem 1rem;
  border-left: 3px solid var(--brass);
  border-radius: var(--r-1, 4px);
  background: rgba(201,165,87,0.06);
}
.help-callout p { font-style: normal; margin: 0 0 0.5rem; }
.help-callout p:last-child { margin-bottom: 0; }
.help-callout.tip {
  border-left-color: var(--brass);
  background: rgba(201,165,87,0.07);
}
.help-callout.warn {
  border-left-color: var(--oxblood-hi);
  background: rgba(165,42,61,0.08);
}
.help-callout.note {
  border-left-color: var(--royal, #5b6e8f);
  background: rgba(91,110,143,0.08);
}
:root[data-theme="light"] .help-callout.tip { background: rgba(139,110,31,0.10); }
:root[data-theme="light"] .help-callout.warn { background: rgba(165,42,61,0.10); }
:root[data-theme="light"] .help-callout.note { background: rgba(91,110,143,0.10); }

/* Visible-error chip for unknown shortcodes — never silent */
.help-error {
  display: inline-block;
  font: 500 0.8125rem/1.3 var(--font-mono, monospace);
  background: rgba(165,42,61,0.12);
  color: var(--oxblood-hi);
  padding: 0.0625rem 0.375rem;
  border-radius: var(--r-1, 4px);
  border: 1px dashed var(--oxblood);
}

/* ────────────────────────────────────────────── Audit log (3.X+)
 * Two surfaces share these rules:
 *   1. The full-page audit views at /c/<co>/audit + /system/audit.
 *   2. The compact dossier widget slotted into the left column of
 *      every Company / Mission / Squad / Joe dossier page.
 *
 * The full page reads wider so the `.audit-table` rules carry no
 * column-width caps; the widget reads narrow so the `.audit-widget-*`
 * rules pin column widths + scale the font down. The actor-pill
 * variants (`.audit-actor--operator|system|proposal`) and the row-
 * separator hairline are shared. */
.audit-table,
.audit-widget-table {
  width: 100%;
  border-collapse: collapse;
}
.audit-table th,
.audit-table td {
  padding: 0.375rem 0.5rem;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--hair-2);
}
.audit-table th {
  font-weight: 600;
  color: var(--text-2);
  font-size: 0.875rem;
}
/* Narrow-rail variant — table-layout: fixed pins the column ratio
 * so the kind cell can wrap freely without pushing the When column
 * out of legibility. */
.audit-widget-table {
  table-layout: fixed;
  font-size: 0.8125rem;
}
.audit-widget-table th,
.audit-widget-table td {
  padding: 0.25rem 0.375rem;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--hair-2);
}
.audit-widget-table th {
  font-weight: 600;
  color: var(--text-2);
  font-size: 0.75rem;
}
.audit-widget-when {
  width: 5.5rem;
  white-space: nowrap;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
.audit-widget-who,
.audit-widget-kind {
  word-break: break-word;
  overflow-wrap: anywhere;
}
.audit-widget-kind code {
  font-size: 0.75rem;
}
.audit-widget-outcome {
  width: 5rem;
}
.audit-widget-empty {
  color: var(--text-2);
  font-style: italic;
  margin: 0.5rem 0;
}
.audit-widget-footer {
  margin-top: 0.5rem;
  text-align: right;
  font-size: 0.875rem;
}
/* Actor pills — same in both surfaces. Operator emails read in the
 * default text colour; system tokens dim into text-2 + a subtle
 * background so they read as supporting metadata, not first-class
 * actions; proposal-driven rows tint to the brass family to echo
 * the proposal-page chrome they came through. */
.audit-actor {
  display: inline-block;
  font-size: 0.8125rem;
  line-height: 1.3;
}
.audit-actor--operator {
  color: var(--text-1);
}
.audit-actor--system {
  color: var(--text-2);
  font-family: var(--font-mono, monospace);
  font-size: 0.75rem;
}
.audit-actor--proposal {
  color: var(--brass-hi);
  font-style: italic;
}
:root[data-theme="light"] .audit-actor--proposal {
  color: var(--brass-lo);
}
.audit-error {
  display: inline-block;
  margin-left: 0.375rem;
  color: var(--oxblood-hi);
  font-size: 0.75rem;
  font-style: italic;
}
:root[data-theme="light"] .audit-error {
  color: var(--oxblood-lo);
}
.audit-noscope {
  color: var(--text-3);
}
.audit-scope-sep {
  color: var(--text-3);
}
.audit-empty {
  color: var(--text-2);
  font-style: italic;
}
.audit-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: flex-end;
  margin: 0.75rem 0 1rem;
}
.audit-filter label {
  display: flex;
  flex-direction: column;
  font-size: 0.8125rem;
  color: var(--text-2);
  gap: 0.25rem;
}
.audit-filter-system {
  flex-direction: row;
  align-items: center;
}

/* ───────────────────────────────────────────────────── 3.434.0
 * /system/vitals — VPS-vitals page chrome.
 *
 * Two style families:
 *   - `.vitals-widget*` — panel header / live-pulse dot / refresh
 *     button on every widget on the page.
 *   - `.vitals-bar*` / `.vitals-cores` — progress-bar shape used by
 *     CPU%, memory, disk volumes, and the storage-tree rows.
 *   - `.vitals-tree*` — storage tree breadcrumb + row list.
 *
 * Tones (`-ok`, `-warn`, `-crit`) follow the existing brass/amber/
 * red ladder; both light + dark themes get explicit overrides.
 */

.vitals-widget-header {
  align-items: center;
}
.vitals-widget-header h2 {
  margin: 0;
}
.vitals-live-dot {
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--brass-hi);
  box-shadow: 0 0 8px var(--brass);
  margin-right: 0.5rem;
  animation: vitals-pulse 3s ease-in-out infinite;
  vertical-align: middle;
}
@keyframes vitals-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}
.vitals-refresh-btn {
  font: inherit;
  font-size: 0.75rem;
  background: transparent;
  color: var(--brass-hi);
  border: 1px solid var(--brass-rim);
  padding: 0.25rem 0.625rem;
  border-radius: 4px;
  cursor: pointer;
}
.vitals-refresh-btn:hover {
  background: var(--brass-dim);
  border-color: var(--brass);
}
:root[data-theme="light"] .vitals-refresh-btn { color: var(--brass); }

.vitals-kv {
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: 0.875rem;
  row-gap: 0.25rem;
  font-size: 0.875rem;
}
.vitals-kv dt { color: var(--text-2); }
.vitals-kv dd { margin: 0; font-variant-numeric: tabular-nums; }

.vitals-bar {
  width: 100%;
  height: 0.5rem;
  background: var(--brass-dim);
  border-radius: 3px;
  overflow: hidden;
  margin-top: 0.25rem;
}
.vitals-bar-fill {
  height: 100%;
  transition: width 0.4s ease-out;
}
.vitals-bar-ok .vitals-bar-fill   { background: var(--brass-hi); }
.vitals-bar-warn .vitals-bar-fill { background: #d9a23a; }
.vitals-bar-crit .vitals-bar-fill { background: #c83838; }
:root[data-theme="light"] .vitals-bar-ok .vitals-bar-fill { background: var(--brass); }

.vitals-cores {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.25rem;
  margin-top: 0.5rem;
}
.vitals-core-row {
  display: grid;
  grid-template-columns: 4rem 1fr 3rem;
  gap: 0.5rem;
  align-items: center;
  font-size: 0.8125rem;
}
.vitals-core-label { color: var(--text-2); }
.vitals-core-pct {
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--text-1);
}

.vitals-disks th,
.vitals-procs th,
.vitals-daemons th {
  text-align: left;
  font-weight: 500;
  color: var(--text-2);
  font-size: 0.75rem;
  padding: 0.25rem 0.5rem 0.5rem 0;
  border-bottom: 1px solid var(--brass-rim);
}
.vitals-disks td,
.vitals-procs td,
.vitals-daemons td {
  padding: 0.5rem 0.5rem 0.5rem 0;
  border-bottom: 1px solid var(--brass-dim);
  font-size: 0.875rem;
  vertical-align: top;
}
.vitals-procs td code {
  font-size: 0.8125rem;
}

/* In-process daemons widget — armed / stopped pill + sweep-ok /
 * sweep-err chips. ARMED uses the same brass-on-dim tone as the
 * existing `.pill` defaults so the row sits visually quiet when
 * everything is healthy; STOPPED + sweep-err shift to amber/red
 * so a broken sweep is immediately spottable in peripheral
 * vision. */
.pill.vitals-daemon-armed {
  background: var(--brass-dim);
  color: var(--brass-hi);
  border-color: var(--brass-rim);
}
.pill.vitals-daemon-stopped {
  background: rgba(200, 56, 56, 0.18);
  color: #d97a7a;
  border-color: rgba(200, 56, 56, 0.36);
}
.vitals-daemon-sweep-ok {
  color: var(--brass-hi);
  font-weight: 500;
}
.vitals-daemon-sweep-err {
  color: #d97a7a;
  font-weight: 500;
}
:root[data-theme="light"] .pill.vitals-daemon-armed { color: var(--brass); }
:root[data-theme="light"] .vitals-daemon-sweep-ok { color: var(--brass); }

/* Storage-tree drill-down chrome. */
.vitals-tree-crumbs {
  margin-bottom: 0.75rem;
  font-size: 0.875rem;
  color: var(--text-2);
}
.vitals-tree-crumb {
  color: var(--brass-hi);
  cursor: pointer;
}
:root[data-theme="light"] .vitals-tree-crumb { color: var(--brass); }
.vitals-tree-crumb:hover { text-decoration: underline; }
.vitals-tree-crumb-active {
  color: var(--text-1);
  font-weight: 500;
}
.vitals-tree-rows {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}
.vitals-tree-row {
  display: grid;
  grid-template-columns: minmax(8rem, 1fr) minmax(6rem, 2fr) max-content;
  gap: 0.75rem;
  align-items: center;
  padding: 0.5rem 0.625rem;
  border: 1px solid var(--brass-dim);
  border-radius: 4px;
  font-size: 0.875rem;
  color: inherit;
  text-decoration: none;
  background: transparent;
}
a.vitals-tree-row {
  cursor: pointer;
}
a.vitals-tree-row:hover {
  background: var(--brass-dim);
  border-color: var(--brass-rim);
  text-decoration: none;
}
.vitals-tree-row-label {
  overflow: hidden;
  text-overflow: ellipsis;
}
.vitals-tree-row-size {
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--text-1);
}
.vitals-tree-loading {
  opacity: 0.5;
  pointer-events: none;
}
