/* GuessDaily — the GAME surface: everything engine.js drives (top bar, rolodex of cards, the
   hand-drawn marks, dock, archive, result modal, toast).

   This sheet's *structure* is game-agnostic: every colour and face below resolves through a CSS
   variable, and the values that make a game look like itself come from its config —
   `public/games/<slug>.js` exports a `theme` block and engine.js applies it as custom properties
   on #game-shell at boot and on every theme flip (see applyTheme()). The declarations on
   `#game-shell` below are only *fallbacks*, and they are deliberately neutral: they carry no
   game's identity, so a new game that themes nothing renders legibly rather than wearing
   Recipedle's face. Faces in particular are generic stacks (serif / cursive / system-ui) — a
   named display face like Fraunces or Caveat belongs in a game's config, never here.

   The one set of literals here that is *not* a placeholder is the marks palette
   (--mk-good/--mk-bad/--mk-hi/--mk-hi-2, and --card-chip-on): green=exact, gold=close,
   gray=miss is the engine's universal mechanic (see CLAUDE.md), not any game's branding. A game
   may retune those shades to its paper, but the meaning is the engine's.

   THEME VARIABLE CONTRACT — a game's theme may set any of these (all optional):
     surface   --bg --bg-2 --panel --panel-edge --shadow --scrim
     text      --ink --ink-soft --ink-faint
     brand     --accent --accent-d --accent-ink --hi --trim
     card      --card --card-edge --card-ink --card-ink-soft --card-ink-faint --card-accent
               --card-rule --card-margin --card-lead --card-chip --card-chip-ink
               --card-chip-on --card-chip-on-ink --card-flag --card-radius
     marks     --mk-good --mk-bad --mk-hi --mk-hi-2 --mk-arrow --mk-neutral
     status    --ok-ink --miss-ink
     faces     --font-display --font-hand --font-ui
   Motif knobs: set --card-rule to `transparent` for unruled paper, --card-margin to
   `transparent` to drop the margin line, --card-radius to reshape the card.

   ONE DELIBERATE EXCEPTION: the card/frow/mark selectors (`.recipe`, `.frow`, `.mk`, `.ann*`,
   `.hilite`) are each also matched under `[data-mk-demo]`. That attribute scopes the hub's
   contained "How it works" worked example (see renderHub() in app.js) — it carries Recipedle's
   real card + mark tokens as inline custom properties on that one element, the same way each
   hub game-card already tints from an inline `--accent` (hub.css), so the example is a truthful,
   unforked reuse of these rules without the rest of the hub adopting a game's theme. No other
   selector in this file is reachable outside `#game-shell`. */

#game-shell {
  /* --- neutral fallbacks (a game's theme overrides these inline) --- */
  --bg:#14161A; --bg-2:#1B1E24; --panel:#1B1E24; --panel-edge:#2C313A;
  --ink:#ECEEF2; --ink-soft:#A8AEB8; --ink-faint:#767D88;
  --accent:#7A8899; --accent-d:#616E7D; --accent-ink:#FFFFFF;
  --hi:#C9B77E; --trim:#98A0AC;
  --card:#FFFFFF; --card-edge:#E3E6EA; --card-ink:#2E3238; --card-ink-soft:#7C838D;
  --card-ink-faint:#A3AAB3; --card-accent:#B24A3C; --card-rule:#DDE6EC; --card-margin:#E9A79B;
  --card-lead:#C9CDD3; --card-chip:#ECEEF1; --card-chip-ink:#7C838D;
  --card-chip-on:#DCF0E2; --card-chip-on-ink:#2E7D46; --card-flag:#F1E7CC; --card-radius:12px;
  --mk-good:#2E8B4E; --mk-bad:#BE4A2E; --mk-hi:rgba(247,206,64,.52); --mk-hi-2:rgba(236,188,44,.42);
  --mk-arrow:#6b5a49;
  /* Neutral/not-applicable tile (state "n"): the attribute doesn't apply to the day's answer, so
     it must read as inert — neither a match nor a miss. No light override: like --mk-good/
     --mk-bad/--mk-hi above, this sits on the card (always paper-coloured in both themes), not
     the shell background, so one value serves both themes. */
  --mk-neutral:#8B93A0;
  --ok-ink:#63d494; --miss-ink:#f0a58c;
  --shadow:0 22px 46px -22px rgba(0,0,0,.8); --scrim:rgba(8,7,6,.74);
  /* Generic faces only — a game's identity type comes from its config (Recipedle supplies
     Fraunces/Caveat/Inter in games/recipe.js). These just keep an unthemed game readable. */
  --font-display:ui-serif,Georgia,serif; --font-hand:cursive; --font-ui:system-ui,sans-serif;

  /* The game surface paints its own ambient field (it fills the viewport), so the accent tint
     stays inside the game's scope and can never wash the hub. */
  background: radial-gradient(1100px 560px at 50% -14%, color-mix(in srgb, var(--accent) 14%, var(--bg)) 0%, var(--bg) 55%) fixed;
  color:var(--ink);
  font-family:var(--font-ui);
}
/* Light fallbacks. A game's theme supplies its own light values (engine.js re-applies the
   merged set on every data-theme change), so these only back an unthemed game. */
html[data-theme="light"] #game-shell {
  --bg:#F2F3F5; --bg-2:#FFFFFF; --panel:#FFFFFF; --panel-edge:#DFE3E8;
  --ink:#22262C; --ink-soft:#5A626C; --ink-faint:#858C96;
  --trim:#6E7681;
  --shadow:0 22px 46px -26px rgba(30,40,60,.35);
}

/* Optional crossfading background (engine.setupBackground, config.backgrounds). The shell goes
   transparent + isolates so the layer pair sits behind the game content; each card centred fades in
   the next scene. A scrim darkens the edges so the parchment card and text stay readable.
   ABSOLUTE, not fixed, and deliberately so: `#game-shell` sets `isolation:isolate`, which confines
   this layer's `z-index:-1` to the shell's own stacking context — it can never drop behind anything
   OUTSIDE the shell. A viewport-fixed `inset:0` therefore painted over its siblings in <body>, which
   silently hid the local-dev / staging env banner (`#envBanner`) on any game declaring a background.
   Botanidle was the only such game, which is exactly how it presented: banner visible everywhere but
   there. Anchoring to the shell (`position:relative` above) keeps the scene identical inside the
   game surface while leaving page chrome above it alone. `#game-shell` fills the viewport minus that
   chrome and does not itself scroll, so absolute and fixed render the same thing within it. */
#game-shell.pm-has-bg { background:transparent; position:relative; isolation:isolate; }
#game-shell #pm-bg { position:absolute; inset:0; z-index:-1; overflow:hidden; pointer-events:none; }
#game-shell #pm-bg .pm-bg-l { position:absolute; inset:0; opacity:0; transition:opacity .7s ease;
  background-size:cover !important; background-position:center !important; }
