/* ═══════════════════════════════════════════════════════════════════════════
   Root Turn Timer — styles.css
   No @import, no off-origin url(). Every font file is bundled in ./fonts/.

   Layout contract (the fix for the 2025-08 UX review's B1/B2/B3/B5):
   `#app` is the single 100dvh flex column. Every screen is a flex child with
   `min-height: 0`, so a screen's own scroller is bounded and its footer can
   never fall below the fold. Nothing that carries a control is `position:
   fixed` over anything else.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 0. Faces ─────────────────────────────────────────────────────────────
   Root's own typography is Luminari (titles), Mrs Eaves Small Caps (headers)
   and Baskerville 10 Pro (body). All three are commercial and none may be
   redistributed here, so every family is LOCAL-FIRST: `src` opens with
   `local()` so a machine that already has the real face uses it (Luminari and
   Baskerville both ship with macOS; Baskerville also ships with iOS), and
   falls through to a bundled OFL stand-in, then to a system font.

   The stand-ins were chosen from rendered specimens of the real app strings,
   not from names:
     Almendra           — for Luminari. Pen-drawn, high-contrast, wedge-serif
                          storybook roman. Keeps conventional letter skeletons,
                          so "Underground Duchy" survives a glance across a
                          table; the uncial candidates did not (their `u`
                          reads as `v`). It is also the most compact, so every
                          faction name still sets on one line in the seat band.
     Libre Baskerville  — for Baskerville 10 Pro. A Baskerville drawn for
                          screens: large x-height and lining figures, so 13–15px
                          copy and "3:00 allotment" stay solid.
     Root SC            — for Mrs Eaves Small Caps. Libre Baskerville Bold with
                          its capitals scaled to true small-cap proportions and
                          real `smcp`/`c2sc` features added, so the label system
                          is set in an actual small-caps font rather than the
                          browser's synthetic scaling. See fonts/README.txt.
   The mono numeral stack is untouched: the countdown and every scannable digit
   stay in tabular figures.
   ────────────────────────────────────────────────────────────────────────── */
@font-face {
  font-family: "Root Title";
  src: local("Luminari"), local("Luminari-Regular"),
       url("fonts/almendra-400.woff2") format("woff2");
  font-weight: 100 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  /* Luminari has no bold; naming it here means a bold title uses the real face
     rather than a smeared synthetic one. */
  font-family: "Root Title";
  src: local("Luminari"), local("Luminari-Regular"),
       url("fonts/almendra-700.woff2") format("woff2");
  font-weight: 600 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Root SC";
  src: local("Mrs Eaves Small Caps"), local("MrsEavesSmallCaps"),
       local("Mrs Eaves Roman Small Caps"), local("MrsEavesRomanSmallCaps"),
       url("fonts/root-smallcaps.woff2") format("woff2");
  font-weight: 400 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Root Body";
  src: local("Baskerville 10 Pro"), local("Baskerville10Pro-Roman"),
       local("Baskerville"), local("Baskerville-Regular"),
       url("fonts/libre-baskerville-400.woff2") format("woff2");
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Root Body";
  src: local("Baskerville 10 Pro Bold"), local("Baskerville10Pro-Bold"),
       local("Baskerville Bold"), local("Baskerville-Bold"),
       url("fonts/libre-baskerville-700.woff2") format("woff2");
  font-weight: 600 900;
  font-style: normal;
  font-display: swap;
}

/* ── 1. Design tokens ─────────────────────────────────────────────────────
   Both themes are the same Root map at two times of day.

   LIGHT is the autumn board in daylight: the ground is the map's khaki
   parchment field (#E8DDC0), panels are the pale clearing discs and cream card
   stock (#FAF3E1), and every line of type is Kyle Ferrin's warm sepia ink
   (#2A1C0E) rather than black — nothing on a Root component is printed black.

   DARK is the same woodland at night: the ground drops to a green-black forest
   floor (#171A14) and the parchment becomes the text (#F2E9D3). The hue axis
   never leaves the map, so a screenshot of either theme reads as one product.

   The accent ladder is the map's own signal system, and it escalates:
     accent (forest green, ~123°)  — calm; the shaded wedges between clearings
     warn   (burnt sienna, ~28°)   — low time; the Marquise's orange in shadow
     expired (alarm red)           — a full-surface takeover, not a colour swap
   Green → sienna → red is a hue rotation as well as a value jump, so the state
   survives both a glance across a table and dichromatic vision.

   NOTE on --edge (R3.6): a single neutral cannot clear 3:1 against BOTH the
   Duchy navy (#2E2A8C) and the Keepers silver (#C6CBD2) — no colour can, so
   --edge is a maximin, and an inset hairline (--edge-inner) is layered on
   swatches so no swatch can vanish in either theme. It is now a warm khaki
   rather than a cold grey (#7E7A61 light / #827E65 dark): the same maximin
   band (2.65 / 2.51 against those two extremes, versus 2.62 before) but drawn
   from the map's own ink-and-field neutral, and ≥3:1 against --bg, --surface
   and --surface-2 in both themes.
   ────────────────────────────────────────────────────────────────────────── */
:root {
  /* Surfaces — the autumn map read from the outside in: the khaki field, the
     mottled wash over it, then the pale clearing discs and the card stock. */
  --bg: #E8DDC0;          /* map field: warm khaki parchment */
  --bg-grain: #D9CAA6;    /* the darker mottling of the printed field */
  --surface: #FAF3E1;     /* a clearing disc / Root's cream card stock */
  --surface-2: #F1E7CD;   /* aged card back */
  --surface-3: #E2D4B2;   /* deep parchment, one step under the field */

  /* Ink — Ferrin draws in warm sepia, never black. */
  --text: #2A1C0E;
  --text-dim: #5C4A31;
  --edge: #7E7A61;
  --edge-inner: rgba(255, 255, 255, .45);
  --hairline: #D3C4A0;

  /* Accents — the map's own three signals, in escalation order:
     forest green (calm) → burnt sienna (low time) → alarm red (expired). */
  --accent: #375A2C;      /* the shaded forest wedges between clearings */
  --accent-fg: #FBF5E5;   /* card cream, not paper white */
  --accent-soft: #DCE3C4; /* sunlit undergrowth */
  --warn: #9A3F0C;        /* burnt sienna: the Marquise's orange, in shadow */
  --warn-fg: #FFF6EC;
  --warn-soft: #F6E1CD;
  --danger: #9C1F26;      /* oxblood, the Lord of the Hundreds' red */
  --danger-fg: #FFF6F4;
  --expired-bg: #B3161C;  /* the rulebook's alarm red */
  --expired-fg: #FFF4F0;
  --focus: #1A5E7A;       /* the river */
  --shadow: 0 10px 28px -14px rgba(48, 34, 16, .40);
  --shadow-sm: 0 2px 6px -3px rgba(48, 34, 16, .35);

  /* Geometry */
  --radius: 10px;
  --radius-lg: 16px;
  --radius-pill: 999px;
  --tap: 44px;
  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 24px;
  --s6: 32px;
  --gap: 8px;
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
  --col: 720px;

  /* Type scale — rem, so the OS text-size setting moves it (WCAG 1.4.4).
     Display type (numeral, seat band, primary label) stays on clamp()+vw. */
  --fs-eyebrow: .6875rem;   /* 11px — mono glyph badges only */
  --fs-label: .875rem;      /* 14px — the small-caps label face. Small caps sit
                               at ~76% of cap height, so a label needs a larger
                               nominal size than the 11px sans caps it replaces
                               to keep the same 8px optical cap height. */
  --fs-xs: .875rem;         /* 14px — one step up from the old sans scale: a
                               Baskerville reads lighter than a grotesque, so
                               the small end of the scale was raised rather
                               than shipped thin (R11.10 spirit). */
  --fs-sm: .9375rem;        /* 15px */
  --fs-md: 1rem;            /* 16px */
  --fs-lg: 1.0625rem;       /* 17px */
  --fs-xl: 1.1875rem;       /* 19px */
  --fs-2xl: 1.5rem;         /* 24px */
  --fs-3xl: 1.75rem;        /* 28px */

  --seat-color: #5A5A63;
  --seat-fg: #FFFFFF;

  /* Three roles, three faces. Each ends in a system font, so the page is still
     typeset if every bundled file fails to load. */
  --font-title: "Root Title", Luminari, "Palatino Linotype", Palatino,
                Georgia, serif;
  --font-header: "Root SC", Baskerville, Georgia, "Times New Roman", serif;
  --font-body: "Root Body", Baskerville, Georgia, "Times New Roman", serif;
  --font: var(--font-body);
  --font-num: ui-monospace, "SF Mono", "SFMono-Regular", Menlo, Consolas,
              "Roboto Mono", monospace;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #191E16;
    --bg-grain: #222819;
    --surface: #212818;
    --surface-2: #2C3423;
    --surface-3: #353E2A;
    --text: #F2E9D3;
    --text-dim: #B5A98D;
    --edge: #827E65;
    --edge-inner: rgba(0, 0, 0, .45);
    --hairline: #3E4733;
    --accent: #79B063;
    --accent-fg: #10150C;
    --accent-soft: #26301D;
    --warn: #E8A05A;
    --warn-fg: #17110A;
    --warn-soft: #33241A;
    --danger: #EF8B93;
    --danger-fg: #1A0B0C;
    --expired-bg: #8E1114;
    --expired-fg: #FFF1EE;
    --focus: #7EC7E8;
    --shadow: 0 12px 34px -14px rgba(0, 0, 0, .75);
    --shadow-sm: 0 2px 8px -4px rgba(0, 0, 0, .8);
  }
}

