/* Angled Watch — "the detective's desk" UI. Every surface is a physical object
   (paper, manila folder, mugshot card, rubber stamp). Light paper, dark ink —
   the inverse of a web dashboard. Sized in rem against a viewport-scaled root. */

/* vendored period typeface (Apache-2.0) — off-gameplay text only */
/* Special Elite ships as Google's standard TWO-FILE pairing, each scoped by
   unicode-range. THE SUBSET TRAP (it shipped): only the latin-EXT file was
   vendored for the whole project, whose cmap has no a-z, no digits — so the
   face "loaded" and rendered nothing but the space; every letter silently fell
   back to Courier New. No CSS- or style-reading test can see that failure —
   ENFORCED by tests/fontAudit.mjs, which parses the files' own cmaps. */
@font-face {
  font-family: 'Special Elite';
  font-style: normal; font-weight: 400; font-display: swap;
  src: url('./src/lib/fonts/special-elite-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Special Elite';
  font-style: normal; font-weight: 400; font-display: swap;
  src: url('./src/lib/fonts/special-elite-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* the desk palette — light paper surfaces, dark ink text */
  --desk: #17120e;                   /* dark felt/wood the scene sits on */
  --paper: #e7dcc3;                  /* aged card stock */
  --paper-dim: #d8cba9;
  --manila: #d8c48c;                 /* case-folder */
  --card: #efe7d4;                   /* brighter mugshot stock */
  /* ===================== THE INK LAW (user-locked) =====================
     THERE IS NO GREY TEXT IN THIS GAME. Every ink below is DARK on paper.
     Hierarchy is carried by WEIGHT, SIZE and SPACING — never by fading a
     colour toward the background.

     Why this is a law and not a preference: the panels are aged paper under a
     canvas grain, and the type is Special Elite, a distressed face with broken
     strokes. Both eat contrast that a contrast checker says you have. --ink-dim
     was #6b5d48 = 4.70:1 on paper (4.12:1 on the docket's aged stock) — nominally
     "AA" and in practice invisible, which shipped twice before it was banned.

     Measured on --paper #e7dcc3 / --tex-paper-aged #dccfa9 (tests below):
       --ink-strong  13.92 / 12.22   the darkest strike. Paper prose + headings.
       --ink         11.28 /  9.90   default text on paper.
       --ink-dim      9.54 /  8.38   SUBORDINATE, STILL DARK. The floor.
       --amber-deep   5.86 /  5.14   *CAPS* values only, inside dark copy.
     NOTHING player-facing may sit below --ink-dim's contrast on a light
     surface. If a thing needs to recede, make it smaller or lighter in WEIGHT.
     ==================================================================== */
  --ink: #2a241c;                    /* near-black warm ink — text on paper */
  --ink-strong: #14100a;             /* body copy on paper: the darkest strike (Special Elite reads
                                        light at normal weight, so paragraphs use this + weight 700) */
  --ink-dim: #383024;                /* SUBORDINATE ink — dark, never grey (9.54:1) */
  --stamp-red: #b23a2e;              /* rubber-stamp red — FILLS and borders only (4.36:1 on
                                        paper: too light to be text, see the ink law) */
  --stamp-red-ink: #5e1c16;          /* the same stamp, as TEXT on paper (9.33:1; 7.37:1 on
                                        --manila, the darkest stock — the binding constraint) */
  --brass: #c9a24a;                  /* slider knobs, folder tabs */
  --amber: #a8781a;                  /* gold that READS on paper (bright amber is for the canvas) */
  --amber-deep: #6d4a08;             /* *CAPS* VALUES ONLY, and only INSIDE dark body copy: at
                                        5.86:1 the surrounding sentence carries it. Never a
                                        standalone word — that needs --amber-ink. */
  --amber-ink: #472e05;              /* amber as STANDALONE text: stamps, tallies, streak lines
                                        (9.29:1 on paper, 7.34:1 on --manila) */
  --amber-soft: #8a6415;
  --good: #3f7d33;                   /* ink-green — FILLS/borders/icons only (3.68:1 as text) */
  --good-ink: #1d3c17;               /* ink-green as TEXT on paper (9.01:1; 7.13:1 on --manila) */
  --bad: #b23a2e;                    /* = stamp red */
  --blue: #3a5a8f;                   /* muted ink-blue */
  --shadow: 0 0.5rem 1.5rem rgba(0,0,0,0.5), 0 0.12rem 0.35rem rgba(0,0,0,0.4);
  --shadow-soft: 0 0.25rem 0.7rem rgba(0,0,0,0.35);
  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --sans: "Segoe UI", system-ui, -apple-system, Roboto, sans-serif;
  --type: "Special Elite", "Courier New", var(--serif); /* typewriter for menus/stamps */
  /* legacy tokens still referenced across the sheet — remapped onto the desk */
  --bg: #0b0e14;
  --panel: var(--paper);
  --panel-solid: var(--paper);
  --edge: rgba(42,36,28,0.20);       /* soft ink edge */
  --edge-strong: rgba(42,36,28,0.42);
  /* procedural textures injected by paper.js (fallbacks if baking fails) */
  --tex-paper: none; --tex-manila: none; --tex-card: none; --tex-paper-aged: none; --tex-grain: none;

  /* ============================ TYPE SCALE ============================
     EVERY player-facing font-size comes from this list. Do NOT invent a
     one-off rem value: that is exactly how this UI ended up with a dozen
     near-identical sizes (0.62/0.66/0.72/0.75/0.76/0.78/0.8/0.85/0.88/0.92)
     and text that kept shipping too small to read.

     Reading the game is the game. When unsure, go UP a step, not down.

       --fs-head   a PANEL'S TITLE. One per screen, and it must out-rank
                   everything under it (see the ink law: rank by weight and
                   size, never by fading the things below it).
       --fs-title  names, panel headings. One per panel.
       --fs-lead   the PRIMARY thing to read right now (the typed slip, a
                   lesson card). The biggest body text on screen.
       --fs-body   THE FLOOR FOR ANY SENTENCE. Records the player must read:
                   notebook captions, observation notes, statements, help
                   steps. Never go below this for prose.
       --fs-label  NOT prose. Roles, captions under an icon, short labels.
       --fs-micro  NOT prose. Attribution rows, tags, 1-3 words maximum.

     Paper copy also needs --ink-strong + weight 700 (Special Elite is a
     distressed face and reads washed-out at normal weight — see --ink-strong).
     Sentences are NEVER line-clamped; clamping a clue destroys it.
     ==================================================================== */
  /* Every step raised ~14% (2026-07): the whole scale used to sit BELOW 1rem,
     so every sentence in the game rendered smaller than the root font while
     the panels floated in a mostly-empty screen. --fs-body is now 1rem: the
     floor for a sentence is the root, not a fraction of it. */
  --fs-head: 1.72rem;
  --fs-title: 1.32rem;
  --fs-lead: 1.12rem;
  --fs-body: 1rem;
  --fs-label: 0.87rem;
  --fs-micro: 0.78rem;
}

/* THE adaptive knob. Everything in this UI is rem-based, so this ONE line
   decides how big the whole game reads — get it wrong and the game is
   unplayable, not just ugly.
   Tied to viewport HEIGHT, deliberately:
     - width would mean a wide/ultrawide window inflates the HUD (it did: at
       1900px the old `1.45vw + 8px` hit ~36px root and swamped the screen)
     - height is what actually decides how much HUD fits, and it makes the game
       look THE SAME in every browser at the same window shape. Browser zoom and
       device pixel ratio both change the CSS viewport, and vh moves with them,
       so a zoomed browser lands in the same place instead of a different game.
   The clamp bounds are the safety rails for very short/tall windows. Keep the
   whole range narrow: this multiplies with --ui-scale (the A-/A+ chips), and
   the two used to compound into a 14px..72px spread across browsers. */
:root { --ui-scale: 1; }
html { font-size: calc(clamp(16px, 2.5vh, 26px) * var(--ui-scale, 1)); }

* { box-sizing: border-box; margin: 0; padding: 0; }
body { height: 100%; overflow: hidden; background: var(--bg); color: var(--ink); font-family: var(--sans); font-size: 0.9rem; }
html { height: 100%; }

/* NO TEXT SELECTION anywhere in the game. Dragging to orbit the camera used to
   highlight the HUD paper text (a drag that starts on a word runs a selection);
   this is a game, not a document, so selection is off everywhere. iOS's
   tap-and-hold "callout" menu is killed too. There is nothing the player is
   meant to copy — the share code is copied by a BUTTON, not by selecting. */