#game-shell #pm-bg .pm-bg-l.on { opacity:1; }
#game-shell #pm-bg::after { content:""; position:absolute; inset:0;
  background:radial-gradient(125% 100% at 50% 42%, transparent 32%, rgba(5,9,6,.55) 100%); }
@media (prefers-reduced-motion: reduce) { #game-shell #pm-bg .pm-bg-l { transition:none; } }

/* ---------- Top bar ---------- */
#game-shell .topbar { flex:0 0 auto; padding:10px 14px 9px; border-bottom:1px solid var(--panel-edge);
  background:linear-gradient(180deg, color-mix(in srgb, var(--bg) 92%, transparent), color-mix(in srgb, var(--bg) 72%, transparent)); backdrop-filter:blur(6px); }
#game-shell .brandrow { display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:nowrap; }
#game-shell .brand { display:flex; flex-direction:column; min-width:0; flex:1 1 auto; }
/* The kicker cross-fades between the tagline and the host. The two labels are stacked in one
   grid cell so the box sizes to the wider of them (not their sum) and never inflates the brand
   column or bumps the controls to a second line; each label truncates with an ellipsis as a
   safety net for very long hosts. minmax(0,max-content) lets the cell shrink when constrained. */
#game-shell .kicker { font-size:10px; letter-spacing:.16em; text-transform:uppercase; color:var(--ink-faint); font-weight:600;
  text-decoration:none; border-radius:3px; max-width:100%; display:inline-grid; grid-template-columns:minmax(0,max-content); }
#game-shell .kicker .kf { grid-area:1/1; min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
#game-shell .kicker .kf-b { opacity:0; }
@media (prefers-reduced-motion: no-preference){
  #game-shell .kicker .kf-a { animation:gd-kicker-a 10s ease-in-out infinite; }
  #game-shell .kicker .kf-b { animation:gd-kicker-b 10s ease-in-out infinite; }
}
@keyframes gd-kicker-a { 0%,42%{opacity:1} 50%,92%{opacity:0} 100%{opacity:1} }
@keyframes gd-kicker-b { 0%,42%{opacity:0} 50%,92%{opacity:1} 100%{opacity:0} }
#game-shell .kicker b { color:var(--hi); font-weight:700; }
#game-shell .kicker:hover { color:var(--ink-soft); }
#game-shell .wordmark { font-family:var(--font-display); font-weight:700; font-size:clamp(24px,6.4vw,32px); line-height:1;
  letter-spacing:-.01em; display:inline-flex; align-items:center; gap:0; }
#game-shell .wordmark .dle { color:var(--accent); }
#game-shell .wordmark .ic { font-size:.6em; transform:translateY(1px); margin-right:.18em; }

#game-shell .rightcluster { display:flex; align-items:center; gap:8px; flex:0 0 auto; }
#game-shell .iconbtn { width:40px; height:40px; border-radius:11px; border:1px solid var(--panel-edge); background:var(--panel); color:var(--ink);
  font-size:17px; cursor:pointer; flex:0 0 auto; transition:.15s; display:grid; place-items:center; }
#game-shell .iconbtn:hover { border-color:var(--trim); }
#game-shell .iconbtn:focus-visible, #game-shell .gbtn:focus-visible, #game-shell .arch-row:focus-visible,
#game-shell .suggest button:focus-visible, #game-shell input:focus-visible, #game-shell .kicker:focus-visible,
#game-shell .fb-opt:focus-visible, #game-shell footer .footfb:focus-visible, #game-shell .fb-toggle:focus-visible {
  outline:2px solid var(--hi); outline-offset:2px; }

#game-shell .mini { display:flex; align-items:center; gap:10px; background:var(--panel); border:1px solid var(--panel-edge);
  border-radius:12px; padding:7px 11px 7px 8px; cursor:default; }
#game-shell .mini.solved, #game-shell .mini.lost { cursor:pointer; }
#game-shell .mini.solved:hover, #game-shell .mini.lost:hover { border-color:var(--trim); }
#game-shell .mini .qmark { width:34px; height:34px; flex:0 0 34px; border-radius:9px; display:grid; place-items:center; font-size:19px;
  background:repeating-linear-gradient(45deg, color-mix(in srgb, var(--trim) 22%, var(--panel)), color-mix(in srgb, var(--trim) 22%, var(--panel)) 5px, var(--panel) 5px, var(--panel) 10px); border:1px dashed var(--panel-edge); }
#game-shell .mini .mt-l { font-size:9px; letter-spacing:.1em; text-transform:uppercase; color:var(--trim); font-weight:700; }
/* The revealed answer lives here persistently. Clamp it to two lines + a width cap so a long
   entity name can't balloon the card's height (mobile) or width into the wordmark (desktop) —
   the full name is always shown in the result modal, which the solved card opens on click. */
#game-shell .mini .mt-n { font-family:var(--font-display); font-size:16px; font-weight:600; margin-top:1px; line-height:1.08;
  max-width:min(46vw, 210px); overflow:hidden; text-overflow:ellipsis;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
#game-shell .mini.solved .mt-n { color:var(--hi); }
#game-shell .mini.lost .mt-n { color:var(--accent); }
#game-shell .mini .tries { margin-left:2px; text-align:center; padding-left:10px; border-left:1px solid var(--panel-edge); }
#game-shell .mini .tries .big { font-family:var(--font-display); font-size:18px; font-weight:700; line-height:1; }
#game-shell .mini .tries .lab { font-size:8.5px; color:var(--ink-faint); letter-spacing:.06em; text-transform:uppercase; }

#game-shell .legend { margin-top:9px; display:flex; flex-wrap:wrap; gap:8px 14px; justify-content:center; font-size:11.5px; color:var(--ink-soft); }
#game-shell .legend .lg { display:inline-flex; align-items:center; gap:6px; }
/* `.demo` needs its own stacking context, same as `.recipe` gets for free from its
   `transform:rotate(...)`. Without one, `.mk.hl .hilite`'s `z-index:-1` has no local
   context to resolve into, escapes to the page root, and paints *behind* every ordinary
   in-flow box on the page — including `.demo`'s own opaque background — so the "Close"
   swatch shows no highlight at all. `isolation:isolate` creates that context with no visual
   side effect of its own (no rotation, no opacity change), so `.hilite` lands exactly where
   it does on a card: above `.demo`'s background, below the label text, blending against
   `.demo`'s own background only (mix-blend-mode is scoped to this stacking context too). */
#game-shell .legend .demo { background:var(--card); color:var(--card-ink); font-family:var(--font-display); font-weight:600; font-size:13px;
  padding:3px 12px; border-radius:6px; display:inline-block; isolation:isolate; }
#game-shell .legend b { color:var(--ink); font-weight:700; }
#game-shell .legend.collapsed { display:none; }
#game-shell .legend-toggle { display:block; margin:9px auto 0; padding:2px 12px 3px; border-radius:99px;
  border:1px solid var(--panel-edge); background:var(--panel); color:var(--ink-soft);
  font:600 11px/1.4 var(--font-ui); letter-spacing:.03em; cursor:pointer; }