:root[data-theme="dark"] {
  --bg: #191E16;
  --bg-grain: #222819;
  --surface: #212818;
  --surface-2: #2C3423;
  --surface-3: #353E2A;
  --text: #F2E9D3;
  --text-dim: #B5A98D;
  --edge: #827E65;
  --edge-inner: rgba(0, 0, 0, .45);
  --hairline: #3E4733;
  --accent: #79B063;
  --accent-fg: #10150C;
  --accent-soft: #26301D;
  --warn: #E8A05A;
  --warn-fg: #17110A;
  --warn-soft: #33241A;
  --danger: #EF8B93;
  --danger-fg: #1A0B0C;
  --expired-bg: #8E1114;
  --expired-fg: #FFF1EE;
  --focus: #7EC7E8;
  --shadow: 0 12px 34px -14px rgba(0, 0, 0, .75);
  --shadow-sm: 0 2px 8px -4px rgba(0, 0, 0, .8);
}

/* ── 2. Reset / base ─────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  overscroll-behavior: none;
}

html { font-size: 100%; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--fs-md);
  /* A serif wants a little more leading than the grotesque did. */
  line-height: 1.5;
  /* Lining figures: a Baskerville that defaults to old-style would set "3:00"
     with descending digits, which is the wrong signal in a timer. */
  font-variant-numeric: lining-nums;
  /* `auto` (not 100%) so Android Chrome's text boosting and the iOS text-size
     control are both honored — WCAG 1.4.4. */
  -webkit-text-size-adjust: auto;
  text-size-adjust: auto;
  -webkit-font-smoothing: antialiased;
}

[hidden] { display: none !important; }

/* ── 2b. The two named roles ──────────────────────────────────────────────
   TITLE — screen headings, the seat band, wizard step titles. Almendra's caps
   sit at .666em against the old sans's ~.72em, so title sizes are nudged up a
   step and the sans's negative tracking is dropped: a pen-drawn roman needs its
   sidebearings.

   HEADER — every tracked label in the app. `all-small-caps` hits Root SC's real
   `smcp`/`c2sc` lookups; text-transform is gone because the feature already
   folds "ROUND 3" and "Longest turn" into the same small-cap colour. If the
   font file never arrives, the system serif synthesises small caps, so a label
   can never fall back to unstyled lowercase.
   ────────────────────────────────────────────────────────────────────────── */
.idle__title,
.wizstep__title,
.overlay__title,
.sheet__title,
.stats__title,
#seat-band-name {
  font-family: var(--font-title);
  letter-spacing: 0;
}

.idle__title-sub,
.step-dot,
#round-pill,
#state-pill,
#btn-primary,
.faction-tile__taken,
.stats__metric dt,
#stats-table thead th {
  font-family: var(--font-header);
  font-variant-caps: all-small-caps;
  font-variant-numeric: lining-nums;
  text-transform: none;
}

h1, h2, h3, p, dl, dd, figure { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

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

button,
input[type="checkbox"],
.chip,
.faction-tile,
.menu-item,
.seat-row__identity-btn {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.chip:active,
.faction-tile:active,
.menu-item:active,
.seat-row__identity-btn:active,
.switch-row:active {
  transform: scale(.975);
  filter: brightness(.95);
}


button, .seat-chip, #timer, .step-dot, #seat-band, #btn-primary,
/* Order rows are draggable surfaces, so a press must lift the row rather than
   start a text selection across the faction names. */
.order-row {
  -webkit-user-select: none;
  user-select: none;
}
/* Belt and braces while a drag is live: a pointer that leaves the list mid-drag
   must not start selecting the rest of the step. */
.order-list[data-dragging="true"],
.order-list[data-dragging="true"] * {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

:focus { outline: none; }
:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius);
}

.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ── 3. App shell ────────────────────────────────────────────────────────── */
#app {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
  transition: background-color 180ms ease, color 180ms ease;
}

/* Every screen is a bounded flex child: its own body scrolls, its footer does
   not move (B2). */
.screen {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* ── 4. Buttons ──────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--gap);
  min-height: var(--tap);
  min-width: var(--tap);
  padding: 10px 18px;
  border: 1.5px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-md);
  font-weight: 600;
  text-align: center;
  cursor: pointer;
  transition: transform 90ms ease, background-color 140ms ease, border-color 140ms ease;
}
.btn:active { transform: scale(.975); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .38; cursor: default; }
.btn[disabled]:active { transform: none; }

.btn--primary {
  background: var(--accent);
  color: var(--accent-fg);
  border-color: transparent;
  font-size: var(--fs-lg);
  box-shadow: var(--shadow-sm);
}
.btn--accent {
  background: var(--accent-soft);
  color: var(--text);
  border-color: var(--accent);
}
.btn--ghost { background: var(--surface-2); }
.btn--secondary {
  background: var(--surface-2);
  border-color: var(--hairline);
  min-height: 64px;
  font-size: var(--fs-lg);
  padding-inline: 22px;
}
.btn--danger { background: var(--danger); color: var(--danger-fg); border-color: transparent; }
.btn--tall { min-height: 68px; font-size: var(--fs-xl); }
.btn--wide { width: 100%; }
.btn--small { min-height: var(--tap); padding: 6px 14px; font-size: var(--fs-sm); }
.btn--icon {
  padding: 0;
  width: var(--tap);
  height: var(--tap);
  flex: 0 0 auto;
  border-radius: var(--radius);
  font-size: 1.375rem;
  line-height: 1;
}
.btn--top { background: transparent; border-color: transparent; color: var(--text-dim); }
.btn--top:active { background: var(--surface-2); }
.btn--onbanner { background: transparent; border-color: transparent; }

.btn-row { display: flex; gap: var(--gap); margin-top: var(--s3); }
.btn-row--split > .btn { flex: 1 1 0; }
.btn-row--nested { margin-top: var(--s2); }

/* ── 5. Form primitives ──────────────────────────────────────────────────── */
.field { margin-top: var(--s5); }
.field:first-of-type { margin-top: var(--s4); }
.field--row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3); flex-wrap: wrap;
}
.field--nested {
  margin-top: var(--s2);
  padding: var(--s3) var(--s3) 2px;
  background: var(--surface-2);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
}
/* One label system: sentence-case control labels everywhere; tracked all-caps
   is reserved for eyebrows (stats metrics, table heads, the round pill). */
.field__label {
  display: block;
  font-size: var(--fs-md);
  font-weight: 700;
  letter-spacing: -.005em;
  color: var(--text);
  margin-bottom: var(--s2);
}
.field--row .field__label { margin-bottom: 0; }
.field--nested .field__label { font-size: var(--fs-sm); color: var(--text-dim); }

.input {
  width: 100%;
  min-height: var(--tap);
  padding: 10px 14px;
  background: var(--surface);
  color: var(--text);
  border: 1.5px solid var(--hairline);
  border-radius: var(--radius);
}
.input--num {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: 1.25rem;
  font-weight: 700;
  text-align: center;
}

.stepper-ctl {
  display: flex;
  align-items: center;
  gap: var(--gap);
  background: var(--surface-2);
  border-radius: var(--radius);
  padding: var(--s1);
}
.stepper-ctl--wide { width: 100%; }
.stepper-ctl--wide .input--num { flex: 1 1 auto; width: auto; }
.stepper-ctl .btn--icon { background: var(--surface); }
.stepper-ctl__value {
  min-width: 56px;
  text-align: center;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: 1.375rem;
  font-weight: 700;
}

.chips { display: flex; flex-wrap: wrap; gap: var(--gap); margin-top: var(--s2); }
.chip {
  min-height: var(--tap);
  min-width: var(--tap);
  padding: 8px 16px;
  border: 1.5px solid var(--hairline);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text);
  font-size: var(--fs-md);
  font-weight: 600;
  cursor: pointer;
}
.chip[aria-pressed="true"] {
  background: var(--accent);
  color: var(--accent-fg);
  border-color: transparent;
}

/* Switch — the input itself is a 56×44 target; track and knob are drawn on it. */
.switch-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  min-height: 60px;
  margin-top: var(--s2);
  padding: var(--s2) var(--s3);
  background: var(--surface-2);
  border-radius: var(--radius);
  cursor: pointer;
  transition: transform 90ms ease, filter 120ms ease;
}
.switch-row__text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.switch-row__label { font-weight: 600; }
.switch-row__note { font-size: var(--fs-xs); color: var(--text-dim); }