* {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/* KEYBOARD FOCUS RING (accessibility). :focus-visible only — a mouse click
   never draws it, so the noir surfaces stay clean; a Tab/arrow user always sees
   where they are. Amber ink reads on paper AND over the dark scene. */
:focus-visible { outline: 2px solid var(--amber-ink); outline-offset: 2px; }
/* the canvas-side controls sit over the dark 3D scene, where amber-on-dark is
   fine but a paper halo reads even better against the night */
.controls :focus-visible, .cbtn:focus-visible, .chip:focus-visible,
.card:focus-visible { outline-color: var(--paper); }

/* the screen-reader-only utility: present in the a11y tree, invisible on screen.
   NOT display:none (that hides it from screen readers too) — the clip pattern. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
#game { position: fixed; inset: 0; width: 100vw; height: 100vh; display: block; touch-action: none; z-index: 1; }

/* the world reads as a framed noir SCENE, not an embedded canvas: a permanent
   soft vignette + a whisper of film grain — layered ABOVE the canvas but BELOW
   all the UI panels (z 5, between #game z1 and #ui z10). Body children, so the
   #ui > * pointer rule doesn't touch them. */
.frame, .grain { position: fixed; inset: 0; z-index: 5; pointer-events: none; }
.frame { box-shadow: inset 0 0 7rem 1.5rem rgba(0,0,0,0.5), inset 0 0 22rem 4rem rgba(12,7,2,0.4); }
.grain { background-image: var(--tex-grain); background-repeat: repeat; opacity: 0.5; mix-blend-mode: overlay; }
/* When the WebGL noir grade is active it renders its own grain + vignette onto
   the canvas — hide the DOM fallbacks so the look isn't applied twice. */
.post-graded .frame, .post-graded .grain { display: none; }

/* LAST NOTCH — a slow red breath around the frame, for as long as one tick is
   left. The cigarette is in a corner; the player is looking at a suspect in the
   middle of the screen. This is the only part of the meter that reaches them
   there, and it is why the clock used to run out unseen: the sole "one left"
   warning was a ONE-TIME lesson card (main.js _strikeWarned), so every case
   after the first said nothing at all.
   IT LIVES ON THE SCENE LAYER, NOT THE .vignette LAYER, AND THAT IS THE POINT.
   .vignette is z 8 — ABOVE the rails, the typeline and the inspect panel. A
   750ms .flash can tint paper for a dramatic beat; this state can persist for
   minutes, and washing red over text the player must read is exactly what law 6
   forbids. So it sits at z 6 with .frame/.grain: over the 3D scene (#game z1),
   under every sheet of paper (#ui z10). Never raise this above #ui.
   Not hidden by .post-graded: the WebGL grade owns the LOOK, this is a WARNING.
   Never full opacity, and the trough keeps a hint, so the scene stays readable.
   GEOMETRY IS TIGHTER THAN .vignette'S ON PURPOSE (6rem/1.5rem vs 9rem/3rem):
   .vignette only ever plays for 750ms, so it can afford to wash half the frame.
   This one can sit there for minutes, so it HUGS the border — it has to be
   impossible to miss in peripheral vision without fogging the scene the player
   is trying to read a suspect in. Peak 0.5 is half of .flash's 1.0. */
.warnedge {
  position: fixed; inset: 0; z-index: 6; pointer-events: none; opacity: 0;
  box-shadow: inset 0 0 6rem 1.5rem rgba(224,85,107,0.6);
}
.warnedge.on { animation: dangerwarn 1.7s ease-in-out infinite; }
@keyframes dangerwarn { 0%, 100% { opacity: 0.08; } 50% { opacity: 0.5; } }

/* ---------- loading ---------- */
#loading {
  position: fixed; inset: 0; z-index: 100; display: grid; place-items: center;
  background: radial-gradient(circle at 50% 42%, #2a2018, #0d0a07 72%);
  transition: opacity 0.6s ease;
}
#loading.gone { opacity: 0; pointer-events: none; }
.ld-inner { text-align: center; font-family: var(--type); font-weight: 700; letter-spacing: 0.06em; color: #cbb98e; font-size: 1.05rem; }
.ld-badge { color: #d9a94a; margin-bottom: 0.6rem; filter: drop-shadow(0 0 1rem rgba(217,169,74,0.35)); animation: bob 2s ease-in-out infinite; display: grid; place-items: center; }
.ld-badge svg { width: 3.2rem; height: 3.2rem; }
@keyframes bob { 50% { transform: translateY(-0.5rem); } }

/* ---------- overlay root ---------- */
#ui { position: fixed; inset: 0; z-index: 10; pointer-events: none; }
#ui > * { pointer-events: auto; }
/* decorative full-area / floating overlays must NOT eat clicks meant for the
   game canvas (higher specificity than the blanket rule above) */
#ui > .vignette, #ui > .cigwrap, #ui > .hint { pointer-events: none; }

/* ---------- top bar ---------- */
.topbar {
  position: absolute; top: 0; left: 0; right: 0; display: flex; align-items: flex-start;
  justify-content: space-between; gap: 0.75rem; padding: 0.85rem 1.1rem;
  background: linear-gradient(to bottom, rgba(7,5,2,0.55), rgba(7,5,2,0));
}
.top-actions { display: flex; gap: 0.5rem; flex-shrink: 0; }
/* chips are little PAPER TABS — a big ink icon that fills the square, press-down
   feedback (no web hover) */
.chip {
  font-family: var(--type); font-weight: 700; background-color: var(--paper); background-image: var(--tex-paper);
  background-size: 210px; color: var(--ink); border: 1px solid var(--edge-strong);
  border-radius: 0.24rem; width: 2.2rem; height: 2.2rem; padding: 0.32rem; cursor: pointer;
  display: grid; place-items: center;
  box-shadow: var(--shadow-soft); transition: transform 0.08s ease, box-shadow 0.08s ease;
}
.chip:hover { transform: translateY(-1px); }
.chip:active { transform: translateY(1px); box-shadow: 0 0.08rem 0.18rem rgba(0,0,0,0.35); }
.chip.off { color: var(--ink-dim); opacity: 0.72; }
.chip.flash { background-color: var(--good); background-image: none; color: #f2ecda; } /* wordless "copied" */

/* the killer's-patience cigarette — burns down as the player spends actions.
   Hard-pinned to the top-left corner (its own layer, not the top-bar flex) so
   it stays on the left at every viewport width and UI scale.
   MUST mirror .topbar's safe-area inset (see the notch rule further down): the
   topbar pads itself down past a notch and this layer does not, so without the
   env() the meter would ride UP into the chips it is supposed to sit beside.
   Harmless while the meter was small; the 12.6rem one lands right on them. */
.cigwrap {
  position: absolute; top: calc(0.85rem + env(safe-area-inset-top, 0px)); left: 1.1rem; z-index: 12;
  display: flex; align-items: center; pointer-events: none;
}
/* Sized big on purpose: this is the only clock the player has, it lives in a
   corner, and the eyes are centre screen tailing a suspect. It shipped at
   8.5rem and burned out unnoticed (user, 2026-07). See hud.js setPressure. */
/* the origin here serves .cig.spent (the only thing that still transforms the
   canvas itself); .cigwrap carries its own for the flare */
.cig { width: 12.6rem; height: auto; filter: drop-shadow(0 0.1rem 0.4rem rgba(0,0,0,0.6)); transform-origin: 20% 80%; }
.cig.hidden { display: none; }
/* THE TWO ANIMATIONS RIDE DIFFERENT ELEMENTS ON PURPOSE. The burn flare drives
   TRANSFORM on the wrapper; the danger pulse drives FILTER on the canvas. When
   both were on .cig they had equal specificity, so source order silently ate one
   of them — and the loser was the flare at exactly the moment it matters most:
   the last burn before the strike. Different elements cannot collide. */
.cigwrap { transform-origin: 22% 78%; }
/* a burn: a hard pop + a kick, then settle. A tick is unrecoverable, so it is
   allowed to be the loudest thing on screen for a third of a second. */
.cigwrap.flare { animation: cigflare 0.42s ease-out; }
@keyframes cigflare {
  0% { transform: scale(1) translateX(0); }
  18% { transform: scale(1.3) translateX(-0.14rem); filter: drop-shadow(0 0 0.85rem rgba(255,150,60,1)); }
  38% { transform: scale(1.2) translateX(0.12rem); }
  60% { transform: scale(1.1) translateX(-0.05rem); }
  100% { transform: scale(1) translateX(0); }
}
/* one notch from the strike: an urgent red breath. The canvas already paints a
   static red halo + hot ember underneath, so the state survives reduce-motion. */
.cig.danger { animation: cigdanger 0.9s ease-in-out infinite; }
@keyframes cigdanger {
  0%, 100% { filter: drop-shadow(0 0.1rem 0.4rem rgba(0,0,0,0.6)); }
  50% { filter: drop-shadow(0 0 0.9rem rgba(224,85,107,1)); }
}
/* clock spent (the killer struck): crush out + fade away */
.cig.spent { animation: cigspent 0.7s ease-in forwards; }
@keyframes cigspent {
  0% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(0.82) translateY(0.35rem); }
}

/* result-screen star rating — stamped amber marks */
.stars { display: flex; gap: 0.5rem; justify-content: center; margin: 0 0 0.9rem; font-size: 1.9rem; }
.stars.win { color: var(--amber); }
.stars.lose { color: var(--ink-dim); }
.stars svg.icon { font-size: 1.9rem; filter: drop-shadow(0 0.08rem 0.12rem rgba(0,0,0,0.3)); }
.stars.win svg.icon:nth-child(1) { transform: rotate(-8deg); }
.stars.win svg.icon:nth-child(2) { transform: rotate(4deg); }
.stars.win svg.icon:nth-child(3) { transform: rotate(-3deg); }

/* ---------- menus & settings (text IS allowed off-gameplay) ---------- */
/* the panel's title OUT-RANKS everything under it: biggest, boldest, darkest.
   (It used to be --ink at normal weight, which Special Elite renders lighter
   than the 700-weight stat line beneath it — the heading lost to its own
   subtitle. Rank by weight and size, per the ink law.) */
.menu-title {
  font-family: var(--type); font-size: var(--fs-head); font-weight: 700;
  color: var(--ink-strong); margin-bottom: 1rem; letter-spacing: 0.03em;
}
.menu-list { display: flex; flex-direction: column; gap: 0.6rem; width: min(22rem, 84vw); margin: 0 auto; }
/* menu buttons = paper strips with ink borders */
.menu-btn {
  font-family: var(--type); font-weight: 700; font-size: 1rem; letter-spacing: 0.03em; padding: 0.72rem 1rem; color: var(--ink);
  background-color: var(--paper); background-image: var(--tex-paper); background-size: 200px;
  border: 1px solid var(--edge-strong); border-radius: 0.16rem; cursor: pointer;
  box-shadow: var(--shadow-soft); transition: transform 0.1s, box-shadow 0.1s;
}
.menu-btn:hover { transform: translateY(-1px); box-shadow: var(--shadow); }
.menu-btn:active { transform: translateY(1px); }
.menu-btn.primary { background: var(--stamp-red); background-image: none; color: #f2ecda; border-color: rgba(0,0,0,0.25); }
.title-menu .logo { margin-bottom: 1.3rem; }

/* THE MOTION-COMIC CUTSCENE — the LIVE 3D world (rendered on #game) framed as
   2.5D. This is only the DOM overlay ON TOP of it: letterbox bars, a narration
   strip, a title card, the skip chip. pointer-events pass through to #game so a
   tap anywhere skips (main.js listens on the canvas). z 40, above #ui (10). */
/* while the cutscene films the live 3D world, the gameplay HUD is hidden so it
   never shows behind the letterbox; the .frame/.grain grade stays (it is the
   look), and #game keeps rendering the scene. */
body.in-cutscene #ui { display: none; }
.cutscene-ui { position: fixed; inset: 0; z-index: 40; pointer-events: none; }
/* THE SCENE FADE: a full-screen black veil used to cover a hard handoff (the
   cutscene -> gameplay cut). z 45 sits ABOVE the cutscene UI (40) and below the
   HUD rails/modals (#ui z 10 is under the scene layers; the fade must beat both
   the scene canvas z1 and the cutscene overlay, so 45). Opacity is driven inline
   by hud.sceneFade so the duration can vary per call. */
.scene-fade { position: fixed; inset: 0; z-index: 45; background: #050609;
  opacity: 0; pointer-events: none; transition: opacity 0.45s ease; }
.scene-fade.on { opacity: 1; }
.cut-bar { position: absolute; left: 0; right: 0; height: 0; background: #050609;
  transition: height 0.5s ease; }
.cut-bar.top { top: 0; } .cut-bar.bot { bottom: 0; }
.cutscene-ui.barred .cut-bar { height: 11vh; }
.cut-strip {
  position: absolute; left: 50%; bottom: 14vh; transform: translateX(-50%);
  width: min(80vw, 720px); padding: 0.75rem 1.1rem; border-radius: 0.5rem;
  background: rgba(231,220,195,0.94); border: 1px solid rgba(42,28,14,0.35);
  font-family: var(--type); font-weight: 700; font-size: var(--fs-lead); color: #2a1e12;
  text-align: center; opacity: 0; transition: opacity 0.4s ease; box-shadow: 0 4px 16px rgba(0,0,0,0.4);
}
.cut-strip.on { opacity: 1; }
.cut-title { position: absolute; left: 0; right: 0; top: 26vh; text-align: center;
  opacity: 0; transition: opacity 0.5s ease; }
.cut-title.on { opacity: 1; }
.cut-title-main { font-family: var(--type); font-weight: 700; font-size: var(--fs-title);
  color: #efe7d4; letter-spacing: 0.03em; }
.cut-title-sub { font-family: var(--type); font-weight: 700; font-size: var(--fs-head);
  color: #d7cfbe; margin-top: 0.3rem; }
.cutscene-skip {
  position: absolute; top: calc(0.9rem + env(safe-area-inset-top, 0px)); right: 1rem;
  pointer-events: auto; font-family: var(--type); font-weight: 700; font-size: var(--fs-micro);
  color: #f2ecda; background: rgba(6,7,11,0.6); border: 1px solid rgba(242,236,218,0.4);
  border-radius: 0.2rem; padding: 0.35rem 0.8rem; cursor: pointer; letter-spacing: 0.04em;
}
.cutscene-skip:hover { background: rgba(6,7,11,0.85); }

/* THE UPDATE NEWS (v1.1): a persistent collapsible note pinned to the title's
   top-right corner, over the scene, clear of the left rail. Always present,
   never dismissed. A pinned paper slip: header toggles the notes open/closed. */
.news-panel {
  position: absolute; top: 1.1rem; right: 1.1rem; z-index: 4;
  width: min(22rem, 42vw);
  background-color: var(--paper); background-image: var(--tex-paper); background-size: 180px;
  border: 1px solid rgba(42,28,14,0.35); border-radius: 0.2rem;
  box-shadow: 0 6px 20px rgba(0,0,0,0.45); transform: rotate(0.5deg);
  overflow: hidden;
}
.news-head {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 0.6rem;
  padding: 0.6rem 0.85rem; cursor: pointer; background: transparent; border: none;
  font-family: var(--type); font-weight: 700; font-size: var(--fs-label); color: var(--ink-strong);
}
.news-title { letter-spacing: 0.02em; }
/* a CSS-drawn chevron (law 2: no glyph) — points down when closed, up when open */
.news-caret {
  width: 0.55rem; height: 0.55rem; border-right: 2px solid var(--ink); border-bottom: 2px solid var(--ink);
  transform: rotate(45deg); transition: transform 0.18s ease; margin-bottom: 0.15rem;
}
.news-panel.open .news-caret { transform: rotate(-135deg); margin-bottom: -0.1rem; }
.news-body {
  display: none; padding: 0 0.85rem 0.75rem;
  border-top: 1px dashed rgba(42,28,14,0.3);
}
.news-panel.open .news-body { display: block; }
.news-ver {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-micro);
  color: var(--stamp-red-ink); margin: 0.5rem 0 0.4rem; letter-spacing: 0.03em;
}
.news-list { list-style: none; margin: 0; padding: 0; }
.news-list li {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-micro);
  color: var(--ink-strong); line-height: 1.4; padding: 0.28rem 0;
  border-bottom: 1px dashed rgba(42,28,14,0.18);
}
.news-list li:last-child { border-bottom: none; }
/* the first note is the recommendation — the panel's one emphasis (dark stamp
   red ink, still ≥7:1 on paper; law 6) */
.news-list li.news-reco { color: var(--stamp-red-ink); }
/* SEE MORE — an <a> styled like a set-btn; the itch devlog link */
.news-more {
  display: inline-block; margin-top: 0.6rem; text-decoration: none;
  font-family: var(--type); font-weight: 700; font-size: var(--fs-micro);
  color: #f7f1e2; background: var(--stamp-red-ink);
  padding: 0.28rem 0.7rem; border-radius: 0.16rem;
}
.news-more:hover { filter: brightness(1.1); }
/* portrait / phone: the rail bottom-anchors, so the news note moves to the top
   band full-width instead of a corner it would crowd */
@media (orientation: portrait) {
  .news-panel { position: static; width: auto; margin: 0.6rem 0.8rem 0; transform: none; }
}
/* the old modal bulletin (showBulletin) is retained dormant for a future use */
.panel.bulletin { width: min(34rem, 94vw); text-align: left; }
.panel.bulletin .help-h { text-align: left; }
.panel.bulletin .primary { margin-top: 1rem; }

/* THE DAILY CASE — a menu strip with a state sub-line; spent = today's verdict
   is in, the strip becomes its own stamp (no hover life, no click) */
.menu-btn.daily { text-align: left; }
.menu-btn.daily .menu-label { font-size: var(--fs-body); font-weight: 700; color: var(--ink-strong); }
/* today's daily is done: the strip goes quiet by losing its lift and its fresh
   paper, NOT by fading — "Today's file is CLOSED. Back tomorrow." is the whole
   reason the strip is still on screen */
.menu-btn.daily.spent { cursor: default; background-color: var(--paper-dim); box-shadow: none; }
.menu-btn.daily.spent:hover { transform: none; box-shadow: none; }
.menu-sub { font-family: var(--type); font-weight: 700; font-size: var(--fs-body); color: var(--ink-dim); margin-top: 0.2rem; }
/* a sub-line on a PRIMARY (red) button must be PALE, not dark ink — the story
   Play button's "Chapter N of 6" read at 2.19:1 as dark-on-red otherwise */
.menu-btn.primary .menu-sub { color: #f2ecda; }
.menu-sub.streakline { display: flex; align-items: center; gap: 0.35rem; color: var(--amber-ink); }
.menu-sub.streakline svg.icon { width: 0.9rem; height: 0.9rem; flex: none; }

/* the duty ledger — six weeks of showing up, stamped on paper */
.ledger { width: min(30rem, 94vw); }
.ledger-head { margin-bottom: 0.7rem; }
.ledger-streak { font-family: var(--type); font-size: var(--fs-lead); font-weight: 700; color: var(--ink); }
.ledger-line { font-family: var(--type); font-weight: 700; font-size: var(--fs-body); color: var(--ink-dim); }
.ledger-grid {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 0.28rem;
  margin: 0.6rem 0 0.8rem;
}
/* the ruled grid: this is a LEDGER, so its rules must read as ruled lines.
   They were 0.28-alpha dashes and vanished into the paper. */
.ledger-cell {
  position: relative; aspect-ratio: 1; border: 1px solid rgba(42,36,28,0.55);
  border-radius: 0.12rem; display: grid; place-items: center;
}
.ledger-day {
  position: absolute; top: 0.08rem; left: 0.18rem;
  font-family: var(--type); font-weight: 700; font-size: var(--fs-micro); color: var(--ink-dim);
}
/* the day's stamp — it is SLAMMED on the square, so it fills it. (It rendered
   as a speck smaller than the day number: `.ledger-stamp svg` tied with the
   base `svg.icon { width: 1em }` on specificity and lost on source order, so
   the icon fell back to 1em. Every icon rule in this sheet must say
   `svg.icon`, or size by `font-size` — the base rule owns width/height.) */
.ledger-stamp { width: 82%; height: 82%; color: var(--ink-dim); transform: rotate(-8deg); }
.ledger-stamp svg.icon { width: 100%; height: 100%; }
.ledger-stamp.won { color: var(--amber-ink); }
.ledger-favors { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.9rem; }
.ledger-favors svg.icon { width: 1.15rem; height: 1.15rem; color: var(--amber-deep); flex: none; }

/* THE SERVICE RECORD — the personnel folder with three tabbed sheets */
.records { width: min(36rem, 94vw); max-height: 88vh; display: flex; flex-direction: column; }
.rec-tabs { display: flex; gap: 0.25rem; margin-bottom: 0; }
/* an inactive tab recedes by WEIGHT and a dropped shadow, never by fading its
   ink (it was opacity 0.75, i.e. hierarchy-by-invisibility — the ink law) */
.rec-tab {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-label); letter-spacing: 0.06em;
  padding: 0.4rem 0.8rem 0.32rem; color: var(--ink-dim); cursor: pointer;
  background-color: var(--paper-dim); background-image: var(--tex-paper); background-size: 200px;
  border: 1px solid var(--edge-strong); border-bottom: none;
  border-radius: 0.22rem 0.22rem 0 0;
}
.rec-tab.on { color: var(--ink-strong); font-weight: 700; background-color: var(--paper); }
.rec-sheet {
  border: 1px solid var(--edge-strong); border-radius: 0 0.16rem 0.16rem 0.16rem;
  padding: 0.9rem; margin-bottom: 0.7rem; overflow-y: auto; min-height: 12rem;
  background-color: var(--paper); background-image: var(--tex-paper); background-size: 200px;
}
/* SERVICE — one question per page: what you have, what the next desk costs.
   The grade is the headline; the bar is the answer; the proof line is the ask. */
.rec-rank { display: flex; gap: 0.9rem; align-items: center; margin-bottom: 0.9rem; }
.rec-shield { width: 3.4rem; height: 3.4rem; color: var(--amber-ink); flex: none; }
.rec-shield svg.icon { width: 100%; height: 100%; }
.rec-grade { font-family: var(--type); font-size: var(--fs-title); font-weight: 700; color: var(--ink-strong); letter-spacing: 0.04em; }
.rec-merit { font-family: var(--type); font-size: var(--fs-lead); font-weight: 700; color: var(--ink-strong); }
.rec-line { font-family: var(--type); font-weight: 700; font-size: var(--fs-body); color: var(--ink); padding: 0.12rem 0; }
.rec-line.dim { color: var(--ink-dim); } /* subordinate by INK, not by shrinking prose */
.rec-next { font-family: var(--type); font-size: var(--fs-body); font-weight: 700; color: var(--ink-strong); margin-top: 0.7rem; }
.rec-wallet { font-family: var(--type); font-size: var(--fs-body); font-weight: 700; color: var(--ink-strong); margin-bottom: 0.5rem; }
/* the merit rule: ink creeping toward the next desk. Drawn, never a glyph. */
.merit-bar {
  height: 0.5rem; margin: 0.35rem 0 0.1rem; border: 1.5px solid var(--ink);
  border-radius: 0.1rem; background: rgba(42,36,28,0.08); overflow: hidden;
}
.merit-fill { height: 100%; background: var(--amber-ink); transition: width 0.4s ease; }

/* ledger rows — one typed line per filed case */
.rec-ledger { display: flex; flex-direction: column; }
.lrow {
  display: flex; align-items: center; gap: 0.55rem; padding: 0.34rem 0.1rem;
  border-bottom: 1px dashed rgba(42,36,28,0.25);
}
.lrow-main { flex: 1; min-width: 0; }
.lrow-place { font-family: var(--type); font-size: var(--fs-label); font-weight: 700; color: var(--ink-strong); }
.lrow-date { font-family: var(--type); font-weight: 700; font-size: var(--fs-micro); color: var(--ink-dim); }
.lrow-stamp {
  font-family: var(--type); font-size: var(--fs-micro); font-weight: 700; letter-spacing: 0.05em;
  border: 1.6px solid currentColor; border-radius: 0.14rem; padding: 0.1rem 0.32rem;
  transform: rotate(-4deg); display: flex; align-items: center; gap: 0.25rem; flex: none;
}
.lrow-stamp.o1, .lrow-stamp.o4, .lrow-stamp.o5 { color: var(--amber-ink); }
.lrow-stamp.o2, .lrow-stamp.o3 { color: var(--stamp-red-ink); } /* WRONG MAN / GOT AWAY are read */
.lrow-air svg.icon { width: 0.85rem; height: 0.85rem; display: block; }
.lrow-stars { display: flex; gap: 0.06rem; flex: none; color: var(--amber-deep); }
.lrow-stars svg.icon { width: 0.8rem; height: 0.8rem; }
.lrow-reopen {
  font-family: var(--type); font-size: var(--fs-micro); font-weight: 700; letter-spacing: 0.05em;
  padding: 0.22rem 0.5rem; cursor: pointer; color: #f2ecda; background: var(--stamp-red);
  border: 1px solid rgba(0,0,0,0.25); border-radius: 0.14rem; flex: none;
}
.lrow-reopen.poor { opacity: 0.45; cursor: default; }
/* the cost glyph inside a reopen button (star + "2", or the ad reel) */
.reopen-cost { display: inline-flex; align-items: center; gap: 0.15rem; margin-left: 0.35rem; }
.reopen-cost svg.icon { width: 0.82rem; height: 0.82rem; }

/* citations wall */
.rec-cites { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; }
/* An UNEARNED citation must still be READABLE — it is the goal, and a goal you
   can't read is not a goal. It used to fade to opacity 0.45 (2.87:1 on its
   name, 2.36:1 on its line). "Unearned" is now said with the paper, the border
   and the ribbon's colour; the words stay full-strength ink. */
.cite {
  border: 1px dashed rgba(42,36,28,0.5); border-radius: 0.16rem; padding: 0.5rem;
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: 0.2rem;
  background-color: var(--paper-dim);
}
.cite.got {
  border-style: solid; border-color: var(--edge-strong); box-shadow: var(--shadow-soft);
  background-color: var(--paper);
}
.cite-ribbon { width: 1.6rem; height: 1.6rem; color: var(--ink-dim); }
.cite.got .cite-ribbon { color: var(--amber-deep); }
.cite-ribbon svg.icon { width: 100%; height: 100%; }
.cite-name { font-family: var(--type); font-size: var(--fs-label); font-weight: 700; color: var(--ink-strong); letter-spacing: 0.04em; }
.cite-line { font-family: var(--type); font-weight: 700; font-size: var(--fs-body); color: var(--ink-dim); }

/* verdict-card stamps + the promotion order inset (wins only) */
.vstamps { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; margin: 0.6rem 0 0.2rem; }
.vstamp {
  display: flex; align-items: center; gap: 0.35rem;
  font-family: var(--type); font-size: var(--fs-body); font-weight: 700; letter-spacing: 0.05em;
  border: 2px solid currentColor; border-radius: 0.16rem; padding: 0.22rem 0.55rem;
  transform: rotate(-3deg); color: var(--amber-ink);
}
/* NB the citation stamp's class is `citation`, NOT `cite`: the citations wall
   uses `.cite { opacity: 0.45 }` to fade UNEARNED awards, and a `.vstamp.cite`
   silently inherited it — the card drew a citation you had just won as though
   you never had. Contrast tokens can't catch that; only the rendered opacity
   can. Never reuse `cite` for anything that isn't a wall tile. */
.vstamp svg.icon { width: 1.15rem; height: 1.15rem; flex: none; }
.vstamp.training { color: var(--ink-dim); }
.vstamp.reopened { color: var(--ink); }
.vstamp.record { border-style: dashed; transform: rotate(2deg); }
.promo {
  display: flex; align-items: center; gap: 0.7rem; margin: 0.55rem auto 0.2rem;
  border: 1px solid var(--edge-strong); border-radius: 0.16rem; padding: 0.55rem 0.8rem;
  background-color: var(--paper); background-image: var(--tex-paper); background-size: 200px;
  box-shadow: var(--shadow-soft); max-width: 20rem; text-align: left;
}
.promo-seal { width: 2.2rem; height: 2.2rem; color: var(--stamp-red); flex: none; }
.promo-seal svg.icon { width: 100%; height: 100%; }
.promo-order { font-family: var(--type); font-weight: 700; font-size: var(--fs-label); letter-spacing: 0.12em; color: var(--ink-dim); }
.promo-rank { font-family: var(--type); font-size: var(--fs-title); font-weight: 700; color: var(--ink-strong); letter-spacing: 0.04em; }
.promo-signed { font-family: var(--type); font-weight: 700; font-size: var(--fs-body); color: var(--ink-dim); font-style: italic; }

/* THE HARD DOCKET — basement stock. Difficulty lives on the PAPER: the aged
   texture, the cold-case stamp, the corner mark. The 3D scene never changes. */
.aged { background-image: var(--tex-paper-aged) !important; }
.panel.result { position: relative; } /* anchors the docket corner mark */
/* TWO-COLUMN VERDICT (was one tall stack): the verdict column (glyph, stars,
   stamps, the killer's face) beside the consequences column (promotion,
   citations, daily/rival, the story epilogue). A plain career win has no
   consequences column and stays the normal narrow, centered card. */
.panel.result.two-col { width: min(46rem, 94vw); }
.result-cols { display: flex; gap: 1.6rem; align-items: flex-start; justify-content: center; }
.result-cols.single { display: block; } /* one column: the old centered stack */
.rcol { display: flex; flex-direction: column; align-items: center; gap: 0.7rem; min-width: 0; }
.rcol.rverdict { flex: 0 1 auto; }
.rcol.rrecord { flex: 1 1 0; align-items: stretch; text-align: left; padding-top: 0.4rem; }
/* the record column's blocks already carry their own top/bottom margins; zero
   the column gap's double-spacing and let each block breathe on its own */
.rcol.rrecord > * { margin-top: 0; }
.rcol.rrecord .promo, .rcol.rrecord .epilogue { margin-left: 0; margin-right: 0; }
/* was @media (max-width: 640px) { } — converted to body.gw640 (JS sets it from GAME dims so force-landscape rotation works; see main._updateLayoutClasses) */
 body.gw640 .result-cols{ flex-direction: column; align-items: center; } body.gw640 .rcol.rrecord{ align-items: center; text-align: center; }


.docket-mark {
  position: absolute; top: 0.55rem; right: 0.7rem;
  font-family: var(--type); font-size: var(--fs-micro); font-weight: 700; letter-spacing: 0.14em;
  color: var(--stamp-red-ink); border: 1.6px solid currentColor; border-radius: 0.12rem;
  padding: 0.06rem 0.3rem; transform: rotate(6deg);
}
.coldstamp {
  font-family: var(--type); font-size: var(--fs-lead); font-weight: 700; letter-spacing: 0.18em;
  color: var(--stamp-red-ink); border: 2.2px solid currentColor; border-radius: 0.14rem;
  padding: 0.2rem 0.8rem; width: fit-content; margin: 0 auto 0.6rem;
  transform: rotate(-5deg);
}
.ordersmemo { width: min(32rem, 94vw); position: relative; }
.order-row {
  display: flex; gap: 0.6rem; align-items: flex-start; text-align: left;
  border: 1px dashed rgba(42,36,28,0.35); border-radius: 0.14rem;
  padding: 0.5rem 0.6rem; margin: 0.4rem 0; cursor: pointer;
}
.order-row.on { border-style: solid; border-color: var(--edge-strong); box-shadow: var(--shadow-soft); }
/* a BLOCKED order still has to be readable: you are deciding between orders,
   and you cannot weigh one you can't read. It was opacity 0.4 (2.39:1 on its
   name). The block is said by the paper and the dead checkbox, not by fading
   the words. */
.order-row.blocked { background-color: var(--paper-dim); cursor: default; }
.order-row.blocked .order-check { border-color: var(--ink-dim); background: rgba(42,36,28,0.1); }
.order-check {
  width: 1.15rem; height: 1.15rem; flex: none; margin-top: 0.1rem;
  border: 1.6px solid var(--ink); border-radius: 0.1rem; color: var(--stamp-red);
}
.order-check svg.icon { width: 100%; height: 100%; opacity: 0; }
.order-row.on .order-check svg.icon { opacity: 1; }
.order-name { font-family: var(--type); font-size: var(--fs-body); font-weight: 700; color: var(--ink-strong); letter-spacing: 0.04em; }
.order-line { font-family: var(--type); font-weight: 700; font-size: var(--fs-body); color: var(--ink-dim); }
.order-tally { font-family: var(--type); font-weight: 700; font-size: var(--fs-label); color: var(--amber-ink); }
.ordersmemo .menu-btn { width: 100%; margin-top: 0.35rem; }
.docket-pull { margin-top: 0.45rem; font-size: var(--fs-label); letter-spacing: 0.08em; font-weight: 700; }

/* the rival's verdict on the daily card — typed word in a drawn stamp frame */
.rival-strip {
  display: flex; align-items: center; justify-content: center; gap: 0.5rem;
  font-family: var(--type); font-weight: 700; letter-spacing: 0.06em; margin: 0.4rem auto 0.1rem;
  border: 1.8px solid currentColor; border-radius: 0.16rem; padding: 0.22rem 0.7rem;
  transform: rotate(-2deg); width: fit-content;
}
.rival-strip.outpaced, .rival-strip.matched { color: var(--amber-ink); }
.rival-strip.trailed { color: var(--ink-dim); }
.rival-name { font-size: var(--fs-label); font-weight: 700; }
.rival-word { font-size: var(--fs-body); font-weight: 700; }
.rival-next { font-family: var(--type); font-weight: 700; font-size: var(--fs-body); color: var(--ink-dim); font-style: italic; }

/* THE SPARROW FILES — the chapter epilogue on a winning story card. Typed
   paper, never faded: the ink law carries hierarchy by weight and size only. */
.epilogue {
  /* v1.1: the story win now folds to a single centered column, so the epilogue
     is a contained quote block (auto-centered, ~26rem) with its red margin,
     not a full-bleed left column. text stays left inside the block. */
  margin: 0.5rem auto 0.2rem; max-width: 26rem; text-align: left;
  border-left: 2.5px solid var(--stamp-red); padding-left: 0.7rem;
}
.epilogue-line {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-body);
  color: var(--ink-strong); line-height: 1.45; margin: 0 0 0.35rem;
}
.epilogue-line:last-child { margin-bottom: 0; }
.epilogue-end {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-lead);
  color: var(--stamp-red-ink); letter-spacing: 0.06em; text-transform: uppercase;
  margin-top: 0.5rem; transform: rotate(-1.5deg);
}
/* the reward line under the final stamp: quieter than the stamp, still full ink */
.epilogue-unlock {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-body);
  color: var(--ink-strong); line-height: 1.4; margin-top: 0.5rem; max-width: 34rem;
}
/* The mode picker's rows: paper with a label + sub-line, the daily's shape.
   NOT `.primary` — that is a red fill with pale text, and paper inks on it read
   as dark on dark (the readability sweep caught exactly that). */
.menu-btn.modes-row { text-align: left; }
.menu-btn.modes-row .menu-label {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-lead); color: var(--ink-strong);
}
/* a closed file is said with PAPER, never by fading the words */
.menu-btn.modes-row.spent { cursor: default; background-color: var(--paper-dim); box-shadow: none; }
.menu-btn.modes-row.spent:hover { transform: none; box-shadow: none; }