#game-shell .legend-toggle:hover { color:var(--ink); border-color:var(--trim); }
#game-shell .legend-toggle::after { content:" ▾"; opacity:.7; }
#game-shell .legend-toggle[aria-expanded="false"]::after { content:" ▸"; }

/* ---------- Archive panel ---------- */
#game-shell .archive { position:fixed; top:66px; left:50%; transform:translateX(-50%); width:min(94vw,430px); max-height:64vh; overflow-y:auto;
  background:var(--bg-2); border:1px solid var(--panel-edge); border-radius:16px; box-shadow:var(--shadow); z-index:56; padding:10px; }
#game-shell .archive[hidden] { display:none; }
#game-shell .arch-head { font-size:11px; letter-spacing:.14em; text-transform:uppercase; color:var(--trim); font-weight:700; padding:6px 8px 8px; display:flex; justify-content:space-between; align-items:center; }
#game-shell .arch-head .x { cursor:pointer; color:var(--ink-soft); font-size:16px; background:none; border:none; }
#game-shell .arch-row { display:flex; align-items:center; gap:10px; width:100%; background:none; border:none; border-radius:10px; padding:11px 10px; cursor:pointer; color:var(--ink); font-family:inherit; text-align:left; }
#game-shell .arch-row:hover { background:color-mix(in srgb, var(--ink) 6%, transparent); }
#game-shell .arch-row.current { background:color-mix(in srgb, var(--accent) 14%, transparent); }
#game-shell .arch-row .an { font-family:var(--font-display); font-weight:700; font-size:14px; width:54px; flex:0 0 54px; }
#game-shell .arch-row .ad { flex:1; font-size:13px; color:var(--ink-soft); }
#game-shell .arch-row .st { font-size:10.5px; font-weight:700; padding:3px 9px; border-radius:99px; white-space:nowrap; }
#game-shell .st.solved { background:color-mix(in srgb, var(--mk-good) 18%, transparent); color:var(--ok-ink); }
#game-shell .st.missed { background:color-mix(in srgb, var(--accent) 16%, transparent); color:var(--miss-ink); }
#game-shell .st.play, #game-shell .st.today { background:color-mix(in srgb, var(--hi) 16%, transparent); color:var(--hi); }

/* ---------- Stage (rolodex) ---------- */
#game-shell .stage { flex:1 1 auto; min-height:0; position:relative; display:flex; align-items:stretch; }

#game-shell .rack { --card-w:min(86vw, 372px); --rack-gap:20px;
  width:100%; display:flex; gap:var(--rack-gap);
  align-items:center; align-items:safe center;   /* centre when it fits, fall back to top when a card is taller than the stage */
  overflow-x:auto; overflow-y:auto; overscroll-behavior:contain;
  scroll-snap-type:x mandatory; padding:34px 0 20px; scrollbar-width:none; perspective:1500px; }
#game-shell .rack::-webkit-scrollbar { display:none; }
/* The room that lets the FIRST and LAST card reach the centre is real flex spacers, not
   padding-inline. Safari does not count a scroll container's *end* padding as scrollable
   overflow, so with padding there was simply no scroll range to centre the newest card into
   (no scroll API could fix that) — and the old `calc(50% - 190px)` was in any case 4px short
   of the 372px card at desktop, and 26px short at mobile. Basis = half the leftover minus one
   gap, which lands every card dead centre (verified WebKit + Chromium, 1-6 cards, 375-1200px).
   Pseudo-elements deliberately: they are flex items but NOT in `rack.children`, so the
   engine's card indexing/dot sync is untouched. */
#game-shell .rack::before, #game-shell .rack::after { content:""; flex:0 0 max(0px, calc(50% - var(--card-w)/2 - var(--rack-gap))); }
@media (max-width:520px){ #game-shell .rack { padding:34px 0 18px; }
  /* Too narrow for brand + controls on one row: stack the controls below the wordmark. */
  #game-shell .brandrow { flex-wrap:wrap; }
  #game-shell .brand { flex:1 1 100%; }
  /* On its own row the controls span full width and reverse: the status card and archive sit
     at the left (card under the wordmark), and the theme toggle is pushed to the right edge —
     margin-left:auto opens the flexible space between the archive and theme buttons. */
  #game-shell .rightcluster { flex:1 1 100%; flex-direction:row-reverse; }
  #game-shell #themeBtn { margin-left:auto; }
}

/* Short viewports: reclaim vertical space so a full card fits without scrolling where
   possible; where it still can't, the safe-centre + overflow-y above lets you scroll to it. */
@media (max-height:760px){
  #game-shell .topbar { padding:7px 14px 6px; }
  #game-shell .legend { margin-top:6px; gap:5px 10px; font-size:10.5px; }
  #game-shell .legend .demo { font-size:12px; padding:2px 9px; }
  #game-shell .rack { padding-top:30px; }
  #game-shell .dock { padding:6px 14px 5px; }
  #game-shell .dockpanel { padding:8px; }
  #game-shell .dots { margin-bottom:7px; }
}
@media (max-height:620px){
  #game-shell .legend { display:none; }   /* colour key hidden on very short screens; marks are self-evident + in the result modal */
}
/* Card compaction on short screens — trims a card's height ~60px so it clears the stage without scrolling. */
@media (max-height:700px){
  #game-shell .recipe { padding:20px 16px 14px 38px; }
  #game-shell .r-title { font-size:20px; padding-top:9px; }
  #game-shell .r-tags { margin:7px 0 2px; }
  #game-shell .frow { padding:2.5px 0; min-height:22px; }
  #game-shell .r-ing { margin-top:8px; }
  #game-shell .r-ing li { padding:2px 0; font-size:14px; }
  #game-shell .recipe.blank { min-height:0; }
}

/* `[data-mk-demo]` is the hub's contained "How it works" example (see app.js renderHub()): it
   carries Recipedle's real card + mark tokens as inline custom properties on that one element
   (exactly like each hub game-card already tints from an inline `--accent`, see hub.css), so the
   example is a truthful, unforked reuse of these rules without the rest of the hub adopting a
   game's theme. Only the card/frow/mark selectors below are broadened this way — nothing else
   in this file (topbar, dock, modal, etc.) is reachable outside `#game-shell`. */