.switch {
  appearance: none; -webkit-appearance: none;
  position: relative;
  flex: 0 0 auto;
  width: 56px;
  height: var(--tap);
  margin: 0;
  cursor: pointer;
}
.switch::before {
  content: "";
  position: absolute;
  inset: 6px 0;
  border-radius: var(--radius-pill);
  background: var(--surface-3);
  border: 1.5px solid var(--edge);
  transition: background-color 150ms ease, border-color 150ms ease;
}
.switch::after {
  content: "";
  position: absolute;
  top: 9px; left: 3px;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--surface);
  border: 1.5px solid var(--edge);
  transition: transform 150ms ease;
}
.switch:checked::before { background: var(--accent); border-color: var(--accent); }
.switch:checked::after { transform: translateX(27px); }

.validation {
  margin-top: var(--s2);
  min-height: 1.2em;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--danger);
}
.validation:empty { margin-top: 0; min-height: 0; }
.static-note {
  margin-top: var(--s2);
  padding: 10px var(--s3);
  background: var(--surface-2);
  border-left: 3px solid var(--edge);
  border-radius: var(--radius);
  font-size: var(--fs-sm);
  color: var(--text-dim);
}
.static-note--plain { border-left-color: var(--accent); }

.seat-chip,
#seat-band,
.faction-tile,
.seat-row__swatch,
.stats-row__swatch,
.order-row__glyph {
  background: var(--seat-color);
  color: var(--seat-fg);
  border: 2px solid var(--edge);
  box-shadow: inset 0 0 0 1px var(--edge-inner);
}

/* ── 7. IDLE ─────────────────────────────────────────────────────────────── */
.screen--idle {
  padding:
    calc(var(--safe-top) + var(--s5))
    calc(var(--safe-right) + var(--s5))
    calc(var(--safe-bottom) + var(--s4))
    calc(var(--safe-left) + var(--s5));
  max-width: var(--col);
  margin-inline: auto;
  width: 100%;
  background: radial-gradient(130% 55% at 50% 0%, var(--bg-grain) 0%, transparent 72%);
}
.idle__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--s6);
  padding-block: var(--s4);
}
.idle__top {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--s3);
}
.mark { color: var(--accent); }
.idle__title {
  font-size: clamp(2.75rem, 14vw, 4rem);
  font-weight: 700;
  letter-spacing: 0;
  line-height: 1;
}
.idle__title-sub {
  display: block;
  margin-top: 12px;
  font-size: clamp(1rem, 4.4vw, 1.1875rem);
  font-weight: 700;
  letter-spacing: .16em;
  /* Small caps carry width of their own, so the sans's .24em is far too open. */
  color: var(--text-dim);
}
.idle__blurb {
  max-width: 32ch;
  color: var(--text-dim);
  font-size: var(--fs-md);
}

.how {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  padding: var(--s4);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
}
.how__step { display: flex; align-items: baseline; gap: var(--s3); }
.how__n {
  flex: 0 0 auto;
  width: 22px;
  font-family: var(--font-num);
  font-size: var(--fs-sm);
  font-weight: 800;
  color: var(--accent);
}
.how__text { font-size: var(--fs-sm); color: var(--text-dim); }
.how__text b { color: var(--text); font-weight: 700; }

.idle__actions { flex: 0 0 auto; padding-top: var(--s4); }

.notice {
  display: flex;
  gap: var(--s3);
  align-items: flex-start;
  padding: var(--s3) var(--s4);
  background: var(--surface-2);
  border: 1.5px solid var(--edge);
  border-radius: var(--radius);
}
.notice__icon { flex: 0 0 auto; color: var(--danger); margin-top: 1px; }
.notice__text { font-size: var(--fs-sm); }

/* ── 8. SETUP ────────────────────────────────────────────────────────────── */
.setup__head {
  flex: 0 0 auto;
  padding: calc(var(--safe-top) + 10px) calc(var(--safe-right) + var(--s4))
           10px calc(var(--safe-left) + var(--s4));
  background: var(--bg);
  border-bottom: 1px solid var(--hairline);
}
.stepper {
  display: flex;
  gap: 6px;
  max-width: var(--col);
  margin-inline: auto;
}
.step-dot {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s1);
  padding-top: var(--s2);
  border-top: 4px solid var(--hairline);
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: .05em;
  color: var(--text-dim);
}
.step-dot[data-state="done"] { border-top-color: var(--accent); opacity: .8; }
.step-dot[aria-current="step"] { border-top-color: var(--accent); color: var(--text); }
.step-dot__n {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--surface-2);
  /* The rail's step number is a figure, not a label: mono, full height. */
  font-family: var(--font-num);
  font-variant-caps: normal;
  font-size: var(--fs-eyebrow);
}
.step-dot[aria-current="step"] .step-dot__n { background: var(--accent); color: var(--accent-fg); }

.setup__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  scroll-padding-top: var(--s4);
  padding: 0 calc(var(--safe-right) + var(--s4)) var(--s5) calc(var(--safe-left) + var(--s4));
}
.wizstep { max-width: var(--col); margin-inline: auto; padding-top: var(--s5); }
.wizstep__title { font-size: 1.625rem; font-weight: 700; letter-spacing: 0; line-height: 1.15; }
.wizstep__hint { margin-top: var(--s1); color: var(--text-dim); font-size: var(--fs-sm); }

.setup__foot {
  flex: 0 0 auto;
  display: flex;
  gap: var(--gap);
  width: 100%;
  max-width: var(--col);
  margin-inline: auto;
  padding: var(--s3) calc(var(--safe-right) + var(--s4))
           calc(var(--safe-bottom) + var(--s3)) calc(var(--safe-left) + var(--s4));
  background: var(--surface);
  border-top: 1px solid var(--hairline);
}
.setup__foot > .btn { flex: 1 1 0; min-height: 56px; }
.setup__foot #btn-setup-back { flex: 0 0 32%; }

/* Step 1 — seat rows. One line per seat: identity first, mode segment after. */
.seat-rows { display: flex; flex-direction: column; gap: var(--gap); margin-top: var(--s4); }
.seat-row {
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr) auto;
  grid-template-areas: "num ident mode";
  align-items: center;
  /* Tightened from 8/10 to 6/8: the wider faces need the horizontal room to
     stay on the seat name, which is the point of the row. */
  gap: 6px;
  padding: var(--s2) var(--s2);
  background: var(--surface);
  border: 1.5px solid var(--hairline);
  border-radius: var(--radius-lg);
}
.seat-row__num {
  grid-area: num;
  font-family: var(--font-num);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text-dim);
  text-align: center;
}
.seat-row__mode {
  grid-area: mode;
  display: flex;
  gap: 2px;
  padding: 3px;
  background: var(--surface-2);
  border-radius: var(--radius);
}
.seat-row__identity-btn {
  grid-area: ident;
  display: flex;
  align-items: center;
  gap: var(--s2);
  width: 100%;
  min-height: 52px;
  padding: var(--s1) 6px;
  background: var(--surface-2);
  border: 1.5px solid var(--hairline);
  border-radius: var(--radius);
  cursor: pointer;
  text-align: left;
  transition: transform 90ms ease, filter 120ms ease;
}
.seat-row__swatch {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  width: 38px; height: 38px;
  border-radius: var(--radius);
}
.seat-row__glyph {
  font-family: var(--font-num);
  font-size: var(--fs-sm);
  font-weight: 800;
  letter-spacing: .02em;
}
/* The identity is the point of the row, so it wraps rather than truncating. */
.seat-row__name {
  flex: 1 1 auto;
  min-width: 0;
  /* One step below the other identity rows: this is the only row that shares
     its width with the mode segment, and at 14px every faction word — down to
     "Underground" — still sets whole. Libre Baskerville's x-height at 14px is
     larger than the grotesque's was, so nothing is lost. */
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1.25;
  /* `anywhere` used to be harmless under a narrow grotesque; under a wider
     Baskerville it started splitting "Woodland Alliance" as "Woodlan / d
     Alliance". Break at word boundaries first. */
  overflow-wrap: break-word;
  word-break: normal;
  hyphens: none;
}
.seat-row__chev { color: var(--text-dim); font-size: 1.25rem; }