/* the verdict card's daily filing line + the COPY REPORT slip */
.daily-line {
  display: flex; align-items: center; justify-content: center; gap: 0.4rem;
  font-family: var(--type); font-size: var(--fs-body); font-weight: 700; color: var(--ink-strong);
  margin: 0.55rem 0 0.15rem;
}
.daily-line svg.icon { width: 1rem; height: 1rem; flex: none; color: var(--ink-dim); }
.copy-report { margin: 0.45rem auto 0.1rem; display: block; font-size: var(--fs-label); }

/* settings rows on paper, with a TYPED label and physical controls */
.settings { width: min(24rem, 90vw); }
.set-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.65rem 0; border-bottom: 1px dashed rgba(42,36,28,0.3); }
.set-label { font-family: var(--type); font-weight: 700; font-size: 0.95rem; color: var(--ink); }
/* custom slider: a pencil line track + a brass knob (kills the browser range) */
.set-slider { -webkit-appearance: none; appearance: none; width: 9rem; height: 1.2rem; background: transparent; cursor: pointer; }
.set-slider::-webkit-slider-runnable-track { height: 2px; background: var(--ink); border-radius: 2px; }
.set-slider::-moz-range-track { height: 2px; background: var(--ink); border-radius: 2px; }
.set-slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 1.1rem; height: 1.1rem; margin-top: -0.5rem; border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #f0d68f, var(--brass) 60%, #8a6e2a);
  border: 1px solid rgba(0,0,0,0.4); box-shadow: 0 0.1rem 0.25rem rgba(0,0,0,0.5);
}
.set-slider::-moz-range-thumb {
  width: 1.1rem; height: 1.1rem; border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #f0d68f, var(--brass) 60%, #8a6e2a);
  border: 1px solid rgba(0,0,0,0.4); box-shadow: 0 0.1rem 0.25rem rgba(0,0,0,0.5);
}
.set-toggle, .set-btn {
  font-family: var(--type); font-weight: 700; font-size: 0.85rem; padding: 0.4rem 0.9rem; min-width: 4rem; color: var(--ink);
  background-color: var(--paper); background-image: var(--tex-paper); background-size: 160px;
  border: 1px solid var(--edge-strong); border-radius: 0.14rem; cursor: pointer; box-shadow: var(--shadow-soft);
}
.set-toggle.on { color: var(--good-ink); border-color: var(--good); background-color: rgba(63,125,51,0.14); }
.set-btn:active, .set-toggle:active { transform: translateY(1px); }
/* the language picker: one chip per language, native names, current one wears
   the toggle's earned-green .on. Wraps on narrow panels. */
.lang-chips { display: flex; flex-wrap: wrap; gap: 0.3rem; justify-content: flex-end; }
.lang-chips .lang-chip { min-width: 0; padding: 0.35rem 0.6rem; }
.lang-chips .lang-chip.on { color: var(--good-ink); border-color: var(--good); background-color: rgba(63,125,51,0.14); }
/* a one-line consequence note under a settings row (ink law: real ink, small
   via the scale's label size, never faded) */
.set-note {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-label);
  color: var(--ink-dim); text-align: right; margin: -0.2rem 0 0.4rem;
}
.menu-back { margin-top: 1.1rem; }

/* lost screen — the title is a RED RUBBER STAMP slammed on the folder.
   The stamp's ink is --stamp-red-ink: this is the sentence that tells the
   player they lost, so it is READ, not decoration (the ink law). The border
   keeps the brighter --stamp-red: a rule is a shape, not text. */
.result.lost .lost-title {
  font-family: var(--type); font-size: var(--fs-head); font-weight: 700;
  color: var(--stamp-red-ink); text-transform: uppercase;
  letter-spacing: 0.05em; line-height: 1.15; margin: 0.4rem auto 1rem; padding: 0.5rem 1rem; max-width: 22ch;
  border: 3px double var(--stamp-red); border-radius: 0.2rem; transform: rotate(-4deg);
}
.result.lost .lost-sub { font-family: var(--type); font-weight: 700; font-size: var(--fs-body); color: var(--ink-dim); margin: 0.6rem 0 1rem; }