#game-shell .recipe, [data-mk-demo] .recipe {
  scroll-snap-align:center; flex:0 0 var(--card-w, min(86vw, 372px)); max-width:372px; position:relative;
  background: repeating-linear-gradient(var(--card) 0, var(--card) 27px, var(--card-rule) 27px, var(--card-rule) 28px);
  background-color:var(--card); border-radius:var(--card-radius); padding:24px 18px 16px 40px; color:var(--card-ink);
  box-shadow:var(--shadow); border:1px solid var(--card-edge); transform:rotate(var(--tilt,-.6deg)); transform-origin:top center;
}
#game-shell .recipe::before, [data-mk-demo] .recipe::before { content:""; position:absolute; top:0; bottom:0; left:31px; width:2px; background:var(--card-margin); opacity:.8; }
#game-shell .recipe.flipin { animation:flipin .55s cubic-bezier(.2,.8,.25,1) both; }
@keyframes flipin { from { transform:rotateX(-88deg) rotate(var(--tilt,-.6deg)); opacity:0; } to { transform:rotateX(0) rotate(var(--tilt,-.6deg)); opacity:1; } }
#game-shell .recipe.win { box-shadow:0 0 0 2px var(--mk-good), var(--shadow); }

#game-shell .r-num { position:absolute; top:13px; right:15px; font-family:var(--font-hand); font-size:19px; color:var(--card-accent); font-weight:700; transform:rotate(4deg); }
#game-shell .r-title { font-family:var(--font-display); font-weight:700; font-size:22px; line-height:1.08; padding-top:11px; display:flex; align-items:center; gap:7px; }
#game-shell .r-rule { height:0; border-bottom:2px solid var(--card-accent); margin:6px 0 3px; position:relative; }
#game-shell .r-rule::after { content:""; position:absolute; left:0; right:0; top:3px; border-bottom:1px solid var(--card-accent); opacity:.5; }
#game-shell .r-tags { display:flex; flex-wrap:wrap; gap:5px; margin:9px 0 2px; }
#game-shell .r-tag { font-family:var(--font-ui); font-size:10.5px; font-weight:700; padding:2px 8px; border-radius:4px; background:var(--card-chip); color:var(--card-chip-ink); transform:rotate(-1deg); }
#game-shell .r-tag.g { background:var(--card-chip-on); color:var(--card-chip-on-ink); }

#game-shell .r-fields { margin-top:8px; }
#game-shell .frow, [data-mk-demo] .frow { display:flex; align-items:baseline; gap:7px; padding:4px 0; min-height:26px; }
#game-shell .frow .fl, [data-mk-demo] .frow .fl { font-size:10px; letter-spacing:.09em; text-transform:uppercase; font-weight:700; color:var(--card-accent); opacity:.85; flex:0 0 auto; }
#game-shell .frow .lead, [data-mk-demo] .frow .lead { flex:1; border-bottom:1.5px dotted var(--card-lead); transform:translateY(-3px); }
#game-shell .frow .fv, [data-mk-demo] .frow .fv { font-family:var(--font-display); font-size:16px; font-weight:600; display:inline-flex; align-items:center; gap:5px; flex:0 0 auto; color:var(--card-ink); }
#game-shell .frow .arw, [data-mk-demo] .frow .arw { display:inline-flex; align-items:center; }
/* set-overlap per-member marks (TS-016): renderSetOverlap() in engine.js puts several `.mk`
   marks (one per guessed value, e.g. each native-range region) inline on one row inside a
   single `.ov-members` wrapper — one flex child of `.fv`, so the comma-separated marks lay out
   as ordinary text rather than each fighting `.fv`'s own flex `gap`. A little horizontal margin
   keeps neighbouring marks' hand-drawn SVGs (which bleed a few px past their own text box, see
   MARK_PAD in placeMarks()) from visually touching on a tight comma-separated list; text wraps
   normally at the comma/space between members on narrow (mobile) widths. */
#game-shell .ov-members, [data-mk-demo] .ov-members { display:inline; }
#game-shell .ov-members .mk, [data-mk-demo] .ov-members .mk { margin:0 3px; }

/* set-overlap ROW-LEVEL overall-state cue (TS-016 orchestrator-review fix). Per-member marks
   alone cannot show the exact-vs-partial(subset) distinction gradeSetOverlap computes: a guess
   whose set is a strict SUBSET of a larger answer set grades overall "y" (gold) even though
   every member the player named is individually matched (green) — without this, that case is
   pixel-identical to an overall "g" exact match. A subtle left-border, keyed to the SAME tokens
   the per-member marks already use (--mk-good/--mk-hi-2/--mk-bad), carries the overall tri-state
   as a secondary cue under the per-member marks — deliberately border-only, no background fill,
   so it stays quiet rather than fighting the hand-drawn marks. The compensating negative margin
   keeps the label column aligned with ordinary (non-`.ov-*`) rows above/below it. The neutral
   "n" tile gets no `.ov-*` class at all (see setOverlapRow() in engine.js) — TS-005's plain
   inert treatment stands alone, unchanged. */
#game-shell .frow.ov-g, #game-shell .frow.ov-y, #game-shell .frow.ov-x,
[data-mk-demo] .frow.ov-g, [data-mk-demo] .frow.ov-y, [data-mk-demo] .frow.ov-x {
  border-left:3px solid transparent; padding-left:7px; margin-left:-10px; border-radius:0 3px 3px 0;
}
#game-shell .frow.ov-g, [data-mk-demo] .frow.ov-g { border-left-color:var(--mk-good); }
#game-shell .frow.ov-y, [data-mk-demo] .frow.ov-y { border-left-color:var(--mk-hi-2); }
#game-shell .frow.ov-x, [data-mk-demo] .frow.ov-x { border-left-color:var(--mk-bad); }

/* ---------- hand-drawn marks ----------
   The insets below are only a pre-measurement fallback: engine.js's placeMarks() sizes and
   offsets each mark to the *glyphs* it annotates (left/top/width/height inline), because a
   fixed inset stretches the mark over the whole inline box and drifts further the longer the
   value is. Note `.mk` keeps its inherited line-height: the box's line-height padding no
   longer offsets the mark (placeMarks measures the glyphs, not the box), and `line-height:1`
   here would drag `.fv`'s baseline — and so every value — up out of line with its label. */
#game-shell .mk, [data-mk-demo] .mk { position:relative; display:inline-block; }
#game-shell .ann, [data-mk-demo] .ann { position:absolute; top:-6px; left:-9px; right:-9px; bottom:-6px; overflow:visible; pointer-events:none; }
#game-shell .ann path, [data-mk-demo] .ann path { fill:none; stroke-width:2.2; stroke-linecap:round; stroke-linejoin:round; filter:url(#rough); }
#game-shell .ann.circle path, [data-mk-demo] .ann.circle path { stroke:var(--mk-good); stroke-width:2.1; }
#game-shell .ann.strike path, [data-mk-demo] .ann.strike path { stroke:var(--mk-bad); stroke-width:2.6; filter:url(#rough2); }
#game-shell .ann.check { position:static; inset:auto; width:20px; height:20px; display:inline-block; }
#game-shell .ann.check path { stroke:var(--mk-good); stroke-width:2.8; filter:url(#rough2); }
#game-shell .ann.arrow, [data-mk-demo] .ann.arrow { position:static; inset:auto; width:15px; height:19px; display:inline-block; vertical-align:-4px; }
#game-shell .ann.arrow path, [data-mk-demo] .ann.arrow path { stroke:var(--mk-arrow); stroke-width:2.3; filter:url(#rough2); }