/* Step 2 — order */
.order-list {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  margin-top: var(--s4);
}
.order-row {
  display: flex;
  align-items: center;
  gap: var(--gap);
  padding: 6px var(--gap);
  background: var(--surface);
  border: 1.5px solid var(--hairline);
  border-radius: var(--radius-lg);
  cursor: grab;
  /* Rows are moved by transform, never by reflow, so a reorder can be
     interpolated. The transition is what every displaced row rides; the row
     under the finger has it switched off and is driven by the pointer. */
  transition: transform 200ms cubic-bezier(.2, .7, .3, 1),
              box-shadow 160ms ease,
              border-color 160ms ease;
  will-change: transform;
  touch-action: manipulation;
}
/* While a drag is live the whole list stops interpreting touch as scrolling. */
.order-list[data-dragging="true"] { touch-action: none; }
.order-list[data-dragging="true"] .order-row { cursor: grabbing; }
.order-row.is-dragging {
  /* Driven frame-by-frame from the pointer: no transition, or it would lag. */
  transition: box-shadow 160ms ease, border-color 160ms ease;
  position: relative;
  z-index: 3;
  cursor: grabbing;
  border-color: var(--accent);
  box-shadow: 0 10px 22px -8px rgb(0 0 0 / .45), 0 0 0 1px var(--accent) inset;
}
/* The settle at the end of a drag: same easing as a displaced row. */
.order-row.is-settling {
  transition: transform 200ms cubic-bezier(.2, .7, .3, 1),
              box-shadow 160ms ease,
              border-color 160ms ease;
}
.order-row__handle {
  width: 22px;
  text-align: center;
  color: var(--text-dim);
  font-size: 1.25rem;
  cursor: grab;
  touch-action: none;
}
.order-row__pos {
  width: 14px;
  font-family: var(--font-num);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text-dim);
}
.order-row__glyph {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  width: 38px; height: 38px;
  border-radius: var(--radius);
  font-family: var(--font-num);
  font-size: var(--fs-sm);
  font-weight: 800;
}
.order-row__name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.25;
  overflow-wrap: break-word;
  word-break: normal;
  hyphens: none;
}
/* Stacked 44×22 arrows: two full-width targets in one 44px column, so the
   name keeps the horizontal space (MN4). */
.order-row__moves { display: flex; flex-direction: column; gap: 2px; flex: 0 0 auto; }
.btn-order-up, .btn-order-down {
  background: var(--surface-2);
  border-color: transparent;
  width: 44px;
  height: 26px;
  min-height: 26px;
  font-size: var(--fs-md);
}
.order-row__moves { padding-block: 2px; }

/* ── 9. Overlays: faction picker + name editor ───────────────────────────── */
.overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  transform: translateY(100%);
  visibility: hidden;
  transition: transform 220ms ease, visibility 0s linear 220ms;
}
.overlay[data-open="true"] {
  transform: none;
  visibility: visible;
  transition: transform 220ms ease, visibility 0s;
}
.overlay__head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: calc(var(--safe-top) + var(--s3)) calc(var(--safe-right) + var(--s4))
           var(--s3) calc(var(--safe-left) + var(--s4));
  border-bottom: 1px solid var(--hairline);
}
.overlay__title { font-size: 1.3125rem; font-weight: 700; letter-spacing: 0; }
.overlay__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 0 calc(var(--safe-right) + var(--s4)) var(--s5) calc(var(--safe-left) + var(--s4));
}
.overlay__foot {
  flex: 0 0 auto;
  padding: var(--s3) calc(var(--safe-right) + var(--s4))
           calc(var(--safe-bottom) + var(--s3)) calc(var(--safe-left) + var(--s4));
  background: var(--surface);
  border-top: 1px solid var(--hairline);
}
.overlay__foot .btn { min-height: 56px; }

.faction-grid {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  grid-auto-rows: 1fr;
  gap: 10px;
  padding: var(--s4) calc(var(--safe-right) + var(--s4))
           calc(var(--safe-bottom) + var(--s5)) calc(var(--safe-left) + var(--s4));
  align-content: start;
}
.faction-tile {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 2px;
  min-height: 88px;
  padding: 12px 14px;
  border-radius: var(--radius-lg);
  cursor: pointer;
  text-align: left;
  transition: transform 90ms ease, filter 120ms ease;
}
.faction-tile__glyph {
  font-family: var(--font-num);
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: .12em;
  opacity: .88;
}
/* Two lines are reserved so one- and two-line tiles square up (N5). */
.faction-tile__name {
  font-size: var(--fs-md);
  font-weight: 700;
  line-height: 1.15;
  min-height: 2.3em;
}
.faction-tile__taken {
  margin-top: auto;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: var(--seat-fg);
  color: var(--seat-color);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .04em;
}
.faction-tile[data-taken="false"] .faction-tile__taken { display: none; }
.faction-tile[data-taken="true"] {
  /* Deliberately identical to an untaken tile. A taken faction keeps its full
     colour and portrait; the seat pill is the only thing that marks it, because
     tapping it swaps rather than being refused. */
  cursor: pointer;
}

.color-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 10px;
}

/* ── 10. PLAY ────────────────────────────────────────────────────────────── */
.screen--play {
  min-height: 0;
  overflow: hidden;
  padding-left: var(--safe-left);
  padding-right: var(--safe-right);
}

#play-topbar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: calc(var(--safe-top) + 6px) 8px 6px 8px;
}
/* The rule separates the round from the actions and absorbs whatever slack is
   left. It yields first: on a narrow phone it collapses to nothing rather than
   pushing the last action off the edge. */
#play-topbar .topbar__rule { margin: 0 4px; flex: 1 1 0; min-width: 0; }
#play-topbar #round-pill { min-width: 0; }

#round-pill {
  flex: 0 0 auto;
  padding: 5px 10px;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  border: 1.5px solid var(--hairline);
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: .05em;
  white-space: nowrap;
}
.topbar__rule {
  flex: 1 1 auto;
  height: 1px;
  min-width: 0;
  background: var(--hairline);
}

/* ── In-game actions: a rail of labelled icons, not a dropdown ───────────────
   Every option that used to hide behind the ⋯ menu is on the screen. The
   caption matters more than the icon at arm's length across a table, so both
   ship; the caption is what makes an unfamiliar glyph legible. */
.topbar__action {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  width: auto;
  min-width: var(--tap);
  min-height: var(--tap);
  /* No horizontal padding: five labelled actions plus the round have to fit
     across 375px, and the 44px min-width already guarantees the tap target. */
  padding: 2px 0;
  line-height: 1;
}
.topbar__action-label {
  font-family: var(--font-header);
  font-size: .625rem;          /* 10px — a caption, not body copy */
  font-variant-caps: all-small-caps;
  letter-spacing: .02em;
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
}
/* Ending or discarding a game is not the same kind of act as viewing stats.
   Both already route through a two-step confirm; the colour is the warning that
   comes before the tap, and they sit at the end of the rail, furthest from
   Undo, so a mis-reach lands on something harmless. */
.topbar__action--danger { color: var(--danger); }
.topbar__action:disabled { opacity: .38; }
.topbar__action:not(:disabled):active { background: var(--surface-2); }

/* Below ~360px five captioned actions plus the round no longer fit, and the
   last one would fall off the edge. The captions go, not the actions: the
   aria-label carries the same words to a screen reader either way. */
@media (max-width: 359px) {
  .topbar__action-label { display: none; }
  .topbar__action { min-width: 40px; }
}

/* The strip gets its own full-width row, so every seat is visible at every
   phone width (MJ1). */
#turn-strip {
  flex: 0 0 auto;
  display: flex;
  align-items: stretch;
  gap: 6px;
  padding: 2px 12px 8px;
  border-bottom: 1px solid var(--hairline);
  overflow: hidden;
}
#turn-strip:focus { outline: none; }

.seat-chip {
  flex: 1 1 0;
  min-width: 0;
  max-width: 108px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  min-height: 46px;
  padding: 4px 2px;
  border-radius: var(--radius);
  transition: transform 140ms ease, opacity 140ms ease;
}
.seat-chip__glyph {
  font-family: var(--font-num);
  font-size: var(--fs-xs);
  font-weight: 800;
  line-height: 1.05;
}
.seat-chip__badge {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.1;
  opacity: .95;
}
.seat-chip[data-current="true"] {
  outline: 3px solid var(--text);
  outline-offset: 2px;
  z-index: 1;
}
.seat-chip[data-played="true"] { opacity: .55; }

.gap-banner {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--gap);
  margin: var(--gap) 12px 0;
  padding: 10px 12px;
  background: var(--surface-2);
  border: 1.5px solid var(--accent);
  border-radius: var(--radius);
}
.gap-banner__text { font-size: var(--fs-sm); font-weight: 600; flex: 1 1 190px; min-width: 0; }
.gap-banner__actions { display: flex; gap: var(--gap); }

#seat-band {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s3);
  min-height: 64px;
  margin: 10px 12px 0;
  padding: 10px var(--s4);
  border-radius: var(--radius-lg);
  /* No coloured bloom (N1) — a flat field plus the shared inset hairline. */
  box-shadow: inset 0 0 0 1px var(--edge-inner);
}
#seat-band-name {
  font-size: clamp(1.75rem, 7.4vw, 2.625rem);
  font-weight: 700;
  letter-spacing: 0;
  line-height: 1.05;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.play-main {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 6px 12px;
  overflow: hidden;
}
.play-main > * { min-height: 0; max-width: 100%; }

#timer { display: flex; align-items: center; justify-content: center; min-height: 0; }
#timer-value {
  display: inline-block;
  font-family: var(--font-num);
  /* Height-aware: the numeral yields when there is no vertical room (MJ4). */
  font-size: min(clamp(72px, 26vw, 220px), 24dvh);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  line-height: 1.04;
  letter-spacing: -.035em;
  white-space: nowrap;
  transition: color 180ms ease, opacity 180ms ease;
}
/* H:MM:SS keeps far more of its size than before (MN12). */
#timer[data-len="7"] #timer-value,
#timer[data-len="8"] #timer-value { font-size: min(clamp(56px, 21vw, 150px), 20dvh); }