/* one-time red danger flash when the clock nears empty */
.vignette { position: fixed; inset: 0; pointer-events: none; opacity: 0; z-index: 8; box-shadow: inset 0 0 9rem 3rem rgba(224,85,107,0.6); }
.vignette.flash { animation: dangerflash 0.75s ease-out; }
@keyframes dangerflash { 0% { opacity: 0; } 30% { opacity: 1; } 100% { opacity: 0; } }

/* ---------- rails ---------- */
.rail {
  position: absolute; top: 4.6rem; bottom: 4.6rem; width: min(19rem, 30vw); display: flex; flex-direction: column;
  overflow: hidden;
}
/* LEFT rail — a loose STACK of mugshot cards; no panel, the cards ARE the objects */
.rail.left { left: 1rem; background: none; border: none; box-shadow: none; }
/* RIGHT rail — a worn NOTEBOOK PAGE: paper + faint ruled lines, tilted slightly */
.rail.right {
  right: 1rem; color: var(--ink); overflow: hidden;
  background-color: var(--paper);
  background-image: var(--tex-paper),
    repeating-linear-gradient(transparent 0 1.55rem, rgba(58,90,143,0.12) 1.55rem 1.62rem);
  background-size: 240px, auto;
  border: 1px solid var(--edge-strong); border-radius: 0.16rem;
  box-shadow: var(--shadow); transform: rotate(0.5deg);
}
.rail-head {
  font-family: var(--type); font-weight: 700; font-size: 0.8rem; letter-spacing: 0.1em; color: var(--ink);
  padding: 0.8rem 1rem 0.56rem; border-bottom: 1px solid var(--edge-strong);
  display: flex; justify-content: space-between; align-items: baseline;
}
/* the left header is its own little paper tab pinned above the stack */
.rail.left .rail-head {
  align-self: flex-start; margin: 0 0 0.4rem 0.15rem; padding: 0.34rem 0.7rem; border: 1px solid var(--edge-strong);
  border-radius: 0.2rem; box-shadow: var(--shadow-soft);
  background-color: var(--paper); background-image: var(--tex-paper); background-size: 200px;
}
.rail-sub { font-size: 0.68rem; letter-spacing: 0.12em; color: var(--ink-dim); padding: 0.75rem 1rem 0.4rem; }
.cand { font-family: var(--type); font-weight: 700; font-size: 0.7rem; letter-spacing: 0.04em; color: var(--ink-dim); }
.cand.one { color: var(--stamp-red); animation: pulse 1.4s ease-in-out infinite; }
@keyframes pulse { 50% { opacity: 0.5; } }

/* ---------- suspect cards = a loose stack of mugshot index cards ---------- */
.cards { overflow-y: auto; padding: 0.3rem 0.35rem 0.6rem; display: flex; flex-direction: column; gap: 0.5rem; }

/* NOIR SCROLLBARS — the case file has no plastic OS scrollbars. A slim aged-
   brass rail with a rounded thumb: it reads on the DARK scene behind the left
   rail (.cards) and on the manila panels (.clues, .panel) alike. Track stays
   invisible; the thumb is inset via a transparent border + padding-box clip. */
.cards, .clues, .panel {
  scrollbar-width: thin;                                  /* Firefox */
  scrollbar-color: rgba(122,100,64,0.75) transparent;
}
.cards::-webkit-scrollbar, .clues::-webkit-scrollbar, .panel::-webkit-scrollbar {
  width: 0.62rem; height: 0.62rem;
}
.cards::-webkit-scrollbar-track, .clues::-webkit-scrollbar-track, .panel::-webkit-scrollbar-track {
  background: transparent;
}
.cards::-webkit-scrollbar-thumb, .clues::-webkit-scrollbar-thumb, .panel::-webkit-scrollbar-thumb {
  background: rgba(122,100,64,0.75); border-radius: 0.5rem;
  border: 0.15rem solid transparent; background-clip: padding-box;
}
.cards::-webkit-scrollbar-thumb:hover, .clues::-webkit-scrollbar-thumb:hover, .panel::-webkit-scrollbar-thumb:hover {
  background: rgba(122,100,64,0.95); background-clip: padding-box;
}
.cards::-webkit-scrollbar-corner, .clues::-webkit-scrollbar-corner, .panel::-webkit-scrollbar-corner {
  background: transparent;
}
.card {
  display: flex; gap: 0.7rem; padding: 0.5rem; cursor: pointer;
  color: var(--ink); border: 1px solid var(--edge-strong); border-radius: 0.14rem;
  background-color: var(--card); background-image: var(--tex-card); background-size: 200px;
  box-shadow: var(--shadow-soft);
  transition: transform 0.14s ease, box-shadow 0.14s ease;
  /* the card is now a <button> (natively tabbable): strip the UA button
     appearance so the pixels are identical, keep the text left-aligned and the
     font inherited (a button would otherwise center + shrink its label) */
  font: inherit; text-align: left; width: 100%; -webkit-appearance: none; appearance: none;
}
/* each card sits at a slightly different angle so the stack looks handled */
.card:nth-child(odd) { transform: rotate(-0.9deg); }
.card:nth-child(even) { transform: rotate(0.8deg); }
.card:nth-child(3n) { transform: rotate(0.4deg); }
.card:hover { transform: rotate(0deg) translateY(-2px); box-shadow: var(--shadow); }
/* focused = the card PULLED FORWARD onto the desk (straight, lifted, deep shadow) */
.card.focused { transform: rotate(0deg) scale(1.03); box-shadow: 0 0.7rem 1.6rem rgba(0,0,0,0.55); border-color: var(--amber); }
.card.prime { border-color: var(--good); box-shadow: var(--shadow-soft), 0 0 0.9rem rgba(63,125,51,0.35); }
.card.eliminated { opacity: 0.5; filter: sepia(0.3); }
.card.eliminated .portrait { filter: grayscale(0.9) brightness(0.85); }
.card .portrait { width: 3.4rem; height: 3.82rem; border-radius: 0.1rem; background: #0d1017; flex-shrink: 0; box-shadow: 0 0.1rem 0.25rem rgba(0,0,0,0.4); }
.cinfo { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.19rem; }
.cname {
  font-family: var(--type); font-size: var(--fs-body); color: var(--ink-strong); font-weight: 700;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.card.marked .cname { text-decoration: line-through; color: var(--ink-dim); }
.crole { font-family: var(--type); font-weight: 700; font-size: var(--fs-label); color: var(--ink-dim); text-transform: capitalize; }
.cells { display: flex; gap: 0.25rem; margin-top: 0.19rem; flex-wrap: wrap; }
/* trait cells: small ink-outlined boxes drawn ON the card */
.cell {
  width: 1.375rem; height: 1.375rem; border-radius: 0.14rem; display: grid; place-items: center;
  border: 1px solid var(--edge-strong); background: rgba(255,255,255,0.35); font-size: 0.75rem; color: var(--ink);
}
.cell.yes { border-color: var(--good); background: rgba(63,125,51,0.16); }
.cell.no { border-color: var(--bad); background: rgba(178,58,46,0.16); }
.cell.unk { opacity: 0.4; }
.cstatus { font-family: var(--type); font-weight: 700; font-size: 0.64rem; letter-spacing: 0.04em; color: var(--ink-dim); margin-top: 0.12rem; }
.card.prime .cstatus { color: var(--good); font-weight: 600; }
.card.eliminated .cstatus { color: var(--bad); }

/* trait icons */
.ticon { font-size: 0.81rem; line-height: 1; }
.cell-ic { font-size: 0.75rem; }
.swatch { width: 0.81rem; height: 0.81rem; border-radius: 0.14rem; display: inline-block; border: 1px solid rgba(42,36,28,0.4); }
/* THE COAT CHIP — swatch + name. The name is the ONLY reason the coat clue is
   playable for a colourblind player (every other dim has a drawn icon whose
   shape carries it; the coat was pure hue). It is real ink, never faded. */
.coat-chip { display: inline-flex; align-items: center; gap: 0.28rem; min-width: 0; }
.coat-name {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-micro);
  color: var(--ink-strong); letter-spacing: 0.02em; line-height: 1;
  text-transform: capitalize; white-space: nowrap;
}

/* ---------- profile + clues ---------- */
.profile { padding: 0.5rem 0.75rem; display: flex; flex-direction: column; gap: 0.375rem; }
.pchip {
  display: flex; align-items: center; gap: 0.56rem; padding: 0.44rem 0.625rem; border-radius: 0.5rem;
  background: rgba(246,201,68,0.07); border: 1px solid rgba(246,201,68,0.22);
}
.pchip .pl { font-size: 0.81rem; color: var(--ink); }
.pchip .swatch { width: 0.94rem; height: 0.94rem; }
.clues { overflow-y: auto; padding: 0.25rem 0.75rem 0.5rem; display: flex; flex-direction: column; gap: 0.5rem; }
.clue { display: flex; gap: 0.56rem; align-items: flex-start; }
.cno {
  flex-shrink: 0; width: 1.19rem; height: 1.19rem; border-radius: 50%; background: var(--amber); color: #201800;
  font-size: 0.69rem; font-weight: 700; display: grid; place-items: center; margin-top: 0.06rem;
}
.ct { font-family: var(--serif); font-size: 0.81rem; line-height: 1.42; color: var(--ink-dim); font-style: italic; }
.tip { margin-top: auto; padding: 0.75rem 0.875rem; font-size: 0.72rem; line-height: 1.5; color: var(--ink-dim); border-top: 1px solid var(--edge); }

/* ---------- inspect panel = a mugshot card pulled onto the desk ---------- */
/* the desk stack: the typed case-file line sits above the pulled-out mugshot
   card, one column, so the two can never collide at any interface scale */
.deskstack {
  position: absolute; left: 50%; bottom: 1.25rem; transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 0.55rem;
  width: min(42rem, 94vw); z-index: 12; pointer-events: none;
}
.inspect {
  /* WIDER than the 42rem deskstack, centered — so the panel runs HORIZONTAL
     (evidence|interview side by side) instead of a tall stack that ate the map */
  position: relative; transform: rotate(-0.5deg); width: min(52rem, 96vw); color: var(--ink);
  background-color: var(--card); background-image: var(--tex-card); background-size: 240px;
  border: 1px solid var(--edge-strong); border-radius: 0.16rem; box-shadow: var(--shadow);
  padding: 1.1rem; pointer-events: auto;
}
.inspect.hidden { display: none; }
/* the two-column body: LEFT = what you can SEE (evidence + notes), RIGHT = what
   you HEAR (the interview ledger + verbs). Head + action buttons span full width. */
.ibody { display: flex; gap: 1.1rem; align-items: flex-start; margin: 0.2rem 0 0.1rem; }
.ibody.single { display: block; }
/* an un-questioned suspect has no interview column — narrow the panel so its
   short content isn't marooned in a wide sheet (the wide layout is only earned
   once there's an interview to sit beside the evidence). Toggled in hud.js
   (_renderInspect) rather than :has(), which the preview Chromium ignores. */
.inspect.solo { width: min(34rem, 94vw); }
.icol { min-width: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.icol.ileft { flex: 1 1 42%; }
.icol.iright { flex: 1 1 58%; }
.icol .compare, .icol .notes, .icol .statements { margin-top: 0; margin-bottom: 0; }
.icol .compare { flex-wrap: wrap; }
/* was @media (max-width: 720px) { } — converted to body.gw720 (JS sets it from GAME dims so force-landscape rotation works; see main._updateLayoutClasses) */
 body.gw720 .inspect{ width: 100%; } body.gw720 .ibody{ flex-direction: column; }



/* one typed sentence on a paper slip — the detective writing up the case.
   The slip is CLICKABLE: click finishes the line, click again moves on. */
/* THE most-read surface in the game — every clue, note and beat lands here, so
   it gets the room. (It was capped at 30rem inside a 34rem stack and set in
   --fs-lead while the whole scale sat under 1rem: the primary text was smaller
   than the root font on a mostly-empty screen.) */
.typeline {
  position: relative; width: 100%; max-width: 42rem; text-align: left;
  pointer-events: auto; cursor: pointer;
  background-color: var(--paper); background-image: var(--tex-paper); background-size: 220px;
  border: 1px solid var(--edge-strong); border-radius: 0.16rem; box-shadow: var(--shadow);
  padding: 0.85rem 2.1rem 0.8rem 1.15rem; transform: rotate(-0.4deg);
  font-family: var(--type); font-size: var(--fs-title); line-height: 1.5;
  color: var(--ink-strong); font-weight: 700; /* see --ink-strong: this face reads light */
  opacity: 0; translate: 0 0.6rem; transition: opacity 0.28s ease, translate 0.28s ease;
}
.typeline.show { opacity: 1; translate: 0 0; }
.typeline.hidden { display: none; }
/* "there's more — click on" nib, only once the line has finished typing */
.tl-next {
  position: absolute; right: 0.5rem; bottom: 0.38rem; display: none;
  color: var(--ink-dim); line-height: 1;
}
.tl-next svg.icon { font-size: 0.9rem; transform: rotate(-90deg); }
.typeline.done .tl-next { display: block; animation: nextnudge 1.1s ease-in-out infinite; }
@keyframes nextnudge { 50% { transform: translateX(0.16rem); opacity: 0.55; } }
.typeline .hl, .note-row .hl { color: var(--amber-deep); font-weight: 700; font-style: normal; }
.tl-speaker { font-size: var(--fs-micro); letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-dim); margin-bottom: 0.15rem; }
.tl-speaker:empty { display: none; }
.tl-caret { display: inline-block; width: 0.48em; height: 0.95em; margin-left: 0.06em; background: var(--ink); vertical-align: -0.12em; animation: caretblink 0.9s steps(1) infinite; }
@keyframes caretblink { 50% { opacity: 0; } }

/* the detective's notes on a suspect — written as each tell was witnessed */
.notes { display: flex; flex-direction: column; margin: 0.55rem 0 0.1rem; text-align: left; }
.note-row { display: flex; gap: 0.5rem; align-items: center; padding: 0.22rem 0.1rem; border-bottom: 1px dashed rgba(42,36,28,0.22); font-family: var(--type); font-size: var(--fs-body); color: var(--ink-strong); font-weight: 700; }
.note-row:last-child { border-bottom: none; }
.note-row svg.icon { font-size: 1rem; flex-shrink: 0; }
.note-row.new { animation: capMine 0.35s ease both; }

/* what they SAID next to what you SAW. Both cells look identical whether they
   agree or contradict — putting the two facts side by side is legibility;
   deciding what it means is the player's job, never the game's. */
.statements { display: flex; flex-direction: column; gap: 0.45rem; margin: 0.55rem 0 0.1rem; text-align: left; }
.stmt-row { display: flex; gap: 0.5rem; align-items: stretch; flex-wrap: wrap; }
/* two chips per row, each filling half — SAID | SEEN. Icons are big, the value
   is NAMED in words (the coat-clue idiom): no more guessing a tiny glyph. */
.stmt-cell {
  flex: 1 1 45%; min-width: 8.5rem;
  display: flex; align-items: center; gap: 0.5rem; padding: 0.45rem 0.6rem;
  background: rgba(255,255,255,0.32); border: 1px solid var(--edge-strong); border-radius: 0.16rem;
}
.sc-tag { font-family: var(--type); font-weight: 700; font-size: var(--fs-micro); letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-dim); flex-shrink: 0; }
.stmt-cell svg.icon { font-size: 1.7rem; flex-shrink: 0; }
.sc-val { font-family: var(--type); font-weight: 700; font-size: var(--fs-body); color: var(--ink-strong); line-height: 1.2; }
.stmt-cell .swatch { width: 1.5rem; height: 1.5rem; border-radius: 0.16rem; flex-shrink: 0; }
.stmt-cell.watch { color: var(--blue); }
.stmt-cell.watch .sc-val { color: var(--blue); }
.stmt-cell.watch svg.icon { animation: watchblink 1.6s ease-in-out infinite; }
/* a cross row names the OTHER party — the surname spans the full width above
   its two chips */
.stmt-row.cross .stmt-who { flex-basis: 100%; }
.stmt-text { font-family: var(--type); font-size: var(--fs-body); line-height: 1.5; color: var(--ink-strong); font-weight: 700; }
@keyframes watchblink { 50% { opacity: 0.35; } }