#game-shell .mk.hl, [data-mk-demo] .mk.hl { position:relative; }
#game-shell .mk.hl .hilite, [data-mk-demo] .mk.hl .hilite { position:absolute; top:-3px; left:-8px; right:-8px; bottom:-2px; z-index:-1; mix-blend-mode:multiply; overflow:visible; transform:rotate(-1.2deg); }
#game-shell .mk.hl .hilite .swipe,  [data-mk-demo] .mk.hl .hilite .swipe  { fill:var(--mk-hi); filter:url(#rough2); }
#game-shell .mk.hl .hilite .swipe2, [data-mk-demo] .mk.hl .hilite .swipe2 { fill:var(--mk-hi-2); filter:url(#rough2); }

/* Neutral/not-applicable tile (state "n"): deliberately carries no hand-drawn SVG (no circle,
   strike, or highlight) — a muted ink with a dotted underline reads as "does not apply" without
   looking like either a match or a miss. Paired under [data-mk-demo] so the hub's "How it works"
   modal can never contradict the real board (see howMark() in app.js). */
#game-shell .mk-neutral, [data-mk-demo] .mk-neutral {
  color:var(--mk-neutral); font-style:italic; opacity:.82;
  text-decoration:underline dotted; text-decoration-color:var(--mk-neutral); text-underline-offset:3px;
}

#game-shell .recipe.flipin .ann path { stroke-dasharray:1; stroke-dashoffset:1; animation:draw .5s .2s ease forwards; }
@keyframes draw { to { stroke-dashoffset:0; } }
#game-shell .recipe.flipin .mk.hl .hilite { animation:hlin .42s .22s ease both; }
@keyframes hlin { from { clip-path:inset(0 100% 0 0); } to { clip-path:inset(0 0 0 0); } }

@media (prefers-reduced-motion: reduce) {
  #game-shell .recipe.flipin { animation:none; }
  #game-shell .recipe.flipin .ann path { animation:none; stroke-dashoffset:0; }
  #game-shell .recipe.flipin .mk.hl .hilite { animation:none; clip-path:none; }
  #game-shell .modal, #game-shell .scrim.show, #game-shell .toast { animation:none !important; }
  /* The feedback acknowledgement fades in when it replaces the choice row, the disclosure panel
     fades in when expanded, and the toggle's chevron rotates — all listed separately because the
     rule above matches `.modal` itself, not its descendants. */
  #game-shell .modal .fb-thanks, #game-shell .modal .fb-panel { animation:none !important; }
  #game-shell .modal .fb-toggle::after { transition:none !important; }
}

#game-shell .r-ing { margin-top:10px; }
#game-shell .r-ing .ih { font-family:var(--font-hand); font-size:19px; font-weight:700; color:var(--card-accent); }
#game-shell .r-ing ul { list-style:none; margin-top:2px; }
#game-shell .r-ing li { display:flex; align-items:center; gap:7px; padding:3px 0; font-family:var(--font-display); font-size:15px; }
#game-shell .r-ing li .chk { width:20px; height:20px; flex:0 0 20px; display:inline-flex; align-items:center; justify-content:center; }
#game-shell .r-ing li.off { color:var(--card-ink-soft); }
#game-shell .r-ing li .mainflag { font-family:var(--font-ui); font-size:9px; letter-spacing:.06em; text-transform:uppercase; font-weight:700;
  color:var(--card-accent); background:var(--card-flag); padding:1px 6px; border-radius:4px; margin-left:4px; }

#game-shell .stamp { position:absolute; bottom:16px; right:16px; font-family:var(--font-display); font-weight:700; font-size:16px; color:var(--mk-good);
  border:3px double var(--mk-good); border-radius:8px; padding:3px 11px; transform:rotate(-11deg); opacity:.9; letter-spacing:.04em; }

#game-shell .recipe.blank { display:grid; place-items:center; text-align:center; color:var(--card-ink-faint); min-height:452px; --tilt:-.6deg; }
#game-shell .recipe.blank .bh { font-family:var(--font-hand); font-size:26px; color:var(--card-accent); }
#game-shell .recipe.blank .bs { font-family:var(--font-display); font-size:15px; margin-top:6px; max-width:220px; }

#game-shell .chev { position:absolute; top:50%; transform:translateY(-50%); width:40px; height:40px; border-radius:50%; z-index:5;
  border:1px solid var(--panel-edge); background:color-mix(in srgb, var(--bg) 82%, transparent); color:var(--ink); font-size:18px; cursor:pointer;
  display:grid; place-items:center; transition:.15s; backdrop-filter:blur(3px); }
#game-shell .chev:hover { border-color:var(--trim); }
#game-shell .chev:disabled { opacity:0; pointer-events:none; }
#game-shell .chev.prev { left:10px; }
#game-shell .chev.next { right:10px; }

/* ---------- Guess dock ---------- */
#game-shell .dock { flex:0 0 auto; padding:8px 14px 6px; }
#game-shell .dockpanel { background:var(--panel); border:1px solid var(--panel-edge); border-radius:16px; box-shadow:var(--shadow); padding:10px; position:relative; }
#game-shell .dots { display:flex; gap:5px; justify-content:center; margin-bottom:9px; flex-wrap:wrap; }
#game-shell .dots i { width:6px; height:6px; border-radius:50%; background:var(--panel-edge); display:block; transition:.2s; }
#game-shell .dots i.on { background:var(--accent); width:16px; border-radius:99px; }
#game-shell .dots i.win { background:var(--mk-good); }
#game-shell .inputrow { display:flex; gap:9px; }
#game-shell .field { flex:1; position:relative; }
#game-shell .field input { width:100%; background:color-mix(in srgb, var(--bg) 70%, #000 8%); border:1.5px solid var(--panel-edge); color:var(--ink); font-size:16px; font-family:inherit;
  padding:14px 15px; border-radius:12px; outline:none; transition:border-color .15s; }
#game-shell .field input::placeholder { color:var(--ink-faint); }
#game-shell .field input:focus { border-color:var(--accent); }
#game-shell .gbtn { background:var(--accent); color:var(--accent-ink); border:none; font-weight:700; font-size:15px; padding:0 20px; border-radius:12px;
  cursor:pointer; font-family:inherit; transition:background .15s, transform .05s; }
#game-shell .gbtn:hover { background:var(--accent-d); }
#game-shell .gbtn:active { transform:translateY(1px); }
#game-shell .gbtn:disabled { background:var(--panel-edge); color:var(--ink-faint); cursor:not-allowed; }
#game-shell .donebar { display:none; gap:9px; }
#game-shell .donebar .gbtn { flex:1; padding:13px 0; }
#game-shell .suggest { position:absolute; left:10px; right:10px; bottom:calc(100% + 6px); background:var(--bg-2); border:1px solid var(--panel-edge);
  border-radius:13px; box-shadow:var(--shadow); overflow:hidden; max-height:44vh; overflow-y:auto; z-index:30;
  /* The unfiltered list is the whole pool, so it always scrolls — keep that scroll inside the
     panel instead of handing it to the page once the list bottoms out (mobile especially). */
  overscroll-behavior:contain; -webkit-overflow-scrolling:touch; }