#composition-line,
#next-up-line {
  /* These two are read at arm's length across a table, and a Baskerville at
     15px is not the same size as a grotesque at 15px — so they move up to the
     body size proper rather than staying a step below it. */
  font-size: var(--fs-md);
  font-weight: 600;
  /* Set solid: PLAY is a fixed-height column, and the body line-height of 1.5
     would push these two lines past the fold on a 667px phone. */
  line-height: 1.25;
  color: var(--text-dim);
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}
#composition-line { margin-top: 3px; }

/* The one place a game state is spelled out in words (MJ2/MJ3/MJ7). */
#state-pill {
  margin: 6px 0 2px;
  padding: 5px 14px;
  border-radius: var(--radius-pill);
  font-size: clamp(1rem, 4.2vw, 1.1875rem);
  font-weight: 700;
  letter-spacing: .07em;
  line-height: 1.15;
  text-align: center;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#state-pill[data-state="paused"] {
  background: var(--surface-3);
  color: var(--text);
  border: 1.5px solid var(--edge);
}
#state-pill[data-state="low"] {
  background: var(--warn);
  color: var(--warn-fg);
}
#state-pill[data-state="expired"] {
  background: var(--expired-fg);
  color: var(--expired-bg);
  letter-spacing: .05em;
}

/* Low time: a persistent state, not a blink — it survives reduced motion. */
#app[data-low="true"] #timer-value {
  color: var(--warn);
  border-bottom: 4px solid var(--warn);
  padding-bottom: 2px;
}
/* Paused: unmistakable at three feet — dimmed numeral, drained seat colour, and
   a Resume button that looks like the thing to press. */
#app[data-phase="TURN_PAUSED"] #timer-value { opacity: .5; }
#app[data-phase="TURN_PAUSED"] #seat-band { filter: saturate(.5); }
#app[data-phase="TURN_PAUSED"] #btn-pause {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--text);
}

.play-actions {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  padding: 0 var(--s4) calc(var(--safe-bottom) + var(--s3));
}
#secondary-row {
  display: flex;
  align-items: center;
  gap: var(--gap);
  min-height: 64px;
  margin-bottom: var(--s5);   /* R5.2 fumble gap */
}
#secondary-row > .btn { flex: 1 1 0; }
#btn-pause { min-height: 64px; }
#btn-extra[data-armed="true"] {
  background: var(--accent);
  color: var(--accent-fg);
  border-color: transparent;
}
/* The reserved slot is never an empty hole (N7): between turns it carries the
   next-up line. */
#standby-note {
  flex: 1 1 auto;
  min-width: 0;
  text-align: center;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#btn-primary {
  width: 100%;
  min-height: max(30vh, 200px);
  padding: 20px;
  border: 0;
  border-radius: var(--radius-lg);
  background: var(--accent);
  color: var(--accent-fg);
  /* Small caps stand at ~76% of cap height, so the nominal size rises to hold
     the same ~20px optical cap height the sans label had at 7.5vw. */
  font-size: clamp(2rem, 9.5vw, 3rem);
  font-weight: 700;
  letter-spacing: .05em;
  line-height: 1.1;
  cursor: pointer;
  box-shadow: var(--shadow);
  transition: transform 90ms ease, filter 120ms ease;
}
#btn-primary:active { transform: scale(.985); filter: brightness(.94); }
#btn-primary[data-action="end"] { letter-spacing: .035em; }

/* ── 11. Expired surface (R7.1) ──────────────────────────────────────────── */
#app[data-phase="TURN_EXPIRED"] {
  --bg: var(--expired-bg);
  --bg-grain: var(--expired-bg);
  --text: var(--expired-fg);
  --text-dim: #FDE9E8;          /* 5.97:1 on light #B3161C, 8.06:1 on dark #8E1114 */
  --surface: rgba(0, 0, 0, .22);
  --surface-2: rgba(0, 0, 0, .26);
  --surface-3: rgba(0, 0, 0, .34);
  --hairline: rgba(255, 255, 255, .34);
  --accent: #F7F2EC;
  --accent-fg: #17120A;
  --accent-soft: rgba(0, 0, 0, .26);
  --focus: #FFFFFF;
}
#app[data-phase="TURN_EXPIRED"] #round-pill,
#app[data-phase="TURN_EXPIRED"] .gap-banner { border-color: rgba(255, 255, 255, .4); }
#app[data-phase="TURN_EXPIRED"] #btn-primary { box-shadow: 0 12px 34px -14px rgba(0, 0, 0, .8); }
#app[data-phase="TURN_EXPIRED"] #turn-strip { border-bottom-color: rgba(255, 255, 255, .28); }
#app[data-phase="TURN_EXPIRED"] #timer-value { border-bottom: 0; padding-bottom: 0; }
#app[data-phase="TURN_EXPIRED"] #timer-value { color: var(--expired-fg); }

/* Pause / extra visibility is JS-driven via `hidden`; these keep the
   attribute protocol honest where CSS can help. */
#app[data-phase="GAME_OVER"] #secondary-row { visibility: hidden; }

/* ── 12. Low-time pulse (R9.3) ───────────────────────────────────────────── */
/* A scale pulse, not an opacity blink: the digits stay readable throughout,
   and the persistent colour state above carries the information when motion
   is suppressed. */
@keyframes lowpulse {
  0%, 100% { transform: scale(1); }
  40%      { transform: scale(1.05); }
}
#timer[data-flash] #timer-value { animation: lowpulse 600ms 1; }

/* ── 13. STATS ───────────────────────────────────────────────────────────── */
.stats__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  width: 100%;
  max-width: var(--col);
  margin-inline: auto;
  padding: calc(var(--safe-top) + var(--s5)) calc(var(--safe-right) + var(--s4))
           var(--s5) calc(var(--safe-left) + var(--s4));
}
.stats__title { font-size: 1.875rem; font-weight: 700; letter-spacing: 0; line-height: 1.15; }

.stats__summary {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--gap);
  margin-top: var(--s4);
}
.stats__metric {
  padding: 10px var(--gap);
  background: var(--surface-2);
  border-radius: var(--radius);
  text-align: center;
}
.stats__metric dt {
  font-size: clamp(11.5px, 3.2vw, 14px);
  font-weight: 700;
  letter-spacing: .04em;
  white-space: nowrap;
  color: var(--text-dim);
}
.stats__metric dd {
  margin: var(--s1) 0 0;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: 1.25rem;
  font-weight: 800;
}

.table-wrap {
  position: relative;
  margin-top: var(--s5);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  overflow: hidden;
}
/* Fade the right edge so it is obvious the table can be swiped sideways; the
   fade goes away once there is nothing left to reveal (MN5). */
.table-wrap::after {
  content: "";
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: 30px;
  pointer-events: none;
  opacity: 1;
  transition: opacity 180ms ease;
  background: linear-gradient(to left, var(--bg) 25%, transparent);
}
.table-wrap[data-scroll="none"]::after,
.table-wrap[data-scroll="end"]::after { opacity: 0; }
.table-hint {
  margin-top: 6px;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-dim);
  text-align: right;
}
.table-wrap[data-scroll="none"] ~ .table-hint,
.table-wrap[data-scroll="end"] ~ .table-hint { visibility: hidden; }
.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}
#stats-table { width: 100%; border-collapse: collapse; font-size: var(--fs-xs); }
#stats-table th, #stats-table td { padding: 8px 5px; text-align: right; white-space: nowrap; }
#stats-table thead th {
  position: sticky; top: 0;
  background: var(--surface-2);
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: .04em;
  color: var(--text-dim);
  border-bottom: 1px solid var(--hairline);
}
/* The seat column stays put while the numeric columns scroll. */
#stats-table .col-seat, #stats-table .stats-row__seat {
  min-width: 116px;
  position: sticky;
  left: 0;
  z-index: 2;
  text-align: left;
  padding-left: var(--gap);
  background: var(--bg);
  border-right: 1px solid var(--hairline);
}
#stats-table .col-seat { z-index: 3; background: var(--surface-2); }
#stats-table .stats-row__name { flex: 1 1 auto; text-align: left; }
#stats-table tbody tr + tr th, #stats-table tbody tr + tr td { border-top: 1px solid var(--hairline); }
#stats-table abbr { text-decoration: none; }

.stats-row__seat {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 600;
}
.stats-row__swatch {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  width: 28px; height: 28px;
  border-radius: 6px;
  border-width: 2px;
}
.stats-row__glyph { font-family: var(--font-num); font-size: var(--fs-eyebrow); font-weight: 800; }
.stats-row__name {
  /* The identity wraps at word boundaries rather than truncating; the numeric
     columns give up the width instead (MN4). */
  min-width: 74px;
  max-width: 13ch;
  white-space: normal;
  word-break: normal;
  overflow-wrap: normal;
  hyphens: none;
  line-height: 1.2;
}
.stats-row__turns, .stats-row__total, .stats-row__avg,
.stats-row__zeros, .stats-row__extra, .stats-row__bank {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
}

