/* =========================================================================
 * Bastion Builder — Illuminated Codex (v1.1.0)
 *
 * Parchment + vermillion + gilt design language. Legacy token names
 * (--bg, --fg, --accent, …) remain as aliases over the Codex --bb-*
 * palette so unmodified rules in this file pick up the new theme via
 * the cascade. Per-surface restyles land in later slices.
 *
 * Spec: design/DESIGN_SPEC_codex.md
 * ========================================================================= */

/* Self-hosted Codex font families (#44). Files live under web/fonts/ as
 * woff2 — see web/fonts/SOURCES.md for the upstream Google Fonts URLs
 * and the CSP-allowance retirement this swap unlocks. Subsets cover
 * latin + latin-ext; anything outside falls back through the family
 * stack to Georgia / serif. */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 400 500;
  font-display: swap;
  src: url(fonts/co3ZmX5slCNuHLi8bLeY9MK7whWMhyjYrEtGmSq17w.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: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 400 500;
  font-display: swap;
  src: url(fonts/co3ZmX5slCNuHLi8bLeY9MK7whWMhyjYrEtImSo.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: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(fonts/co3bmX5slCNuHLi8bLeY9MK7whWMhyjYp3tKgS4.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: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(fonts/co3bmX5slCNuHLi8bLeY9MK7whWMhyjYqXtK.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: 'Cormorant SC';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/0yb5GD4kxqXBmOVLG30OGwsupTAxxTY.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: 'Cormorant SC';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/0yb5GD4kxqXBmOVLG30OGwsuqzAx.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: 'Cormorant SC';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(fonts/0ybmGD4kxqXBmOVLG30OGwsmWBMk9BfzwDM.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: 'Cormorant SC';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(fonts/0ybmGD4kxqXBmOVLG30OGwsmWBMk-hfz.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: 'Cormorant SC';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(fonts/0ybmGD4kxqXBmOVLG30OGwsmdBQk9BfzwDM.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: 'Cormorant SC';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(fonts/0ybmGD4kxqXBmOVLG30OGwsmdBQk-hfz.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: 'Cormorant SC';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/0ybmGD4kxqXBmOVLG30OGwsmEBUk9BfzwDM.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: 'Cormorant SC';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/0ybmGD4kxqXBmOVLG30OGwsmEBUk-hfz.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: italic;
  font-weight: 400 500;
  font-display: swap;
  src: url(fonts/SlGWmQSNjdsmc35JDF1K5GRweDU1ZzPJ.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 500;
  font-display: swap;
  src: url(fonts/SlGWmQSNjdsmc35JDF1K5GRweDs1Zw.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 600;
  font-display: swap;
  src: url(fonts/SlGUmQSNjdsmc35JDF1K5GR7SDktYw.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 600;
  font-display: swap;
  src: url(fonts/SlGUmQSNjdsmc35JDF1K5GR1SDk.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: 'IM Fell English';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/Ktk3ALSLW8zDe0rthJysWrnLsAzHFZOafYs.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: 'IM Fell English';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/Ktk1ALSLW8zDe0rthJysWrnLsAzHEKOY.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: 'IM Fell English SC';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/a8IENpD3CDX-4zrWfr1VY879qFF05pZ7PIIP.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: 400 700;
  font-display: swap;
  src: url(fonts/8vIJ7ww63mVu7gt7-GT7LEc.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: 400 700;
  font-display: swap;
  src: url(fonts/8vIJ7ww63mVu7gt79mT7.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 — self-hosted (#113): mono stack for Cartographer, Verdant, and Arcane.
 * Normal + italic, weight range 400–700 (variable font), latin + latin-ext only. */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(fonts/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx7cwhsk.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 700;
  font-display: swap;
  src: url(fonts/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxDcwg.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: italic;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/tDba2o-flEEny0FZhsfKu5WU4xD-IQ-PuZJJXxfpAO-LfmOZmaKjhw.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: italic;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/tDba2o-flEEny0FZhsfKu5WU4xD-IQ-PuZJJXxfpAO-LfmOXmaI.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;
}

/* =========================================================================
 * Cascade layers (#259). Later layers win over earlier ones regardless of
 * source order or selector specificity, so a rule's precedence is its layer,
 * not its position in this 10k-line file. This kills the "my override sits
 * above the base rule" bug family (e.g. base `.modal` clobbering
 * `.modal.overview-dialog` purely by coming later) and lets the compound-
 * specificity workarounds that dodged it relax.
 *
 *   reset      — element normalize / box-sizing
 *   base       — bare-element + single-primitive shell defaults (the rules a
 *                component is entitled to override): body, typography, the base
 *                `.modal` shell. NOT [data-theme] tokens — those stay in
 *                `components`; custom-property resolution is layer-independent.
 *   components — the bulk: shell, chapters, cards, forms, floorplan, and every
 *                class-specific rule incl. per-dialog overrides
 *   themes     — [data-theme] token + structural overrides (beat everything above)
 *   utilities  — helpers / responsive nudges (last word)
 *
 * There is deliberately no `dialogs` layer: the bug #259 kills is
 * defaults-clobber-overrides in general (modals, buttons, forms alike) — a
 * precedence-ROLE problem, not a dialog-FAMILY one. The fix is putting the
 * shell DEFAULT in `base`; per-dialog overrides just stay in `components` and
 * win by layer. A `dialogs` layer would name a component family inside a
 * precedence ladder — the wrong axis.
 *
 * @font-face above is intentionally left UNLAYERED — it defines font
 * resources, not cascade rules. */
@layer reset, base, components, themes, utilities;

@layer components {

:root {
  /* ---- Codex palette (§2.1) ---- */
  --bb-parchment:        #f1e5c6;
  --bb-parchment-light:  #f8efd4;
  --bb-parchment-mid:    #e8d9b3;
  --bb-parchment-dark:   #d9c592;
  --bb-parchment-deep:   #c7ad77;

  --bb-ink:              #2a1d0c;
  --bb-ink-soft:         #5a4423;
  --bb-ink-faint:        #6f5a32; /* darkened for AA — 5.3:1 on parchment (#132) */

  --bb-vermillion:       #a8281d;
  --bb-vermillion-deep:  #7a1b14;
  --bb-vermillion-light: #c64a3a;

  --bb-gilt:             #a87826;
  --bb-gilt-bright:      #d4a548;
  --bb-gilt-deep:        #7a5318;
  --bb-gilt-fill:        rgba(212,165,72,0.22);
  --bb-gilt-fill-strong: rgba(212,165,72,0.32);

  --bb-sapphire:         #2e4a7c;
  --bb-sapphire-deep:    #1f3458;
  --bb-sapphire-soft:    rgba(46,74,124,0.10);

  /* ---- Treatment tokens — promoted from hardcoded literals (#110) ---- */
  /* Codex values equal the prior hardcoded literals: zero visual change.  */
  /* Arcane values live in [data-theme="arcane"] (issue #55).              */

  /* facility fills */
  --bb-special-fill:         rgba(168,40,29,0.10);
  --bb-basic-fill:           rgba(46,74,124,0.10);
  --bb-special-fill-wash:    rgba(168,40,29,0.08);
  --bb-haunted:              #6b5a8a;
  --bb-haunted-wash:         rgba(107,90,138,0.08);
  --bb-basic-fill-stair:     rgba(46,74,124,0.20);
  --bb-stone-fill:           rgba(120,90,50,0.45);
  --bb-stone-fill-ghost:     rgba(120,90,50,0.36);
  --bb-stone-hatch:          rgba(60,40,15,0.30);
  --bb-drawbridge-fill:      rgba(196,158,102,0.94);
  --bb-corridor-fill:        rgba(80,40,10,0.18);
  /* Terrain paint (#143) — hue-for-material carve-out (§5.9): fill + contour
     line per kind. Ink-wash tints over the warm parchment. */
  --bb-terrain-water:        rgba(46,90,130,0.16);
  --bb-terrain-water-line:   rgba(34,68,104,0.60);
  --bb-terrain-road:         rgba(168,140,96,0.24);    /* packed earth — sand owns the pale dune (#236) */
  --bb-terrain-road-line:    rgba(128,102,52,0.55);
  --bb-terrain-grass:        rgba(126,150,74,0.15);
  --bb-terrain-grass-line:   rgba(92,116,48,0.55);
  --bb-terrain-sand:         rgba(222,200,132,0.20);
  --bb-terrain-sand-line:    rgba(176,150,86,0.55);
  --bb-terrain-rock:         rgba(120,116,108,0.18);
  --bb-terrain-rock-line:    rgba(78,74,66,0.58);
  --bb-terrain-marsh:        rgba(78,116,96,0.17);
  --bb-terrain-marsh-line:   rgba(46,80,64,0.60);
  /* Vehicle environment wash (#238) — FULL-BLEED behind a hull, so every fill
     sits lower in alpha than its --bb-terrain-* cousin. Sky is lighter and
     greyer-blue, water deeper and greener-blue: that fill-level separation
     survives even for a reader who never registers the marks. Ballast is
     grey-warm, never tan, so it can't be confused with the packed-earth road. */
  --bb-vehicle-water:        rgba(46,90,130,0.11);
  --bb-vehicle-water-deep:   rgba(34,68,104,0.18);
  --bb-vehicle-water-line:   rgba(34,68,104,0.50);
  --bb-vehicle-sky:          rgba(132,164,198,0.12);
  --bb-vehicle-sky-line:     rgba(96,124,158,0.42);
  --bb-vehicle-ballast:      rgba(122,112,98,0.14);
  --bb-vehicle-ballast-line: rgba(84,76,64,0.52);
  /* Conductor stones (#238) — held to a muted teal on parchment; a true Eberron
     cyan reads as a highlighter on this ground. */
  --bb-vehicle-conductor:      rgba(46,138,148,0.62);
  --bb-vehicle-conductor-glow: rgba(46,138,148,0.16);
  --bb-grid-line:            rgba(80,40,10,0.12);
  --bb-canvas-fleck:         rgba(60,40,15,0.04);

  /* shadow ramp */
  --bb-shadow:               rgba(80,40,10,0.35);
  --bb-shadow-mid:           rgba(80,40,10,0.40);
  --bb-shadow-deep:          rgba(80,40,10,0.45);
  --bb-shadow-inset-1:       rgba(80,40,10,0.10);
  --bb-shadow-inset-2:       rgba(80,40,10,0.12);

  /* inset highlights */
  --bb-inset-light:          rgba(255,250,235,0.50);
  --bb-inset-strong:         rgba(255,250,235,0.55);
  --bb-inset-faint:          rgba(255,250,235,0.15);

  /* accent soft washes */
  --bb-accent-soft:          rgba(168,40,29,0.15);
  --bb-accent-soft-hover:    rgba(168,40,29,0.22);

  /* tooltip rings */
  --bb-special-ring:         rgba(168,40,29,0.15);
  --bb-basic-ring:           rgba(46,74,124,0.15);

  /* structural / ambient */
  --bb-halo:                 var(--bb-parchment-light);
  --bb-hairline:             rgba(80,40,10,0.30);
  --bb-peek-wash:            rgba(80,40,10,0.08);
  --bb-below-wash:           rgba(80,40,10,0.06);
  --bb-vignette:             rgba(80,40,10,0.18);
  --bb-backdrop:             rgba(40,25,5,0.45);

  /* danger fill */
  --bb-danger-fill:          rgba(168,40,29,0.28);

  /* glow + muted accent */
  --bb-glow:                 transparent;
  --bb-accent-muted:         var(--bb-vermillion-deep);

  /* danger split */
  --bb-danger:               var(--bb-vermillion);
  --bb-danger-deep:          var(--bb-vermillion-deep);

  --bb-verdigris:        #4a6f3a;
  --bb-verdigris-deep:   #2f4a26;

  --bb-rule:             #8a6a35;
  --bb-rule-soft:        #b8985e;
  --bb-wall:             #3d2a14;

  /* ---- Codex type stack (§2.2) ---- */
  --bb-font-text:    'Cormorant Garamond', 'EB Garamond', Georgia, serif;
  --bb-font-display: 'IM Fell English SC', 'Cormorant SC', Georgia, serif;
  --bb-font-italic:  'IM Fell English', 'EB Garamond', Georgia, serif;
  --bb-font-mono:    ui-monospace, 'SF Mono', Consolas, monospace;
  /* Theme-INVARIANT sans for SVG map symbols (secret-door S, and future keyed
   * markers / compass letters / ruler labels). Deliberately NOT redefined per
   * theme — cartographic iconography must not re-theme — and web-safe so it
   * renders identically in the standalone SVG export. Prose SVG text (names,
   * annotations) keeps --bb-font-display. */
  --bb-font-map:     Arial, Helvetica, sans-serif;

  /* ---- Legacy aliases — keep existing rules working until per-slice restyles ---- */
  --bg:          var(--bb-parchment);
  --bg-card:     var(--bb-parchment-light);
  --bg-card-2:   var(--bb-parchment-mid);
  --bg-sunken:   var(--bb-parchment-dark);
  --bg-raised:   var(--bb-parchment-mid);

  --fg:          var(--bb-ink);
  --fg-dim:      var(--bb-ink-soft);
  --fg-faint:    var(--bb-ink-faint);

  --accent:        var(--bb-vermillion);
  --accent-hover:  var(--bb-vermillion-light);
  --accent-soft:   var(--bb-accent-soft);
  --danger:        var(--bb-danger);
  --success:       var(--bb-verdigris);
  --info:          var(--bb-sapphire-deep);

  --border:        var(--bb-rule);
  --border-soft:   var(--bb-rule-soft);
  --hairline:      var(--bb-hairline);

  --font-display: var(--bb-font-display);
  --font-ui:      var(--bb-font-text);

  /* Squared corners across the chrome (§2.4). Per-surface exceptions
   * (progress-bar fill caps, pb-icon disc) re-introduce their own radius. */
  --radius:    0;
  --radius-sm: 0;
  --radius-lg: 0;

  /* Organic-corner token — Verdant overrides this for stat plaques (§7, DESIGN_SPEC_verdant.md).
   * All other themes keep it 0 so switching is a value-only swap. */
  --bb-radius-organic: 0;

  /* ---- Elevation — soft sepia drops on parchment (§2.4) ---- */
  --shadow-card:
    0 1px 0 var(--bb-parchment),
    0 18px 28px -22px var(--bb-shadow);
  --shadow-sunken:
    inset 0 1px 0 var(--bb-shadow-inset-1),
    inset 0 2px 4px var(--bb-shadow-inset-2);
  --shadow-pop:
    0 8px 18px -6px var(--bb-shadow),
    0 0 0 1px var(--bb-rule);
}

} /* @layer components — pause for the theme layer (#259) */

/* Theme overrides live in the `themes` layer so they beat every component and
   dialog rule regardless of source order. They already won today (token blocks
   by source order over base `:root`; structural blocks by [data-theme]
   specificity) — the layer makes that precedence intentional instead of
   incidental, and future-proofs it against a later same-specificity rule. */
@layer themes {

/* ============================================================
   Arcane Spellbook theme — token overrides (§5–§6, DESIGN_SPEC_arcane.md)
   Apply by setting data-theme="arcane" on <html> or <body>.
   Every --bb-* token is overridden so the swap re-skins the
   entire app with no DOM changes.
   ============================================================ */
[data-theme="arcane"] {
  /* Ground — midnight indigo ramp (§5.1) */
  --bb-parchment:        #110e1d;
  --bb-parchment-light:  #211b34;
  --bb-parchment-mid:    #1a162a;
  --bb-parchment-dark:   #0c0a16;
  --bb-parchment-deep:   #080611;

  /* Ink — moonlight lavender (§5.2) */
  --bb-ink:        #e9e5f4;
  --bb-ink-soft:   #b2aac8;
  --bb-ink-faint:  #938bab; /* lightened for AA — ≥5.1:1 on arcane surfaces (#132) */

  /* Brand / active — arcane violet (§5.3) */
  --bb-vermillion:        #9a6df2;
  --bb-vermillion-deep:   #c2a6f7;  /* LUMINOUS — headings, wordmark, emphasis */
  --bb-vermillion-light:  #b48bf5;

  /* Secondary / progress / CTA — astral gold (§5.4) */
  --bb-gilt:         #c8a24f;
  --bb-gilt-bright:  #e6c87a;
  --bb-gilt-deep:    #a07e36;
  --bb-gilt-fill:    rgba(230,200,122,0.22);
  --bb-gilt-fill-strong: rgba(230,200,122,0.32);

  /* Basic-room ink — astral azure (§5.5) */
  --bb-sapphire:        #5a93d8;
  --bb-sapphire-deep:   #84b3e6;
  --bb-sapphire-soft:   rgba(90,147,216,0.14);

  /* Success — eldritch jade (§5.6) */
  --bb-verdigris:       #54b187;
  --bb-verdigris-deep:  #7ccaa0;

  /* Danger — crimson (§5.7 / §4 split) */
  --bb-danger:        #e2546b;
  --bb-danger-deep:   #ef7d8e;

  /* Hairlines + architecture (§5.8) */
  --bb-rule:       rgba(200,162,79,0.34);
  --bb-rule-soft:  rgba(176,166,210,0.22);
  --bb-wall:       #847d97;

  /* facility fills */
  --bb-special-fill:         rgba(154,109,242,0.16);
  --bb-basic-fill:           rgba(90,147,216,0.16);
  --bb-special-fill-wash:    rgba(154,109,242,0.10);
  --bb-haunted:              #8f86b8;
  --bb-haunted-wash:         rgba(143,134,184,0.12);
  --bb-basic-fill-stair:     rgba(90,147,216,0.24);
  --bb-stone-fill:           rgba(132,125,151,0.42);
  --bb-stone-fill-ghost:     rgba(132,125,151,0.36);
  --bb-stone-hatch:          rgba(132,125,151,0.55);
  --bb-drawbridge-fill:      rgba(178,150,112,0.92);
  --bb-corridor-fill:        rgba(176,166,210,0.12);
  /* Terrain (#143/#236): luminous re-hues, not re-alphas — low-alpha dark
     fills vanish on the midnight void. Azure water · packed-earth road ·
     jade grass/marsh · pale-gold sand · violet-grey rock (the arcane
     hard-ground family), with bright contour strokes. */
  --bb-terrain-water:        rgba(90,147,216,0.20);
  --bb-terrain-water-line:   rgba(132,179,230,0.60);
  --bb-terrain-road:         rgba(150,112,66,0.28);    /* deep packed earth — WARM by ruling: a violet road would collide with the lavender corridor wash */
  --bb-terrain-road-line:    rgba(206,170,112,0.58);   /* bright warm tan — the dim line here was a light-theme leftover (#236) */
  --bb-terrain-grass:        rgba(150,190,120,0.16);
  --bb-terrain-grass-line:   rgba(176,214,146,0.52);
  --bb-terrain-sand:         rgba(236,220,164,0.24);   /* luminous pale gold — re-HUED bright, not re-alphaed, to clear the earth road */
  --bb-terrain-sand-line:    rgba(234,218,164,0.48);
  --bb-terrain-rock:         rgba(150,146,168,0.18);   /* stays in the violet-grey family */
  --bb-terrain-rock-line:    rgba(188,184,206,0.52);
  --bb-terrain-marsh:        rgba(96,168,150,0.17);
  --bb-terrain-marsh-line:   rgba(140,200,182,0.55);
  /* Vehicle environment wash (#238) — chosen AGAINST the indigo ground, never
     inherited from the light themes (the #236 road-contour lesson). Two
     inversions are deliberate: below-the-waterline is DIMMER here, not darker
     (a darker tint on a dark ground is invisible), and the rail is the
     brightest mark on the canvas rather than the darkest. */
  --bb-vehicle-water:        rgba(96,150,214,0.16);    /* moonlit sea — lighter than the ground */
  --bb-vehicle-water-deep:   rgba(70,108,170,0.15);
  --bb-vehicle-water-line:   rgba(140,186,234,0.55);
  --bb-vehicle-sky:          rgba(150,150,206,0.13);   /* lavender night sky */
  --bb-vehicle-sky-line:     rgba(196,192,238,0.50);
  --bb-vehicle-ballast:      rgba(150,142,166,0.13);   /* violet-grey, kin to arcane rock */
  --bb-vehicle-ballast-line: rgba(190,184,206,0.48);
  /* Conductor stones (#238) — the one place this theme gets to be literal: a
     bright arcane cyan on the midnight field, exactly as the stones are drawn. */
  --bb-vehicle-conductor:      rgba(124,236,242,0.78);
  --bb-vehicle-conductor-glow: rgba(124,236,242,0.20);
  --bb-grid-line:            rgba(176,166,210,0.14);
  --bb-canvas-fleck:         rgba(206,196,255,0.07);

  /* shadow ramp */
  --bb-shadow:               rgba(0,0,0,0.55);
  --bb-shadow-mid:           rgba(0,0,0,0.65);
  --bb-shadow-deep:          rgba(0,0,0,0.72);
  --bb-shadow-inset-1:       rgba(0,0,0,0.18);
  --bb-shadow-inset-2:       rgba(0,0,0,0.22);

  /* inset highlights — faint lavender on dark */
  --bb-inset-light:          rgba(206,196,255,0.08);
  --bb-inset-strong:         rgba(206,196,255,0.10);
  --bb-inset-faint:          rgba(206,196,255,0.04);

  /* accent soft washes */
  --bb-accent-soft:          rgba(154,109,242,0.20);
  --bb-accent-soft-hover:    rgba(154,109,242,0.28);

  /* tooltip rings */
  --bb-special-ring:         rgba(154,109,242,0.20);
  --bb-basic-ring:           rgba(90,147,216,0.20);

  /* structural / ambient */
  --bb-halo:                 var(--bb-parchment);
  --bb-hairline:             rgba(176,166,210,0.22);
  --bb-peek-wash:            rgba(154,109,242,0.08);
  --bb-below-wash:           rgba(154,109,242,0.06);
  --bb-vignette:             rgba(0,0,5,0.45);
  --bb-backdrop:             rgba(4,3,10,0.62);

  /* danger fill */
  --bb-danger-fill:          rgba(226,84,107,0.28);

  /* glow + muted accent */
  --bb-glow:                 rgba(154,109,242,0.40);
  --bb-accent-muted:         #7a6490;

  /* Typography (§6) — body + italic carried by EB Garamond (§6.1):
   * IM Fell's letterpress ink-bleed reads as fringing on the dark ground. */
  --bb-font-text:    'EB Garamond', Georgia, serif;
  --bb-font-display: 'Cormorant SC', 'Cinzel', Georgia, serif;
  --bb-font-italic:  'EB Garamond', Georgia, serif;
  --bb-font-mono:    'JetBrains Mono', ui-monospace, Consolas, monospace;
}

/* ============================================================
   Arcane — structural overrides that can't be expressed as
   token swaps alone (§2a–§2c, DESIGN_SPEC_arcane.md §8)
   ============================================================ */

/* §2a — Body background: void + starfield */
[data-theme="arcane"] body {
  background-color: var(--bb-parchment);
  background-image:
    radial-gradient(ellipse at 25% 15%, rgba(154,109,242,0.07) 0%, transparent 55%),
    radial-gradient(ellipse at 75% 85%, rgba(90,147,216,0.05) 0%, transparent 50%),
    repeating-conic-gradient(var(--bb-canvas-fleck) 0% 25%, transparent 0% 50%) 0 0 / 34px 34px,
    repeating-conic-gradient(var(--bb-canvas-fleck) 0% 25%, transparent 0% 50%) 17px 17px / 46px 46px;
}

/* §2b — Banner pulse: violet glow aura at the 50% apex.
   @keyframes cannot be scoped inside a selector; declare a separate named
   animation and point the Arcane placement-banner at it.
   The Codex 50%-frame hardcodes rgba(212,165,72,0.55) (gilt); here it
   resolves through --bb-glow (violet) instead. */
@keyframes banner-pulse-arcane {
  0%, 100% {
    box-shadow:
      0 1px 0 var(--bb-inset-light) inset,
      0 6px 14px -10px var(--bb-hairline);
  }
  50% {
    box-shadow:
      0 1px 0 var(--bb-inset-light) inset,
      0 8px 22px -6px var(--bb-glow);
  }
}
[data-theme="arcane"] .placement-banner {
  animation-name: banner-pulse-arcane;
}

/* §2b — Luminous accent: glow moment #3 (§8)
   Applied only to the three sites: wordmark, bastion name drop cap,
   active mode pill. Nowhere else. */
[data-theme="arcane"] .top-bar-brand .wordmark {
  text-shadow: 0 0 18px var(--bb-glow);
}

[data-theme="arcane"] .charter-identity .bastion-name-display::first-letter,
[data-theme="arcane"] .house-name-display::first-letter {
  text-shadow: 0 0 14px var(--bb-glow);
}

[data-theme="arcane"] .grounds-command {
  background: linear-gradient(180deg,
      rgba(33,27,52,0.94),
      rgba(26,22,42,0.88));
}

/* Arcane: parchment-light flips to dark lavender under the token swap, so it
 * can't sit on gilt-deep at AA. Use plain --bb-gilt with warm-dark text —
 * mirrors the status chip + btn-primary Arcane treatment (~7.8:1). (#131) */
[data-theme="arcane"] .mode-btn.active,
[data-theme="arcane"] .mode-btn[aria-pressed="true"] {
  background: var(--bb-gilt);
  color: #2a2008;
  border-color: var(--bb-gilt);
  box-shadow:
    0 1px 0 var(--bb-inset-faint) inset,
    0 0 10px var(--bb-glow);
}

/* §2c — CTA button: gilt fill keeps dark text in Arcane.
   btn-primary uses color: var(--bb-ink) which becomes lavender (#e9e5f4)
   under the Arcane token swap. Override to warm dark so gold stays legible. */
[data-theme="arcane"] button.btn-primary {
  color: #2a2008;
}

/* Top-bar: replace hardcoded warm parchment gradient with midnight indigo. */
[data-theme="arcane"] .top-bar {
  background: linear-gradient(180deg,
      rgba(33,27,52,0.88) 0%,
      rgba(26,22,42,0.70) 100%);
}

/* Stage: replace warm parchment radial vignette with a faint violet glow. */
[data-theme="arcane"] .stage {
  background: radial-gradient(ellipse 60% 40% at 50% 0%,
      rgba(154,109,242,0.08) 0%,
      transparent 60%);
}

/* Placement banner: background uses hardcoded gilt rgba; swap to arcane violet. */
[data-theme="arcane"] .placement-banner {
  background: linear-gradient(180deg,
      rgba(154,109,242,0.14),
      rgba(154,109,242,0.08));
}

/* Tool-mode chip in Arcane: parchment-light flips to dark lavender under the
 * Arcane token swap, so it can't sit on gilt-deep at AA. Use plain --bb-gilt
 * with warm-dark text — mirrors the btn-primary Arcane treatment above
 * (~7.8:1). (#131) */
[data-theme="arcane"] .placement-banner.mode #placement-status {
  background: var(--bb-gilt);
  color: #2a2008;
}

/* Cap meter fill: dial back the gradient peak so it reads as inlaid gold, not emission. */
[data-theme="arcane"] .cap-row .meter > .fill {
  background: linear-gradient(90deg, var(--bb-gilt-deep), var(--bb-gilt));
}

/* ============================================================
   Cartographer's Chart — token overrides (§5, DESIGN_SPEC_cartographer.md)
   Apply by setting data-theme="cartographer" on <html>.
   Light theme: temperature inversion — cool Admiralty-blue brand,
   warm iron-gall sepia basic foil, no ground ramp flip.
   ============================================================ */
[data-theme="cartographer"] {
  /* Ground — cool sun-bleached linen (§5.1) */
  --bb-parchment:        #e7e2d3;
  --bb-parchment-light:  #f1ede1;
  --bb-parchment-mid:    #ddd6c2;
  --bb-parchment-dark:   #cfc6ad;
  --bb-parchment-deep:   #bdb293;

  /* Ink — chart brown (§5.2) */
  --bb-ink:              #2a2419;
  --bb-ink-soft:         #5c5142;
  --bb-ink-faint:        #6a5f48; /* locked darker than mockup — measures 4.85:1 on parchment */

  /* Brand / active — Admiralty blue (§5.3) */
  --bb-vermillion:        #1f4e6b;
  --bb-vermillion-deep:   #163a51; /* DARKER — light theme emphasis; unlike Arcane's luminous flip */
  --bb-vermillion-light:  #2f6f93;

  /* Secondary / CTA — compass-rose brass (§5.4) */
  --bb-gilt:             #b08b3e;
  --bb-gilt-bright:      #d4ad5a;
  --bb-gilt-deep:        #8a6a2a;
  --bb-gilt-fill:        rgba(212,173,90,0.22);
  --bb-gilt-fill-strong: rgba(212,173,90,0.32);

  /* Basic-room ink — iron-gall sepia (§5.5)
   * NOTE: --bb-sapphire resolves to a WARM value here — intentional role-vs-name.
   * The role is "contrasting-temperature info ink"; Cartographer inverts the
   * temperature so the foil is warm sepia. Do not "fix" this toward blue. */
  --bb-sapphire:         #7a4a24;
  --bb-sapphire-deep:    #5e3618;
  --bb-sapphire-soft:    rgba(122,74,36,0.12);

  /* Success — sea-green (§5.6) */
  --bb-verdigris:        #3f7a5a;
  --bb-verdigris-deep:   #2c5640;

  /* Danger — madder red (§5.7) */
  --bb-danger:           #a63a2a;
  --bb-danger-deep:      #7e2a1d;

  /* Hairlines + architecture (§5.8) */
  --bb-rule:             #8a7048;
  --bb-rule-soft:        #b3a079;
  --bb-wall:             #163a51; /* deep Admiralty — chart structural ink; wall≠brand by weight */

  /* facility fills (§3.1) */
  --bb-special-fill:         rgba(31,78,107,0.10);
  --bb-basic-fill:           rgba(122,74,36,0.10);   /* warm sepia — the temperature inversion */
  --bb-special-fill-wash:    rgba(31,78,107,0.08);
  --bb-haunted:              #5f5780;
  --bb-haunted-wash:         rgba(95,87,128,0.08);
  --bb-basic-fill-stair:     rgba(122,74,36,0.20);
  --bb-stone-fill:           rgba(110,90,60,0.42);
  --bb-stone-fill-ghost:     rgba(110,90,60,0.36);
  --bb-stone-hatch:          rgba(60,45,25,0.30);
  --bb-drawbridge-fill:      rgba(190,158,108,0.93);
  --bb-corridor-fill:        rgba(31,78,107,0.12);   /* charted water-channel wash */
  /* Terrain (#143/#236): water bumps the charted-wash blue to 0.16 so it
     out-reads the corridor; drafted-earth road, muted sepia-leaning greens
     and greys for the soft grounds. */
  --bb-terrain-water:        rgba(31,78,107,0.16);
  --bb-terrain-water-line:   rgba(22,58,81,0.60);
  --bb-terrain-road:         rgba(158,130,88,0.24);   /* packed earth vs the charted-wash corridor — sand owns the dune (#236) */
  --bb-terrain-road-line:    rgba(120,92,44,0.55);
  --bb-terrain-grass:        rgba(120,138,72,0.15);
  --bb-terrain-grass-line:   rgba(84,104,44,0.55);
  --bb-terrain-sand:         rgba(210,186,124,0.20);
  --bb-terrain-sand-line:    rgba(166,138,80,0.55);
  --bb-terrain-rock:         rgba(112,106,96,0.18);
  --bb-terrain-rock-line:    rgba(72,66,56,0.58);
  --bb-terrain-marsh:        rgba(70,106,86,0.17);
  --bb-terrain-marsh-line:   rgba(42,74,58,0.60);
  /* Vehicle environment wash (#238) — sits in the chart's own ink family, so a
     ship under Admiralty ruling reads as a vessel plotted on the sheet. */
  --bb-vehicle-water:        rgba(31,78,107,0.12);
  --bb-vehicle-water-deep:   rgba(22,58,81,0.19);
  --bb-vehicle-water-line:   rgba(22,58,81,0.52);
  --bb-vehicle-sky:          rgba(108,144,176,0.12);
  --bb-vehicle-sky-line:     rgba(70,102,134,0.44);
  --bb-vehicle-ballast:      rgba(116,108,96,0.15);
  --bb-vehicle-ballast-line: rgba(76,70,58,0.54);
  --bb-vehicle-conductor:      rgba(30,120,134,0.62);
  --bb-vehicle-conductor-glow: rgba(30,120,134,0.15);
  --bb-grid-line:            rgba(31,78,107,0.16);   /* Admiralty chart ruling */
  --bb-canvas-fleck:         rgba(31,78,107,0.05);   /* linen stipple / rhumb wash base */

  /* shadow ramp — light theme, warm sepia family (§3.1) */
  --bb-shadow:               rgba(70,55,35,0.35);
  --bb-shadow-mid:           rgba(70,55,35,0.40);
  --bb-shadow-deep:          rgba(70,55,35,0.45);
  --bb-shadow-inset-1:       rgba(70,55,35,0.10);
  --bb-shadow-inset-2:       rgba(70,55,35,0.12);

  /* inset highlights */
  --bb-inset-light:          rgba(255,253,246,0.50);
  --bb-inset-strong:         rgba(255,253,246,0.55);
  --bb-inset-faint:          rgba(255,253,246,0.15);

  /* accent soft washes */
  --bb-accent-soft:          rgba(31,78,107,0.15);
  --bb-accent-soft-hover:    rgba(31,78,107,0.22);

  /* tooltip rings */
  --bb-special-ring:         rgba(31,78,107,0.15);
  --bb-basic-ring:           rgba(122,74,36,0.15);

  /* structural / ambient */
  --bb-halo:                 var(--bb-parchment-light);
  --bb-hairline:             rgba(31,78,107,0.30);
  --bb-peek-wash:            rgba(31,78,107,0.08);
  --bb-below-wash:           rgba(31,78,107,0.06);
  --bb-vignette:             rgba(70,55,35,0.16);
  --bb-backdrop:             rgba(40,30,15,0.45);

  /* danger fill */
  --bb-danger-fill:          rgba(166,58,42,0.28);

  /* glow + muted accent — NO glow; Cartographer's signature is precision linework */
  --bb-glow:                 transparent;
  --bb-accent-muted:         #6b7480;

  /* Typography (§6) — body + italic carried by EB Garamond (§6.1):
   * IM Fell's antique bleed is off-voice for chart precision linework. */
  --bb-font-text:    'EB Garamond', Georgia, serif;
  --bb-font-display: 'Cinzel', 'Cormorant SC', Georgia, serif;
  --bb-font-italic:  'EB Garamond', Georgia, serif;
  --bb-font-mono:    'JetBrains Mono', ui-monospace, Consolas, monospace;
}

/* Cartographer — CTA button keeps dark text on brass fill */
[data-theme="cartographer"] button.btn-primary {
  color: #2a2008;
}

/* ============================================================
   Verdant Keep — token overrides (§5, DESIGN_SPEC_verdant.md)
   Apply by setting data-theme="verdant" on <html>.
   Mid-light theme: sage-stone ground, moss brand, verdigris-copper
   patina basic foil, spring-green success (§4 three-green resolution).
   ============================================================ */
[data-theme="verdant"] {
  /* Ground — sage-stone (§5.1) — brand-new hue, real token stress test */
  --bb-parchment:        #d6dcc4;
  --bb-parchment-light:  #e6e9d6;
  --bb-parchment-mid:    #c8d0b2;
  --bb-parchment-dark:   #b6c09c;
  --bb-parchment-deep:   #a3ae86;

  /* Ink — bark brown (§5.2) */
  --bb-ink:              #33291c;
  --bb-ink-soft:         #5e503a;
  --bb-ink-faint:        #6a5d42; /* locked darker than mockup — measures 4.57:1 on parchment */

  /* Brand / active — moss green (§5.3) */
  --bb-vermillion:        #3f5e30;
  --bb-vermillion-deep:   #2c4420; /* DARKER — light-family emphasis */
  --bb-vermillion-light:  #587b44;

  /* Secondary / CTA — gold leaf (§5.4) */
  --bb-gilt:             #a8862e;
  --bb-gilt-bright:      #cda742;
  --bb-gilt-deep:        #836726;
  --bb-gilt-fill:        rgba(205,167,66,0.22);
  --bb-gilt-fill-strong: rgba(205,167,66,0.32);

  /* Basic-room ink — verdigris-copper patina (§5.5)
   * NOTE: --bb-sapphire role = "cool contrasting info ink" — here it's cool teal
   * patina, the temperature foil to warm moss. Do not "fix" toward lapis blue.
   * Ink tokens darkened for AA contrast; wash tokens keep the brighter mockup
   * patina (§5.5 ink-vs-wash split — do not consistency-fix fills to match ink). */
  --bb-sapphire:         #336757;
  --bb-sapphire-deep:    #27554a;
  --bb-sapphire-soft:    rgba(74,140,122,0.13); /* wash: brighter mockup value — correct */

  /* Success — spring green (§5.6)
   * §4 three-green resolution: brighter + more saturated than moss brand and patina info.
   * Luminance: spring 0.26 vs moss 0.09 / patina 0.11 — separates by value in greyscale. */
  --bb-verdigris:        #5e9a35;
  --bb-verdigris-deep:   #436e25;

  /* Danger — rust (§4.1) */
  --bb-danger:           #a4502a;
  --bb-danger-deep:      #7c3a1d;

  /* Hairlines + architecture (§5.8) */
  --bb-rule:             #7d7a4a;
  --bb-rule-soft:        #a7a878;
  --bb-wall:             #3a3a28; /* dark lichened stone */

  /* facility fills (§3.1) */
  --bb-special-fill:         rgba(63,94,48,0.12);
  --bb-basic-fill:           rgba(74,140,122,0.13);  /* brighter patina wash — §5.5 ink-vs-wash split */
  --bb-special-fill-wash:    rgba(63,94,48,0.08);
  --bb-haunted:              #61567f;
  --bb-haunted-wash:         rgba(97,86,127,0.08);
  --bb-basic-fill-stair:     rgba(74,140,122,0.24);
  --bb-stone-fill:           rgba(110,100,70,0.44);
  --bb-stone-fill-ghost:     rgba(110,100,70,0.36);
  --bb-stone-hatch:          rgba(60,55,30,0.32);
  --bb-drawbridge-fill:      rgba(188,162,112,0.93);
  --bb-corridor-fill:        rgba(94,80,58,0.20);    /* earthen trodden path — warm brown, distinct from moss/patina */
  /* Terrain (#143/#236) on a green ground: water pushed bluer (away from
     teal patina); road a light packed earth (must not read as the darker
     earthen corridor); grass leans hard YELLOW and marsh hard TEAL so the
     two terrain greens separate from moss brand + spring success greens —
     the four-green resolution, held with forest retired. */
  --bb-terrain-water:        rgba(58,110,140,0.18);
  --bb-terrain-water-line:   rgba(38,84,110,0.60);
  --bb-terrain-road:         rgba(166,140,92,0.26);   /* packed earth — sand owns the pale dune (#236) */
  --bb-terrain-road-line:    rgba(120,98,54,0.55);
  --bb-terrain-grass:        rgba(110,146,68,0.16);   /* leans hard yellow — clear of water/marsh in the green field */
  --bb-terrain-grass-line:   rgba(70,104,40,0.58);
  --bb-terrain-sand:         rgba(216,192,128,0.22);
  --bb-terrain-sand-line:    rgba(172,144,84,0.55);
  --bb-terrain-rock:         rgba(116,112,102,0.19);
  --bb-terrain-rock-line:    rgba(74,70,60,0.60);
  --bb-terrain-marsh:        rgba(64,100,78,0.18);   /* leans hard teal */
  --bb-terrain-marsh-line:   rgba(36,68,52,0.62);
  /* Vehicle environment wash (#238) — this ground already reads blue-green, so
     the fills are held to the low end or the sea swallows the whole sheet. */
  --bb-vehicle-water:        rgba(58,110,140,0.12);
  --bb-vehicle-water-deep:   rgba(38,84,110,0.20);
  --bb-vehicle-water-line:   rgba(38,84,110,0.52);
  --bb-vehicle-sky:          rgba(120,152,180,0.13);
  --bb-vehicle-sky-line:     rgba(74,106,136,0.46);
  --bb-vehicle-ballast:      rgba(118,112,100,0.16);
  --bb-vehicle-ballast-line: rgba(72,68,58,0.56);
  /* Pushed cooler and lighter than the terrain marsh teal so the stones can't
     be mistaken for wet ground on this green-leaning field. */
  --bb-vehicle-conductor:      rgba(38,146,158,0.64);
  --bb-vehicle-conductor-glow: rgba(38,146,158,0.17);
  --bb-grid-line:            rgba(63,94,48,0.18);    /* moss ruling */
  --bb-canvas-fleck:         rgba(63,94,48,0.05);    /* mossy stipple */

  /* shadow ramp — mid-light theme, warm-green family (§3.1) */
  --bb-shadow:               rgba(45,52,28,0.38);
  --bb-shadow-mid:           rgba(45,52,28,0.44);
  --bb-shadow-deep:          rgba(45,52,28,0.50);
  --bb-shadow-inset-1:       rgba(45,52,28,0.12);
  --bb-shadow-inset-2:       rgba(45,52,28,0.14);

  /* inset highlights */
  --bb-inset-light:          rgba(247,250,238,0.50);
  --bb-inset-strong:         rgba(247,250,238,0.55);
  --bb-inset-faint:          rgba(247,250,238,0.15);

  /* accent soft washes */
  --bb-accent-soft:          rgba(63,94,48,0.16);
  --bb-accent-soft-hover:    rgba(63,94,48,0.24);

  /* tooltip rings */
  --bb-special-ring:         rgba(63,94,48,0.16);
  --bb-basic-ring:           rgba(74,140,122,0.16);

  /* structural / ambient */
  --bb-halo:                 var(--bb-parchment-light);
  --bb-hairline:             rgba(63,94,48,0.30);
  --bb-peek-wash:            rgba(63,94,48,0.08);
  --bb-below-wash:           rgba(63,94,48,0.06);
  --bb-vignette:             rgba(45,52,28,0.20);
  --bb-backdrop:             rgba(25,30,15,0.48);

  /* danger fill */
  --bb-danger-fill:          rgba(164,80,42,0.30);

  /* glow + muted accent — NO glow; Verdant's signature is living growth */
  --bb-glow:                 transparent;
  --bb-accent-muted:         #6a7458;

  /* Organic corner radius for stat plaques (§7, decision #2) */
  --bb-radius-organic:       2px 10px 2px 10px;

  /* Typography (§6, rev 3) — display reversed to Cormorant Garamond (the
   * mockup's softer voice; decision #3 revisited): with Arcane already on
   * Cormorant SC, the SC choice left the two themes typographically
   * identical. Not a small-caps face, so tracked-caps labels get a
   * weight floor below (§6.1 rev 3). Body + italic carried by EB Garamond:
   * IM Fell's ink-bleed reads as grime on the sage ground (§6.1). */
  --bb-font-text:    'EB Garamond', Georgia, serif;
  --bb-font-display: 'Cormorant Garamond', 'Cormorant SC', Georgia, serif;
  --bb-font-italic:  'EB Garamond', Georgia, serif;
  --bb-font-mono:    'JetBrains Mono', ui-monospace, Consolas, monospace;
}

/* ============================================================
   Verdant Keep — structural overrides (DESIGN_SPEC_verdant.md)
   Two overrides: the tracked-caps weight floor (§6.1 rev 3) and the
   organic glyph-well shape (§7): leaf corner vs. the default disc.
   All other elements stay squared — canvas, rects, modals, mode buttons.
   ============================================================ */

/* CTA button keeps dark text on gold-leaf fill */
[data-theme="verdant"] button.btn-primary {
  color: #2c2206;
}

/* Display-face weight floor (§6.1 rev 3): Cormorant Garamond is not a
 * small-caps face — at the 10–14px tracked-caps label sizes its 400 weight
 * reads anemic where the SC/engraved faces of the other themes hold the
 * line. Floor every --bb-font-display consumer ≤14px UPPERCASE that does
 * not already set 600+. Keep in sync when adding tracked-caps labels. */
[data-theme="verdant"] .top-bar .btn-ghost,
[data-theme="verdant"] .btn-handout,
[data-theme="verdant"] .cap-row .label-row,
[data-theme="verdant"] .stat-tile .stat-label,
[data-theme="verdant"] .defenses-line .defenses-label,
[data-theme="verdant"] .defenses-badge,
[data-theme="verdant"] .placement-banner #btn-cancel-placement,
[data-theme="verdant"] .grid-ctrls label,
[data-theme="verdant"] .grid-ctrls > button,
[data-theme="verdant"] .grid-ctrls .map-display-wrap > button,
[data-theme="verdant"] .edit-mode-toolbar .view-toggle,
[data-theme="verdant"] .mode-btn,
[data-theme="verdant"] .plate-eyebrow,
[data-theme="verdant"] .plate-label,
[data-theme="verdant"] .spine-add-btn,
[data-theme="verdant"] .annotation-popover-actions button,
[data-theme="verdant"] .dialog-actions .dialog-cancel,
[data-theme="verdant"] .dialog-actions .dialog-confirm,
[data-theme="verdant"] .dialog-actions-list .dialog-choice-label,
[data-theme="verdant"] .milestone-highlight-btn,
[data-theme="verdant"] .milestone-action,
[data-theme="verdant"] .help-nav a,
[data-theme="verdant"] #facility-tooltip .tt-meta {
  font-weight: 600;
}

} /* @layer themes */

@layer reset {
* { box-sizing: border-box; }
}

/* Bare-element defaults live in `base` so any specific component rule beats
   them by layer, not by being authored later (#259). These already lost to
   later component rules by source order today — the layer makes that
   intentional. Class-specific primitives (.card, button.btn-primary, …) stay
   in `components`; they are the specific rules, not the defaults. */
@layer base {

body {
  margin: 0;
  font-family: var(--bb-font-text);
  font-weight: 400;
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--bb-ink);
  padding: 20px;
  line-height: 1.45;
  min-height: 100vh;

  /* Parchment ground — copied verbatim from Illuminated Codex.html (§2.1). */
  background:
    radial-gradient(ellipse at 20% 10%, rgba(255,245,210,0.7), transparent 60%),
    radial-gradient(ellipse at 80% 90%, rgba(140,90,30,0.16), transparent 60%),
    radial-gradient(ellipse at 60% 50%, rgba(180,130,60,0.06), transparent 50%),
    repeating-linear-gradient(0deg, rgba(120,80,30,0.035) 0 1px, transparent 1px 32px),
    var(--bb-parchment);
  background-attachment: fixed;
}

/* ---- Scrollbars ---------------------------------------------------------- */
/* W3C spec (Firefox; Chrome 121+): parchment track, ink thumb.
 * All four themes inherit correct values automatically via --bb-* tokens. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--bb-ink-soft) var(--bb-parchment-dark);
}

/* Chrome / Edge / Safari: ::-webkit-scrollbar pseudo-elements */
::-webkit-scrollbar              { width: 8px; height: 8px; }
::-webkit-scrollbar-track        { background: var(--bb-parchment-dark); }
::-webkit-scrollbar-thumb        { background: var(--bb-ink-soft); }
::-webkit-scrollbar-thumb:hover  { background: var(--bb-ink); }
::-webkit-scrollbar-corner       { background: var(--bb-parchment-dark); }

/* Page-edge vignette (§2.1). z-index over chrome but below modals. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(ellipse at center, transparent 50%, var(--bb-vignette) 100%);
  z-index: 100;
}

/* ---- Typography ---------------------------------------------------------- */

h1, h2, h3 {
  margin: 0 0 10px 0;
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--fg);
}
h1 {
  font-size: 1.95rem;
  line-height: 1.05;
  color: var(--accent);            /* brand mark — the one place gold leads */
  font-weight: 700;
  letter-spacing: -0.015em;
}
h2 {
  font-size: 1.35rem;
  line-height: 1.1;
  color: var(--fg);                /* demoted from gold */
}
h3 {
  font-size: 0.84rem;
  font-family: var(--font-ui);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-dim);
  margin-top: 14px;
}

} /* @layer base */

@layer components {

/* Tabular figures everywhere a number appears in a stat or input */
.summary strong,
input[type="number"] {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum", "ss01";
}

/* ---- Cards / panels ----------------------------------------------------- */

.card {
  background:
    linear-gradient(180deg, var(--bg-card) 0%, var(--bg-card-2) 100%);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 16px 18px;
  margin-bottom: 14px;
  box-shadow: var(--shadow-card);
}

/* ---- Form fields -------------------------------------------------------- */

input, select {
  background: var(--bb-parchment-light);
  color: var(--bb-ink);
  border: 1px solid var(--bb-rule);
  border-radius: 0;
  padding: 6px 9px;
  font: inherit;
  font-family: var(--bb-font-text);
  min-width: 120px;
  box-shadow: var(--shadow-sunken);
  transition: border-color 0.12s, box-shadow 0.12s;
}
input[type="number"] {
  max-width: 80px;
  appearance: textfield;        /* Firefox: hide native spin buttons */
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  appearance: none;             /* WebKit/Blink: hide native spin buttons */
}
input[type="checkbox"] {
  min-width: 0;
  width: auto;
  padding: 0;
  margin: 0;
  border: none;
  background: none;
  box-shadow: none;
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--bb-vermillion);
  box-shadow: var(--shadow-sunken), 0 0 0 2px rgba(168,40,29,0.20);
}

/* ---- Buttons ------------------------------------------------------------ */

button {
  background: linear-gradient(180deg, var(--bb-parchment-light), var(--bb-parchment-mid));
  color: var(--bb-ink);
  border: 1px solid var(--bb-rule);
  border-radius: 0;
  padding: 6px 12px;
  font: inherit;
  font-family: var(--bb-font-text);
  font-weight: 500;
  cursor: pointer;
  box-shadow:
    0 1px 0 var(--bb-inset-light) inset,
    0 1px 2px rgba(80,40,10,0.15);
  transition: background 0.12s, border-color 0.12s, transform 0.04s;
}
button:hover {
  background: linear-gradient(180deg, var(--bb-parchment-light), var(--bb-parchment-light));
  border-color: var(--bb-ink-faint);
}
button:active { transform: translateY(1px); }
button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  box-shadow: none;
}
button:disabled:hover {
  background: linear-gradient(180deg, var(--bb-parchment-light), var(--bb-parchment-mid));
  border-color: var(--bb-rule);
}

/* Primary (gilt-filled) — handout CTA + modal confirm. Slice 7 narrows
 * this further on the modal Save/Confirm buttons. */
button.btn-primary {
  background: linear-gradient(180deg, var(--bb-gilt-bright), var(--bb-gilt));
  color: var(--bb-ink);
  border: 1px solid var(--bb-gilt-deep);
  font-weight: 600;
  box-shadow:
    0 1px 0 var(--bb-inset-strong) inset,
    0 2px 4px var(--bb-shadow);
}
button.btn-primary:hover {
  background: linear-gradient(180deg,
      color-mix(in oklch, var(--bb-gilt-bright), white 6%),
      var(--bb-gilt-bright));
  border-color: var(--bb-gilt-deep);
}

/* Danger — vermillion outline on parchment, fill on hover. */
button.btn-danger,
button.danger {
  border-color: var(--bb-vermillion);
  color: var(--bb-vermillion);
  background: transparent;
  box-shadow: none;
}
button.btn-danger:hover,
button.danger:hover {
  background: var(--bb-vermillion);
  color: var(--bb-parchment-light);
  border-color: var(--bb-vermillion);
}

@media (pointer: coarse) {
  button, select, input[type="number"] {
    min-height: 44px;
    padding-top: 9px;
    padding-bottom: 9px;
  }
  .top-bar-actions { gap: 8px; }
  /* Inline tap targets (#158): help dots, the defenses badge, dice tokens,
   * and the result-row delete crosses sit inside headings, stat lines,
   * justified prose, or a 1.25rem grid track. The 44px button rule above
   * doesn't reach them (spans / inline boxes), and padding would inflate
   * their bordered visual boxes — so grow only the *hit area* with an
   * invisible ::after overlay. .info-i and the badge already open Help on
   * tap via their data-help-section click delegate (app.js); this just makes
   * that reachable by thumb. */
  .info-i,
  .defenses-badge,
  .roll-die,
  .roll-die-chip,
  .turn-result-del,
  .turn-row-del {
    position: relative;
  }
  .info-i::after,
  .defenses-badge::after,
  .roll-die::after,
  .roll-die-chip::after {
    content: "";
    position: absolute;
    inset: -12px;
  }
  /* The delete crosses are real buttons (44px tall via the rule above) in a
   * 1.25rem grid track — widen sideways only, so the halo never steals taps
   * from the row above/below. */
  .turn-result-del::after,
  .turn-row-del::after {
    content: "";
    position: absolute;
    inset: 0 -12px;
  }
}

/* ---- Two-column ---------------------------------------------------------- */

/* ---- Entry list (Special facilities, Basic rooms) ----------------------- */

.picker-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* ---- Picker ------------------------------------------------------------- */

/* ---- Picker panels (§5.6) ----------------------------------------------- */

.picker {
  margin-top: 12px;
  padding: 12px;
  border: 1px dashed var(--bb-rule-soft);
  border-radius: 0;
  background: var(--bb-parchment-mid);
  box-shadow: var(--shadow-sunken);
}
.picker-list li {
  padding: 8px 10px;
  border-radius: 0;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  transition: background 0.1s;
}
.picker-list li:hover { background: var(--bb-parchment-light); }
.picker-list li.selected {
  background: rgba(168,40,29,0.08);
  color: var(--bb-ink);
  outline: 1px solid var(--bb-vermillion);
}
.picker-actions { margin-top: 12px; display: flex; gap: 8px; }
.picker-field-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 12px 0 4px;
}
.picker-field-row label {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--bb-ink-soft);
}

/* ---- Kind-chooser: 2-up card grid (#169) --------------------------------
   Only two abstract choices (Special facility / Basic room), so give them
   room rather than list rows — modeled on .theme-option-group's grid
   mechanics, but square corners (no .theme-tile radius) per house style. */
.kind-option-group {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin: 10px 0 4px;
}
@media (max-width: 420px) {
  .kind-option-group { grid-template-columns: 1fr; }
}
.kind-tile {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px;
  text-align: left;
  border: 1px solid var(--bb-rule);
  border-top-width: 3px;
  box-shadow: var(--shadow-card);
  background: var(--bb-parchment-light);
  cursor: pointer;
  font: inherit;
  color: var(--bb-ink);
  transition: border-color 0.12s;
}
.kind-tile.special { border-top-color: var(--bb-vermillion); }
.kind-tile.basic { border-top-color: var(--bb-sapphire); }
.kind-tile.special:hover { border-color: var(--bb-vermillion-deep); }
.kind-tile.basic:hover { border-color: var(--bb-sapphire-deep); }
.kind-tile:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: -2px; }
.kind-tile-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.kind-tile-body strong {
  font-family: var(--bb-font-display);
  font-size: 0.95rem;
  color: var(--bb-ink);
}
.kind-tile-body .desc {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--bb-ink-soft);
}
/* Cap-blocked state: dim + the swapped-in reason text carries the meaning —
   never strike through the whole card, that reads as "broken/deleted". */
.kind-tile:disabled { opacity: 0.45; cursor: not-allowed; }
.kind-tile.special:disabled:hover,
.kind-tile.basic:disabled:hover { border-color: var(--bb-rule); }

/* Special-facility picker rows (#36) — keep the .pick-* hierarchy. */
.picker-list li:has(.pick-title) {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 10px;
  border: 1px solid var(--bb-rule);
  margin-bottom: 6px;
}
.picker-list li:has(.pick-title):last-child { margin-bottom: 0; }
.picker-list .pick-body { flex: 1 1 auto; min-width: 0; }
.picker-list .pick-title {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.picker-list .pick-name {
  font-family: var(--bb-font-display);
  font-size: 1.0625rem;          /* 17px */
  color: var(--bb-ink);
}
.picker-list .pick-level {
  flex: 0 0 auto;
  font-family: var(--bb-font-display);
  font-size: 0.75rem;          /* 12px */
  letter-spacing: 0.04em;
  color: var(--bb-ink-soft);
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule-soft);
  border-radius: 0;
  padding: 1px 6px;
}
.picker-list .pick-meta,
.picker-list .pick-desc {
  margin-top: 3px;
  color: var(--bb-ink-soft);
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.75rem;          /* 12px */
}
.picker-list li:hover .pick-level {
  background: rgba(168,40,29,0.10);
  border-color: var(--bb-vermillion);
  color: var(--bb-vermillion-deep);
}
.picker-list li.selected .pick-level {
  color: var(--bb-vermillion-deep);
  background: rgba(168,40,29,0.10);
  border-color: var(--bb-vermillion);
}
.picker-list li:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: -2px; }
.picker-list li.selected .pick-meta { color: var(--bb-ink-soft); }
.picker-list .pick-prereq {
  margin-top: 3px;
  color: var(--bb-gilt-deep);
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.75rem;          /* 12px */
}
.picker-prereq-hint {
  margin: 0 0 8px;
  color: var(--bb-ink-soft);
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;            /* 13px */
}

/* Owner selector row — multi-character bastions only; hidden for single-char. */
.picker-owner-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 6px;
  margin: 0 0 8px;
}
.picker-owner-label {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--bb-ink-soft);
}
.picker-owner-btn {
  font-size: 0.8125rem;
  font-family: var(--bb-font-ui);
  font-variant-numeric: lining-nums tabular-nums;
  border: 1px solid var(--bb-rule-soft);
  border-radius: 0;
  padding: 3px 10px;
  background: none;
  cursor: pointer;
  color: var(--bb-ink-soft);
  transition: color 0.1s, border-color 0.1s, background 0.1s;
}
.picker-owner-btn:hover { color: var(--bb-ink); border-color: var(--bb-ink-soft); }
.picker-owner-btn:focus-visible { outline: 2px solid var(--bb-gilt-deep); outline-offset: 1px; }
.picker-owner-btn.active {
  background: var(--bb-parchment-light);
  color: var(--bb-gilt-deep);
  border-color: var(--bb-gilt-deep);
  font-weight: 600;
}
.picker-owner-btn.at-cap { color: var(--bb-ink-faint); text-decoration: line-through; }
.picker-owner-btn.at-cap:hover { color: var(--bb-ink-faint); border-color: var(--bb-rule-soft); }

/* Special-facility picker heading — action masthead + faint filter readout. */
.picker-heading {
  margin: 0 0 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--bb-rule-soft);
  text-transform: none;
  letter-spacing: normal;
}
.picker-heading-action {
  display: block;
  font-family: var(--bb-font-display);
  font-size: 1.0625rem;          /* 17px */
  font-weight: 600;
  color: var(--bb-ink);
}
.picker-heading-filter {
  display: block;
  margin-top: 2px;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;            /* 13px */
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--bb-ink-soft);
}
.picker-heading-filter:not(:empty)::before { content: "("; }
.picker-heading-filter:not(:empty)::after  { content: ")"; }
/* No action label above (redundant with the dialog title) — the filter is
 * the whole heading, so drop the parens and give it a touch more presence. */
.picker-heading-filter.standalone {
  margin-top: 0;
  font-size: 0.875rem;            /* 14px */
}
.picker-heading-filter.standalone::before,
.picker-heading-filter.standalone::after { content: none; }

/* ---- Misc small bits ---------------------------------------------------- */

.count {
  font-size: 0.875rem;
  color: var(--fg-dim);
  font-weight: 500;
  font-family: var(--font-ui);
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}
/* The status chip is styled per-state: idle/active leave it as plain text
   (no pill — see §6.1), and only tool-mode renders it as a pill via
   `.placement-banner.mode #placement-status` (#131 removed the legacy
   unscoped `#placement-status:not(:empty)` rule that forced a red pill on
   every state, including idle). */

/* ---- Summary bar -------------------------------------------------------- */

.summary {
  display: flex;
  flex-wrap: wrap;
  gap: 28px;
  align-items: center;
}
.summary strong {
  color: var(--accent);
  font-family: var(--font-display);
  font-size: 1.4em;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.status {
  margin-left: auto;
  color: var(--fg-dim);
  font-size: 0.84rem;
  font-style: italic;
  font-family: var(--font-display);
}
.status.success { color: var(--success); }
.status.error { color: var(--danger); }

.empty {
  color: var(--fg-dim);
  font-style: italic;
  font-family: var(--font-display);
  padding: 8px 4px;
}

/* ===== FLOORPLAN ========================================================== */

.hint {
  font-size: 0.84rem;
  color: var(--fg-dim);
  max-width: 380px;
  text-align: right;
  font-style: italic;
  font-family: var(--font-display);
  line-height: 1.4;
}

/* #floor-stack holds the single active floor's .plate-frame (#160). */
.floor-stack {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* The "drafting surface" (Codex §6.5) — warm vellum + faint sepia-ink
 * prick noise + matted frame. The only place in Codex chrome that uses
 * a textured fill rather than flat parchment. */
.floor-canvas {
  position: relative;
  background:
    /* sepia ink-prick noise */
    radial-gradient(var(--bb-canvas-fleck) 1px, transparent 1px) 0 0 / 3px 3px,
    /* warm vellum tint */
    linear-gradient(180deg,
        var(--bb-parchment-light) 0%,
        var(--bb-parchment-mid)   100%);
  border: 2px solid var(--bb-wall);
  border-radius: 0;
  /* Matted frame: a 4px parchment outline + an outer rule-soft hairline. */
  outline: 4px solid var(--bb-parchment);
  outline-offset: 0;
  padding: 10px;
  /* Matted frame always bounds the grid; placement-mode peek SVG still
   * bleeds via overflow:visible on the wrapper. Wide bastions scroll
   * horizontally via .stage. */
  overflow: visible;
  flex: 0 0 auto;
  box-shadow:
    0 0 0 5px var(--bb-rule-soft),
    inset 0 1px 0 var(--bb-inset-light),
    inset 0 0 24px var(--bb-shadow-inset-1);
}
.floor-empty-hint {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.875rem;
  color: var(--bb-ink-soft);
  text-align: center;
  padding: 12px;
}
/* Inline "Commission a facility" link in the empty-floor hint (R2.2). The hint
 * is pointer-events:none so grid clicks pass through — re-enable just the link.
 * Mirrors the .char-focus-btn vocabulary (the rule is scoped to
 * .charter-identity, so restate it here): italic, dotted underline, vermillion. */
.floor-empty-hint .char-focus-btn {
  pointer-events: auto;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: inherit;
  color: var(--bb-vermillion);
  text-decoration: underline dotted;
  text-underline-offset: 2px;
}
.floor-empty-hint .char-focus-btn:hover { color: var(--bb-vermillion-deep); }
.floor-canvas svg {
  display: block;
  user-select: none;
}

/* Four-direction grow (#43): the SVG is wrapped in a div sized to the
 * committed grid so the on-screen origin stays pinned when the left/top
 * peek bands open during placement. The SVG itself bleeds outward via
 * negative absolute positioning, painting over neighboring UI (the
 * floor-side panel, the previous floor row, etc.) for the duration of
 * the placement gesture. With all peeks at 0 the SVG sits exactly on
 * the wrapper — visually identical to the pre-#43 layout. */
.floor-svg-wrap {
  position: relative;
  overflow: visible;
  flex: 0 0 auto;
}
.floor-svg-wrap > .floor-svg {
  position: absolute;
  /* Let the grid edge-drag handles (#129) bleed ~4px past the grid rim
   * without being clipped by the SVG's default overflow:hidden. */
  overflow: visible;
}

/* ---- SVG overlays inside the floor canvas (Codex §6.5) ------------------ */

/* Below-floor shadow (§6.5.12) — faint outlines + dashed walls. */
.below-outline {
  fill: var(--bb-below-wash);
  pointer-events: none;
}
.below-outline-group { pointer-events: none; }
.below-wall {
  stroke: var(--bb-hairline);
  stroke-width: 2;
  stroke-linecap: square;
  stroke-dasharray: 4 3;
  pointer-events: none;
}
.below-defensive-wall {
  fill: var(--bb-stone-fill-ghost);
  stroke: var(--bb-hairline);
  stroke-width: 1;
  stroke-dasharray: 3 2;
  pointer-events: none;
}
/* Opening projected a floor down (#242): the ghost tint without the wall's
 * dashed border, so it reads as the passable stretch of the underlaid ring. */
.below-defensive-opening {
  fill: var(--bb-stone-fill-ghost);
  pointer-events: none;
}
/* Below-floor stair shadow (#160) — structural footprint outline, both paths.
 * Export inks it in hairline to sit with the other export below-* linework; the
 * editor underlay overrides to --bb-wall below. */
.below-stair {
  fill: none;
  stroke: var(--bb-hairline);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
  pointer-events: none;
}

/* Ghost underlay (#160, §5.9): the editor-only tracing of lower floors. The
 * export shadow pass is promoted to Revit-style halftone linework — structural
 * only (no fills / icons / names), composited ONCE via this group's opacity so
 * dashed joins never double-darken. Inner linework runs near full strength
 * (depth-1 = 1.0); this ~0.24 group carries the ghost presence. Export renders
 * the same classes WITHOUT this ancestor, so it keeps today's exact treatment. */
.floor-underlay { opacity: 0.24; }
/* Structural ink: walls, facility footprints (as perimeters, not washes), and
 * stairs all trace in --bb-wall. */
.floor-underlay .below-wall,
.floor-underlay .below-stair { stroke: var(--bb-wall); }
.floor-underlay .below-outline {
  fill: none;
  stroke: var(--bb-wall);
  stroke-width: 1.5;
}
/* Corridor wash — faint circulation fill, editor underlay only. */
.below-corridor {
  fill: var(--bb-below-wash);
  pointer-events: none;
}

/* Placement-mode peek bands (§6.5.4). */
.bg-canvas {
  fill: var(--bb-parchment-light);
}
.grid-line {
  stroke: var(--bb-grid-line);
  stroke-width: 1;
}
.grid-peek {
  fill: var(--bb-peek-wash);
  pointer-events: none;
}
.grid-edge {
  stroke: var(--bb-vermillion);
  stroke-width: 1.5;
  stroke-dasharray: 6 4;
  pointer-events: none;
  opacity: 0.85;
}

/* ---- Placed facility rects (§6.5.1) ------------------------------------- */
/* Hue-coded: special = vermillion, basic = sapphire (decision #1). */

.facility-rect {
  cursor: pointer;
  transition: filter 0.12s, fill 0.12s, stroke 0.12s, stroke-width 0.12s;
  filter: drop-shadow(0 1px 1px var(--bb-hairline));
}
@media (prefers-reduced-motion: reduce) {
  .facility-rect { transition: none; }
}
.facility-rect:hover {
  filter: brightness(1.05) drop-shadow(0 2px 3px var(--bb-shadow-deep));
}
.facility-rect.special {
  fill: var(--bb-special-fill);
  stroke: var(--bb-vermillion-deep);
  stroke-width: 2;
}
.facility-rect.basic {
  fill: var(--bb-basic-fill);
  stroke: var(--bb-sapphire-deep);
  stroke-width: 2;
}

/* ---- Walls / openings / doors (§6.5.5) ---------------------------------- */

.wall {
  stroke: var(--bb-wall);
  stroke-width: 4;
  stroke-linecap: square;
  pointer-events: none;
}
/* Interior partition tier (#178): a facility's inner fixtures — closet /
 * washroom walls and their doors, windows, arches — read as one dimmer layer
 * (internal subdivisions, not the building's shell). renderFloorSvg collects
 * the whole interior network into ONE `.interior-partitions` group, so a
 * single group opacity flattens it: overlapping strokes composite once and a
 * closet reads as an even tier. Per-element opacity was wrong — two 0.6 square
 * linecaps meeting at a join stack to ~0.84, a darker dot at every corner.
 * Walls keep a thinner line-weight as a second cue. Mirror to export-svg.js. */
.interior-partitions {
  opacity: 0.6;
}
/* Second interior cue: a thinner line than the exterior shell (the drafting
 * convention — inner partitions read lighter than load-bearing walls). Applies
 * to the interior wall AND the structural wall-jambs of interior fixtures
 * (window / arch / secret-door) so a jamb sitting inline with an interior wall
 * matches its weight. Door leaf / swing / panel glyphs keep their own intrinsic
 * weight — they're glyphs, not wall lines. Mirror any change to export-svg.js. */
.wall.interior,
.window.interior .window-wall,
.arch.interior .arch-jamb,
.door.interior .door-secret-wall {
  stroke-width: 3;
}
.opening {
  stroke: var(--bb-wall);
  stroke-width: 2;
  stroke-dasharray: 3 3;
  opacity: 0.35;
  pointer-events: none;
}
/* Arch (#175): wall jambs frame a door-length gap (supports), so two adjacent
 * arches leave a column between them — distinct from a wall-less opening. */
.arch { pointer-events: none; }
.arch-jamb {
  stroke: var(--bb-wall);
  stroke-width: 4;
  stroke-linecap: square;
  pointer-events: none;
}
/* Door glyph (#128): hinge nub + leaf + quarter swing arc. Stroke weight
 * hierarchy: wall 4 > leaf 2 > arc 1 (arc is the motion trace, leaf is the
 * door itself). Arc is solid — dash is .opening's language. */
.door {
  fill: none;
  pointer-events: none;
}
.door-hinge {
  fill: var(--bb-vermillion-deep);
  stroke: none;
}
.door-leaf {
  stroke: var(--bb-vermillion-deep);
  stroke-width: 2;
  stroke-linecap: round;
}
.door-swing {
  fill: none;
  stroke: var(--bb-vermillion);
  stroke-width: 1;
}
/* Pre-#128 panel style — the per-bastion alternative to swing arcs
 * (Settings → Floorplan → Show door swings). */
.door-panel {
  fill: var(--bb-vermillion-light);
  stroke: var(--bb-vermillion-deep);
  stroke-width: 1;
  pointer-events: none;
}
/* Hover preview of the next swing position (doors mode, never exported). */
.door-ghost {
  opacity: 0.5;
}
.door-ghost .door-leaf,
.door-ghost .door-swing {
  stroke-dasharray: 4 3;
}
/* Ghost door panel (doors-mode hover on an empty edge, arcs off): a dashed
 * vermillion outline so it reads as a door preview, distinct from the Walls
 * tool's solid hover line. */
.door-panel.door-ghost {
  fill: none;
  stroke-dasharray: 3 2;
}
/* Muted when the click will remove the door, vermillion when it will add one. */
.door-panel.door-ghost.remove {
  stroke: var(--bb-accent-muted);
}

/* Window (#175): the wall barrier stays, with two thin glaze bars parallel to
 * it (the drafting double-tick). Drawn in the wall ink — never a family hue. */
.window-wall {
  stroke: var(--bb-wall);
  stroke-width: 4;
  stroke-linecap: square;
  pointer-events: none;
}
/* Opaque pane matching the empty-grid ground, so a window reads the same over
 * any facility fill (basic/special) as it does on an external wall. */
.window-pane {
  fill: var(--bb-parchment);
  stroke: none;
  pointer-events: none;
}
/* Thin double glazing line bridging the gap — lighter than a door. */
.window-bar {
  stroke: var(--bb-wall);
  stroke-width: 1;
  pointer-events: none;
}
/* Door kinds (#175): added treatment on the vermillion door group — never a hue
 * swap, so a door can still flip to a state hue with its kind marks intact. */
.door-bolt { fill: var(--bb-vermillion-deep); stroke: none; pointer-events: none; }
.door-secret-wall {
  stroke: var(--bb-wall);
  stroke-width: 4;
  stroke-linecap: square;
  pointer-events: none;
}
/* Secret: the wall reads unbroken, marked by the classic "S" map convention —
 * an actual capital-S glyph so it reads crisply at any size. */
.door-secret-s {
  fill: var(--bb-vermillion-deep);
  /* Theme-invariant sans (see --bb-font-map) — crisp as a symbol and identical
   * in the SVG export / handout, which can't embed the codex web fonts. */
  font-family: var(--bb-font-map, Arial, Helvetica, sans-serif);
  font-weight: 500;
  font-size: 17px;
  stroke: none;
  pointer-events: none;
}
/* Portcullis: a symmetric vermillion grille dropped into a breached wall (the
 * faint-dash opening ground) — so it reads as a gate, not a decorated wall. */
.door-portcullis-bar {
  stroke: var(--bb-vermillion-deep);
  stroke-width: 1.5;
  stroke-linecap: round;
  pointer-events: none;
}
/* Ghost preview for window/arch placement (openings mode; never exported). */
.opening-ghost { opacity: 0.55; }

/* ---- Corridor cells (§6.5.6) -------------------------------------------- */

.corridor {
  fill: var(--bb-corridor-fill);
  pointer-events: none;
}
/* Corridor-family kinds (#179) — every kind shares the .corridor fill; the
 * kind reads from line-work alone (§5.9 class-by-treatment). Shared rules,
 * not per-theme: the strokes reference existing per-theme tokens. */
.corridor-walkway-hatch {
  pointer-events: none;
  stroke: var(--bb-stone-hatch);
  stroke-width: 0.75;
}
.corridor-balcony-edge {
  pointer-events: none;
  stroke: var(--bb-wall);
  stroke-width: 1;
  stroke-dasharray: 1.5 2;
  fill: none;
}
.corridor-hover {
  pointer-events: none;
  fill: rgba(80,40,10,0.12);
  stroke: var(--bb-vermillion);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
}
.corridor-hover.remove { stroke: var(--bb-accent-muted); }
/* .retype (convert-in-place) keeps the add accent — the armed kind's faint
 * signature line-work inside the outline is what marks it as a conversion. */
.corridor-hover-preview .corridor-walkway-hatch,
.corridor-hover-preview .corridor-balcony-edge { opacity: 0.55; }

/* ---- Terrain paint (#143, blob contours + six kinds #236) ---------------- */
/* Ground materials beneath everything built. Hue-for-material is the
 * sanctioned §5.9 carve-out — kind also reads from line-work plus texture,
 * never color alone. Contour matrix invariant: no two kinds share both
 * stroke-width and dash (water 1.25 solid · road 1.0 solid butt · grass
 * 0.75/1.5 3 · sand 1.0/2 2.5 · rock 1.25/5 1.5 butt · marsh 1.0/4 2). */

.terrain { pointer-events: none; }
.terrain-water { fill: var(--bb-terrain-water); }
.terrain-road  { fill: var(--bb-terrain-road); }
.terrain-grass { fill: var(--bb-terrain-grass); }
.terrain-sand  { fill: var(--bb-terrain-sand); }
.terrain-rock  { fill: var(--bb-terrain-rock); }
.terrain-marsh { fill: var(--bb-terrain-marsh); }
.terrain-edge { pointer-events: none; fill: none; }
.terrain-water-edge {
  stroke: var(--bb-terrain-water-line);
  stroke-width: 1.25;
  stroke-linecap: round;
}
.terrain-road-edge {
  stroke: var(--bb-terrain-road-line);
  stroke-width: 1;
  stroke-linecap: butt;
}
.terrain-grass-edge {
  stroke: var(--bb-terrain-grass-line);
  stroke-width: 0.75;
  stroke-dasharray: 1.5 3;
  stroke-linecap: round;
}
.terrain-sand-edge {
  stroke: var(--bb-terrain-sand-line);
  stroke-width: 1;
  stroke-dasharray: 2 2.5;
  stroke-linecap: round;
}
.terrain-rock-edge {
  stroke: var(--bb-terrain-rock-line);
  stroke-width: 1.25;
  stroke-dasharray: 5 1.5;
  stroke-linecap: butt;
}
.terrain-marsh-edge {
  stroke: var(--bb-terrain-marsh-line);
  stroke-width: 1;
  stroke-dasharray: 4 2;
  stroke-linecap: round;
}
.terrain-water-ripple {
  pointer-events: none;
  fill: none; /* tilde <path> — an unset fill would render as a black blob */
  stroke: var(--bb-terrain-water-line);
  stroke-width: 0.75;
  stroke-linecap: round;
  opacity: 0.55;
}
.terrain-road-rut {
  pointer-events: none;
  fill: none; /* bend ruts are open <path> arcs — an unset fill closes + fills them into a dark crescent at every corner (#218) */
  stroke: var(--bb-terrain-road-line);
  stroke-width: 0.6;
  stroke-dasharray: 5 3;
  stroke-linecap: round;
  opacity: 0.5;
}
.terrain-grass-tuft {
  pointer-events: none;
  stroke: var(--bb-terrain-grass-line);
  stroke-width: 0.6;
  stroke-linecap: round;
}
.terrain-sand-stipple {
  pointer-events: none;
  stroke: var(--bb-terrain-sand-line);
  stroke-width: 1.1;
  stroke-linecap: round;
  opacity: 0.45;
}
.terrain-rock-hatch {
  pointer-events: none;
  stroke: var(--bb-terrain-rock-line);
  stroke-width: 0.8;
  stroke-linecap: butt;
}
.terrain-marsh-reed {
  pointer-events: none;
  fill: none; /* wavelet <path> shares the class with the reed lines */
  stroke: var(--bb-terrain-marsh-line);
  stroke-width: 0.6;
  stroke-linecap: round;
}
.terrain-hover {
  pointer-events: none;
  fill: rgba(80,40,10,0.12);
  stroke: var(--bb-vermillion);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
}
.terrain-hover.remove { stroke: var(--bb-accent-muted); }
/* Upper-floor echo: group opacity set inline by drawBelowTerrain. */
.below-terrain { pointer-events: none; }

/* ---- Vehicle environment wash (#238) ------------------------------------ */
/* A fixed decorative backdrop derived from bastion_type alone — no tool, no
 * per-cell state. Drawn into .vehicle-hull-layer, so the grid ruling paints
 * over it and the opaque deck plate restores plain parchment inside the hull.
 * The wavelet takes slightly more weight than its terrain cousin (0.75): these
 * marks are ~9× sparser and have no competing terrain to out-shout. */
.vehicle-wash { pointer-events: none; }
.vehicle-wash-water   { fill: var(--bb-vehicle-water); }
.vehicle-wash-deep    { fill: var(--bb-vehicle-water-deep); }
.vehicle-wash-sky     { fill: var(--bb-vehicle-sky); }
.vehicle-wash-ballast { fill: var(--bb-vehicle-ballast); }
.vehicle-wash-ripple {
  pointer-events: none;
  fill: none; /* tilde <path> — an unset fill renders as a black blob */
  stroke: var(--bb-vehicle-water-line);
  stroke-width: 0.85;
  stroke-linecap: round;
  opacity: 0.6;
}
.vehicle-wash-swell {
  pointer-events: none;
  fill: none;
  stroke: var(--bb-vehicle-water-line);
  stroke-width: 0.6;
  stroke-linecap: round;
  opacity: 0.32;
}
.vehicle-wash-stratus {
  pointer-events: none;
  stroke: var(--bb-vehicle-sky-line);
  stroke-width: 0.9;
  stroke-linecap: round;
  opacity: 0.5;
}
.vehicle-wash-grit {
  pointer-events: none;
  stroke: var(--bb-vehicle-ballast-line);
  stroke-width: 1.1;
  stroke-linecap: round;
  opacity: 0.4;
}
/* Conductor stones — the enchanted line a lightning rail floats above, not a
 * pair of iron rails. The only saturated accent in any wash, and the heaviest
 * mark on the canvas: it is a made, working thing among natural texture. The
 * glow is a second, larger diamond behind each stone — the export pipeline
 * carries no filters, so the halo has to be geometry. */
.vehicle-wash-conductor-thread {
  pointer-events: none;
  stroke: var(--bb-vehicle-conductor);
  stroke-width: 0.9;
  stroke-dasharray: 1.5 3;
  opacity: 0.5;
}
.vehicle-wash-conductor-glow {
  pointer-events: none;
  fill: var(--bb-vehicle-conductor-glow);
  stroke: none;
}
.vehicle-wash-conductor {
  pointer-events: none;
  fill: var(--bb-vehicle-conductor);
  stroke: none;
}
/* Waterline collar — a fat, faint restatement of the hull path drawn UNDER the
 * opaque deck plate, so only a ~3px ring survives outside the hull. Geometry,
 * not effects: the export pipeline carries no filters or <defs>. */
.vehicle-waterline {
  pointer-events: none;
  fill: none;
  stroke: var(--bb-vehicle-water-line);
  stroke-width: 11;
  opacity: 0.35;
  stroke-linejoin: round;
  stroke-linecap: round;
}
/* Deck plate — hull interior restored to plain canvas, so a deck reads exactly
 * as a stationary floorplan does. Same fill as .bg-canvas. */
.vehicle-deck-plate {
  pointer-events: none;
  fill: var(--bb-parchment-light);
  stroke: none;
}

/* ---- Defensive walls (§6.5.7) ------------------------------------------- */
/* Warm-stone fill with a diagonal hatch — visibly heavier than the corridor
 * sepia wash at the same z-level. */

.defensive-wall {
  pointer-events: none;
  fill: var(--bb-stone-fill);
  stroke: var(--bb-wall);
  stroke-width: 1;
}
.defensive-wall-hatch {
  pointer-events: none;
  stroke: var(--bb-stone-hatch);
  stroke-width: 1;
}
.defensive-wall-hover {
  pointer-events: none;
  fill: rgba(120,90,50,0.20);
  stroke: var(--bb-vermillion);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
}
.defensive-wall-hover.remove { stroke: var(--bb-accent-muted); }

/* Opening in the defensive ring (§6.5.8, #242): gate + drawbridge share this
 * ghosted-stone tint (the upper-course colour) so the cell still reads as wall
 * — passable, but a visible continuation of the masonry, and still enclosing. */
.defensive-opening {
  pointer-events: none;
  fill: var(--bb-stone-fill-ghost);
}
/* Gate leaf: a gilt-deep bar across the passage, centred in the wall thickness
 * — the shut gate filling the opening. One mark over the ghost tint. */
.defensive-gate-bar {
  pointer-events: none;
  stroke: var(--bb-gilt-deep);
  stroke-width: 3;
  /* Butt caps, not round: the bar must end exactly on the cell edge — round
   * caps spill 1.5px into the neighbours, and butt caps let multi-cell gates
   * meet seamlessly at the shared edge. */
  stroke-linecap: butt;
  fill: none;
}
.gate-hover {
  pointer-events: none;
  fill: rgba(120,90,50,0.10);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
}
.gate-hover.to-gate { stroke: var(--bb-gilt-deep); }
.gate-hover.to-remove { stroke: var(--bb-accent-muted); }

/* Drawbridge (#242): a lowered planked deck projecting out over the moat from a
 * gate cell. Two figure/ground families — the gilt-deep frame (side rails + far
 * lip) reads chromatically as the gate it extends, the ink-soft surface (cross-
 * wise planks + splayed chains) as deck line-work. Stroke-only, per the register
 * spec; kind is treatment on the group, never a new hue (#175). */
.drawbridge-deck-fill {
  pointer-events: none;
  fill: var(--bb-drawbridge-fill);
  stroke: none;
}
/* Deck side rails: ink-soft line-work — the deck's own edge, not gate structure.
 * Only the far lip is gilt (the landing that ties back to the gate chroma). */
.drawbridge-rail {
  pointer-events: none;
  stroke: var(--bb-ink-soft);
  stroke-width: 1;
  fill: none;
}
.drawbridge-lip {
  pointer-events: none;
  stroke: var(--bb-gilt-deep);
  stroke-width: 2;
  stroke-linecap: round;
  fill: none;
}
.drawbridge-plank,
.drawbridge-chain {
  pointer-events: none;
  stroke: var(--bb-ink-soft);
  stroke-width: 1;
  fill: none;
}
.drawbridge-chain {
  stroke-linecap: round;
  stroke-dasharray: 3 2;
}
.drawbridge-pivot {
  pointer-events: none;
  fill: var(--bb-ink-soft);
  stroke: none;
}

/* ---- Stairs (§6.5.9) ---------------------------------------------------- */
/* Sapphire — the "info ink" — reads as stairs at every scale. */

.stair { pointer-events: none; }
.stair-bg     { fill: var(--bb-basic-fill-stair); }
.stair-step {
  stroke: var(--bb-sapphire-deep);
  stroke-width: 1.2;
  stroke-linecap: round;
}
.stair-arrow-shaft {
  stroke: var(--bb-sapphire-deep);
  stroke-width: 1.5;
  stroke-linecap: round;
  fill: none;
}
.stair-arrow-head {
  fill: none;
  stroke: var(--bb-sapphire-deep);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* Fill-state direction encoding (#182): hollow head = up, solid head = down.
 * Rotation-proof — the fill never flips meaning, unlike an arrow that spins
 * with the glyph. */
.stair-arrow-head.up   { fill: none; }
.stair-arrow-head.down { fill: var(--bb-sapphire-deep); }
/* Break line where the run passes through this floor's slice (#182) — the
 * drafting cut: solid below it, ghosted or ink-heavy beyond it. */
.stair-cut {
  stroke: var(--bb-sapphire-deep);
  stroke-width: 0.8;
  fill: none;
  opacity: 0.7;
  stroke-linecap: round;
}
/* UP/DN tags on the big flights (grand 2×2, ramp 1×2) — 1×1 plates have no
 * room and rely on the fill-state head alone. Typography is inline per node
 * (survives standalone export); the halo matches the annotation idiom. */
.stair-label {
  fill: var(--bb-sapphire-deep);
  text-anchor: middle;
  paint-order: stroke;
  stroke: var(--bb-halo);
  stroke-width: 2px;
  stroke-linejoin: round;
}
/* Open gable chevron marking a roof hatch (#144) — pitched-roof silhouette. */
.stair-roof {
  fill: none;
  stroke: var(--bb-sapphire-deep);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* Grand's flared ceremonial base — the bullnose that reads it as one grand
 *  flight rather than four tiled straights (#176). */
.stair-bullnose {
  fill: none;
  stroke: var(--bb-sapphire-deep);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* Ramp rails — run ALONG the slope (inverted from stair treads), so a ramp
 *  never reads as stairs (#176). */
.stair-rail {
  stroke: var(--bb-sapphire-deep);
  stroke-width: 1.5;
  stroke-linecap: round;
}
/* Spiral stair-well boundary — the round shaft the treads wind inside (#176). */
.stair-well {
  fill: none;
  stroke: var(--bb-sapphire-deep);
  stroke-width: 1.2;
}
.stair-hover {
  pointer-events: none;
  fill: var(--bb-basic-fill);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
}
.stair-hover.add    { stroke: var(--bb-vermillion); }
.stair-hover.remove { stroke: var(--bb-accent-muted); }

/* ---- Annotations (§6.5.10) ---------------------------------------------- */
/* Italic IM Fell — feels handwritten in the margins. */

/* Typography on .annotation-text is driven by inline style from annotationRecipe()
   in annotation-styles.js — one source, no cascade-override surprises. */
.annotation { pointer-events: none; }
.annotation-text {
  fill: var(--bb-ink);          /* fallback if recipe inline style is absent */
  paint-order: stroke;
  stroke: var(--bb-halo);
  stroke-width: 3px;
  stroke-linejoin: round;
}
body[data-edit-mode="annotations"] .annotation {
  pointer-events: all;
  cursor: grab;
}
/* Keyed markers are always interactive so the tooltip works in every mode */
.annotation-keyed {
  pointer-events: all;
}
.annotation-keyed:focus {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}
.annotation-hover {
  pointer-events: none;
  fill: var(--bb-special-fill-wash);
  stroke: var(--bb-vermillion);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
}

/* Hover edge highlight in walls/doors mode (§6.5.5). */
.wall-hover {
  stroke-width: 4;
  stroke-linecap: square;
  stroke-dasharray: 5 3;
  pointer-events: none;
}
.wall-hover.add    { stroke: var(--bb-vermillion);      opacity: 0.85; }
.wall-hover.remove { stroke: var(--bb-accent-muted); opacity: 0.85; }

/* ---- Ghost rect + drag handles (§6.5.3) --------------------------------- */

.ghost-group { pointer-events: none; }
.ghost-rect {
  pointer-events: none;
  stroke-dasharray: 5 3;
  stroke-width: 2;
}
.ghost-rect.ok {
  fill: rgba(74,111,58,0.32);
  stroke: var(--bb-verdigris);
}
/* Upgrade tier (#37): gilt sits between verdigris (ok) and vermillion (bad)
 * so the user reads it as "different from both — committing also bumps
 * the size and charges its cost/days". */
.ghost-rect.upgrade {
  fill: var(--bb-gilt-fill-strong);
  stroke: var(--bb-gilt);
}
.ghost-rect.bad {
  fill: var(--bb-danger-fill);
  stroke: var(--bb-danger);
}
/* Adjust (parked) phase: solid stroke distinguishes from the follow-phase
 * dashed ghost; pointer-events re-enabled so body drag and tap-to-commit work. */
.ghost-rect.parked {
  stroke-dasharray: none;
  pointer-events: all;
  cursor: grab;
}
.ghost-group.dragging .ghost-rect.parked {
  cursor: grabbing;
}

/* w×h label anchored at the ghost's top-left — mono nums with a parchment-
 * light halo so it reads against any of the three tier fills. */
.ghost-label {
  pointer-events: none;
  font-family: var(--bb-font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  paint-order: stroke fill;
  stroke: var(--bb-halo);
  stroke-opacity: 0.85;
  stroke-width: 3;
  stroke-linejoin: round;
}
.ghost-label.ok      { fill: var(--bb-verdigris-deep); }
.ghost-label.upgrade { fill: var(--bb-gilt-deep); }
.ghost-label.bad     { fill: var(--bb-accent-muted); }

/* 8 drag handles drawn inside the ghost group (§6.5.3). The group is
 * pointer-events: none; the handles re-enable hit-testing so drag begins
 * from a handle, never from the rect body. Per-handle cursors are set
 * inline by the renderer so nw/n/ne/e/se/s/sw/w-resize lands immediately. */
.ghost-handle {
  pointer-events: all;
  fill: var(--bb-parchment-light);
  stroke: var(--bb-vermillion-deep);
  stroke-width: 1.5;
  transition: transform 0.08s ease-out;
  transform-box: fill-box;
  transform-origin: center;
}
.ghost-handle:hover {
  fill: var(--bb-vermillion);
  transform: scale(1.25);
}

/* Facility icon (§6.5.2). currentColor on the SVG paths picks up the
 * per-kind color set below. The parchment-light halo (drop-shadow) keeps
 * the icon legible against the tinted facility fill. */
.facility-icon {
  pointer-events: none;
  transition: filter 0.12s;
  filter: drop-shadow(0 0 1px var(--bb-halo));
}
.facility-icon.special { color: var(--bb-vermillion-deep); }
.facility-icon.basic   { color: var(--bb-sapphire-deep); }
.facility-icon.haunted { color: var(--bb-haunted); }
.facility-icon.construction { color: var(--bb-gilt-deep); }
/* Auto-furnish props (#184) — floor markings, not chrome: secondary ink, no
 * halo/drop-shadow. Quieter than the facility icon/label, louder than grid. */
.facility-icon.furnishing { color: var(--bb-ink-soft); filter: none; }
.facility-deco.hovered .facility-icon {
  filter: drop-shadow(0 0 2px var(--bb-halo)) brightness(1.05);
}

/* Decoration layer sits above walls so icons/names/× stay legible when a
 * wall crosses through. Pointer events pass through to the rect group
 * underneath; the × button re-enables them when visible. */
.facility-deco { pointer-events: none; }

/* In-tile facility name. Bottom-aligned, only rendered when it fits — the
 * parchment-light stroke gives a halo so it stays legible against either
 * the vermillion or sapphire fill tint. */
.facility-name {
  font-family: var(--bb-font-display);
  font-weight: 500;
  /* font-size is set inline per node, scaled to CELL_PX (#160 label-scale) —
   * see placedDecorationsNode in render-svg-nodes.js. */
  letter-spacing: 0.01em;
  fill: var(--bb-ink);
  pointer-events: none;
  paint-order: stroke;
  stroke: var(--bb-halo);
  stroke-width: 3px;
  stroke-linejoin: round;
}

.x-btn {
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.1s ease;
}
/* Idle-only (#186): the × sits over the tile's top-right corner, so keeping it
 * live in a drawing tool would swallow clicks meant for stairs/notes/openings
 * there. .map-idle is set on the SVG in Select & Move mode only. */
.map-idle .facility-deco.hovered .x-btn {
  opacity: 1;
  pointer-events: auto;
}
.x-btn rect {
  fill: var(--bb-parchment-light);
  stroke: var(--bb-danger);
}
.x-btn text {
  fill: var(--bb-danger);
  font-size: 13px;
  font-family: var(--bb-font-display);
  font-weight: 600;
}
.x-btn:hover rect { fill: var(--bb-danger); }
.x-btn:hover text { fill: var(--bb-parchment-light); }
/* =========================================================================
 * Two-pane layout overrides for the Bastion Builder mockup.
 * Layered on top of styles.v2.css — only the things that change.
 * ========================================================================= */

body {
  padding: 0;
  min-height: 100vh;
}

/* ---- App shell ---------------------------------------------------------- */

.app-shell {
  display: grid;
  grid-template-rows: auto auto 1fr;   /* top-bar · chapter-nav · chapter-region */
  /* minmax(0, 1fr), not the implicit `auto` (#218). An `auto` column grows to
   * its widest item's min-content, so any content wider than the viewport —
   * the floorplan SVG, or a register row set in a wide display face — dragged
   * the column past the window and took the top bar and every panel with it,
   * right-anchoring the ⋯ menu off-screen where only a horizontal scroll could
   * reach it. The phone tier has pinned this since #105 and its comment below
   * records the same symptom at ~794px; scoping it to ≤700px left every wider
   * window exposed, which is what surfaced on Cartographer between 701 and
   * 803px (Cinzel's wider measure crosses the viewport there, so it showed up
   * on one theme while the defect was never theme-specific).
   *
   * The chain below is already built for this: .chapter carries min-width: 0
   * with overflow: auto, and .floor-stack min-width: 0 — so the column shrinks
   * to the viewport and a wide floorplan scrolls inside its own pane, which is
   * what the fixed-height two-pane shell wanted all along. */
  grid-template-columns: minmax(0, 1fr);
  height: 100vh;
  max-width: 1600px;
  margin: 0 auto;
}

/* ---- Band 1: the controls-only top bar (#121, §5.2) --------------------- */
/* App verbs ONLY — the single chrome band. No bastion content lives here
 * (crest, name, "held by", figures all moved to the Charter). Reuses the
 * manuscript double-rule band recipe. */

.top-bar {
  display: flex;
  align-items: center;
  /* Wrap is containment, not a layout (#218). Without it the bar's items shrink
   * to min-content before the container gives: the caret wraps INSIDE
   * `Bastions ▾`, the band grows 54→76px, and .app-shell's grid column still
   * blows out — the same min-content failure the phone tier documents below,
   * but sourced from the bar, so the whole DOCUMENT scrolls sideways. With the
   * label nowrap below, the worst case is a two-line band instead. The tablet
   * fold tier should mean this never fires on the default face; it is the guard
   * for wider display faces (Cartographer's Cinzel runs far wider, and
   * per-theme rules may change values, never density) and for save phrases we
   * cannot measure in advance. */
  flex-wrap: wrap;
  gap: 10px;
  margin: 0;
  padding: 9px 20px;
  background:
    linear-gradient(180deg,
        color-mix(in srgb, var(--bb-parchment-light) 55%, transparent) 0%,
        color-mix(in srgb, var(--bb-parchment-mid)   30%, transparent) 100%);
  border-bottom: 1px solid var(--bb-rule);
  box-shadow:
    0 1px 0 var(--bb-parchment),
    0 2px 0 var(--bb-rule-soft);
}

/* Far-left: app identity (NOT bastion identity) — sigil + wordmark. */
.top-bar-brand {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  /* It became a link to home in #264 (it is a <div> only on home itself, where
     it would be a self-link). Without these it draws the UA underline and link
     colour straight through the wordmark. */
  text-decoration: none;
  color: inherit;
  gap: 7px; /* shield sigil's angled flanks add optical space vs the old hexagon */
  margin-right: 4px;
}
.top-bar-brand .brand-sigil {
  display: block;
  flex: 0 0 auto;
  fill: var(--bb-vermillion);
  opacity: 0.9;
}
.top-bar-brand .wordmark {
  font-family: var(--bb-font-display);
  font-size: 1.06rem;         /* 17px */
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1;
  color: var(--bb-ink);
  white-space: nowrap;
}

.top-bar-spacer { flex: 1 1 auto; }

/* Right: app-level controls. */
.top-bar-actions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 6px;
  /* Keeps the cluster right-anchored if the wrap guard above ever puts it on
   * its own line — .top-bar-spacer only right-aligns it while they share one. */
  margin-left: auto;
}
.top-bar .btn-ghost {
  background: transparent;
  border: 1px solid transparent;
  box-shadow: none;
  color: var(--bb-ink);
  padding: 6px 14px;
  font-family: var(--bb-font-display);
  font-size: 0.84rem;         /* 13.5px */
  letter-spacing: 0.10em;
  text-transform: uppercase;
  white-space: nowrap;        /* labels are atoms; the ROW wraps, never the label */
}
.top-bar .btn-ghost:hover,
.top-bar .btn-ghost[aria-expanded="true"] {
  background: var(--bb-parchment-mid);
  border-color: var(--bb-rule);
  color: var(--bb-ink);
}
/* Bastions ▾ sits beside the mixed-case wordmark in the left cluster, so it
 * reads in sentence case — the right-cluster app actions stay uppercase. */
#btn-bastions-menu {
  text-transform: none;
  letter-spacing: 0.02em;
  font-size: 0.95rem;
}

/* The autosave note (#save-status / .save-status) carries its own styling; the
 * .top-bar-colophon class only drives the phone-tier hide (§5.2). */
.top-bar-colophon { flex: 0 0 auto; }

/* ---- Band 2: chapter tabs (#121, §5.3) ---------------------------------- */
/* The ledger-tab affordance: tabs are plaques sitting on a recessed band; the
 * active tab shares the page's fill and bridges into it (drops its bottom
 * border), so the row reads as book index tabs, not a menu. Squared (§2.5). */

.chapter-nav {
  display: flex;
  gap: 3px;
  align-items: flex-end;
  padding: 6px 20px 0;
  background: linear-gradient(180deg, var(--bb-parchment-mid), var(--bb-parchment-dark));
}

.chapter-tab {
  position: relative;
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  background: var(--bb-parchment-mid);
  border: 1px solid var(--bb-rule-soft);
  border-bottom: none;
  border-radius: 0;
  box-shadow: none;
  color: var(--bb-ink-soft);
  padding: 7px 15px;
  margin-bottom: -1px;          /* overlap the page body's top border */
  font-family: var(--bb-font-display);
  font-size: 0.84rem;           /* 13.5px */
  letter-spacing: 0.06em;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color 90ms ease, transform 90ms ease, color 90ms ease;
}
/* Roman ordinal — decorative chapter marker (§5.3 Ruling 1), oldstyle italic. */
.chapter-tab .numeral {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.78rem;
  letter-spacing: 0;
  color: var(--bb-ink-faint);
}
.chapter-tab:hover {
  background: var(--bb-parchment-light);
  color: var(--bb-ink);
  transform: translateY(-1px);
}
.chapter-tab:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}
/* Active — bridges into the page: page-fill, raised, gilt top-edge accent, no
 * bottom rule. Three non-color cues (raise + gilt edge + aria) over the fill. */
.chapter-tab[aria-selected="true"] {
  background: var(--bb-parchment-light);
  border-color: var(--bb-rule);
  border-top: 2px solid var(--bb-gilt);
  padding-top: 6px;             /* compensate the 2px gilt top border */
  color: var(--bb-ink);
  z-index: 2;
}
.chapter-tab[aria-selected="true"] .numeral { color: var(--bb-gilt-deep); }
/* The page-fill strip that masks the page's top border under the active tab —
 * "opens" the page to receive it. */
.chapter-tab[aria-selected="true"]::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  background: var(--bb-parchment-light);
}

/* Cross-tab alert dot — 8px danger pip, top-right (§5.3). Mirrored by an
 * aria-label suffix + data-tip set in render-shell.js (non-color channel). */
.chapter-tab.has-alert::before {
  content: "";
  position: absolute;
  top: 4px;
  right: 4px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--bb-danger);
}
/* Household vacancy — a gentle gilt pip, not a red alert (#121). */
.chapter-tab.has-vacancy::before {
  content: "";
  position: absolute;
  top: 4px;
  right: 4px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--bb-gilt);
}

/* ---- Band 3: the page body (#121, §5.4) --------------------------------- */
/* The open page — a bordered parchment card the active tab bridges into. */

.chapter-region {
  min-height: 0;
  overflow: hidden;
  display: grid;
  /* Pin the single cell to the region's own box. Without explicit 1fr tracks
   * the implicit row is content-sized, so a tall chapter (e.g. a long Household)
   * grows past the region and is clipped by overflow:hidden instead of
   * scrolling. 1fr forces the cell to the region height; the chapter scrolls. */
  grid-template-rows: 1fr;
  grid-template-columns: 1fr;
  background: linear-gradient(180deg, var(--bb-parchment-light), var(--bb-parchment) 120px);
  border-top: 1px solid var(--bb-rule);   /* the page frame the active tab opens */
}
.chapter {
  min-height: 0;
  min-width: 0;
  grid-area: 1 / 1;        /* stack all chapters in one cell; hidden ones drop out */
  overflow: auto;
}
/* The hidden attribute must win over the chapters' own display rules —
 * otherwise an inactive chapter would still paint. */
.chapter[hidden] { display: none; }

/* The Grounds manages its own scroll (head + temp + canvas); everything else
 * is a single scrolling column. */
#chapter-grounds { overflow: hidden; }

/* Register / Household / Chronicle are single scrolling columns. */
.chapter-register,
.chapter-household,
.chapter-chronicle {
  padding: 18px 22px 40px;
}

/* ---- Charter (chapter I, §5.4) ------------------------------------------ */
/* The cover page: a 340px identity column (seal + name + owners) beside the
 * figures grid + lore. Collapses to one column on narrow screens. */
.chapter-charter { padding: 24px 24px 48px; }
.charter-grid {
  display: grid;
  grid-template-columns: 340px 1fr;
  gap: 30px;
  max-width: 1120px;
  align-items: start;
}
@media (max-width: 980px) {
  .charter-grid { grid-template-columns: 1fr; gap: 24px; }
}

.charter-identity {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}
/* The seal — large, the page's emblem (§5.4). Overrides the rail 64px sizing. */
.charter-identity .charter-crest-wrap {
  position: relative;
  width: 104px;
  justify-self: start;
  /* Center the seal in the 340px identity column — a centered emblem over the
   * left-aligned drop-capped title is the title-page look, and it closes the
   * dead margin between the small seal and the figures column (#218). The
   * ≤980px single-column block re-anchors this, so desktop-only. */
  align-self: center;
}
.charter-identity .crest-slot {
  width: 104px;
  height: 125px;                /* 5:6 heater-shield ratio */
  padding: 2px;
}
/* Crest menu opens DOWN under the seal (overriding the rail's open-up). */
.charter-identity .crest-menu {
  top: calc(100% + 4px);
  bottom: auto;
  left: 0;
  right: auto;
}
.charter-identity .bastion-name-wrap {
  position: relative;
  width: 100%;
}
/* Unconditional illuminated drop-cap (§5.4). */
.charter-identity .bastion-name-display::first-letter {
  font-family: var(--bb-font-display);
  font-size: 56px;
  line-height: 40px;
  color: var(--bb-vermillion);
  margin-right: 6px;
  float: left;
  /* #218 — see .house-name-display::first-letter: uppercase keeps a lowercase
   * initial from reading as a dropped-to-second-line glyph. */
  text-transform: uppercase;
}
/* The Charter masthead reads larger than the shared display base — it's the
 * cover-page title, and the extra presence helps balance the centered seal
 * above it (#218). Read overlay + edit input must match. */
.charter-identity .bastion-name-display { font-size: 2rem; }  /* 32px */
.charter-identity .bastion-name {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  min-width: 0;
  font-family: var(--bb-font-display);
  font-size: 2rem;              /* 32px — matches the overlay */
  font-weight: 500;
  letter-spacing: 0.01em;
  color: transparent;           /* read state shows the overlay */
  caret-color: transparent;
  background: transparent;
  border: 1px solid transparent;
  padding: 12px 8px 0;
  box-shadow: none;
}
.charter-identity .bastion-name:focus {
  outline: none;
  color: var(--bb-ink);
  caret-color: var(--bb-vermillion);
  background: var(--bb-parchment-light);
  border-color: var(--bb-vermillion);
  box-shadow: 0 0 0 2px rgba(168,40,29,0.20);
}
.charter-identity .bastion-name-wrap:focus-within .bastion-name-display {
  visibility: hidden;
}
.charter-held-by {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.9rem;
  color: var(--bb-ink-soft);
  line-height: 1.3;
}
.charter-held-by:empty { display: none; }

/* Body column — the aggregate figures + the lore field. */
.charter-body {
  display: flex;
  flex-direction: column;
  gap: 24px;
  min-width: 0;
}
.figures {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  align-items: start;
}
/* The cap meter + the defensive-walls / since-milestone strips span both
 * columns. */
.figures .fig-cap,
.figures .fig-wide { grid-column: 1 / -1; }
/* The cap section mirrors the stat-tile manuscript chrome. */
.fig-cap {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 12px 12px;
  background: linear-gradient(180deg, var(--bb-parchment-light), transparent);
  border-top: 1px solid var(--bb-rule);
  border-bottom: 1px solid var(--bb-rule);
}
@media (max-width: 560px) {
  .figures { grid-template-columns: 1fr; }
}
.charter-lore { display: flex; flex-direction: column; }
/* Lore / The Works / Stores: a section-label sharing its row with an action.
 * The row owns the rule so the underline spans label + action, and the label
 * itself sits flush (its own border/margins zeroed). */
.charter-lore-head,
.chronicle-works-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin: 22px 0 10px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--bb-rule);
}
.charter-lore-head .section-label,
.chronicle-works-head .section-label {
  margin: 0;
  padding: 0;
  border-bottom: 0;
}
.lore-edit-btn {
  flex: 0 0 auto;
  background: transparent;
  border: 1px solid transparent;
  box-shadow: none;
  padding: 3px 10px;
  font-family: var(--bb-font-display);
  font-size: 0.74rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bb-ink-soft);
  cursor: pointer;
}
.lore-edit-btn:hover {
  background: var(--bb-parchment-mid);
  border-color: var(--bb-rule);
  color: var(--bb-ink);
}
/* Rendered read view — beautiful, auto-height prose; click anywhere to edit. */
.lore-view {
  width: 100%;
  min-height: 3.2em;
  padding: 10px 12px;
  font-family: var(--bb-font-text);
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--bb-ink);
  white-space: pre-wrap;
  word-break: break-word;
  cursor: text;
  border: 1px solid transparent;
  border-left: 2px solid var(--bb-rule-soft);
  transition: background-color 90ms ease, border-color 90ms ease;
}
.lore-view:hover {
  background: var(--bb-parchment);
  border-left-color: var(--bb-gilt);
}
.lore-view:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}
/* Empty state — italic marginalia prompt (content from data-empty). */
.lore-view:empty::before {
  content: attr(data-empty);
  font-family: var(--bb-font-italic);
  font-style: italic;
  color: var(--bb-ink-faint);
}
.charter-lore .notes-field {
  width: 100%;
  min-height: 8em;
  overflow: hidden;          /* JS auto-sizes height to content */
}

/* ---- The Grounds (chapter II, §5.9) ------------------------------------- */
/* Full-canvas floorplan editor — a flex column: head (fixed), the temporary
 * facilities panel + compliance banner, then the scrolling .stage. The rail
 * is gone. */
.chapter-grounds {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
/* The Grounds chapter-head band is RETIRED (#230 pt1): both its ▾ menus moved
 * out (Export → app top bar, Map display → canvas view-dock), so the Grounds
 * chapter leads straight into .grounds-body with no header band. The generic
 * .chapter-head still serves the Register. */

/* Compact, read-only cap echo (§5.6). */
.cap-echo {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  background: var(--bb-parchment);
  border: 1px solid var(--bb-rule-soft);
  cursor: default;
}
.cap-echo .cap-echo-label {
  font-family: var(--bb-font-display);
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--bb-ink-faint);
}
.cap-echo .cap-echo-count {
  font-family: var(--bb-font-display);
  font-variant-numeric: lining-nums tabular-nums;
  font-size: 1rem;
  /* Tight line-box so the display-font digits sit optically centered against the
   * label + meter — the inherited 1.45 line-height floated the count high. */
  line-height: 1;
  /* Keep "12 / 8" on one line — a two-digit count was wrapping at the slash
   * when the aside squeezed the flex row (the meter shrinks first instead). */
  white-space: nowrap;
  color: var(--bb-ink);
}
.cap-echo .cap-echo-level {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.78rem;
  color: var(--bb-ink-faint);
}
.cap-echo .cap-echo-meter {
  width: 72px;
  height: 6px;
  background: var(--bb-parchment-mid);
  border: 1px solid var(--bb-rule-soft);
  overflow: hidden;
}
.cap-echo .cap-echo-fill {
  height: 100%;
  width: 0;
  background: var(--bb-gilt);
}
.cap-echo .cap-echo-fill.over { background: var(--bb-danger); }

/* ⊕ Commission a facility — the add-flow entry point (Grounds head + Register
 * header). Elevated to resting gilt-fill outline per #157 steward verdict:
 * handout owns the lone gilt primary, so Commission uses the outline family
 * with gilt-fill background at rest (NOT solid gilt primary). */
.commission-btn {
  flex: 0 0 auto;
  align-self: center;
  background: var(--bb-gilt-fill);
  border: 1px solid var(--bb-gilt);
  border-radius: 0;
  box-shadow: none;
  color: var(--bb-gilt-deep);
  padding: 9px 16px;
  font-family: var(--bb-font-display);
  font-size: 0.82rem;
  letter-spacing: 0.04em;
  cursor: pointer;
  white-space: nowrap;
}
.commission-btn:hover {
  border-color: var(--bb-gilt-deep);
}
.commission-btn:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}

/* ---- The placeable-content library — right column (#161, §5.9) ---------- */
/* The canvas (hero) and the library aside sit in a flex row; each scrolls on its
 * own, so neither moves the other (§5.9, "the column scrolls independently; the
 * canvas stays put"). Two zones only in the interim — the left plate-spine,
 * single-active-floor view, and caliper seam are #160 (deferred); the canvas
 * keeps its stacked-floor scroll and the seam here is a plain --bb-rule hairline,
 * NOT the caliper. */
.grounds-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: stretch;
}
/* The scale-ruler divider between the canvas and the library (#160). It IS the
 * column divider AND an illustrative 5-ft ruler: ticks per grid row, majors
 * every five squares labelled in running feet. The <svg.seam-ruler> inside is
 * painted + kept grid-true by floorplan-view.js drawSeamRuler() — the seam sits
 * outside the zoom/scroll container, so its ticks are synced by JS to track zoom
 * (pitch) and canvas scroll (offset). Dissolves into the aside's dashed shelf-top
 * border when the library folds below the canvas on the narrow layout. */
.grounds-seam {
  flex: 0 0 34px;
  align-self: stretch;
  position: relative;
  overflow: hidden;
  background-color: var(--bb-parchment-mid);
  border-left: 1px solid var(--bb-rule-soft);
  border-right: 1px solid var(--bb-rule-soft);
}
.seam-ruler {
  position: absolute;
  inset: 0;
  display: block;
  pointer-events: none;
}
.seam-ruler .seam-tick { stroke: var(--bb-rule-soft); stroke-width: 1; }
.seam-ruler .seam-tick.major { stroke: var(--bb-ink-soft); stroke-width: 1.1; }
.seam-ruler .seam-label {
  fill: var(--bb-ink-faint);
  font-family: var(--bb-font-display);
  font-size: 8.5px;
  letter-spacing: 0.02em;
  font-variant-numeric: lining-nums tabular-nums;
}
/* The library: narrow, quiet (one parchment step down from the canvas page), its
 * own scroll. No accent fill / shadow — the canvas stays unmistakably the hero. */
.grounds-aside {
  flex: 0 0 288px;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  background: var(--bb-parchment);
  /* No padding-bottom: the sticky milestone footer (.grounds-since) sits flush
   * to the aside's bottom edge and carries its own padding; a container
   * padding-bottom would leave a parchment gap below it when scrolled to end.
   * The caliper seam (#160) is the wide-layout divider; the ≤700px shelf
   * restores a dashed top border in the mobile block. */
}
/* Commission sits at the top of the column and stays put while chips scroll under
 * it — its own parchment backing so nothing bleeds through the gilt-fill. */
.grounds-aside-head {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 14px 10px;
  background: var(--bb-parchment);
  border-bottom: 1px solid var(--bb-rule-soft);
}
.grounds-aside .commission-btn {
  display: block;
  width: 100%;
  text-align: center;
}
/* The cap-echo fills the aside-head width, beneath Commission. */
.grounds-aside-head .cap-echo { justify-content: center; }
/* A category block: a small-caps rule label (reusing the tray/cap-echo label
 * idiom) over a hint line and the chips. Generic so #143's Terrain drops in as a
 * second .library-cat with no new scaffolding. */
.library-cat {
  display: flex;
  flex-direction: column;
  padding: 12px 14px 0;
}
.library-cat-label {
  margin: 0;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--bb-rule-soft);
  font-family: var(--bb-font-display);
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--bb-ink-faint);
}
.library-cat .tray-note {
  margin: 8px 0 10px;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8rem;
  color: var(--bb-ink-faint);
}
.library-cat .tray-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  min-width: 0;
}
.library-empty {
  margin: 2px 0 0;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8rem;
  color: var(--bb-ink-faint);
}

/* Furnish mode palette (#232): occupies the library's column, shown only while
 * the Furnish tool is armed. CSS-gated on the edit mode so no JS toggling — the
 * facilities library and the prop palette are mutually exclusive right docks. */
.furnish-palette { display: none; }
body[data-edit-mode="furnishings"] .grounds-aside { display: none; }
body[data-edit-mode="furnishings"] .furnish-palette {
  /* --furnish-head-h is the sticky-head height AND the offset the sticky
     category labels pin at, so the two sticky layers share one number and
     never overlap. Tuned to the compact title+search head; nudge if the
     head font-size changes. */
  --furnish-head-h: 74px;
  flex: 0 0 300px;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  background: var(--bb-parchment);
}
.furnish-palette-head {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 12px 14px 10px;
  background: var(--bb-parchment);
  border-bottom: 1px solid var(--bb-rule-soft);
}
.furnish-palette-title {
  margin: 0 0 8px;
  font-family: var(--bb-font-display);
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--bb-ink-faint);
}
.furnish-search {
  width: 100%;
  box-sizing: border-box;
  padding: 6px 9px;
  /* --bb-font-text: --bb-font-body was never defined, so this field silently
     rendered in the UA font while every other input uses the text face (#218). */
  font-family: var(--bb-font-text);
  font-size: 0.85rem;
  color: var(--bb-ink);
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule-soft);
}
.furnish-search:focus { outline: none; border-color: var(--bb-gilt); }
/* Themed clear ✕ (#218) — same recipe as .help-search-clear. */
.furnish-search-wrap { position: relative; }
.furnish-search { padding-right: 2rem; /* room for the ✕ */ }
.furnish-search::placeholder {
  color: var(--bb-ink-faint);
  font-family: var(--bb-font-italic);
  font-style: italic;
}
.furnish-search-clear {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  padding: 0 2px;
  color: var(--bb-ink-faint);
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
}
.furnish-search-clear:hover { color: var(--bb-vermillion); }
/* Keep centering under the global button:active nudge (see .help-search-clear). */
.furnish-search-clear:active { transform: translateY(calc(-50% + 1px)); }
.furnish-search-clear:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: 1px; }
.furnish-note {
  margin: 0;
  padding: 10px 14px 0;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.78rem;
  color: var(--bb-ink-faint);
}
.furnish-palette-body { padding: 10px 14px 16px; }
.furnish-group { margin-top: 12px; }
.furnish-group:first-child { margin-top: 2px; }
.furnish-group-label {
  /* Sticky category running-head: pins just under the palette head (z 1 > this
     z 0, so labels slide under it) on an opaque bar so chips don't ghost
     through. Full-bleed via negative margins against the body's 14px padding. */
  position: sticky;
  top: var(--furnish-head-h);
  z-index: 0;
  margin: 0 -14px 8px;
  padding: 4px 14px 5px;
  background: var(--bb-parchment);
  border-bottom: 1px solid var(--bb-rule-soft);
  font-family: var(--bb-font-display);
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bb-ink-faint);
}
.furnish-chips {
  display: grid;
  /* 3-up at the 300px dock (272px content → 85.3px chips, 75.3px of caption
     box). 4-up cannot hold this register: the longest single word in the
     74-prop catalog needs ~69px against the 54px a 62px chip left, so ALL FOUR
     themes overflowed — Cartographer was just the only one whose excess
     (+26.6px on "Candelabrum") beat the 8px gap and landed on the neighbour
     instead of in the gutter. Cinzel sets the width budget: it is ~15% wider
     than any other theme's display face. Re-measure against it before adding a
     prop whose longest word exceeds 12 characters. */
  grid-template-columns: repeat(auto-fill, minmax(78px, 1fr));
  gap: 8px;
}
.furnish-chip {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 8px 4px 6px;
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule-soft);
  cursor: pointer;
  user-select: none;
}
.furnish-chip:hover { border-color: var(--bb-gilt); }
/* #218 — the palette had no focus ring at all and fell back to the UA outline.
   Mirrors .kind-tile:focus-visible, and vermillion is already this chip's
   armed colour. */
.furnish-chip:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: -2px; }
.furnish-chip.armed { border-color: var(--bb-vermillion); background: var(--bb-parchment-mid); }
.furnish-chip-art {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--bb-ink-soft);
}
.furnish-chip-art .glyph-svg { max-width: 100%; max-height: 100%; display: block; }
.furnish-chip-name {
  /* A prop name is a NAME: core spec §2.6 puts names on --bb-font-text and
     reserves --bb-font-display for the wordmark, headings and button/section
     labels. The palette's own chrome (.furnish-palette-title,
     .furnish-group-label) keeps the display face, so the panel still speaks it
     where it belongs. */
  font-family: var(--bb-font-text);
  font-size: 0.78rem;
  letter-spacing: 0;
  line-height: 1.2;
  /* Bound the caption to the chip's content box. As a flex item in a column
     chip it otherwise floors at min-content — its widest WORD — and
     align-items:center splits the excess out both sides. */
  align-self: stretch;
  min-width: 0;
  text-align: center;
  color: var(--bb-ink-soft);
}
/* R6.3: shown by furnish-mode.js when a search hides every group — otherwise
   the body just goes blank and reads as broken. */
.furnish-empty {
  margin: 0;
  padding: 4px 14px 16px;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.82rem;
  color: var(--bb-ink-faint);
}
.furnish-clear {
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  font-style: normal;
  color: var(--bb-gilt);
  text-decoration: underline;
  cursor: pointer;
}
/* Selected-prop outline in Furnish mode (#232). */
.furnish-sel {
  fill: none;
  stroke: var(--bb-vermillion);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
}

.tray-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 6px 5px 9px;
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule-soft);
  cursor: grab;
  touch-action: pan-y;     /* page scroll on touch; horizontal drag → canvas */
  user-select: none;
}
.tray-chip:hover { border-color: var(--bb-gilt); }
.tray-chip.placing { border-color: var(--bb-vermillion); background: var(--bb-parchment-mid); }
.tray-chip .tray-chip-glyph {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  display: inline-flex;
}
.tray-chip.special .tray-chip-glyph { color: var(--bb-vermillion); }
.tray-chip.basic .tray-chip-glyph { color: var(--bb-sapphire, var(--bb-ink-soft)); }
.tray-chip .tray-chip-name {
  font-family: var(--bb-font-display);
  font-size: 0.86rem;
  letter-spacing: 0.02em;
  color: var(--bb-ink);
}
.tray-chip .tray-chip-size {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.78rem;
  color: var(--bb-ink-faint);
  font-variant-numeric: lining-nums tabular-nums;
}
.tray-chip .tray-chip-remove {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: transparent;
  border: none;
  box-shadow: none;
  border-radius: 0;
  color: var(--bb-ink-faint);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
}
.tray-chip .tray-chip-remove:hover { color: var(--bb-parchment-light); background: var(--bb-danger); }

/* Off-map shelf chip (#195) — neutral, never amber (that's construction) and
   never greyed (greyed reads "disabled"; this is a deliberate, resolved
   location). Sapphire is already the app's cool/neutral accent (basic-room
   glyphs), so it reads as informational rather than a warning. */
.tray-chip.offmap { border-color: var(--bb-sapphire); }
.tray-chip.offmap:hover { border-color: var(--bb-sapphire-deep); }
/* Name + location note stack in a column so the note owns its own row (#218). */
.tray-chip .tray-chip-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  gap: 1px;
}
.tray-chip .tray-chip-text .tray-chip-name { overflow-wrap: anywhere; }
.tray-chip .tray-chip-note {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.78rem;
  color: var(--bb-ink-faint);
  /* Stacked beneath the name: wrap up to two lines instead of ellipsizing a
     short note like "in town" down to a few letters. Full text lives in the
     title for the rare long note that still overflows. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.library-cat-offmap .library-cat-label {
  color: var(--bb-sapphire-deep);
  border-bottom-color: var(--bb-sapphire);
}

/* ---- Commission dialog (#121, §5.9) ------------------------------------- */
/* Hosts the kind → special / basic pickers inside a modal. The pickers carry
 * their own list styling; give them room and drop the rail margins. */
#commission-dialog .picker {
  margin: 8px 0 0;
  padding: 0;
  background: transparent;
  border: none;
  box-shadow: none;
}
#commission-dialog .picker-list {
  max-height: 56vh;
  overflow-y: auto;
  scrollbar-gutter: stable; /* reserve the scrollbar's own lane — don't overlap the cards */
  padding-right: 4px;
}

/* The "added since milestone" strip is the facilities aside's FOOTER (#230,
 * §5.9): a sticky-bottom band mirroring the sticky-top .grounds-aside-head, so
 * the column reads as head / scrolling chips / foot — the milestone chrome
 * brackets the content instead of floating loose inside it. It sheds the
 * .defenses-line recessed-well box (that four-sided border is what read
 * "heavy/orphaned") for flush parchment with a single top hairline; the
 * .defenses-label/.defenses-stats typography inside is kept. `margin-top:auto`
 * pins it to the bottom edge when content is short; `sticky` keeps Mark
 * milestone / Show new in view while chips scroll. Scoped to beat .defenses-line
 * on specificity. Hidden until render, so the rule only shows with content. */
.grounds-aside .grounds-since {
  position: sticky;
  bottom: 0;
  z-index: 2;
  margin-top: auto;
  background: var(--bb-parchment);
  border: 0;
  border-top: 1px solid var(--bb-rule-soft);
  border-radius: 0;
  padding: 10px 14px 14px;
}
/* The footer is always ~288px wide on desktop (not just on the ≤700px phone
 * shelf), so it needs the two-row stacking unconditionally (#230 footer
 * follow-up): label+name / delta full-width / the two pills + help ⓘ on one row.
 * The delta takes its own row; the pills then pack together and the ⓘ right-
 * aligns (margin-left:auto) onto that same row so it never orphans to a line of
 * its own — pills stay left-grouped, ⓘ trails at the right edge. */
.grounds-aside .grounds-since { gap: 6px; }
.grounds-aside .grounds-since .defenses-stats { flex-basis: 100%; }
/* Shrink the pills so "Show new" + "Mark milestone" actually fit one row in the
 * ~260px aside — drop the 8.5em min-width, tighten tracking/padding, and let the
 * flex gap (6px) do the spacing (not the base 8px margin). The help ⓘ then
 * right-aligns onto that same row so it never orphans to a line of its own. */
.grounds-aside .grounds-since .milestone-highlight-btn { min-width: 0; }
.grounds-aside .grounds-since .milestone-highlight-btn,
.grounds-aside .grounds-since .milestone-mark-btn {
  margin-left: 0;
  letter-spacing: 0.05em;
  padding: 3px 6px;
}
.grounds-aside .grounds-since .info-i { margin-left: auto; }

/* Compliance is evicted from the Grounds (ruling 3) — the notice now lives on
 * the Charter (character-side) and the Register (facility-side). Both get a
 * little breathing room from the surrounding chapter content. */
.charter-compliance { margin: 4px 0 14px; }
.register-compliance { margin: 0 0 16px; }
/* Two-homes hint line: which fix this surface offers (§5.9(5)). Quiet, italic,
 * sits under the issue list. */
.compliance-hint {
  margin: 8px 0 0;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  line-height: 1.35;
  color: var(--bb-ink-faint);
}

/* The canvas pane — the hero column inside .grounds-body; scrolls on its own.
 * min-width:0 lets it shrink (not the fixed-width aside) when space is tight. */
.chapter-grounds .stage {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  /* #230 pt1 restructure: .stage is the positioning context + the fullscreen
     target, NOT the scroller. It stays overflow:visible so the floating dock can
     be an absolute HUD that truly floats (sticky-only fell into flow when the map
     fit the screen) and so Map display's upward popover isn't clipped. The inner
     .stage-scroll now does the scrolling. */
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: visible;
}
/* The scroll container (#230 pt1): takes over .stage's former scrolling role;
   holds the sticky command rail + the spine/canvas. */
.chapter-grounds .stage-scroll {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  overflow: auto;
  padding: 0 22px 24px;
}

/* Chapter header band (§ codex standard): the bridged tab already names the
 * chapter, so there is no title row — the .chapter-head band is an optional
 * utility strip carrying a caption/counts (.chapter-head-text) plus right-aligned
 * chrome (.chapter-head-aside: cap-echo / view controls). Only chapters with such
 * payload render it (Grounds, Register); the rest lead with a .section-label. The
 * Charter is exempt entirely — its bastion-name masthead is the title. */
.chapter-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 16px;
  margin: 0 0 18px;
}
.chapter-head-text { min-width: 0; }
.chapter-head-aside {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.chapter-sub {
  margin: 0 0 14px;
  color: var(--bb-ink-faint);
  font-size: 0.86rem;
}
.chapter-head .chapter-sub { margin: 0; }

/* Tier 2: subsection heading (extracted from the old .reg-group). A small-caps
 * ruled label, used even when a chapter has a single section. Action-bearing
 * heads (Lore, The Works, Stores) wrap it in a flex row that owns the rule. */
.section-label {
  font-family: var(--bb-font-display);
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bb-ink-soft);
  margin: 22px 0 10px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--bb-rule);
}
/* Register head has no title — center the counts caption against the cap-echo box
 * so the band reads as a ledger-tally row: running count left, capacity gauge
 * right. Trim the band's bottom margin and pull the first section label up under
 * it — the default 22px section gap left a needless void between the gauge and
 * the Special Facilities heading. */
.chapter-register .chapter-head { align-items: center; margin-bottom: 8px; }
#register-view > .section-label:first-child { margin-top: 8px; }
/* Chapters whose head band is gone (Household, Chronicle) lead with their first
 * heading; pull it up tight under the tabs so all four content chapters start at
 * the same offset. Household leads with a bare .section-label; Chronicle's
 * sections each lead with a ruled .chronicle-works-head (a row shared with
 * Charter's lore head — scope the pull to .chronicle-col so Charter is untouched).
 * Applying to ALL chronicle heads is reorder-safe (the .is-live lift reshuffles
 * which section is first); the grid row-gap owns the between-section spacing. */
.chapter-household > .section-label:first-child { margin-top: 4px; }
.chronicle-col .chronicle-works-head { margin-top: 4px; }

/* Chronicle — Of the Founding (lore) over The Works (milestones). */
.chronicle-col {
  display: flex;
  flex-direction: column;
  gap: 26px;
  max-width: 760px;
}
/* The codex spread: at desk width the Chronicle opens as a centered two-page
 * spread — Turns + Works on the main page, Stores on the facing rail, a
 * hairline spine mid-gutter. 1200px is the floor at which the main column
 * keeps its full 760px measure (760 + 56 gutter + 340 rail + chapter
 * padding); below it, the single column above is untouched. Grid
 * auto-placement respects `order`, so the .is-live lift keeps flipping Turns
 * above Works inside the main column. */
@media (min-width: 1200px) {
  /* Left-aligned like the other chapters (not centered) — a centered spread put
   * the Chronicle's left edge well inboard of every other page's 22px gutter. */
  .chronicle-col {
    max-width: 1216px;
  }
  .chronicle-col {
    display: grid;
    grid-template-columns: minmax(0, 760px) minmax(340px, 400px);
    column-gap: 56px;
    row-gap: 26px;
    align-items: start;
  }
  #chronicle-works,
  #chronicle-turns { grid-column: 1; }
  #chronicle-stores {
    grid-column: 2;
    grid-row: 1 / span 2;
    position: relative;
  }
  /* The spine — a pseudo-element so the rail's track width stays honest, and
   * the line ends where Stores ends (no orphan rule over blank parchment). */
  #chronicle-stores::before {
    content: "";
    position: absolute;
    left: -28px;
    top: 0;
    bottom: 0;
    width: 1px;
    background: var(--bb-rule-soft);
  }
  /* A shared snapshot (#254) brings only Stores into the Chronicle — Works and
     Turns are history and stay home. The spread's main page would then be
     blank, leaving Stores stranded in a 340px rail beside 760px of nothing,
     with a spine ruled down the middle of a one-page book. So the viewer keeps
     the single column the narrow tier already uses: same section, same measure
     as every other chapter, no facing page to face. */
  body.viewer .chronicle-col {
    display: flex;
    flex-direction: column;
    max-width: 760px;
    /* Every property the spread set has to be undone, not just `display`:
       `align-items: start` is a grid instruction here but a FLEX one there,
       where it means "don't stretch across" — the sections silently shrank to
       their content (590px of a 760px column) and sat ragged. */
    align-items: stretch;
  }
  body.viewer #chronicle-stores::before { content: none; }
}
.chronicle-section { display: flex; flex-direction: column; }
/* The chapter's closing boundary in a read-only snapshot (#272): what a
   snapshot is, not an error and not an invitation. Same fact register as
   .annotation-legend-hint / .fig-note, with a dotted rule above so it reads as
   a closing note rather than a stranded paragraph.

   No `grid-column` needed, and that's worth stating because it looks like an
   omission: this note only ever exists in a viewer, and `body.viewer
   .chronicle-col` above already collapses the ≥1200px codex spread back to a
   single flex column — so there is no facing rail for it to fall into. */
.chronicle-viewer-note {
  margin: 0;
  padding-top: 14px;
  border-top: 1px dotted var(--bb-rule-soft);
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8rem;
  color: var(--bb-ink-soft);
}
/* Section descriptor under a Chronicle section-label (faint, like .chapter-sub). */
.chronicle-section-sub {
  margin: 0 0 12px;
  color: var(--bb-ink-faint);
  font-size: 0.86rem;
}

/* Autosave indicator — italic marginalia in the manuscript voice (§3.4).
 * The autosave note in the top bar's left cluster (§5.2) — quiet italic,
 * centered on the band's optical line. */
.save-status {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--bb-ink-soft);
  display: inline-flex;
  align-items: center;
  line-height: 1;
  white-space: nowrap;
  user-select: none;
}
.save-status.pending { color: var(--bb-vermillion); }
/* Quota-full autosave failure — persistent, not a transient toast state. */
.save-status.save-error { color: var(--bb-vermillion); font-weight: 600; }

/* ---- Bastions ▾ menu (Codex §4) ----------------------------------------- */

.bastions-menu-wrap { position: relative; }

.bastions-menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  margin: 0;
  padding: 4px 0;
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule);
  border-radius: 0;
  box-shadow: 0 8px 18px -6px var(--bb-shadow);
  min-width: 340px;
  max-height: 60vh;
  overflow-y: auto;
  z-index: 40;
}

/* Saves menu only (the class is shared with the Grounds "Map display ▾" panel,
 * which stays a single scrolling block): the bastion list scrolls inside the
 * menu while New / Load sample / the sync line stay put. Before this they were
 * "pinned" only by being last in the DOM, so a library of seventeen pushed them
 * out of reach — the one thing a switcher must never do. */
#bastions-menu {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
#bastions-menu .bastions-menu-list {
  min-height: 0;
  overflow-y: auto;
  scrollbar-gutter: stable;
  padding-right: 12px;
}
#bastions-menu .bastions-menu-foot { flex: 0 0 auto; }

.bastions-menu-row {
  display: grid;
  /* Just the name now — clone / export / share / delete moved to home in #264,
     and the four `auto` tracks they used to occupy went with them. */
  grid-template-columns: 1fr;
  align-items: stretch;
  background: transparent;
}
.bastions-menu-row:hover { background: var(--bb-parchment-mid); }

.bastions-menu-row .bm-load {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  text-align: left;
  padding: 8px 14px;
  background: transparent;
  border: none;
  border-radius: 0;
  color: var(--bb-ink);
  font-family: var(--bb-font-display);
  font-size: 0.875rem;        /* 14px */
  font-weight: 500;
  letter-spacing: 0.02em;
  cursor: pointer;
  box-shadow: none;
  min-width: 0;
}
.bastions-menu-row.current .bm-load { color: var(--bb-vermillion-deep); }

/* The verb buttons left this menu in #264 — duplicate / export / share / delete
 * live on home now, which is the authoritative library surface. What remains
 * here is the switcher: the current-dot, the name, and the row itself. The
 * .bm-action family below is therefore scoped to .holding ALONE; it is no
 * longer a shared vocabulary, which is also the end of the drift that produced
 * two spellings of Duplicate and two share glyphs. */
.bastions-menu-row .bm-dot,
.holding .bm-dot {
  display: inline-block;
  width: 0.8em;
  color: var(--bb-vermillion);
  font-size: 0.8em;
  flex: 0 0 auto;
}
/* Wraps rather than ellipsising (#264): home promises names are never
 * truncated, and this switcher is one click away. Menu rows growing to fit a
 * long name is correct behaviour for a menu. */
.bastions-menu-row .bm-name {
  white-space: normal;
  overflow-wrap: anywhere;
}

.holding .bm-action {
  background: transparent;
  border: none;
  border-radius: 0;
  color: var(--bb-ink-soft);
  width: 28px;
  height: 28px;
  padding: 0;
  font-size: 0.95rem;
  cursor: pointer;
  box-shadow: none;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color 80ms ease, background-color 80ms ease;
}
/* Every verb hovers, including share and the overflow — on home the buttons are
 * always visible and hover is the primary affordance, so an inert one reads as
 * broken. (.bm-share had no hover rule at all while it lived in the dropdown;
 * the gap was invisible there and obvious here.) */
.holding .bm-clone:hover,
.holding .bm-export:hover,
.holding .bm-share:hover,
.holding .bm-more:hover { color: var(--bb-vermillion); background: transparent; }
.holding .bm-delete:hover {
  color: var(--bb-parchment-light);
  background: var(--bb-danger);
}

/* Pinned bottom rows (`+ New bastion`, `↥ Import from file`). The divider
 * above the pinned section is drawn once, on the first .new row only —
 * the second .new row suppresses it via the adjacent-sibling selector. */
.bastions-menu-row.new {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 14px;
  background: transparent;
  border: none;
  border-top: 1px solid var(--bb-rule-soft);
  border-radius: 0;
  color: var(--bb-ink-soft);
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.875rem;        /* 14px */
  cursor: pointer;
  box-shadow: none;
}
.bastions-menu-row.new + .bastions-menu-row.new { border-top: 0; }
.bastions-menu-row.new:hover {
  background: var(--bb-parchment-mid);
  color: var(--bb-ink);
}

/* Cloud-sync status colophon (#216) — a read-only footer line in the Bastions
 * menu when cloud sync is connected. Not a menuitem: no hover, no focus, no click. */
.bastions-menu-colophon {
  padding: 8px 14px;
  border-top: 1px solid var(--bb-rule-soft);
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--bb-ink-faint);
  cursor: default;
}

/* Context menu (#190) — right-click a facility rect or tray chip in Select &
 * Move mode. Reuses the .bastions-menu recipe verbatim: parchment fill, square
 * corners, ink rule, hover fill, no glyph discs. position:fixed; JS sets
 * left/top from the cursor (createContextMenu in dom.js). */
.context-menu {
  position: fixed;
  z-index: 60;
  margin: 0;
  padding: 4px 0;
  list-style: none;
  min-width: 220px;
  max-width: min(320px, 90vw);
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule);
  border-radius: 0;
  box-shadow: 0 8px 18px -6px var(--bb-shadow);
}
.context-menu .ctx-li { margin: 0; }
.context-menu .ctx-li.ctx-divider {
  border-top: 1px solid var(--bb-rule-soft);
  margin-top: 4px;
  padding-top: 4px;
}
.context-menu .ctx-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  text-align: left;
  padding: 8px 14px;
  background: transparent;
  border: none;
  border-radius: 0;
  color: var(--bb-ink);
  font-family: var(--bb-font-display);
  font-size: 0.875rem;        /* 14px */
  font-weight: 500;
  letter-spacing: 0.02em;
  cursor: pointer;
  box-shadow: none;
}
.context-menu .ctx-item:hover { background: var(--bb-parchment-mid); }
.context-menu .ctx-item:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: -2px;
}
.context-menu .ctx-label { flex: 1 1 auto; }
.context-menu .ctx-lead { flex: 0 0 auto; width: 1.1em; text-align: center; }
.context-menu .ctx-trail { flex: 0 0 auto; color: var(--bb-ink-soft); }
.context-menu .ctx-shortcut {
  flex: 0 0 auto;
  margin-left: auto;
  padding: 1px 5px;
  font-size: 0.72rem;
  color: var(--bb-ink-faint);
  border: 1px solid var(--bb-rule-soft);
  border-radius: 3px;
  font-variant-numeric: tabular-nums;
}
.context-menu .ctx-danger:hover .ctx-shortcut { color: inherit; border-color: currentColor; }
.context-menu .ctx-item[aria-disabled="true"] { color: var(--bb-ink-faint); cursor: default; }
.context-menu .ctx-item[aria-disabled="true"]:hover { background: transparent; }
/* Destructive Remove row — mirrors .bm-delete:hover. */
.context-menu .ctx-danger:hover { color: var(--bb-parchment-light); background: var(--bb-danger); }
.context-menu .ctx-danger:hover .ctx-lead,
.context-menu .ctx-danger:hover .ctx-trail { color: inherit; }
/* Cross-nav anchor row ("Manage in Register →") — italic, never a danger. */
.context-menu .ctx-anchor {
  color: var(--bb-ink-soft);
  font-family: var(--bb-font-italic);
  font-style: italic;
}
.context-menu .ctx-anchor:hover { color: var(--bb-ink); }

/* Right-clicked tray chip echo, mirroring .facility-group.rail-lit on the map. */
.tray-chip.ctx-target { outline: 2px solid var(--bb-vermillion); outline-offset: 1px; }

/* Register card echo (#201) — same vocabulary at card scale, marking which
 * card the open context menu acts on. */
.reg-card.ctx-target { outline: 2px solid var(--bb-vermillion); outline-offset: 2px; }

/* Overflow "⋯ More" menu (#105) — phones only. Hidden on desktop; the phone
 * tier (bottom of file) flips .more-menu-wrap to inline-block. Reuses the
 * .bastions-menu popover chrome, anchored to the right so it doesn't overflow
 * the viewport near the screen edge. Rows are plain text items (no .bm-load
 * child), so they carry their own padding/typography. */
.more-menu-wrap { position: relative; display: none; }
#more-menu {
  left: auto;
  right: 0;
  min-width: 220px;
  max-width: calc(100vw - 24px);
}
#more-menu .bastions-menu-row {
  display: block;
  width: 100%;
  text-align: left;
  padding: 12px 14px;         /* ~44px touch row, matching the pointer:coarse min */
  background: transparent;
  border: none;
  border-radius: 0;
  color: var(--bb-ink);
  font-family: var(--bb-font-display);
  font-size: 0.875rem;        /* 14px — parity with the Bastions/History rows */
  font-weight: 500;
  letter-spacing: 0.02em;
  cursor: pointer;
  box-shadow: none;
}
#more-menu .bastions-menu-row:hover { background: var(--bb-parchment-mid); }
/* Visible ring for the row auto-focused on open (rows are full-bleed → inset). */
#more-menu .bastions-menu-row:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: -2px;
}

/* ---- Open handout — top-bar primary CTA (§3.5) -------------------------- */

.btn-handout {
  white-space: nowrap;
  font-family: var(--bb-font-display);
  font-size: 0.84rem;       /* 13.5px — unified with .btn-ghost */
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 6px 14px;
}
/* btn-primary itself is restyled to the gilt CTA treatment in §7 modal work
 * later; for slice 2 we re-anchor it here on the handout button only. */
button.btn-primary.btn-handout {
  background: linear-gradient(180deg, var(--bb-gilt-bright), var(--bb-gilt));
  color: var(--bb-ink);
  border: 1px solid var(--bb-gilt-deep);
  font-weight: 600;
  box-shadow:
    0 1px 0 var(--bb-inset-strong) inset,
    0 2px 4px var(--bb-shadow);
}
button.btn-primary.btn-handout:hover {
  background: linear-gradient(180deg,
      color-mix(in oklch, var(--bb-gilt-bright), white 6%),
      var(--bb-gilt-bright));
  border-color: var(--bb-gilt-deep);
}

/* ---- Bastions ▾ hover-preview tooltip (#53 / §4.3) ---------------------- */

.bastion-preview {
  position: fixed;
  pointer-events: none;
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule);
  border-radius: 0;
  box-shadow: 0 8px 18px -6px var(--bb-shadow);
  padding: 6px;
  z-index: 50;
  max-width: 360px;
  max-height: 70vh;
  overflow: hidden;
}
.bastion-preview svg { display: block; pointer-events: none; }

/* Compliance notice — persistent non-compliant state banner (#83). */
.compliance-notice {
  padding: 10px 12px;
  background: var(--bb-parchment-mid);
  border: 1px solid var(--bb-gilt-deep);
  border-left: 3px solid var(--bb-gilt);
  box-shadow: var(--shadow-sunken);
  gap: 0;
}
.compliance-head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
}
.compliance-head h3 {
  margin: 0;
  font-family: var(--bb-font-display);
  font-size: 0.875rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--bb-gilt-deep);
}
.compliance-glyph {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: var(--bb-gilt-deep);
}
.compliance-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.compliance-item {
  font-family: var(--bb-font-italic);
  font-size: 0.875rem;
  line-height: 1.35;
  color: var(--bb-ink-soft);
}
.compliance-item::before {
  content: "— ";
  color: var(--bb-gilt-deep);
}

/* Decorative fleuron under the identity block (§5.1). */
.fleuron {
  display: block;
  margin: 6px auto 0;
  fill: var(--bb-gilt);
  opacity: 0.85;
}

/* ---- Bastion name overlay (Charter, §5.4) ------------------------------- */
/* The read-mode overlay is in-flow and sizes the wrapper (it may wrap to a
 * second line); the edit input stretches over it absolutely. On :focus-within
 * the overlay hides — keeping its layout box — and the input takes over at the
 * same display size. (The `.charter-identity`-scoped wrap / input / drop-cap
 * rules live in the Charter block; this is the shared display base.) */
.bastion-name-display {
  display: block;
  font-family: var(--bb-font-display);
  font-size: 1.75rem;           /* 28px — matches the input's edit-state size */
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--bb-ink);
  line-height: 1.15;
  padding: 12px 9px 0 9px;      /* 9px x-inset = input's 8px padding + 1px border */
  min-height: 56px;             /* room for the 56px drop cap on one-liners */
  max-height: calc(12px + 2.3em); /* clip past two lines (no clamp — it would kill ::first-letter) */
  overflow: hidden;
  pointer-events: none;
  user-select: none;
}
/* ---- Heraldry crest slot (#123) ----------------------------------------- */

.crest-wrap { position: relative; width: 64px; justify-self: end; }
.crest-slot {
  display: block;
  width: 64px;
  height: 77px;                 /* 5:6 — heater-shield ratio, seal-sized for the corner */
  padding: 2px;
  background: transparent;
  border: 1px solid transparent; /* holds the box — real art sits frameless on the parchment */
  border-radius: 0;
  cursor: pointer;
  box-shadow: none;
}
.crest-slot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
/* Hover/focus affordance is an outline halo, never a frame — a solid border
 * around full-color art read as a harsh black box on parchment. */
.crest-slot:not(.is-placeholder):hover,
.crest-slot:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}
/* Empty state reads as a fillable ghost slot, not real art: dashed border
 * is the non-color cue, the washed-out placeholder the second. */
.crest-slot.is-placeholder { border: 1px dashed var(--bb-rule-soft); }
.crest-slot.is-placeholder img { opacity: 0.45; filter: grayscale(1); }
.crest-slot.is-placeholder:hover,
.crest-slot.is-placeholder:focus-visible { border-color: var(--bb-vermillion); }
.crest-slot.is-placeholder:hover img,
.crest-slot.is-placeholder:focus-visible img { opacity: 1; }

/* Upload/Replace/Remove menu — .bastions-menu chrome, narrow plain rows.
 * From a bottom-right host the shared down-left placement would overflow the
 * section bottom and the rail's right edge — open up + right-aligned. */
.crest-menu {
  min-width: 180px;
  top: auto;
  bottom: calc(100% + 4px);
  left: auto;
  right: 0;
}

.crest-menu .bastions-menu-row {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 14px;
  background: transparent;
  border: none;
  border-radius: 0;
  color: var(--bb-ink);
  font-family: var(--bb-font-display);
  font-size: 0.875rem;
  cursor: pointer;
  box-shadow: none;
}
.crest-menu .crest-remove { border-top: 1px solid var(--bb-rule-soft); }
.crest-menu .crest-remove:hover {
  color: var(--bb-parchment-light);
  background: var(--bb-danger);
}

/* ---- Cap meter (special facilities) ------------------------------------ */

/* ---- Cap meter (§5.4) --------------------------------------------------- */

.cap-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 12px;
  background: var(--bb-parchment-mid);
  border: 1px solid var(--bb-rule-soft);
  box-shadow: var(--shadow-sunken);
}
.cap-row .label-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-family: var(--bb-font-display);
  font-size: 0.75rem;         /* 12px */
  letter-spacing: 0.20em;
  text-transform: uppercase;
  color: var(--bb-ink-soft);
}
.cap-row .count-big {
  font-family: var(--bb-font-display);
  font-size: 1.0625rem;         /* 17px */
  color: var(--bb-vermillion-deep);
  letter-spacing: 0;
  text-transform: none;
  font-variant-numeric: lining-nums tabular-nums;
  white-space: nowrap;
}
.cap-row .meter {
  position: relative;
  height: 8px;
  background: var(--bb-parchment-dark);
  border: 1px solid var(--bb-rule-soft);
  border-radius: 0;
  overflow: hidden;
}
.cap-row .meter > .fill {
  position: absolute;
  inset: 0 auto 0 0;
  background: linear-gradient(90deg, var(--bb-gilt), var(--bb-gilt-bright));
  border-radius: 1px;           /* §2.4 exception: progress-bar fill caps */
}
.cap-row .next-up {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.84rem;         /* 13.5px */
  color: var(--bb-ink-soft);
}
.cap-row .next-up .lvl {
  color: var(--bb-vermillion-deep);
  font-family: var(--bb-font-display);
  font-style: normal;
  font-weight: 600;
  font-variant-numeric: lining-nums tabular-nums;
}
#cap-per-char:not(:empty) {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--bb-rule-soft);
}
.cap-char-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 0.84rem;          /* 13.5px */
}
.cap-char-name {
  flex: 1 1 auto;
  color: var(--bb-ink-soft);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.cap-char-count {
  flex: 0 0 auto;
  color: var(--bb-vermillion-deep);
  font-family: var(--bb-font-display);
  font-variant-numeric: lining-nums tabular-nums;
  white-space: nowrap;
  padding-left: 8px;
}

/* ---- Facility icon discs (§5.5) — shared by Stores cards, the Commission
   dialog candidate rows, and the kind-chooser tiles (#169). ---------------- */

.glyph {
  width: 28px; height: 28px;
  display: grid; place-items: center;
  border: 1px solid var(--bb-rule);
  border-radius: 50%;            /* §2.4 exception: tinted icon disc */
  background: var(--bb-parchment-light);
  flex: 0 0 auto;
}
.glyph .glyph-svg {
  width: 18px; height: 18px;
  display: block;
  fill: currentColor;
}
.glyph.special {
  background: var(--bb-special-fill);
  color: var(--bb-vermillion-deep);
  border-color: rgba(168,40,29,0.35);
}
.glyph.basic {
  background: var(--bb-basic-fill);
  color: var(--bb-sapphire-deep);
  border-color: rgba(46,74,124,0.35);
}

/* Section headers (§5.4–5.7) ---------------------------------------------- */

.section-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}
/* h2 shares the recipe (#264): home's outline is h1 → h2 per bay, and the
 * alternative was skipping a level or restyling. A selector widening, not a
 * second typography. */
.section-head h3,
.section-head h2 {
  margin: 0;
  font-family: var(--bb-font-display);
  font-size: 0.75rem;          /* 12px */
  font-weight: 600;
  letter-spacing: 0.20em;
  text-transform: uppercase;
  color: var(--bb-ink);
}

/* ---- Stats pair + defenses line (§5.2 / §5.3) --------------------------- */

.stat-tile {
  padding: 10px 12px 12px;
  background: linear-gradient(180deg, var(--bb-parchment-light), transparent);
  border-top: 1px solid var(--bb-rule);
  border-bottom: 1px solid var(--bb-rule);
  border-left: none;
  border-right: none;
  border-radius: var(--bb-radius-organic);
  box-shadow: none;
}
.stat-tile .stat-label {
  font-family: var(--bb-font-display);
  font-size: 0.75rem;          /* 12px */
  letter-spacing: 0.20em;
  text-transform: uppercase;
  color: var(--bb-ink-faint);
  margin-bottom: 4px;
}
.stat-tile .stat-val {
  font-family: var(--bb-font-display);
  font-size: 1.6875rem;          /* 27px */
  color: var(--bb-vermillion-deep);
  letter-spacing: 0;
  font-variant-numeric: lining-nums tabular-nums;
  line-height: 1;
}
.stat-tile .stat-val .unit {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.875rem;           /* 14px */
  color: var(--bb-ink-soft);
  margin-left: 4px;
  font-weight: 400;
}
/* Humanised build time ("~2 years, 3 months") — a phrase, not a figure, so it
   drops the small-caps display face for readable body type that wraps cleanly
   in the half-width tile. Class lands on the inner value span (#sum-seq), so
   this is a descendant selector, not a chained one. */
.stat-tile .stat-val .stat-val-prose {
  display: inline-block;
  font-family: var(--bb-font-text);
  font-size: 1rem;               /* 16px */
  font-weight: 600;
  color: var(--bb-vermillion-deep);
  letter-spacing: 0.005em;
  line-height: 1.15;
  font-variant-numeric: lining-nums;   /* keep figures, drop tabular */
  text-wrap: balance;
}
.stat-tile .stat-sub {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.84rem;          /* 13.5px */
  color: var(--bb-ink-soft);
  margin-top: 6px;
}
.info-i {
  display: inline-grid;
  place-items: center;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: var(--bb-parchment-mid);
  border: 1px solid var(--bb-rule-soft);
  color: var(--bb-ink-faint);
  font-size: 0.6875rem;
  font-family: var(--bb-font-display);
  font-style: normal;
  vertical-align: 2px;
  margin-left: 4px;
  cursor: pointer;
}
.info-i:hover {
  color: var(--bb-ink);
  border-color: var(--bb-rule);
}

/* Anything carrying a Help deep-link (#117) is clickable — info-i glyphs,
 * the enclosed-defenses badge, owner chips, the type badge. */
[data-help-section] { cursor: pointer; }

/* Defensive-walls summary line — its own parchment-mid recessed strip
 * below the stats pair (§5.3). Hidden until at least one defensive-wall
 * cell is painted. */
.defenses-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px 10px;
  margin-top: 10px;
  padding: 8px 12px;
  background: var(--bb-parchment-mid);
  border: 1px solid var(--bb-rule-soft);
  border-radius: 0;
  font-family: var(--bb-font-display);
  font-size: 0.875rem;           /* 14px */
  color: var(--bb-ink);
}
.defenses-line .defenses-label {
  font-family: var(--bb-font-display);
  font-size: 0.6875rem;           /* 11px */
  letter-spacing: 0.20em;
  text-transform: uppercase;
  color: var(--bb-ink-faint);
}
.defenses-line .defenses-stats {
  color: var(--bb-ink);
  font-variant-numeric: lining-nums tabular-nums;
}
.defenses-line .defenses-stats .unit {
  font-family: var(--bb-font-italic);
  font-style: italic;
  color: var(--bb-ink-faint);
  margin-left: 1px;
}
/* Share currency (#271) — the third .defenses-line strip. Its verb takes the
   right-hand slot .defenses-badge holds on the walls line.

   Both overrides undo tile assumptions baked into .fig-turn-link, which was
   authored for the column-flex stat tiles (Bastion Turn, Treasury): align-self
   and a top margin are meaningless-to-harmful inside a baseline-aligned,
   wrap-flex strip. */
.share-line .fig-turn-link {
  margin-left: auto;
  margin-top: 0;
  align-self: baseline;
}
/* Two different problems, two different queries — an earlier cut conflated them
   under `pointer: coarse` and so only ever fixed one of the two renderings.

   WIDTH decides whether the verb wraps. Once it drops to its own line, a
   `margin-left: auto` strands it against the right edge under a left-aligned
   chain, which reads as a stray control rather than this strip's verb. Pinned to
   the same 560px the .figures grid itself reflows at, and it must be a width
   query: a narrow desktop window with a mouse wraps exactly the same way a phone
   does. */
@media (max-width: 560px) {
  .share-line .fig-turn-link { margin-left: 0; flex-basis: 100%; }
}
/* POINTER decides height. The shared coarse-tier rule gives every <button> a
   44px minimum, and a 44px box on a baseline drags the strip's baseline off the
   label and the figures beside it. Centre the tall box instead — the tap target
   keeps its full height, the text it sits beside keeps its alignment. */
@media (pointer: coarse) {
  .share-line .fig-turn-link { align-self: center; }
}
.defenses-badge {
  margin-left: auto;
  padding: 3px 8px;
  border-radius: 999px;          /* §2.4 exception: badge pill */
  background: linear-gradient(180deg,
      rgba(74,111,58,0.28),
      rgba(74,111,58,0.18));
  border: 1px solid rgba(74,111,58,0.45);
  color: var(--bb-verdigris-deep);
  font-family: var(--bb-font-display);
  font-size: 0.6875rem;           /* 11px */
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* ---- Right pane: the floorplan as hero --------------------------------- */

/* ---- Stage container (Codex §6) ----------------------------------------- */

.stage {
  /* Scrolling + padding moved to the inner .stage-scroll (#230 pt1); .stage is
   * now the relative wrapper (see .chapter-grounds .stage). Only the faint
   * vignette ground stays here so it fills the whole stage incl. fullscreen. */
  background:
    radial-gradient(ellipse 60% 40% at 50% 0%,
        color-mix(in srgb, var(--bb-parchment-mid) 55%, transparent) 0%,
        transparent 60%);
}

/* Fullscreen floorplan (#121): .stage carries only a faint overlay gradient —
   the opaque parchment ground lives on <body>, which isn't rendered while
   .stage is promoted to the top layer. Without an opaque base here the default
   black ::backdrop shows through. Set just the base colour (per-theme via
   --bb-parchment) so each theme's top-glow gradient is preserved. */
.stage:fullscreen { background-color: var(--bb-parchment); }
.stage:fullscreen::backdrop { background: var(--bb-parchment); }

/* ---- Placement banner — active/placing state (§6.1) --------------------- */

/* Idle drops the banner entirely (R2.3); display:flex below would otherwise
 * win over the [hidden] attribute, so guard it explicitly. */
.placement-banner[hidden] { display: none; }
.placement-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  /* Row 2 of .grounds-command — the rail's padding + the toolbar's
   * margin-bottom set the spacing, so the status zone carries no outer margin
   * of its own (a stray margin here would reintroduce reflow). */
  margin: 0;
  background: linear-gradient(180deg,
      color-mix(in srgb, var(--bb-gilt-bright) 18%, transparent),
      color-mix(in srgb, var(--bb-gilt-bright) 10%, transparent));
  border: 1px solid var(--bb-gilt);
  border-radius: 0;
  color: var(--bb-ink);
  box-shadow:
    0 1px 0 var(--bb-inset-light) inset,
    0 6px 14px -10px var(--bb-hairline);
  animation: banner-pulse 2.2s ease-in-out infinite;
}
@keyframes banner-pulse {
  0%, 100% {
    box-shadow:
      0 1px 0 var(--bb-inset-light) inset,
      0 6px 14px -10px var(--bb-hairline);
  }
  50% {
    box-shadow:
      0 1px 0 var(--bb-inset-light) inset,
      0 8px 22px -6px color-mix(in srgb, var(--bb-gilt-bright) 55%, transparent);
  }
}
@media (prefers-reduced-motion: reduce) {
  .placement-banner { animation: none; }
}

.placement-banner .pb-icon {
  flex: 0 0 22px;
  width: 22px; height: 22px;
  display: grid; place-items: center;
  border-radius: 50%;            /* §2.4 exception: icon disc */
  background: var(--bb-vermillion);
  color: var(--bb-parchment-light);
  font-family: var(--bb-font-display);
  font-weight: 600;
  font-size: 0.875rem;
}
.placement-banner .pb-text {
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-width: 0;
}
.placement-banner .pb-text strong {
  font-family: var(--bb-font-display);
  font-size: 0.875rem;           /* 14px */
  font-weight: 600;
  color: var(--bb-ink);
}
.placement-banner .pb-text small {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.84rem;          /* 13.5px */
  color: var(--bb-ink-soft);
  letter-spacing: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.placement-banner .pb-text small.bad { color: var(--bb-danger); }
.placement-banner .spacer { flex: 1; }
/* Help deep-link glyph — quiet chrome at the banner's trailing edge,
 * shown only while a tool mode is active (#117). */
.placement-banner .pb-help {
  flex: none;
  margin: 0 2px;
  vertical-align: 0;
}

/* Tool-mode chip — gilt-deep pill (§6.1 tool-mode state). Gilt, not red:
 * a selected tool is a current/active state, not an alert (#131 — red is
 * reserved for over-cap/compliance). Pairs with the gilt pb-icon disc as one
 * gold "active tool" cluster. parchment-light text clears AA on gilt-deep
 * (5.98:1 default; Arcane has no gilt luminous flip so it gets an override
 * below). The chip must stay one line at narrow widths (tuned in #40 — do
 * not regress). */
.placement-banner.mode #placement-status {
  flex: 0 0 auto;
  padding: 4px 12px;
  border-radius: 999px;          /* §2.4 exception: status pill */
  background: var(--bb-gilt-deep);
  color: var(--bb-parchment-light);
  font-family: var(--bb-font-display);
  font-size: 0.75rem;          /* 12px */
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  white-space: nowrap;
}
/* Tool-mode pb-icon: gilt-filled disc with a hand-drawn line-art SVG
 * glyph (§2.5 + §6.1, decision #3). The path inside the SVG file
 * carries stroke="currentColor" so the disc's color drives the ink. */
.placement-banner.mode .pb-icon {
  background: var(--bb-gilt);
  color: var(--bb-parchment-light);
}
.placement-banner .pb-icon .glyph-svg {
  width: 14px;
  height: 14px;
  display: block;
}

/* Cancel button — vermillion text on transparent, fill on hover (§6.1). */
.placement-banner #btn-cancel-placement {
  background: transparent;
  border: 1px solid var(--bb-vermillion);
  color: var(--bb-vermillion);
  box-shadow: none;
  font-family: var(--bb-font-display);
  font-size: 0.75rem;          /* 12px */
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 5px 12px;
}
.placement-banner #btn-cancel-placement:hover {
  background: var(--bb-vermillion);
  color: var(--bb-parchment-light);
  border-color: var(--bb-vermillion);
}

/* ---- Stage tools row (Codex §6.2) --------------------------------------- */

/* .stage-tools removed per #157 — view controls now ride the Grounds
 * .chapter-head-aside (the generic two-slot chapter header). */

/* Grid controls cluster. One instance now (#230 pt1): the canvas view-dock. (The
 * Grounds .chapter-head band was retired — Export map ▾ → app top bar, Map display
 * ▾ → this dock.) This base rule styles it; the dock rule below adds the float
 * slab + positioning. */
.grid-ctrls {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* Canvas view-dock (#230 pt1) — the ephemeral-view instruments (grid-dims, zoom,
 * fullscreen, ghost, Fit, Overview) + Map display ▾ as a quiet translucent slab
 * floating on the map, bottom-left. It carries .grid-ctrls so every existing
 * view-control button keeps its chrome; the float slab + absolute HUD positioning
 * are on this rule (see the inner note). The translucency is deliberately lighter
 * than the command rail so the dock reads as "laid on the map," not part of the
 * chrome. */
.grounds-view-dock {
  /* Absolute HUD on .stage (position:relative), OUTSIDE .stage-scroll — so it
   * floats over the canvas viewport at every zoom, in both the fits-the-screen
   * and zoomed-and-panning cases (the earlier sticky version fell into flow with
   * no scroll). Float inward over the grid vellum, inset from the plate's
   * bottom-left neatline — never straddling it. left:150 clears the spine
   * (~116px) + plate frame; bottom:20 lifts off the bottom neatline. The
   * drop-shadow carries the depth. Tune against the live rendered edges. */
  position: absolute;
  left: 150px;
  bottom: 20px;
  z-index: 30;                  /* above the command rail (20), below modals */
  width: fit-content;
  gap: 8px;
  padding: 5px 8px;
  border-radius: 0;             /* honor the square handout grammar */
  background: color-mix(in srgb, var(--bb-parchment-light) 82%, transparent);
  backdrop-filter: blur(8px) saturate(1.04);
  -webkit-backdrop-filter: blur(8px) saturate(1.04);
  border: 1px solid var(--bb-rule-soft);
  /* Real elevation is the "floating" cue — markedly stronger than a flat plaque. */
  box-shadow:
    0 4px 16px -6px rgba(60,30,10,0.40),
    0 1px 0 var(--bb-inset-light) inset;
}
/* Buttons stay solid parchment chips on the glass slab — don't let them go
 * translucent too or they'd mush into the map. (Their chrome comes from the
 * .grid-ctrls .zoom-btn / > button rules below, unchanged.) */
.grid-ctrls label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--bb-font-display);
  font-size: 0.75rem;           /* 12px */
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bb-ink-soft);
}
.grid-ctrls input { width: 60px; min-width: 60px; }
.grid-ctrls > button,
/* #215/#224 Export map ▾ and Map display ▾ each sit in a positioned wrapper (for
 * their dropdowns), so they aren't direct children of .grid-ctrls — mirror the
 * sibling view-control chrome onto them. */
.grid-ctrls .map-display-wrap > button {
  font-family: var(--bb-font-display);
  font-size: 0.8125rem;             /* 13px */
  letter-spacing: 0.14em;
  text-transform: uppercase;
  background: var(--bb-parchment-light);
  color: var(--bb-ink);
  border: 1px solid var(--bb-rule);
  box-shadow: 0 1px 0 var(--bb-inset-light) inset, 0 1px 2px rgba(80,40,10,0.15);
  padding: 6px 12px;
}
.grid-ctrls > button:hover,
.grid-ctrls .map-display-wrap > button:hover {
  background: var(--bb-parchment-mid);
  border-color: var(--bb-ink-faint);
}

/* Map display popover (#224): reuses .bastions-menu card chrome but holds
 * settings-rows, not menu-rows. Right-anchored (it sits near the cluster's right
 * edge) and given the calm two-line row shape the toggles had in Settings. */
.map-display-panel {
  right: 0;
  left: auto;
  padding: 6px 14px;
  min-width: 320px;
}
/* On the floating dock (desktop) Map display opens UPWARD — the dock lives at the
 * canvas bottom-left, so a downward panel would fall off. Unclipped thanks to the
 * overflow:visible .stage restructure (#230 pt1). Phones keep the fixed bottom
 * sheet (the ≤700px rule below), so scope this to wider viewports. */
@media (min-width: 701px) {
  .grounds-view-dock .map-display-panel {
    top: auto;
    bottom: calc(100% + 6px);
    left: 0;
    right: auto;
  }
}
.map-display-panel .settings-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 10px;
  row-gap: 2px;
  align-items: center;
  padding: 6px 0;
  border-bottom: none;
}
.map-display-panel .settings-row + .settings-row { border-top: 1px solid var(--bb-rule-soft); }
.map-display-panel .settings-row .settings-sub { grid-column: 1 / -1; }
/* checkbox rows hang the description under the label text, clear of the box */
.map-display-panel .settings-row:has(.view-toggle) .settings-sub { padding-left: 26px; }
.map-display-panel .settings-subhead { margin: 0.6rem 0 0.125rem; }

/* Map display popover on phones (#224): keep the trigger a ≥44px tap target and
 * render the panel as a bottom sheet so its content never overflows the wrapped
 * cluster. The on-grid reading toggles (names, marginalia) stay reachable — they
 * are legit aids for the view-only phone map. */
@media (max-width: 700px) {
  .grid-ctrls .map-display-wrap > button { min-height: 44px; }
  .map-display-panel {
    position: fixed;
    left: 8px; right: 8px; bottom: 8px; top: auto;
    min-width: 0;
    max-height: 70vh;
  }
  .map-display-panel .view-toggle,
  .map-display-panel .type-pick-btn { min-height: 40px; }
  .map-display-panel .type-pick-btn { display: inline-flex; align-items: center; }
}

/* Floorplan zoom (#121 carve-out) — neutral *view* chrome, so it stays in the
   parchment palette. Gilt signals "the view is off its 100% anchor"; never the
   vermillion mode-btn wash, never red (red = compliance alert only, #131). */
.grid-ctrls .zoom-ctrl { display: inline-flex; align-items: stretch; }
.grid-ctrls .zoom-btn {
  font-family: var(--bb-font-display);
  font-size: 0.8125rem;             /* 13px — matches the grid-ctrls buttons */
  line-height: 1;
  background: var(--bb-parchment-light);
  color: var(--bb-ink);
  border: 1px solid var(--bb-rule);
  box-shadow: 0 1px 0 var(--bb-inset-light) inset, 0 1px 2px rgba(80,40,10,0.15);
  padding: 6px 11px;
  cursor: pointer;
}
.grid-ctrls .zoom-btn + .zoom-btn { margin-left: -1px; }  /* shared borders */
.grid-ctrls .zoom-btn:hover {
  background: var(--bb-parchment-mid);
  border-color: var(--bb-ink-faint);
  position: relative;
  z-index: 1;                       /* lift the joined border on hover */
}
.grid-ctrls .zoom-readout {
  font-variant-numeric: lining-nums tabular-nums;
  /* Fixed width so the readout — and the whole dock — never resizes as the value
   * changes width. The display face has no tabular figures, so "1" is narrower
   * than "2"/"0"; ch-sizing (and a too-small em) let 75%↔100% / 150%↔200% jiggle
   * the dock (#230 pt1 follow-up). box-sizing is border-box here, so this width
   * must clear the widest reading ("200%") PLUS the button's own h-padding. */
  min-width: 5em;
  box-sizing: border-box;
  text-align: center;
}
/* Gilt underline only when zoom ≠ 100%. */
.grid-ctrls .zoom-readout.is-zoomed {
  color: var(--bb-gilt-deep);
  box-shadow: 0 1px 0 var(--bb-inset-light) inset, 0 1px 2px rgba(80,40,10,0.15),
              inset 0 -2px 0 var(--bb-gilt);
}
.grid-ctrls .zoom-btn:disabled {
  opacity: 0.38;
  cursor: not-allowed;
}
.grid-ctrls .zoom-btn:disabled:hover {
  background: var(--bb-parchment-light);
  border-color: var(--bb-rule);
}
.grid-ctrls .zoom-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bb-parchment-light);
  color: var(--bb-ink);
  border: 1px solid var(--bb-rule);
  box-shadow: 0 1px 0 var(--bb-inset-light) inset, 0 1px 2px rgba(80,40,10,0.15);
  padding: 6px 9px;
  cursor: pointer;
}
.grid-ctrls .zoom-icon-btn:hover {
  background: var(--bb-parchment-mid);
  border-color: var(--bb-ink-faint);
}
/* Fullscreen is a persistent toggled state → it earns the stronger gilt cue. */
.grid-ctrls .zoom-icon-btn[aria-pressed="true"] {
  color: var(--bb-gilt-deep);
  border-color: var(--bb-gilt);
}
.grid-ctrls .zoom-glyph:not([hidden]) { display: block; }
.grid-ctrls .zoom-btn:focus-visible,
.grid-ctrls .zoom-icon-btn:focus-visible {
  outline: 2px solid var(--bb-gilt);
  outline-offset: 2px;
}
/* Hairline seam separating the view cluster from the Fit/Save actions. */
.grid-ctrls .grid-ctrls-seam {
  align-self: stretch;
  width: 1px;
  margin: 2px 2px;
  background: var(--bb-rule);
  opacity: 0.6;
}

/* Grid size readout (#129) — a live cols × rows chip in the stage tools.
 * Resizing itself is done by dragging the floor-edge handles (.grid-edge-*
 * below). */
.grid-dims {
  font-family: var(--bb-font-mono);
  font-size: 0.8125rem;             /* 13px */
  color: var(--bb-ink);
  font-variant-numeric: lining-nums tabular-nums;
  padding: 0 6px;
  white-space: nowrap;
  display: inline-flex;
  align-items: baseline;
}
/* Each number reserves room for up to two digits (GRID_MAX = 60) so the
 * 9→10 transition doesn't shove the neighbouring controls sideways. */
.grid-dim-num {
  display: inline-block;
  min-width: 2ch;
  text-align: center;
}
.grid-dim-x {
  padding: 0 1px;
  color: var(--bb-ink-faint);
}

/* Grid edge-drag resize handles (#129) — one capsule bar per grid edge, shown
 * only in idle edit mode. The palette is gilt/ambient (deliberately quieter
 * than the vermillion facility ghost-handle: grid edges are structural canvas
 * chrome, not a hot object selection). `.grid-edge-bar` is the visible capsule;
 * `.grid-edge-hit` is the wider transparent grab/focus/cursor target. */
.grid-edge-bar {
  pointer-events: none;
  fill: var(--bb-parchment-light);
  stroke: var(--bb-rule-soft);
  stroke-width: 1.5;
  transition: transform 0.08s ease-out, fill 0.08s ease-out, stroke 0.08s ease-out;
  transform-box: fill-box;
  transform-origin: center;
}
.grid-edge-hit {
  pointer-events: all;
  fill: transparent;
}
.grid-edge-handle:hover .grid-edge-bar,
.grid-edge-handle:focus-within .grid-edge-bar {
  fill: var(--bb-gilt-fill);
  stroke: var(--bb-gilt);
  transform: scale(1.18);
}
.grid-edge-handle.dragging .grid-edge-bar {
  fill: var(--bb-gilt);
  stroke: var(--bb-gilt-deep);
  transform: scale(1.18);
}
/* Hard-stop feedback: flash gilt-bright while the drag pushes past the bound
 * (or for ~180ms on a blocked keyboard bump — the JS toggles the class). */
.grid-edge-handle.bound-hit .grid-edge-bar {
  fill: var(--bb-gilt-bright);
  stroke: var(--bb-gilt-deep);
}
/* Both directions blocked (at GRID_MAX and flush to content): dim + no-drag. */
.grid-edge-handle.at-bound .grid-edge-bar {
  opacity: 0.4;
}
.grid-edge-handle.at-bound .grid-edge-hit {
  cursor: not-allowed !important;
}
/* Keyboard focus ring on the (otherwise invisible) hit target. */
.grid-edge-hit:focus-visible {
  outline: 2px solid var(--bb-gilt);
  outline-offset: 2px;
}

/* ---- Edit-mode toolbar (Codex §6.3) ------------------------------------- */

/* Command rail (§5.9 item 5): the sticky wrapper around the toolbar (row 1) and
 * the constant-height status zone (row 2), pinned together while the floor stack
 * scrolls below (#33). It carries the negative inline margin + padding + backdrop
 * the toolbar used to own, so the chrome spans the full visible .stage width when
 * stuck. Pinning both rows as a unit is what lets arming a tool swap the status
 * copy in place — zero canvas reflow. */
.grounds-command {
  position: sticky;
  top: 0;
  z-index: 20;
  /* Span exactly the canvas column's content width (.chapter-grounds .stage
   * pads 22px) so the rail's backdrop reaches both stage edges without bleeding
   * across the seam into the aside. */
  margin-inline: -22px;
  padding: 10px 22px 8px;
  background: linear-gradient(180deg,
      color-mix(in srgb, var(--bb-parchment)     92%, transparent),
      color-mix(in srgb, var(--bb-parchment-mid) 85%, transparent));
  backdrop-filter: blur(6px);
  /* Soft separator, not the heavy --bb-rule — the canvas sits below with a
   * 16px gap (.stage-body padding-top) so its own matted top edge shows (#160). */
  border-bottom: 1px solid var(--bb-rule-soft);
}

.edit-mode-toolbar {
  display: flex;
  align-items: center;
  gap: 0;                          /* joined-plaque mode buttons share borders */
  margin-bottom: 8px;              /* small gap before the status zone (row 2) */
}

/* Joined plaque (§6.3): adjacent mode buttons share their inner border via
 * a -1px left margin. View-toggle uses its own auto margin so the gap-0
 * parent doesn't crowd the right end. */
.edit-mode-toolbar .mode-btn + .mode-btn { margin-left: -1px; }

/* Toolbar seam (#139): the toolbar holds drawing verbs only; a single seam
 * separates the Select home from the draw tools, breaking the joined-plaque
 * margin above. Place "nouns" live in the Grounds library aside, not here. */
.edit-mode-toolbar .tool-seam {
  align-self: stretch;
  width: 1px;
  margin: 2px 12px;
  background: var(--bb-rule);
  opacity: 0.6;
}

.edit-mode-toolbar .view-toggle {
  margin-left: auto;
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--bb-font-display);
  font-size: 0.75rem;            /* 12px */
  letter-spacing: 0.20em;
  text-transform: uppercase;
  color: var(--bb-ink-soft);
  cursor: pointer;
  user-select: none;
}

/* Visually hide the real checkbox; keep it accessible. The square chip is
 * a sibling pseudo-element drawn alongside it. */
.edit-mode-toolbar .view-toggle input {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
.edit-mode-toolbar .view-toggle::before {
  content: '';
  display: inline-block;
  width: 18px; height: 18px;
  flex: 0 0 18px;
  border: 1px solid var(--bb-rule);
  background: var(--bb-parchment-light);
  border-radius: 0;
  box-sizing: border-box;
}
.edit-mode-toolbar .view-toggle:has(input:checked)::before {
  background: var(--bb-vermillion-deep);
  border-color: var(--bb-vermillion-deep);
}
.edit-mode-toolbar .view-toggle::after {
  content: '';
  position: absolute;
  left: 5px; top: 50%;
  width: 5px; height: 10px;
  border-right: 2px solid var(--bb-parchment-light);
  border-bottom: 2px solid var(--bb-parchment-light);
  transform: translateY(-65%) rotate(45deg);
  opacity: 0;
  pointer-events: none;
}
.edit-mode-toolbar .view-toggle:has(input:checked)::after { opacity: 1; }
.edit-mode-toolbar .view-toggle:has(input:focus-visible)::before {
  box-shadow: 0 0 0 2px var(--bb-vermillion);
}

/* Sourcebooks disclosure (Charter, #170) — closed by default so the cover
   page stays light; the summary line carries the at-a-glance scope. */
.charter-sources {
  margin: 22px 0 0;
  border-top: 1px solid var(--bb-rule);
}
.charter-sources > summary {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  cursor: pointer;
  list-style: none;
}
.charter-sources > summary::-webkit-details-marker { display: none; }
.charter-sources-title {
  font-family: var(--bb-font-display);
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bb-ink-soft);
}
.charter-sources-summary {
  margin-left: auto;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--bb-ink-soft);
}
.charter-sources > summary::after {
  content: "";
  flex: none;
  width: 7px; height: 7px;
  margin-right: 3px;
  border-right: 1.5px solid var(--bb-ink-soft);
  border-bottom: 1.5px solid var(--bb-ink-soft);
  transform: rotate(45deg);
  transition: transform 120ms;
}
.charter-sources[open] > summary::after { transform: rotate(-135deg); }
.charter-sources > summary:hover .charter-sources-title,
.charter-sources > summary:focus-visible .charter-sources-title { color: var(--bb-vermillion); }
.charter-sources > summary:hover::after,
.charter-sources > summary:focus-visible::after { border-color: var(--bb-vermillion); }
.charter-sources > summary:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}
.charter-sources[open] { padding-bottom: 8px; }
.charter-sources .chapter-sub { margin: 0 0 8px; }

/* Settings dialog checkboxes — same visual as toolbar toggle but readable size.
   Shared with the per-bastion Sourcebooks control on the Charter (#170) and the
   Grounds Map display popover (#224). */
.settings-body .view-toggle,
.charter-sources .view-toggle,
#welcome-dialog .view-toggle,
#map-display-menu .view-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.875rem;
  color: var(--bb-ink);
  cursor: pointer;
  user-select: none;
}
.settings-body .view-toggle input,
.charter-sources .view-toggle input,
#welcome-dialog .view-toggle input,
#map-display-menu .view-toggle input {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.settings-body .view-toggle::before,
.charter-sources .view-toggle::before,
#welcome-dialog .view-toggle::before,
#map-display-menu .view-toggle::before {
  content: ''; display: inline-block;
  width: 18px; height: 18px; flex: 0 0 18px;
  border: 1px solid var(--bb-rule);
  background: var(--bb-parchment-light);
  border-radius: 0; box-sizing: border-box;
}
.settings-body .view-toggle:has(input:checked)::before,
.charter-sources .view-toggle:has(input:checked)::before,
#welcome-dialog .view-toggle:has(input:checked)::before,
#map-display-menu .view-toggle:has(input:checked)::before {
  background: var(--bb-vermillion-deep);
  border-color: var(--bb-vermillion-deep);
}
.settings-body .view-toggle::after,
.charter-sources .view-toggle::after,
#welcome-dialog .view-toggle::after,
#map-display-menu .view-toggle::after {
  content: ''; position: absolute;
  left: 5px; top: 50%;
  width: 5px; height: 10px;
  border-right: 2px solid var(--bb-parchment-light);
  border-bottom: 2px solid var(--bb-parchment-light);
  transform: translateY(-65%) rotate(45deg);
  opacity: 0; pointer-events: none;
}
.settings-body .view-toggle:has(input:checked)::after,
.charter-sources .view-toggle:has(input:checked)::after,
#welcome-dialog .view-toggle:has(input:checked)::after,
#map-display-menu .view-toggle:has(input:checked)::after { opacity: 1; }
.settings-body .view-toggle:has(input:focus-visible)::before,
.charter-sources .view-toggle:has(input:focus-visible)::before,
#welcome-dialog .view-toggle:has(input:focus-visible)::before,
#map-display-menu .view-toggle:has(input:focus-visible)::before {
  box-shadow: 0 0 0 2px var(--bb-vermillion);
}

/* Locked row (Core — always on) */
.settings-row.locked .view-toggle {
  cursor: default; color: var(--bb-ink-faint);
}
.settings-row.locked .view-toggle::before {
  background: var(--bb-vermillion-deep); border-color: var(--bb-vermillion-deep);
  opacity: 0.45;
}
.settings-row.locked .view-toggle::after { opacity: 1; }

/* Row layout */
.settings-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--bb-rule-soft);
}
.settings-group .settings-row:last-child { border-bottom: none; }

/* #216 — inside the Settings dialog, rows grid into a calm two-line shape:
 * label (+ trailing control) on the first line, the italic description on its
 * own full-width line below, so every description starts at the same edge
 * instead of trailing labels of assorted lengths. Per-row dividers drop — the
 * group-title underline alone bounds each section. Scoped to the dialog: the
 * founding form reuses .settings-row and keeps the flex recipe. */
#settings-dialog .settings-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 10px;
  row-gap: 2px;
  align-items: center;
  border-bottom: none;
}
#settings-dialog .settings-row .settings-sub { grid-column: 1 / -1; }
/* checkbox rows hang the description under the label text, clear of the box */
#settings-dialog .settings-row:has(.view-toggle) .settings-sub { padding-left: 26px; }
/* #218 — a row whose only text IS its description (the Build stamp) keeps that
   text in column 1 beside the trailing control, instead of spanning the full
   width and leaving an empty cell where a label would have been. */
#settings-dialog .settings-row:not(:has(.settings-row-label)) .settings-sub {
  grid-column: 1;
}
/* trailing controls sit beside the label, never after the description */
#settings-dialog .settings-row .settings-action,
#settings-dialog .settings-row .version-badge {
  grid-row: 1;
  grid-column: 2;
}

/* #216 — toggle-only groups (Map & floorplan / Handout / At the table) sit
 * tighter so minor tweaks read as minor next to the roomy tile/action groups. */
#settings-dialog .settings-group--compact .settings-row { padding: 6px 0; }

/* #216 — on touch, the compact density must not shrink tap targets: hold every
 * settings control (checkbox labels, action buttons, N/E/S/W picks) at 40px. */
@media (max-width: 700px) {
  .settings-body .view-toggle,
  .settings-body .settings-action,
  .settings-body .type-pick-btn {
    min-height: 40px;
  }
  /* labels don't centre their text in a taller box on their own */
  .settings-body .type-pick-btn {
    display: inline-flex;
    align-items: center;
  }
}

.settings-sub {
  font-family: var(--bb-font-italic); font-style: italic;
  font-size: 0.8125rem; color: var(--bb-ink-soft);
  flex: 1;
}
.settings-count {
  font-size: 0.75rem; color: var(--bb-ink-soft);
  font-variant-numeric: lining-nums tabular-nums;
  white-space: nowrap;
}

/* #150 backup/restore action rows — a plain bold label + a quiet ghost button.
   The button stays text-only (no ↥/↧ arrows) so the whole-state actions read
   apart from the per-bastion Import/Export glyphs in the Bastions menu. */
.settings-row-label {
  font-family: var(--bb-font-display);
  font-size: 0.875rem; color: var(--bb-ink);
  white-space: nowrap;
}
.settings-action {
  margin-left: auto; flex: none;
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule); border-radius: 0;
  color: var(--bb-ink);
  padding: 5px 14px;
  font-family: var(--bb-font-display);
  font-size: 0.8125rem; letter-spacing: 0.06em;
  cursor: pointer; white-space: nowrap;
  min-width: 7rem; text-align: center; /* #216 — equal plates, aligned edges */
}
.settings-action:hover {
  background: var(--bb-accent-soft);
  border-color: var(--bb-vermillion);
  color: var(--bb-vermillion);
}
.settings-body {
  margin: 14px 0 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* #136 — the Settings dialog kept growing past the viewport, pushing Done
 * off-screen. Cap the form to the viewport and let the settings list scroll
 * inside, so the Done button stays pinned and always reachable. When the
 * content is short the dialog still shrinks to fit (no forced tall box).
 * #216 — the dialog itself must never scroll (the UA gives <dialog> its own
 * overflow, which doubled the scrollbar on phones); dvh keeps the cap honest
 * under mobile URL bars, with vh as the older-engine fallback. */
#settings-dialog {
  overflow: hidden;
}
#settings-dialog .dialog-form {
  max-height: 88vh;
  max-height: 88dvh;
}
/* .dialog-sub's 1rem bottom margin stacks on the form's 14px flex gap (no
 * collapsing in flex) — twice the 16px group rhythm. The gap alone spaces it. */
#settings-dialog .dialog-sub { margin-bottom: 0; }
#settings-dialog .settings-body {
  margin-top: 0;       /* the form's own gap spaces it under the intro line */
  min-height: 0;       /* allow the flex child to shrink below its content … */
  overflow-y: auto;    /* … and scroll, rather than pushing Done off-screen */
  padding-right: 12px; /* house gutter so the scrollbar doesn't crowd toggles */
  scrollbar-gutter: stable;
}
#settings-dialog .dialog-actions {
  flex-shrink: 0;      /* the Done row never compresses */
}

/* Settings groups (Content / Handout / …) */
.settings-group {
  display: flex;
  flex-direction: column;
}
.settings-group-title {
  margin: 0;
  font-family: var(--bb-font-display);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bb-gilt-deep);
  padding-bottom: 4px;
  border-bottom: 1px solid var(--bb-rule-soft);
}
.settings-group-sub {
  margin: 4px 0 2px;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--bb-ink-soft);
}
/* #216 — sub-cluster label inside a group ("On exports", "Palette"): a
 * lighter echo of the group title, so clusters read as units without a rule. */
.settings-subhead {
  margin: 0.75rem 0 0.125rem;
  font-family: var(--bb-font-display);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--bb-ink-soft);
}

/* Theme picker tiles — 2-column grid so 4 tiles sit as 2×2 */
.theme-option-group {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  padding: 10px 0 4px;
}
.theme-tile {
  /* #218 — contain the .sr-only radio inside the label. Without a positioned
   * ancestor here, the absolutely-positioned radio's containing block is the
   * <dialog> (UA position:absolute); focusing it on click makes the browser
   * scroll the whole dialog into view, shooting it off-screen (blank Settings). */
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px;
  border: 1px solid var(--bb-rule);
  border-radius: 4px;
  cursor: pointer;
  min-width: 0;
  background: var(--bb-parchment-light);
  transition: border-color 0.15s, box-shadow 0.15s;
}
.theme-tile:hover {
  border-color: var(--bb-vermillion);
}
.theme-tile--selected {
  border-color: var(--bb-vermillion-deep);
  box-shadow: 0 0 0 1px var(--bb-vermillion-deep);
}
.theme-swatches {
  display: flex;
  gap: 4px;
}
.theme-swatch {
  display: block;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid var(--bb-rule);
}
.theme-tile-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.theme-tile-label {
  font-family: var(--bb-font-display);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  color: var(--bb-ink);
}
.theme-tile-desc {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.75rem;
  color: var(--bb-ink-soft);
}
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border-width: 0;
}

/* Mode buttons (§6.3) — joined plaque with a gilt active fill. The armed tool
 * is a current/active state, not an alert, so it wears gold, not red (#131 —
 * red is reserved for danger/Cancel, which sits right below in this rail). The
 * fill mirrors the status-chip recipe (gilt-deep + parchment-light, ~5.98:1)
 * so the pressed button and the banner's tool chip read as one gold cluster. */
.mode-btn {
  padding: 7px 14px;
  font-family: var(--bb-font-display);
  font-size: 0.75rem;            /* 12px */
  letter-spacing: 0.12em;
  text-transform: uppercase;
  background: var(--bb-parchment-light);
  color: var(--bb-ink-soft);
  border: 1px solid var(--bb-rule);
  border-radius: 0;
  box-shadow: none;
}
/* Hover is colourless (parchment-mid) so gilt reads unambiguously as "armed" —
 * no red wash to pre-echo the old active colour, no gold to pre-echo the new. */
.mode-btn:hover {
  background: var(--bb-parchment-mid);
  color: var(--bb-ink);
  border-color: var(--bb-rule);
}
.mode-btn.active,
.mode-btn[aria-pressed="true"] {
  background: var(--bb-gilt-deep);
  color: var(--bb-parchment-light);
  border-color: var(--bb-gilt-deep);
  text-shadow: 0 1px 0 rgba(0,0,0,0.25);
  box-shadow: 0 1px 0 var(--bb-inset-faint) inset;
  /* Lift active button so its borders overlap neighbours cleanly. */
  position: relative;
  z-index: 1;
}
/* No active:hover rule: `.mode-btn[aria-pressed="true"]` (b:2) ties `.mode-btn
 * :hover` (b:2) on specificity and wins on source order, so the gilt fill holds
 * on hover without a rule that would fight the per-theme dark-text overrides. */
.mode-btn:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
  z-index: 2;
}

/* ---- Register of Chambers (#134) -------------------------------------- */

/* The Register is now a peer chapter (#121): its own <section hidden> that the
 * chapter switch shows/hides. The old `body.register-open` floorplan-chrome
 * hiding is obsolete — Plates lives in a separate section that's hidden whole
 * when another chapter is active. */

.register-view {
  /* Lining/tabular figures across the whole view (gold, days, levels). */
  font-variant-numeric: lining-nums tabular-nums;
  /* No own padding — the .chapter-register band (18px 22px 40px) governs, so the
   * header sits at the same top offset as the other chapters (nit 1 follow-up). */
}
/* ---- Register card management (#121 Stage B) ---------------------------- */
.reg-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--bb-rule-soft);
}
.reg-rename {
  display: flex;
  align-items: center;
  gap: 8px;
}
.reg-rename-label {
  font-family: var(--bb-font-display);
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--bb-ink-faint);
  flex: 0 0 auto;
}
.reg-name-edit {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 280px;
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule-soft);
  border-radius: 0;
  box-shadow: none;
  padding: 4px 8px;
  font-family: var(--bb-font-text);
  font-size: 0.9rem;
  color: var(--bb-ink);
}
.reg-name-edit:focus {
  outline: none;
  border-color: var(--bb-vermillion);
  background: var(--bb-parchment-light);
}
.reg-act-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.reg-act {
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule-soft);
  border-radius: 0;
  box-shadow: none;
  padding: 5px 11px;
  font-family: var(--bb-font-display);
  font-size: 0.78rem;
  letter-spacing: 0.03em;
  color: var(--bb-ink);
  cursor: pointer;
}
.reg-act:hover { background: var(--bb-parchment-mid); border-color: var(--bb-rule); }
.reg-act:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: 2px; }
.reg-act:disabled { opacity: 0.5; cursor: not-allowed; }
.reg-act.danger { color: var(--bb-danger); }
.reg-act.danger:hover:not(:disabled) {
  background: var(--bb-danger);
  border-color: var(--bb-danger);
  color: var(--bb-parchment-light);
}
.reg-empty {
  color: var(--bb-ink-faint);
  font-style: italic;
  font-family: var(--bb-font-italic);
}
.reg-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 12px;
  /* Stretch (grid default) so cards sharing a row match height — closed
   * cards carry different amounts of optional content (order line, owner
   * chip, haunted badge, …) and align-items: start left them jagged. The
   * full-row open card is alone in its row either way, so this is safe. */
  align-items: stretch;
}
/* Trailing add-tile (steward ruling): a dashed gilt affordance filling one grid
 * cell, replacing the old header Commission button. Doubles as the empty-state
 * CTA. One per grid (Commission for specials, Add basic room for basics). */
.reg-card-add {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 64px;
  padding: 14px;
  background: transparent;
  border: 1px dashed var(--bb-rule);
  box-shadow: none;
  cursor: pointer;
  font-family: var(--bb-font-display);
  font-size: 0.82rem;
  letter-spacing: 0.04em;
  color: var(--bb-gilt-deep);
}
.reg-card-add:hover {
  background: var(--bb-gilt-fill);
  border-color: var(--bb-gilt);
}
.reg-card-add:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}
/* At the special-facility cap the Commission tile is greyed and inert — raise a
 * character level or remove a facility to add another. Kept hoverable (aria-
 * disabled, not [disabled]) so its explanatory tooltip still fires. */
.reg-card-add.is-at-cap {
  color: var(--bb-ink-faint);
  border-color: var(--bb-rule-soft);
  cursor: not-allowed;
  opacity: 0.65;
}
.reg-card-add.is-at-cap:hover {
  background: transparent;
  border-color: var(--bb-rule-soft);
}

.reg-card {
  display: flex;
  flex-direction: column;
  background: var(--bg-card);
  border: 1px solid var(--bb-rule);
  box-shadow: var(--shadow-card);
}
/* Grid-stretched cards can run taller than their own content (siblings in the
 * row carry more optional text) — as a flex column the head sits at its own
 * height and the card's background fills the rest, instead of leaving a
 * plain-block auto-height box whose fill below the content read as a seam. */
/* An open accordion card spans the full grid so its order text gets room. */
.reg-card.special.is-open { grid-column: 1 / -1; }
.reg-card.is-unplaced { border-left: 3px solid var(--bb-vermillion); }

/* Under-construction strip (#195) — sits between the card head (itself a
   <button>, can't nest controls) and the panel, so the one-click finish
   stays reachable without expanding the card. Gilt: a "next move," not an
   alert (vermillion stays reserved for the unplaced marker). */
.reg-construction-strip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 14px;
  background: var(--bb-gilt-fill);
  border-top: 1px solid var(--bb-gilt);
  border-bottom: 1px solid var(--bb-gilt);
}
.reg-construction-label {
  font-family: var(--bb-font-display);
  font-size: 0.78rem;
  letter-spacing: 0.02em;
  color: var(--bb-gilt-deep);
}
.reg-construction-finish {
  flex: 0 0 auto;
  padding: 4px 10px;
  font-size: 0.8rem;
}

/* Facility-condition strip (#250) — lingering event consequences below the
   head, beside the construction strip (the head is a <button>, can't nest the
   × control). Valence-coloured pills in the flag-chip idiom (border + text, no
   fill — matching .reg-haunted / .reg-offmap): adverse = vermillion, boon =
   gilt. Each carries an inline hand-clear ×. */
.reg-conditions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 6px 14px;
  border-top: 1px solid var(--bb-rule-soft);
}
.reg-condition {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 3px 1px 7px;
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  border: 1px solid;
  border-radius: 2px;
}
.reg-condition.is-adverse { color: var(--bb-vermillion-deep); border-color: var(--bb-vermillion); }
.reg-condition.is-boon { color: var(--bb-gilt-deep); border-color: var(--bb-gilt); }
.reg-condition-text { line-height: 1.3; }
.reg-condition-clear {
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  width: 18px;
  height: 18px;
  padding: 0;
  font-size: 14px;
  line-height: 1;
  color: inherit;
  background: transparent;
  border: 0;
  border-radius: 2px;
  cursor: pointer;
  opacity: 0.7;
}
.reg-condition-clear:hover { opacity: 1; background: var(--bb-gilt-fill); }
@media (pointer: coarse) {
  .reg-condition-clear { width: 26px; height: 26px; font-size: 16px; }
}

.reg-card-head {
  display: flex;
  flex: 0 0 auto;
  align-items: flex-start;
  gap: 12px;
  width: 100%;
  text-align: left;
  padding: 12px 14px;
  background: transparent;
  border: 0;
  /* Reset the base `button` rule's inset highlight + drop shadow (line ~1042)
   * — with the card height-stretched, that shadow no longer sits flush against
   * the card's own bottom border and instead draws as a stray line partway
   * down the card, under the head's own (now shorter-than-card) box. */
  box-shadow: none;
  cursor: pointer;
  color: var(--bb-ink);
  font: inherit;
}
.reg-card .reg-card-head:hover { background: var(--bb-gilt-fill); }
.reg-card-head:focus-visible {
  outline: 2px solid var(--bb-gilt);
  outline-offset: -2px;
}
.reg-glyph {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  color: var(--bb-ink-soft);
}
.reg-glyph .glyph-svg { width: 26px; height: 26px; }
.reg-head-text { flex: 1 1 auto; min-width: 0; }
.reg-name {
  font-family: var(--bb-font-display);
  font-size: 1rem;
  color: var(--bb-ink);
  line-height: 1.2;
}
.reg-kind {
  font-size: 0.78rem;
  color: var(--bb-ink-faint);
  letter-spacing: 0.02em;
  margin-top: 1px;
}
/* The catalog type (shown only when the facility is custom-named) carries a
   touch more weight than the surrounding faint metadata — identity, not a
   capability, so monochrome (never gilt). */
.reg-kind-type { color: var(--bb-ink-soft); }
.reg-haunted {
  display: inline-block;
  margin-left: 6px;
  padding: 0 5px;
  font-size: 10px;
  letter-spacing: 0.04em;
  border: 1px solid var(--bb-haunted);
  border-radius: 2px;
  color: var(--bb-haunted);
}
/* Owner chip (nit 6): always-visible on a special card's head when the bastion
 * is shared, so "who owns what" reads at a glance. Identity, not capability —
 * monochrome on parchment, never gilt. Stacked under the name in the head-text
 * column (not raced horizontally against it in the head's flex row) — a fixed-
 * width sibling there squeezed the title/meta text down to a sliver, and a
 * long character name overran the card entirely; max-width + ellipsis here
 * cap it to the column's own width instead. */
.reg-owner-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  max-width: 100%;
  margin-top: 3px;
  padding: 2px 8px;
  overflow: hidden;
  font-family: var(--bb-font-display);
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--bb-ink-soft);
  background: var(--bb-parchment-mid);
  border: 1px solid var(--bb-rule-soft);
}
.reg-order-line {
  margin-top: 5px;
  font-size: 0.9rem;
  color: var(--bb-gilt-deep);
  font-weight: 600;
}
.reg-loc {
  margin-top: 4px;
  font-size: 0.78rem;
  color: var(--bb-ink-faint);
}
/* Off-map (#195) — neutral, distinct from both the vermillion "not placed
   yet" marker and the gilt haunted/construction chips: this is a deliberate,
   resolved location, not a warning or a next-move prompt. Sapphire matches
   the shelf chip treatment (render-sidebar.js). */
.reg-offmap-line {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 5px;
}
.reg-offmap {
  display: inline-block;
  padding: 0 5px;
  font-size: 10px;
  letter-spacing: 0.04em;
  border: 1px solid var(--bb-sapphire);
  border-radius: 2px;
  color: var(--bb-sapphire-deep);
}
.reg-offmap-line .reg-loc { margin-top: 0; }
.reg-unplaced {
  display: inline-block;
  margin-top: 5px;
  font-size: 0.8rem;
  color: var(--bb-vermillion);
  font-family: var(--bb-font-italic);
}
.reg-chevron {
  flex: 0 0 auto;
  align-self: center;
  color: var(--bb-ink-faint);
  transition: transform 0.15s ease;
}
.reg-card.is-open .reg-chevron { transform: rotate(180deg); }

.reg-card-panel {
  padding: 4px 14px 16px;
  border-top: 1px solid var(--bb-rule);
}
/* Expanded cards span the full grid width; split the panel into a narrative
   prose column and a compact facts aside so the short label/value rows don't
   sprawl across an empty card. Wraps to a single stack on narrow panels. */
.reg-panel-cols {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 28px;
  align-items: start;
}
.reg-col-prose {
  flex: 1 1 380px;
  min-width: 0;
  max-width: 64ch;
}
.reg-col-facts {
  flex: 1 1 250px;
  min-width: 0;
  max-width: 360px;
}
/* Both columns start flush with the top of the panel so they read as a row. */
.reg-hero { margin: 0 0 14px; }
.reg-col-facts > :first-child { margin-top: 0; }
.reg-flavor {
  margin: 0 0 10px;
  font-family: var(--bb-font-italic);
  font-size: 0.92rem;
  line-height: 1.45;
  color: var(--bb-ink-soft);
}
.reg-order-card {
  background: var(--bb-gilt-fill);
  /* Boxed (not a left rail) so it never competes with the card-level
     vermillion left rail on an expanded unplaced facility. */
  border: 1px solid var(--bb-gilt);
  padding: 8px 12px;
}
.reg-order-card-head {
  font-family: var(--bb-font-display);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bb-gilt-deep);
  margin-bottom: 3px;
}
.reg-order-card-body {
  margin: 0;
  font-size: 0.92rem;
  line-height: 1.45;
  color: var(--bb-ink);
}

/* Clickable dice — Beyond20 forwarding (#145). Surface-neutral names (not
   reg-*) so the affordance travels to the handout / bastion events later.
   A literal notation roll keeps its ink and wears a gilt dotted underline —
   the lowest-ink "rollable" mark that survives inside justified prose without
   breaking the line box. Gilt = an available reference action; vermillion is
   reserved for the active press (per the red-vs-gilt rule). */
.roll-die {
  display: inline;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  text-decoration: underline dotted var(--bb-gilt);
  text-underline-offset: 2px;
  cursor: pointer;
}
.roll-die:hover,
.roll-die:focus-visible { text-decoration-style: solid; color: var(--bb-gilt-deep); }
.roll-die:active { color: var(--bb-vermillion); }
.roll-die:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: 2px; }

/* The "roll a die" / odd-even phrase has no notation, so instead of underlining
   words (which would mislead about *which* die fires) we append a small mono
   chip showing the default d20 — a distinct shape + font that names the die. */
.roll-die-chip {
  display: inline-block;
  margin-left: 6px;
  padding: 0 6px;
  border: 1px solid var(--bb-gilt);
  border-radius: 2px;
  background: none;
  color: var(--bb-gilt-deep);
  font-family: var(--bb-font-mono);
  font-size: 0.78em;
  cursor: pointer;
}
.roll-die-chip:hover,
.roll-die-chip:focus-visible { color: var(--bb-vermillion); border-color: var(--bb-vermillion); }
.roll-die-chip:active { color: var(--bb-vermillion-deep); }
.roll-die-chip:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: 2px; }

/* Open5e stat-reference glyph + card (#213). The open-book glyph is a quiet
   inline sibling of .roll-die — it trails a creature/spell name and only
   renders where the lookup will resolve (never a dead control). Rest state
   inherits the surrounding ink; gilt names the hover/focus affordance. */
.ref-glyph {
  display: inline-block;
  vertical-align: -0.08em;
  width: 0.85em;
  height: 0.85em;
  margin-left: 4px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  line-height: 1;
  cursor: pointer;
}
.ref-glyph .glyph-svg { display: block; width: 100%; height: 100%; }
.ref-glyph:hover,
.ref-glyph:focus-visible { color: var(--bb-gilt-deep); }
.ref-glyph:active { color: var(--bb-vermillion); }
.ref-glyph:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: 2px; }
/* Companion slot beside a folded option <select> — holds the glyph for the
   currently chosen creature (a button can't live inside an <option>). */
.ref-slot { display: inline-flex; align-items: center; }

/* The reference card: an anchored popover (desktop) that turns into a bottom
   sheet ≤700px. Header + colophon stay fixed; the stat body scrolls. */
.ref-card {
  position: fixed;
  z-index: 60;
  display: flex;
  flex-direction: column;
  min-width: 260px;
  max-width: 360px;
  max-height: min(70vh, 560px);
  padding: 10px 14px;
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule);
  border-radius: 0;
  box-shadow: 0 10px 24px -8px var(--bb-shadow-mid);
}
.ref-card-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.ref-card-title {
  flex: 1 1 auto;
  margin: 0;
  font-family: var(--bb-font-display);
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  outline: none;
}
.ref-card-close {
  flex: 0 0 auto;
  padding: 0 4px;
  border: 0;
  background: none;
  color: var(--bb-ink-faint);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
.ref-card-close:hover,
.ref-card-close:focus-visible { color: var(--bb-vermillion); }
.ref-card-close:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: 2px; }
.ref-card-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding-right: 12px;
  scrollbar-gutter: stable;
}
.ref-meta {
  margin: 2px 0 6px;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.86rem;
  color: var(--bb-ink-faint);
}
.ref-section {
  margin: 10px 0 2px;
  font-family: var(--bb-font-display);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--bb-ink-faint);
  border-bottom: 1px solid var(--bb-rule-soft);
}
.ref-entry {
  margin: 6px 0;
  font-size: 0.86rem;
}
/* An attack's bold lead-in name doubling as the one-click attack+damage roll —
   the .roll-die gilt-underline grammar, kept bold like its <strong> sibling. */
.ref-attack { font-weight: 600; }
.ref-card-note {
  margin: 8px 0;
  font-family: var(--bb-font-italic);
  font-style: italic;
  color: var(--bb-ink-faint);
}
/* AC/HP/Speed rows reuse .reg-detail; the tighter label column suits the
   short stat words better than the register's 88px. */
.ref-card .reg-detail-label { flex-basis: 76px; }
.ref-card-footer {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px dashed var(--bb-rule);
}
/* Attribution colophon — renders in every card state (CC-BY requirement). */
.ref-card-colophon {
  display: flex;
  gap: 10px;
  margin-left: auto;
  font-size: 0.72rem;
  color: var(--bb-ink-faint);
  white-space: nowrap;
}
.ref-card-colophon a {
  color: inherit;
  text-decoration: underline dotted var(--bb-rule-soft);
  text-underline-offset: 2px;
}
.ref-card-colophon a:hover,
.ref-card-colophon a:focus-visible { color: var(--bb-gilt-deep); }
@media (max-width: 700px) {
  /* Bottom sheet: an anchored popover beside a tapped word is cramped on a
     phone — pin the card full-width to the bottom edge instead. The JS skips
     inline left/top positioning when it applies this class. */
  .ref-card--sheet {
    left: 0;
    right: 0;
    top: auto;
    bottom: 0;
    min-width: 0;
    max-width: none;
    max-height: 80vh;
    border-radius: 10px 10px 0 0;
    box-shadow: 0 -10px 24px -8px var(--bb-shadow-mid);
  }
}

/* Name-suggester popover (#208) — clones the .ref-card anchored-popover idiom:
   fixed parchment card that flips above when it'd overflow and becomes a bottom
   sheet ≤700px. The style select doubles as the header (no title bar). */
.name-suggest-card {
  position: fixed;
  z-index: 60;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 200px;
  max-width: 260px;
  padding: 10px 12px;
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule);
  border-radius: 0;
  box-shadow: 0 10px 24px -8px var(--bb-shadow-mid);
}
.name-suggest-head {
  display: flex;
  align-items: center;
  gap: 8px;
}
/* The style select IS the header — reuses the manuscript-select grammar (small
   caps display face, dotted gilt underline) so it reads as parchment chrome. */
.name-suggest-style {
  flex: 1 1 auto;
  min-width: 0;
  padding: 2px 2px 3px;
  border: 0;
  border-bottom: 1px dotted var(--bb-gilt);
  background: none;
  font-family: var(--bb-font-display);
  font-size: 0.9rem;
  letter-spacing: 0.02em;
  color: var(--bb-ink);
  cursor: pointer;
}
.name-suggest-style:hover { border-bottom-color: var(--bb-gilt-deep); }
.name-suggest-style:focus-visible { outline: 2px solid var(--bb-gilt); outline-offset: 2px; }
/* Animal-recruit lock: the picker is replaced by a static kicker. */
.name-suggest-kicker {
  flex: 1 1 auto;
  font-family: var(--bb-font-display);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--bb-ink-faint);
}
.name-suggest-close {
  flex: 0 0 auto;
  padding: 0 4px;
  border: 0;
  background: none;
  color: var(--bb-ink-faint);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
.name-suggest-close:hover,
.name-suggest-close:focus-visible { color: var(--bb-vermillion); }
.name-suggest-close:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: 2px; }
.name-suggest-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
/* Candidate rows — the names are the star; borrow the .house-role gilt-left-rule
   grammar so the picks feel native to the Household card family. */
.name-suggest-pick {
  width: 100%;
  text-align: left;
  padding: 0.3rem 0.4rem;
  border: 0;
  border-left: 2px solid transparent;
  background: none;
  font-family: var(--bb-font-display);
  font-size: 1.05rem;
  color: var(--bb-ink);
  cursor: pointer;
}
.name-suggest-pick:hover,
.name-suggest-pick:focus-visible {
  background: var(--bb-parchment);
  border-left-color: var(--bb-gilt-deep);
  outline: none;
}
.name-suggest-pick:focus-visible { outline: 2px solid var(--bb-gilt); outline-offset: -2px; }
.name-suggest-foot {
  display: flex;
  justify-content: flex-end;
  margin-top: 2px;
  padding-top: 6px;
  border-top: 1px dashed var(--bb-rule);
}
/* Reroll — quiet, secondary to picking; the dice glyph names the "roll again"
   action (the quill trigger inscribes; the dice reroll). */
.name-suggest-reroll {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 4px;
  border: 0;
  background: none;
  font-family: var(--bb-font-display);
  font-size: 0.82rem;
  letter-spacing: 0.03em;
  color: var(--bb-ink-soft);
  cursor: pointer;
}
.name-suggest-reroll:hover,
.name-suggest-reroll:focus-visible { color: var(--bb-gilt-deep); }
.name-suggest-reroll:focus-visible { outline: 2px solid var(--bb-gilt); outline-offset: 2px; }
.name-suggest-dice { display: inline-flex; width: 1em; height: 1em; }
.name-suggest-dice .glyph-svg { display: block; width: 100%; height: 100%; }
@media (max-width: 700px) {
  .name-suggest-card--sheet {
    left: 0;
    right: 0;
    top: auto;
    bottom: 0;
    min-width: 0;
    max-width: none;
    border-radius: 10px 10px 0 0;
    box-shadow: 0 -10px 24px -8px var(--bb-shadow-mid);
  }
  .name-suggest-card--sheet .name-suggest-pick { padding: 0.5rem 0.4rem; }
}

.reg-detail {
  display: flex;
  gap: 8px;
  padding: 3px 0;
  font-size: 0.86rem;
}
.reg-detail-label {
  flex: 0 0 88px;
  color: var(--bb-ink-faint);
  font-family: var(--bb-font-display);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding-top: 2px;
}
.reg-detail-value { flex: 1 1 auto; color: var(--bb-ink); min-width: 0; }
.reg-fold {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px dashed var(--bb-rule);
}
.reg-full-rules {
  margin-top: 12px;
  border-top: 1px solid var(--bb-rule);
  padding-top: 8px;
}
.reg-full-rules > summary {
  cursor: pointer;
  font-family: var(--bb-font-display);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bb-ink-soft);
  list-style-position: inside;
}
.reg-full-rules > summary:hover { color: var(--bb-ink); }
.reg-full-rules > summary:focus-visible {
  outline: 2px solid var(--bb-gilt);
  outline-offset: 2px;
}
.reg-full-rules[open] > summary { color: var(--bb-ink); margin-bottom: 6px; }
.reg-full-rules-body p {
  margin: 0 0 8px;
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--bb-ink-soft);
}
.reg-full-rules-body p:last-child { margin-bottom: 0; }
.reg-rules-lead { margin-bottom: 4px !important; }
.reg-rules-list {
  margin: 0 0 10px;
  padding-left: 20px;
  list-style: disc;
}
.reg-rules-list li {
  margin: 0 0 4px;
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--bb-ink-soft);
}
.reg-rules-list li:last-child { margin-bottom: 0; }
.reg-full-rules-body strong { color: var(--bb-ink); font-weight: 600; }
.reg-rules-source {
  margin-top: 12px !important;
  padding-top: 8px;
  border-top: 1px dashed var(--bb-rule);
  font-size: 0.82rem;
  color: var(--bb-ink-faint);
}
.reg-rules-source strong { color: var(--bb-ink-faint); }
.reg-show-on-plate {
  /* Sits directly under the Location row it acts on — grouped, not stranded. */
  margin-top: 6px;
  padding: 6px 14px;
  background: transparent;
  border: 1px solid var(--bb-gilt);
  color: var(--bb-gilt-deep);
  font-family: var(--bb-font-display);
  font-size: 0.74rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
}
.reg-show-on-plate:hover { background: var(--bb-gilt-fill); }
.reg-show-on-plate:focus-visible {
  outline: 2px solid var(--bb-gilt);
  outline-offset: 2px;
}

/* Read-only Stores echo: grouped current-state facts + one nav link out to the
   editable Stores dialog. No inputs, no gilt box — these read as facts. */
.reg-stores {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px dashed var(--bb-rule);
}
.reg-stores-head {
  font-family: var(--bb-font-display);
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bb-ink-faint);
  margin-bottom: 6px;
}
.reg-num-max { color: var(--bb-ink-faint); }
.reg-note-echo { font-family: var(--bb-font-italic); font-style: italic; }
.reg-manage-stores {
  margin-top: 6px;
  padding: 2px 0;
  background: none;
  border: 0;
  color: var(--bb-gilt-deep);
  font-family: var(--bb-font-text);
  font-size: 0.84rem;
  cursor: pointer;
}
.reg-manage-stores:hover { text-decoration: underline; }
.reg-manage-stores:focus-visible {
  outline: 2px solid var(--bb-gilt);
  outline-offset: 2px;
}
/* The Hirelings fact-row value doubles as the jump to the Household gallery
   (#154) — roll-die link idiom: the dotted gilt underline carries it, the
   names stay ink. */
.reg-household-jump {
  display: inline;
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  text-decoration: underline dotted var(--bb-gilt);
  text-underline-offset: 2px;
}
.reg-household-jump:hover,
.reg-household-jump:focus-visible { text-decoration-style: solid; color: var(--bb-gilt-deep); }
.reg-household-jump:focus-visible { outline: 2px solid var(--bb-gilt); outline-offset: 2px; }

/* Single column + full-width cards on narrow screens; no horizontal scroll. */
@media (max-width: 640px) {
  .reg-grid { grid-template-columns: 1fr; }
  .reg-card.special.is-open { grid-column: auto; }
  .reg-detail { flex-direction: column; gap: 1px; }
  .reg-detail-label { flex-basis: auto; }
}

/* ---- Plate-spine: single-active-floor switcher (#160, §5.9) ------------- */
/* Left rail of one vellum plate per floor (high→basement), bracketed by dashed
 * + buttons. Retires the per-floor .floor-side gutter — floor verbs moved to
 * the plate context menu (render-spine.js). Slice C adds the vellum polish. */

/* The spine lives inside .stage-body (below the command rail), sticky-left so
 * it stays pinned when a wide map scrolls horizontally — its parchment backing
 * keeps the scrolled canvas from bleeding beneath it. align-self:flex-start so
 * it's only as tall as its plates, not stretched to the canvas height. */
.stage-body {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding-top: 16px;
  min-width: 0;
}
.floor-spine {
  position: sticky;
  left: 0;
  z-index: 5;
  align-self: flex-start;
  flex: 0 0 116px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 2px 4px;
  background: var(--bb-parchment);
}
.floor-spine-plates {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 0;
}

/* One plate. Quiet by default; the active plate lifts + gains a vermillion
 * inboard edge-bar (a fill would read as an armed .mode-btn — precedent:
 * .bastions-menu-row.current .bm-load). */
.floor-plate {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  min-height: 52px;
  padding: 9px 8px 9px 11px;
  /* Vellum card: a faint top-lit parchment gradient + inset sheen. */
  background: linear-gradient(180deg, var(--bb-parchment-light) 0%, var(--bb-parchment) 100%);
  border: 1px solid var(--bb-rule-soft);
  border-top-color: var(--bb-inset-light);
  box-shadow: inset 0 1px 0 var(--bb-inset-light);
  cursor: pointer;
  text-align: left;
  color: var(--bb-ink-soft);
  transition: background 0.12s, color 0.12s;
}
.floor-plate:hover { background: var(--bb-parchment-light); }
.floor-plate:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--bb-vermillion);
}
.floor-plate.is-active {
  background: var(--bb-parchment-light);
  color: var(--bb-ink);
}
.floor-plate.is-active::after {
  content: "";
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: 3px;
  background: var(--bb-vermillion-deep);
}

.plate-eyebrow {
  font-family: var(--bb-font-display);
  font-size: 0.625rem;              /* 10px */
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--bb-ink-faint);
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.floor-plate.is-active .plate-eyebrow { color: var(--bb-vermillion); }
/* Vehicles have no structural word — drop the empty eyebrow line. */
.plate-eyebrow:empty { display: none; }

.plate-label {
  font-family: var(--bb-font-display);
  font-size: 0.9375rem;             /* 15px */
  line-height: 1.15;
  color: inherit;
  /* Clamp to 2 lines; the full name rides the tooltip. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
}
/* An unnamed floor has no user-name line — collapse it so the plate shows just
 * the eyebrow (no empty gap). Renaming re-inflates it via the [contenteditable]
 * rule below. */
.plate-label:empty { display: none; }
/* Rename affordance — contenteditable is switched on only during a rename. */
.plate-label[contenteditable] {
  display: block;
  overflow: visible;
  cursor: text;
  outline: none;
  background: var(--bb-parchment-light);
  box-shadow: 0 0 0 1px var(--bb-vermillion);
}

/* ⋯ verbs button on the active plate — persistent-but-faint (the only touch
 * path to floor verbs once the .floor-actions links are gone). */
.plate-menu-btn {
  position: absolute;
  top: 2px;
  right: 5px;
  width: 20px;
  height: 20px;
  padding: 0;
  line-height: 1;
  background: transparent;
  border: none;
  box-shadow: none;
  color: var(--bb-ink-faint);
  font-size: 1rem;
  cursor: pointer;
  opacity: 0.55;
}
.plate-menu-btn:hover { opacity: 1; color: var(--bb-vermillion); }

/* + Add-floor / + Add-basement — quiet dashed affordances at spine top & bottom
 * (reuse the retired .add-floor-btn vocabulary, glyph-only). */
.spine-add-btn {
  flex: 0 0 auto;
  padding: 8px 8px 8px 11px;
  text-align: left;
  background: transparent;
  border: 1px dashed var(--bb-rule);
  color: var(--bb-ink-soft);
  font-family: var(--bb-font-display);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.1;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: none;
  border-radius: 0;
}
.spine-add-btn:hover {
  border-color: var(--bb-vermillion);
  color: var(--bb-vermillion);
  background: rgba(168,40,29,0.06);
}

/* Compact floor switcher — hidden on the wide layout (the plates are the
 * switcher there), shown ≤1099px in place of the plates. */
/* The wrapper gates visibility (hidden on desktop, shown ≤1099px); the button
 * itself is always block so it fills the wrap when shown. */
.floor-select-wrap { display: none; width: 100%; max-width: 260px; }
.floor-select {
  display: block;
  width: 100%;
  text-align: left;
  font-family: var(--bb-font-display);
  font-size: 0.8rem;
  /* A custom-dropdown trigger dressed as the old themed select: strip button
   * chrome and draw a hand-inked caret so it reads as a picker. Roomy padding
   * gives a ~40px thumb target at the phone tier (#218). */
  appearance: none;
  -webkit-appearance: none;
  padding: 9px 28px 9px 10px;
  border: 1px solid var(--bb-rule);
  border-radius: 0;
  box-shadow: none;
  background-color: var(--bb-parchment-light);
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='10'%20height='7'%20viewBox='0%200%2010%207'%3E%3Cpath%20d='M1%201l4%204%204-4'%20fill='none'%20stroke='%236b5433'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  color: var(--bb-ink);
  cursor: pointer;
}
/* The global `button:hover` uses the `background` *shorthand*, which resets
 * background-repeat to `repeat` and background-position to 0 0 — that tiles the
 * caret across the trigger into a chevron zigzag while the menu is open (#218,
 * the caret survives because the [aria-expanded] rule outspecifies the hover).
 * Re-assert the picker's own fill + caret on hover (these outspecify the global
 * chrome) so the caret stays a single mark and the parchment fill is kept. */
.floor-select:hover {
  background-color: var(--bb-parchment-light);
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='10'%20height='7'%20viewBox='0%200%2010%207'%3E%3Cpath%20d='M1%201l4%204%204-4'%20fill='none'%20stroke='%236b5433'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
}
/* Caret flips up while the menu is open — the tell that this is a menu trigger,
 * not an inert field. Carries onto :hover so the open + hovered trigger keeps
 * the single up-caret rather than the down one (see the #218 note above). */
.floor-select[aria-expanded="true"],
.floor-select[aria-expanded="true"]:hover {
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='10'%20height='7'%20viewBox='0%200%2010%207'%3E%3Cpath%20d='M1%205l4-4%204%204'%20fill='none'%20stroke='%236b5433'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'/%3E%3C/svg%3E");
}
} /* @layer components — pause for the stray theme rule (#259) */
/* Light caret over the arcane theme's dark panel. Lives in the themes layer
   with the rest of the [data-theme] overrides. */
@layer themes {
[data-theme="arcane"] .floor-select,
[data-theme="arcane"] .floor-select:hover {
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='10'%20height='7'%20viewBox='0%200%2010%207'%3E%3Cpath%20d='M1%201l4%204%204-4'%20fill='none'%20stroke='%23bfb8d6'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'/%3E%3C/svg%3E");
}
[data-theme="arcane"] .floor-select[aria-expanded="true"],
[data-theme="arcane"] .floor-select[aria-expanded="true"]:hover {
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='10'%20height='7'%20viewBox='0%200%2010%207'%3E%3Cpath%20d='M1%205l4-4%204%204'%20fill='none'%20stroke='%23bfb8d6'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'/%3E%3C/svg%3E");
}
} /* @layer themes */
@layer components {
/* Pin the menu to the trigger's width (never narrower than its opener); grow to
 * fit the longest floor label, capped to the viewport. .bastions-menu already
 * gives max-height:60vh + overflow-y:auto, so a tall building scrolls inside.
 * Compound selector (.bastions-menu.floor-select-menu) so min-width:100% beats
 * the phone-tier `.bastions-menu { min-width:0 }` rule, which would otherwise
 * let the menu render narrower than its trigger on small phones. */
.bastions-menu.floor-select-menu {
  min-width: 100%;
  width: max-content;
  max-width: calc(100vw - 24px);
}
/* ≤1099px: the vellum plates give way to the compact dropdown; the spine keeps
 * its + buttons above/below. The seam + right-column library stay. */
@media (max-width: 1099px) {
  .floor-spine-plates { display: none; }
  .floor-select-wrap { display: block; }
}

/* ---- Active floor frame (was .floor-row) ------------------------------- */
/* #floor-stack now holds exactly one .plate-frame (the active floor). */
.floor-stack { gap: 0; }
.plate-frame {
  position: relative;
  display: flex;
  min-width: 0;
}

/* Read-only all-floors overview lightbox (#160 follow-up): the export-style
 * stack, captioned per floor, high→basement, inside a modal. The stack scrolls
 * WITHIN the dialog body — the hero canvas never re-stacks, so #160's no-scroll
 * editing win is preserved. */
/* .modal.overview-dialog, not .overview-dialog: the generic `.modal` rule
 * (padding:0; max-width:520px) is defined later in the file with equal
 * specificity, so it would otherwise win — leaving the overview edge-to-edge
 * and capped at 520px (#218). */
.modal.overview-dialog { max-width: min(920px, 94vw); padding: 20px 22px 16px; }
.overview-dialog-head { margin-bottom: 12px; }
.overview-body {
  max-height: 68vh;
  overflow: auto;
  /* Scroll gutter so the last floor never sits under the scrollbar. */
  padding-right: 12px;
  scrollbar-gutter: stable;
}
.floor-overview { display: flex; flex-direction: column; gap: 22px; }
.overview-floor { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; max-width: 100%; }
.overview-floor-cap {
  font-family: var(--bb-font-display);
  font-size: 0.8rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bb-ink-soft);
}
/* Scale each floor's export SVG to fit the dialog width, keeping aspect. */
.overview-canvas { max-width: 100%; }
.overview-canvas svg { max-width: 100%; height: auto; }
.overview-dialog-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }
.overview-dialog-actions button {
  font-family: var(--bb-font-display);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 7px 14px;
  background: var(--bb-parchment);
  border: 1px solid var(--bb-rule-soft);
  color: var(--bb-ink);
  cursor: pointer;
}
.overview-dialog-actions button:hover { border-color: var(--bb-gilt); }
.overview-dialog-actions .is-close { background: transparent; border-color: transparent; color: var(--bb-ink-soft); }
.overview-dialog-actions .is-close:hover { color: var(--bb-ink); border-color: var(--bb-rule-soft); }

/* Lore & Notes textarea (Charter, §5.4) — the editor that the read-mode lore
 * view swaps in. */
.notes-field {
  width: 100%;
  box-sizing: border-box;
  background: var(--bb-parchment-mid);
  border: 1px dashed var(--bb-rule-soft);
  border-radius: 0;
  padding: 10px 12px;
  resize: vertical;
  font-family: var(--bb-font-text);
  font-size: 0.9375rem;
  color: var(--bb-ink);
}
.notes-field::placeholder {
  color: var(--bb-ink-faint);
  font-family: var(--bb-font-italic);
  font-style: italic;
}
.notes-field:focus { outline: none; border-color: var(--bb-rule); }
.dialog-sub {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.875rem;
  color: var(--bb-ink-soft);
  margin: -0.5rem 0 1rem;
}

/* Annotation editor popover (§6.5.10). Floating panel anchored next to
 * the clicked cell by JS. Parchment-light chrome with a sepia drop shadow,
 * italic input that reads as handwritten marginalia. */
.annotation-popover {
  position: fixed;
  z-index: 50;
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule);
  border-radius: 0;
  box-shadow: 0 10px 24px -8px var(--bb-shadow-mid);
  padding: 8px;
  min-width: 240px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Kind picker (#163) reuses .type-pick/.type-pick-btn verbatim — see the
 * "Status-zone type-picker" rule further down; .note-kind-seg/.note-kind-btn
 * are scoping hooks only, no chrome of their own, matching door/stair. */

/* Size segmented control + rotate */
.annotation-meta-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.annotation-size-seg {
  display: flex;
  gap: 0;
}
.annotation-size-btn {
  padding: 2px 8px;
  font-size: 0.8125rem;
  font-family: var(--bb-font-display);
  letter-spacing: 0.05em;
  background: transparent;
  border: 1px solid var(--bb-rule);
  border-right-width: 0;
  color: var(--bb-ink-soft);
  cursor: pointer;
  border-radius: 0;
  box-shadow: none;
}
.annotation-size-btn:last-child { border-right-width: 1px; }
.annotation-size-btn:hover { background: var(--bb-parchment-mid); color: var(--bb-ink); }
.annotation-size-btn[aria-pressed="true"] {
  background: var(--bb-gilt-deep);
  border-color: var(--bb-gilt-deep);
  color: var(--bb-parchment-light);
}

/* Status-zone type-picker (#175): the on-arm segmented pick for Openings
 * (Door·Window·Arch) and Defenses (Wall·Gate), shown in the placement banner.
 * Mirrors the .annotation-size joined-plaque; selected = gilt-deep (gilt =
 * current selection within a tool; vermillion stays the ACTIVE-tool signal). */
.type-pick {
  display: flex;
  gap: 0;
  align-items: center;
}
.type-pick-btn {
  /* #218 — contain the .sr-only radio (see .theme-tile) so focusing it on
   * click can't scroll the enclosing dialog off-screen. */
  position: relative;
  padding: 4px 10px;
  font-size: 0.8125rem;
  font-family: var(--bb-font-display);
  letter-spacing: 0.05em;
  white-space: nowrap;
  background: transparent;
  border: 1px solid var(--bb-rule);
  border-right-width: 0;
  color: var(--bb-ink-soft);
  cursor: pointer;
  border-radius: 0;
  box-shadow: none;
}
.type-pick-btn:last-child { border-right-width: 1px; }
.type-pick-btn:hover { background: var(--bb-parchment-mid); color: var(--bb-ink); }
.type-pick-btn[aria-pressed="true"] {
  background: var(--bb-gilt-deep);
  border-color: var(--bb-gilt-deep);
  color: var(--bb-parchment-light);
}
/* A type segment the object can't become right now — a stair kind whose
 *  footprint won't fit at its anchor (#176 inspector). Muted + inert. */
.type-pick-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  color: var(--bb-ink-faint);
}
.type-pick-btn:disabled:hover { background: transparent; color: var(--bb-ink-faint); }
/* #180 Map marks — the North picker reuses the .type-pick joined-plaque chrome
 * as a native <label>-wrapped radiogroup (like the Handout palette picker in the
 * same dialog), so selected/disabled state comes from the input, not aria-pressed.
 * Scoped to the settings dialog and the Grounds Map display popover (#224);
 * reuses existing tokens, adds none. */
.settings-body .type-pick-btn:has(input:checked),
#map-display-menu .type-pick-btn:has(input:checked) {
  background: var(--bb-gilt-deep);
  border-color: var(--bb-gilt-deep);
  color: var(--bb-parchment-light);
}
/* #258 added #vtt-export-dialog: the VTT dialog gained its first disabled
 * SEGMENTS (Background/Palette gate under the data formats). #257 only ever
 * disabled the native <select>, which carries its own rule, so without this a
 * dead segment rendered fully live — and the :hover companion below matters
 * just as much, or it still lights up under the cursor. */
.settings-body .type-pick-btn:has(input:disabled),
#map-display-menu .type-pick-btn:has(input:disabled),
#vtt-export-dialog .type-pick-btn:has(input:disabled) {
  opacity: 0.4;
  cursor: not-allowed;
  color: var(--bb-ink-faint);
}
.settings-body .type-pick-btn:has(input:disabled):hover,
#map-display-menu .type-pick-btn:has(input:disabled):hover,
#vtt-export-dialog .type-pick-btn:has(input:disabled):hover { background: transparent; }
/* #215 VTT export dialog — reuse the settings radio idioms outside the settings
 * body: the .type-pick segments and .theme-tile grid take their selected state
 * from the checked radio (no JS class toggle needed). */
#vtt-export-dialog .type-pick-btn:has(input:checked) {
  background: var(--bb-gilt-deep);
  border-color: var(--bb-gilt-deep);
  color: var(--bb-parchment-light);
}
#vtt-export-dialog .theme-tile:has(input:checked) {
  border-color: var(--bb-vermillion-deep);
  box-shadow: 0 0 0 1px var(--bb-vermillion-deep);
}
#vtt-export-dialog .theme-tile-desc {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.75rem;
  color: var(--bb-ink-soft);
}
/* The .dialog-form is a flex column with a 14px gap; the intro paragraph's 1rem
 * bottom margin stacked on top of that gap AND the first subhead's top margin,
 * so "Grid Resolution" floated in far more space than the later headings. Zero
 * the paragraph margin and trim the subhead margins so all three read evenly. */
#vtt-export-dialog .dialog-sub { margin-bottom: 0; }
#vtt-export-dialog .settings-subhead { margin-top: 0.15rem; margin-bottom: 0; }
/* Per-field caption under a VTT radiogroup (#224 Palette): disambiguates ink
 * colour from the Background opacity radio above it. Shares its register with
 * the readout's hint line (#257) — one declaration block, two callers. */
.vtt-field-note,
#vtt-export-dialog .vtt-readout-hint {
  margin: 4px 0 0;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--bb-ink-soft);
}
#vtt-export-dialog .theme-option-group { padding-top: 8px; padding-bottom: 0; }
/* #258 — one column, still, now that there are two tiles rather than three.
 * Not the 2-col .theme-option-group default: each tile carries an "is this me?"
 * sentence that needs a full line, and at .modal's 520px a 2-col tile has a
 * ~209px text box while "Map with walls and doors" measures ~209px in the
 * display face — it wraps on the widest theme and not on others. This is also
 * the carve-out from the house "2 options = segmented .type-pick" rule
 * (Floors/Background/Palette below): .type-pick-btn is one nowrap line and
 * cannot host the desc, and this is the question that changes what the others
 * mean. Already phone-shaped, so the ≤480px block below needs nothing for it. */
#vtt-export-dialog .vtt-format-group { grid-template-columns: 1fr; }

/* #257 — three-part scaffold copied from #settings-dialog: the choices scroll,
 * the readout and the Download row stay pinned. Without it the UA scrolls the
 * whole <dialog>, carrying the title and the button off-screen; adding the
 * readout + floor select made that the common case on a phone. */
#vtt-export-dialog { overflow: hidden; }
#vtt-export-dialog .dialog-form {
  max-height: 88vh;
  max-height: 88dvh;   /* the phone URL-bar fix */
}
#vtt-export-dialog .vtt-export-body {
  min-height: 0;       /* let the flex child shrink below its content … */
  overflow-y: auto;    /* … and scroll */
  padding-right: 12px; /* house gutter so the scrollbar doesn't crowd the tiles */
  scrollbar-gutter: stable;
  display: flex;
  flex-direction: column;
  gap: 14px;           /* the .dialog-form rhythm, now that the groups are nested */
}
#vtt-export-dialog .vtt-readout,
#vtt-export-dialog .dialog-actions { flex-shrink: 0; }

/* The floor picker sits under the Floors segments; the .settings-subhead above
 * heads the group, so the select carries only an aria-label. Wrapping the three
 * into one element (so a single-floor bastion can hide the lot) makes them one
 * flex item, which drops the form's 14px rhythm between the subhead and its
 * segments — restate it. The select keeps the tighter 6px: it is a sub-control
 * of the radio above it, not the next question. */
#vtt-export-dialog #vtt-floors-group .settings-subhead { margin-bottom: 14px; }
.vtt-floor-pick { margin-top: 6px; }
#vtt-export-dialog .vtt-floor-pick select { width: 100%; }
#vtt-export-dialog .vtt-floor-pick select:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* The computed-result footer strip: display-face figure, colophon aside (the
 * middot register the exported sheet's own footer band speaks in), italic hint.
 * .dialog-actions carries no top border, so this hairline is the only seam and
 * reads as the ruled foot of a parchment form. */
.vtt-readout {
  margin: 0;
  padding-top: 10px;
  border-top: 1px solid var(--bb-rule);
}
.vtt-readout-figure {
  font-family: var(--bb-font-display);
  font-size: 0.9375rem;
  color: var(--bb-ink);
}
.vtt-readout-aside {
  font-size: 0.8125rem;
  color: var(--bb-ink-soft);
}
#vtt-export-dialog .vtt-readout-hint { display: block; margin-top: 2px; }
/* Same two registers as .dialog-warn (gilt) and .field-error (vermillion),
 * restated rather than cross-scoped so neither selector grows a dependency on
 * this dialog. Colour never carries the state alone — the wording changes too.
 * The `#vtt-export-dialog` prefix is load-bearing, not decoration: the hint
 * shares its base declarations with .vtt-field-note under an ID selector, so a
 * bare `.vtt-readout[data-state=…]` rule loses on specificity and the states
 * render identical ink-soft. Cascade layers don't help — same layer. */
#vtt-export-dialog .vtt-readout[data-state="warn"] .vtt-readout-hint { color: var(--bb-gilt-deep); }
#vtt-export-dialog .vtt-readout[data-state="over"] .vtt-readout-hint { color: var(--bb-vermillion); }

/* Phone tier: stack the segmented pickers. .type-pick-btn is deliberately
 * `white-space: nowrap`, so at ~292px of dialog body the Palette row measured
 * 355px and scrolled the body sideways — Floors did too, and Background only
 * escaped by a hair. Stacking gives every segment the full width on one line
 * and keeps the body's only scroll axis vertical. Segments join by suppressing
 * the right border except on the last child, so a column has to suppress the
 * bottom one instead and give every segment its right border back. Scoped to
 * this dialog: .type-pick elsewhere is short enough to stay inline. */
@media (max-width: 480px) {
  #vtt-export-dialog .vtt-radio-seg { flex-direction: column; align-items: stretch; }
  #vtt-export-dialog .vtt-radio-seg .type-pick-btn {
    border-right-width: 1px;
    border-bottom-width: 0;
    text-align: left;
  }
  #vtt-export-dialog .vtt-radio-seg .type-pick-btn:last-child { border-bottom-width: 1px; }
}
/* Five-segment Stairs picker on a tablet-touch banner (701–900px): drop the
 *  picker onto its own banner line so segments never truncate (#176). Scoped to
 *  stairs — the 2–3-segment Openings/Defenses pickers still share the row. */
@media (max-width: 900px) {
  body[data-edit-mode="stairs"] .placement-banner { flex-wrap: wrap; }
  body[data-edit-mode="stairs"] #pb-typepick {
    order: 10;
    flex-basis: 100%;
    margin-top: 4px;
  }
  /* Defenses carries two pickers since #237 (Wall·Gate + the floor count) —
   * five segments between them, so it wraps like Stairs. Both drop to the
   * second line together and keep their 10px banner gap. */
  body[data-edit-mode="defenses"] .placement-banner { flex-wrap: wrap; }
  body[data-edit-mode="defenses"] #pb-typepick,
  body[data-edit-mode="defenses"] #pb-typepick-2 {
    order: 10;
    margin-top: 4px;
  }
  body[data-edit-mode="defenses"] #pb-typepick { margin-left: auto; }
}
/* Persistent ▾ on a sub-typed toolbar tool (#175 discoverability backstop) —
 * currentColor so it stays legible on the vermillion active button. */
.mode-btn .mode-caret {
  opacity: 0.55;
  font-size: 0.7em;
  margin-left: 2px;
}
.annotation-rotate-btn {
  padding: 2px 6px;
  background: transparent;
  border: 1px solid var(--bb-rule);
  color: var(--bb-ink-soft);
  cursor: pointer;
  border-radius: 0;
  box-shadow: none;
  display: flex;
  align-items: center;
}
.annotation-rotate-btn:hover { background: var(--bb-parchment-mid); color: var(--bb-ink); }
.annotation-rotate-btn[aria-pressed="true"] {
  background: var(--bb-gilt-deep);
  border-color: var(--bb-gilt-deep);
  color: var(--bb-parchment-light);
}
.annotation-rotate-btn .glyph-svg {
  width: 14px;
  height: 14px;
  display: block;
}

/* Textarea */
.annotation-popover textarea {
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 4px 6px;
  color: var(--bb-ink);
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.9375rem;
  width: 100%;
  box-sizing: border-box;
  box-shadow: none;
  resize: none;
  field-sizing: content;
  min-height: 2.5em;
}
.annotation-popover textarea:focus {
  outline: none;
  background: var(--bb-parchment-mid);
}

/* Legend helper line (#163) — shown only when the Legend kind is selected.
 * Names what the marker becomes; the #181 Map-key strip below is the actual
 * tether. Deliberately not .dialog-warn (that's dialog-scoped, reads as a
 * caution, not a fact). */
.annotation-legend-hint {
  margin: 0;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8rem;
  color: var(--bb-ink-soft);
}

/* Map-key strip + keyed-marker ↔ legend tether (#181) — the in-app legend
 * beneath each floor. Numbering is shared with the canvas discs + handout/SVG
 * key (keyedAnnotationsForFloor). No new tokens: the badge + rail-bar are an
 * identity echo of the (already vermillion, #163) disc, NOT a new
 * "vermillion = state" use, so they stay vermillion, not gilt. Every tether
 * state pairs hue with a shape cue (rail-bar / ring / badge) → not color-only. */
.map-key-strip {
  margin-top: 8px;
  max-width: 100%;
}
.map-key-strip-head {
  font-family: var(--bb-font-display);
  font-size: 0.6875rem;              /* 11px */
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bb-ink-faint);
  margin-bottom: 4px;
}
.map-key-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.map-key-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  padding: 3px 6px;
  border: none;
  border-radius: 0;
  background: transparent;
  box-shadow: inset 3px 0 0 transparent;
  text-align: left;
  line-height: 1.5;
  cursor: pointer;
  transition: background-color 0.12s, box-shadow 0.12s;
}
/* Rest → linked/hover/active share the rail-bar + parchment wash. The active
 * row (aria-current, set while its editor popover is open) carries .is-linked
 * persistently; the attribute is the SR differentiator, not extra colour. */
.map-key-row:hover,
.map-key-row.is-linked {
  background: var(--bb-parchment-mid);
  box-shadow: inset 3px 0 0 var(--bb-vermillion);
}
.map-key-badge {
  flex: 0 0 auto;
  align-self: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--bb-vermillion);
  border: 1.5px solid var(--bb-vermillion-deep);
  color: #f8efd4;                    /* the disc's literal parchment numeral */
  font-family: var(--bb-font-display);
  font-weight: 500;
  font-size: 11px;
  font-variant-numeric: lining-nums tabular-nums;
  display: flex;
  align-items: center;
  justify-content: center;
}
.map-key-row .label {
  font-family: var(--bb-font-italic);
  font-style: italic;
  color: var(--bb-ink-soft);
  white-space: normal;               /* wrap, never truncate (annotation convention) */
  overflow-wrap: anywhere;
}
/* Tether echo on the canvas disc: a static ring reusing the :focus shape. */
.annotation-keyed.is-linked {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}
/* One-shot reveal glow when a map-key row is clicked — reuses the #89 SVG
 * drop-shadow mechanism (vermillion here, an identity echo — not the temporal
 * gilt highlight). Motion-gated; under reduced motion the scroll carries it. */
@media (prefers-reduced-motion: no-preference) {
  .annotation-keyed.is-revealing {
    animation: map-key-reveal 1.2s ease-out forwards;
  }
}
@keyframes map-key-reveal {
  0% {
    filter: drop-shadow(0 0 3px var(--bb-vermillion))
            drop-shadow(0 0 7px var(--bb-vermillion));
  }
  100% {
    filter: drop-shadow(0 0 0 transparent);
  }
}

.annotation-popover-actions {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
}
.annotation-popover-actions button {
  padding: 4px 12px;
  font-family: var(--bb-font-display);
  font-size: 0.8125rem;              /* 13px */
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.charter-identity .roster { display: flex; flex-direction: column; gap: 14px; }
.charter-identity .char-focus-btn {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.9375rem;
  color: var(--bb-vermillion);
  text-decoration: underline dotted;
  text-underline-offset: 2px;
}
.charter-identity .char-focus-btn:hover { color: var(--bb-vermillion-deep); }
.charter-identity .char-focus-btn.active { color: var(--bb-ink-soft); }
.charter-identity .char-focus-btn.active:hover { color: var(--bb-ink); }
.charter-identity .char-focus-btn.btn-add-char { color: var(--bb-ink-soft); }
.charter-identity .char-focus-btn.btn-add-char:hover { color: var(--bb-ink); }
.charter-identity .char-focus-btn.btn-remove-char { color: var(--bb-danger); }
.charter-identity .char-focus-btn.btn-remove-char:hover { color: var(--bb-danger-deep); }

/* ---- Owner cards (#147) -------------------------------------------------- */

.charter-owners {
  margin-top: 18px;
  padding-top: 0;
  border-top: none;
}
.owners-label { margin-bottom: 10px; }
.party-name-field { margin-bottom: 14px; }
.party-name {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.875rem;
  background: transparent;
  border: 1px solid transparent;
  color: var(--bb-ink);
  padding: 2px 6px;
  box-shadow: none;
  border-radius: 0;
  width: 100%;
}
.party-name:hover {
  background: var(--bb-parchment-mid);
  border-color: var(--bb-rule-soft);
}
.party-name:focus {
  background: var(--bb-parchment-light);
  border-color: var(--bb-vermillion);
  outline: none;
}
.owner-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 14px;
}
.owner-card {
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule);
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  position: relative;
}
/* "+ Add owner" tile — dashed ghost card */
button.owner-card.owner-add {
  background: none;
  border: 1px dashed var(--bb-rule-soft);
  color: var(--bb-ink-soft);
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.9375rem;
  cursor: pointer;
  min-height: 72px;
  align-self: start;
  display: flex;
  align-items: center;
  justify-content: center;
}
button.owner-card.owner-add:hover {
  border-color: var(--bb-vermillion);
  color: var(--bb-vermillion);
}
/* Owner head: portrait + id column side by side */
.owner-head {
  display: flex;
  gap: 12px;
  align-items: flex-start;
}
.owner-portrait-wrap {
  position: relative;
  flex-shrink: 0;
}
/* Square ~72px portrait miniature — mirrors .crest-slot */
.owner-portrait {
  display: block;
  width: 72px;
  height: 72px;
  padding: 2px;
  background: var(--bb-parchment);
  border: 1px solid var(--bb-rule-soft);
  border-radius: 0;
  cursor: pointer;
  box-shadow: none;
  overflow: hidden;
}
.owner-portrait-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* No portrait uploaded (#272): a monogram in the display face, or — with no
   name to abbreviate — bare parchment inside the dashed slot. What this replaced
   was a line-art user icon baked into a data-URI: line art and a modern object,
   both against the icon vocabulary, and a reader of a shared snapshot met a row
   of them. */
.owner-portrait-monogram,
.owner-portrait-blank {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  background: var(--bb-parchment-mid);
}
.owner-portrait-monogram {
  font-family: var(--bb-font-display);
  font-size: 2.2rem;
  line-height: 1;
  color: var(--bb-ink-faint);
  user-select: none;
}
.owner-portrait.is-placeholder {
  border-style: dashed;
  border-color: var(--bb-rule-soft);
}
.owner-portrait:not(.is-placeholder):hover,
.owner-portrait:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}
.owner-portrait.is-placeholder:hover,
.owner-portrait.is-placeholder:focus-visible {
  border-color: var(--bb-vermillion);
  outline: none;
}
/* Portrait upload/replace/remove menu — opens below the portrait */
.owner-portrait-menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 200;
  min-width: 160px;
}
.owner-id {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.owner-id .char-name {
  font-family: var(--bb-font-display);
  font-size: 1rem;
  font-weight: 600;
  color: var(--bb-ink);
  background: transparent;
  border: 1px solid transparent;
  padding: 2px 4px;
  box-shadow: none;
  border-radius: 0;
  width: 100%;
}
.owner-id .char-name:hover {
  background: var(--bb-parchment-mid);
  border-color: var(--bb-rule-soft);
}
.owner-id .char-name:focus {
  background: var(--bb-parchment-light);
  border-color: var(--bb-vermillion);
  outline: none;
}
.owner-meta {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}
.owner-level-label {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.875rem;
  color: var(--bb-ink-soft);
  display: flex;
  align-items: baseline;
  gap: 4px;
  white-space: nowrap;
}
.owner-level-label .lvl {
  width: 2em;
  min-width: 0;
  color: var(--bb-vermillion-deep);
  font-family: var(--bb-font-display);
  font-style: normal;
  font-weight: 600;
  font-variant-numeric: lining-nums tabular-nums;
  text-align: center;
  background: transparent;
  border: 1px solid transparent;
  padding: 1px 2px;
  box-shadow: none;
  border-radius: 0;
  -moz-appearance: textfield;
}
.owner-level-label .lvl::-webkit-outer-spin-button,
.owner-level-label .lvl::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.owner-level-label .lvl:hover {
  background: var(--bb-parchment-mid);
  border-color: var(--bb-rule-soft);
}
.owner-level-label .lvl:focus {
  background: var(--bb-parchment-light);
  border-color: var(--bb-vermillion);
  outline: none;
}
.owner-class {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.84rem;
  color: var(--bb-ink-soft);
  background: transparent;
  border: 1px solid transparent;
  padding: 1px 4px;
  box-shadow: none;
  border-radius: 0;
  min-width: 0;
  flex: 1 1 0;
  box-sizing: border-box;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.owner-class:hover {
  background: var(--bb-parchment-mid);
  border-color: var(--bb-rule-soft);
}
.owner-class:focus {
  background: var(--bb-parchment-light);
  border-color: var(--bb-gilt-deep);
  outline: none;
}
.owner-class::placeholder { color: var(--bb-ink-faint); font-style: italic; }
/* Owner standing: credential chip groups */
.owner-standing {
  display: flex;
  flex-direction: column;
  gap: 8px;
  position: relative;
}
.cred-group { display: flex; flex-direction: column; gap: 4px; }
.cred-group-label {
  font-family: var(--bb-font-display);
  font-size: 0.75rem;
  color: var(--bb-ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.cred-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
/* Base chip — parchment-mid background; vermillion/sapphire stay for facility kinds */
.cred-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  background: var(--bb-parchment-mid);
  border: 1px solid var(--bb-rule-soft);
  font-family: var(--bb-font-text);
  font-size: 0.8125rem;
  color: var(--bb-ink-soft);
  cursor: default;
  white-space: nowrap;
}
/* Dashed "+" add-chip — resets to a clickable ghost */
button.cred-chip.cred-add {
  background: none;
  border-style: dashed;
  border-color: var(--bb-rule-soft);
  color: var(--bb-ink-faint);
  cursor: pointer;
  align-self: flex-start;
}
button.cred-chip.cred-add:hover {
  border-color: var(--bb-vermillion);
  color: var(--bb-vermillion);
}
/* Renown chip carries a score input inline */
.cred-chip-renown { padding: 2px 6px; }
.cred-renown-house {
  font-family: var(--bb-font-text);
  font-size: 0.8125rem;
  color: var(--bb-ink-soft);
}
/* The score reads as inline editable illumination on the chip's parchment,
 * not a form field dropped into it: no box, no spinners — just a hairline
 * underline (#218). */
.cred-renown-score {
  width: 34px;
  padding: 0 2px;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: textfield;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--bb-rule-soft);
  border-radius: 0;
  color: var(--bb-ink);
  font-family: var(--bb-font-text);
  font-size: 0.8125rem;
  font-variant-numeric: lining-nums tabular-nums;
  text-align: right;
  line-height: 1.4;
}
.cred-renown-score::-webkit-inner-spin-button,
.cred-renown-score::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.cred-renown-score:focus {
  outline: 2px solid var(--bb-gilt-deep);
  outline-offset: 1px;
}
/* × remove button inside a chip */
.cred-chip-remove {
  background: none;
  border: none;
  padding: 0;
  color: var(--bb-ink-faint);
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  margin-left: 2px;
}
.cred-chip-remove:hover { color: var(--bb-vermillion); }
.cred-chip-remove:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: 1px; }
/* Add-credential dropdown menu */
.cred-add-menu {
  position: absolute;
  z-index: 200;
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule);
  box-shadow: var(--shadow-pop);
  min-width: 200px;
  max-width: 280px;
}
.cred-menu-group + .cred-menu-group {
  border-top: 1px solid var(--bb-rule-soft);
}
.cred-menu-group-label {
  font-family: var(--bb-font-display);
  font-size: 0.75rem;
  color: var(--bb-ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 6px 12px 2px;
}
.cred-menu-row {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 5px 12px;
  font-family: var(--bb-font-text);
  font-size: 0.84rem;
  color: var(--bb-ink-soft);
  cursor: pointer;
}
.cred-menu-row:hover,
.cred-menu-row:focus-visible { background: var(--bb-parchment-mid); color: var(--bb-ink); outline: none; }
.cred-menu-empty {
  padding: 8px 12px;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.84rem;
  color: var(--bb-ink-faint);
}
/* Owner card footer */
.owner-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  min-height: 1.4em;   /* reserve space even when empty (single owner) */
}
.owner-foot-btn {
  background: none;
  border: none;
  padding: 0;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.875rem;
  cursor: pointer;
  color: var(--bb-danger);
  text-decoration: underline dotted;
  text-underline-offset: 2px;
}
.owner-foot-btn:hover { color: var(--bb-danger-deep); }
/* Import button — same italic-link idiom as remove, non-danger color */
.owner-import-btn { color: var(--bb-ink-soft); }
.owner-import-btn:hover { color: var(--bb-vermillion); }
/* Picker hint → jump link. A <button> in link clothing: the global `button`
   chrome (gradient, border, box-shadow, :hover gradient, :active nudge) must
   be beaten per-state — `button:hover` outspecifies a lone class, so the
   hover/active resets below are load-bearing, not belt-and-braces. */
.picker-hint-link {
  background: none;
  border: none;
  box-shadow: none;
  padding: 0;
  font: inherit;
  color: var(--bb-vermillion);
  text-decoration: underline dotted;
  text-underline-offset: 2px;
  cursor: pointer;
}
.picker-hint-link:hover {
  background: none;
  border: none;
  color: var(--bb-vermillion-deep);
}
.picker-hint-link:active { transform: none; }
/* Owner card highlight — vermillion pulse on picker-hint jump */
@keyframes owner-highlight-pulse {
  0%   { outline-color: var(--bb-vermillion); outline-offset: 0px; }
  50%  { outline-color: var(--bb-vermillion); outline-offset: 3px; }
  100% { outline-color: transparent; outline-offset: 3px; }
}
.owner-card.is-highlight {
  outline: 2px solid var(--bb-vermillion);
}
@media (prefers-reduced-motion: no-preference) {
  .owner-card.is-highlight {
    animation: owner-highlight-pulse 1.2s ease-out forwards;
  }
}
/* Status toast (Codex §7.3) — floats at the bottom of the viewport with
 * a parchment-light gradient and per-status border tints. */
.status-toast {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  min-width: 160px;
  max-width: 80vw;
  padding: 10px 18px;
  border-radius: 0;
  background: linear-gradient(180deg, var(--bb-parchment-light), var(--bb-parchment-mid));
  color: var(--bb-ink);
  font-family: var(--bb-font-display);
  font-size: 0.875rem;              /* 14px */
  border: 1px solid var(--bb-rule);
  box-shadow: 0 8px 24px -8px var(--bb-shadow-deep);
  z-index: 50;
  text-align: center;
  pointer-events: none;
}
.status-toast:empty { display: none; }
.status-toast.success { border-color: var(--bb-verdigris); }
.status-toast.error   { border-color: var(--bb-danger); }
.status-toast.warn    { border-color: var(--bb-gilt); }

/* ---- Themed confirm + choice dialog (Codex §7.1) ----------------------- */

} /* @layer components — pause: the base modal shell belongs in `base` (#259) */

/* The documented footgun (#259): this default — padding:0; max-width:520px —
   sat physically late in the file and clobbered earlier per-dialog overrides
   purely by source order (the `.modal.overview-dialog` case). In the `base`
   layer it is beaten by any `.modal.<variant>` in components/themes by layer,
   so a single-class dialog override no longer needs the `.modal.<x>` compound-
   specificity dodge to win. */
@layer base {
.modal {
  border: 1px solid var(--bb-rule);
  border-radius: 0;
  background: linear-gradient(180deg, var(--bb-parchment-light), var(--bb-parchment-mid));
  color: var(--bb-ink);
  padding: 0;
  max-width: 520px;
  width: 90vw;
  box-shadow: 0 16px 36px -10px var(--bb-shadow-deep);
}
.modal::backdrop {
  background: var(--bb-backdrop);
  backdrop-filter: blur(2px);
}
}

@layer components {

.dialog-form {
  padding: 22px 24px 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.dialog-title {
  margin: 0;
  font-family: var(--bb-font-display);
  font-size: 1.125rem;              /* 18px */
  color: var(--bb-vermillion-deep);
  letter-spacing: 0.01em;
}
.dialog-body {
  margin: 0;
  white-space: pre-line;
  color: var(--bb-ink-soft);
  font-family: var(--bb-font-text);
  font-size: 0.9375rem;             /* 15px */
  line-height: 1.45;
  max-height: 60vh;
  overflow-y: auto;
}
/* Text-input body for prompts that collect a value rather than just a
   decision (#195 off-map location note) — same recipe as .founding-input. */
.dialog-field-label {
  display: block;
  margin-bottom: 6px;
  font-family: var(--bb-font-display);
  font-size: 0.8125rem;
  color: var(--bb-ink-soft);
}
.dialog-text-input {
  width: 100%;
  box-sizing: border-box;
  font-family: var(--bb-font-text);
  font-size: 0.9375rem;
  color: var(--bb-ink);
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule);
  border-radius: 0;
  padding: 7px 9px;
}
.dialog-text-input::placeholder {
  color: var(--bb-ink-faint);
  font-family: var(--bb-font-italic);
  font-style: italic;
}
.dialog-text-input:focus {
  outline: none;
  border-color: var(--bb-vermillion);
  background: var(--bb-parchment-light);
}

.dialog-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 4px;
}
.dialog-actions button { min-width: 80px; padding-inline: 10px; }
.dialog-actions-break { flex-basis: 100%; height: 0; }

/* Cancel = ghost text, Confirm = gilt (additive) or vermillion (danger).
   Also reused (via .picker) for the Commission dialog's own Cancel — a
   dismiss action, same tier as Done. Wizard-nav Back uses .picker-back
   instead (see below), a lighter text-link so it doesn't read as a peer
   of Cancel/Done. */
.dialog-actions .dialog-cancel,
.picker .dialog-cancel {
  background: transparent;
  border: 1px solid transparent;
  color: var(--bb-ink-soft);
  box-shadow: none;
  font-family: var(--bb-font-display);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.dialog-actions .dialog-cancel:hover,
.picker .dialog-cancel:hover {
  background: var(--bb-parchment-mid);
  color: var(--bb-ink);
  border-color: var(--bb-rule-soft);
}
.picker-back {
  background: transparent;
  border: none;
  box-shadow: none;
  color: var(--bb-ink-soft);
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  padding: 4px 2px;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: transparent;
  transition: color 0.12s, text-decoration-color 0.12s;
}
.picker-back:hover { color: var(--bb-ink); text-decoration-color: var(--bb-ink-soft); }
.picker-back:focus-visible { outline: 2px solid var(--bb-gilt-deep); outline-offset: 2px; }
.dialog-actions .dialog-confirm {
  background: linear-gradient(180deg, var(--bb-gilt-bright), var(--bb-gilt));
  color: var(--bb-ink);
  border: 1px solid var(--bb-gilt-deep);
  box-shadow:
    0 1px 0 var(--bb-inset-strong) inset,
    0 2px 4px var(--bb-shadow);
  font-family: var(--bb-font-display);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.dialog-actions .dialog-confirm:hover {
  background: linear-gradient(180deg,
      color-mix(in oklch, var(--bb-gilt-bright), white 6%),
      var(--bb-gilt-bright));
  border-color: var(--bb-gilt-deep);
}
.dialog-actions .dialog-confirm.danger {
  background: var(--bb-danger);
  color: var(--bb-parchment-light);
  border-color: var(--bb-danger-deep);
}
.dialog-actions .dialog-confirm.danger:hover {
  background: var(--bb-danger-deep);
  border-color: var(--bb-danger-deep);
}
.dialog-actions .dialog-confirm:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}

/* List-layout choice tiles (opt-in via choiceModal layout:"list", §7.3).
 * Stacks full-width tiles; primary = gilt, rest = ghost. Reuses the
 * gilt/ghost hierarchy already established by .dialog-confirm/.dialog-cancel. */
.dialog-actions.dialog-actions-list {
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
}
.dialog-actions-list .dialog-choice {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 10px;
  min-width: 0;
  min-height: 3.4rem;
  padding: 10px 14px;
  text-align: left;
  background: var(--bb-parchment-mid);
  border: 1px solid var(--bb-rule-soft);
  color: var(--bb-ink);
}
.dialog-actions-list .dialog-choice:hover {
  background: var(--bb-parchment-light);
  border-color: var(--bb-rule);
}
.dialog-actions-list .dialog-choice.primary {
  background: linear-gradient(180deg, var(--bb-gilt-bright), var(--bb-gilt));
  border: 1px solid var(--bb-gilt-deep);
  box-shadow:
    0 1px 0 var(--bb-inset-strong) inset,
    0 2px 4px var(--bb-shadow);
}
.dialog-actions-list .dialog-choice.primary:hover {
  background: linear-gradient(180deg,
    color-mix(in oklch, var(--bb-gilt-bright), white 6%),
    var(--bb-gilt-bright));
}
.dialog-actions-list .dialog-choice-label {
  flex: 1 1 auto;
  font-family: var(--bb-font-display);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.dialog-actions-list .dialog-choice-hint {
  flex-basis: 100%;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.875rem;
  color: var(--bb-ink-soft);
}
.dialog-actions-list .dialog-choice.warn {
  border-left: 3px solid var(--bb-gilt-deep);
}
.dialog-actions-list .dialog-choice.warn .dialog-choice-hint {
  color: var(--bb-gilt-deep);
}
.dialog-actions-list .dialog-choice.danger {
  background: var(--bb-danger);
  border-color: var(--bb-danger-deep);
  color: var(--bb-parchment-light);
}
.dialog-actions-list .dialog-choice.danger .dialog-choice-hint {
  color: var(--bb-parchment-mid);
}
.dialog-actions-list .dialog-choice:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}
.dialog-actions-list .dialog-cancel {
  align-self: flex-end;
  margin-top: 2px;
}

/* Founding form (#159) — the "Create your Bastion" view swapped into
   #welcome-dialog. Reuses .dialog-*, .view-toggle, .settings-row, .lvl and the
   .charter-sources disclosure recipe; only the form's own layout lives here.
   The rarely-touched sections (type / sourcebooks / focuses) collapse so the
   resting form stays short enough not to scroll. */
.welcome-view[hidden] { display: none; }
/* The welcome views are block-wrapped (not the six-dialog flex-gap layout), so
   .dialog-sub's -0.5rem top margin pulls it into the title — cancel it here. */
#welcome-dialog .dialog-sub { margin-top: 0; }
.founding-body {
  display: flex;
  flex-direction: column;
  gap: 13px;
  white-space: normal;                 /* structured markup — undo the modal-body pre-line */
  scrollbar-gutter: stable both-edges; /* symmetric gutter; no lopsided right margin */
}
.founding-field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 0;
  border: none;
  padding: 0;
}
.founding-label {
  font-family: var(--bb-font-display);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bb-ink-soft);
}
.founding-optional {
  font-family: var(--bb-font-italic);
  font-style: italic;
  letter-spacing: 0;
  text-transform: none;
  color: var(--bb-ink-faint);
}
.founding-input {
  font-family: var(--bb-font-text);
  font-size: 0.9375rem;
  color: var(--bb-ink);
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule);
  border-radius: 0;
  padding: 7px 9px;
}
.founding-input::placeholder {
  color: var(--bb-ink-faint);
  font-family: var(--bb-font-italic);
  font-style: italic;
}
.founding-input:focus {
  outline: none;
  border-color: var(--bb-vermillion);
  background: var(--bb-parchment-light);
}

/* Level stepper + teaching line, side by side to save vertical space. */
.founding-level-row {
  display: flex;
  align-items: baseline;
  gap: 12px;
}
.founding-level-field .lvl {
  width: 3.2em;
  min-width: 0;
  color: var(--bb-vermillion-deep);
  font-family: var(--bb-font-display);
  font-weight: 600;
  font-variant-numeric: lining-nums tabular-nums;
  text-align: center;
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule);
  padding: 5px 4px;
}
.founding-level-field .lvl:focus {
  outline: none;
  border-color: var(--bb-vermillion);
}
.founding-threshold {
  margin: 0;
  font-size: 0.875rem;
  color: var(--bb-ink-soft);
}
.founding-threshold.is-locked {
  font-family: var(--bb-font-italic);
  font-style: italic;
  color: var(--bb-gilt-deep);
}

/* Import from D&D Beyond — a prominent secondary path (parchment, bordered),
   quieter than the gilt Create button but clearly featured. */
.founding-ddb {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  text-align: left;
  padding: 10px 14px;
  background: var(--bb-parchment-mid);
  border: 1px solid var(--bb-rule);
  color: var(--bb-ink);
  cursor: pointer;
}
.founding-ddb:hover {
  background: var(--bb-parchment-light);
  border-color: var(--bb-vermillion);
}
.founding-ddb:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}
.founding-ddb-label {
  font-family: var(--bb-font-display);
  font-size: 0.78rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bb-vermillion-deep);
}
.founding-ddb-hint {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--bb-ink-soft);
}

/* Collapsed sections — the .charter-sources disclosure chrome (title + right
   summary + chevron), reused so type / sourcebooks / focuses default closed. */
.founding-collapse {
  border-top: 1px solid var(--bb-rule-soft);
}
.founding-collapse > summary {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 0;
  cursor: pointer;
  list-style: none;
}
.founding-collapse > summary::-webkit-details-marker { display: none; }
.founding-collapse-title {
  margin-right: auto;   /* pushes the summary value + chevron to the far right */
  font-family: var(--bb-font-display);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bb-ink-soft);
}
.founding-collapse-summary {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--bb-ink-soft);
}
.founding-collapse > summary::after {
  content: "";
  flex: none;
  width: 7px; height: 7px;
  margin-right: 3px;
  border-right: 1.5px solid var(--bb-ink-soft);
  border-bottom: 1.5px solid var(--bb-ink-soft);
  transform: rotate(45deg);
  transition: transform 120ms;
}
.founding-collapse[open] > summary::after { transform: rotate(-135deg); }
.founding-collapse > summary:hover .founding-collapse-title,
.founding-collapse > summary:focus-visible .founding-collapse-title { color: var(--bb-vermillion); }
.founding-collapse > summary:hover::after,
.founding-collapse > summary:focus-visible::after { border-color: var(--bb-vermillion); }
.founding-collapse > summary:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}
.founding-collapse[open] { padding-bottom: 8px; }
.founding-collapse .chapter-sub { margin: 0 0 8px; }
/* Toggle rows inside a collapsed section — no trailing rule under the last one. */
.founding-collapse .settings-row:last-of-type { border-bottom: none; }
.founding-collapse .settings-row .view-toggle { flex: none; }

.founding-type-hint {
  margin: 6px 0 0;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--bb-gilt-deep);
}
.founding-type-hint strong { color: var(--bb-gilt-deep); font-style: normal; }
/* Mobile rows dim when Eberron isn't enabled for this bastion. */
.founding-type-mobile:has(input:disabled) {
  opacity: 0.5;
}
.founding-focus-options {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Upgrade-confirm ledger (§7.2) — mono nums per step row, hairline rule
 * above the total. */
.dialog-body .upgrade-lead {
  margin: 0;
}
.dialog-body .upgrade-ledger {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  font-family: var(--bb-font-mono);
  font-size: 0.8125rem;               /* 13px */
  font-variant-numeric: lining-nums tabular-nums;
}
.dialog-body .upgrade-ledger li {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 16px;
  padding: 3px 0;
  color: var(--bb-ink-soft);
}
.dialog-body .upgrade-ledger li.total {
  margin-top: 4px;
  padding-top: 6px;
  border-top: 1px solid var(--bb-rule-soft);
  color: var(--bb-ink);
  font-weight: 600;
}
.dialog-body .upgrade-ledger .step { text-align: left; }
.dialog-body .upgrade-ledger .gp,
.dialog-body .upgrade-ledger .days { text-align: right; min-width: 5ch; }

/* Build milestones (#87) — since-strip name + Build-history ledger. The
 * since-strip reuses .defenses-line (square corners, flex-wrap stat row); the
 * ledger mirrors .upgrade-ledger's mono-number grammar. No new tokens. */
.milestone-line .milestone-name {
  font-family: var(--bb-font-italic);
  font-style: italic;
  color: var(--bb-ink);
}
.milestone-line .milestone-since-link {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: inherit;
  color: var(--bb-ink);
  text-decoration: underline dotted;
  text-underline-offset: 2px;
}
.milestone-line .milestone-since-link:hover { color: var(--bb-gilt-deep); }
.milestone-line .defenses-stats {
  font-variant-numeric: lining-nums tabular-nums;
}
.milestone-line .defenses-stats .none {
  font-family: var(--bb-font-italic);
  font-style: italic;
  color: var(--bb-ink-faint);
}

/* #89 — new-since-milestone floorplan highlight toggle */
/* The delta grows to fill the strip so the action pills right-align without
 * auto-margin juggling (mark-milestone affordance, item 2). */
.milestone-line .defenses-stats { flex: 1 1 auto; min-width: 0; }
.milestone-highlight-btn,
.milestone-mark-btn {
  margin-left: 8px;
  padding: 3px 8px;
  font-family: var(--bb-font-display);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  background: transparent;
  color: var(--bb-gilt-deep);
  border: 1px solid var(--bb-gilt);
  border-radius: 0;
  cursor: pointer;
  white-space: nowrap;
}
/* Reserve the wider state's width so "Show new" ⇄ "Showing new" never grows
 * the pill and bumps it to a new line (#218). */
.milestone-highlight-btn { min-width: 8.5em; text-align: center; }
.milestone-highlight-btn:hover,
.milestone-mark-btn:hover:not(:disabled) {
  background: rgba(168,120,38,0.08);
  color: var(--bb-gilt);
  border-color: var(--bb-gilt-bright);
}
/* Disabled mark pill (nothing new to capture) — same quiet treatment as
 * .add-btn:disabled, so the strip stays visible without inviting a dead click. */
.milestone-mark-btn:disabled {
  border-color: var(--bb-rule-soft);
  color: var(--bb-ink-faint);
  background: transparent;
  cursor: not-allowed;
}
.milestone-highlight-btn[aria-pressed="true"] {
  background: var(--bb-gilt-deep);
  color: var(--bb-parchment-light);
  border-color: var(--bb-gilt-deep);
}
.milestone-highlight-btn[aria-pressed="true"]:hover {
  background: var(--bb-gilt);
  border-color: var(--bb-gilt);
  color: var(--bb-ink);
}
/* Narrow tier: instead of one pill per line, the delta claims the full first
 * row and the two pills drop to a right-aligned pair on the second (#218).
 * Whichever pill leads the row gets the auto-margin that pushes the pair
 * right; when both are present the trailing mark pill keeps a plain gap. */
@media (max-width: 700px) {
  .milestone-line { row-gap: 6px; }
  .milestone-line .defenses-stats { flex-basis: 100%; }
  .milestone-line .milestone-highlight-btn,
  .milestone-line .milestone-mark-btn { margin-left: auto; }
  .milestone-line .milestone-highlight-btn ~ .milestone-mark-btn { margin-left: 8px; }
}

/* Gilt glow halo on newly-placed facility rects when highlight is active */
.fp-new-facility .facility-rect {
  filter: drop-shadow(0 0 3px var(--bb-gilt-bright))
          drop-shadow(0 0 7px var(--bb-gilt-bright));
}
/* Rail row → floorplan spotlight. Gilt fill + outlined stroke + double halo.
 * Same gilt vocabulary as .fp-new-facility but brighter+outlined so a transient
 * hover reads differently from the persistent new-placement glow. */
.facility-group.rail-lit .facility-rect {
  fill: var(--bb-gilt-fill);
  stroke: var(--bb-gilt-deep);
  stroke-width: 3;
  filter: drop-shadow(0 0 3px var(--bb-gilt-bright))
          drop-shadow(0 0 6px var(--bb-gilt-bright));
}
.fp-new-coord {
  filter: drop-shadow(0 0 3px var(--bb-gilt-bright))
          drop-shadow(0 0 7px var(--bb-gilt-bright));
}

/* Inline in the Chronicle chapter now (not a dialog): shed the .dialog-body
 * 60vh cap + nested scroll so the ledger flows with the page (same fix as the
 * Household roster, nit 9). */
#milestones-body { white-space: normal; max-height: none; overflow: visible; }
.milestone-ledger {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* Milestone plate cards — the Stores chassis (.cap-row) laid horizontal: the
 * phase's floorplan PLATE beside its entry. No head bar, no glyph disc — the
 * plate is the emblem, and it's the door into the checkpoint gallery. */
.milestone-card { flex-direction: row; gap: 12px; align-items: stretch; }
.milestone-plate {
  flex: 0 0 100px;
  width: 100px;
  height: 100px;
  align-self: flex-start;
  padding: 6px;
  display: grid;
  place-items: center;
  overflow: hidden;
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule-soft);
  border-radius: 0;
  box-shadow: none;
  cursor: pointer;
}
.milestone-plate:hover { border-color: var(--bb-gilt); background: var(--bb-gilt-fill); }
.milestone-plate:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: 2px; }
.milestone-plate svg { display: block; max-width: 100%; max-height: 100%; }
.milestone-card-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.milestone-card .ck-name {
  background: transparent;
  border: none;
  border-radius: 0;
  box-shadow: none;                 /* the global input's sunken well reads as a strip here */
  min-width: 0;
  padding: 2px 4px;
  margin: -2px -4px;
  color: var(--bb-ink);
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.9375rem;             /* 15px */
  width: 100%;
  box-sizing: border-box;
}
.milestone-card .ck-name:hover {
  background: var(--bb-parchment-mid);
  cursor: text;
}
.milestone-card .ck-name:focus {
  outline: none;
  background: var(--bb-parchment-mid);
}
/* Identity + delta on one line: the in-game date (italic, in-character) and
 * level left; the phase delta right as ONE quiet .turn-log-meta string
 * ("+17,500 gp · +700 d") — the same ledger grammar as the sealed turn rows.
 * "rebuilt" is the italic meta word for a phase that changed shape but cost
 * nothing. */
.milestone-id-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  font-size: 0.8125rem;               /* 13px */
  color: var(--bb-ink-soft);
}
.milestone-identity { min-width: 0; }
.milestone-card .is-rebuilt {
  font-family: var(--bb-font-italic);
  font-style: italic;
}
/* Footer: the real-world stamp, plain and faint (NOT italic — italic is the
 * in-character calendar's voice, plain is the system's), beside the one text
 * verb (delete, vermillion whisper). */
.milestone-footer { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; margin-top: 2px; }
.milestone-marked { font-size: 0.75rem; color: var(--bb-ink-faint); font-variant-numeric: lining-nums tabular-nums; }
.milestone-action { font-size: 0.8125rem; }

/* The unsealed current phase (#198): the milestone-card anatomy wearing the
   ghost launcher's gilt-dashed grammar — dashed = not a record yet, solid =
   sealed. The eyebrow is real text so the unsealed state never rides on the
   border alone. */
.milestone-wip { border-style: dashed; border-color: var(--bb-gilt); background: var(--bb-gilt-fill); }
.milestone-wip .milestone-plate { border-style: dashed; border-color: var(--bb-gilt); }
.milestone-wip-eyebrow {
  font-family: var(--bb-font-display);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bb-gilt-deep);
}
/* The ghost launcher — the .reg-card-add tile at the top of the ledger (the
 * slot where the next milestone lands); deliberately lighter than the plate
 * cards: not a record yet. Doubles as the empty state. */
.milestone-launch { width: 100%; min-height: 64px; flex-direction: column; gap: 4px; }
.milestone-launch .milestone-launch-sub {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  letter-spacing: 0;
  color: var(--bb-ink-soft);
}
/* Resting states for The Works (#218) — the quiet stand-in for the retired
 * launcher when there's nothing to seal (no build yet, or nothing new since the
 * last milestone). A manuscript aside, not a card or a dead button: deliberately
 * faint so it never competes with the dashed-gilt in-progress card. */
.milestone-resting {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--bb-ink-faint);
  padding: 6px 2px;
}
/* The in-progress card's footer is an action row now (it carries the Mark
 * milestone pill), not a passive caption — a little more air, and center the
 * pill against the VTT link. Scoped to the WIP card; sealed footers unchanged. */
.milestone-wip .milestone-footer { align-items: center; margin-top: 6px; }
@media (max-width: 700px) {
  .milestone-wip .milestone-mark-btn { padding: 8px 14px; }  /* thumb-sized primary act */
}
.cpv-gallery { display: flex; align-items: center; gap: 8px; }
.cpv-floorplan { flex: 1; min-width: 0; max-height: 70vh; overflow: auto; text-align: center; }
.cpv-floorplan svg { max-width: 100%; height: auto; }
.cpv-nav-btn {
  flex-shrink: 0;
  background: transparent;
  border: none;
  box-shadow: none;
  font-size: 1.75rem;
  line-height: 1;
  color: var(--bb-ink-faint);
  cursor: pointer;
  padding: 0 2px;
}
.cpv-nav-btn:hover:not(:disabled) { color: var(--bb-ink); }
.cpv-nav-btn:disabled { opacity: 0.25; cursor: default; }
.cpv-actions { justify-content: space-between; }
.milestone-details {
  margin-top: 6px;
}
.milestone-details > summary {
  cursor: pointer;
  list-style: none;
  font-family: var(--bb-font-display);
  font-size: 0.8125rem;               /* 13px */
  letter-spacing: 0.08em;
  /* Neutral navigation ink — vermillion on this card is reserved for the
   * delete whisper (the chapter's destructive color-coding). */
  color: var(--bb-ink-soft);
}
.milestone-details > summary:hover { color: var(--bb-ink); }
.milestone-details > summary::-webkit-details-marker { display: none; }
.milestone-details > summary::before {
  content: "\25B8";                 /* ▸ */
  display: inline-block;
  margin-right: 6px;
  color: currentColor;
}
.milestone-details[open] > summary::before { content: "\25BE"; }  /* ▾ */
.milestone-details ul {
  list-style: none;
  margin: 6px 0 0;
  padding: 0 0 0 18px;
  font-size: 0.84rem;             /* 13.5px */
  color: var(--bb-ink-soft);
}
.milestone-details li { padding: 2px 0; }

.dialog-body .dialog-warn {
  display: block;
  margin-top: 4px;
  font-family: var(--bb-font-italic);
  font-style: italic;
  color: var(--bb-gilt-deep);
}

/* #221: advisory line in the change-type picker (a Garden regrows 21 days).
 * A non-interactive list row — the gilt italic voice, none of the affordance. */
#special-candidates .pick-type-note {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.875rem;
  color: var(--bb-gilt-deep);
  cursor: default;
  pointer-events: none;
}

/* Household portrait gallery — Chapter IV (#154). One card per hireling slot
 * on the .reg-card chassis: flat and always-editable, no accordion. The name
 * is the Charter bastion-name overlay pair (§5.4) at card scale — an
 * aria-hidden display div paints the vermillion versal at rest, a transparent
 * input sits inset:0 on top and inks in on focus — plus the empty-name state
 * the Charter never needs: an unnamed slot shows the input itself, visible,
 * with an italic placeholder, so the invitation has a real hit area. One
 * vermillion accent per card (the versal); the facility cartouche stays a
 * quiet ink link. The chapter region scrolls (.chapter overflow:auto) — the
 * list must NOT cap its own height. */
.household-list,
.residence-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 16px;
  align-items: start;
}
.house-card {
  gap: 6px;
  padding: 12px 14px 14px;
}
/* Vacant slot: the system's "provisional" grammar (dashed, faded) — an
 * opportunity, not a blank form. Role/prose/status stay in the DOM (the
 * reveal is a class flip, never a rebuild — focus survives) but hide until
 * the household member exists in some detail. */
.house-card.is-vacant {
  border-style: dashed;
  border-color: var(--bb-rule-soft);
  box-shadow: none;
}
.house-card.is-vacant .house-cartouche-glyph { opacity: 0.65; }
.house-card.is-vacant .house-role,
.house-card.is-vacant .house-prose,
.house-card.is-vacant .house-status-row { display: none; }
.house-card:not(.is-vacant) .house-invite { display: none; }
.house-invite {
  margin: 0;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.92rem;
  color: var(--bb-ink-faint);
}
/* Register spotlight (#154) — the "Hirelings" fact-row jump lands here. */
.house-card.house-lit {
  border-color: var(--bb-gilt-deep);
  box-shadow: 0 0 0 2px var(--bb-gilt-bright), var(--shadow-card);
}

/* Facility cartouche: affiliation eyebrow + the jump back to the Register.
 * roll-die link idiom — the dotted gilt underline, not color, carries it. */
.house-cartouche {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  gap: 6px;
  max-width: 100%;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--bb-ink-soft);
  font-family: var(--bb-font-display);
  font-variant: small-caps;
  font-size: 0.84rem;
  letter-spacing: 0.03em;
  text-align: left;
}
.house-cartouche-label {
  text-decoration: underline dotted var(--bb-gilt);
  text-underline-offset: 3px;
}
.house-cartouche:hover .house-cartouche-label,
.house-cartouche:focus-visible .house-cartouche-label { text-decoration-style: solid; }
.house-cartouche:hover,
.house-cartouche:focus-visible { color: var(--bb-gilt-deep); }
.house-cartouche:focus-visible { outline: 2px solid var(--bb-gilt); outline-offset: 2px; }
.house-cartouche-glyph { flex: none; width: 20px; height: 20px; }
.house-cartouche-glyph .glyph-svg { display: block; width: 20px; height: 20px; }

/* The illuminated name. Wrap min-height keeps a hit area when empty. */
.house-name-wrap { position: relative; min-height: 42px; }
.house-name-display {
  min-height: 36px;
  padding: 2px 2px 0;
  font-family: var(--bb-font-display);
  font-size: 1.3rem;
  line-height: 1.25;
  color: var(--bb-ink);
  overflow-wrap: anywhere;
}
.house-name-display::first-letter {
  float: left;
  margin-right: 3px;
  font-family: var(--bb-font-display);
  font-size: 34px;
  line-height: 27px;
  color: var(--bb-vermillion);
  /* #218 — always a capital, like a scribe's illuminated initial. A lowercase
   * glyph sits at x-height inside the tall floated box, so it reads as dropping
   * to the second line; the uppercase form fills from cap-height near the top. */
  text-transform: uppercase;
}
.house-name {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  min-width: 0;
  padding: 2px 2px 0;
  font-family: var(--bb-font-display);
  font-size: 1.3rem;
  color: var(--bb-ink);
  caret-color: var(--bb-vermillion);
  background: transparent;
  border: 1px solid transparent;
  box-shadow: none;
}
.house-name::placeholder {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 1rem;
  color: var(--bb-ink-faint);
}
/* Named: the overlay reads, the input goes glass until focused. */
.house-name-wrap.is-named .house-name { color: transparent; caret-color: transparent; }
.house-name-wrap.is-named .house-name:focus { color: var(--bb-ink); caret-color: var(--bb-vermillion); }
.house-name:focus {
  outline: none;
  background: var(--bb-parchment-light);
  border-color: var(--bb-vermillion);
  box-shadow: 0 0 0 2px rgba(168, 40, 29, 0.2);
}
.house-name-wrap:focus-within .house-name-display { visibility: hidden; }
/* The SRD glyph rides inside the display, but the absolute .house-name input
   overlays it — lift the glyph above the input so its click opens the stat
   card instead of falling through and starting a name edit. */
.house-name-display .ref-glyph { position: relative; z-index: 1; }

/* Name suggester (#208): the quill trigger on an unnamed name field. A real
   <button> (keyboard-reachable, unlike the aria-hidden ref-glyph), a direct
   child of the wrap — not nested in the display — so it survives :focus-within
   and stays pinned right; z-index lifts it above the inset:0 .house-name input
   so its click opens the popover instead of falling through into a name edit.
   Gilt (not the ref-glyph's vermillion) focus ring — the Household surface's
   controls all use a gilt ring. Hidden once the slot is named. */
.house-suggest {
  position: absolute;
  top: 50%;
  right: 4px;
  transform: translateY(-50%);
  z-index: 2;
  display: inline-flex;
  width: 1.05em;
  height: 1.05em;
  padding: 0;
  border: 0;
  background: none;
  color: var(--bb-ink-faint);
  line-height: 1;
  cursor: pointer;
}
.house-suggest .glyph-svg { display: block; width: 100%; height: 100%; }
.house-suggest:hover,
.house-suggest:focus-visible,
.house-suggest[aria-expanded="true"] { color: var(--bb-gilt-deep); }
/* The global button:active nudge REPLACES a transform, it doesn't add to it —
   without this override the press swaps translateY(-50%) for translateY(1px)
   and the button jumps half its height out from under the pointer, so the
   mouseup lands on the input and the click retargets past the delegated
   handler (the #227 cascade lesson, hit-test edition). */
.house-suggest:active {
  color: var(--bb-vermillion);
  transform: translateY(calc(-50% + 1px));
}
.house-suggest:focus-visible { outline: 2px solid var(--bb-gilt); outline-offset: 2px; }
.house-name-wrap.is-named .house-suggest { display: none; }

/* Role / prose / status: seamless manuscript fields — the validated left-rule
 * + gilt-on-focus idiom, typeset until touched. */
.house-role,
.house-prose,
.house-status {
  width: 100%;
  padding: 0.3rem 0.5rem;
  background: var(--bb-parchment);
  border: 1px solid transparent;
  border-left: 2px solid var(--bb-rule-soft);
  border-radius: 0;
  color: var(--bb-ink);
  font-family: var(--bb-font-italic);
  font-size: 0.95rem;
  transition: background-color 90ms ease, border-color 90ms ease;
}
.house-role::placeholder,
.house-prose::placeholder,
.house-status::placeholder { color: var(--bb-ink-faint); font-style: italic; }
.house-role:hover,
.house-prose:hover,
.house-status:hover { border-left-color: var(--bb-gilt); }
.house-role:focus,
.house-prose:focus,
.house-status:focus {
  outline: none;
  border-color: var(--bb-rule);
  border-left-color: var(--bb-gilt-deep);
  background: var(--bb-parchment-light);
}
.house-prose { min-height: 72px; line-height: 1.45; resize: vertical; }
.house-status-row { display: flex; align-items: baseline; gap: 8px; margin-top: 2px; }
.house-status-kicker {
  flex: none;
  font-family: var(--bb-font-display);
  font-variant: small-caps;
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  color: var(--bb-ink-faint);
}
.house-status { flex: 1 1 auto; font-size: 0.9rem; color: var(--bb-ink-soft); }

/* In Residence recruit cards: same chassis, legibly not hirelings — an
 * editable home-facility select in the cartouche slot, a kind chip instead
 * of a role line, and a remove × (catalog-derived hireling slots have none). */
.house-recruit-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.house-home {
  min-width: 0;
  flex: 0 1 auto;
  padding: 0 0 1px;
  background: none;
  border: 0;
  border-bottom: 1px dotted var(--bb-gilt);
  color: var(--bb-ink-soft);
  font-family: var(--bb-font-display);
  font-variant: small-caps;
  font-size: 0.84rem;
  letter-spacing: 0.03em;
  cursor: pointer;
}
.house-home:hover { color: var(--bb-gilt-deep); border-bottom-style: solid; }
.house-home:focus-visible { outline: 2px solid var(--bb-gilt); outline-offset: 2px; }
.house-remove {
  flex: none;
  padding: 0 0.2rem;
  background: none;
  border: 0;
  color: var(--bb-ink-faint);
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
}
.house-remove:hover { color: var(--bb-danger); }
.house-kind-chip {
  align-self: flex-start;
  padding: 2px 10px;
  background: none;
  border: 1px solid var(--bb-rule-soft);
  border-radius: 999px;
  color: var(--bb-ink-soft);
  font-family: var(--bb-font-display);
  font-variant: small-caps;
  font-size: 0.8rem;
  letter-spacing: 0.03em;
  cursor: pointer;
}
.house-kind-chip:hover { border-color: var(--bb-gilt); color: var(--bb-ink); }
.house-kind-chip:focus-visible { outline: 2px solid var(--bb-gilt); outline-offset: 2px; }
.house-add { min-height: 96px; font-size: 0.95rem; }

/* Stores (#114, inline in Chronicle › Stores since the turn-page rework):
   hand-maintained status trackers + ledger. Each facility sits in its own
   sunken .cap-row card, headed by the roster's tinted glyph disc so the
   tracker↔facility link reads at a glance; the derived defender line reuses
   .cap-row too, so it reads as computed output, never an input. The host is
   a plain flow block — the chapter owns scrolling (the old 60vh well was the
   dialog's). Stores never lifts: one reading order in every layout (Turns,
   Works, Stores) so nothing teleports across the spread breakpoint. */
.stores-list { margin-top: 0.2rem; }
/* Rail liveness: when an edit on one surface write-throughs to its twin
   (sweep card ↔ Stores), the patched control flashes a brief gilt wash so
   the mirroring is visible instead of inferred. */
@keyframes bb-tracker-flash {
  from { background-color: color-mix(in srgb, var(--bb-gilt) 30%, transparent); }
  to { background-color: transparent; }
}
.tracker-flash { animation: bb-tracker-flash 1.1s ease-out; }
@media (prefers-reduced-motion: reduce) {
  .tracker-flash { animation: none; }
}
.stores-defenders { margin-bottom: 1rem; }
.stores-defenders.is-over .count-big { font-weight: 700; }
.stores-cap-max { color: var(--bb-ink-soft); }
.stores-over-note { color: var(--bb-gilt-deep); }
/* Defender meter: small ink-soft shield inline with the label — derived, so it
   gets a plain glyph, not the vermillion identity disc the facility cards use. */
.stores-meter-label { display: inline-flex; align-items: center; gap: 0.4rem; }
.stores-meter-icon { display: inline-grid; place-items: center; width: 16px; height: 16px; color: var(--bb-ink-soft); }
.stores-meter-icon .glyph-svg { width: 16px; height: 16px; display: block; fill: currentColor; }

/* Facility status card — .cap-row provides the sunken parchment chassis. */
.stores-card { margin-bottom: 0.75rem; }
.stores-card-head { display: flex; align-items: center; gap: 0.55rem; margin-bottom: 0.5rem; padding-bottom: 0.4rem; border-bottom: 1px solid var(--bb-rule-soft); }
.stores-card-name { font-family: var(--bb-font-display); font-size: 1.0625rem; color: var(--bb-ink); }
.stores-card-ord { font-family: var(--bb-font-display); font-size: 0.875rem; color: var(--bb-ink-soft); font-variant-numeric: lining-nums tabular-nums; }

.stores-row { display: grid; grid-template-columns: 1fr auto; gap: 0.35rem 0.5rem; align-items: baseline; margin-bottom: 0.3rem; }
.stores-row--stacked { grid-template-columns: 1fr; gap: 0.2rem; }
.stores-label { font-family: var(--bb-font-display); font-variant: small-caps; font-size: 0.8rem; color: var(--bb-ink-faint); letter-spacing: 0.04em; }
/* Dotted ledger leader between label and control — the character-sheet texture. */
.stores-row:not(.stores-row--stacked) .stores-label { display: flex; align-items: baseline; white-space: nowrap; }
.stores-row:not(.stores-row--stacked) .stores-label::after { content: ""; flex: 1 1 auto; margin-left: 0.5em; border-bottom: 1px dotted var(--bb-rule-soft); transform: translateY(-0.18em); }
.stores-counter-field { display: inline-flex; align-items: baseline; gap: 0.3rem; justify-self: end; }
/* Frame + focus from Archetype B (the ruled slot). Ink numerals at a calmer
   1.05rem — the card's figure, no longer a vermillion shout in a white box. */
.stores-num { width: 3.25rem; text-align: right; font-family: var(--bb-font-display); font-size: 1.05rem; font-variant-numeric: lining-nums tabular-nums; padding: 0.1rem 0.35rem; }
.stores-max { font-family: var(--bb-font-display); font-size: 0.9375rem; color: var(--bb-ink-soft); font-variant-numeric: lining-nums tabular-nums; }
.stores-toggle { display: inline-flex; gap: 0; justify-self: end; }
.stores-toggle-btn { font-size: 0.84rem; padding: 2px 10px; }
.stores-toggle-btn + .stores-toggle-btn { margin-left: -1px; }
.stores-note { font-family: var(--bb-font-italic); font-size: 0.9375rem; padding: 0.2rem 0.4rem; width: 100%; }
.stores-row:not(.stores-row--stacked) .stores-note { min-width: 12rem; justify-self: end; }
textarea.stores-note { resize: vertical; }

/* Read-only viewer readouts (#254): the static twin of every tracker control
   in a shared snapshot. Deliberately NOT a greyed-out field — a frame with no
   ink in it reads as "you may type here, but not yet", and there is no "yet".
   The value inherits the control's own type (display face + tabular figures
   for numbers, the italic hand for text) and simply sits where the control
   sat, so a card keeps its shape between the player's screen and the DM's. */
.stores-readout { font-family: var(--bb-font-display); font-size: 1.05rem; font-variant-numeric: lining-nums tabular-nums; color: var(--bb-ink); }
.stores-readout--text { font-family: var(--bb-font-italic); font-size: 0.9375rem; font-variant-numeric: normal; }
/* Direct children only — the counter's readout is nested inside
   .stores-counter-field, which already carries the end-alignment. Each readout
   lands exactly where the control it replaced sat, so a card doesn't reflow
   between the player's screen and the DM's. */
.stores-row:not(.stores-row--stacked) > .stores-readout { justify-self: end; text-align: right; }
.stores-readout.is-empty { color: var(--bb-ink-faint); }

/* Loadout picks (#221): choose-N chip groups inside a Stores card. Option
 * effect text rides a [data-tip] tooltip — chips stay a clean wrapped row. */
.stores-loadout { display: flex; flex-wrap: wrap; gap: 4px 6px; align-items: center; }
.stores-loadout .stores-loadout-btn + .stores-loadout-btn { margin-left: 0; }
.stores-loadout-btn[disabled] { opacity: 0.45; cursor: not-allowed; }
.stores-loadout-status { flex-basis: 100%; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.85rem; color: var(--bb-ink-soft); }
.stores-loadout-status.is-open { color: var(--bb-gilt-deep); }
.stores-loadout-note { flex-basis: 100%; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.82rem; color: var(--bb-ink-soft); }
.reg-loadout-due { opacity: 0.75; }

/* Empty state — a quiet centred crate so a fresh bastion still looks designed. */
.stores-empty { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 0.5rem; padding: 1.25rem 0.5rem; }
.stores-empty-icon { display: inline-grid; place-items: center; width: 40px; height: 40px; color: var(--bb-ink-faint); opacity: 0.7; }
.stores-empty-icon .glyph-svg { width: 40px; height: 40px; display: block; fill: currentColor; }
.stores-empty .empty-line { margin: 0; max-width: 30rem; }
/* The ledger is free-form and never facility-gated — set its card apart from
   the facility cards with extra space above. */
.stores-ledger { margin-top: 1.25rem; }
.stores-ledger-row { display: grid; grid-template-columns: 1fr 1fr 1.5rem; gap: 0.4rem; align-items: center; margin-bottom: 0.3rem; }
.stores-ledger-label, .stores-ledger-value { font-family: var(--bb-font-italic); font-size: 0.9375rem; padding: 0.2rem 0.4rem; width: 100%; }
.stores-ledger-del { background: none; border: none; color: var(--bb-ink-faint); cursor: pointer; font-size: 1.1rem; line-height: 1; padding: 0 0.2rem; }
.stores-ledger-del:hover { color: var(--bb-danger); }
.stores-ledger-del:focus-visible { outline: 2px solid var(--bb-gilt-deep); outline-offset: 1px; }
.stores-add-row { background: none; border: none; padding: 0; margin-top: 0.4rem; cursor: pointer; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.9375rem; color: var(--bb-ink-soft); text-decoration: underline dotted; text-underline-offset: 2px; box-shadow: none; }
.stores-add-row:hover { color: var(--bb-ink); }

/* Derived turn-tally block (#191) */
.stores-derived-marker { font-variant: small-caps; font-size: 0.8rem; color: var(--bb-ink-faint); margin-bottom: 0.35rem; }
.stores-derived-block { margin-bottom: 0.25rem; }
.stores-derived-row { display: grid; grid-template-columns: 1fr auto; align-items: baseline; padding: 0.15rem 0; }
.stores-derived-label { font-family: var(--bb-font-italic); font-style: italic; font-size: 0.9375rem; color: var(--bb-ink); min-width: 0; }
.stores-derived-value { font-family: var(--bb-font-display); font-size: 1.1rem; font-variant-numeric: lining-nums tabular-nums; text-align: right; color: var(--bb-ink); white-space: nowrap; }
.stores-ledger-split { border: none; border-top: 1px solid var(--bb-rule-soft); margin: 0.5rem 0 0.35rem; }

/* Treasury (#191) */
.stores-treasury { display: grid; grid-template-columns: 1fr auto; align-items: baseline; margin-bottom: 0.15rem; }
.stores-treasury-label { font-variant: small-caps; font-size: 0.9rem; color: var(--bb-ink-faint); }
.stores-treasury-field { display: inline-flex; align-items: baseline; gap: 0.35rem; }
.stores-treasury-field .stores-num { width: 4.5rem; }
.stores-treasury-unit { font-size: 0.875rem; color: var(--bb-ink-soft); }
.stores-treasury-sub { font-family: var(--bb-font-italic); font-style: italic; font-size: 0.8125rem; color: var(--bb-ink-faint); display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: baseline; margin-bottom: 0.5rem; }
.stores-treasury-stop { background: none; border: none; padding: 0; cursor: pointer; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.8125rem; color: var(--bb-ink-soft); text-decoration: underline dotted; text-underline-offset: 2px; box-shadow: none; }
.stores-treasury-stop:hover { color: var(--bb-ink); }

/* ---- Bastion Turn page (#112) ------------------------------------------- */
/* Top-bar cartouche: status + jump to this turn's Chronicle page. .btn-ghost
   supplies the chrome; the number rides the ledgers' tabular-numeral family
   (Arabic — Roman ordinals stay reserved for the five fixed chapters). The
   pip + "· N ready" suffix are the .has-vacancy gilt register — "something
   finished, gentle good news", never danger. */
.turn-cartouche { position: relative; text-transform: none; letter-spacing: 0.02em; font-variant-numeric: lining-nums tabular-nums; }
.turn-cartouche .turn-caret { color: var(--bb-ink-soft); margin-left: 0.15rem; }
.turn-cartouche.has-completions::after { content: ""; position: absolute; top: 2px; right: 2px; width: 8px; height: 8px; border-radius: 50%; background: var(--bb-gilt); }
.turn-cartouche-ready { color: var(--bb-gilt-deep); }

/* The open page (#turn-page): the live turn at the top of Chronicle › The
   Bastion Turns — unframed (the framed sweep cards ARE the page body; a
   wrapping border would be border-in-border). Reading order: head → active
   cards → compacted rows → event form → tally + Advance, then the "Earlier
   turns" divider and the sealed log. Once live, the whole section rises to
   the top of the Chronicle column. */
#chronicle-turns.is-live { order: -1; }
.turn-page:not(:empty) { margin: 0.2rem 0 0.4rem; }
.turn-page-head { margin-bottom: 0.9rem; }
.turn-page-title-row { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 0.4rem 1rem; }
.turn-page-title { font-family: var(--bb-font-display); font-size: 1.25rem; font-weight: normal; color: var(--bb-ink); margin: 0; }
.turn-title-sep { color: var(--bb-ink-faint); }
/* Fold caret: the open page can collapse to its head line for reference
   browsing (echoing the sealed rows' disclosure grammar). Class flip only —
   .is-collapsed hides everything but the head, and quiets the head's
   interactive whispers so the folded line reads as a summary, not a form. */
.turn-page-toggle { background: none; border: none; padding: 0 0.3rem 0 0; cursor: pointer; font-size: 0.9rem; color: var(--bb-ink-soft); }
.turn-page-toggle:hover { color: var(--bb-ink); }
.turn-page-toggle:focus-visible { outline: 2px solid var(--bb-gilt-deep); outline-offset: 1px; }
/* Per-card fold control (#218) — the trailing ▾ on a reopened handled card that
 * folds it back to its ledger row (the counterpart to the compact row's reopen
 * click). Same chevron grammar + hover/focus as the page-level toggle; trailing
 * with margin-left:auto so it reads as header chrome (a present done-mark takes
 * the free space first, so the caret still lands at the true right edge). */
.turn-fac-fold {
  margin-left: auto;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 2rem; min-height: 2rem;
  background: none; border: none; padding: 0 0 0 0.3rem;
  cursor: pointer; font-size: 0.9rem; line-height: 1;
  color: var(--bb-ink-soft);
}
.turn-fac-fold:hover { color: var(--bb-ink); }
.turn-fac-fold:focus-visible { outline: 2px solid var(--bb-gilt-deep); outline-offset: 1px; }
/* Thumb target on the phone turn page (the class min-height would otherwise
 * win over the global coarse-button 44px rule). */
@media (pointer: coarse) { .turn-fac-fold { min-width: 2.75rem; min-height: 2.75rem; } }
.turn-page.is-collapsed > :not(.turn-page-head) { display: none; }
.turn-page.is-collapsed .turn-page-head { margin-bottom: 0.2rem; }
.turn-page.is-collapsed .turn-progress,
.turn-page.is-collapsed .turn-rewind,
.turn-page.is-collapsed .turn-maintain-link,
.turn-page.is-collapsed .turn-maintain-prompt,
.turn-page.is-collapsed .turn-date-btn:not(.has-date),
.turn-page.is-collapsed .turn-page-title:has(.turn-date-btn:not(.has-date)) .turn-title-sep { display: none; }
.turn-page.is-collapsed .turn-date-btn { pointer-events: none; text-decoration: none; }
/* Folded one-liner: the sweep's state echoes into the title row (a twin of
 * the sealed log rows' meta, so the fold rhymes with the ledger below it). */
.turn-fold-echo { display: none; font-family: var(--bb-font-display); font-size: 0.875rem; color: var(--bb-ink-soft); font-variant-numeric: lining-nums tabular-nums; white-space: nowrap; }
.turn-page.is-collapsed .turn-fold-echo { display: inline; }
/* Inline in-game date on the title line: quiet whisper button ↔ transparent
   input (the milestone date-slot pattern) — commit on blur/Enter, Esc reverts. */
.turn-date-btn { background: none; border: none; box-shadow: none; padding: 0; cursor: pointer; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.9375rem; color: var(--bb-ink-faint); text-decoration: underline dotted; text-underline-offset: 2px; }
.turn-date-btn:hover { color: var(--bb-ink-soft); }
.turn-date-btn.has-date { color: var(--bb-ink-soft); text-decoration: none; }
.turn-date-btn.has-date:hover { color: var(--bb-ink); text-decoration: underline dotted; text-underline-offset: 2px; }
.turn-date-btn:focus-visible { outline: 2px solid var(--bb-gilt-deep); outline-offset: 2px; }
.turn-date-input { width: 16rem; max-width: 60vw; background: transparent; border: none; border-bottom: 1px dotted var(--bb-rule); border-radius: 0; color: var(--bb-ink-soft); font-family: var(--bb-font-italic); font-style: italic; font-size: 0.9375rem; padding: 0.1rem 0; }
.turn-date-input:focus { outline: none; border-bottom-color: var(--bb-ink-soft); color: var(--bb-ink); }
.turn-date-input::placeholder { color: var(--bb-ink-faint); }
/* Progress line — the sweep's rhythm; the gilt tail is the "results await"
   register (good news, never danger). */
.turn-progress { margin: 0.35rem 0 0; font-family: var(--bb-font-display); font-variant: small-caps; letter-spacing: 0.04em; font-size: 0.875rem; color: var(--bb-ink-soft); font-variant-numeric: lining-nums tabular-nums; }
.turn-progress-due { color: var(--bb-gilt-deep); }
/* Maintain (RAW): the whole-sweep verb rides the progress line as a whisper
   ("issue Maintain" / "resume orders"); the gilt prompt below points the
   DM's Bastion-Event roll at the event form — the app records, never rolls. */
.turn-maintain-link { background: none; border: none; padding: 0; margin-left: 0.5rem; cursor: pointer; font-family: var(--bb-font-italic); font-style: italic; font-variant: normal; font-size: 0.875rem; color: var(--bb-ink-faint); text-decoration: underline dotted; text-underline-offset: 2px; letter-spacing: normal; }
.turn-maintain-link:hover { color: var(--bb-ink-soft); }
.turn-maintain-prompt { margin: 0.3rem 0 0; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.875rem; color: var(--bb-gilt-deep); }

/* Sweep cards ride .cap-row.stores-card; only the turn states are new. */
/* The order's level-resolved rules text — same voice as .reg-order-card-body,
   so the page reads like the Register hero it mirrors (and Beyond20 chips
   roll right here). */
.turn-rules { margin: 0 0 0.45rem; font-size: 0.875rem; line-height: 1.45; color: var(--bb-ink-soft); }
.turn-unplaced { font-family: var(--bb-font-italic); font-style: italic; font-size: 0.78rem; color: var(--bb-ink-faint); border: 1px solid var(--bb-rule-soft); padding: 0 6px; }
/* Under construction (#195) — the axis that actually threatens the tracked
   count now; gilt (a "next move"), distinct from the neutral off-map chip. */
.turn-construction { font-family: var(--bb-font-italic); font-style: italic; font-size: 0.78rem; color: var(--bb-gilt-deep); border: 1px solid var(--bb-gilt); padding: 0 6px; }
.turn-pick-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.35rem 1rem; margin: 0.1rem 0 0.35rem; }
.turn-issue-one { align-self: flex-start; font-size: 0.875rem; padding: 3px 12px; background: var(--bb-parchment-light); border: 1px solid var(--bb-rule); color: var(--bb-ink); cursor: pointer; border-radius: 0; }
.turn-issue-one:hover { background: var(--bb-parchment); border-color: var(--bb-ink-soft); }
/* The deliberate no-order-this-week whisper — counts the facility as tended
   and folds its card to a "stood down" row. */
.turn-stand-down { background: none; border: none; padding: 0; cursor: pointer; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.875rem; color: var(--bb-ink-faint); text-decoration: underline dotted; text-underline-offset: 2px; }
.turn-stand-down:hover { color: var(--bb-ink-soft); }
.turn-fac-card.is-busy { opacity: 0.62; }
.turn-fac-card.is-busy .turn-abandon { opacity: 1; }
.turn-fac-card.is-done { border-left: 3px solid var(--bb-verdigris); }
/* Due-from-earlier awaiting its result: the gilt act-here accent (the is-done
   border pattern, in the good-news register) — these cards never compact and
   sort to the top of the sweep. */
.turn-fac-card.is-due { border-left: 3px solid var(--bb-gilt); }
.turn-done-mark { margin-left: auto; color: var(--bb-verdigris); font-size: 1.05rem; }

/* Compacted ledger rows — handled facilities fold flat (no card frame),
   echoing the sealed log's label | meta grammar: half-sealed. The row is a
   full-width reopen button (every handled state still holds editable
   trackers/arrivals); busy rows keep Abandon visible beside it (touch has no
   hover). Declared AFTER the card state rules so the flat look wins the
   shared-class specificity tie. */
.turn-fac-row { display: flex; align-items: baseline; gap: 0.6rem; border-bottom: 1px dotted var(--bb-rule-soft); padding: 0.1rem 0; }
.turn-fac-row.is-busy { opacity: 1; }
.turn-fac-row.is-done, .turn-fac-row.is-due { border-left: none; }
.turn-fac-row-open { flex: 1 1 auto; min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0.6rem; align-items: baseline; background: none; border: none; border-radius: 0; padding: 0.2rem 0.15rem; cursor: pointer; text-align: left; color: inherit; font: inherit; }
.turn-fac-row-open:hover { background: var(--bb-parchment-mid); }
.turn-fac-row-open:focus-visible { outline: 2px solid var(--bb-gilt-deep); outline-offset: 1px; }
.turn-fac-row-id { display: inline-flex; align-items: baseline; gap: 0.45rem; min-width: 0; }
.turn-fac-row-id .glyph { align-self: center; opacity: 0.55; }
.turn-fac-row-name { font-family: var(--bb-font-display); color: var(--bb-ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.turn-fac-row-meta { font-family: var(--bb-font-display); font-size: 0.875rem; color: var(--bb-ink-soft); font-variant-numeric: lining-nums tabular-nums; text-align: right; }
.turn-fac-row-meta .turn-done-mark { margin-left: 0; font-size: 0.95rem; }
.turn-fac-row.is-stood .turn-fac-row-meta { font-family: var(--bb-font-italic); font-style: italic; color: var(--bb-ink-faint); }
/* Maintained rows (RAW Maintain) are the one static fold — a span, not a
   button: no hover wash, no pointer, faint meta. */
.turn-fac-row-static { cursor: default; }
.turn-fac-row-static:hover { background: none; }
.turn-fac-row.is-maintained .turn-fac-row-meta { color: var(--bb-ink-faint); }
.turn-busy-line, .turn-order-verb-line { margin: 0 0 0.35rem; font-family: var(--bb-font-italic); font-style: italic; color: var(--bb-ink-soft); }
.turn-order-line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; margin-bottom: 0.4rem; }
.turn-order-verb { font-family: var(--bb-font-display); font-variant: small-caps; font-size: 0.95rem; color: var(--bb-ink); }
.turn-order-select { max-width: 100%; }
.turn-detail { flex: 1 1 16rem; min-width: 10rem; font-family: var(--bb-font-italic); font-size: 0.9375rem; padding: 0.2rem 0.4rem; }
/* Duration hides behind a quiet dotted link ("takes 1 turn") — the stepper
   appears on demand and folds back on blur. */
.turn-duration-link { background: none; border: none; padding: 0; cursor: pointer; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.875rem; color: var(--bb-ink-soft); text-decoration: underline dotted; text-underline-offset: 2px; white-space: nowrap; }
.turn-duration-link:hover { color: var(--bb-ink); }
.turn-duration-wrap { display: inline-flex; align-items: baseline; gap: 0.3rem; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.875rem; color: var(--bb-ink-soft); white-space: nowrap; }
.turn-duration { width: 2.6rem; flex: none; text-align: right; font-family: var(--bb-font-display); font-variant-numeric: lining-nums tabular-nums; padding: 0.1rem 0.3rem; }

/* Record plaques (#188) — the catalog's known costs/yields as one-click
   "record" buttons above the result rows. A BUTTON, never a pre-filled input:
   the gilt edge is the act-here invitation (never vermillion), numerals stay
   ink-on-parchment (gilt fills fail contrast in every theme). Hidden again
   the moment the order carries a row (its group is spoken for). */
.turn-suggest { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.35rem 0.6rem; margin: 0.1rem 0 0.4rem; }
.turn-suggest-label { font-family: var(--bb-font-display); font-variant: small-caps; font-size: 0.78rem; letter-spacing: 0.04em; color: var(--bb-ink-faint); }
.turn-suggest-apply { font-size: 0.875rem; padding: 3px 10px; background: var(--bb-parchment-light); border: 1px solid var(--bb-rule); border-left: 3px solid var(--bb-gilt); color: var(--bb-ink); cursor: pointer; border-radius: 0; font-variant-numeric: lining-nums tabular-nums; }
.turn-suggest-apply:hover { background: var(--bb-parchment); border-color: var(--bb-ink-soft); border-left-color: var(--bb-gilt-deep); }
.turn-suggest-apply:focus-visible { outline: 2px solid var(--bb-gilt-deep); outline-offset: 1px; }
.turn-suggest-select { max-width: 100%; }

/* Result rows — signed, user-entered; gp | qty | item, the way a result is
   said aloud. Header and rows share one grid so the labels stay glued to
   their columns; min-width:0 everywhere or the number inputs' intrinsic
   width blows the grid out the card's right edge (the round-3 screenshot). */
.turn-results { margin: 0.2rem 0 0.4rem; }
.turn-results-head,
.turn-result-row { display: grid; grid-template-columns: 4.25rem 3rem minmax(0, 1fr) 1.25rem; gap: 0.4rem; align-items: center; }
.turn-results-head > span,
.turn-result-row > * { min-width: 0; }
.turn-results-head { margin-bottom: 0.15rem; }
.turn-results-head span { font-family: var(--bb-font-display); font-variant: small-caps; font-size: 0.72rem; letter-spacing: 0.04em; color: var(--bb-ink-faint); }
.turn-results-head span:nth-child(1),
.turn-results-head span:nth-child(2) { text-align: right; }
.turn-result-row { margin-bottom: 0.3rem; }
.turn-result-num { text-align: right; font-family: var(--bb-font-display); font-size: 0.95rem; font-variant-numeric: lining-nums tabular-nums; padding: 0.15rem 0.3rem; width: 100%; }
.turn-result-item { font-family: var(--bb-font-italic); font-size: 0.9375rem; padding: 0.15rem 0.4rem; width: 100%; }
.turn-result-del { background: none; border: none; color: var(--bb-ink-faint); cursor: pointer; font-size: 1.1rem; line-height: 1; padding: 0 0.2rem; }
.turn-result-del:hover { color: var(--bb-danger); }
.turn-result-add { background: none; border: none; padding: 0; cursor: pointer; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.9rem; color: var(--bb-ink-soft); text-decoration: underline dotted; text-underline-offset: 2px; }
.turn-result-add:hover { color: var(--bb-ink); }

/* Shared bare vermillion text-link (Abandon, Rewind) — the char-focus-btn
   vocabulary: quiet until hovered, never a filled danger button. */
.link-vermillion { background: none; border: none; box-shadow: none; padding: 0; cursor: pointer; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.9rem; color: var(--bb-vermillion); text-decoration: underline dotted; text-underline-offset: 2px; }
.link-vermillion:hover { color: var(--bb-vermillion-deep); }
.link-vermillion:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: 1px; }

/* Send to VTT (#198) — the shared Beyond20 share control: the gilt cousin of
   .link-vermillion / .fig-turn-link (gilt = an available reference action;
   vermillion stays destructive). Rendered ONLY while Beyond20 is present —
   the #145 degrade contract, so never a disabled stub. What varies per
   surface is seating only, never the look. */
.vtt-send, .discord-send, .share-send, .share-file { background: none; border: none; box-shadow: none; padding: 0; cursor: pointer; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.8125rem; letter-spacing: normal; text-transform: none; color: var(--bb-gilt-deep); text-decoration: underline dotted; text-underline-offset: 2px; white-space: nowrap; }
.vtt-send:hover, .discord-send:hover, .share-send:hover, .share-file:hover { color: var(--bb-ink); }
.vtt-send:focus-visible, .discord-send:focus-visible, .share-send:focus-visible, .share-file:focus-visible { outline: 2px solid var(--bb-gilt); outline-offset: 1px; }
/* Charter circulation cluster (#271) — the whole-bastion verbs that put this
   bastion in front of someone else. A wrapping ROW, never a stack: at the coarse
   tier the shared 44px button floor would otherwise stack 130px of chrome above
   the figures. `:empty` matters because .share-send is the only unconditional
   member — with Beyond20 absent and the cluster somehow emptied, this collapses
   rather than leaving a gap under the owner line (#145: no dead controls, and no
   dead containers either). */
.charter-acts { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 8px; }
.charter-acts:empty { display: none; }
/* A snapshot has no saved record to build a link from, so the verb would only
   ever toast "Couldn't find that bastion to share." The two dispatch verbs stay:
   canPost() already degrades them to a plain copy, and copying a summary of
   somebody else's snapshot is harmless. */
body.viewer #charter-share-link { display: none; }
/* .discord-send (#199) is the same gilt verb voice but its own class: .vtt-send
   visibility rides the Beyond20 handshake, Discord's never does (the copy
   floor always works) — sharing a class would conflate the two gates. */
/* Seating: the Register full-rules disclosure (the payload IS that text; the
   summary keeps its list-item marker, so the link floats right), Household
   card heads (identity anchor left, forward action right — vacant hirelings
   have nothing to forward), Charter identity head, the Stores section head,
   the turn title row's right cluster (gilt left of vermillion Rewind), and
   the milestone footer's action pair. */
.reg-full-rules > summary .vtt-send { float: right; }
.house-card-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.house-card-head-trail { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.house-card.is-vacant .house-vtt-send { display: none; }
/* Under-construction annotation (#195) — the roster keeps showing a staffed
   facility's card (an already-named hireling shouldn't vanish), but flags
   that it isn't counted toward capacity right now. Same gilt "next move"
   treatment as the Register strip, scaled down for a card-head chip. */
.house-construction-chip {
  flex: none;
  padding: 0 5px;
  font-family: var(--bb-font-display);
  font-size: 10px;
  letter-spacing: 0.04em;
  border: 1px solid var(--bb-gilt);
  border-radius: 2px;
  color: var(--bb-gilt-deep);
  white-space: nowrap;
}
.house-recruit-head .vtt-send { margin-left: auto; }
#charter-vtt-send { margin-top: 2px; }
#charter-discord-send { margin-top: 2px; }
/* Sealed turn rows: the recap verb sits right-aligned under the week's items,
   inside the expanded body only — the collapsed log stays calm. */
.turn-log-row .turn-log-discord { display: block; margin-left: auto; margin-top: 0.35rem; }

/* Discord post preview (#199): literal markdown source inside the post
   choiceModal — never a mock of Discord's rendered chrome. Scroll-gutter
   convention: right padding + stable gutter. */
.discord-preview {
  max-height: 40vh;
  overflow: auto;
  padding: 8px;
  padding-right: 12px;
  scrollbar-gutter: stable;
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-hairline);
  border-radius: 3px;
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 0.76rem;
  line-height: 1.45;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--bb-ink);
  text-align: left;
}

/* Settings › At the table › Discord channels (#199): one row per connected
   bastion, bastion name as the anchor, masked URL, Forget. Orphaned entries
   (bastion deleted out from under an older build's connection) mute the name
   but keep Forget — a credential the user set is never silently discarded. */
.settings-subtitle {
  margin: 0.8rem 0 0.1rem;
  font-family: var(--bb-font-display);
  font-size: 0.85rem;
  font-variant: small-caps;
  letter-spacing: 0.05em;
  color: var(--bb-ink-soft);
}
/* Connect dialog fields ride the .hb-field labeled-stack convention (label
   above, input, hint below, full width) — .settings-row's grid is for the
   Settings toggles and crams the hint into a right column here. */
#discord-connect-dialog .hb-field { margin: 0.7rem 0; }
#discord-connect-dialog .hb-field input { width: 100%; }

.discord-channel-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.discord-channel-info { display: flex; flex-direction: column; min-width: 0; }
.discord-channel-name { font-family: var(--bb-font-display); font-size: 0.85rem; color: var(--bb-ink); }
.discord-channel-row.is-orphan .discord-channel-name { color: var(--bb-ink-faint); font-style: italic; }
/* Share dialog (#209): the secondary-verb row beneath the Copy link row, home
   to the quiet-gilt Send to VTT. Wraps on phones; the thin verb gets a
   comfortable tap target at the narrow tier. */
.share-more-acts { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem 1rem; margin-top: 0.5rem; }
/* Send to VTT pins right; #272's Save as a file takes the left of the row. By
   ID, not `.vtt-send`, because two auto margins in one flex row push BOTH
   members rightward and strand the first one mid-line. */
.share-more-acts #share-vtt-send { margin-left: auto; }
@media (max-width: 700px) { .share-more-acts > * { padding: 0.5rem 0; } }
.turn-page-title-row .vtt-send { margin-left: auto; }
.milestone-footer-acts { display: inline-flex; align-items: baseline; gap: 0.7rem; }

/* On-card Stores counters — the "update the stores" step of the per-facility
   flow. The readout owns the count ("12 / 20"); the small ± field records the
   week's signed change (the Stores card below sets totals). */
.turn-trackers { margin: 0.2rem 0 0.4rem; padding-top: 0.35rem; border-top: 1px dotted var(--bb-rule-soft); }
.turn-tracker-row { display: grid; grid-template-columns: 1fr auto; gap: 0.35rem 0.5rem; align-items: baseline; margin-bottom: 0.25rem; }
.turn-tracker-label { font-family: var(--bb-font-display); font-variant: small-caps; font-size: 0.8rem; color: var(--bb-ink-faint); letter-spacing: 0.04em; }
.turn-tracker-field { display: inline-flex; align-items: baseline; gap: 0.3rem; justify-self: end; }
.turn-tracker-count { font-family: var(--bb-font-display); font-size: 0.9rem; color: var(--bb-ink); font-variant-numeric: lining-nums tabular-nums; }
.turn-tracker-delta { width: 3rem; flex: none; }

/* Chronicle log: per-order recorded results, quiet next to the order line. */
.turn-log-results { color: var(--bb-ink-soft); font-variant-numeric: lining-nums tabular-nums; }

/* On-card arrivals ("+ someone arrived") — journaled recruits, recorded where
   the Recruit order lives. */
.turn-arrivals { margin: 0.2rem 0 0.1rem; }
.turn-arrivals-line { margin: 0 0 0.25rem; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.875rem; color: var(--bb-ink-soft); }
.turn-arrival-form { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 0.4rem; align-items: center; margin-bottom: 0.25rem; }
.turn-arrival-name-wrap { position: relative; display: block; min-width: 0; }
.turn-arrival-name { min-width: 0; width: 100%; font-family: var(--bb-font-italic); font-size: 0.9375rem; padding: 0.15rem 1.35rem 0.15rem 0.4rem; }
.turn-arrival-kind { font-size: 0.875rem; color: var(--bb-ink-soft); }
.turn-arrival-commit { font-size: 0.84rem; padding: 2px 10px; background: var(--bb-parchment-light); border: 1px solid var(--bb-rule); color: var(--bb-ink); cursor: pointer; border-radius: 0; }
.turn-arrival-commit:hover { background: var(--bb-parchment); border-color: var(--bb-ink-soft); }
.turn-arrival-link { background: none; border: none; padding: 0; cursor: pointer; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.9rem; color: var(--bb-ink-soft); text-decoration: underline dotted; text-underline-offset: 2px; }
.turn-arrival-link:hover { color: var(--bb-ink); }

.turn-card-actions { display: flex; align-items: baseline; gap: 0.9rem; }

/* Event outcomes — "the table already rolled it; note what changed." The
   dotted top rule marks the zone off from the sweep (on the flat page the
   trailing position no longer does that for free); the measure cap keeps the
   delta/gold rows at a comfortable form width on the 760px column. */
.turn-event { margin: 0.9rem 0 0.4rem; padding-top: 0.6rem; border-top: 1px dotted var(--bb-rule-soft); }
.turn-event > summary { cursor: pointer; font-family: var(--bb-font-italic); font-style: italic; color: var(--bb-ink-soft); }
.turn-event > summary:hover { color: var(--bb-ink); }
.turn-event-form { padding: 0.6rem 0 0.2rem; display: flex; flex-direction: column; gap: 0.6rem; max-width: 36rem; }
.turn-event-sub { margin: 0; font-size: 0.875rem; color: var(--bb-ink-faint); }
.turn-event-label { font-family: var(--bb-font-italic); font-size: 0.9375rem; padding: 0.25rem 0.4rem; }
.turn-event-group-label { display: block; font-family: var(--bb-font-display); font-variant: small-caps; font-size: 0.8rem; letter-spacing: 0.04em; color: var(--bb-ink-faint); margin-bottom: 0.25rem; }
.turn-delta-row { display: grid; grid-template-columns: 1.4fr 1fr 4rem 1.5rem; gap: 0.4rem; align-items: center; margin-bottom: 0.3rem; }
.turn-recruit-row { display: grid; grid-template-columns: 1.2fr auto 1fr 1.5rem; gap: 0.4rem; align-items: center; margin-bottom: 0.3rem; }
/* Gold rows share the sweep cards' result-row grammar byte-for-byte
   (± gp | ± qty | item under the same header) — same grid, same order. */
.turn-gold-row { display: grid; grid-template-columns: 4.25rem 3rem minmax(0, 1fr) 1.25rem; gap: 0.4rem; align-items: center; margin-bottom: 0.3rem; }
/* min-width:0 or the number inputs' intrinsic width blows past their fixed
   columns and slides UNDER the next field — right-aligned typed digits paint
   beneath the neighbour's opaque background, so the field looks dead (the
   round-3 .turn-result-row lesson, belatedly applied to the event form). */
.turn-delta-row > *, .turn-recruit-row > *, .turn-gold-row > * { min-width: 0; }
/* Condition offer row (#250): the verb+duration label, the facility picker, and
   the remove ×. The label leads (it's the fixed part); the facility ▾ is the
   only field the user sets. */
.turn-condition-row { display: grid; grid-template-columns: auto 1fr 1.5rem; gap: 0.4rem; align-items: center; margin-bottom: 0.3rem; }
.turn-condition-row > * { min-width: 0; }
.turn-condition-verb { font-family: var(--bb-font-display); font-variant: small-caps; font-size: 0.8rem; letter-spacing: 0.03em; color: var(--bb-vermillion-deep); white-space: nowrap; }
/* #208: the name field is wrapped so the suggest quill can anchor to its
   trailing edge; the wrap is the grid child now, the input fills it. */
.turn-recruit-name-wrap { position: relative; display: block; min-width: 0; }
.turn-recruit-name { font-family: var(--bb-font-italic); font-size: 0.9375rem; padding: 0.15rem 1.35rem 0.15rem 0.4rem; width: 100%; }
.turn-gold-item { font-family: var(--bb-font-italic); font-size: 0.9375rem; padding: 0.15rem 0.4rem; width: 100%; }
.turn-row-del { background: none; border: none; color: var(--bb-ink-faint); cursor: pointer; font-size: 1.1rem; line-height: 1; padding: 0 0.2rem; }
.turn-row-del:hover { color: var(--bb-danger); }
.turn-event-add { background: none; border: none; padding: 0; cursor: pointer; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.9rem; color: var(--bb-ink-soft); text-decoration: underline dotted; text-underline-offset: 2px; }
.turn-event-add:hover { color: var(--bb-ink); }
.turn-event-actions { display: flex; justify-content: flex-end; }
.turn-event-record { font-size: 0.875rem; padding: 3px 12px; background: var(--bb-parchment-light); border: 1px solid var(--bb-rule); color: var(--bb-ink); cursor: pointer; border-radius: 0; }
.turn-event-record:hover { background: var(--bb-parchment); border-color: var(--bb-ink-soft); }

/* Bastion Events reference (#200) — die-then-reading: the narrow roll input
   maps the DM's result to the table entry; the select is the same lookup by
   name. One composed control, so the block strides tighter than the form. */
.turn-event-pick-block { display: flex; flex-direction: column; gap: 0.35rem; align-items: flex-start; }
.turn-event-pick-row { display: flex; align-items: center; gap: 0.4rem; width: 100%; }
.turn-event-roll { width: 2.9rem; flex: none; text-align: right; font-family: var(--bb-font-display); font-size: 0.95rem; font-variant-numeric: lining-nums tabular-nums; padding: 0.15rem 0.3rem; }
/* The die stamp fronts the row (roll-die-chip look, its prose margin
   dropped); rolling it is the send-to-VTT — the input stays the record. */
.turn-event-pick-row .roll-die-chip { flex: none; margin-left: 0; font-size: 0.8rem; }
.turn-event-pick { min-width: 0; flex: 1; }
.turn-event-hint { margin: 0; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.875rem; color: var(--bb-ink-faint); }
/* Attack casualty helper (#251) — the one COMPUTED line in the event form. No
   panel, no spine (both spine semantics are spoken for): emphasis is the pool
   line's ink weight, and adverse valence is carried in vermillion text only. */
.turn-casualty { display: flex; flex-direction: column; gap: 0.3rem; margin: 0.15rem 0 0.1rem; }
.turn-casualty-pool { margin: 0; color: var(--bb-ink); font-size: 0.9375rem; }
.turn-casualty-why { font-family: var(--bb-font-italic); font-style: italic; color: var(--bb-ink-faint); font-size: 0.82rem; }
.turn-casualty-tally-row { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }
.turn-casualty-label { display: inline-flex; align-items: baseline; gap: 0.4rem; font-family: var(--bb-font-display); font-variant: small-caps; font-size: 0.8rem; letter-spacing: 0.04em; color: var(--bb-ink-faint); }
.turn-casualty-ones { width: 3rem; flex: none; }
.turn-casualty-readout { font-family: var(--bb-font-italic); font-style: italic; font-size: 0.875rem; color: var(--bb-vermillion-deep); }
.turn-casualty-readout.is-complete { color: var(--bb-gilt-deep); }
.turn-casualty-alarm { margin: 0; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.875rem; color: var(--bb-vermillion-deep); }
/* The gilt prompts' own actions (record the outcome / all is well / record a
   haunting) — links in the prompt's italic voice, never separate buttons. */
.turn-prompt-record, .turn-quick-well { background: none; border: none; padding: 0; cursor: pointer; font: inherit; color: var(--bb-gilt-deep); text-decoration: underline dotted; text-underline-offset: 2px; }
.turn-prompt-record:hover, .turn-quick-well:hover { color: var(--bb-ink); }
.turn-prompt-record:focus-visible, .turn-quick-well:focus-visible { outline: 2px solid var(--bb-gilt-deep); outline-offset: 1px; }

/* Footer: the page's closing line — derived tally (sign-neutral ink — an
   almanac, not a P&L), the honest pre-advance note when work remains, and the
   loop's primary verb. Rewind lives in the page head, away from Advance. */
.turn-page-footer { margin-top: 0.9rem; padding-top: 0.7rem; border-top: 1px solid var(--bb-rule-soft); }
.turn-advance-note { margin: 0 0 0.5rem; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.875rem; color: var(--bb-gilt-deep); }
/* Active-condition reminders on an expanded sweep card (#250) — the
   .turn-advance-note idiom, valence-coloured. Enforcing conditions name their
   cause in the folded row's meta; this is the informational (double / slowed)
   reminder, and what an enforcing card shows once reopened to override. */
.turn-condition-notes { display: flex; flex-wrap: wrap; gap: 2px 10px; margin: 0.1rem 0 0.4rem; }
.turn-condition-note { font-family: var(--bb-font-italic); font-style: italic; font-size: 0.82rem; }
.turn-condition-note.is-adverse { color: var(--bb-vermillion-deep); }
.turn-condition-note.is-boon { color: var(--bb-gilt-deep); }
.turn-tally { margin: 0 0 0.6rem; font-family: var(--bb-font-display); font-size: 0.95rem; color: var(--bb-ink); font-variant-numeric: lining-nums tabular-nums; }
.turn-footer-actions { display: flex; justify-content: flex-end; align-items: baseline; gap: 1rem; }

/* "Earlier turns" — the ruled divider between the open page and the sealed
   log rows (hidden until a second turn exists). */
.turn-log-divider { margin: 1.4rem 0 0.4rem; padding-bottom: 0.25rem; border-bottom: 1px solid var(--bb-rule-soft); font-family: var(--bb-font-display); font-variant: small-caps; font-weight: normal; letter-spacing: 0.05em; font-size: 0.9rem; color: var(--bb-ink-faint); }

/* Chronicle › The Bastion Turns: the Begin invitation (gilt = gentle, never a
   vermillion alarm) and the read-only turn log — the handout .turn-ledger's
   label | meta grammar transposed to app tokens. */
.btn-begin-turns { align-self: flex-start; background: var(--bb-parchment-light); border: 1px solid var(--bb-gilt); color: var(--bb-gilt-deep); font-family: var(--bb-font-display); font-size: 0.95rem; padding: 6px 14px; cursor: pointer; border-radius: 0; }
.btn-begin-turns:hover { background: color-mix(in srgb, var(--bb-gilt) 12%, var(--bb-parchment-light)); border-color: var(--bb-gilt-deep); }
.turn-log-row { border-bottom: 1px dotted var(--bb-rule-soft); padding: 0.35rem 0; }
.turn-log-row > summary { display: grid; grid-template-columns: 1fr auto; gap: 0.6rem; align-items: baseline; cursor: pointer; list-style-position: outside; }
.turn-log-label { font-family: var(--bb-font-display); color: var(--bb-ink); }
.turn-log-meta { font-family: var(--bb-font-display); font-size: 0.875rem; color: var(--bb-ink-soft); font-variant-numeric: lining-nums tabular-nums; white-space: nowrap; }
.turn-log-body { margin: 0.35rem 0 0.15rem; padding-left: 1.3rem; font-size: 0.9375rem; color: var(--bb-ink); }
.turn-log-body li { margin-bottom: 0.15rem; }
.turn-log-note { font-family: var(--bb-font-italic); font-style: italic; color: var(--bb-ink-soft); }
.turn-log-event { color: var(--bb-ink-soft); }
.turn-log-quiet { margin: 0.35rem 0 0.15rem; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.9rem; color: var(--bb-ink-faint); }

/* "Recorded this turn" (#192) — the live week's correction ledger on the open
   page: sealed-log row grammar plus a revert-and-delete × per row. */
.turn-recorded-log { margin-bottom: 0.9rem; }
.turn-recorded-log .turn-log-divider { margin-top: 1.1rem; }
.turn-recorded-row { display: flex; align-items: baseline; gap: 0.4rem; }
.turn-recorded-text { flex: 1 1 auto; min-width: 0; }

/* #218 — Chronicle field vocabulary (single source of truth for the chapter's
   editable fields). The Chronicle is the bastion's ledger; its inputs had
   drifted to heavy full-bordered boxes that read as pasted-on form chrome
   against the parchment — out of step with the Household and Register. Re-cast
   them in the app's two established editable-field idioms so the chapter reads
   as one hand. Each field below keeps only its own width / font / padding /
   alignment; the frame lives here.

   Archetype A — the written line: the Household's recessed field (a parchment
   ground under a transparent frame with a 2px left rule that warms to gilt on
   touch). Prose and free-text ledger entries. */
.stores-note,
.stores-ledger-label,
.stores-ledger-value,
.turn-detail,
.turn-result-item,
.turn-arrival-name,
.turn-recruit-name,
.turn-gold-item,
.turn-event-label {
  background: var(--bb-parchment);
  border: 1px solid transparent;
  border-left: 2px solid var(--bb-rule-soft);
  border-radius: 0;
  color: var(--bb-ink);
  transition: background-color 90ms ease, border-color 90ms ease;
}
.stores-note:hover,
.stores-ledger-label:hover,
.stores-ledger-value:hover,
.turn-detail:hover,
.turn-result-item:hover,
.turn-arrival-name:hover,
.turn-recruit-name:hover,
.turn-gold-item:hover,
.turn-event-label:hover { border-left-color: var(--bb-gilt); }
.stores-note:focus,
.stores-ledger-label:focus,
.stores-ledger-value:focus,
.turn-detail:focus,
.turn-result-item:focus,
.turn-arrival-name:focus,
.turn-recruit-name:focus,
.turn-gold-item:focus,
.turn-event-label:focus {
  outline: none;
  border-color: var(--bb-rule);
  border-left-color: var(--bb-gilt-deep);
  background: var(--bb-parchment-light);
}

/* Archetype B — the ruled slot: a fill-in-the-blank ledger line (transparent
   ground, one bottom rule) for figures written into the record. Lighter than a
   box, and the underline rhymes with the dotted leaders in the Stores rows and
   the existing date slots. Numerals stay ink — vermillion is the app's
   identity/alarm register, never "here's a value". */
.stores-num,
.turn-result-num,
.turn-duration,
.turn-event-roll {
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--bb-rule-soft);
  border-radius: 0;
  /* Clear the global inset "sunken" shadow — a ruled slot must read as a flat
     line, not a recessed box (this is what still boxed the numbers). */
  box-shadow: none;
  color: var(--bb-ink);
  transition: background-color 90ms ease, border-color 90ms ease, box-shadow 90ms ease;
}
.stores-num:hover,
.turn-result-num:hover,
.turn-duration:hover,
.turn-event-roll:hover { border-bottom-color: var(--bb-gilt); }
/* Focus: a 2px gilt underline drawn as an inset shadow (no height shift, no
   re-boxing vermillion ring) — the date-slot idiom, in the gilt register. */
.stores-num:focus,
.turn-result-num:focus,
.turn-duration:focus,
.turn-event-roll:focus {
  outline: none;
  border-bottom-color: var(--bb-gilt-deep);
  background: var(--bb-parchment);
  box-shadow: inset 0 -2px 0 var(--bb-gilt-deep);
}
.turn-event-roll[aria-invalid="true"],
.turn-casualty-ones[aria-invalid="true"] { border-bottom-color: var(--bb-vermillion); }

/* Milestone in-game date (#87 rider): a faint dotted micro-affordance that
   becomes an inline input — the ck-name inline-edit look at meta scale. */
.milestone-date-slot { display: inline-flex; align-items: baseline; }
.milestone-date-btn { background: none; border: none; box-shadow: none; padding: 0; cursor: pointer; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.8125rem; color: var(--bb-ink-faint); text-decoration: underline dotted; text-underline-offset: 2px; }
.milestone-date-btn:hover { color: var(--bb-ink); }
.milestone-date-btn.has-date { color: var(--bb-ink-soft); text-decoration: none; }
.milestone-date-btn.has-date:hover { text-decoration: underline dotted; text-underline-offset: 2px; }
.milestone-date-input { width: 9rem; min-width: 0; background: transparent; border: none; border-bottom: 1px dotted var(--bb-rule); border-radius: 0; box-shadow: none; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.8125rem; color: var(--bb-ink); padding: 0; }
.milestone-date-input:focus { outline: none; border-bottom-color: var(--bb-ink-soft); }

/* Charter vitals tile action (#112): a quiet in-tile text link. */
.fig-turn-link { align-self: flex-start; background: none; border: none; padding: 0; margin-top: 0.15rem; cursor: pointer; font-family: var(--bb-font-italic); font-style: italic; font-size: 0.8125rem; color: var(--bb-gilt-deep); text-decoration: underline dotted; text-underline-offset: 2px; }
.fig-turn-link:hover { color: var(--bb-ink); }

/* A stat tile's FACT register (#272) — what's in the air on a shared snapshot,
   sitting in the slot .fig-turn-link vacates in a viewer. Same recipe as
   .annotation-legend-hint and .filing-note; deliberately NOT .fig-turn-link's
   gilt dotted underline, which promises somewhere to go. */
.fig-note {
  align-self: flex-start;
  margin: 0.15rem 0 0;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8rem;
  color: var(--bb-ink-soft);
}

/* Register order-card echo (#112): a small this-turn line under the outcome
   prose; doubles as the pre-start discoverability hint (ink-faint). */
.reg-turn-echo { margin: 0.4rem 0 0; padding-top: 0.35rem; border-top: 1px solid var(--bb-rule-soft); font-size: 0.85rem; color: var(--bb-ink-soft); }
.reg-turn-hint { font-family: var(--bb-font-italic); font-style: italic; color: var(--bb-ink-faint); }

/* Household › In Residence: recruit cards share the #154 gallery idiom —
   grid + card rules live with the .house-card family above. */
#residence-block { margin-top: 1.5rem; }

/* Help dialog (§A, issue #71) and welcome dialog (§B) */
.modal-wide { max-width: 760px; }

/* Help dialog fills a bounded height and lets each pane scroll independently, so
   the content pane reaches the bottom instead of stopping at a fixed cap while
   the (grouped, now longer) nav column runs taller. Mirrors #settings-dialog. */
#help-dialog .dialog-form {
  max-height: 88vh;
}
#help-dialog .dialog-actions {
  flex-shrink: 0;
}
/* In-dialog Help search (#173). A full-width bar pinned between the title and
   the two scrolling columns — belongs to neither, so it stays put while nav and
   content scroll under it. Filters the rendered sections client-side. */
.help-search {
  position: relative;
  flex: none;
  margin: 0 0 14px;
}
.help-search-input {
  width: 100%;
  padding-right: 2rem; /* room for the ✕ */
}
.help-search-input::placeholder {
  color: var(--bb-ink-faint);
  font-family: var(--bb-font-italic);
  font-style: italic;
}
/* Themed clear ✕ — mirrors .renown-remove's ink-soft → vermillion language. */
.help-search-clear {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  padding: 0 2px;
  color: var(--bb-ink-faint);
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
}
.help-search-clear:hover { color: var(--bb-vermillion); }
/* Keep centering under the global button:active nudge (see .house-suggest). */
.help-search-clear:active { transform: translateY(calc(-50% + 1px)); }
.help-search-clear:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: 1px; }
/* Matched-term highlight — gilt (neutral/temporal), never vermillion (alert)
   or sapphire (reserved). Text stays ink for AA contrast. */
mark.help-hit {
  background: color-mix(in srgb, var(--bb-gilt) 28%, transparent);
  color: inherit;
  border-radius: 0;
  padding: 0 1px;
}
.help-empty {
  padding: 8px 0;
  font-family: var(--bb-font-italic);
  font-style: italic;
  color: var(--bb-ink-soft);
}
.help-empty span {
  display: block;
  margin-top: 4px;
  color: var(--bb-ink-faint);
}
.help-layout {
  display: grid;
  grid-template-columns: 152px 1fr;
  gap: 0 20px;
  flex: 1 1 auto;
  min-height: 0;
}
.help-nav {
  display: flex;
  flex-direction: column;
  gap: 1px;
  border-right: 1px solid var(--bb-rule-soft);
  padding-right: 16px;
  padding-top: 2px;
  overflow-y: auto;
  min-height: 0;
}
.help-nav a {
  display: block;
  padding: 5px 0;
  color: var(--bb-ink-soft);
  font-family: var(--bb-font-display);
  font-size: 0.8125rem;
  letter-spacing: 0.06em;
  text-decoration: none;
  text-transform: uppercase;
  line-height: 1.3;
}
.help-nav a:hover,
.help-nav a:focus-visible {
  color: var(--bb-vermillion);
  outline: none;
}
/* Category header in the nav rail (#117 follow-up): a quiet gilt label with a
   hairline rule, grouping the section links beneath it. Not a link. */
.help-nav-group {
  margin-top: 14px;
  padding-bottom: 3px;
  border-bottom: 1px solid var(--bb-rule-soft);
  color: var(--bb-gilt-deep);
  font-family: var(--bb-font-display);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.help-nav-group:first-child {
  margin-top: 0;
}
.help-content {
  overflow-y: auto;
  min-height: 0;
  padding-right: 4px;
}
.help-content h3 {
  font-family: var(--bb-font-display);
  font-size: 0.9375rem;
  color: var(--bb-vermillion-deep);
  margin: 0 0 10px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--bb-rule-soft);
  letter-spacing: 0.04em;
}

/* Collapsible help sections (#94). Each section is a <details>; its <summary>
   carries the section heading and a chevron. Getting started ships open. */
.help-section {
  border-bottom: 1px solid var(--bb-rule-soft);
}
.help-section:last-child { border-bottom: none; }
.help-section[open] { padding-bottom: 8px; }
.help-section > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 0;
  cursor: pointer;
  list-style: none;
}
.help-section > summary::-webkit-details-marker { display: none; }
.help-section > summary h3 {
  margin: 0;
  padding-bottom: 0;
  border-bottom: none;
}
.help-section > summary::after {
  content: "";
  flex: none;
  width: 7px;
  height: 7px;
  margin-right: 3px;
  border-right: 1.5px solid var(--bb-ink-soft);
  border-bottom: 1.5px solid var(--bb-ink-soft);
  transform: rotate(45deg);
  transition: transform 0.15s ease, border-color 0.15s ease;
}
.help-section[open] > summary::after { transform: rotate(-135deg); }
.help-section > summary:hover h3,
.help-section > summary:focus-visible h3 { color: var(--bb-vermillion); }
.help-section > summary:hover::after,
.help-section > summary:focus-visible::after { border-color: var(--bb-vermillion); }
.help-section > summary:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}
.help-content p {
  margin: 0 0 10px;
  font-family: var(--bb-font-text);
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--bb-ink-soft);
}
.help-content p:last-child { margin-bottom: 0; }
.help-content a { color: var(--bb-vermillion); text-underline-offset: 2px; }
.help-content strong { color: var(--bb-ink); }
.help-content h4 {
  font-family: var(--bb-font-display);
  font-size: 0.875rem;
  color: var(--bb-ink);
  margin: 16px 0 6px;
  font-weight: 600;
  letter-spacing: 0.02em;
}
.help-content ul {
  margin: 0 0 10px;
  padding-left: 1.25em;
  font-family: var(--bb-font-text);
  font-size: 0.9375rem;
  color: var(--bb-ink-soft);
}
.help-content li {
  margin-bottom: 4px;
  line-height: 1.5;
}
/* Numbered "Getting started" steps — gilt counters in the display face so the
   onboarding track reads like a handout's lettered instructions (#94). */
.help-content ol.help-steps {
  margin: 0 0 10px;
  padding-left: 0;
  list-style: none;
  counter-reset: help-step;
  font-family: var(--bb-font-text);
  font-size: 0.9375rem;
  color: var(--bb-ink-soft);
}
.help-content ol.help-steps > li {
  counter-increment: help-step;
  position: relative;
  padding-left: 2em;
  margin-bottom: 8px;
  line-height: 1.5;
}
.help-content ol.help-steps > li::before {
  content: counter(help-step);
  position: absolute;
  left: 0;
  top: 0;
  width: 1.4em;
  text-align: center;
  font-family: var(--bb-font-display);
  font-size: 0.875rem;
  color: var(--bb-gilt-deep);
  font-weight: 600;
}
.keys-list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 6px 16px;
  margin: 0 0 10px;
  padding: 0;
  font-size: 0.875rem;
  color: var(--bb-ink-soft);
}
.keys-list dt { margin: 0; align-self: center; }
.keys-list dd { margin: 0; line-height: 1.4; align-self: center; }
.keys-list kbd {
  display: inline-block;
  font-family: var(--bb-font-mono);
  font-size: 0.8125rem;
  font-variant-numeric: lining-nums tabular-nums;
  background: var(--bb-parchment-mid);
  border: 1px solid var(--bb-rule);
  padding: 1px 6px;
  white-space: nowrap;
}
.help-about-version {
  font-family: var(--bb-font-mono);
  font-size: 0.84rem;
  color: var(--bb-ink-soft);
  margin-top: 6px;
}

/* ---- Standalone Help page (#174) ---------------------------------------- */
/* The in-app guide, published as its own page (web/help.html) so it can sit
 * beside the app in a second tab. Reuses every .help-* style above; the rules
 * below are only the deltas from the modal context — a bounded centered column,
 * one page scroll (not two inner scrollbars), and a sticky nav rail. Scoped to
 * .help-page / .help-page-bar so the in-app dialog is untouched. */

/* Tier-1 chrome: the app top-bar recipe, pinned so the brand + search stay
 * reachable while the guide scrolls. Opaque (the app bar is translucent, which
 * would let scrolled content bleed through on a scrolling page). */
.help-page-bar {
  position: sticky;
  top: 0;
  z-index: 20;
  background: linear-gradient(180deg, var(--bb-parchment-light), var(--bb-parchment-mid));
}
.help-page-bar a { text-decoration: none; }
/* Search sits at the right of the bar; shrinks before the brand does. */
.help-page-bar .help-search {
  flex: 0 1 280px;
  min-width: 0;
  margin: 0;
}

.help-page {
  max-width: 880px;
  margin-inline: auto;
  padding: 18px 24px 40px;
}
.help-page .dialog-title { margin-top: 0; }

/* The dialog bounds each pane and scrolls it internally; on a full page we want
 * a single document scroll instead. */
.help-page .help-nav,
.help-page .help-content {
  overflow: visible;
}

/* Nav rail follows the reader; clears the sticky top-bar (~52px) and scrolls on
 * its own only if it outgrows the viewport. */
.help-page .help-nav {
  position: sticky;
  top: 58px;
  align-self: start;
  max-height: calc(100vh - 68px);
  overflow-y: auto;
}
/* Jump-links and native Ctrl+F must not land under the sticky bar. */
.help-page .help-section { scroll-margin-top: 66px; }

/* Phone: drop the nav rail (all sections default open + search cover it) and go
 * single-column. The back-link stays — it's the only way back without switching
 * tabs, unlike the app's in-chrome Help button. */
@media (max-width: 640px) {
  .help-page { padding: 14px 16px 32px; }
  .help-page .help-layout { grid-template-columns: 1fr; }
  .help-page .help-nav { display: none; }
}

/* ---- Themed tooltip (§G) ------------------------------------------------ */

.bb-tooltip {
  position: fixed;
  z-index: 9000;
  display: none;
  max-width: 300px;
  padding: 6px 10px;
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule);
  border-radius: 0;
  font-family: var(--bb-font-text);
  font-size: 0.84rem;          /* 13.5px */
  line-height: 1.4;
  color: var(--bb-ink-soft);
  box-shadow: 0 10px 24px -8px var(--bb-shadow-mid);
  pointer-events: none;
}
.bb-tooltip.visible { display: block; }
.bb-tip-title {
  font-family: var(--bb-font-display);
  font-size: 0.84rem;          /* 13.5px */
  color: var(--bb-ink);
  line-height: 1.2;
  padding-bottom: 5px;
  margin-bottom: 5px;
  border-bottom: 1px solid var(--bb-rule-soft);
}
.bb-tip-key {
  display: inline-block;
  margin-left: 6px;
  font-family: var(--bb-font-mono);
  font-size: 0.75rem;
  font-variant-numeric: lining-nums tabular-nums;
  line-height: 1.3;
  color: var(--bb-ink);
  background: var(--bb-parchment-mid);
  border: 1px solid var(--bb-rule);
  border-radius: 0;
  padding: 0 5px;
  white-space: nowrap;
  vertical-align: baseline;
}

/* Placement-banner pb-icon kind override (§2.5): special = vermillion
 * (default), basic = sapphire. Both render the glyph in parchment-light. */
.placement-banner .pb-icon.basic {
  background: var(--bb-sapphire);
  color: var(--bb-parchment-light);
}

/* Picker panels inside the rail need to not bust the rail width. */

/* Status toast — when the page is in single-column mode, give it more room. */
@media (max-width: 1000px) {
  .status-toast { bottom: 12px; }
}

/* The `hidden` attribute must always win over our display: flex/grid rules. */
[hidden] { display: none !important; }

/* Disabled picker item — used when the Special-facility option is at cap. */
.picker-list li.disabled,
.picker-list li[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}
.picker-list li.disabled:hover,
.picker-list li[aria-disabled="true"]:hover {
  background: transparent;
  border-color: var(--bb-rule-soft);
}

/* Idle placement-banner state (§6.1): calm parchment-mid hint strip. No
 * pulse, dashed pb-icon, no chip background on the status text. */
/* Two-weight status zone (#230): idle recedes to a quiet caption so it stops
 * competing with the toolbar above it; arming a tool lights the zone back up
 * (the .mode / placing states are untouched). The row keeps its reserved
 * height either way — content swaps in place, never visibility, so the canvas
 * never reflows and the aria-live region is never torn down. */
.placement-banner.idle {
  background: transparent;
  border-color: var(--bb-rule-soft);
  box-shadow: none;
  animation: none;
  color: var(--bb-ink-faint);
}
.placement-banner.idle .pb-icon {
  /* Drop the disc entirely: a bare, muted inline mark — no fill, no ring — so
   * idle reads as a caption line, not a boxed banner. */
  background: transparent;
  border: none;
  color: var(--bb-ink-faint);
  font-weight: 400;
  font-size: 0.9rem;
}
.placement-banner.idle .pb-text strong {
  /* Muted caption weight — no display-face bolding, no confident ink. */
  color: var(--bb-ink-faint);
  font-family: var(--bb-font-display);
  font-weight: 500;
  font-style: normal;
  letter-spacing: 0.01em;
}
.placement-banner.idle .pb-text small {
  font-family: var(--bb-font-italic);
  font-style: italic;
  color: var(--bb-ink-faint);
}
/* Keep the Cancel button in layout when idle so the banner has the same
 * height whether idle or active — just hide it visually + from focus. */
.placement-banner.idle #btn-cancel-placement { visibility: hidden; }

/* Haunted badge — eerie corner mark on placed facility rects (SVG). */
.haunted-badge { pointer-events: none; }
.haunted-badge-bg {
  fill: var(--bb-parchment);
  stroke: var(--bb-haunted);
  stroke-width: 1;
  opacity: 0.92;
}

/* Under-construction badge (#195) — same corner-disc grammar, gilt instead
 * of haunted's violet; top-right so it never collides with the haunted mark. */
.construction-badge { pointer-events: none; }
.construction-badge-bg {
  fill: var(--bb-parchment);
  stroke: var(--bb-gilt);
  stroke-width: 1;
  opacity: 0.92;
}

/* =========================================================================
 * Facility hover tooltip (floor canvas)
 * ========================================================================= */
/* Facility hover tooltip (§6.5.11) — parchment-light gradient with a
 * per-kind outer halo ring (no colored border — the halo is the hue tag). */
#facility-tooltip {
  position: fixed;
  z-index: 1000;
  pointer-events: none;
  background: linear-gradient(180deg, var(--bb-parchment-light), var(--bb-parchment-mid));
  border: 1px solid var(--bb-rule);
  border-radius: 0;
  padding: 8px 12px 9px;
  box-shadow: 0 10px 24px -8px var(--bb-shadow-deep);
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 100ms ease, transform 100ms ease;
  min-width: 160px;
  max-width: 260px;
}
#facility-tooltip.visible { opacity: 1; transform: translateY(0); }
#facility-tooltip.special {
  box-shadow:
    0 10px 24px -8px var(--bb-shadow-deep),
    0 0 0 3px var(--bb-special-ring);
}
#facility-tooltip.basic {
  box-shadow:
    0 10px 24px -8px var(--bb-shadow-deep),
    0 0 0 3px var(--bb-basic-ring);
}
#facility-tooltip .tt-name {
  font-family: var(--bb-font-display);
  font-size: 0.875rem;             /* 14px */
  line-height: 1.15;
  color: var(--bb-ink);
}
#facility-tooltip .tt-meta {
  margin-top: 4px;
  font-family: var(--bb-font-display);
  font-size: 0.6875rem;             /* 11px */
  letter-spacing: 0.20em;
  text-transform: uppercase;
  color: var(--bb-ink-faint);
}
#facility-tooltip .tt-meta .dot { color: var(--bb-rule-soft); margin: 0 6px; }
#facility-tooltip .tt-sub {
  color: var(--bb-vermillion);
  font-family: var(--bb-font-italic);
  font-style: italic;
}
/* Catalog one-liner (#117). Line-clamped — the tooltip floats over the
 * floorplan and must never blanket it. */
#facility-tooltip .tt-desc {
  margin-top: 5px;
  font-family: var(--bb-font-text);
  font-size: 0.8125rem;              /* 13px */
  line-height: 1.35;
  color: var(--bb-ink-soft);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
#facility-tooltip .tt-orders {
  margin-top: 4px;
  font-family: var(--bb-font-text);
  font-size: 0.8125rem;              /* 13px */
  color: var(--bb-ink-soft);
}

/* =========================================================================
 * Credits footer — fixed bottom-right corner so it never reflows the
 * floorplan-height-100vh layout.
 * ========================================================================= */
/* Version badge (Codex §8) — channel-aware build pill. Now lives in the
 * Settings → About row (it used to float in a fixed bottom-right footer). */
.version-badge {
  display: inline-block;
  position: relative;
  padding: 1px 7px;
  border-radius: 0;                 /* squared per §2.4 */
  font-family: var(--bb-font-mono);
  font-size: 0.75rem;             /* 12px */
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  vertical-align: 1px;
  user-select: none;
  cursor: pointer;
  opacity: 1;
  transition: filter 0.15s;
  border: 1px solid var(--bb-rule);
}
.version-badge:hover { filter: brightness(1.06); }
.version-badge[data-channel="release"] {
  background: var(--bb-parchment-mid);
  color: var(--bb-ink-soft);
  border-color: var(--bb-rule);
}
.version-badge[data-channel="edge"] {
  background: rgba(168,120,38,0.18);
  color: var(--bb-gilt-deep);
  border-color: var(--bb-gilt);
}
/* Barber-pole striped pill — "you're running an unbuilt working tree". */
.version-badge[data-channel="dev"] {
  background: repeating-linear-gradient(
    135deg,
    var(--bb-parchment-mid),
    var(--bb-parchment-mid) 4px,
    var(--bb-parchment-deep) 4px,
    var(--bb-parchment-deep) 8px
  );
  color: var(--bb-ink-soft);
  border-color: var(--bb-rule);
}
.version-badge.has-update {
  outline: 2px solid var(--bb-gilt);
  outline-offset: 2px;
}
.version-badge.has-update::after {
  content: "";
  position: absolute;
  top: -4px;
  right: -4px;
  width: 8px;
  height: 8px;
  background: var(--bb-vermillion);
  border-radius: 0;
}
/* #164 — the same update cue mirrored onto persistent chrome: the top-bar
   Settings button, and the phone-tier "⋯ More" trigger it collapses into. Dot
   ONLY here, no gilt outline: on an interactive control the outline reads as a
   focus ring (the static badge keeps its outline because it never takes focus).
   The vermillion dot is a sanctioned exception to the #131 vermillion-is-alert
   rule, reserved for the update cue. */
#btn-settings.has-update,
#btn-more-menu.has-update {
  position: relative;
}
#btn-settings.has-update::after,
#btn-more-menu.has-update::after {
  content: "";
  position: absolute;
  top: -4px;
  right: -4px;
  width: 8px;
  height: 8px;
  background: var(--bb-vermillion);
  border-radius: 0;
}
/* Changelog "What's new" body: fetched CHANGELOG.md is rendered to real
   markup client-side (help.js highlightsToHtml), so it needs its own
   typography rather than the plain-text .dialog-body pre-line default. */
.changelog-body {
  white-space: normal;
}
.changelog-body h3 {
  font-family: var(--bb-font-display);
  font-size: 0.9375rem;
  color: var(--bb-vermillion-deep);
  margin: 18px 0 8px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--bb-rule-soft);
  letter-spacing: 0.04em;
}
.changelog-body h3:first-child { margin-top: 0; }
.changelog-body ul {
  margin: 0 0 4px;
  padding-left: 1.25em;
  font-family: var(--bb-font-text);
  font-size: 0.84rem;
}
.changelog-body li {
  margin-bottom: 8px;
  line-height: 1.55;
}
.changelog-body a { color: var(--bb-vermillion); text-underline-offset: 2px; }
.changelog-body strong { color: var(--bb-ink); }
.changelog-body kbd {
  display: inline-block;
  font-family: var(--bb-font-mono);
  font-size: 0.78rem;
  background: var(--bb-parchment-mid);
  border: 1px solid var(--bb-rule);
  padding: 0 5px;
  white-space: nowrap;
}

/* Level-up digest (#252) — the celebratory twin of the compliance notice, in the
   shared confirm dialog. `.levelup-body` restores normal white-space (the enclosing
   .dialog-body is pre-line). Gilt-keyed (earned-good), where compliance is vermillion. */
.levelup-body { white-space: normal; }
.levelup-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-bottom: 12px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--bb-rule-soft);
}
.levelup-glyph { flex: none; width: 40px; height: 40px; color: var(--bb-gilt-deep); }
.levelup-glyph .glyph-svg { width: 100%; height: 100%; }
.levelup-hero { margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; }
.levelup-hero-num {
  font-family: var(--bb-font-display);
  font-size: 1.5rem;
  color: var(--bb-gilt-deep);
  line-height: 1;
}
.levelup-hero-label { font-family: var(--bb-font-display); font-size: 0.9375rem; color: var(--bb-ink); }
.levelup-hero-total {
  flex-basis: 100%;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.875rem;
  color: var(--bb-ink-soft);
}
.levelup-avail { margin: 0; color: var(--bb-ink-soft); }
.levelup-avail strong { color: var(--bb-ink); }
.levelup-subhead {
  font-family: var(--bb-font-display);
  font-size: 0.9375rem;
  color: var(--bb-gilt-deep);
  margin: 16px 0 8px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--bb-rule-soft);
  letter-spacing: 0.04em;
}
.levelup-improved-list { list-style: none; margin: 0; padding: 0; }
.levelup-improved-list li { display: flex; flex-direction: column; margin-bottom: 8px; }
.levelup-improved-name { font-family: var(--bb-font-text); color: var(--bb-ink); }
.levelup-improved-note {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.875rem;
  color: var(--bb-ink-soft);
}

/* Vehicle hull outline on the floorplan */
.vehicle-hull-outline {
  fill: none;
  stroke: var(--bb-ink);
  stroke-width: 5;
  opacity: 0.45;
  stroke-linejoin: round;
  stroke-linecap: round;
}

/* Vehicle type badge in the sidebar identity section */
.bastion-type-badge {
  font-family: var(--bb-font-italic);
  font-size: 0.78125rem;
  font-style: italic;
  color: var(--bb-ink-soft);
  letter-spacing: 0.02em;
}

/* Disabled mode buttons (e.g. Defenses on vehicle bastions, Note when
 * marginalia is hidden) */
.mode-btn:disabled,
.mode-btn:disabled:hover {
  opacity: 0.38;
  background: var(--bb-parchment-light);
  color: var(--bb-ink-soft);
  border-color: var(--bb-rule);
  cursor: not-allowed;
}

/* =========================================================================
 * Tablet / narrow-window tier (#218) — the top bar folds before it breaks.
 *
 * The ⋯ fold used to begin at the phone tier, so the whole 701–1024px band had
 * no fold path at all: .top-bar-spacer collapsed to zero around 960px and the
 * bar overflowed .app-shell's grid column from there down (measured: 921px of
 * content in a 905px window, with Help clipped off the right edge). Tablet
 * portrait, iPad, and any half-width desktop window sat inside that band.
 *
 * Folded here: Export map ▾ / Settings / Help — the consult-and-configure
 * verbs. NOT folded: Open handout (the band's only gilt CTA and its right-hand
 * anchor; a tablet's band is not scarce enough to trade it for a lone ⋯) and
 * the autosave colophon (its phone-tier hide is a scarcity argument, and this
 * fold frees far more than the note costs).
 *
 * 1024px rather than the measured ~960: it clears the collapse with headroom
 * for display faces that cannot be fixed per-theme (Cartographer's Cinzel is
 * much wider, and per-theme rules may change values, never density), and it is
 * iPad landscape — so a tablet keeps one chrome through a rotation instead of
 * changing shape between 768 and 1024.
 * ========================================================================= */
@media (max-width: 1024px) {
  .more-menu-wrap { display: inline-block; }
  .export-map-wrap,
  #btn-settings,
  #btn-help { display: none; }
}

/* Open handout stays a button above the phone tier, so the panel must not
 * repeat the verb the bar already carries. Scoped as a range because ⋯ does not
 * exist above 1024px; the phone tier re-shows the row for free by hiding the
 * button instead. */
@media (min-width: 701px) and (max-width: 1024px) {
  #more-menu [data-more="handout"] { display: none; }
}

/* The chapter nav sheds its ordinals before it overflows (#218). Measured max-
 * content width of the five tabs, by display face: Cartographer (Cinzel) 805px,
 * arcane 727, codex 712, verdant 669 — so between 700 and ~805 the nav, not the
 * top bar, was what pushed the document sideways, and it did it on the one
 * theme whose face is widest. That is the whole of the reported 701–803 band.
 *
 * The numeral is the right thing to spend: it is decorative (aria-hidden) and
 * costs ~80px across all five tabs, which is more than the deficit at every
 * width in this range. The alternative — overflow-x: auto, the phone tier's
 * fallback — was measured and rejected here: it adds a 10px scrollbar gutter
 * and lifts the active tab off the page body, breaking the connected-plaque
 * bridge (+1px → −9px) that is the nav's whole affordance.
 *
 * flex-to-fill comes along because ordinals alone still leave Cinzel ~10px
 * short at 705px; together they clear it with no label clipped. Both levers are
 * the phone tier's own (#158 round 2) — this tier just starts them earlier. */
@media (max-width: 820px) {
  .chapter-tab .numeral { display: none; }
  .chapter-nav .chapter-tab {
    flex: 1 1 auto;
    min-width: 0;
    justify-content: center;
  }
}

/* =========================================================================
 * Phone tier (#105) — make the app usable on small screens.
 *
 * The desktop shell is a fixed-height (100vh) grid with two independently
 * scrolling panes and overflow:hidden — on a phone that clips the floorplan
 * below the fold. Here we relax the shell into a single scrolling document,
 * make the floorplan pannable, hide the (desktop-only) editing chrome, and
 * collapse the top bar's secondary actions into the ⋯ More menu. Editing is
 * a desktop affordance; touch-based editing is deferred to #107.
 * ========================================================================= */
@media (max-width: 700px) {
  /* --- Un-clip the shell: one scrolling document, not two fixed panes --- */
  /* grid-template-columns: minmax(0, 1fr) is the load-bearing fix. The
   * .app-shell grid's implicit column is `auto`, which grows to its widest
   * item's min-content — and once render() builds the floorplan SVG (~770px
   * for the default grid), that min-content blew the single column out to
   * ~794px, dragging the top bar and every panel off the right edge (the
   * "looks fine on load, then jumps to overflow" symptom). Pinning the column
   * to minmax(0, 1fr) lets it shrink to the viewport; min-width: 0 down the
   * chain lets the floorplan's width land on .floor-stack, which scrolls it.
   * overflow-x: clip (not hidden — hidden would force overflow-y: auto and make
   * .app-shell a nested vertical scroller, breaking the sticky chapter nav) is
   * a belt-and-braces guard against any residual bleed.
   *
   * display: block (#158): a sticky grid item can't travel inside its own
   * `auto` row, so the pinned chapter-nav below needs the shell to stop being
   * a grid. Block flow is equivalent here — the phone tier is a single
   * min-content-safe column already (grid-template-columns kept as a no-op
   * guard should display:block ever be reverted). */
  .app-shell {
    display: block;
    height: auto;
    min-height: 100dvh;
    grid-template-columns: minmax(0, 1fr);
    overflow-x: clip;
  }
  /* One scrolling document: let the page card + The Grounds flow instead of
   * clipping to internal scroll regions. */
  .chapter-region { overflow: visible; }
  .chapter { overflow: visible; }
  #chapter-grounds { overflow: visible; display: block; }
  /* --- Charter above-the-fold (#158): flatten the two columns so the cap
   * meter (which carries the "at level N" figure) lands right under the
   * seal instead of below the owners stack. display:contents promotes the
   * grandchildren into one grid; the crest spans two rows with the name +
   * held-by seated beside it (round 2 — the lone left-aligned seal over a
   * full-width name read as a layout accident), everything else spans both
   * tracks, and order demotes owners/lore/sources below the figures.
   * Trade-off (steward-gated): keyboard order stays DOM order, so tab order
   * diverges from visual order for owners — visual-only reflow, same as CSS
   * order elsewhere in this block. */
  .charter-grid {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    gap: 8px 14px;
  }
  .charter-identity,
  .charter-body { display: contents; }
  .charter-identity .charter-crest-wrap { grid-row: span 2; align-self: start; }
  .charter-identity .bastion-name-wrap { align-self: end; }
  .charter-held-by { align-self: start; }
  /* Everything below the seal row spans both tracks. */
  .charter-grid .vtt-send,
  .charter-grid .discord-send,
  .charter-grid .charter-compliance,
  .charter-grid .bastion-type-badge,
  .charter-grid .charter-owners,
  .charter-grid .figures,
  .charter-grid .charter-lore,
  .charter-grid .charter-sources { grid-column: 1 / -1; min-width: 0; }
  /* Shrink-wrapped chrome must not stretch to the full span. */
  .charter-grid .vtt-send,
  .charter-grid .discord-send,
  .charter-grid .bastion-type-badge { justify-self: start; }
  .charter-owners { order: 1; margin-top: 16px; }
  .charter-lore { order: 2; }
  .charter-sources { order: 3; }
  /* Figures run 2-up (denser than the ≤560px 1fr — source order wins) with
   * the cap meter hoisted to the top, spanning both tracks. */
  .charter-grid .figures {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    margin-top: 10px;
  }
  .figures .fig-cap { order: -1; grid-column: 1 / -1; }
  /* The whole document scrolls on the shelf, not the pane — so .stage reverts to
     a plain block and .stage-scroll stops being a nested scroller (#230 pt1). */
  .chapter-grounds .stage { display: block; overflow: visible; padding: 0 12px 24px; min-width: 0; }
  .chapter-grounds .stage-scroll { overflow: visible; padding: 0; min-height: 0; }
  /* Spine folds to a horizontal strip above the canvas — just the compact
   * #floor-select switcher (plates + add buttons are hidden here). Un-sticky
   * since the whole document scrolls now, not the pane (#160). */
  .stage-body { flex-direction: column; }
  .floor-spine {
    position: static;
    flex: 0 0 auto;
    width: auto;
    flex-direction: row;
    align-items: center;
    background: transparent;
    padding: 0 0 10px;
  }
  .floor-select-wrap { max-width: 220px; }
  .chapter-grounds .chapter-head { padding-left: 12px; padding-right: 12px; }
  /* View-controls cluster wraps instead of running off the right edge —
   * Save SVG was half off-screen (#158 round 2). */
  .grid-ctrls { flex-wrap: wrap; row-gap: 6px; }

  /* Help dialog goes single-column like the standalone help page (≤640px
   * rule at .help-page): the 152px nav rail left the reading pane ~120px
   * wide on a phone. Search + scrolling carry navigation. */
  #help-dialog .help-layout { grid-template-columns: 1fr; }
  #help-dialog .help-nav { display: none; }

  /* Library reflows to a full-width shelf below the canvas (#161): the two
   * columns collapse to a stack, the left seam becomes the dashed shelf-top
   * cue, and Commission unsticks. Chips + Commission stay usable; touch
   * drag-placement remains today's (deferred-to-#160) behavior. */
  .grounds-body { display: block; }
  /* The caliper seam dissolves into the aside's dashed shelf-top border. */
  .grounds-seam { display: none; }
  .grounds-aside {
    flex: none;
    width: auto;
    overflow: visible;
    border-left: none;
    border-top: 1px dashed var(--bb-rule);
  }
  .grounds-aside-head { position: static; }
  /* Footer follows the head off its sticky pin on the shelf: it becomes the
   * last stacked block with its top hairline, no bottom-edge pinning. */
  .grounds-aside .grounds-since { position: static; margin-top: 0; }

  /* #218 — the prop palette is the aside's twin and needs the same treatment.
   * It had no rule here at all and only worked by accident, because
   * `.grounds-body { display: block }` makes its `flex: 0 0 300px` inert. Its
   * head kept `position: sticky` inside a container that no longer scrolls,
   * pinning it to nothing; the palette itself kept `overflow-y: auto`, so a
   * 74-prop register scrolled INSIDE a short box instead of with the page. */
  .furnish-palette,
  body[data-edit-mode="furnishings"] .furnish-palette {
    flex: none;
    width: auto;
    overflow: visible;
    border-top: 1px dashed var(--bb-rule);
  }
  .furnish-palette-head { position: static; }
  .furnish-group-label { position: static; }

  /* Sticky editing bars would stack under the sticky top bar once the document
   * (not the pane) scrolls — un-stick them. */
  .grounds-command { position: static; }

  /* View-dock sheds its float slab and flows inline below the canvas as a static
   * row (sticky can't pin once the document, not the pane, scrolls). On phones
   * the zoom cluster / Fit / fullscreen / ghost are already display:none (#158,
   * below); grid-dims + seams go too, leaving Overview as a thumb-sized target. */
  .grounds-view-dock {
    position: static;
    width: auto;
    padding: 8px 0 0;
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border: 0;
    box-shadow: none;
  }
  .grounds-view-dock .grid-dims,
  .grounds-view-dock .grid-ctrls-seam { display: none; }
  #btn-overview { min-height: 44px; }

  /* --- Floorplan: view + pan only --- */
  .floor-stack {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    /* `pinch-zoom` must be listed EXPLICITLY: `pan-x pan-y` alone disables the
     * browser's pinch gesture over this element, which killed the one zoom the
     * phone tier has (#158 removed the zoom buttons precisely because pinch is
     * the native gesture). The symptom was pinching working everywhere on the
     * page EXCEPT on the canvas you actually want to zoom. */
    touch-action: pan-x pan-y pinch-zoom;
  }

  /* --- Hide desktop-only editing chrome (display:none, not DOM removal, so
   * landscape/resize restores it without JS). Keep Save SVG + Overview —
   * they're view/export actions. The zoom cluster + fullscreen go too (#158
   * round 3): the canvas auto-fits the screen width and pinch-zoom is the
   * native gesture from there — a "reset to 100%" tap is meaningless on a
   * phone, and iPhone Safari has no requestFullscreen at all. --- */
  .grounds-command,
  .spine-add-btn,
  .plate-menu-btn,
  .grid-edge-handles-layer,
  #btn-ghost-underlay,
  #btn-fit-grid,
  .chapter-grounds .zoom-ctrl,
  #btn-fullscreen { display: none; }

  /* --- Top bar: the ⋯ fold begins at the tablet tier above (#218) — this block
   * folds two more things. Open handout (the last verb standing; here the band
   * IS scarce) and the autosave colophon (it reclaims the scarce band; saves
   * are still managed from Bastions ▾). Bastions ▾ stays at every width
   * (#121 §5.2). --- */
  #btn-export-handout,
  .top-bar-colophon { display: none; }
  /* The VTT battle map is a prep-desk act, not a table one — the tablet tier's
   * ⋯ carries it, the phone's does not (the #230 pt1 ruling, previously
   * expressed by offering only the SVG row at all widths). */
  #more-menu [data-more="export-map-vtt"] { display: none; }
  /* Turn cartouche (#112) STAYS on phones — turn state is play state, and the
   * hidden colophon vacates exactly this slot. Sheds to a minimal "Turn 3"
   * pill (caret dropped); never folded into ⋯ More. */
  .turn-cartouche { padding: 4px 8px; }
  .turn-cartouche .turn-caret { display: none; }

  /* Dropdown panels: never wider than the screen. Triggers are left-aligned on
   * narrow, so keep the base left:0 anchor (opens rightward) and just cap the
   * width — the base min-width:340px would otherwise risk spilling off-screen. */
  .bastions-menu {
    min-width: 0;
    width: max-content;
    max-width: calc(100vw - 24px);
  }
  /* #more-menu keeps its desktop right:0 anchor: the ⋯ trigger trails at the
   * right edge of the (now single-line, #158 round 2) bar, so the panel opens
   * leftward into the screen. (The old left:0 re-anchor served the two-row
   * layout where the trigger sat flush left — kept it would now push the
   * panel off the right edge.) */

  /* Top bar compacts to ONE line: the wordmark drops, the sigil stays as a
   * compact app mark, and the spacer keeps its desktop flex:1 role so the ⋯
   * button trails right on the same row (the old forced wrap gave ⋯ More a
   * whole second band — #158 round 2). */
  .top-bar {
    padding: 8px 14px;
    flex-wrap: wrap;
    gap: 8px 10px;
  }
  .top-bar-brand .wordmark { display: none; }

  /* Chapter-nav: five tabs, horizontally scrollable until the icon slugs land
   * (#121 §5.3 — icons deferred to the icon system). The Roman ordinal drops.
   * Pinned (#158): at the table you switch Register ↔ Chronicle ↔ Grounds
   * mid-turn; the nav earns its band. The top bar still scrolls away. The base
   * parchment gradient is opaque, so nothing bleeds through. z-index clears
   * the floorplan SVG and the (statically positioned but z-indexed) spine
   * (z:5) while staying under the top-bar dropdowns (.bastions-menu chrome,
   * z:40) and popovers (z:200). */
  .chapter-nav {
    position: sticky;
    top: 0;
    z-index: 30;
    padding: 4px 8px 0;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  /* scrollIntoView targets clear the pinned bar: the cartouche jump, the turn
   * sheet's focusCard, Register's spotlight, and the Map-key disc reveal. */
  .turn-fac-card,
  .chronicle-section,
  .reg-card,
  .floor-svg-wrap {
    scroll-margin-top: 56px;
  }
  .chapter-tab {
    /* Share the band so all five tabs fit one line (#158 round 2): the long
     * labels shed their articles (.tab-extra) and the tabs flex to fill.
     * overflow-x:auto above stays as the fallback for very narrow screens
     * or wide theme faces. */
    flex: 1 1 auto;
    min-width: 0;
    justify-content: center;
    text-align: center;
    padding: 8px 5px;
    font-size: 0.75rem;
    letter-spacing: 0.02em;
  }
  .chapter-tab .numeral { display: none; }
  .chapter-tab .tab-extra { display: none; }

  /* --- Turn sheet as a play surface (#158) --- */
  /* Full-width terminal action: the "seal and advance" bar. Rewind stays a
   * text link behind its itemized confirm, so there's no destructive-tap
   * risk in making Advance unmissable. */
  .turn-footer-actions .turn-advance { width: 100%; }

  /* --- Stores rows stack (#158): tracker labels are user-authored and the
   * non-stacked regime pins them nowrap beside a min-width control — a long
   * label is guaranteed horizontal overflow at 375px. Mirror the existing
   * .stores-row--stacked look (label above control) and neutralize the
   * :not(--stacked) leader/nowrap/min-width rules that would otherwise
   * still bite. --- */
  .stores-row { grid-template-columns: 1fr; gap: 0.2rem; }
  .stores-row:not(.stores-row--stacked) .stores-label {
    display: block;
    white-space: normal;
  }
  .stores-row:not(.stores-row--stacked) .stores-label::after { content: none; }
  .stores-row:not(.stores-row--stacked) .stores-note {
    min-width: 0;
    justify-self: stretch;
  }
  /* Same treatment for the viewer's static twin of a free-text tracker (#254):
     a note that wrapped to two lines would otherwise sit ragged-right under a
     left-aligned label. Numbers and toggle states stay end-aligned, matching
     the live controls beside them. */
  .stores-row:not(.stores-row--stacked) > .stores-readout--text {
    justify-self: stretch;
    text-align: left;
  }
}

/* ---- D&D Beyond import dialog (#148) ------------------------------------ */

/* Step rail: Roman ordinals matching the chapter-nav numeral language */
.ddb-steps {
  display: flex;
  gap: 0;
  list-style: none;
  margin: -6px 0 2px;
  padding: 0;
  border-bottom: 1px solid var(--bb-rule-soft);
}
.ddb-step {
  padding: 5px 14px 5px 0;
  font-family: var(--bb-font-display);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--bb-ink-faint);
}
.ddb-step.is-active { color: var(--bb-vermillion); }

/* Panel headings and intro copy */
.ddb-panel-heading {
  margin: 0 0 6px;
  font-family: var(--bb-font-display);
  font-size: 0.9375rem;
  color: var(--bb-ink);
}
.ddb-panel-sub {
  margin: 0 0 10px;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.875rem;
  color: var(--bb-ink-soft);
}
.ddb-panel-sub kbd {
  font-family: var(--bb-font-text);
  font-style: normal;
  font-size: 0.875em;
  background: var(--bb-parchment-mid);
  border: 1px solid var(--bb-rule-soft);
  padding: 0 4px;
}

/* Step I: URL input + service URL block */
.ddb-url-input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule-soft);
  border-radius: 0;
  padding: 6px 10px;
  font-family: var(--bb-font-text);
  font-size: 0.875rem;
  color: var(--bb-ink);
  box-shadow: none;
}
.ddb-url-input:focus { outline: none; border-color: var(--bb-vermillion); }
.ddb-service-block {
  margin-top: 8px;
  padding: 8px 10px;
  background: var(--bb-parchment);
  border: 1px solid var(--bb-rule-soft);
}
.ddb-url-code {
  display: block;
  font-family: var(--bb-font-text);
  font-size: 0.8125rem;
  color: var(--bb-ink-soft);
  word-break: break-all;
  margin-bottom: 6px;
  user-select: all;
}
.ddb-url-actions {
  display: flex;
  gap: 14px;
}
.ddb-inline-btn {
  background: none;
  border: none;
  padding: 0;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.875rem;
  color: var(--bb-vermillion);
  text-decoration: underline dotted;
  text-underline-offset: 2px;
  cursor: pointer;
}
.ddb-inline-btn:hover { color: var(--bb-vermillion-deep); }
a.ddb-inline-btn { display: inline; }

/* Step II: JSON textarea + parse error */
.ddb-json-input { margin-bottom: 0; }
.ddb-parse-error {
  margin: 6px 0 0;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.875rem;
  color: var(--bb-danger);
}

/* #204 — muted "live fetch didn't work out" notice atop the paste step. A
   gilt hairline, not the red error voice: the manual paste path is the plan B
   the user was always promised, not a failure state. */
.ddb-fetch-notice {
  border-left: 3px solid var(--bb-gilt);
  padding-left: 10px;
}

/* Sync entries land straight on Review — this quiet link is the way back to
   step I for pointing the owner at a different character. */
.ddb-relink { margin-top: 6px; }

/* Step III: preview fields */
.ddb-preview-fields {
  display: grid;
  gap: 0;
  margin: 0 0 12px;
}
.ddb-preview-row {
  display: grid;
  grid-template-columns: 5rem 1fr;
  gap: 4px;
  padding: 5px 0;
  border-bottom: 1px solid var(--bb-rule-soft);
}
.ddb-preview-row:first-child { border-top: 1px solid var(--bb-rule-soft); }
.ddb-preview-row dt {
  font-family: var(--bb-font-display);
  font-size: 0.8125rem;
  color: var(--bb-ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  align-self: center;
}
.ddb-preview-row dd {
  margin: 0;
  font-family: var(--bb-font-text);
  font-size: 0.9375rem;
  color: var(--bb-ink);
  align-self: center;
}

/* Focus-type fieldset */
.ddb-focus-group {
  border: none;
  margin: 0 0 10px;
  padding: 0;
}
.ddb-focus-legend {
  font-family: var(--bb-font-display);
  font-size: 0.8125rem;
  color: var(--bb-ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 4px;
  padding: 0;
}
.ddb-focus-sub {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--bb-ink-soft);
  margin: 0 0 8px;
}
.ddb-focus-options {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.ddb-focus-hint {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--bb-ink-faint);
  margin-left: 4px;
}
/* URL echo block in step II — same chrome as step I's .ddb-service-block */
.ddb-step-ii-echo { margin-bottom: 8px; }

/* Focus checkboxes reuse the settings-body .view-toggle recipe */
#ddb-import-dialog .view-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.875rem;
  color: var(--bb-ink);
  cursor: pointer;
  user-select: none;
}
#ddb-import-dialog .view-toggle input {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
#ddb-import-dialog .view-toggle::before {
  content: ''; display: inline-block;
  width: 18px; height: 18px; flex: 0 0 18px;
  border: 1px solid var(--bb-rule);
  background: var(--bb-parchment-light);
  border-radius: 0; box-sizing: border-box;
}
#ddb-import-dialog .view-toggle:has(input:checked)::before {
  background: var(--bb-vermillion-deep);
  border-color: var(--bb-vermillion-deep);
}
#ddb-import-dialog .view-toggle::after {
  content: ''; position: absolute;
  left: 5px; top: 50%;
  width: 5px; height: 10px;
  border-right: 2px solid var(--bb-parchment-light);
  border-bottom: 2px solid var(--bb-parchment-light);
  transform: translateY(-65%) rotate(45deg);
  opacity: 0; pointer-events: none;
}
#ddb-import-dialog .view-toggle:has(input:checked)::after { opacity: 1; }
#ddb-import-dialog .view-toggle:has(input:focus-visible)::before {
  box-shadow: 0 0 0 2px var(--bb-vermillion);
}

/* Caveats list — gilt advisory, bulletless */
.ddb-caveats {
  list-style: none;
  margin: 2px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* .dialog-warn inside the DDB dialog panels (outside .dialog-body) */
#ddb-import-dialog .dialog-warn {
  display: block;
  font-family: var(--bb-font-italic);
  font-style: italic;
  color: var(--bb-gilt-deep);
}

/* =========================================================================
 * #209 — Shared-snapshot read-only viewer
 * ========================================================================= */

/* The app shell is a 3-row grid (top-bar · nav · region = auto auto 1fr). In the
 * viewer the snapshot banner becomes a 4th grid child, so add its (auto) row —
 * otherwise the banner would grab the region's 1fr and balloon to fill the page. */
body.viewer .app-shell {
  grid-template-rows: auto auto auto 1fr;
}

/* Passing through Settings from home with an empty library (#268). The boot
 * mints an untitled bastion for the dialog to sit over, and a DM who came to
 * back up a shelf or connect cloud sync has no business seeing an editable
 * phantom that looks like a bastion of theirs. Hide the shell rather than
 * suppress the boot: the dialog needs a live session behind it, and app.js
 * returns them to home on close. */
/* Every `<dialog>` in index.html is a SIBLING of .app-shell, not a descendant,
 * so hiding the shell cannot hide them and no companion un-hide rule is needed.
 * (One was written and removed: it was dead weight, and scoping it to
 * #settings-dialog would have been dead weight with a hazard attached, since it
 * implies the others are being suppressed when they never were.) */
body.settings-passthrough .app-shell { visibility: hidden; }

/* The banner shown only in the viewer (body.viewer). A full-bleed gilt-wash
 * band with hairline top/bottom rules — deliberately NOT the gilt-left-accented
 * .compliance-notice card nor the vermillion Register alert, so "you're holding
 * a borrowed snapshot" never reads as "something is wrong". */
.snapshot-banner {
  display: flex;
  align-items: center;
  /* Wraps at every width, not just the phone tier: a fourth control makes the
   * action row too wide to sit beside the message on a narrow laptop, and
   * wrapping the row onto its own line is what the ≤720px tier already did by
   * hand. Flexbox wraps before it shrinks, so the message keeps its natural
   * measure instead of being squeezed to one word. No new breakpoint. */
  flex-wrap: wrap;
  gap: 12px;
  padding: 10px 20px;
  background: color-mix(in srgb, var(--bb-gilt) 12%, var(--bb-parchment-light));
  border-top: 1px solid var(--bb-gilt-deep);
  border-bottom: 1px solid var(--bb-gilt-deep);
  color: var(--bb-ink);
}
.snapshot-seal {
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
  background-color: var(--bb-gilt-deep);
  -webkit-mask: url(icons/wax-seal.svg) center / contain no-repeat;
  mask: url(icons/wax-seal.svg) center / contain no-repeat;
}
.snapshot-msg {
  margin: 0;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.9375rem;
  color: var(--bb-ink-soft);
}
.snapshot-name {
  font-weight: 600;
  color: var(--bb-ink);
}
.snapshot-actions {
  margin-left: auto;
  display: flex;
  /* Once the row is on a line of its own it can still be wider than the band,
   * so it wraps inside itself too. `flex-shrink: 1` is what lets that happen:
   * with the old `0 0 auto` the group could not be squeezed, wrap never
   * engaged, and the band simply ran off the page — 258px of overflow at
   * 760px once a fourth control joined. */
  flex-wrap: wrap;
  gap: 8px;
  flex: 0 1 auto;
}
/* An intentional three-tier set: gilt primary (Download), neutral parchment
 * secondary (Save a copy — the base button chrome), text-like ghost (Leave).
 * .btn-ghost's transparent chrome is .top-bar-scoped, so restate it here or the
 * ghost would render as a solid button like the secondary. */
.snapshot-actions button {
  font-family: var(--bb-font-display);
  font-size: 0.8125rem;
  letter-spacing: 0.05em;
  padding: 6px 14px;
}
/* DONE, not unavailable (#268). A completed act must never wear the disabled
 * treatment. `#2a1d0c` at the house 0.4 dim composites to 2.66:1 over this gilt
 * band at 13px/500 — the confirmation of the act would be the faintest text in
 * the banner — and `disabled` drops the control from the tab order and announces
 * "unavailable", the opposite of what happened. WCAG exempts inactive controls
 * from 1.4.3; a status message in a disabled control's clothes borrows an
 * exemption it isn't entitled to. Full ink, gilt check, no opacity: the mark
 * carries the state and the contrast stays legible. `aria-disabled` keeps it
 * focusable, so its tooltip has a keyboard path.
 * Nothing in this row is dimmed any more — the Roll20-Pro control that was
 * (greyed because it was out of ORDER, not missing) went with the two-step. So
 * `opacity: 1` is a guard, not a fix: it exists to make a future blanket dim on
 * this row fail loudly here rather than quietly re-create the 2.66:1. */
.snapshot-actions button.is-done {
  cursor: default;
  opacity: 1;
}
.snapshot-actions button.is-done .shelf-done-mark {
  color: var(--bb-gilt-deep);
  font-weight: 700;
}
.snapshot-actions .btn-ghost {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
  color: var(--bb-ink-soft);
}
.snapshot-actions .btn-ghost:hover {
  background: transparent;
  border-color: var(--bb-rule-soft);
  color: var(--bb-ink);
}
@media (max-width: 720px) {
  .snapshot-actions { margin-left: 0; width: 100%; }
  /* Touch targets: the row's 6px/0.8125rem chrome measured ~30px tall. */
  .snapshot-actions button { min-height: 44px; padding: 10px 14px; }
}

/* Read-only hide-set — the belt-and-braces layer behind the render-time
 * omissions (render-*.js). Chrome that only makes sense for an owner editing
 * their own library disappears; the view-only affordances (zoom, Overview,
 * Export map ▾, floor switching, Open handout, Help) stay. */
/* :not(.export-map-wrap) — Export map ▾ moved into .top-bar as a .bastions-menu-wrap
   (#230 pt1) and is a view-only affordance that STAYS in the viewer (per the note
   above); only the Bastions ▾ switcher wrapper should hide here. */
body.viewer .top-bar .bastions-menu-wrap:not(.export-map-wrap),
body.viewer #turn-cartouche,
body.viewer #save-status,
body.viewer #btn-settings,
body.viewer #more-menu [data-more="settings"],
/* #tab-chronicle used to hide here (#209): the chapter's three sections were
   milestones, turns and stores, and the payload emptied all three, so the tab
   led to nothing. #254 gives Stores real content — how the bastion actually
   stands — so the tab comes back, and render-history.js hides the two sections
   that are still history. Restore the hide here if that ever reverses. */
body.viewer .grounds-command,
body.viewer .grounds-aside,
body.viewer .grounds-seam,
body.viewer #btn-fit-grid,
body.viewer #btn-ghost-underlay,
body.viewer .spine-add-btn,
/* Charter figures + credentials, Household roster — the mutation buttons that
 * render-time guards don't already omit. Their fields are frozen by
 * lockViewerFields(); these are the buttons. */
body.viewer .btn-add-char,
body.viewer .btn-remove-char,
body.viewer .cred-add,
body.viewer .cred-chip-remove,
body.viewer .house-add,
body.viewer .house-remove,
/* Charter figure/lore actions that start tracking, edit, or import — all
 * owner-only mutations the viewer must not reach (#209). */
body.viewer .fig-turn-link,
body.viewer #lore-edit-btn,
body.viewer .btn-ddb-import {
  display: none !important;
}
/* The crest and owner portraits keep showing, but their pickers are owner-only
 * editors — freeze the click without hiding the artwork. */
body.viewer #crest-slot,
body.viewer .owner-portrait { pointer-events: none; }
/* …and an empty slot stops advertising itself (#272). The dashed border and the
   pointer cursor are the "click to upload" affordance; on a stranger's card they
   are an invitation the reader cannot accept, which is the same read-only lock
   gap the empty states were fixed for. Solid, so it reads as a frame around a
   portrait nobody supplied. */
body.viewer .owner-portrait.is-placeholder {
  border-style: solid;
  cursor: default;
}

/* Export map ▾ dropdown (#215) reuses .bastions-menu, which is left-anchored and
 * 340px wide for the top-bar saves list. Here it sits at the right edge of the
 * Grounds view-controls with only two short rows, so right-align it and let it
 * size to its content — otherwise it overflows the window and looks half-empty. */
#export-map-menu {
  left: auto;
  right: 0;
  min-width: 0;
  white-space: nowrap;
}
#export-map-menu .bastions-menu-row {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 16px;
  background: transparent;
  border: none;
  border-radius: 0;
  color: var(--bb-ink);
  font-family: var(--bb-font-display);
  font-size: 0.875rem;
  letter-spacing: 0.02em;
  cursor: pointer;
  box-shadow: none;
}
#export-map-menu .bastions-menu-row:hover { background: var(--bb-parchment-mid); }

/* Share dialog (#209) — read-only link field + copy, plus the crest/length
 * caveat notes and the slim-link offer. */
.share-link-row {
  display: flex;
  gap: 8px;
  margin: 4px 0 8px;
}
.share-link-input {
  flex: 1 1 auto;
  min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.8125rem;
  padding: 6px 8px;
  border: 1px solid var(--bb-rule);
  border-radius: 3px;
  background: var(--bb-parchment-light);
  color: var(--bb-ink-soft);
}
.linklike {
  background: none;
  border: none;
  padding: 0;
  color: var(--bb-gilt-deep);
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}
.linklike:hover { color: var(--bb-gilt); }

/* ==== Homebrew (#146) ====================================================
   The facility editor dialog, the picker's authoring affordances, and the
   NEW .field-error convention (aria-invalid + vermillion border + a short
   message under the field — first ratified here; precedent for the border
   is .turn-event-roll[aria-invalid]). Provenance is ink-soft — a neutral
   third channel, never vermillion (earned) or gilt (active/reference). */

/* Picker rows: quiet provenance tag + the trailing authoring verb. */
.picker-list .pick-provenance {
  margin-left: 8px;
  color: var(--bb-ink-soft);
  letter-spacing: 0.03em;
}
.pick-fork {
  flex: 0 0 auto;
  align-self: center;
  width: 44px;
  height: 44px;
  margin: -6px 0;                /* stay inside the row's visual height */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid transparent;
  border-radius: 0;
  color: var(--bb-ink-soft);
  cursor: pointer;
  opacity: 0;                    /* revealed on row hover / keyboard focus */
  transition: opacity 0.12s, border-color 0.12s;
}
.picker-list li:hover .pick-fork,
.picker-list li:focus-within .pick-fork { opacity: 1; }
.pick-fork:hover { border-color: var(--bb-rule); background: var(--bb-parchment-mid); }
.pick-fork:focus-visible { opacity: 1; outline: 2px solid var(--bb-vermillion); outline-offset: -2px; }
.pick-fork .glyph-svg { width: 18px; height: 18px; }
@media (pointer: coarse) {
  .pick-fork { opacity: 1; }     /* no hover on touch — always a real target */
}

/* The editor form: one scrolling column of fieldsets inside a bounded dialog. */
#homebrew-editor-dialog .hb-form {
  display: flex;
  flex-direction: column;
  max-height: 88vh;
}
.hb-form-scroll {
  overflow-y: auto;
  padding-right: 12px;           /* scroll gutter, house rule */
  scrollbar-gutter: stable;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.hb-fieldset {
  border: 1px dashed var(--bb-rule-soft);
  background: var(--bb-parchment-mid);
  padding: 10px 12px 12px;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.hb-fieldset legend {
  font-family: var(--bb-font-display);
  font-size: 0.875rem;
  letter-spacing: 0.04em;
  color: var(--bb-ink-soft);
  padding: 0 6px;
}
.hb-field { display: flex; flex-direction: column; gap: 4px; }
.hb-field-row { display: flex; gap: 12px; }
.hb-field-row .hb-field { flex: 1 1 0; }
.hb-field > label,
.hb-field-label {
  font-family: var(--bb-font-display);
  font-size: 0.8125rem;
  color: var(--bb-ink-soft);
}
.hb-optional {
  font-family: var(--bb-font-italic);
  font-style: italic;
  letter-spacing: 0;
  color: var(--bb-ink-faint);
}
.hb-form input[type="text"],
.hb-form input[type="number"],
.hb-form select,
.hb-form textarea {
  width: 100%;
  box-sizing: border-box;
  font-family: var(--bb-font-text);
  font-size: 0.9375rem;
  color: var(--bb-ink);
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule);
  border-radius: 0;
  padding: 7px 9px;
}
.hb-form textarea { resize: vertical; }
.hb-form input:focus,
.hb-form select:focus,
.hb-form textarea:focus {
  outline: none;
  border-color: var(--bb-vermillion);
}

/* The .field-error convention (new): invalid field + message line. */
.hb-form :is(input, select, textarea)[aria-invalid="true"] {
  border-color: var(--bb-vermillion);
}
.field-error {
  color: var(--bb-vermillion);
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
}

/* Orders: the six bastion orders as a compact toggle grid. */
.hb-orders-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 4px 12px;
}
.hb-order-toggle { font-size: 0.9375rem; }

/* Level-outcome tiers: level + text + remove per row. */
.hb-outcome-row {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-bottom: 6px;
}
.hb-outcome-row .hb-outcome-level { flex: 0 0 72px; }
.hb-outcome-row .hb-outcome-text { flex: 1 1 auto; }
.hb-outcome-remove {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  background: none;
  border: 1px solid transparent;
  color: var(--bb-ink-soft);
  cursor: pointer;
}
.hb-outcome-remove:hover { color: var(--bb-vermillion); border-color: var(--bb-rule); }
.hb-add-outcome {
  align-self: flex-start;
  background: none;
  border: 1px dashed var(--bb-rule);
  color: var(--bb-ink-soft);
  font: inherit;
  font-size: 0.8125rem;
  padding: 5px 10px;
  cursor: pointer;
}
.hb-add-outcome:hover { border-color: var(--bb-ink-soft); color: var(--bb-ink); }

/* Icon picker: selected disc + a collapsible filterable radiogroup grid.
   Inline (not a popover/nested dialog) — the editor dialog owns the top
   layer, so anything body-appended would render behind it. */
.hb-icon-disclosure { display: flex; align-items: center; gap: 10px; }
.hb-icon-disclosure .glyph { flex: 0 0 auto; }
#hb-icon-toggle {
  background: none;
  border: 1px solid var(--bb-rule);
  color: var(--bb-ink);
  font: inherit;
  font-size: 0.8125rem;
  padding: 6px 10px;
  cursor: pointer;
}
#hb-icon-toggle:hover { border-color: var(--bb-ink-soft); }
#hb-icon-picker { margin-top: 8px; }
#hb-icon-filter { margin-bottom: 8px; }
.hb-icon-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
  gap: 4px;
  max-height: 200px;
  overflow-y: auto;
  padding-right: 12px;
  scrollbar-gutter: stable;
}
.hb-icon-option {
  aspect-ratio: 1;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule-soft);
  color: var(--bb-ink);
  cursor: pointer;
}
.hb-icon-option .glyph-svg { width: 24px; height: 24px; }
.hb-icon-option:hover { border-color: var(--bb-ink-soft); }
.hb-icon-option[aria-checked="true"] {
  border-color: var(--bb-vermillion);
  outline: 1px solid var(--bb-vermillion);
  background: rgba(168, 40, 29, 0.08);
}
.hb-icon-option:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: -2px; }

@media (max-width: 700px) {
  .hb-field-row { flex-direction: column; gap: 10px; }
  .hb-orders-grid { grid-template-columns: repeat(2, 1fr); }
}

/* Homebrew management dialog (#146): the library list + New button. */
.hb-new-btn {
  align-self: flex-start;
  margin-bottom: 10px;
  background: none;
  border: 1px dashed var(--bb-rule);
  color: var(--bb-ink);
  font: inherit;
  font-size: 0.875rem;
  padding: 7px 12px;
  cursor: pointer;
}
.hb-new-btn:hover { border-color: var(--bb-ink-soft); }
/* #229: the manager's file verbs share a row with + New. Secondary buttons
   are quiet solid hairlines (dashed stays the add/placeholder affordance);
   resets beat the global button chrome at rest AND :hover (#227 lesson). */
.hb-manage-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}
.hb-manage-actions .hb-new-btn { margin-bottom: 0; }
.hb-manage-secondary {
  background: none;
  border: 1px solid var(--bb-rule);
  box-shadow: none;
  color: var(--bb-ink-soft);
  font: inherit;
  font-size: 0.8125rem;
  padding: 7px 12px;
  cursor: pointer;
}
.hb-manage-secondary:hover,
.hb-manage-secondary:active {
  background: none;
  border: 1px solid var(--bb-ink-soft);
  box-shadow: none;
  transform: none;
  color: var(--bb-ink);
}
.hb-manage-secondary:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: -2px; }
.hb-list {
  list-style: none;
  margin: 0;
  padding: 0 12px 0 0;           /* scroll gutter, house rule */
  max-height: 50vh;
  overflow-y: auto;
  scrollbar-gutter: stable;
}
.hb-list .empty {
  color: var(--bb-ink-soft);
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.875rem;
  padding: 8px 2px;
}
.hb-row {
  display: flex;
  /* Three verbs since #227 — on narrow widths the verb cluster wraps to a
     second line instead of crushing the name (.hb-row-body has min-width:0). */
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--bb-rule);
  margin-bottom: 6px;
}
.hb-row-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.hb-row-name {
  font-family: var(--bb-font-display);
  font-size: 1rem;
  color: var(--bb-ink);
}
.hb-row-meta {
  color: var(--bb-ink-soft);
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.75rem;
}
.hb-row-verb {
  flex: 0 0 auto;
  background: none;
  border: 1px solid var(--bb-rule);
  color: var(--bb-ink);
  font: inherit;
  font-size: 0.8125rem;
  padding: 5px 10px;
  cursor: pointer;
  min-height: 32px;
}
.hb-row-verb:hover { border-color: var(--bb-ink-soft); }
.hb-row-verb-danger:hover {
  border-color: var(--bb-vermillion);
  color: var(--bb-vermillion-deep);
}
.hb-row-verb:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: -2px; }

/* ==== Homebrew editor v2 (#226): family disclosures + row-level errors ====
   The three structured-family sections wear .hb-fieldset chrome with a
   full-width disclosure button standing in for the legend (steward P1 — a
   real <legend> can't collapse). Row-level errors extend the .field-error
   convention (steward P5): the offending row/card carries a vermillion
   left-rule and one message line; aria-invalid still marks the input. */
.hb-disclosure { padding: 0; }
.hb-disclosure > div:not([hidden]) {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 0 12px 12px;
}
.hb-disclosure-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 10px 12px;
  text-align: left;
  cursor: pointer;
  font-family: var(--bb-font-display);
  font-size: 0.875rem;
  letter-spacing: 0.04em;
  color: var(--bb-ink-soft);
  /* Quiet header, not a parchment button — reset the global button chrome
     at rest AND on :hover/:active (button:hover outspecifies a lone class —
     the #227 lesson). */
  background: none;
  border: none;
  box-shadow: none;
}
.hb-disclosure-toggle:hover,
.hb-disclosure-toggle:active {
  background: none;
  border: none;
  box-shadow: none;
  transform: none;
  color: var(--bb-ink);
}
.hb-disclosure-toggle:focus-visible { outline: 2px solid var(--bb-vermillion); outline-offset: -2px; }
.hb-disclosure-caret {
  display: inline-block;
  transition: transform 0.15s ease;
}
.hb-disclosure-toggle[aria-expanded="true"] .hb-disclosure-caret { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
  .hb-disclosure-caret { transition: none; }
}
.hb-disclosure-count {
  font-family: var(--bb-font-italic);
  font-style: italic;
  letter-spacing: 0;
  color: var(--bb-ink-faint);
}
.hb-disclosure-hint {
  font-size: 0.8125rem;
  margin: 0;
}
/* Family rows/cards reserve a transparent left rule so flagging an error
   never shifts layout; builders set it, this class only recolors it. */
.hb-row-invalid { border-left-color: var(--bb-vermillion); }

/* Content cards inside a repeatable group (steward P3/P6): SOLID hairline —
   dashed stays reserved for add/placeholder affordances. */
.hb-type-card,
.hb-option-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 10px;
  border: 1px solid var(--bb-rule);
  border-left: 3px solid transparent;
  background: var(--bb-parchment);
  margin-bottom: 6px;
}
.hb-family-row-head {
  display: flex;
  align-items: center;
  gap: 8px;
}
.hb-family-row-head > input[type="text"],
.hb-family-row-head > select {
  flex: 1 1 auto;
  min-width: 0;
}
/* Trackers: flat morphing rows (steward P2) — same box as the cards, but
   the kind-specific fields swap inside .hb-tracker-morph. */
.hb-tracker-row {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px 10px;
  border: 1px solid var(--bb-rule);
  border-left: 3px solid transparent;
  background: var(--bb-parchment);
  margin-bottom: 6px;
}
.hb-tracker-row .hb-family-row-head select { flex: 0 0 auto; width: auto; }

/* Event-table rows (#256) — the same flat box as a tracker row, because they
   are the same species of thing: one repeatable record with a head line and
   trailing detail. What differs is that the head leads with a ROLL RANGE
   rather than a label, so the range gets a fixed narrow pair and the name
   takes the remaining width. */
.hb-table-row {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px 10px;
  border: 1px solid var(--bb-rule);
  border-left: 3px solid transparent;
  background: var(--bb-parchment);
  margin-bottom: 6px;
}
.hb-roll-range {
  display: flex;
  align-items: baseline;
  gap: 4px;
  flex: 0 0 auto;
  font-variant-numeric: lining-nums tabular-nums;
}
/* Wide enough for "100" and no wider. Two separate things have to be undone,
   and only one of them is a specificity fight:
     · `.hb-form input[type="number"] { width: 100% }` needs outranking, hence
       the `.hb-form` + attribute selector;
     · `input, select { min-width: 120px }` (the global field floor) silently
       wins over ANY smaller width regardless of specificity, so `min-width: 0`
       is what actually makes a narrow field narrow here. Without it this rule
       computes to 120px and looks like it never applied. */
.hb-form .hb-roll-range input[type="number"] {
  width: 3.25rem;
  min-width: 0;
  flex: 0 0 auto;
  text-align: center;
  font-variant-numeric: lining-nums tabular-nums;
}
/* The low end is DERIVED from the row above (#256 follow-up), so it's printed,
   not typed — same size and figures as the input beside it so the pair still
   reads as one range rather than a label and a field. */
.hb-roll-lo {
  min-width: 2.5rem;
  text-align: right;
  font-size: 0.9375rem;
  color: var(--bb-ink-soft);
}
.hb-roll-dash { color: var(--bb-ink-faint); }
/* Live coverage, in the disclosure head beside the count. Gilt while the table
   is short of its die, plain once it's covered — the answer to "did I miss
   any numbers?" without saving to find out. */
.hb-rows-coverage { font-variant-numeric: lining-nums tabular-nums; }
.hb-rows-coverage.is-short { color: var(--bb-gilt-deep); }
.hb-row-hint {
  width: 100%;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.9375rem;
}
.hb-row-condition {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 8px 14px;
}
/* The kind select leads and sizes to its own text; its two trailing fields
   share what's left, so "No lingering effect" (the common case) sits alone on
   the line rather than beside two empty boxes. */
.hb-row-condition > select { flex: 0 1 auto; min-width: 0; }

/* Casualties share the condition row's shape — both are "this row also does
   something mechanical", folded away until asked for. */
.hb-row-casualty {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 8px 14px;
}
.hb-row-casualty > .view-toggle { flex: 0 0 auto; }
/* The INPUT has to be sized too, not just its wrapper. Number inputs here carry
   a fixed 120px from the tracker morph's sizing, so narrowing only the
   .hb-mini-field left the input overflowing its own field by 40px and running
   under the note beside it. */
.hb-row-casualty .hb-mini-field { flex: 0 0 4.5rem; }
.hb-row-casualty .hb-mini-field input { width: 100%; min-width: 0; }
/* The rule the checkbox imports, spelled out — this field is the one place a
   homebrew row inherits mechanics rather than a value, so the row says which. */
.hb-casualty-note { flex: 1 1 16rem; min-width: 0; font-size: 0.8rem; }

.hb-tracker-morph {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 8px 14px;
}
.hb-mini-field {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex: 1 1 120px;
  min-width: 0;
}
.hb-mini-label {
  font-family: var(--bb-font-display);
  font-size: 0.75rem;
  color: var(--bb-ink-soft);
}
.hb-mini-check {
  flex: 0 0 auto;
  flex-direction: row;
  align-items: center;
  gap: 6px;
  font-size: 0.8125rem;
  color: var(--bb-ink);
  padding-bottom: 7px; /* baseline-align with neighboring inputs */
}
.hb-mini-check input[type="checkbox"] { width: auto; }
.hb-state-pair {
  display: flex;
  gap: 8px;
}
.hb-state-pair input { min-width: 0; }
/* Loadout expands into an indented block under the row (steward P2). */
.hb-loadout-block {
  flex: 1 1 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-left: 10px;
  padding-left: 10px;
  border-left: 1px solid var(--bb-rule-soft);
}
.hb-loadout-option {
  display: flex;
  gap: 8px;
  align-items: center;
}
.hb-loadout-option input { min-width: 0; }
.hb-loadout-option input[data-opt="label"],
.hb-loadout-option input[data-field$=".label"] { flex: 3 1 0; }
.hb-loadout-option input[data-opt="hint"],
.hb-loadout-option input[data-field$=".hint"] { flex: 2 1 0; }
.hb-loadout-pick {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 8px 14px;
}
.hb-pick-pair {
  display: flex;
  gap: 8px 14px;
  flex: 1 1 auto;
}

/* Order options: card internals (steward P3). Effect blocks are the
   omit-don't-disable zone — ghost +Add buttons materialize them. */
.hb-option-card .hb-family-row-head select {
  flex: 0 0 auto;
  width: auto;
}
.hb-option-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 8px 14px;
}
.hb-effect-blocks {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.hb-effect-block {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 6px 8px;
  border: 1px solid var(--bb-rule-soft);
  background: var(--bb-parchment-mid);
}
.hb-effect-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.hb-effect-fields {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 8px 14px;
}
.hb-effect-rows {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.hb-effect-line {
  display: flex;
  gap: 8px;
  align-items: center;
}
.hb-effect-line input,
.hb-effect-line select {
  min-width: 0;
  flex: 1 1 auto;
}
.hb-effect-line input[type="number"] { flex: 0 1 80px; }
.hb-effect-line .hb-write-value { flex: 0 1 120px; display: flex; }
.hb-effect-line .hb-write-value > * { width: 100%; }
.hb-ghost-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.hb-ghost-row .hb-ghost-add { align-self: auto; }

/* New-in-#226 removes must clear the 44px thumb bar on touch; bumping the
   legacy 32px outcome-row ✕ along with them is a welcome side effect. */
@media (pointer: coarse) {
  .hb-outcome-remove {
    width: 44px;
    height: 44px;
  }
}

/* ≤700px: family morph zones and effect lines stack; cards stay cards
   (steward P7). */
@media (max-width: 700px) {
  .hb-tracker-morph,
  .hb-option-meta,
  .hb-effect-fields,
  .hb-loadout-pick {
    flex-direction: column;
    align-items: stretch;
  }
  .hb-effect-line,
  .hb-loadout-option {
    flex-wrap: wrap;
  }
  .hb-mini-field { flex: 1 1 auto; }
  .hb-state-pair { flex-direction: column; }
  .hb-pick-pair { flex-direction: column; }
}

/* Picker footer "Manage homebrew…" (#146 steward ruling b): the management
   door where homebrew is experienced. Quiet link idiom, pushed opposite the
   Back button; never competes with commissioning. */
.picker-manage-link {
  margin-left: auto;
  /* The bare .picker-hint-link inherits its font from a styled parent (the
     prereq hint sets the italic face); this one sits in the plain footer, so
     it must speak the footer's voice itself — same recipe as .picker-back. */
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
}
@media (max-width: 700px) {
  .picker-manage-link { min-height: 44px; }
}

/* =========================================================================
 * HOME — the front door (#264)
 *
 * web/home.html. The page is a REGISTER, not a pitch: a masthead, then one
 * ruled column of variable-height entries. Variable row height is the point —
 * it is the only anatomy where "names never truncate" is structurally free
 * rather than a special case, and the only one that doesn't render a single
 * lonely card beside two dead columns at the median library size of one.
 *
 * Every colour is a --bb-* token, so all four themes are covered without a
 * single [data-theme] rule below. Net addition to @layer themes: zero.
 * ========================================================================= */

/* Home's own bar: the wordmark is suppressed at EVERY width, not just on
 * phones. The h1 immediately below carries the name in full with the
 * illuminated initial, and a page that says "Bastion Builder" twice ~40px
 * apart makes the drop cap read as decoration instead of identity. */
.home-page-bar .wordmark { display: none; }

/* The bar's two controls are ANCHORS, not buttons — they navigate, and a page
 * that must work with JS off cannot fake that with a click handler. The base
 * button recipe is element-scoped (`button`, `button.btn-primary`), so it does
 * not reach them; these rules re-apply it rather than restating a new one, and
 * the two must stay in step with the `button` block above.
 *
 * Gilt on the return control never collides with the gilt "Create your
 * Bastion" tile: the tile exists only in the empty population, where this
 * control is the plain "Open the app". They are never on screen together. */
/* Add ONLY what an anchor lacks. Deliberately no font-family and no colour
 * here: `.top-bar .btn-ghost` (styles.css:2581) already sets the display face,
 * size, tracking and ink for this bar, and an earlier version of this block
 * overrode it with the body face — which rendered the bar's labels as
 * uppercase Cormorant, visibly a different font from every other page.
 * Anything this block declares must be a property the shared rule doesn't. */
.home-page-bar a.btn-ghost,
.home-page-bar a.btn-primary {
  /* inline-block, NOT inline-flex: flex layout drops whitespace-only text
     nodes between children, which ate the space before the label's arrow. */
  display: inline-block;
  text-decoration: none;
  white-space: nowrap;
}
/* btn-primary is the one that genuinely has no anchor-reachable recipe —
 * `button.btn-primary` is element-scoped — so it restates the gilt fill. Keep
 * in step with the `button.btn-primary` block. */
.home-page-bar a.btn-primary {
  padding: 6px 12px;
  border-radius: 0;
  background: linear-gradient(180deg, var(--bb-gilt-bright), var(--bb-gilt));
  color: var(--bb-ink);
  border: 1px solid var(--bb-gilt-deep);
  font-family: var(--bb-font-display);
  font-size: 0.84rem;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  box-shadow:
    0 1px 0 var(--bb-inset-strong) inset,
    0 2px 4px var(--bb-shadow);
}
.home-page-bar a.btn-primary:hover {
  background: linear-gradient(180deg,
    color-mix(in oklch, var(--bb-gilt-bright), white 6%),
    var(--bb-gilt-bright));
}
.home-page-bar a:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}

/* 820px, not the 960 the register alone would like: the masthead's prose sets a
 * readable measure (~62ch here), and a column wide enough for the roll made the
 * text stop less than two-thirds across, so the page read as two different
 * widths stacked. One column, one left edge, one right edge. */
.home-page {
  max-width: 820px;
  margin-inline: auto;
  padding: 18px 24px 40px;   /* the .help-page rhythm — home and the guide are siblings */
}

/* ---- Masthead ---------------------------------------------------------- */

/* Composed, not stacked: the drop cap, the standfirst and the safety line lock
 * to one left margin, so the vermillion initial has structure under it. */
.home-masthead {
  margin: 6px 0 26px;
}
.home-title {
  margin: 0 0 6px;
  font-family: var(--bb-font-display);
  font-size: clamp(1.9rem, 4vw, 2.4rem);
  line-height: 1.15;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--bb-ink);
}
/* The app's own unconditional gesture, borrowed once. The 1.75em ratio is the
 * Charter's (56px cap on a 2rem name) — not an invented 3.2em. This is the
 * THIRD sanctioned ::first-letter site in the system; it is deliberately not
 * repeated down the register, where twelve identical capitals would stop being
 * illumination and become a bullet. */
.home-title::first-letter {
  float: left;
  font-family: var(--bb-font-display);
  font-size: 1.75em;
  line-height: 0.72;
  margin: 0 6px 0 0;
  color: var(--bb-vermillion);
  text-transform: uppercase;
}
/* No ch cap: the column itself is the measure now (see .home-page). */
.home-standfirst {
  margin: 0 0 14px;
  font-family: var(--bb-font-text);
  font-size: 1.15rem;
  line-height: 1.5;
  color: var(--bb-ink-soft);
}
/* Full width, unlike the text above and below it. A rule that stopped at the
 * measure made the masthead read as an unfinished column beside dead space;
 * spanning the page ties the left-aligned frontispiece to the roll below and
 * makes the asymmetry read as chosen. */
.home-rule-dotted {
  border-top: 1px dotted var(--bb-rule-soft);
  margin: 0 0 14px;
}
/* ROMAN, at full ink. This is the claim, not a gloss on it — and it is the
 * single most load-bearing sentence on the page for an OAuth reviewer. Being
 * on-theme is not a licence to set it in small italic. */
.home-safety {
  margin: 0;
  font-family: var(--bb-font-text);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--bb-ink);
}
.home-safety strong { font-weight: 600; color: var(--bb-ink); }

/* ---- Bays -------------------------------------------------------------- */

.home-bay { margin-bottom: 30px; }

/* .section-head is a two-way space-between, so the filter and the count share
 * one right-hand aside rather than becoming a third child floating mid-header. */
/* The gap matters: the filter carries a bottom border, and with the head flush
 * against the roll the two hairlines stacked and read as one double-weight
 * rule. 7px separates them into what they are. */
.home-bay .section-head { margin-bottom: 7px; }
.home-head-aside {
  display: flex;
  align-items: baseline;
  gap: 12px;
  min-width: 0;
}
.head-count {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.85rem;
  color: var(--bb-ink-faint);
}
.home-filter {
  min-width: 0;
  max-width: 180px;
  padding: 2px 4px;
  font-family: var(--bb-font-text);
  font-size: 0.9rem;
  color: var(--bb-ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--bb-rule-soft);
}
.home-filter:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}
/* The arrange control (#267) — states how the register is currently sorted and
 * opens the alternatives. Deliberately NOT .home-foot-btn: that is 0.72rem
 * tracked uppercase, and dropping it into a three-item aside beside a 0.9rem
 * input and a 0.85rem italic makes three typographic registers in one 24px
 * band. One step darker than .head-count, because the count is marginalia and
 * this is a control. */
.home-arrange {
  flex: 0 0 auto;
  padding: 0;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.85rem;
  color: var(--bb-ink-soft);
  background: transparent;
  border: 0;
  box-shadow: none;
  cursor: pointer;
}
.home-arrange:hover { color: var(--bb-vermillion); }
.home-arrange:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}

/* ---- The roll ---------------------------------------------------------- */

/* The .reg-card surface recipe, so the roll is recognisably a
 * Register-of-Chambers cousin rather than a new kind of panel. */
.home-roll {
  min-width: 0;
  background: var(--bb-parchment-light);
  border: 1px solid var(--bb-rule);
  box-shadow: var(--shadow-card);
}
/* A big library is the success case, not the edge case — 17 bastions ran the
 * page metres long and buried the key band and colophon below it. The roll
 * scrolls internally past roughly six entries so the page itself stays a fixed,
 * scannable height; the filter (rendered above the same threshold) is the other
 * half of that answer. One column is kept deliberately: variable row height and
 * never-truncated names are the register's whole anatomy, and a two-up grid of
 * smaller cards gives both of those up. */
.home-roll-body {
  min-width: 0;
  max-height: min(62vh, 680px);
  overflow-y: auto;
  scrollbar-gutter: stable;
  padding-right: 12px;
}
/* No cap when there is nothing to scroll — the three first-run choices must not
 * sit in a scroll box. */
.home-roll-body.is-blank {
  max-height: none;
  overflow-y: visible;
  scrollbar-gutter: auto;
}

/* The empty population sits in the exact slot the entries will occupy: same
 * padding, same dotted separator. That makes "this is where your entries go"
 * true at the pixel level instead of by assertion — no invented ruled-ledger
 * gradient, which could never align with the tiles anyway. */
.home-roll-body.is-blank { padding: 14px 16px; }
.home-roll-body.is-blank .dialog-actions-list { gap: 0; margin: 0; }
.home-roll-body.is-blank .dialog-choice { text-decoration: none; }
.home-roll-body.is-blank .dialog-choice + .dialog-choice {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px dotted var(--bb-rule-soft);
}

/* Row-gap 10, not 8: at 8px two lines of 0.72rem tracked caps read as a wrapped
 * line, at 10px they read as two rows. No effect when the rail fits on one. */
.home-roll-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px 18px;
  padding: 12px 16px;
  border-top: 1px solid var(--bb-rule);
}
/* The verbs sit in two runs (see home.js renderFoot). Same display, same gap,
 * same baseline as the rail itself, so on one line the runs are invisible. */
.home-foot-run {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px 18px;
}
/* Two poles, not two ragged lines. The library run takes the right edge, so a
 * rail that folds folds into a composed split — start-something at the left
 * margin, library custody at the right — instead of a long line hanging off a
 * short one. Whether it folds at all depends on the theme (Cinzel is far wider
 * than IM Fell at the same size), which is exactly why the fit is not allowed
 * to be load-bearing: both outcomes are the same composition. The same gesture
 * as .home-colophon-links .version-badge, eleven rules below — the page's foot
 * already speaks left-pole / right-pole. Not in the empty population, where the
 * lead-in question must stay beside its answers. */
.home-roll-foot:not(.is-blank) .home-foot-run + .home-foot-run {
  margin-left: auto;
}
.home-foot-lead {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.85rem;
  color: var(--bb-ink-soft);
}
/* The rail mixes anchors (navigate) and one <button> (Import opens a file
 * picker, which needs a real click). They must be indistinguishable, so the
 * button's inherited chrome — gradient, border, shadow, padding — is stripped
 * back to the anchors' plain text verb. */
.home-foot-btn {
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
  font-family: var(--bb-font-display);
  font-size: 0.72rem;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bb-ink-soft);
  text-decoration: none;
  cursor: pointer;
}
.home-foot-btn:hover { background: none; border: 0; }
.home-foot-btn:hover { color: var(--bb-vermillion); }
.home-foot-btn:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}

/* ---- One entry --------------------------------------------------------- */

.holding {
  position: relative;
  display: grid;
  grid-template-columns: 84px 1fr auto;
  gap: 0 14px;
  align-items: start;
  padding: 14px 16px;
}
.holding + .holding { border-top: 1px dotted var(--bb-rule-soft); }
/* Hover ground only. The name does NOT go vermillion-deep on hover: in the
 * saves vocabulary that colour means CURRENT (.bastions-menu-row.current
 * .bm-load), and painting every hovered row with it makes each one impersonate
 * the current bastion. */
.holding:hover,
.holding:focus-within {
  background: var(--bb-gilt-fill);
  transition: background-color 90ms ease;
}
.holding.is-current .hd-name { color: var(--bb-vermillion-deep); }

/* One mechanism at every width: an absolute overlay, with the verbs lifted
 * above it. Survives the ≤640px switch to a float layout, which a grid-area
 * span would not. */
.hd-open {
  position: absolute;
  inset: 0;
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
  cursor: pointer;
}
/* The global button chrome has to be beaten PER STATE — `button:hover` is
 * (0,1,1) and outspecifies a lone class, the same lesson .picker-hint-link and
 * the #227 header link each record above. It matters more here than anywhere
 * else in the app: this button is an absolutely-positioned overlay covering the
 * whole row, and absolute positioning paints it above every in-flow sibling
 * regardless of DOM order. So the inherited `:hover` gradient — an OPAQUE
 * parchment-light fill — painted straight over the card. Hovering a row blanked
 * its name, figures, standing and filing line; the only survivors were the
 * elements that happen to carry `position: relative` and are therefore lifted
 * into the same layer (the seal, and any `[data-tip]` in the body — which is
 * why a party bastion kept exactly its "owned by …" line and nothing else).
 * The row's own hover ground lives on `.holding:hover` and is unaffected. */
.hd-open:hover,
.hd-open:active {
  background: none;
  border: 0;
  box-shadow: none;
  transform: none;
}
.hd-open:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: -2px;
}

/* Tipped text in the body column has to sit ABOVE that overlay, or its tooltip
 * is dead on arrival: tooltip.js resolves with `ev.target.closest("[data-tip]")`,
 * and .hd-open covers the whole row while carrying no tip of its own — so the
 * pointer never lands on anything tipped and closest() returns null.
 *
 * Three separate features had each shipped a tip into this card body before
 * anyone noticed (the wax seal's "read-only", the multi-owner roster, and #265's
 * exact snapshot date), which is why this is a rule about the column rather than
 * a fix to one of them. Positioned and later in DOM order beats the earlier
 * absolutely-positioned sibling, with no z-index needed.
 *
 * The trade is that these elements now swallow the click the overlay would have
 * taken, so each card hands it back — see buildEntry in home.js / home-shelf.js. */
.hd-body [data-tip] { position: relative; }

.hd-seal {
  position: relative;
  /* Centred, not start-aligned. The row's height follows whichever is taller,
     so with a short body (no owner line) the seal filled the row and read as
     centred, while a taller body left it hanging at the top — the same seal
     looking like two different decisions down one column. Centring is the only
     rule that looks the same in both. */
  align-self: center;
  width: 84px;
  height: 84px;
  background: var(--bb-parchment);
  border: 1px solid var(--bb-rule);
  box-shadow: var(--shadow-sunken);
}
.hd-seal svg { display: block; width: 100%; height: 100%; }
.hd-seal.is-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  outline: 1px dashed var(--bb-rule-soft);
  outline-offset: -5px;
}
.hd-seal-empty {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.65rem;
  text-align: center;
  color: var(--bb-ink-soft);
}

/* The seal's four marks. Derived from the SAME hue tokens the floorplan uses,
 * but stronger: --bb-special-fill and friends are 10%-alpha washes tuned for a
 * full-size canvas, and at 84px a facility is ~9x6px, where 10% is simply not
 * there. Strokes carry legibility in the dark themes, where the -deep tokens
 * invert to luminous.
 *
 * The `fp-` prefix is load-bearing — see the header note in home-footprint.js
 * before renaming any of these. */
.fp-special {
  fill: color-mix(in srgb, var(--bb-vermillion) 26%, transparent);
  stroke: var(--bb-vermillion-deep);
}
.fp-basic {
  fill: color-mix(in srgb, var(--bb-sapphire) 26%, transparent);
  stroke: var(--bb-sapphire-deep);
}
/* Corridor and defenses are the pair most at risk of collapsing into one grey
 * at this scale — both are neutral browns in every theme. Pushed apart by
 * VALUE, not hue, so the separation survives a colour-blind reader and a
 * monochrome print: paving is a pale wash, masonry is near-solid. */
.fp-corridor { fill: color-mix(in srgb, var(--bb-wall) 12%, transparent); }
.fp-wall { fill: color-mix(in srgb, var(--bb-wall) 62%, transparent); }
/* Vehicle hull (#264): outline only, no fill — a mobile bastion is a few loose
 * rectangles without it, and unrecognisable as a ship. */
.fp-hull {
  fill: none;
  stroke: color-mix(in srgb, var(--bb-wall) 70%, transparent);
}

.hd-body {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.hd-name {
  font-family: var(--bb-font-display);
  font-size: 1.3rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--bb-ink);
  /* Never truncated, never clamped — the whole reason this is a register. */
  overflow-wrap: anywhere;
}
.hd-figures {
  font-family: var(--bb-font-display);
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  color: var(--bb-ink-soft);
  font-variant-numeric: lining-nums tabular-nums;
}
/* Over cap is an alert, not brand structure — the danger token, as in
 * .cap-echo-fill.over. The numerals already say it; the colour is redundant so
 * the row survives a monochrome print. */
.hd-over { color: var(--bb-danger); font-weight: 600; }
.hd-standing {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.82rem;
  color: var(--bb-ink-faint);
  font-variant-numeric: tabular-nums;
}
/* "Filed under Curse of Strahd" (#267) — last in the body and one step quieter
 * than .hd-standing. The body reads name → who holds it → what's in it → where
 * the record stands → how you filed it, and filing goes last because it is the
 * only line on the card that isn't about the bastion.
 *
 * Not a chip beside the name: .hd-name already carries .bm-dot or .hd-wax, and
 * names never truncate, so they wrap into where a chip would sit. */
.hd-filed {
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8rem;
  color: var(--bb-ink-faint);
}
/* Grouped, the sticky head above the row already says this — the same
 * no-inline-repeats rule that keeps a card from echoing its own heading. It is
 * kept when grouping is OFF for two reasons: below the organise threshold there
 * is nothing to section, so this line is the only visible result of filing at
 * all; and it shows *why* a row matched when a campaign name is typed into the
 * filter. */
.home-roll-body.is-grouped .hd-filed { display: none; }

/* Campaign sections (#267) — the .furnish-group-label running-head, second
 * instance, so this is now a pattern rather than a one-off. Deliberately NOT a
 * .section-head alike: that is the bay-level device (h2 + aside), and repeating
 * it inside a roll makes every campaign look like its own bay.
 *
 * Sticky because the count is the point — it tells a reader whether scrolling
 * further stays inside this campaign. --bb-parchment-light is the roll's own
 * surface (.home-roll above); --bb-parchment here would read as a patch. The
 * negative right margin reaches past .home-roll-body's 12px scrollbar gutter so
 * rows can't ghost through under the head. */
.holding-group-label {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  margin-right: -12px;
  padding: 5px 16px;
  background: var(--bb-parchment-light);
  border-bottom: 1px solid var(--bb-rule-soft);
  font-family: var(--bb-font-display);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bb-ink-faint);
}
/* A campaign name is a user string and wraps rather than truncating, per the
 * never-truncate rule; the count holds its own line-end. */
.hg-name { min-width: 0; }
.hg-count {
  flex: 0 0 auto;
  font-family: var(--bb-font-italic);
  font-style: italic;
  letter-spacing: 0;
  text-transform: none;
  font-variant-numeric: tabular-nums;
}
/* The first head sits flush against the roll's top edge; every later one gets
 * air from the section it opens. */
.holding-group + .holding-group .holding-group-label { margin-top: 6px; }

/* Filter matched nothing (#267) — a gap the library had and the shelf would
 * have inherited with its new filter. Left-aligned: the roll is a ledger, not a
 * card. No echo of the query (it would need escaping and can run sixty
 * characters) and no Clear verb — the search input has a native ×, and the
 * count beside it already reads "0 of 9 bastions". */
.home-roll-empty {
  margin: 0;
  padding: 18px 16px;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.9rem;
  color: var(--bb-ink-faint);
}

/* ---- The "File under a campaign" dialog (#267) -------------------------- */

/* The campaigns already in use, as tappable chips.
 *
 * NOT decoration for the <datalist> beside it — the two do different jobs. A
 * datalist gives keyboard typeahead on a desktop and close to nothing on a
 * phone, so without these the whole point of the picker ("the second bastion in
 * a campaign is a pick, not a retype") is false on touch. Both ship. */
.filing-suggest {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  margin-top: 10px;
}
/* .home-foot-btn carries the verb recipe and the coarse-pointer 44px floor
 * already; this only holds them off the flex stretch. */
.filing-chip { flex: 0 0 auto; }
/* Says where the filing is written. On a library row that is reassurance; on a
 * SHELF row it is the sentence that makes filing legible as filing rather than
 * as editing somebody else's keep, which is why it is one string for both.
 * Deliberately not .dialog-warn — that is gilt, and gilt means caution. This is
 * a fact. Same recipe as .annotation-legend-hint. */
.filing-note {
  margin: 12px 0 0;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8rem;
  color: var(--bb-ink-soft);
}

/* Always visible, never hover-revealed: home is the authoritative management
 * surface, and a verb row that appears only under a pointer is unreachable by
 * touch and unscannable by keyboard. */
.hd-verbs {
  position: relative;
  z-index: 1;
  display: flex;
  gap: 2px;
}

/* ---- The DM's Shelf (#255) --------------------------------------------- */

/* The second register: bastions that are not yours. Same anatomy as the
 * library's rows on purpose — both answer "which bastion do I open?" — so the
 * only thing this modifier changes is what says CUSTODY. */

/* The wax seal, in the slot .bm-dot occupies on a library row. Same slot and
 * the same ink, but it must not be the same MARK: rendered as a disc it was
 * pixel-for-pixel the library's current-dot, and with both registers on one
 * page that reads as "this is the open bastion" twice. A ring reads as an
 * impression rather than a state light — a seal pressed into wax, which is
 * exactly the claim ("someone else's, stamped and closed"). Deliberately
 * typographic and not an emoji: it would be the one full-colour object on a
 * page whose entire mark vocabulary is set in type. */
.holding .hd-wax {
  display: inline-block;
  /* NOT .bm-dot's fixed 0.8em box: a ring is a wider glyph than a disc at the
     same size, so it overflowed the box and sat flush against the name. Sized
     by its own advance, with the gap as a real margin. */
  margin-right: 0.28em;
  color: var(--bb-vermillion-deep);
  /* Larger than .bm-dot's 0.8em because a ring carries less ink than a disc of
     the same size and would otherwise sit lighter than the mark it replaces. */
  font-size: 0.74em;
  /* Optical centring against a 1.3rem display face — the glyph is small enough
     that baseline alignment drops it below the name's visual midline. */
  vertical-align: 0.16em;
  flex: 0 0 auto;
}
/* A shelf row is somebody else's, and the whole bay is read-only, so the name
 * stays plain ink — .is-current's vermillion-deep would claim this is the open
 * bastion, which it can never be. */
.holding.is-shared .hd-name { color: var(--bb-ink); }

/* A snapshot whose blob no longer decodes. It keeps its row (losing one
 * silently is worse) but drops to marginalia weight, since the only useful
 * verb left is Remove. */
.holding.is-damaged .hd-name { color: var(--bb-ink-faint); }
.holding.is-damaged .hd-standing { color: var(--bb-danger); font-style: normal; }
.holding .bm-remove:hover { color: var(--bb-danger); background: transparent; }

/* The bay's own lead-in. Lighter than a .home-standfirst — this explains a
 * register, it doesn't introduce the page. */
.home-shelf-sub {
  margin: 0 0 10px;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.86rem;
  color: var(--bb-ink-faint);
}

/* Paste rail — the shelf's foot. Reuses .home-roll-foot's frame, so it sits
 * exactly where the library's verb rail sits, and .home-filter's field, so the
 * page has one input treatment rather than two. */
.shelf-rail {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  width: 100%;
}
/* Grows into the rail, unlike the fixed-width filter it borrows its look from:
 * what goes in here is a 20KB link, and a field sized for "Filter…" would show
 * the user about eight characters of it. */
.shelf-rail .shelf-paste {
  flex: 1 1 16rem;
  min-width: 0;
  font-family: var(--bb-font-text);
  font-size: 0.82rem;
}
.shelf-rail .home-foot-btn { flex: 0 0 auto; }
/* The file route (#272) — subordinate to the ＋ plaque beside it, because it is
 * another source for the same act rather than a second verb. `flex: 0 0 auto`
 * like its neighbour, so when the rail wraps the two controls stay together on
 * the second line instead of the link stranding on a third. */
.shelf-rail .shelf-rail-file { flex: 0 0 auto; }

/* The same rail standing in for a choice tile on the empty page (#268), where
 * it is the DM's only way in. It replaces a .dialog-choice, so it takes that
 * tile's box to keep the list's rhythm — three rows in, three rows out. */
.shelf-rail-blank {
  padding: 10px 12px;
  border: 1px solid var(--bb-rule-soft);
  /* Transparent, like the tiles it stands among — MEASURED, not assumed: they
   * declare --bb-parchment-mid but compute to rgba(0,0,0,0), so the box a
   * reader sees is the roll body showing through. Giving this a real fill made
   * it lighter than its neighbours inside a bordered list, which reads as a
   * knockout — a hole where a row should be — rather than a peer. */
  background: transparent;
  /* .dialog-actions-list has gap: 0; the run's rhythm comes from each
   * .dialog-choice's own margin-top, which this doesn't inherit by standing in
   * for one. Without it the rail butts flush against the tile above. */
  margin-top: 14px;
}

/* "· from bastion.example.com" — shown only when a snapshot came from a
 * DIFFERENT deployment than the one reading it (#268). Not a warning: a
 * cross-server link is ordinary and supported, and colouring it as a hazard
 * would misread the fact.
 *
 * It carries NO weight change at all, and that is deliberate. The first attempt
 * set `font-style: italic` (a no-op — .hd-standing is already italic) plus
 * `opacity: .85`, so the only rule that did anything was the one that dropped
 * this to 4.15:1, under the 4.5:1 floor. It needs no step down: it is already
 * the last clause of the softest line on the card.
 *
 * The separator rides INSIDE the span so a wrap can't strand a dangling
 * "· from" at the end of a line. */
.hd-foreign { white-space: nowrap; }

/* ---- Key to the seals -------------------------------------------------- */

/* A footnote to the register, not a bay — the .charter-sources disclosure
 * idiom, collapsed by default so a full roll still reaches its colophon. */
.home-key-note {
  margin: 22px 0 0;
  border-top: 1px solid var(--bb-rule);
}
/* <details> carries display:block from the UA sheet; state the hidden case
 * explicitly so home.js's toggle can't lose to it under any layer ordering. */
.home-key-note[hidden] { display: none; }
.home-key-note > summary {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  cursor: pointer;
  list-style: none;
}
.home-key-note > summary::-webkit-details-marker { display: none; }
.home-key-title {
  font-family: var(--bb-font-display);
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bb-ink-soft);
}
/* What the summary adds that the title can't: the seal draws ONE floor, which
   is the first question a three-storey bastion's owner asks. */
.home-key-scope {
  margin-left: auto;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--bb-ink-soft);
}
.home-key-note > summary::after {
  content: "";
  flex: none;
  width: 7px;
  height: 7px;
  margin-right: 3px;
  border-right: 1.5px solid var(--bb-ink-soft);
  border-bottom: 1.5px solid var(--bb-ink-soft);
  transform: rotate(45deg);
  transition: transform 120ms;
}
.home-key-note[open] > summary::after { transform: rotate(-135deg); }
.home-key-note > summary:hover .home-key-title,
.home-key-note > summary:hover .home-key-scope { color: var(--bb-vermillion); }
.home-key-note > summary:hover::after { border-color: var(--bb-vermillion); }
.home-key-note > summary:focus-visible {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}

/* Borderless: the disclosure's own border-top is the rule now, and the list's
   would double it directly under the summary. */
.home-key {
  margin: 0;
  padding: 0 0 12px;
  list-style: none;
}
.home-key-row {
  display: grid;
  grid-template-columns: 28px minmax(0, 10rem) 1fr;
  align-items: baseline;
  gap: 4px 12px;
  padding: 6px 0;
}
.home-key-swatch { width: 22px; height: 16px; align-self: center; }
.home-key-term {
  font-family: var(--bb-font-display);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bb-ink);
}
.home-key-gloss {
  font-family: var(--bb-font-text);
  font-size: 0.95rem;
  line-height: 1.45;
  color: var(--bb-ink-soft);
}

/* ---- Colophon ---------------------------------------------------------- */

/* Left-aligned on the page's own left edge. Centring it was the only element on
 * the page that didn't share the column's axis, which read as a stray footer
 * from another document. */
.home-colophon {
  max-width: 820px;
  margin-inline: auto;
  padding: 0 24px 36px;
}
/* Gilt via `color`, which both the stroke and the fill inherit through
 * currentColor — one token, one declaration. */
.home-ornament {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 14px;
  color: var(--bb-gilt);
  opacity: 0.85;
}
.home-ornament .ho-rule {
  flex: 1 1 0;
  border-top: 1px solid currentColor;
  opacity: 0.5;
}
/* Flex, not a run of "·"-separated inline links: the separators were colliding
 * with the underlines and the version pill was jammed against "Source". */
.home-colophon-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 16px;
  margin: 0 0 10px;
  font-family: var(--bb-font-text);
  font-size: 0.9rem;
  color: var(--bb-ink-soft);
}
.home-colophon-links a {
  color: var(--bb-vermillion);
  text-underline-offset: 3px;
}
/* Pushed clear of the links — it is build metadata, not a fifth link. */
.home-colophon-links .version-badge { margin-left: auto; }
.home-colophon-note {
  margin: 0;
  font-family: var(--bb-font-italic);
  font-style: italic;
  font-size: 0.82rem;
  color: var(--bb-ink-faint);
}

/* The theme echo (#264). `.home-foot-btn` carries the whole verb recipe —
 * display face, tracking, uppercase, hover; this adds only what a label with a
 * contained sr-only radio needs. Names, not swatches: the page is the swatch. */
.home-colophon-theme {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 14px;
  margin: 0 0 12px;
}
.home-theme-btn {
  /* Contains the .sr-only radio. Without a positioned ancestor its containing
     block is the viewport, and focusing it scrolls the page to the top. */
  position: relative;
  display: inline-flex;
  align-items: baseline;
  gap: 2px;
}
/* Current, in the page's own vocabulary — the same token .holding.is-current
   .hd-name uses for "this is the one you're on". */
.home-theme-btn:has(input:checked) { color: var(--bb-vermillion-deep); }
/* Hidden, not absent: a reserved 0.8em stops the row reflowing on switch. */
.home-theme-btn:not(:has(input:checked)) .bm-dot { visibility: hidden; }
/* Without this the shared .home-foot-btn:hover lifts the current one from
   vermillion-deep to vermillion, which reads as "hovering deselects it". */
.home-theme-btn:has(input:checked):hover { color: var(--bb-vermillion-deep); }
.home-theme-btn:has(input:focus-visible) {
  outline: 2px solid var(--bb-vermillion);
  outline-offset: 2px;
}

/* ---- Responsive -------------------------------------------------------- */

@media (max-width: 980px) {
  .home-page { padding: 16px 18px 32px; }
  .holding { grid-template-columns: 72px 1fr auto; gap: 0 12px; }
  .hd-seal { width: 72px; height: 72px; }
  .hd-name { font-size: 1.2rem; }
}

@media (max-width: 640px) {
  .home-page { padding: 14px 16px 32px; }
  .home-colophon { padding: 0 16px 28px; }
  /* Drop the grid: the seal floats and the text wraps around and below it, so
     the register keeps its rhythm without a column structure to maintain. */
  .holding { display: block; padding: 12px 12px; }
  .hd-seal { float: left; width: 64px; height: 64px; margin: 0 12px 8px 0; }
  /* Right-aligned: a single ⋯ hanging at the left edge of a block row reads as
     leftover markup rather than as that row's actions. */
  .hd-verbs { clear: both; justify-content: flex-end; padding-top: 4px; }
  .home-head-aside { flex-wrap: wrap; }
  .home-filter { max-width: none; flex: 1 0 100%; }
  .home-key-row { grid-template-columns: 28px 1fr; }
  .home-key-gloss { grid-column: 2; }
  /* One verb per line below this width, as before the rail was split into runs
     — the runs go column too, so the grouping changes nothing at this tier. */
  .home-roll-foot { flex-direction: column; align-items: flex-start; gap: 10px; }
  .home-foot-run { flex-direction: column; align-items: flex-start; gap: 10px; }
  /* An auto inline margin outranks align-items in a column flex container —
     without this reset the library verbs flush right on the stacked tier. */
  .home-roll-foot:not(.is-blank) .home-foot-run + .home-foot-run { margin-left: 0; }
}

/* Any coarse pointer, at any width: collapse the verb row to a single labelled
 * overflow. Four unlabelled glyphs with no tooltip path (data-tip is
 * pointer-driven) are four mysteries on a phone; the app's own idiom is exactly
 * this fold to ⋯. The trigger stays permanently visible, so the verbs are still
 * never hover-revealed — the touch user just reaches a LABELLED menu in one tap
 * instead of guessing at glyphs. */
@media (max-width: 640px), (hover: none) {
  /* The paste field is a tap target like any other, and on the cold-DM page it
     is the FIRST one anyone touches in this product (#268) — a 24px field under
     a 44px button was the one control in that rail below the line. */
  .shelf-rail .shelf-paste { min-height: 44px; }
  /* The register filters, on the same floor as the paste field one line up —
     they are the same control at the same size, and .home-filter computes to
     26px without this. It had always been under the line on the library; #267
     put a second one on the shelf, which is what turned a single miss into a
     pattern worth fixing rather than inheriting. */
  .home-filter { min-height: 44px; }
  /* Same floor, same reason (#267): a 0.85rem italic text button sitting in a
     wrapped header row is well under it. */
  .home-arrange {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
  .hd-verbs .bm-clone,
  .hd-verbs .bm-export,
  .hd-verbs .bm-share { display: none; }
  .hd-verbs .bm-action {
    min-width: 44px;
    min-height: 44px;
    padding: 0;
  }
  /* The whole foot-verb family, not just the register's: these are 0.72rem
     text targets sitting in a wrapped row, which is the likeliest mis-tap on
     the page. Applied to .home-foot-btn rather than to one row so the roll's
     rail and the colophon can't drift apart. */
  .home-foot-btn {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
  /* Closed, the summary is ~36px — under the thumb floor. */
  .home-key-note > summary { padding: 14px 0; }
}

@media (max-width: 420px) {
  .home-continue .hc-who { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  /* Hover and focus state CHANGES remain — they are information, not
     decoration. They simply arrive instantly. */
  .holding,
  .holding .bm-action { transition: none; }
}

} /* @layer components (#259) — closes the file-wide holding layer */