/* THE INTERVIEW LEDGER (THE DRAGNET). A cross-claim row carries the OTHER
   party's surname (lookalike faces are ambiguous by design, so the name is the
   identity). The claim/seen chips are the same .stmt-cell pair as a self-claim
   — law 8: the panel never marks a claim true or false, it puts two facts side
   by side. */
.interview.heard { margin-top: 0.5rem; border-top: 1px solid var(--edge-strong); padding-top: 0.5rem; }
.heard-title { font-family: var(--type); font-weight: 700; font-size: var(--fs-label); color: var(--ink-dim); margin-bottom: 0.3rem; }
.stmt-row.cross { gap: 0.4rem; }
.stmt-who { display: flex; align-items: center; min-width: 4.2rem; }
.stmt-who .sn { font-family: var(--type); font-weight: 700; font-size: var(--fs-label); color: var(--ink); }
/* the escalation verbs — paper WORD buttons inside the ledger (law 6a: the
   typewriter face needs weight 700). At most one shows at a time. */
.iverbs { display: flex; gap: 0.5rem; margin: 0.5rem 0 0.1rem; }
.wbtn {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-label);
  letter-spacing: 0.04em; color: var(--ink); background: rgba(255,255,255,0.32);
  border: 1.5px solid var(--edge-strong); border-radius: 0.14rem;
  padding: 0.5rem 0.9rem; cursor: pointer; transition: background 0.12s, transform 0.08s;
}
.wbtn:hover { background: rgba(255,255,255,0.55); }
.wbtn:active { transform: translateY(1px); }

/* a word hangs over them: someone the player interviewed named this suspect.
   The quote mark mirrored + hollow, in the free corner (qmark/ink-x/pin take
   the others). Show/hide by display, never opacity (law 6c). */
.heardmark {
  position: absolute; right: -0.35rem; bottom: -0.35rem; display: none; place-items: center;
  color: var(--paper); filter: drop-shadow(0 0.1rem 0.3rem rgba(0,0,0,0.8));
  transform: scaleX(-1);
}
.heardmark svg.icon { font-size: 0.95rem; }
.card.named .heardmark { display: grid; }

/* they've given a statement — a corner mark on the mugshot (same show/hide
   idiom as the ink ✗ and the hunch pin: display, not opacity) */
.qmark {
  position: absolute; left: -0.35rem; bottom: -0.35rem; display: none; place-items: center;
  color: var(--paper); filter: drop-shadow(0 0.1rem 0.3rem rgba(0,0,0,0.8));
}
.qmark svg.icon { font-size: 1.05rem; }
.card.questioned .qmark { display: grid; }
/* the QUESTION button — sized to match its neighbours in .iacts (they each get
   an explicit 1.8rem; without one this fell back to the 0.84rem base and drew
   at half their size). Its bubble fills more of the viewBox than the ✗/pin do,
   so the glyph itself is dialled back like the gavel's is. */
.iacts .ibtn.iask { font-size: 1.8rem; padding: 0.8rem; }
.ibtn.iask svg.icon { font-size: 1.45rem; }
.ibtn.iask { border-color: rgba(42,36,28,0.3); color: var(--ink-dim); }
.ibtn.iask:hover { color: var(--ink); background: rgba(42,36,28,0.08); }
.ibtn.iask.on { color: var(--ink); border-color: var(--ink-dim); background: rgba(42,36,28,0.12); }

/* one-time lesson card: a paper slip taped over the scene. Positioned per
   lesson so it never covers the thing it's explaining. */
.lesson {
  position: absolute; left: 50%; top: 34%; transform: translate(-50%, -50%) rotate(-0.6deg);
  width: min(24rem, 88vw); z-index: 28; /* above .hint (25), below .modal-layer (30) */
  background-color: var(--paper); background-image: var(--tex-paper); background-size: 220px;
  border: 1px solid var(--edge-strong); border-radius: 0.16rem; box-shadow: var(--shadow);
  padding: 1.05rem 1.15rem 0.9rem; color: var(--ink); font-family: var(--type); font-weight: 700;
  display: flex; flex-direction: column; align-items: center; gap: 0.65rem; text-align: center;
  animation: lessonIn 0.28s ease-out;
}
.lesson::before { /* a strip of tape */
  content: ''; position: absolute; top: -0.45rem; left: 50%;
  transform: translateX(-50%) rotate(-2deg); width: 3.2rem; height: 0.9rem;
  background: rgba(214,196,148,0.6); border: 1px solid rgba(42,36,28,0.18);
}
.lesson.hidden { display: none; }
.lesson .lesson-icon { color: var(--ink); }
.lesson .lesson-icon svg.icon { font-size: 2.1rem; }
/* Special Elite is a DISTRESSED face: at normal weight its broken strokes read
   washed-out on paper even at near-black. Body copy on paper gets the darkest
   ink + a heavier strike (a typewriter key pressed hard), never a light tint. */
.lesson .lesson-text { font-size: var(--fs-lead); line-height: 1.55; max-width: 36ch; color: var(--ink-strong); font-weight: 700; }
.lesson .lesson-text .hl { color: var(--amber-deep); }
.lesson .lesson-ok { padding: 0.5rem 1.7rem; font-size: 0.85rem; }
.lesson.danger { border-color: var(--stamp-red); }
.lesson.danger .lesson-icon { color: var(--stamp-red); }
.lesson.pos-bottom { top: auto; bottom: 5rem; transform: translateX(-50%) rotate(0.5deg); }
.lesson.pos-left { left: 27%; }
.lesson.pos-right { left: 73%; }
@keyframes lessonIn { from { opacity: 0; transform: translate(-50%, -46%) rotate(-0.6deg); } }
/* the stakes on the arrest confirm — stated every time, not just once */
.confirm-note { font-family: var(--type); font-size: var(--fs-body); color: var(--ink-strong); font-weight: 700; margin-bottom: 0.9rem; text-align: center; }
.confirm.unsure .confirm-note { color: var(--amber-deep); }
/* how-to-play: a caption under each pictogram */
.tcap { font-family: var(--type); font-size: var(--fs-label); color: var(--ink-strong); font-weight: 700; letter-spacing: 0.03em; margin-top: 0.4rem; }
/* first run: a single, unmissable Play */
.menu-btn.primary.solo { font-size: 1.15rem; padding: 0.9rem 1rem; }
/* was @media (max-width: 820px) { } — converted to body.gw820 (JS sets it from GAME dims so force-landscape rotation works; see main._updateLayoutClasses) */
 body.gw820 .lesson.pos-left, body.gw820 .lesson.pos-right{ left: 50%; } 

@media (prefers-reduced-motion: reduce) {
  .lesson { animation: none; }
  .typeline { transition: none; }
  .tl-next, .typeline.done .tl-next { animation: none; }
}
.ihead { display: flex; gap: 0.9rem; align-items: center; position: relative; margin-bottom: 0.5rem; }
.iportrait { width: 4.6rem; height: 5.05rem; border-radius: 0.1rem; background: #0d1017; box-shadow: 0 0.12rem 0.3rem rgba(0,0,0,0.4); }
.iwho { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.iname { font-family: var(--type); font-size: var(--fs-title); color: var(--ink-strong); font-weight: 700; }
.irole { font-family: var(--type); font-weight: 700; font-size: var(--fs-label); color: var(--ink-dim); text-transform: capitalize; }
.iclose { position: absolute; top: 0.55rem; right: 0.7rem; z-index: 2; background: none; border: none; color: var(--ink-dim); font-size: 1.375rem; cursor: pointer; line-height: 1; }
.iclose:hover { color: var(--ink); }
.obs { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0.44rem; margin: 0.8rem 0; }
.orow { background: rgba(255,255,255,0.3); border: 1px solid var(--edge-strong); border-radius: 0.14rem; padding: 0.44rem 0.56rem; }
.olabel { display: block; font-family: var(--type); font-weight: 700; font-size: 0.63rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-dim); margin-bottom: 0.19rem; }
.oval { font-size: 0.81rem; display: flex; align-items: center; gap: 0.31rem; }
.oval.unk { color: var(--ink-dim); font-style: italic; font-size: 0.72rem; }
.iacts { display: flex; gap: 0.56rem; }
/* action buttons = ink-stamped squares on the card */
.ibtn {
  flex: 1; padding: 0.7rem; border-radius: 0.14rem; border: 1.5px solid var(--edge-strong); color: var(--ink);
  background: rgba(255,255,255,0.28); font-family: var(--type); font-weight: 700; font-size: 0.84rem; cursor: pointer;
  transition: background 0.12s, transform 0.08s;
}
.ibtn:hover { background: rgba(255,255,255,0.5); }
.ibtn:active { transform: translateY(1px); }
.ibtn:disabled { opacity: 0.5; cursor: default; }
.ibtn.danger { border-color: var(--stamp-red); color: var(--stamp-red); background: rgba(178,58,46,0.1); }
.ibtn.danger:hover { background: var(--stamp-red); color: #f2ecda; }

/* ---------- camera controls = small paper tabs ---------- */
.controls { position: absolute; right: 1rem; bottom: 1rem; display: flex; gap: 0.375rem; }
.cbtn {
  width: 2.5rem; height: 2.5rem; border-radius: 0.2rem; color: var(--ink); font-size: 1.06rem; cursor: pointer;
  background-color: var(--paper); background-image: var(--tex-paper); background-size: 200px;
  border: 1px solid var(--edge-strong); box-shadow: var(--shadow-soft); transition: transform 0.08s, box-shadow 0.08s;
}
.cbtn:hover { transform: translateY(-1px); }
.cbtn:active { transform: translateY(1px); box-shadow: 0 0.08rem 0.18rem rgba(0,0,0,0.35); }

/* ---------- toasts = paper slips ---------- */
.toasts { position: absolute; bottom: 4.6rem; left: 50%; transform: translateX(-50%); display: flex; flex-direction: column; gap: 0.5rem; align-items: center; }
.toast {
  color: var(--ink); padding: 0.56rem 1rem; font-family: var(--type); font-weight: 700;
  background-color: var(--paper); background-image: var(--tex-paper); background-size: 200px;
  border: 1px solid var(--edge-strong); border-radius: 0.16rem;
  font-size: 0.81rem; box-shadow: var(--shadow); opacity: 0; transform: translateY(0.6rem);
  transition: opacity 0.3s, transform 0.3s; white-space: nowrap;
}
.toast.show { opacity: 1; transform: translateY(0); }
.toast.good { border-color: var(--good); }
.toast.warn { border-color: var(--amber); color: var(--amber); }

/* ---------- modal = a case file tossed on the desk ---------- */
.modal-layer { position: absolute; inset: 0; z-index: 30; display: grid; place-items: center;
  background: radial-gradient(circle at 50% 40%, rgba(20,14,8,0.72), rgba(6,4,2,0.86)); padding: 1rem; }
.modal-layer.hidden { display: none; }

/* THE ROOFTOP VARIANTS (set only via hud._openModal — the one owner).
   vtitle = the title rail over the LIVE vignette: no radial scrim (it would
   bury the scene the menu exists to show); a left gradient keeps the rail's
   paper readable and dissolves before the detective. vsub = a title-family
   sheet (settings/help/ledger/records/mode picker/academy): centered panel
   over a LIGHTER scrim so the scene stays visible and animating around it. */
.modal-layer.vtitle {
  place-items: stretch start; padding: 0;
  background: linear-gradient(90deg, rgba(6,4,2,0.55) 0, rgba(6,4,2,0.26) 24rem, rgba(6,4,2,0) 38rem);
}
.modal-layer.vsub {
  background: radial-gradient(circle at 50% 40%, rgba(20,14,8,0.42), rgba(6,4,2,0.60));
}
/* the rail: a full-height strip of case-file paper pinned to the left edge —
   not a tossed folder, so no rotation and no folder tab.
   CENTERING IS MARGIN-AUTO, NEVER justify-content: center — on a flex column
   that overflows (big UI scale x short window), justify-center clips the TOP
   irrecoverably and .panel's overflow-y grows a scrollbar over half a menu.
   Margin-auto centers when it fits and scrolls cleanly from the top when it
   can't. Vertical padding breathes with the viewport for the same reason. */
.panel.title-rail {
  width: min(23rem, 92vw); max-height: none; height: 100%;
  transform: none; border-radius: 0;
  border: none; border-right: 1px solid rgba(42,36,28,0.5);
  /* the AGED stock, not fresh manila: a full-height column of bright paper
     glared against the night scene ("a light side bar with a dark background
     ... huge contrast"); the deeper aged tone sits with the noir grade */
  background-image: var(--tex-paper-aged);
  /* the folder sits OVER the scene, so it casts onto it — without the shadow
     the rail is a flat hard cut splitting the screen in two */
  box-shadow: 0.45rem 0 2.4rem rgba(4, 3, 2, 0.65);
  display: flex; flex-direction: column;
  padding: clamp(0.8rem, 3vh, 2rem) 1.8rem;
}
/* ONE centered stack — letterhead directly over the logo, footer directly
   under the menu, like a real document. (Pinning letterhead/footer to the
   rail's extremes left two big voids around the centered core — it shipped
   that way once.) Auto margins on the ENDS center the whole stack and still
   scroll cleanly from the top when the column overflows — never
   justify-content: center (see the overflow note above). */
.panel.title-rail > :first-child { margin-top: auto; }
.panel.title-rail > :last-child { margin-bottom: auto; }
.panel.title-rail .logo { margin-bottom: 0.45rem; }
.panel.title-rail::before { display: none; }
/* the case-file dressing: one letterhead line over a double rule, one footer
   line of ink under a single rule — both hugging the stack they belong to */
.title-rail .letterhead {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-micro);
  letter-spacing: 0.17em; color: var(--ink-dim);
  padding-bottom: 0.5rem; margin-bottom: 1.1rem;
  border-bottom: 3px double rgba(42,36,28,0.6);
}
.title-rail .rail-footer {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-label);
  color: var(--ink-dim); letter-spacing: 0.06em;
  padding-top: 0.5rem; margin-top: 1.1rem;
  border-top: 1px solid rgba(42,36,28,0.6);
}
/* the rail is one tight column, not four loose slips */
.title-rail .menu-list { gap: 0.5rem; }
.title-rail .menu-btn { padding: 0.6rem 1rem; }
/* THE SPENT DAILY IS STATUS PAPER, NOT A DEAD BUTTON: a flat memo strip on
   dim paper wearing its outcome as a rubber stamp (CLOSED / FILED) — the
   button body only belongs to a daily that can still be played */
.menu-memo {
  position: relative; text-align: left;
  font-family: var(--type); font-weight: 700;
  background-color: var(--paper-dim); background-image: var(--tex-paper); background-size: 200px;
  border: 1px solid var(--edge-strong); border-radius: 0.16rem;
  padding: 0.6rem 6.2rem 0.6rem 1rem;
}
.menu-memo .menu-label { font-size: var(--fs-body); font-weight: 700; color: var(--ink-strong); }
.memo-stamp {
  position: absolute; right: 0.7rem; top: 50%;
  transform: translateY(-50%) rotate(-7deg);
  font-family: var(--type); font-weight: 700; font-size: var(--fs-micro);
  letter-spacing: 0.14em; color: var(--stamp-red-ink);
  border: 2px solid var(--stamp-red-ink); border-radius: 0.14rem;
  padding: 0.08rem 0.4rem;
}
/* THE MENU LIST FILLS THE RAIL — never a fixed width. `.menu-list` ships at
   `min(22rem, 84vw)`, sized for the OLD 35rem centered title panel; inside the
   23rem rail (348px content box) that 396px block overflowed by 48px, and
   `.panel`'s overflow-y:auto turned it into a HORIZONTAL scrollbar that clipped
   the right padding. Fill the rail instead. overflow-x:hidden is the backstop:
   a menu rail must NEVER scroll sideways, whatever a future string measures. */