.stats__longest {
  margin-top: var(--s4);
  padding: var(--s3) var(--s3);
  background: var(--surface-2);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  font-size: var(--fs-sm);
  font-weight: 600;
}

.stats__foot {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  width: 100%;
  max-width: var(--col);
  margin-inline: auto;
  padding: var(--s3) calc(var(--safe-right) + var(--s4))
           calc(var(--safe-bottom) + var(--s3)) calc(var(--safe-left) + var(--s4));
  background: var(--surface);
  border-top: 1px solid var(--hairline);
}
.stats__foot .btn { min-height: 52px; }
#btn-back-to-game { min-height: 60px; }

/* ── 14. Scrim, sheets, toast, banners ───────────────────────────────────── */
#scrim {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: rgba(8, 6, 3, .55);
  opacity: 0;
  visibility: hidden;
  transition: opacity 200ms ease, visibility 0s linear 200ms;
}
#scrim[data-open="true"] {
  opacity: 1;
  visibility: visible;
  transition: opacity 200ms ease, visibility 0s;
}

.sheet {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 55;
  width: 100%;
  max-width: var(--col);
  margin-inline: auto;
  max-height: 88dvh;
  display: flex;
  flex-direction: column;
  /* B1: without this the body spills past the viewport with nothing to scroll,
     and the last menu items become physically unreachable in landscape. */
  overflow: hidden;
  padding: var(--gap) calc(var(--safe-right) + var(--s4))
           calc(var(--safe-bottom) + var(--s4)) calc(var(--safe-left) + var(--s4));
  background: var(--surface);
  border-top: 1px solid var(--hairline);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: var(--shadow);
  transform: translateY(101%);
  visibility: hidden;
  transition: transform 240ms cubic-bezier(.2, .8, .3, 1), visibility 0s linear 240ms;
}
.sheet[data-open="true"] {
  transform: none;
  visibility: visible;
  transition: transform 240ms cubic-bezier(.2, .8, .3, 1), visibility 0s;
}
.sheet__grip {
  flex: 0 0 auto;
  width: 44px; height: 4px;
  margin: var(--s1) auto 10px;
  border-radius: var(--radius-pill);
  background: var(--hairline);
}
.sheet__head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  margin-bottom: var(--s1);
}
.sheet__title { font-size: 1.3125rem; font-weight: 700; letter-spacing: 0; }
.sheet__text { margin-top: 6px; color: var(--text-dim); font-size: var(--fs-sm); }
.sheet__note {
  flex: 0 0 auto;
  margin-top: 6px;
  padding: var(--gap) var(--s3);
  background: var(--accent-soft);
  border-radius: var(--radius);
  font-size: var(--fs-sm);
  font-weight: 600;
}
.sheet__body { display: flex; flex-direction: column; gap: var(--gap); margin-top: 10px; }
.sheet__body--scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding-bottom: var(--gap);
}
.sheet--tall { max-height: 90dvh; }

.menu-item {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  width: 100%;
  min-height: 58px;
  padding: 10px var(--s3);
  background: var(--surface-2);
  border: 1.5px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
  text-align: left;
  transition: transform 90ms ease, filter 120ms ease;
}
.menu-item__label { font-size: var(--fs-lg); font-weight: 700; }
.menu-item__note { font-size: var(--fs-xs); color: var(--text-dim); }
.menu-item--danger { border-color: var(--danger); }
.menu-item--danger .menu-item__label { color: var(--danger); }

.sheet--confirm { text-align: left; }
/* Destructive is deliberately NOT under the resting thumb (N10). */
.confirm__actions { display: flex; gap: var(--gap); margin-top: var(--s4); }
.confirm__actions > .btn { min-height: 56px; }
#btn-confirm-no { flex: 1.4 1 0; }
#btn-confirm-yes { flex: 1 1 0; }

/* B5: the toast never eats a tap, and on PLAY it sits clear of the primary
   button (whose height JS publishes as --play-actions-h). */
#toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--safe-bottom) + var(--s4));
  z-index: 70;
  pointer-events: none;
  width: min(calc(100vw - 32px), 440px);
  padding: 12px var(--s4);
  background: var(--surface-3);
  color: var(--text);
  border: 1.5px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-align: center;
  transform: translate(-50%, calc(100% + 32px));
  opacity: 0;
  visibility: hidden;
  transition: transform 220ms ease, opacity 220ms ease, visibility 0s linear 220ms;
}
#app[data-screen="play"] #toast {
  bottom: calc(var(--safe-bottom) + 10px + var(--play-actions-h, 280px));
}
#app[data-screen="setup"] #toast {
  bottom: calc(var(--safe-bottom) + 10px + var(--setup-foot-h, 80px));
}
#app[data-screen="stats"] #toast {
  bottom: calc(var(--safe-bottom) + 10px + var(--stats-foot-h, 130px));
}
#app[data-screen="idle"] #toast {
  bottom: calc(var(--safe-bottom) + 10px + var(--idle-foot-h, 88px));
}
#toast[data-open="true"] {
  transform: translate(-50%, 0);
  opacity: 1;
  visibility: visible;
  transition: transform 220ms ease, opacity 220ms ease, visibility 0s;
}

/* MJ6: in flow, above the screens — a role="status" element never covers a
   control. */
.storage-banner {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--gap);
  width: 100%;
  max-width: var(--col);
  margin: calc(var(--safe-top) + var(--gap)) auto 0;
  padding: 6px 6px 6px var(--s3);
  background: var(--surface-3);
  border: 1.5px solid var(--edge);
  border-radius: var(--radius);
}
.storage-banner__icon { flex: 0 0 auto; color: var(--danger); }
.storage-banner__text { flex: 1 1 auto; font-size: var(--fs-xs); font-weight: 600; }

/* ── 15. Responsive ──────────────────────────────────────────────────────── */

/* Short viewports (R11.6) — collapse the two sub-lines onto one row. */
@media (max-height: 599px) {
  .play-main {
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    align-content: center;
    gap: 2px 18px;
  }
  #timer { flex: 1 0 100%; }
  #state-pill { flex: 1 0 100%; margin: 2px 0; }
  #composition-line, #next-up-line { font-size: var(--fs-sm); }
  #btn-primary { min-height: max(26vh, 160px); font-size: clamp(1.6875rem, 8vw, 2.5rem); }
  #secondary-row { margin-bottom: var(--s3); min-height: 56px; }
  #btn-pause, .btn--secondary { min-height: 56px; }
  #seat-band { min-height: 52px; margin-top: 6px; }
  #seat-band-name { font-size: clamp(1.5rem, 6.4vw, 2.125rem); }
  #turn-strip { padding-bottom: 6px; }
  .seat-chip { min-height: 42px; }
}

/* Landscape phones (R11.7) — two columns, with the strip and the gap banner
   as full-width rows so nothing is ever drawn over the numeral (B3). */
@media (orientation: landscape) and (max-height: 500px) {
  .screen--play {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-rows: auto auto auto auto minmax(0, 1fr);
    grid-template-areas:
      "top    top"
      "strip  strip"
      "banner banner"
      "band   right"
      "main   right";
    column-gap: var(--s3);
  }
  #play-topbar  { grid-area: top; }
  #turn-strip   { grid-area: strip; }
  #gap-banner   { grid-area: banner; margin: 6px 12px 0; }
  #seat-band    { grid-area: band; margin: 6px 0 0 12px; min-height: 44px; }
  .play-main    { grid-area: main; padding: 4px 12px 8px; }
  .play-actions { grid-area: right; justify-content: flex-end; padding: 6px 12px calc(var(--safe-bottom) + 8px) 0; height: 100%; }
  #secondary-row { margin-bottom: var(--s3); }
  #btn-primary { flex: 1 1 auto; min-height: 110px; }
  #timer-value { font-size: min(clamp(48px, 13vw, 130px), 24dvh); }
  #timer[data-len="7"] #timer-value,
  #timer[data-len="8"] #timer-value { font-size: min(clamp(38px, 10vw, 104px), 19dvh); }
  #seat-band-name { font-size: clamp(1.1875rem, 3.6vw, 1.875rem); }
  #btn-primary { font-size: clamp(1.5rem, 4.6vw, 2.25rem); }
  #app[data-screen="play"] #toast {
    left: 25%;
    bottom: calc(var(--safe-bottom) + 8px);
    width: min(calc(50% - 28px), 420px);
  }
}

/* Tablet / desktop (R11.8, 13.9) — cap and center the column. */
@media (min-width: 900px) {
  #app > section { width: 100%; max-width: var(--col); margin-inline: auto; }
  #timer-value { font-size: min(220px, 24dvh); }
  #timer[data-len="7"] #timer-value,
  #timer[data-len="8"] #timer-value { font-size: min(150px, 20dvh); }
  #btn-primary { min-height: max(26vh, 200px); }
  .screen--play { border-inline: 1px solid var(--hairline); }
  .seat-chip { max-width: 120px; }
  .faction-grid, .overlay__body, .overlay__head, .overlay__foot {
    width: 100%;
    max-width: var(--col);
    margin-inline: auto;
  }
}