#game-shell .suggest button { display:flex; width:100%; align-items:center; gap:10px; text-align:left; background:none; border:none; color:var(--ink);
  font-family:inherit; font-size:15px; padding:12px 15px; cursor:pointer; border-bottom:1px solid color-mix(in srgb, var(--ink) 4%, transparent); }
#game-shell .suggest button:last-child { border-bottom:none; }
#game-shell .suggest button:hover, #game-shell .suggest button.hi { background:color-mix(in srgb, var(--accent) 15%, transparent); }
#game-shell .suggest .dishname { font-weight:500; }

#game-shell footer { flex:0 0 auto; display:flex; flex-wrap:wrap; align-items:baseline; justify-content:center;
  column-gap:.5em; row-gap:1px; text-align:center; font-size:11px; color:var(--ink-faint); padding:7px 14px 9px;
  border-top:1px solid var(--panel-edge); background:color-mix(in srgb, var(--bg) 88%, #000 6%); }
#game-shell footer .fam { color:var(--ink-soft); }
/* Copyright stays an unbreakable unit so it drops to its own row intact rather than mid-name. */
#game-shell footer .copy { white-space:nowrap; }
/* Middle dot only while both parts share a line; hidden once the copyright wraps to its own row. */
#game-shell footer .copy::before { content:"·"; margin-right:.5em; color:var(--ink-faint); }
/* Below ~one-line width the copyright drops to its own row; drop the leading dot with it
   (the column-gap still separates the two parts should they briefly share a line here). */
@media (max-width:545px){ #game-shell footer .copy::before { content:none; } }
#game-shell footer a { color:var(--ink-soft); text-decoration:none; }
#game-shell footer a:hover { color:var(--hi); }
/* Footer "Feedback" link — a real button (opens a modal, doesn't navigate) styled to sit quietly
   alongside the family tagline and copyright, same weight/colour, own leading dot to match. */
#game-shell footer .footfb { background:none; border:none; padding:0; margin:0; font:inherit; font-size:11px;
  color:var(--ink-soft); cursor:pointer; }
#game-shell footer .footfb:hover { color:var(--hi); }
#game-shell footer .footfb::before { content:"·"; margin-right:.5em; color:var(--ink-faint); }
@media (max-width:545px){ #game-shell footer .footfb::before { content:none; } }

#game-shell .scrim { position:fixed; inset:0; background:var(--scrim); backdrop-filter:blur(4px); display:none; align-items:center; justify-content:center; padding:20px; z-index:60; }
#game-shell .scrim.show { display:flex; animation:fade .2s; }
#game-shell .modal { position:relative; background:var(--bg-2); border:1px solid var(--panel-edge); border-radius:22px; width:100%; max-width:390px; padding:26px 24px; text-align:center; box-shadow:var(--shadow); animation:popm .3s cubic-bezier(.2,.9,.3,1.3); }
@keyframes popm { from { transform:scale(.9); opacity:0; } to { transform:none; opacity:1; } }
#game-shell .mclose { position:absolute; top:12px; right:14px; width:30px; height:30px; border-radius:50%; border:1px solid var(--panel-edge); background:transparent; color:var(--ink-soft); font-size:18px; cursor:pointer; line-height:1; }
#game-shell .mclose:hover { background:color-mix(in srgb, var(--ink) 6%, transparent); color:var(--ink); }
#game-shell .modal .rlabel { font-size:12px; letter-spacing:.16em; text-transform:uppercase; font-weight:700; color:var(--hi); }
#game-shell .modal h2 { font-family:var(--font-display); font-size:27px; margin:8px 0 2px; }
#game-shell .modal .answer { font-family:var(--font-display); font-size:22px; color:var(--accent); margin:2px 0 4px; }
#game-shell .modal .flagbig { font-size:38px; margin:2px 0; }
#game-shell .modal p { color:var(--ink-soft); font-size:14px; line-height:1.5; }
#game-shell .modal .grid { margin:14px 0; font-size:15px; line-height:1.35; letter-spacing:2px; font-family:monospace; }
#game-shell .modal .row2 { display:flex; gap:10px; margin-top:16px; }
#game-shell .modal .row2 .gbtn { flex:1; padding:13px 0; }
#game-shell .gbtn.ghost { background:transparent; border:1.5px solid var(--panel-edge); color:var(--ink); }
#game-shell .gbtn.ghost:hover { background:color-mix(in srgb, var(--ink) 5%, transparent); }
/* End-of-game fairness vote (engine.js showResult → renderFeedback; also the footer's own small
   modal, openFooterFeedback). Quiet by design: it sits below the share row, reads as a footnote
   rather than a second call to action, and every colour resolves through the shell's own tokens
   so it works unchanged in both themes and in any game's palette. `[hidden]` is restated per
   element because the display rules below would otherwise beat the UA's [hidden] rule — same
   pattern as #game-shell[hidden] in styles/base.css. */
#game-shell .modal .fb { margin-top:16px; padding-top:14px; border-top:1px solid var(--panel-edge); text-align:left; }
#game-shell .modal .fb[hidden], #game-shell .modal .fb-q[hidden], #game-shell .modal .fb-panel[hidden],
#game-shell .modal .fb-row[hidden], #game-shell .modal .fb-thanks[hidden] { display:none; }
#game-shell .modal .fb-q { font-size:13px; color:var(--ink-soft); margin-bottom:9px; }
/* The disclosure trigger IS the question (or, once voted, the acknowledgement) — a real button,
   collapsed by default, so the prompt is always present without ever nagging a repeat visitor.
   A player who wants to change a vote can still tap it open again (see renderFeedback()). */
#game-shell .modal .fb-toggle { display:flex; align-items:center; gap:7px; width:100%; text-align:left;
  background:none; border:none; padding:0; margin:0; font:inherit; font-size:13px; color:var(--ink-soft); cursor:pointer; }
#game-shell .modal .fb-toggle:hover { color:var(--ink); }
#game-shell .modal .fb-toggle::after { content:"▾"; font-size:10px; color:var(--ink-faint); transition:transform .15s; }
#game-shell .modal .fb-toggle[aria-expanded="true"]::after { transform:rotate(180deg); }
#game-shell .modal .fb-panel { margin-top:9px; animation:fade .15s; }
#game-shell .modal .fb-row { display:flex; gap:8px; }
#game-shell .modal .fb-opt { flex:1 1 0; min-width:0; padding:10px 6px; border-radius:11px;
  border:1.5px solid var(--panel-edge); background:transparent; color:var(--ink);
  font-family:inherit; font-size:14px; font-weight:600; line-height:1.2; cursor:pointer; transition:.15s; }