.panel.title-rail { overflow-x: hidden; }
.panel.title-rail .menu-list { width: 100%; }
/* the detective's grade, straight from the service record */
.rank-line {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-label);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-dim);
  margin: -0.2rem 0 1.4rem;
}
/* ALL gameplay chrome is inert on the title (and used to be buried by the
   heavy scrim); with the light variants it shows through — the notebook rail
   and camera controls floated over the rooftop. Hide the lot honestly.
   visibility (not display): the HUD keeps its layout for the return to play. */
body.on-title .topbar, body.on-title .sheet, body.on-title .rail, body.on-title .dock,
body.on-title .cigwrap, body.on-title .controls,
body.on-title .deskstack { visibility: hidden; }
/* PORTRAIT (phone OR tablet OR any narrow-tall window): the rail anchors to
   the BOTTOM and the vignette owns the top. The trigger is ORIENTATION, never
   a fixed px width — a px breakpoint let every portrait viewport WIDER than it
   (tablets, big phones, tall desktop windows) fall through to the full-height
   LEFT rail, which then ate ~66% of the screen. A left rail needs landscape
   room; portrait always gets the sheet. (This mirrors the vignette camera,
   which already switches on aspect >= 1.) */
/* was @media (orientation: portrait) { } — converted to body.portrait-ui (JS sets it from GAME dims so force-landscape rotation works; see main._updateLayoutClasses) */
 body.portrait-ui .modal-layer.vtitle{
    place-items: end stretch;
    background: linear-gradient(0deg, rgba(6,4,2,0.62) 0, rgba(6,4,2,0.30) 16rem, rgba(6,4,2,0) 26rem);
  } body.portrait-ui .panel.title-rail{
    /* height: auto — the sheet sizes to its content and the scene keeps the
       rest. 58vh looked tidy but the full menu never fit inside it (the root
       font is height-driven), so the sheet ALWAYS scrolled; 76vh lets every
       item show while the vignette holds the top band. */
    width: 100%; height: auto; max-height: 76vh; overflow-y: auto;
    border-right: none; border-top: 1px solid rgba(42,36,28,0.5);
    box-shadow: 0 -0.45rem 2.4rem rgba(4, 3, 2, 0.65); /* casts UP onto the scene */
    padding: 1.2rem 1.4rem 1.4rem;
  }
  /* the sheet's height is the scene's height — the letterhead is a luxury
     the phone can't afford (the footer stays: one line of ink) */ body.portrait-ui .title-rail .letterhead{ display: none; }



/* FORCE-LANDSCAPE (user-ruled, supersedes the rotate gate): a touch device in
   portrait doesn't get asked to turn — the whole BODY rotates 90° so the game
   renders landscape no matter how the phone is held. Composite mapping (origin
   top-left, translate THEN rotate): game (gx,gy) → physical (W-gy, gx), so JS
   maps pointers back with gx=py, gy=W-px (main._toGame). Every fixed layer is
   a body child, so inset:0 fills the rotated box; the renderer is sized from
   GAME dims (main._vw/_vh) and the layout classes above are set from GAME dims
   too, which is why they are classes and not media queries (media queries see
   the physical viewport and would lay the game out for the wrong axis). */
body.force-landscape {
  position: fixed; top: 0; left: 0;
  width: 100vh; height: 100vw;
  transform: rotate(90deg) translateY(-100%);
  transform-origin: top left;
  overflow: hidden;
}

/* THE ROTATE GATE — RETIRED by force-landscape (kept dormant: the element and
   S.rotate strings survive in case a platform ever forbids the rotation). */
.rotate-gate {
  position: fixed; inset: 0; z-index: 90; display: none; place-items: center;
  padding: 2rem; pointer-events: auto; /* swallow taps meant for the frozen scene */
  background: radial-gradient(120% 90% at 50% 40%, rgba(24,18,12,0.88), rgba(6,4,2,0.98));
}
body.rotate-locked .rotate-gate { display: grid; }
.rotate-card { text-align: center; max-width: 22rem; }
/* sized by font-size, not width — the base `svg.icon { width:1em }` rule wins on
   specificity otherwise and collapses it (law 2's sizing trap). */
.rotate-glyph {
  font-size: 4.4rem; color: var(--paper); display: block; margin: 0 auto 1.1rem;
  animation: rotatehint 2.6s ease-in-out infinite;
}
.rotate-title {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-title);
  color: var(--paper); letter-spacing: 0.02em; margin-bottom: 0.5rem;
}
.rotate-body {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-body);
  color: var(--paper-dim); line-height: 1.5;
}
@keyframes rotatehint { 0%, 60% { transform: rotate(0deg); } 82%, 100% { transform: rotate(-90deg); } }
/* reduce-motion drops the MOTION, keeps the STATE: the text + the static rotate
   arrow still say "turn the phone" (the reduce-motion law). */
body.reduce-motion .rotate-glyph { animation: none; }

/* the panel is a MANILA FOLDER: texture, a folder tab, a slight toss angle */
.panel {
  position: relative; width: min(35rem, 94vw); max-height: 92vh; overflow-y: auto; color: var(--ink);
  font-family: var(--type); font-weight: 700; /* typewriter cascades to all folder text */
  background-color: var(--manila); background-image: var(--tex-manila); background-size: 300px;
  border: 1px solid rgba(42,36,28,0.5); border-radius: 0.16rem; box-shadow: var(--shadow);
  padding: 2rem 2rem 1.9rem; text-align: center; transform: rotate(-0.5deg);
}
.panel::before { /* the folder tab */
  content: ''; position: absolute; top: -0.85rem; left: 1.6rem; width: 6.5rem; height: 0.9rem;
  background-color: var(--manila); background-image: var(--tex-manila); background-size: 300px;
  border: 1px solid rgba(42,36,28,0.5); border-bottom: none; border-radius: 0.2rem 0.2rem 0 0;
}
.panel .logo { font-family: var(--type); font-weight: 700; font-size: 2.7rem; letter-spacing: 0.01em; color: var(--ink); margin-bottom: 0.375rem; }
/* the ".io" is the one red WORD on the title, so it reads as ink, not as a
   fill (--stamp-red is 3.45:1 here — a shape colour wearing a word's job) */
.panel .logo span { color: var(--stamp-red-ink); }
.panel .tag { font-family: var(--type); font-weight: 700; font-size: 0.95rem; line-height: 1.6; color: var(--ink-dim); margin: 0 auto 1.1rem; max-width: 46ch; }
.panel h2 { font-family: var(--type); font-weight: 700; font-size: 1.5rem; margin-bottom: 0.75rem; }
.how { text-align: left; max-width: 44ch; margin: 0 auto 1.4rem; display: flex; flex-direction: column; gap: 0.625rem; list-style: none; counter-reset: step; }
.how li { font-family: var(--type); font-size: var(--fs-body); line-height: 1.5; color: var(--ink-strong); font-weight: 700; padding-left: 1.875rem; position: relative; }
ol.how li::before { counter-increment: step; content: counter(step); position: absolute; left: 0; top: 0; width: 1.25rem; height: 1.25rem; background: var(--amber); color: #f2ecda; border-radius: 50%; font-size: 0.75rem; font-weight: 700; display: grid; place-items: center; }
ul.how li::before { content: ''; position: absolute; left: 0.45rem; top: 0.6em; width: 0.32rem; height: 0.32rem; background: var(--amber); border-radius: 50%; }
.how b { color: var(--ink); }
.k-yes { color: var(--good); } .k-no { color: var(--bad); }
/* primary = a PAPER button with a stamped-red fill for actions */
.primary {
  font-family: var(--type); font-weight: 700; background: var(--stamp-red); color: #f2ecda; border: 1px solid rgba(0,0,0,0.25);
  padding: 0.75rem 1.75rem; border-radius: 0.16rem; box-shadow: var(--shadow-soft);
  font-size: 0.95rem; cursor: pointer; letter-spacing: 0.04em; transition: transform 0.1s, filter 0.15s;
}
.primary:hover { transform: translateY(-1px); filter: brightness(1.06); }
.primary:active { transform: translateY(1px); }
.primary.danger { background: var(--stamp-red); }
.ghost {
  font-family: var(--type); font-weight: 700; color: var(--ink); padding: 0.75rem 1.4rem; border-radius: 0.16rem; cursor: pointer;
  background-color: var(--paper); background-image: var(--tex-paper); background-size: 200px;
  border: 1px solid var(--edge-strong); box-shadow: var(--shadow-soft); font-size: 0.9rem;
}
.ghost:hover { transform: translateY(-1px); }
.brow { display: flex; gap: 0.75rem; justify-content: center; flex-wrap: wrap; }
/* portraits in modals read as clipped PHOTOGRAPHS */
.result .rportrait, .panel .rportrait {
  width: 5.75rem; height: 6.3rem; border-radius: 0.1rem; margin: 0 auto 0.875rem; display: block; background: #0d1017;
  border: 3px solid #f4ecd8; box-shadow: 0 0.3rem 0.7rem rgba(0,0,0,0.45), 0 0 0 1px rgba(42,36,28,0.3);
}
.result.win h2 { color: var(--good); }
.result.lose h2 { color: var(--bad); }

/* ---------- responsive layout shifts ---------- */

/* THE SHEET WRAPPER. On desktop it must not exist: `display: contents` removes
   it from layout entirely, so the two absolutely-positioned rails sit exactly
   where they always have. Everything below only wakes up on a narrow viewport. */
.sheet { display: contents; }
.sheet-grip, .sheet-tabs { display: none; }

/* NOTE ON THE ROOT FONT: there is deliberately NO `html { font-size }` here.
   A `clamp(13px, 2.6vw, 17px)` used to live in this block — width-driven, which
   law 7 forbids outright, and it dropped var(--ui-scale) so the A-/A+ chips did
   nothing on any phone. It also forced 13px where the height-driven law gives
   ~20px. It was compensating for a layout that did not fit; the sheet below is
   the actual fix. ENFORCED: tests/inkAudit.mjs (law 7 as code). */
/* was @media (max-width: 820px), (orientation: landscape) and (max-height: 520px) { } — converted to body.phone-ui (JS sets it from GAME dims so force-landscape rotation works; see main._updateLayoutClasses) */

  /* THE CASE FILE ON YOUR LAP. The rails used to eat 81% of a phone's width and
     bury the scene the whole game is about; now the scene owns the screen and
     the file pulls up from the bottom. Second arm: a phone in LANDSCAPE is often
     WIDER than 820px yet only ~360px TALL — it must get the sheet too, or it
     falls back to the desktop rails on a screen with no room for them. */ body.phone-ui .sheet{
    display: flex; flex-direction: column;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 6;
    height: 7.6rem;                     /* collapsed: a strip of mugshots */
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background-color: var(--paper);
    background-image: var(--tex-paper);
    background-size: 240px;
    border-top: 1px solid var(--edge-strong);
    box-shadow: 0 -0.5rem 1.4rem rgba(0,0,0,0.45);
    transition: height 180ms ease-out;
  } body.phone-ui .sheet.open{ height: min(62vh, 30rem); } body.phone-ui .sheet-grip{
    display: flex; align-items: center; justify-content: center;
    height: 1.5rem; flex: none; cursor: grab;
    touch-action: none;                 /* the drag is ours, never a page scroll */
  } body.phone-ui .grip-bar{
    width: 2.6rem; height: 0.28rem; border-radius: 0.14rem;
    background: var(--ink-dim);
  } body.phone-ui .sheet-tabs{ display: flex; gap: 0.4rem; padding: 0 0.6rem 0.4rem; flex: none; } body.phone-ui .sheet-tab{
    font-family: var(--type); font-weight: 700; font-size: var(--fs-label);
    letter-spacing: 0.08em; color: var(--ink-dim);
    background: none; border: none; border-bottom: 2px solid transparent;
    padding: 0.2rem 0.5rem; cursor: pointer;
  } body.phone-ui .sheet-tab.on{ color: var(--ink-strong); border-bottom-color: var(--stamp-red); }

  /* the rails stop being rails: they are the sheet's two pages */ body.phone-ui .rail{
    position: static; top: auto; bottom: auto; width: auto; flex: 1 1 auto;
    min-height: 0; overflow: hidden;
  } body.phone-ui .rail.right{
    /* the sheet is already paper — the notebook must not paint a second sheet */
    background-image: repeating-linear-gradient(transparent 0 1.55rem, rgba(58,90,143,0.12) 1.55rem 1.62rem);
    background-color: transparent; border: none; box-shadow: none; transform: none;
    border-radius: 0;
  }
  /* one page at a time */ body.phone-ui .rail.right{ display: none; } body.phone-ui .sheet.tab-notebook .rail.left{ display: none; } body.phone-ui .sheet.tab-notebook .rail.right{ display: flex; }

  /* COLLAPSED: the cards lie down into a horizontal strip of mugshots that the
     thumb can flick through; the head tab is redundant next to the tabs. */ body.phone-ui .rail.left .rail-head{ display: none; } body.phone-ui .cards{
    display: flex; flex-direction: row; gap: 0.5rem;
    overflow-x: auto; overflow-y: hidden;
    padding: 0 0.6rem 0.5rem;
    scroll-snap-type: x mandatory;
    touch-action: pan-x;                /* the strip scrolls sideways, only */
    -webkit-overflow-scrolling: touch;
  } body.phone-ui .card{ flex: 0 0 auto; scroll-snap-align: center; width: 8.2rem; flex-direction: column; align-items: center; text-align: center; gap: 0.375rem; } body.phone-ui .card .portrait{ width: 3.4rem; height: 3.8rem; } body.phone-ui .crole{ display: none; }
  /* COLLAPSED, the strip is MUGSHOTS: the face and the coat (the clue), nothing
     else. The name and the record are the open file's job — a 22-character name
     is 201px and spilled straight out of a 168px card, and there is only ~94px
     of card height under the grip and tabs anyway. */ body.phone-ui .sheet:not(.open) .card .cname, body.phone-ui .sheet:not(.open) .card .cells{ display: none; }
  /* ...and in the open file a long name wraps instead of overflowing */ body.phone-ui .card .cname{ max-width: 100%; overflow-wrap: anywhere; }

  /* EXPANDED: the strip becomes the stack again, and the notebook can scroll */ body.phone-ui .sheet.open .cards{
    flex-direction: column; overflow-x: hidden; overflow-y: auto;
    scroll-snap-type: none; touch-action: pan-y;
  } body.phone-ui .sheet.open .card{ width: auto; flex-direction: row; text-align: left; align-items: center; } body.phone-ui .sheet.open .crole{ display: block; } body.phone-ui .findings{ overflow-y: auto; touch-action: pan-y; } body.phone-ui .brief{ font-size: var(--fs-micro); max-width: 26ch; } body.phone-ui .obs{ grid-template-columns: 1fr 1fr; }
  /* the typed slip sits ABOVE the collapsed sheet — law 3's one text surface
     must never end up underneath it */ body.phone-ui .deskstack{ bottom: 8.2rem; width: 96vw; } body.phone-ui .typeline{ max-width: none; } body.phone-ui .controls{ bottom: 8.2rem; right: 0.5rem; }
  /* the OPEN sheet is the case file in your hands: reading, not orbiting — the
     camera cluster floated OVER the paper (geometry sweep S6). DOM order makes
     the sibling combinator work: .sheet precedes .controls in _build. */ body.phone-ui .sheet.open ~ .controls{ display: none; } body.phone-ui .topbar{ padding-top: env(safe-area-inset-top, 0px); }


/* was @media (max-width: 500px) { } — converted to body.gw500 (JS sets it from GAME dims so force-landscape rotation works; see main._updateLayoutClasses) */
 body.gw500 .brief{ max-width: 18ch; } body.gw500 .cbtn{ width: 2.1rem; height: 2.1rem; }



/* THE DOCK — hidden everywhere except the short-landscape phone layout below */
.dock { display: none; }

/* SHORT LANDSCAPE — DESIGN C, THE BOTTOM DOCK (user-picked from mockups after
   two failed squeezed-desktop attempts). One surface at a time:
     TOP STRIP   cig left · chips right · recenter under the chips
     THE SCENE   owns the screen (~75% height)
     SLIP BAND   the typed line, full width, just above the dock
     THE DOCK    a permanent low paper bar: slim (tabs + hint) with nobody
                 tailed; full (portrait + witnessed facts + 3 verbs) when
                 tailing. Tapping the identity area toggles #ui.dock-x, which
                 shows the REAL inspect panel as a full sheet over the scene.
   The desktop rails/inspect never render here; the sheet exists ONLY as the
   opened case file (suspects/notebook), summoned from the dock's tabs.
   Height is the scarce axis: shrink CHROME, never the sentence (law 5).
   Gated to landscape + short height; tall phones/tablets/desktop untouched. */
/* was @media (orientation: landscape) and (max-height: 520px) { } — converted to body.short-land (JS sets it from GAME dims so force-landscape rotation works; see main._updateLayoutClasses) */

  /* the collapsed mugshot strip is GONE — the dock replaces it. The sheet
     survives only as the opened full file. */ body.short-land .sheet:not(.open){ display: none; } body.short-land .sheet.open{ height: min(92vh, 18rem); } body.short-land .sheet-grip{ height: 1rem; } body.short-land .sheet-tabs{ padding-bottom: 0.2rem; }

  /* camera cluster: gestures cover orbit/zoom on touch — only RECENTER (the
     last cbtn) earns chrome, docked under the chips, off the system nav */ body.short-land .controls{
    bottom: auto; top: 3.4rem; flex-direction: row;
    right: calc(0.3rem + env(safe-area-inset-right, 0px)); gap: 0.3rem;
  } body.short-land .cbtn{ display: none; width: 2.2rem; height: 2.2rem; } body.short-land .controls .cbtn:last-child{ display: grid; }

  /* THE SLIP BAND: full width, floats just above whichever dock height is live */ body.short-land .deskstack{ left: 0.4rem; transform: none; width: calc(100vw - 0.8rem); align-items: stretch; bottom: 3.4rem; } body.short-land #ui.inspect-open .deskstack{ bottom: 6.2rem; } body.short-land .typeline{
    font-size: var(--fs-body); line-height: 1.32;
    padding: 0.45rem 1.4rem 0.45rem 0.85rem;
  }

  /* the inspect panel exists ONLY as the expanded file (#ui.dock-x). Top
     clears the cig meter; while the file is up you are READING, so the
     recenter button yields, and the typed slip rides OVER the file's lower
     edge (a slip on the open file — the desk metaphor, and law 3's one text
     surface stays on top of everything). */ body.short-land .inspect, body.short-land .inspect.solo{ display: none; } body.short-land #ui.dock-x .inspect:not(.hidden), body.short-land #ui.dock-x .inspect.solo:not(.hidden){
    display: block; position: fixed; left: 0; right: 0; bottom: 0; top: 4.7rem;
    width: auto; z-index: 14; overflow-y: auto; transform: none; border-radius: 0.3rem 0.3rem 0 0;
    touch-action: pan-y;
  }
  /* ONE surface: the expanded file REPLACES the dock (it grew out of it) */ body.short-land #ui.dock-x .dock{ display: none; } body.short-land #ui.dock-x .controls{ display: none; } body.short-land #ui.dock-x .deskstack{ z-index: 15; } body.short-land .ibody{ flex-direction: column; }
  /* the open sheet IS the surface — the dock waits underneath (root class,
     because the dock precedes the sheet in DOM and ~ can't look backwards) */ body.short-land #ui.sheet-open .dock{ display: none; }

  /* THE DOCK itself */ body.short-land .dock{
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 13;
    height: 5.6rem; align-items: stretch; gap: 0.6rem;
    padding: 0.45rem 0.6rem calc(0.45rem + env(safe-area-inset-bottom, 0px))
             calc(0.6rem + env(safe-area-inset-left, 0px));
    background-color: var(--paper); background-image: var(--tex-paper); background-size: 240px;
    border-top: 2px solid var(--brass);
    box-shadow: 0 -0.4rem 1.3rem rgba(0,0,0,0.5);
    font-family: var(--type); font-weight: 700; color: var(--ink-strong);
  } body.short-land .dock.slim{ height: 2.9rem; align-items: center; } body.short-land .dock-tabs{ display: flex; gap: 0.4rem; } body.short-land .dock-tab{
    font-family: var(--type); font-weight: 700; font-size: var(--fs-label);
    color: var(--ink-strong); background-color: var(--card); border: 1px solid var(--edge-strong);
    border-radius: 0.2rem; padding: 0.3rem 0.8rem; cursor: pointer;
  } body.short-land .dock-hint{ margin-left: auto; font-size: var(--fs-label); color: var(--ink-dim);
    padding-right: 0.4rem; }
  /* the identity area is ONE button: portrait + name + facts; tap = expand */ body.short-land .dock-who{
    flex: 1; min-width: 0; display: flex; gap: 0.7rem; align-items: center; text-align: left;
    background: none; border: none; padding: 0; cursor: pointer;
    font-family: var(--type); font-weight: 700; color: var(--ink-strong);
  } body.short-land .dock-port{ flex: 0 0 auto; width: 3.5rem; height: 100%; border-radius: 0.2rem;
    border: 1px solid var(--edge-strong); background: #10151f; object-fit: cover; } body.short-land .dock-mid{ flex: 1; min-width: 0; display: flex; flex-direction: column; justify-content: center; gap: 0.1rem; } body.short-land .dock-name{ font-size: var(--fs-lead); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } body.short-land .dock-role{ font-size: var(--fs-label); color: var(--ink-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } body.short-land .dock-chips{ display: flex; gap: 0.3rem; flex-wrap: nowrap; overflow: hidden; margin-top: 0.15rem; } body.short-land .dchip{
    flex: 0 0 auto; font-size: var(--fs-micro); color: var(--ink-strong);
    background-color: var(--paper-dim); border: 1px solid var(--edge-strong);
    border-radius: 0.2rem; padding: 0.12rem 0.4rem; white-space: nowrap;
  } body.short-land .dchip.open{ color: var(--ink-dim); border-style: dashed; } body.short-land .dock-verbs{ flex: 0 0 auto; display: flex; gap: 0.5rem; align-items: center;
    padding-right: env(safe-area-inset-right, 0px); } body.short-land .dock-verbs .ibtn{ width: 3.1rem; height: 3.1rem; } body.short-land .dock-verbs .ibtn svg.icon{ font-size: 1.4rem; }