/* ── 16. Reduced motion (R11.11) ─────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  /* The expired surface, the low-time colour state and the paused state are
     information, not decoration: they are static styles and are unaffected by
     the rules above. */
}

/* ══════════════════════════════════════════════════════════════════════════
   17. SETUP FLOW — appended block (faction-only seats, optional nicknames,
   the reach readout, the taken-faction treatment). Added at the end of the
   file on purpose: it defines new components and overrides two earlier rules,
   and touches no colour token.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Seat row: faction on the left, an optional player name on the right ── */
.seat-row { grid-template-areas: "num ident nick"; }
.seat-row__text {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
  gap: 1px;
}
.seat-row__sub {
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  line-height: 1.2;
  color: var(--text-dim);
  overflow-wrap: break-word;
}
.seat-row__nick-btn {
  grid-area: nick;
  min-height: var(--tap);
  min-width: var(--tap);
  padding: 6px 10px;
  background: var(--surface-2);
  border: 1.5px solid var(--hairline);
  border-radius: var(--radius);
  color: var(--text-dim);
  font-size: var(--fs-xs);
  font-weight: 700;
  cursor: pointer;
  touch-action: manipulation;
  user-select: none;
}
.seat-row__nick-btn[aria-pressed="true"] {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--text);
}

/* Step 1 fills the scroll area so the reach bar sits at the bottom of the step
   even when four seats do not fill the screen. */
#setup-step-1 {
  display: flex;
  flex-direction: column;
  min-height: 100%;
}
#setup-step-1 .reach { margin-top: auto; }

/* ── Reach readout (R3.9) ────────────────────────────────────────────────
   A permanent inline readout rather than a hover tooltip: there is no hover on
   a phone, and this is a number the table wants while it is still choosing.
   It is advisory — `data-level="low"` colours the rail and nothing else. */
.reach {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px var(--s2);
  margin-top: var(--s4);
  /* Sticks to the bottom of the scrolling step so six seats cannot push the
     readout off-screen — it is meant to be visible while you are still
     choosing, which is exactly when the list is longest. Styled as a bar, not
     a floating card, so the rows scrolling under it read as "behind the bar"
     rather than as a layout collision. */
  position: sticky;
  bottom: calc(var(--s5) * -1);
  z-index: 2;
  margin-inline: calc(var(--s4) * -1);
  margin-bottom: calc(var(--s5) * -1);
  padding: 10px var(--s4) calc(10px + var(--s5));
  background: var(--surface);
  border: 0;
  border-top: 1.5px solid var(--hairline);
  border-left: 4px solid var(--edge);
  box-shadow: 0 -10px 16px -10px rgba(0, 0, 0, .35);
}
.reach__total {
  font-family: var(--font-num);
  font-size: var(--fs-lg);
  font-weight: 800;
  letter-spacing: .01em;
}
.reach__note { font-size: var(--fs-xs); color: var(--text-dim); }
.reach[data-level="low"] {
  border-left-color: var(--warn);
  background: var(--warn-soft);
  border-top-color: var(--warn);
}
.reach[data-level="low"] .reach__note,
.reach[data-level="low"] .reach__total { color: var(--warn); }

/* ── Faction tiles: reach, and an unmistakable "taken" ───────────────────
   Overrides the earlier desaturation. A greyed tile read as "this faction is
   grey", which is exactly the confusion the seat colours exist to prevent, so
   a taken tile keeps its real colour and is struck through instead. It is also
   still tappable — the tap swaps the two seats. */
/* Taller than the pre-Homeland tile: the name reserves two lines, and the SEAT
   pill now has to sit under them without being clipped at 375px. */
.faction-tile { position: relative; overflow: hidden; min-height: 132px; }

/* ── Faction portraits ───────────────────────────────────────────────────────
   Kyle Ferrin's character art, one per faction, anchored bottom-right of its
   tile and bled off the corner so the tile reads as a card with a figure on it
   rather than a photo in a frame. The text keeps the left half to itself: the
   art is set behind everything and faded toward the label side, so a light
   portrait can never eat a dark name. */
/* ── FACTION PORTRAIT KNOBS ───────────────────────────────────────────────────
   Everything about how the character art sits in its tile is one of these six
   variables. Change them here; nothing else needs touching.

     --art-h        how tall the art box is, as a % of the tile. >100% bleeds
                    the figure off the bottom edge. 100% = fully inside.
     --art-w        how wide the art box may get, as a % of the tile.
     --art-right    horizontal bleed. 0 = flush with the right edge,
                    negative = hangs off it.
     --art-bottom   vertical bleed. 0 = sits on the bottom edge.
     --art-fade     where the left-to-right fade finishes becoming solid.
                    Smaller = less of the figure is faded away (softer bleed).
     --art-opacity  overall strength of the portrait.

   Per-faction overrides go in the block at the bottom of this section — the
   art is not all the same shape, and a couple of them need a nudge. */
.faction-tile {
  --art-h: 106%;
  --art-w: 56%;
  --art-right: -2%;
  --art-bottom: -3%;
  --art-fade: 22%;
  --art-opacity: .97;
}
.faction-tile__art {
  position: absolute;
  right: var(--art-right);
  bottom: var(--art-bottom);
  width: auto;
  height: var(--art-h);
  max-width: var(--art-w);
  object-fit: contain;
  object-position: right bottom;
  z-index: 0;
  pointer-events: none;
  user-select: none;
  opacity: var(--art-opacity);
  /* Feather the inner edge so the figure emerges from the colour field rather
     than ending in a hard line. The fade is short now: it only softens the
     overlap with the label, it does not erase half the character. */
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 var(--art-fade), #000 100%);
  mask-image: linear-gradient(to right, transparent 0%, #000 var(--art-fade), #000 100%);
  filter: drop-shadow(0 2px 3px rgb(0 0 0 / .24));
}
/* Everything else on the tile sits above the portrait. */
.faction-tile > *:not(.faction-tile__art) { position: relative; z-index: 1; }
/* A taken faction dims its portrait so the strike and the seat pill stay first. */
/* A taken faction keeps its portrait at full strength — see the pill. */

/* ── Per-faction nudges ──────────────────────────────────────────────────────
   The source images are not one shape. A wide one (the bat is 420x286) fitted
   into a tall box by `contain` ends up small and parked at the bottom, so it
   gets a shorter box and no vertical bleed. Ids match js/factions.js. */
.faction-tile[data-faction-id="14"] {   /* Twilight Council — wide, wings out */
  --art-h: 74%;
  --art-w: 62%;
  --art-bottom: 6%;
  --art-right: -1%;
}
.faction-tile[data-faction-id="13"] {   /* Knaves of the Deepwood — wide tail */
  --art-h: 88%;
  --art-w: 60%;
  --art-bottom: 2%;
}
.faction-tile[data-faction-id="8"] {    /* Underground Duchy — wide, low mass */
  --art-h: 90%;
  --art-w: 60%;
  --art-bottom: 2%;
}
.faction-tile[data-faction-id="11"] {   /* Keepers in Iron — tall, narrow */
  --art-h: 112%;
  --art-w: 44%;
}

.faction-tile__top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s2);
  width: 100%;
}
.faction-tile__reach {
  font-family: var(--font-num);
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  opacity: .82;
  white-space: nowrap;
}

.faction-tile[data-taken="true"] .faction-tile__taken,
.faction-tile[data-mine="true"] .faction-tile__taken {
  display: inline-block;
  position: relative;
  z-index: 1;
  /* The pill now carries a typed name, not just "SEAT 3", so it has to cope
     with a long one without pushing into the portrait. */
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The seat's own current faction: outlined, and labelled with its own seat. */
.faction-tile[aria-pressed="true"] {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* ── Play: the nickname leads, the faction stays under it ────────────────── */
#seat-band { flex-wrap: wrap; row-gap: 0; }
#seat-band-name { flex: 0 1 auto; }
#seat-band-faction {
  flex: 1 0 100%;
  text-align: center;
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  opacity: .88;
}

/* ── Stats: same pairing in the seat column ──────────────────────────────── */
.stats-row__text {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
  text-align: left;
}
#stats-table .stats-row__sub {
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  color: var(--text-dim);
  line-height: 1.2;
}

/* ── Nickname editor: two stacked actions in the footer ──────────────────── */
#nickname-editor .overlay__foot {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}


/* ══════════════════════════════════════════════════════════════════════════
   18. HAND-INKED BLOBS

   Root's components are line-art heavy: ink outlines with visible wobble
   rather than geometric rules. These shapes are generated as superellipses
   with per-vertex jitter, so no two are identical and none is a rounded
   rectangle. Each blob is an inline <svg> — not a background image — so its
   fill and stroke read the theme tokens and follow light/dark and the seat
   colour without a second copy of the asset.

   `preserveAspectRatio="none"` lets one shape stretch to whatever the text
   needs; `vector-effect="non-scaling-stroke"` keeps the ink an even weight
   while it does, which is the whole trick.
   ══════════════════════════════════════════════════════════════════════════ */