#game-shell .modal .fb-opt:hover { border-color:var(--trim); background:color-mix(in srgb, var(--ink) 5%, transparent); }
#game-shell .modal .fb-opt:active { transform:translateY(1px); }
#game-shell .modal .fb-opt:disabled { color:var(--ink-faint); cursor:default; }
#game-shell .modal .fb-thanks { font-size:13px; color:var(--ink-soft); animation:fade .2s; }

#game-shell .countdown { margin-top:14px; font-size:12px; color:var(--ink-faint); }
#game-shell .countdown b { color:var(--ink); font-variant-numeric:tabular-nums; }

#game-shell .toast { position:fixed; bottom:80px; left:50%; transform:translateX(-50%) translateY(20px); background:var(--ink); color:var(--bg);
  font-weight:600; font-size:14px; padding:11px 18px; border-radius:12px; opacity:0; pointer-events:none; transition:.25s; z-index:70; }
#game-shell .toast.show { opacity:1; transform:translateX(-50%) translateY(0); }

/* ================= Placard card layout (TS-017) =================================================
   A wholly separate card look for games that declare `layout: "placard"` (Botanidle). engine.js's
   makePlacardCard() builds a `.placard` element INSTEAD of a `.recipe`, so none of the Recipedle
   `.recipe`/`.frow`/`.mk` rules (or the `[data-mk-demo]` hub demo) above are touched — every rule
   below is scoped to `.placard` and its `.pm-*` children only. All colours resolve through the
   same theme tokens (a game supplies the botanic-garden values in games/botany.js); the card is
   parchment in both themes. The grade is drawn ON TOP by hand: a green loop (exact), an amber
   highlighter (close), a red scratch (miss), a clean black rule-through (not-applicable). Marks
   self-fit their box via a `preserveAspectRatio="none"` viewBox + `vector-effect:non-scaling-stroke`
   (constant stroke width regardless of the stretch), so they need no placeMarks() measuring pass. */

/* Rack item: same flex-basis / scroll-snap / tilt contract as a `.recipe` so the rolodex nav,
   centring and dots (which index rack.children generically) work unchanged. */
#game-shell .placard { scroll-snap-align:center; flex:0 0 min(93vw, 430px); max-width:430px;
  position:relative; display:flex; flex-direction:column; align-items:stretch;
  transform:rotate(var(--tilt,-.4deg)); transform-origin:top center; }
#game-shell .placard.flipin { animation:pm-in .5s cubic-bezier(.2,.8,.25,1) both; }
@keyframes pm-in { from { transform:translateY(12px) rotate(var(--tilt,-.4deg)); opacity:0; } to { transform:translateY(0) rotate(var(--tilt,-.4deg)); opacity:1; } }

/* The printed label: warm parchment inside a thick deep-green painted frame, with a fine brass
   inner ring. isolation:isolate makes it the stacking context the highlighter's z-index:-1 resolves
   into (so the amber sits above the parchment but below the value text — like a real marker). */
#game-shell .pm-frame { position:relative; isolation:isolate; background:var(--card); color:var(--card-ink);
  border:7px solid var(--card-frame); border-radius:var(--card-radius); padding:15px 16px 17px;
  box-shadow:var(--shadow), inset 0 0 0 1px color-mix(in srgb, var(--card-brass) 34%, transparent); }
#game-shell .placard.win .pm-frame { box-shadow:0 0 0 2px var(--mk-good), var(--shadow); }

/* Header: the plant name + sci subtitle on the left; an outlined brass "specimen no.N" tag with the
   "Lot N of 10" counter beneath it on the right; a green header rule below. */
#game-shell .pm-head { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; }
#game-shell .pm-head-main { min-width:0; }
#game-shell .pm-meta { display:flex; flex-direction:column; align-items:flex-end; gap:4px; flex:0 0 auto; text-align:right; }
#game-shell .pm-no { font-family:var(--font-ui); font-size:10.5px; font-weight:700; letter-spacing:.02em;
  color:var(--card-brass); background:transparent; border:1px solid color-mix(in srgb, var(--card-brass-edge) 85%, transparent);
  border-radius:999px; padding:3px 11px; white-space:nowrap; }
#game-shell .pm-no b { font-weight:800; }
#game-shell .pm-count { font-family:var(--font-ui); font-size:10.5px; letter-spacing:.02em; font-weight:600; color:var(--card-ink-soft); white-space:nowrap; }
#game-shell .pm-name { font-family:var(--font-display); font-weight:700; font-size:26px; line-height:1.05; color:var(--card-ink);
  display:flex; align-items:center; gap:8px; }
#game-shell .pm-name .pm-emoji { font-size:.92em; flex:0 0 auto; }
#game-shell .pm-sci { font-family:var(--font-hand); font-size:15px; line-height:1.1; color:var(--card-ink-soft); margin-top:2px; }
#game-shell .pm-sci i { font-style:italic; }
#game-shell .pm-rule { height:0; border-bottom:2px solid var(--card-margin); margin:9px 0 3px; position:relative; }
#game-shell .pm-rule::after { content:""; position:absolute; left:0; right:0; top:3px; border-bottom:1px solid var(--card-margin); opacity:.5; }

/* Sections + rows */
#game-shell .pm-section { margin-top:9px; }
#game-shell .pm-sec { font-family:var(--font-ui); font-size:10px; letter-spacing:.06em; font-weight:800;
  color:var(--card-ink-soft); margin:2px 0 2px; }
#game-shell .pm-row { position:relative; display:flex; align-items:baseline; gap:8px; padding:5px 0; min-height:24px;
  min-width:0; border-bottom:1px dotted var(--card-rule); }
#game-shell .pm-section:last-child .pm-row:last-child { border-bottom:none; }
#game-shell .pm-l { position:relative; flex:0 0 auto; font-family:var(--font-ui); font-size:11px; letter-spacing:.02em;
  font-weight:700; color:var(--card-accent); }
/* Quiet leading row icon (inline SVG, currentColor) — supports the label, never dominates. */
#game-shell .pm-icon { flex:0 0 auto; align-self:center; width:15px; height:15px; color:var(--card-ink-faint); opacity:.8; }
#game-shell .pm-icon path, #game-shell .pm-icon circle { fill:none; stroke:currentColor; stroke-width:1.6px;
  stroke-linecap:round; stroke-linejoin:round; }
#game-shell .pm-lead { flex:1 1 auto; min-width:8px; }
#game-shell .pm-val { flex:0 1 auto; min-width:0; display:flex; flex-wrap:wrap; align-items:center; justify-content:flex-end; gap:6px;
  font-family:var(--font-display); font-size:15px; font-weight:600; color:var(--card-ink); text-align:right; }
#game-shell .pm-v { position:relative; display:inline-block; }
#game-shell .pm-v.pm-muted { color:var(--card-ink-faint); }
#game-shell .pm-arw { display:inline-flex; align-items:center; }