/* CREDITS on the help sheet — a licence obligation, so it is real paper text:
   full weight, --ink inks, never faded (the ink law has no small print). */
/* help: the controls table + accessibility list headings */
.help-h {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-label);
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-strong);
  text-align: left; max-width: 44ch; margin: 0.6rem auto 0.5rem;
  border-bottom: 1px solid var(--edge-strong); padding-bottom: 0.25rem;
}
.keymap { text-align: left; max-width: 44ch; margin: 0 auto 1.2rem; display: flex; flex-direction: column; gap: 0.4rem; }
.keyrow { display: grid; grid-template-columns: 9.5rem 1fr; gap: 0.6rem; align-items: baseline; }
.keycap {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-label);
  color: var(--ink-strong); letter-spacing: 0.04em;
}
.keyact { font-family: var(--type); font-weight: 700; font-size: var(--fs-body); color: var(--ink); line-height: 1.4; }

.credits { margin-top: 0.9rem; border-top: 1px solid var(--edge-strong); padding-top: 0.6rem; }
.credits-h {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-label);
  letter-spacing: 0.1em; color: var(--ink-strong); margin-bottom: 0.35rem;
}
.credits-p {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-micro);
  color: var(--ink); line-height: 1.45; margin: 0 0 0.3rem;
}
.credits-list {
  margin: 0; padding-left: 1.1rem; font-family: var(--type); font-weight: 700;
  font-size: var(--fs-micro); color: var(--ink); line-height: 1.5;
}

/* ---------- accessibility ---------- */
/* the Settings sub-heading — hierarchy by weight/size/spacing, never fading */
.set-sub {
  font-family: var(--type); font-weight: 700; font-size: var(--fs-label);
  letter-spacing: 0.12em; color: var(--ink-strong); text-transform: uppercase;
  margin: 0.9rem 0 0.2rem; padding-top: 0.6rem; border-top: 1px solid var(--edge-strong);
}

/* COAT PATTERNS on the flat swatches — CSS spellings of the SAME fixed table in
   src/game/coat-patterns.js (crimson=diagonal, teal=dots, mustard=crosshatch,
   navy=chevron, plum=horizontal, olive=vertical, slate=checks, rust=plain).
   Only paints under body.coat-patterns; ink at ~0.35 over the coat colour. */
body.coat-patterns .swatch.pat-crimson { background-image: repeating-linear-gradient(45deg, rgba(0,0,0,0.35) 0 2px, transparent 2px 5px); }
body.coat-patterns .swatch.pat-teal { background-image: radial-gradient(rgba(0,0,0,0.4) 1.1px, transparent 1.2px); background-size: 5px 5px; }
body.coat-patterns .swatch.pat-mustard { background-image: repeating-linear-gradient(45deg, rgba(0,0,0,0.3) 0 1px, transparent 1px 4px), repeating-linear-gradient(-45deg, rgba(0,0,0,0.3) 0 1px, transparent 1px 4px); }
/* navy = CHEVRON, spelled as a true zigzag: each gradient paints one leg of
   the V and they alternate by half-tile offset — two full-coverage stripe
   families would read as crosshatch and collide with mustard's spelling at
   chip size (the review caught exactly that) */
body.coat-patterns .swatch.pat-navy {
  background-image:
    linear-gradient(135deg, transparent 42%, rgba(0,0,0,0.4) 42% 58%, transparent 58%),
    linear-gradient(45deg, transparent 42%, rgba(0,0,0,0.4) 42% 58%, transparent 58%);
  background-size: 6px 6px, 6px 6px;
  background-position: 0 0, 3px 0;
}
body.coat-patterns .swatch.pat-plum { background-image: repeating-linear-gradient(0deg, rgba(0,0,0,0.35) 0 2px, transparent 2px 6px); }
body.coat-patterns .swatch.pat-olive { background-image: repeating-linear-gradient(90deg, rgba(0,0,0,0.35) 0 1.5px, transparent 1.5px 4.5px); }
body.coat-patterns .swatch.pat-slate { background-image: repeating-conic-gradient(rgba(0,0,0,0.35) 0% 25%, transparent 0% 50%); background-size: 6px 6px; }
/* rust: plain — an unmarked coat is itself the eighth pattern */

/* REDUCE MOTION — the toggle mirrors the prefers-reduced-motion block: stills
   the DECORATIVE infinite loops (pulses, bobs, flares, nudges). Never the
   camera (law 11), never one-shot consequence flashes, never the typing caret
   (it is the "still typing" signal). */
body.reduce-motion .typeline { transition: none; } /* mirror the @media block exactly */
body.reduce-motion .cand.one,
body.reduce-motion .cig.danger,
body.reduce-motion .warnedge.on,
body.reduce-motion .typeline.done .tl-next,
body.reduce-motion .tl-next,
body.reduce-motion .stmt-cell.watch svg.icon,
body.reduce-motion .ocell.studying svg.icon,
body.reduce-motion .c-mine.watch svg.icon,
body.reduce-motion .tut-poster .rail.right,
body.reduce-motion .tut-accuse .card.prime,
body.reduce-motion .tut-accuse .ibtn.danger,
body.reduce-motion .ld-badge,
body.reduce-motion .hint,
body.reduce-motion .lesson { animation: none; }

/* ...but the last-notch warning is INFORMATION, not decoration, so stilling it
   the way we still a decorative pulse would DELETE the only thing telling a
   reduce-motion player their clock is about to run out. The motion goes; the
   RED STAYS — a steady glow on the meter, a steady red edge on the frame.
   (The canvas half, the halo + the hot ember, is painted in hud.js _drawCig and
   never depended on animation at all — which is why it is drawn, not filtered.)
   Both selectors are (0,3,1), so they beat the base .cig / .vignette rules. */
body.reduce-motion .cig.danger { filter: drop-shadow(0 0 0.9rem rgba(224,85,107,1)); }
body.reduce-motion .warnedge.on { opacity: 0.42; }

/* TOUCH AFFORDANCES — driven by the DEVICE class (main.js _applyDevice), not by
   the viewport: a narrow desktop window still has a mouse. */
.input-touch .finding .f-replay { opacity: 1; } /* hover can never reveal it here */
.input-touch .cbtn { min-width: 2.6rem; min-height: 2.6rem; }
.input-touch .sheet-tab { padding: 0.4rem 0.7rem; }

/* =============================================================
   Wordless UI — icons, stamps, and pulses instead of sentences
   ============================================================= */

/* wanted poster */
.poster-wrap { padding: 0.8rem 0.8rem 0.3rem; display: grid; place-items: center; }
canvas.poster { width: min(88%, 15rem); height: auto; border-radius: 0.5rem; box-shadow: 0 0.4rem 1.4rem rgba(0,0,0,0.5); }
.pchips { display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap; padding: 0.5rem 0.8rem 0.9rem; }
.pchip {
  width: 2.5rem; height: 2.5rem; padding: 0; border-radius: 0.6rem; display: grid; place-items: center;
  background: rgba(246,201,68,0.08); border: 1px solid rgba(246,201,68,0.3); font-size: 1.25rem; cursor: help;
}
.pchip .swatch { width: 1.3rem; height: 1.3rem; }
.pchip .ticon { font-size: 1.25rem; }

/* suspect cards: the PLAYER's ink ✗ (their own cross-out, never the game's) */
.pwrap { position: relative; flex-shrink: 0; }
.ink-x {
  position: absolute; inset: 0; display: none; place-items: center;
  color: var(--bad); filter: drop-shadow(0 0 0.4rem rgba(0,0,0,0.9));
}
.ink-x svg.icon { font-size: 2.6rem; transform: rotate(-6deg); }
.card.marked .ink-x { display: grid; }
.card.marked { opacity: 0.5; filter: grayscale(0.55); }
.card.marked .portrait { filter: grayscale(1) brightness(0.72); }
/* the amber hunch pin — top-right corner of the portrait, softer than the ✗ */
.hunch-pin {
  position: absolute; top: -0.35rem; right: -0.35rem; display: none; place-items: center;
  color: var(--amber); filter: drop-shadow(0 0.1rem 0.3rem rgba(0,0,0,0.8));
}
.hunch-pin svg.icon { font-size: 1.3rem; }
.card.hunched .hunch-pin { display: grid; }
.card.hunched { box-shadow: inset 0 0 0 2px rgba(246,201,68,0.55); }
/* hunch action button (amber, mirrors the ✗ button) */
.ibtn.ihunch { border-color: rgba(246,201,68,0.35); color: var(--ink-dim); }
.ibtn.ihunch:hover { color: var(--amber); background: rgba(246,201,68,0.12); }
.ibtn.ihunch.on { color: var(--amber); background: rgba(246,201,68,0.22); border-color: rgba(246,201,68,0.6); }
.iacts .ibtn.ihunch { font-size: 1.8rem; padding: 0.8rem; }
.cand { font-size: 0.85rem; letter-spacing: 0.06em; }

/* inspect: one row of big observation cells */
.orow2 { display: flex; gap: 0.5rem; justify-content: center; margin: 0.9rem 0; }
.ocell {
  width: 2.8rem; height: 2.8rem; border-radius: 0.14rem; display: grid; place-items: center; color: var(--ink);
  background: rgba(255,255,255,0.3); border: 1px solid var(--edge-strong); font-size: 1.3rem;
}
.ocell .swatch { width: 1.35rem; height: 1.35rem; }
.ocell .ticon { font-size: 1.3rem; }
.ocell.unk2 { color: var(--ink-dim); opacity: 0.55; }
.ocell.study { cursor: pointer; border-color: var(--blue); background: rgba(143,182,255,0.1); }
.ocell.study:hover { background: rgba(143,182,255,0.22); }
.iacts { margin-top: 0.7rem; }
.iacts .ibtn.danger { font-size: 1.8rem; padding: 0.8rem; }