/* Every .blob MUST establish a containing block — the art inside is
   absolutely positioned, and without this it escapes to the screen. */
.blob { position: relative; background: none !important; border: 0 !important; box-shadow: none !important; }
.blob > .blob-art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
  overflow: visible;
}
/* Four stacked layers make the stock look printed rather than filled:
     __fill   the base colour, run through the fibre filter
     __grain  a handful of flecks and inclusions
     __shade  one wash: highlight at the top, shadow at the bottom
     __ink    the outline, displaced slightly so it reads as drawn
   Only __fill and __ink take colour from the theme; the middle two are
   deliberately colour-agnostic so they work on parchment, stone and every
   faction colour without a second set of tokens. */
/* The base is the safety net: same shape, same colour, NO filter. If a
   filter or gradient reference fails to resolve — which WebKit does whenever
   the defs are unreachable — the textured layers vanish and this one still
   paints the slab. Texture is allowed to fail; the shape is not. */
.blob > .blob-art .blob-art__base {
  fill: var(--blob-fill, var(--surface));
  stroke: none;
}
.blob > .blob-art .blob-art__fill {
  fill: var(--blob-fill, var(--surface));
  stroke: none;
  filter: url(#rt-fibre);
}
.blob > .blob-art .blob-art__shade {
  fill: url(#rt-wash);
  stroke: none;
  pointer-events: none;
}
.blob > .blob-art .blob-art__grain .blob-art__fleck {
  fill: var(--blob-ink, var(--text));
  opacity: .13;
}
.blob > .blob-art .blob-art__ink {
  fill: none;
  stroke: var(--blob-ink, var(--text));
  stroke-width: 2.4;
  vector-effect: non-scaling-stroke;
  stroke-linejoin: round;
  stroke-linecap: round;
  /* No displacement filter here on purpose: the tears are already in the path,
     and a filter on the one layer that draws the outline is not worth the risk
     of it disappearing. */
}
/* The wash and the fibre are texture, not information. A viewer who has asked
   for less, or a browser that cannot filter, still gets the flat shape. */
@media (prefers-reduced-transparency: reduce) {
  .blob > .blob-art .blob-art__shade { display: none; }
  .blob > .blob-art .blob-art__fill { filter: none; }
}
/* Everything the blob sits behind has to come forward over it. */
.blob > *:not(.blob-art) { position: relative; z-index: 1; }
/* A bare text node cannot be stacked above the absolutely positioned art,
   so every blob's label is wrapped in one of these. */
.blob__text { position: relative; z-index: 1; }

/* Seat band: the blob IS the faction colour, and the label is the derived
   readable shade, so the ink outline stays legible on every faction. */
#seat-band.blob {
  --blob-fill: var(--seat-color);
  --blob-ink: var(--seat-fg);
  /* The torn ends eat horizontal space, so the label needs to clear them. */
  padding-inline: calc(var(--s4) + 14px);
}

/* Wizard headings hug their text rather than spanning the column. */
.wizstep__title.blob,
.stats__title.blob {
  /* `fit-content` so the shape hugs the words: a banner that runs to the edge
     of the column past a two-word heading reads as a bar, not as a blob. */
  display: inline-block;
  width: fit-content;
  max-width: 100%;
  padding: 6px 24px 9px;
  margin-left: -2px;
  --blob-fill: var(--surface);
  --blob-ink: var(--text);
}
#round-pill.blob { padding: 6px 14px; --blob-fill: var(--surface-2); --blob-ink: var(--text-dim); }
.stats__metric.blob { --blob-fill: var(--surface-2); --blob-ink: var(--text-dim); }

/* The running text blurbs — reach readout, longest turn, closed-app banner,
   toast — get the same drawn box as a card's rules text. Their own background
   and border are dropped by `.blob`; padding has to come back so the words
   clear the torn edge. */
/* No padding here on purpose: each of these elements already has its own, and
   overriding it collapsed the sticky reach bar into the footer. The torn edge
   only reaches ~2px, so existing padding already clears it. */
.blob--note { --blob-fill: var(--surface); --blob-ink: var(--text-dim); }
/* It was a full-bleed bar when it had a flat background; as a drawn box it has
   to sit inside the column, or its torn edges are clipped by the screen. */
.reach.blob--note {
  /* Lifted out of the scrolling step and parked directly above the footer.
     Sticky positioning could not work here: the scroll container runs on
     underneath the footer, so "stick to my bottom" meant "stick behind it".
     `relative`, never `static`: the shape inside is absolutely positioned, and
     a static parent lets it escape and stretch to the whole screen. */
  position: relative;
  flex: 0 0 auto;
  width: 100%;
  max-width: var(--col);
  /* Two torn edges facing each other need real air between them. */
  margin: 0 auto calc(var(--s5) + 8px);
}
#toast.blob--note { --blob-fill: var(--surface-2); --blob-ink: var(--text); }
.reach.blob--note { --blob-fill: var(--surface); }
/* The reach readout keeps its state colour in the ink rather than a left rule. */
.reach.blob--note[data-level="low"] { --blob-ink: var(--warn); }
.stats__longest.blob--note { --blob-ink: var(--accent); }

/* ── Stone boxes ─────────────────────────────────────────────────────────────
   The rules panel on a Root card: a pale stone slab with a dark ink edge. The
   buttons that end a screen — Next, Start game, START/END TURN, Rematch — wear
   it, so the thing you tap looks like a component rather than a web button. */
:root {
  --stone: #C6C1B4;
  --stone-2: #B4AE9F;
  --stone-ink: #2A2317;
  --stone-text: #241D12;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --stone: #55574C;
    --stone-2: #45473D;
    --stone-ink: #E7E2CE;
    --stone-text: #F2E9D3;
  }
}
:root[data-theme="dark"] {
  --stone: #55574C;
  --stone-2: #45473D;
  --stone-ink: #E7E2CE;
  --stone-text: #F2E9D3;
}
.blob--stone {
  --blob-fill: var(--stone);
  --blob-ink: var(--stone-ink);
  color: var(--stone-text);
}
.blob--stone:disabled, .blob--stone[aria-disabled="true"] { --blob-fill: var(--stone-2); opacity: .55; }
.blob--stone:not(:disabled):active { --blob-fill: var(--stone-2); }
/* The primary button keeps its size and its accent, but the accent now lives in
   the label and a keyline rather than in a flat fill, so it reads as the same
   material as the rest. */
#btn-primary.blob--stone { color: var(--stone-text); }
#app[data-phase="TURN_EXPIRED"] #btn-primary.blob--stone {
  --blob-fill: var(--paper, #F2EDE2);
  --blob-ink: var(--danger);
  color: var(--danger);
}

/* The ink is decoration, not information: under reduced motion nothing here
   moves anyway, but a forced-colours mode should drop the art entirely and
   fall back to the plain surfaces underneath. */
@media (forced-colors: active) {
  .blob > .blob-art { display: none; }
  .blob { border: 1px solid CanvasText !important; }
}


/* The text column stops well clear of the figure. Declared last so nothing
   earlier in the file widens it back. */
.faction-tile .faction-tile__name { max-width: 52%; }
.faction-tile .faction-tile__top { max-width: 74%; }
.faction-tile .faction-tile__taken { position: relative; z-index: 2; }


/* ══════════════════════════════════════════════════════════════════════════
   19. HEAD BADGES

   Every small faction badge — the turn strip, the seat rows, the turn-order
   list, the stats table, the picker tile — shows the character's face instead
   of its two-letter shorthand. The letters are still in the DOM: they are what
   a screen reader reads, and `paintGlyph` puts them back on screen if the crop
   fails to load, so a badge is never blank.
   ══════════════════════════════════════════════════════════════════════════ */
.has-head {
  position: relative;
  overflow: hidden;
  /* The shorthand is hidden, not removed. */
  color: transparent;
  /* Faces are drawn on white paper, so a seat-coloured ground behind them
     keeps the badge reading as that faction even before the art loads. */
  background-color: var(--seat-color);
}
.glyph-head {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 42%;   /* faces sit above the middle of a square crop */
  pointer-events: none;
  user-select: none;
}
/* Sizing per badge. The seat row and the stats row already draw a coloured
   swatch around their glyph, so there the head fills that swatch instead of
   sitting inside it as a smaller square. */
.seat-row__swatch, .stats-row__swatch { position: relative; }
.seat-row__swatch .has-head,
.stats-row__swatch .has-head {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-color: transparent;
}
.faction-tile__glyph.has-head {
  display: inline-block;
  width: 30px; height: 30px;
  border-radius: 7px;
  box-shadow: inset 0 0 0 1.5px var(--seat-fg);
}
.seat-chip__glyph.has-head {
  display: block;
  width: 30px; height: 30px;
  margin: 0 auto 1px;
  border-radius: 7px;
  box-shadow: inset 0 0 0 1.5px var(--seat-fg);
}
.order-row__glyph.has-head { border-radius: 7px; }