/* Full-width set rows (`display: "tags"`, e.g. Native to / Flower colour): the label sits on its own
   line, then the member tags wrap across the WHOLE card below it — left-aligned, never crammed into
   the value column. This is the primary readability fix for wide-genus cards (Rose's 16 regions). */
#game-shell .pm-row-tags { display:block; }
#game-shell .pm-tagrow-head { display:flex; align-items:center; gap:6px; margin-bottom:6px; }
#game-shell .pm-tags-full { justify-content:flex-start; max-width:100%; }

/* Wrapping origin/colour tags — the overflow fix: many members wrap within the card, never bleed. */
#game-shell .pm-tags { display:flex; flex-wrap:wrap; gap:5px; justify-content:flex-end; min-width:0; max-width:100%; }
#game-shell .pm-tag { position:relative; overflow:visible; font-family:var(--font-ui); font-size:11px; font-weight:700; line-height:1.2;
  padding:3px 9px; border-radius:999px; background:var(--card-chip); color:var(--card-chip-ink);
  border:1px solid transparent; max-width:100%; overflow-wrap:anywhere; }
/* Matched (g) and missed (x) tags share the SAME neutral parchment chip — the hand-drawn green loop
   (match) or red scratch (miss) drawn on top is the sole indicator, not a coloured pill. */
#game-shell .pm-tag.muted { opacity:.72; font-style:italic; font-weight:600; }
/* "+N more" chip closing a capped wide set (nativeRange) — quiet, not a guessable value. */
#game-shell .pm-more { font-family:var(--font-ui); font-size:11px; font-weight:600; color:var(--card-ink-faint);
  align-self:center; padding:3px 4px; white-space:nowrap; }

/* Hand-drawn marks, drawn on top of the value/tag (or, for a subset-gold set, over the row label).
   Absolute overlay stretched to the box; non-scaling-stroke keeps the ink a constant width. */
/* Stretch the mark svg to cover its text box + a little slack. Explicit width/height (not right/bottom
   offsets) is REQUIRED: an <svg> with a viewBox and height:auto takes its height from the viewBox
   aspect ratio and ignores the `bottom` offset, which left every mark sitting ~5px above its text. */
#game-shell .pm-mark { position:absolute; left:-6px; top:-5px; width:calc(100% + 12px); height:calc(100% + 10px); overflow:visible; pointer-events:none; }
#game-shell .pm-mark path { fill:none; stroke-linecap:round; stroke-linejoin:round; vector-effect:non-scaling-stroke; }
#game-shell .pm-loop path { stroke:var(--mk-good); stroke-width:2.2px; filter:url(#rough2); }
/* Quieter miss ink: a thin, translucent scratch that ANNOTATES rather than obliterates — the value/
   tag text stays legible through it, and the green exact-match loops visually dominate over misses.
   This also de-noises tall wide-genus cards (e.g. Rose's 16 native regions, half of them missed). */
#game-shell .pm-scratch path { stroke:var(--mk-bad); stroke-width:1.5px; opacity:.5; filter:url(#rough2); }
#game-shell .pm-hilite { z-index:-1; mix-blend-mode:multiply; }
/* A FILLED highlighter blob (see PM_HILITE): fills the box behind the text, so it reads as a marker
   swipe over the whole word rather than a thin underline. */
#game-shell .pm-hilite path { fill:var(--mk-hi); stroke:none; filter:url(#rough2); }
#game-shell .pm-l-hi { padding:0 3px; }
#game-shell .pm-l-hi .pm-hilite { left:-4px; top:-3px; width:calc(100% + 8px); height:calc(100% + 6px); }

/* Not-applicable ("n") row: a clean black rule struck through the WHOLE row, text muted — never
   the red scratch (which would read as a miss the answer never had). */
#game-shell .pm-row.pm-na { color:var(--card-ink-faint); }
#game-shell .pm-row.pm-na .pm-l { color:var(--card-ink-faint); }
#game-shell .pm-nastrike { position:absolute; left:0; right:0; top:50%; transform:translateY(-50%); width:100%; height:14px; overflow:visible; pointer-events:none; }
#game-shell .pm-nastrike path { fill:none; stroke:var(--mk-na); stroke-width:2.4px; stroke-linecap:round; vector-effect:non-scaling-stroke; filter:url(#rough2); }

/* The freestanding stand: a stake descending from the card onto a base plate that casts the shadow. */
/* Themed empty state: the parchment frame + stand holding the "empty specimen card" prompt. */
#game-shell .pm-blank { display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; min-height:340px; }
#game-shell .pm-blank-h { font-family:var(--font-hand); font-size:27px; color:var(--card-accent); }
#game-shell .pm-blank-s { font-family:var(--font-display); font-size:15px; color:var(--card-ink-soft); margin-top:6px; max-width:240px; }
#game-shell .pm-stand { display:flex; flex-direction:column; align-items:center; position:relative; }
#game-shell .pm-stake { width:16px; height:92px; background:linear-gradient(90deg, color-mix(in srgb, var(--card-frame) 78%, #000), var(--card-frame) 45%, color-mix(in srgb, var(--card-frame) 70%, #000)); border-radius:0 0 2px 2px; }
#game-shell .pm-base { width:104px; height:14px; margin-top:-1px; border-radius:3px;
  background:var(--card-frame); clip-path:polygon(9% 0, 91% 0, 100% 100%, 0 100%); }
#game-shell .pm-stand::after { content:""; width:150px; height:14px; margin-top:4px; border-radius:50%;
  background:radial-gradient(closest-side, rgba(0,0,0,.32), rgba(0,0,0,0)); }
@media (max-height:700px){ #game-shell .pm-stake { height:58px; } }

@media (prefers-reduced-motion: reduce) {
  #game-shell .placard.flipin { animation:none; }
}
/* Short-viewport compaction, matching the .recipe rules above. */
@media (max-height:700px){
  #game-shell .pm-frame { padding:12px 14px 14px; }
  #game-shell .pm-name { font-size:22px; }
  #game-shell .pm-row { padding:3.5px 0; min-height:22px; }
  #game-shell .pm-section { margin-top:7px; }
}

/* ---- About modal (footer "About") -------------------------------------------------------
   A sentence or two per game (config.copy.about), so this is small on purpose. Reuses .scrim and
   .modal wholesale and only sets the text block, inheriting the game's own tokens. */
#game-shell .modal.about { max-width:min(420px, 92vw); }
#game-shell .modal.about h2 { margin:0 0 10px; font-family:var(--font-display); font-size:22px; }
#game-shell .modal.about .ab-intro { margin:0 0 16px; color:var(--ink); font-size:15px; line-height:1.55; }
#game-shell .modal.about .ab-foot { margin:0; font-size:12px; letter-spacing:.02em; color:var(--ink-faint);
  border-top:1px solid color-mix(in srgb, var(--ink-faint) 26%, transparent); padding-top:12px; }