/* splash + result: glyphs, not paragraphs */
.panel.splash .logo { margin-bottom: 1.4rem; }
.primary.play { font-size: 1.7rem; padding: 0.7rem 3.2rem; line-height: 1; }
/* the verdict ✓/✗ is a STAMPED circle mark slammed on the folder */
.rglyph { font-size: 4rem; line-height: 1; margin: 0.2rem auto 0.7rem; }
.result .rglyph {
  display: grid; place-items: center; width: 5rem; height: 5rem; border-radius: 50%;
  border: 3px double currentColor; transform: rotate(-7deg); opacity: 0.88;
}
.rglyph.warn { color: var(--amber); font-size: 2.2rem; margin: 0.5rem 0 0.9rem; border: none; width: auto; height: auto; transform: none; }
.result.win .rglyph { color: var(--good); }
.result.lose .rglyph { color: var(--stamp-red); }
.rname { font-family: var(--serif); font-size: 1.3rem; margin-bottom: 1.1rem; }
.confirm .rname { margin-bottom: 0; }
.ghost.big, .primary.danger.big { font-size: 1.4rem; padding: 0.6rem 2.2rem; line-height: 1; }
.small-portrait { display: inline-block; margin-bottom: 0.5rem; position: relative; }
/* the cross-out over the wrong-accused mugshot is the SVG icon, not a glyph */
.small-portrait.crossed .ink-x { display: grid; color: var(--stamp-red); }
.small-portrait.crossed .ink-x svg.icon { font-size: 2.8rem; }
.rportrait.sm { width: 4rem; height: 4.5rem; opacity: 0.6; filter: grayscale(0.8); }

/* pictogram help */
/* the how-to tiles are ONE horizontal row (v1.2: was flex-wrap:wrap, which
   dropped tile 3 onto a second line). If a narrow viewport can't fit the row,
   it scrolls INSIDE its own container (the wide-content law — the page body
   never scrolls horizontally). */
.tiles { display: flex; align-items: center; justify-content: center; gap: 0.9rem; margin: 0.5rem 0 1.6rem; flex-wrap: nowrap; overflow-x: auto; }
/* at narrow widths, shrink the tiles instead of overflowing early */
body.gw640 .tiles { gap: 0.5rem; }
body.gw640 .tile { padding: 0.7rem 0.7rem; min-width: 4.6rem; }
body.gw640 .tglyph { font-size: 1.9rem; }
body.gw640 .tarrow { font-size: 1.1rem; }
.tile {
  display: flex; flex-direction: column; align-items: center; gap: 0.5rem; color: var(--ink);
  background-color: var(--card); background-image: var(--tex-card); background-size: 180px;
  border: 1px solid var(--edge-strong); border-radius: 0.16rem; padding: 1rem 1.2rem; min-width: 6rem;
  box-shadow: var(--shadow-soft);
}
.tglyph { font-size: 2.4rem; line-height: 1; }
.tmini { font-size: 1.4rem; }
.tmini.one-left {
  width: 1.8rem; height: 1.8rem; border-radius: 50%; background: var(--amber); color: #201800;
  font-weight: 700; display: grid; place-items: center; font-size: 1.1rem;
}
.tarrow { font-size: 1.5rem; color: var(--amber); }
.tile .cells { justify-content: center; }
.tile .cell { width: 1.8rem; height: 1.8rem; }

/* story toast (the one flavor sentence, at the body) */
.toast.story { font-family: var(--serif); font-style: italic; white-space: normal; max-width: 34rem; text-align: center; }

/* tutorial pulses */
@keyframes tutpulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(246,201,68,0); }
  50% { box-shadow: 0 0 0 0.45rem rgba(246,201,68,0.5); }
}
.tut-poster .rail.right { animation: tutpulse 1.3s ease-in-out infinite; }
.tut-accuse .card.prime { animation: tutpulse 1.3s ease-in-out infinite; }
.tut-accuse .ibtn.danger { animation: tutpulse 1.3s ease-in-out infinite; }

/* =============================================================
   Vector icon system + tap-hand hint
   ============================================================= */
svg.icon {
  width: 1em; height: 1em; display: inline-block; vertical-align: -0.125em;
  flex-shrink: 0; pointer-events: none;
}
.chip svg.icon { font-size: 1.45rem; }
.cbtn { display: grid; place-items: center; }
.cbtn svg.icon { font-size: 1.55rem; }
.rail-head { gap: 0.5rem; justify-content: flex-start; align-items: center; }
.rail-head svg.icon { font-size: 1.2rem; }
.rail-head .cand { margin-left: auto; }
.cell { color: var(--ink); }
.cell svg.icon { font-size: 0.95rem; }
.ocell svg.icon { font-size: 1.5rem; }
.ocell.yes { border-color: var(--good); background: rgba(107,224,122,0.16); }
.ocell.no { border-color: var(--bad); background: rgba(224,85,107,0.16); }
.ocell.study svg.icon { color: var(--blue); }
.ocell.studying svg.icon { animation: blink 1s ease-in-out infinite; }
@keyframes blink { 50% { opacity: 0.25; } }
.pchip { color: var(--amber); }
.pchip svg.icon { font-size: 1.4rem; }
.iclose { display: grid; place-items: center; font-size: 1.1rem; }
.ibtn.danger svg.icon { font-size: 1.5rem; }
.tglyph svg.icon { font-size: 2.4rem; }
.tmini svg.icon { font-size: 1.5rem; }
.tarrow { color: var(--amber); display: grid; place-items: center; }
.tarrow svg.icon { font-size: 1.4rem; }
.rglyph { display: grid; place-items: center; }
.rglyph svg.icon { font-size: 3.6rem; }
.rglyph.warn svg.icon { font-size: 2.1rem; }
.primary, .ghost { display: inline-grid; place-items: center; }
.primary svg.icon, .ghost svg.icon { font-size: 1.35em; }

/* the pointer arrow: hovers ABOVE the exact thing to click and bobs DOWN toward
   it. Big, amber, glowing — an unmistakable "here" marker, not a passive glow. */
.hint {
  position: absolute; z-index: 25; pointer-events: none; color: var(--amber);
  font-size: 2.6rem; margin: -3rem 0 0 -1.3rem;
  filter: drop-shadow(0 0 0.4rem rgba(246,201,68,0.95)) drop-shadow(0 0.18rem 0.3rem rgba(0,0,0,0.85));
  animation: arrowbob 0.85s ease-in-out infinite;
}
/* a WORLD-anchored hand (body, suspect, evidence) ducks UNDER the paper: -1
   inside #ui's stacking context sits below every positioned sibling (rails,
   slip, dock, chips) while staying above the scene canvas, which lives below
   #ui entirely. A DOM-anchored hand points AT paper and keeps z 25. */
.hint.world { z-index: -1; }
.hint.hidden { display: none; }
.hint svg.icon { display: block; }
@keyframes arrowbob {
  0%, 100% { transform: translateY(-0.25rem); }
  50% { transform: translateY(0.55rem); }
}

/* section labels on the rails */
.rl-label {
  font-family: var(--serif); font-size: 0.86rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink);
}
.rl-label.wanted {
  color: var(--amber); letter-spacing: 0.34em; font-weight: 600; margin: 0 auto;
  padding-left: 0.34em; /* balance the tracking */
}
.rail.right .rail-head { justify-content: center; border-bottom-color: rgba(246,201,68,0.25); }

/* coach line: one short instruction, top-center */
/* (the old top-of-screen .coach strip is gone: there is ONE text surface, the
   .typeline slip above the mugshot card. Two paper slips asking to be read at
   once only made the player pick which one to look at.) */

/* =============================================================
   Phase 1 — the legible loop: shape-redundant verdicts + the
   killer-vs-suspect comparison with a capture animation.
   ============================================================= */

/* verdict badge (✓/✗) — shape carries meaning, colour reinforces */
.cell { position: relative; overflow: visible; }
.vbadge {
  position: absolute; right: -0.28rem; top: -0.32rem;
  width: 1rem; height: 1rem; border-radius: 50%; display: grid; place-items: center;
  border: 1.5px solid var(--panel-solid); box-shadow: 0 1px 2px rgba(0,0,0,0.5);
}
.vbadge.yes { background: var(--good); color: #06230b; }
.vbadge.no { background: var(--bad); color: #2c0710; }
.vbadge svg.icon { font-size: 0.72rem; }

/* the comparison: killer's required trait (top) over this suspect's (bottom) */
.compare { display: flex; gap: 0.7rem; justify-content: stretch; align-items: stretch; margin: 0.4rem 0 0.2rem; }
.cslot {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: space-between; gap: 0.5rem; color: var(--ink);
  padding: 0.7rem 0.4rem; border-radius: 0.16rem; border: 1px solid var(--edge-strong); background: rgba(255,255,255,0.3);
}
.cslot.yes { border-color: var(--good); background: rgba(63,125,51,0.12); }
.cslot.no { border-color: var(--bad); background: rgba(178,58,46,0.12); }
.cslot.pending { border-color: var(--blue); }
.c-target, .c-mine {
  width: 100%; aspect-ratio: 1; max-height: 3.4rem; display: grid; place-items: center; border-radius: 0.14rem;
}
.c-target { background: rgba(0,0,0,0.12); box-shadow: inset 0 0 0 1.5px rgba(168,120,26,0.6); } /* the wanted trait, ringed like a target */
.c-target svg.icon, .c-mine svg.icon { font-size: 2.1rem; }
.c-target .swatch, .c-mine .swatch { width: 1.9rem; height: 1.9rem; }
.c-verdict { height: 1.5rem; display: grid; place-items: center; }
.c-verdict.yes { color: var(--good); }
.c-verdict.no { color: var(--bad); }
.c-verdict svg.icon { font-size: 1.6rem; }
/* an unwitnessed facet: a softly blinking eye — keep tailing them */
.c-mine.watch { color: var(--blue); }
.c-mine.watch svg.icon { animation: blink 1.6s ease-in-out infinite; font-size: 1.7rem; }
/* the player's ink cross-out button */
.ibtn.imark { border-color: rgba(224,85,107,0.35); color: var(--ink-dim); }
.ibtn.imark:hover { color: #ffb9c4; background: rgba(224,85,107,0.12); }
.ibtn.imark.on { color: #ffb9c4; background: rgba(224,85,107,0.22); border-color: rgba(224,85,107,0.6); }
.iacts .ibtn.imark { font-size: 1.8rem; padding: 0.8rem; }

/* capture: each suspect trait lifts up staggered, its verdict stamps after */
.compare.capture .c-mine {
  animation: capMine 0.42s cubic-bezier(0.2,0.9,0.3,1.25) both;
  animation-delay: calc(var(--i) * 0.12s);
}
.compare.capture .c-verdict {
  animation: capStamp 0.34s cubic-bezier(0.2,1.5,0.4,1) both;
  animation-delay: calc(var(--i) * 0.12s + 0.18s);
}
@keyframes capMine { from { opacity: 0; transform: translateY(1.3rem) scale(0.55); } }
@keyframes capStamp {
  0% { opacity: 0; transform: scale(2.3) rotate(-14deg); }
  55% { opacity: 1; }
  100% { transform: scale(1) rotate(0); }
}

/* =============================================================
   Phase 5/6 — wordless status dots, "why" comparison, guardrail
   ============================================================= */

/* suspects-remaining as dots, not a number */
.cand { display: flex; gap: 0.3rem; margin-left: auto; align-items: center; }
.sdot {
  width: 0.72rem; height: 0.72rem; border-radius: 50%; background: var(--amber);
  box-shadow: 0 0 0.32rem rgba(246,201,68,0.55); display: grid; place-items: center;
  transition: background 0.2s, box-shadow 0.2s;
}
.sdot.out { background: transparent; box-shadow: none; border: 1.5px solid var(--bad); color: var(--bad); }
.sdot.out svg.icon { font-size: 0.5rem; }
.cand.one .sdot:not(.out) { animation: pulse 1.3s ease-in-out infinite; }

/* the "why you were wrong" comparison in the result modal */
.compare.why { margin: 0.3rem auto 0.9rem; gap: 0.4rem; }
.compare.why .cslot { min-width: 2.8rem; padding: 0.35rem; gap: 0.2rem; }
.compare.why .c-target, .compare.why .c-mine { width: 2rem; height: 2rem; }
.compare.why .c-target svg.icon, .compare.why .c-mine svg.icon { font-size: 1.3rem; }
.compare.why .c-target .swatch, .compare.why .c-mine .swatch { width: 1.1rem; height: 1.1rem; }
.compare.why .c-verdict { height: 1rem; }
.compare.why .c-verdict svg.icon { font-size: 1rem; }

/* notebook: replayable findings, written as INK ENTRIES down the ruled page */
.rail.right.notebook { }
.findings { padding: 0.5rem 0.85rem 0.7rem; display: flex; flex-direction: column; overflow-y: auto; }
.finding {
  display: flex; flex-direction: column; align-items: stretch; gap: 0.3rem;
  padding: 0.7rem 0.4rem; min-height: 3.6rem; cursor: pointer; color: var(--ink); background: none;
  border: none; border-bottom: 1px dashed rgba(42,36,28,0.28);
  transition: background 0.15s, transform 0.1s;
  animation: capMine 0.35s ease both; /* pops in when recorded */
  /* now a <button>: strip UA appearance, keep width + inherited font */
  font: inherit; text-align: left; width: 100%; -webkit-appearance: none; appearance: none;
}
.finding .f-top { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
/* What the trace told the detective, in their own words. This is the notebook's
   actual content — it has to be READ, not squinted at, so it gets proper body
   copy (see --ink-strong) and is never clamped: the rail is wide and mostly
   empty, and cutting a clue off mid-sentence makes it worthless. */
.finding .f-caption {
  font-family: var(--type); font-size: var(--fs-body); line-height: 1.45;
  color: var(--ink-strong); font-weight: 700;
}
.finding:last-child { border-bottom: none; }
.finding:hover { background: rgba(42,36,28,0.05); }
.finding .f-icons { display: flex; gap: 0.6rem; align-items: center; }
.finding .f-icons svg.icon { font-size: 1.8rem; }
.finding .f-icons .swatch { width: 1.6rem; height: 1.6rem; border-radius: 0.2rem; }
/* partial strips: CANDIDATE sets, not verdicts — dashed, slightly smaller,
   tighter. The player finds the swatch present in BOTH strips themselves. */
.finding.f-coatA .f-icons, .finding.f-coatB .f-icons, .finding.f-testimony .f-icons { gap: 0.35rem; }
.finding.f-coatA .swatch, .finding.f-coatB .swatch, .finding.f-testimony .swatch {
  width: 1.25rem; height: 1.25rem; border-radius: 0.2rem;
  border: 2px dashed rgba(42,36,28,0.5); opacity: 0.92;
}
.finding.f-coatA .f-icons svg.icon:first-child,
.finding.f-coatB .f-icons svg.icon:first-child,
.finding.f-witness .f-icons svg.icon:first-child,
.finding.f-testimony .f-icons svg.icon:first-child { color: var(--blue); font-size: 1.4rem; }
.finding .f-replay { color: var(--blue); opacity: 0.7; }
.finding .f-replay svg.icon { font-size: 1.25rem; }
.finding:hover .f-replay { opacity: 1; }
.findings:empty::after {
  content: ''; display: block; height: 3.5rem; margin-top: 0.2rem;
  border: 2px dashed rgba(42,36,28,0.28); border-radius: 0.16rem; opacity: 0.6;
}

/* soft evidence guardrail: amber "?" + a one-shot wobble, never a block */
.rail-head.wanted-head { justify-content: center; }
.rglyph.unsure { color: var(--amber); font-size: 2.4rem; margin: 0.4rem 0 0.8rem; animation: wobble 0.6s ease-in-out; }
.panel.confirm.unsure { border-color: var(--amber); }
@keyframes wobble { 0%,100% { transform: rotate(0); } 25% { transform: rotate(-7deg); } 75% { transform: rotate(7deg); } }
@keyframes hintbob {
  0%, 100% { transform: translateY(0.35rem) scale(1); }
  50% { transform: translateY(0) scale(0.92); }
}
@keyframes hintring {
  0%, 45% { opacity: 0; width: 0.4rem; height: 0.4rem; }
  55% { opacity: 0.9; }
  100% { opacity: 0; width: 2.4rem; height: 2.4rem; }
}
