/* =====================================================================
   World Explorers  ·  client/brand.css
   Purpose:  The shared visual language — the COMPLETE design-token layer
             (REQ-WEX-037 / CODE-WEX-018). Every themeable color, radius, and
             shadow is a CSS custom property; a theme is one day/night token
             block and night mode is a pure variable swap (no component restates
             a dark value). Day/night follows the OS preference by default and a
             manual [data-theme] override when the user or kit sets one (via
             theme.js).
   Author:   Frontend / UX (2026-07-17).
   Notes:    Zero-build — link it, don't import. Per-kit palette tokens
             (--accent, --ink, --paper, …) are layered on top at runtime by
             render.js from the active kit's palette{day,night}.
   ===================================================================== */
:root {
  /* Semantic colors (day). */
  --navy:#1E2A44; --ink:#243038; --teal:#2E8B9B; --pink:#E8657A; --gold:#FBD45E;
  --green:#6FBE7A; --sky:#6FB7D6; --purple:#B58CD6; --orange:#F2A03D;
  --paper:#ffffff; --muted:#5f7686;
  --card:rgba(255,255,255,.90); --cardline:#e3ebf2;
  /* The kit-supplied tokens' NO-KIT defaults. Pre-auth and grown-up pages
     (verify/app/migrate) never load a kit, so without these they had to declare
     their own `:root` block — which then shadowed this file and broke their night
     mode. Owning the defaults here is what lets those blocks go away
     (ADR-WEX-029 D3). Kit Zero's values, so a kit swap is a no-op on day one. */
  --accent:#2E8B9B; --coral:#E8657A;
  /* Text drawn ON an accent/teal fill (headers, primary buttons). White reads on
     the saturated day accent; in dark the kit's night accent is pale, so this
     flips to a dark ink for legibility (the idiomatic bright-accent/dark-text
     dark treatment). --error: danger/validation text, brightened for dark. */
  --on-accent:#ffffff; --error:#c0392b; --success:#1b7a3d;
  /* Text drawn ON a --gold fill (selected chips, "done" rows, win banners).
     Deliberately does NOT flip in night: unlike accent, gold stays bright in both
     modes, so the readable pairing is a dark ink in both. Rules that let this
     inherit --ink instead went light-on-gold at night (REQ-WEX-040 AC-6). */
  --on-gold:#1E2A44;
  /* Page background gradient stops. */
  --bg1:#BCE2F4; --bg2:#CFEAF6; --bg3:#D9F0DC; --bg4:#C7E9B6;
  /* Hub-tile paintbox (REQ-WEX-040 FR-5) — six fills a grid of choices cycles
     through, so adjacent tiles never share a colour and the grid reads as a
     paintbox rather than a table. Supplied per kit (declared or derived by
     Kit.kitPalette); these are the no-kit defaults. */
  --tile1:#FBE08A; --tile2:#A9DCEC; --tile3:#BFE6C9; --tile4:#C9C2EE; --tile5:#FFCF9E; --tile6:#F6B6C0;
  /* Type. ONE scale for the whole product (REQ-WEX-040). Headings had been declared
     per page and had drifted — h2 rendered at 16, 18, 19 and clamp(19–26)px across
     five pages, so the same rank of subheading changed size as a child moved
     around. Each step is fluid so it holds from a 390px phone to a wide tablet
     without a breakpoint. */
  --font:"Trebuchet MS","Segoe UI",system-ui,Verdana,sans-serif;
  --fs-title:clamp(24px,5vw,40px);   /* the page's own name */
  --fs-h2:clamp(19px,3.4vw,26px);    /* a section / band heading */
  --fs-h3:clamp(17px,2.4vw,20px);    /* a card or group heading */
  --fs-pagehead:clamp(20px,4vw,27px); /* the h1 inside a page-header card */
  --fs-body:16px;
  --fs-small:13.5px;                 /* captions, meta, sub-lines */
  /* Content-column widths. These had accreted rather than been chosen: in-app pages
     ran at 560 / 620 / 640 / 760 / 820px, so the page-header card — which is as wide
     as its column — visibly changed size between pages that are otherwise identical
     (Mysteries 560 next to Post Box 640). Three deliberate widths replace five
     accidental ones. */
  --w-read:640px;                    /* the default: a reading / list column */
  --w-wide:860px;                    /* galleries and cast grids that need the room */
  --w-narrow:520px;                  /* one focused task (onboarding, a game board) */
  --pad-col:18px;                    /* the content column's inset (was 16 on 17 pages, 18 on 9) */
  /* Radii + shadows (tokenized — no component carries a literal). */
  --radius-sm:12px; --radius:18px; --radius-lg:24px; --pill:999px;
  --shadow-card:0 8px 30px rgba(30,42,68,.12);
  --shadow-btn:0 4px 16px rgba(46,139,155,.34);
  /* The play-surface shadows (ADR-WEX-029 D1). A HARD, un-blurred offset shadow
     is what makes a tile read as a physical, pressable thing rather than a div —
     the blurred pair underneath only grounds it. --press-depth is how far a
     pressed element travels; it must equal the hard shadow's offset so the
     element visually lands flush on press. */
  --shadow-lift:0 7px 0 rgba(30,42,68,.13), 0 10px 20px rgba(30,42,68,.10);
  --shadow-lift-sm:0 4px 0 rgba(30,42,68,.13), 0 6px 14px rgba(30,42,68,.09);
  --press-depth:7px;
  --disc:rgba(255,255,255,.92);   /* the white circle behind every tile icon */
}

/* Night tokens — the ONLY place dark values live. Applied when the OS prefers
   dark (unless the user forced light), OR when the user forces dark. The two
   selectors carry the same token block by necessity (CSS cannot share a
   declaration set across a media-nested and a top-level rule); keep them in sync. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --navy:#EAF0FA; --ink:#EAF0FA; --muted:#9fb2c6;
    --paper:#233246; --card:rgba(40,52,79,.90); --cardline:#33425c;
    --accent:#7FD0DD; --coral:#F2889A;
    --bg1:#1b2740; --bg2:#20304c; --bg3:#233650; --bg4:#1d3346;
    --on-accent:#10233a; --error:#ff8a80; --success:#8fe0a5;
    --shadow-card:0 8px 30px rgba(0,0,0,.45);
    --shadow-btn:0 4px 16px rgba(0,0,0,.40);
    /* Night tiles are DARKENED, never inverted: --ink flips light in night, so a
       pastel fill would drop the label under AA. Each stays a dark hue-tinted
       surface a light ink clears comfortably (REQ-WEX-040 AC-6). */
    --tile1:#5e563d; --tile2:#405367; --tile3:#3b5751; --tile4:#365566; --tile5:#5b3e51; --tile6:#5d4b46;
    --shadow-lift:0 7px 0 rgba(0,0,0,.45), 0 10px 20px rgba(0,0,0,.35);
    --shadow-lift-sm:0 4px 0 rgba(0,0,0,.45), 0 6px 14px rgba(0,0,0,.30);
    --disc:rgba(240,246,255,.88);
  }
}
:root[data-theme="dark"] {
  --navy:#EAF0FA; --ink:#EAF0FA; --muted:#9fb2c6;
  --paper:#233246; --card:rgba(40,52,79,.90); --cardline:#33425c;
  --accent:#7FD0DD; --coral:#F2889A;
  --bg1:#1b2740; --bg2:#20304c; --bg3:#233650; --bg4:#1d3346;
  --on-accent:#10233a; --error:#ff8a80; --success:#8fe0a5;
  --shadow-card:0 8px 30px rgba(0,0,0,.45);
  --shadow-btn:0 4px 16px rgba(0,0,0,.40);
  --tile1:#5e563d; --tile2:#405367; --tile3:#3b5751; --tile4:#365566; --tile5:#5b3e51; --tile6:#5d4b46;
  --shadow-lift:0 7px 0 rgba(0,0,0,.45), 0 10px 20px rgba(0,0,0,.35);
  --shadow-lift-sm:0 4px 0 rgba(0,0,0,.45), 0 6px 14px rgba(0,0,0,.30);
  --disc:rgba(240,246,255,.88);
}

/* Base page surface — the calm sky→meadow gradient, safe-area aware. Opt-in by
   class (ADR-WEX-029 D5) so an immersive game page or a print sheet can stay flat
   simply by not carrying it. The stops match the source's weighting (0/30/60/100)
   rather than an even split: sky holds the top third, the meadow arrives late. */
.wex, body.wex {
  font-family:var(--font); color:var(--ink); margin:0; min-height:100vh;
  background:linear-gradient(var(--bg1) 0%, var(--bg2) 30%, var(--bg3) 60%, var(--bg4) 100%) fixed;
  padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}
.wex * { box-sizing:border-box; }

/* The heading ranks, scoped to the opt-in page surface. Element selectors are used
   deliberately here — the point is that EVERY heading on a shelled page lands on
   the scale without each page opting in per element, which is what let them drift
   in the first place. A page that does not carry `.wex` is untouched, so this stays
   page-by-page and revertible (ADR-WEX-029 D5). Pages may still override margins;
   the sizes are the shared contract. */
.wex h1 { font-size:var(--fs-title); font-weight:800; line-height:1.15; color:var(--navy); }
.wex h2 { font-size:var(--fs-h2);    font-weight:800; line-height:1.2;  color:var(--navy); }
.wex h3 { font-size:var(--fs-h3);    font-weight:800; line-height:1.25; color:var(--navy); }
.wex header h1 { color:inherit; font-size:var(--fs-pagehead); }

/* A shelled page is a flex COLUMN (for the sticky footer), and `main { margin:0 auto }`
   sets auto margins on the cross axis — which disables `align-items:stretch` and makes
   main shrink-wrap to its content. So every page's content column, and the header card
   inside it, was sized by its own longest line rather than by the column: Post Box
   rendered 346px wide next to Mysteries at 608px purely because their ledes differ.
   width:100% restores the fill, and the auto margins then do what they were written
   for — centring within the max-width. */
.wex-shelled > main { width:100%; }

/* Shared components (opt-in classes; never restyle existing ids). Every themeable
   value is a token — night mode swaps the tokens, not these rules. */
.wex-card {
  background:var(--card); border:1.5px solid var(--cardline); border-radius:var(--radius);
  box-shadow:var(--shadow-card); padding:22px;
}
.wex-h { color:var(--navy); font-weight:800; line-height:1.1; }
.wex-btn {
  font:inherit; font-weight:800; font-size:17px; text-decoration:none; cursor:pointer;
  border:0; border-radius:var(--pill); padding:14px 26px; min-height:52px;
  display:inline-flex; align-items:center; justify-content:center;
  color:var(--on-accent); background:var(--teal); box-shadow:var(--shadow-btn);
}
.wex-btn:active { transform:scale(.98); }
.wex-btn.ghost { color:var(--navy); background:var(--card); border:1.5px solid var(--cardline); box-shadow:none; }
.wex-brand { font-weight:800; color:var(--navy); letter-spacing:.2px; }
.wex-muted { color:var(--muted); }

/* Shared shell (shell.js): a compact sticky header bar + footer. The bar is NOT a
   page surface — it must never carry the .wex full-page (min-height:100vh) class. */
/* Mobile-first: the base carries generous, touch-friendly sizing (CODE-WEX-021).
   The hamburger + toggle are real ≥48px targets, the wordmark reads at 20px, and
   the bar breathes at 14px vertical padding. A min-width:768px step below scales
   back to the compact desktop treatment (regression-neutral on wide viewports). */
.wex-shellbar {
  display:flex; align-items:center; gap:12px; padding:14px 18px;
  background:var(--card); border-bottom:1.5px solid var(--cardline);
  position:sticky; top:0; z-index:10;
  /* Shell.mount() builds this bar as a <header>, so every page that styles a bare
     `header {}` element selector — most of them do, for their own page header —
     was also styling the shared bar. It has been silently inheriting page radii
     ever since the shell rolled out; the REQ-WEX-040 header pass (flush band ->
     rounded floating card) turned that into a visibly detached, rounded bar.
     Pinning the three properties a page header sets makes the bar immune: a class
     beats an element selector, so no page can reach it by accident again. */
  border-radius:0; margin:0; box-shadow:none;
}
.wex-shellbar .wex-brand { flex:1 1 auto; font-size:20px; }
.wex-themetoggle {
  font:inherit; font-size:20px; line-height:1; background:none; border:0; cursor:pointer;
  min-width:48px; min-height:48px; border-radius:var(--pill);
}
.wex-themetoggle:active { transform:scale(.94); }
/* Sticky footer: any shelled page (Shell.footer marks `into` with .wex-shelled)
   is a min-height flex column so the footer is pushed to the bottom of the page —
   pinned to the viewport bottom on short pages, below the content on long ones. */
.wex-shelled { display:flex; flex-direction:column; min-height:100vh; min-height:100dvh; }
.wex-shellfoot { text-align:left; padding:18px; font-size:13px; margin-top:auto; }

/* The primary-action button (ISSUE-WEX-065). `.go` is the product's de-facto
   primary control — Send, Make, Print, Play — and it is declared LOCALLY on 42
   pages, none of which set a height. Padding alone put it at 43px: five short of
   the FR-8 floor, on the most important control of every maker a child uses.

   ONE SITE, NOT FORTY-TWO. The same reasoning as scoreKey() in ISSUE-WEX-041 and
   the push() helper in ISSUE-WEX-064: a value that has to be repeated per page
   will be missed on the forty-third. Declaring the FLOOR here and leaving the
   look to each page's own rule is the smallest thing that cannot drift — the
   cascade is per-property, so a page that sets padding, colour and radius but no
   min-height still inherits the floor, and a page that genuinely needs something
   else can still say so.

   Not a visual change: 43 -> 48 grows the hit area, and a button centres its own
   content, so the label does not move relative to the box. */
.go, button.go { min-height:48px; min-width:48px; }

/* ---- Play lattices (REQ-WEX-040 FR-8 Class 2; ISSUE-WEX-065) --------------
   A fixed-geometry play grid — a chess board, the pop grid, the build yard —
   whose cell size falls out of its column count and the width it is given. At
   `--pad-col` (18px) a 390px phone leaves 354px, so eight columns land at 42.5px
   and miss the 44px lattice floor. Nothing about the BOARD is wrong; the gutter
   is simply not the board's to spend.

   So on a narrow screen the lattice bleeds out to a 6px inset while the prose
   around it keeps its comfortable 18px. That is the better layout anyway: the
   surface a child aims at gets the width, and the text does not.

      378px box -> (378 - 7x2 gap) / 8 = 45.5px per cell.

   Deliberately NOT `width:100vw` + centring: that ignores a notch/safe-area and
   overflows a scrollbar-inset viewport. A negative inline margin stays inside
   whatever box the page actually has.

   `!important` is deliberate and is the right tool exactly once here: every board
   declares `margin:0 auto` on its own ID (`#board`, `#yard`, `#grid`), and an id
   beats a class, so the un-flagged version of this rule lost silently and the
   boards stayed at 354px. This is an accessibility floor overriding page-local
   layout — the case the keyword exists for — rather than a specificity shortcut.
   The alternative was naming all six ids in the shared stylesheet, which couples
   brand.css to pages it should not know about. */
@media (max-width: 480px) {
  .play-lattice { margin-inline: calc(6px - var(--pad-col)) !important; }
}

/* Hamburger + grouped drop-down menu (shell.js + nav.js). One menu on every page
   that adopts the shell (ISSUE-WEX-018), themed by the same tokens. */
.wex-hamburger {
  font:inherit; font-size:24px; line-height:1; background:none; border:0; cursor:pointer;
  min-width:48px; min-height:48px; border-radius:var(--pill); color:var(--navy);
}
.wex-hamburger:active { transform:scale(.94); }
.wex-menu {
  position:sticky; top:78px; z-index:9; background:var(--card);
  border-bottom:1.5px solid var(--cardline); padding:12px 14px 16px; box-shadow:var(--shadow-card);
  /* The full nav is taller than a phone viewport; cap it to the space below the
     sticky bar and scroll internally so every group is reachable without
     scrolling to the end of the page content first. (dvh keeps the fit exact on
     mobile where browser chrome eats vh; vh is the fallback.) */
  max-height:calc(100vh - 78px); max-height:calc(100dvh - 78px);
  overflow-y:auto; -webkit-overflow-scrolling:touch; overscroll-behavior:contain;
  /* Full-bleed on a phone (where it IS the screen), but a drawer anchored under
     the hamburger on anything wider — a full-viewport panel left the links
     stranded at the far edge of a desktop window with a field of empty card to
     their right (REQ-WEX-040 layout pass). */
  width:min(340px, 100%); margin-right:auto;
  border-right:1.5px solid var(--cardline);
  border-radius:0 0 var(--radius) 0;
}
.wex-menu[hidden] { display:none; }
.wex-menu-grp { margin:6px 0 10px; }
.wex-menu-h { font-size:12px; font-weight:800; text-transform:uppercase; letter-spacing:.4px; color:var(--muted); margin:0 0 4px; }
/* Mobile-first ≥48px tap target (REQ-WEX-040 FR-8). Padding alone gave 37px —
   below the floor the rest of the shell already meets, and the nav is the single
   most-tapped surface in the product. min-height + flex centring is used rather
   than more padding so the target height is stated, not derived from line-height. */
.wex-menu-link {
  display:flex; align-items:center; min-height:48px; padding:6px 10px;
  color:var(--navy); text-decoration:none; font-weight:700; border-radius:10px;
}
/* An opt-in menu action (shell.js menuActions, e.g. home's "Switch player") is a
   <button> — reset it so it renders identically to the <a> destination links. */
button.wex-menu-link { width:100%; text-align:left; font:inherit; background:none; border:0; cursor:pointer; }
.wex-menu-link:hover, .wex-menu-link:active { background:rgba(46,139,155,.12); }
/* The menu had a hover state but NO focus state, so keyboard navigation through
   the ~26 destinations fell back to the UA default outline — easy to lose against
   the card. The nav is the primary way around the product; it needs the same ring
   every other pressable thing has. */
.wex-menu-link:focus-visible {
  outline:3px solid var(--accent, var(--teal)); outline-offset:-3px; background:rgba(46,139,155,.12);
}

/* Desktop scale-back (CODE-WEX-021): where a pointer is precise and vertical
   space is scarcer, revert the shell bar to its pre-change compact treatment —
   12px padding, 40px hit areas, 20/18/18px glyphs, 66px menu offset. These are the
   file's baseline values, not new constants; touch keeps the generous defaults.

   `and (pointer: fine)` ADDED 2026-07-31 (ISSUE-WEX-065). The rule was width-only,
   and width is not input. A 820px tablet took the desktop branch and got 40px hit
   areas — below the FR-5 floor — on the device this product is most often held on.
   The stated reasoning was always about the POINTER ("a pointer is precise"); the
   query just could not say so at the time. It can now: a tablet and a phone report
   `pointer: coarse` and keep 48px, a mouse reports `fine` and keeps the compact
   treatment CODE-WEX-021 asked for. The intent is unchanged — only the test for it
   is now the thing it was always about.

   Found by the browser smoke pass, which flagged the chrome at 40x40 on its tablet
   viewport and stayed silent on its phone viewport. That split is the whole clue,
   and no jsdom test could have produced it: happy-dom resolves no media query. */
@media (min-width: 768px) and (pointer: fine) {
  .wex-shellbar { padding:12px 18px; }
  .wex-shellbar .wex-brand { font-size:18px; }
  .wex-themetoggle { font-size:18px; min-width:40px; min-height:40px; }
  .wex-hamburger { font-size:20px; min-width:40px; min-height:40px; }
  .wex-menu { top:66px; max-height:calc(100vh - 66px); max-height:calc(100dvh - 66px); }
  /* A pointer is precise and the full nav is long, so the rows tighten here — the
     same trade the bar's 48→40px hit areas make one line up. */
  .wex-menu-link { min-height:40px; }
}

/* =====================================================================
   PLAY SURFACE  ·  REQ-WEX-040 / ADR-WEX-029
   The vocabulary that makes the product read as a toy rather than a console.
   Ported from the source site's FORMS (geometry, weight, colour rhythm, motion)
   with none of its content: every colour here resolves from the active kit's
   palette, so kit A wears an ice-blue paintbox and kit B a mossy one from these
   same rules. Additive classes only — no id or element selector is restyled.
   ===================================================================== */

/* ---- page furniture ------------------------------------------------- */
/* The content column. Matches the source's 1080px measure and fluid padding. */
.wex-main { max-width:1080px; margin:0 auto; padding:clamp(14px,3vw,28px) clamp(14px,3vw,22px) 56px; width:100%; }
/* A shelled page is a flex column (for the sticky footer); the content column
   has to grow into it or short pages collapse the gradient to the bar height. */
.wex-shelled .wex-main { flex:1 1 auto; }

.wex-sec { color:var(--navy); font-size:clamp(19px,3.4vw,26px); font-weight:800; margin:1.3em 0 .5em;
           display:flex; align-items:center; gap:10px; }
/* ---- the hub: a paintbox of pressable tiles -------------------------- */
/* The single most important surface in the product — how a child chooses where
   to go. Chunky radius, a saturated pastel fill, a white disc holding the icon,
   and a HARD offset shadow so the tile looks pressable before it is pressed. */
/* auto-FILL, not auto-fit: auto-fit collapses the empty tracks, so a 3-card band
   stretched its tiles to ~350px while a 9-card band sat at ~170px — the same card
   rendering at two sizes on one page. auto-fill keeps the tracks, so every tile in
   every band is the same size and short bands simply leave space at the end. */
.wex-hub { display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:clamp(10px,2vw,18px); margin-top:14px; }
.wex-hubcard {
  font:inherit; font-weight:800; font-size:clamp(15px,2.2vw,19px); text-align:center; text-decoration:none;
  color:var(--ink); background:var(--tile1); border:0; border-radius:var(--radius-lg);
  padding:18px 12px; min-height:132px; cursor:pointer;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:9px;
  box-shadow:var(--shadow-lift);
}
/* Cycle the paintbox so no two adjacent tiles share a fill (FR-5). nth-child is
   the default for a standalone grid; an explicit .t1–.t6 wins where the caller
   assigns slots itself. Nav.sections() does that so the rotation runs CONTINUOUSLY
   across bands — otherwise every band restarts on gold and the page stripes. */
.wex-hub > :nth-child(6n+1) { background:var(--tile1); }
.wex-hub > :nth-child(6n+2) { background:var(--tile2); }
.wex-hub > :nth-child(6n+3) { background:var(--tile3); }
.wex-hub > :nth-child(6n+4) { background:var(--tile4); }
.wex-hub > :nth-child(6n+5) { background:var(--tile5); }
.wex-hub > :nth-child(6n+6) { background:var(--tile6); }
.wex-hub > .t1 { background:var(--tile1); } .wex-hub > .t2 { background:var(--tile2); }
.wex-hub > .t3 { background:var(--tile3); } .wex-hub > .t4 { background:var(--tile4); }
.wex-hub > .t5 { background:var(--tile5); } .wex-hub > .t6 { background:var(--tile6); }

/* A titled band of related cards — the source site's index shape. A child scans
   "Play" / "Stay in Touch" / "Collect" and finds a thing, instead of reading one
   undifferentiated wall of tiles. */
.wex-band { margin:0 0 26px; }
.wex-band > .wex-sec { margin:1.1em 0 .5em; }
/* The white disc is what turns a bare glyph into something that looks tappable. */
.wex-hubcard .wex-ic {
  width:64px; height:64px; border-radius:50%; background:var(--disc);
  display:grid; place-items:center; font-size:32px; line-height:1; flex:none;
}
.wex-hubcard .wex-ic svg { width:66%; height:66%; }
.wex-hubcard small { display:block; font-weight:400; font-size:12px; opacity:.75; margin-top:-2px; }

.wex-hubcard:active {
  transform:translateY(var(--press-depth));
  box-shadow:0 0 0 rgba(0,0,0,0), 0 2px 6px rgba(30,42,68,.12);
}
.wex-hubcard:focus-visible, .wex-rowtile:focus-visible,
.go:focus-visible, button.go:focus-visible {
  outline:3px solid var(--accent, var(--teal)); outline-offset:3px;
}
/* `.go` joined this group late and the omission was invisible for a reason worth
   recording: the a11y guard listed `.wex-bigbtn`, brand.css defined it, the test
   passed — and NO PAGE HAS EVER USED IT. The product's real primary action is
   `.go` (Send, Make, Print, Play; declared on 42 pages, given its 48px floor here
   by ISSUE-WEX-065), and it had no focus ring at all. A keyboard user tabbing to
   the one button that does the thing got the UA default over a coloured pill.
   Surfaced by retiring `wex-bigbtn` (REQ-WEX-040 FR-3): the guard had been
   watching the component nobody shipped. */

/* ---- a horizontal pressable row (one item in a list of things to open) ---- */
/* The source's .filerow/.qrow form: an icon disc, a title, a sub-line. Used where
   a vertical tile would waste the width — a single game, a quest, a file. */
.wex-rowtile {
  display:flex; align-items:center; gap:12px; width:100%; text-align:left; font:inherit; cursor:pointer;
  text-decoration:none; color:var(--ink); background:var(--card); border:1.5px solid var(--cardline);
  border-radius:var(--radius); padding:13px 16px; box-shadow:var(--shadow-lift-sm);
}
.wex-rowtile .wex-ic { width:48px; height:48px; border-radius:50%; background:var(--disc);
                       display:grid; place-items:center; font-size:26px; line-height:1; flex:none; }
.wex-rowtile .wex-ic svg { width:70%; height:70%; }
.wex-rowtile b { display:block; color:var(--navy); font-size:16px; }
.wex-rowtile small { display:block; color:var(--muted); font-size:13px; margin-top:2px; }
.wex-rowtile:active { transform:translateY(4px); box-shadow:0 1px 4px rgba(30,42,68,.12); }

/* ---- badge wall ------------------------------------------------------ */
/* The gold ring is the reward signal; locked badges keep the layout but drain
   the colour, so progress is legible at a glance without counting. */
.wex-badgewall { display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:14px; }
.wex-badge { margin:0; background:var(--card); border:1.5px solid var(--cardline); border-radius:20px;
             box-shadow:var(--shadow-lift-sm); padding:16px 12px; text-align:center; }
.wex-badge .wex-disc {
  width:74px; height:74px; margin:0 auto 8px; border-radius:50%; display:grid; place-items:center;
  /* A near-neutral disc, NOT a tile fill: the gold ring is the reward signal, and
     it disappears against tile1 (which is itself the gold-tinted fill). */
  background:var(--card); box-shadow:inset 0 0 0 4px var(--gold); font-size:34px; line-height:1;
}
.wex-badge .wex-disc svg { width:60%; height:60%; }
/* <figcaption> is the <figure>-based badge markup's label; <b>/<span> the
   two-line variant. Both read as the badge name. */
.wex-badge b, .wex-badge figcaption { display:block; font-size:15px; font-weight:800; color:var(--navy); }
.wex-badge span { display:block; font-size:12px; color:var(--muted); margin-top:3px; line-height:1.3; }
.wex-badge.locked .wex-disc { background:var(--cardline); box-shadow:inset 0 0 0 4px var(--cardline);
                              filter:grayscale(1); opacity:.55; }
.wex-badge.locked b { color:var(--muted); }

/* ---- colour-coded nav groups (FR-6) ---------------------------------- */
/* The menu's five groups each carry their own accent, so a child navigates by
   colour before they navigate by reading. The tint is a token blend, so it
   re-themes with the kit and inverts correctly in night.                   */
.wex-menu-grp { border-left:6px solid var(--cardline); padding-left:10px; border-radius:3px; }
.wex-menu-grp:nth-of-type(6n+1) { border-left-color:var(--teal); }
.wex-menu-grp:nth-of-type(6n+2) { border-left-color:var(--gold); }
.wex-menu-grp:nth-of-type(6n+3) { border-left-color:var(--green); }
.wex-menu-grp:nth-of-type(6n+4) { border-left-color:var(--pink); }
.wex-menu-grp:nth-of-type(6n+5) { border-left-color:var(--orange); }
.wex-menu-grp:nth-of-type(6n+6) { border-left-color:var(--purple); }
.wex-menu-link { font-size:15px; }

/* ---- motion (FR-7) ---------------------------------------------------- */
/* Deliberately small and CSS-only — no JS animation loop, so nothing here costs
   a frame budget or breaks the offline/zero-build constraints. Every rule sits
   inside the no-preference query: under `prefers-reduced-motion: reduce` the
   layout is byte-identical and completely static. */
@media (prefers-reduced-motion: no-preference) {
  .wex-hubcard { transition:transform .1s ease, box-shadow .1s ease; }
  .wex-rowtile { transition:transform .1s ease, box-shadow .1s ease; }
  .wex-hubcard:hover, .wex-rowtile:hover { transform:translateY(-3px); }
  .wex-menu-link { transition:background .12s ease; }
  /* Ambient bob for character art — slow enough to read as breathing, not as a
     loading spinner. Stagger siblings so a cast doesn't pulse in lockstep. */
  .wex-bob { animation:wexBob 3s ease-in-out infinite; }
  .wex-bob:nth-child(even) { animation-delay:.6s; }
  @keyframes wexBob { 0%,100% { transform:translateY(0); } 50% { transform:translateY(-9px); } }
  /* Badge earned. One-shot; the class is added when a badge flips to unlocked. */
  .wex-badge.justearned .wex-disc { animation:wexPop .6s cubic-bezier(.2,1.4,.4,1); }
  @keyframes wexPop { 0% { transform:scale(.6); } 55% { transform:scale(1.18); } 100% { transform:scale(1); } }
}

/* The active kit's art beside a page heading (Shell.paintKitIcons).
   Sized in `em` so it tracks the heading it labels rather than a fixed pixel
   guess — a heading that shrinks on a phone takes its picture with it. */
.kit-title-icon { display:inline-block; width:1.05em; height:1.05em; vertical-align:-0.14em; margin-right:.42em; }
.kit-title-icon svg { display:block; width:100%; height:100%; }

/* ---------------------------------------------------------------------
   Game juice (REQ-WEX-052 / ADR-WEX-036 D2) — the shared look for the
   per-game juice markup: the #fx layer with floats and sparks, the chatter
   line's colour classes, the sticker banner, the warm-ending rows, and the
   sound toggle. The ids are unique per page, so id rules here serve every
   game page that ships the markup; boots move things through juice.js.
   Every animating declaration sits in the media block at the end, keyframes
   included, following this file's own convention.
   --------------------------------------------------------------------- */
#fx { position:absolute; inset:0; pointer-events:none; z-index:2; overflow:hidden; border-radius:18px; }
.float { position:absolute; transform:translate(-50%,-50%); font-weight:800; font-size:18px;
         color:#F4F7FF; text-shadow:0 1px 4px rgba(0,0,0,.45); }
.float.gold { color:#1E2A44; background:var(--gold); border-radius:999px; padding:1px 8px; text-shadow:none; }
.float.heart { color:var(--error, var(--coral)); font-size:20px; }
/* For light play surfaces (a card-coloured yard), where starlight text vanishes. */
.float.ink { color:var(--ink); text-shadow:none; }
.spark { position:absolute; width:7px; height:7px; border-radius:50%; transform:translate(-50%,-50%); }
.spark.good { background:var(--accent); }
.spark.gold { background:var(--gold); }
.spark.bad  { background:var(--coral); }
.spark.miss { background:var(--muted); }

/* Chatter colours ride the palette so both themes keep their contrast; gold
   rides a chip because gold TEXT on paper does not. */
.say .c-good { color:var(--success, var(--green)); }
.say .c-heart, .say .c-low, .say .c-bad { color:var(--error, var(--coral)); }
.say .c-start { color:var(--accent); }
.say .c-miss, .say .c-over { color:var(--muted); }
.say .c-gold, .say .c-milestone, .say .c-level { background:var(--gold); color:#1E2A44;
    border-radius:999px; padding:2px 14px; display:inline-block; }

.go.snd { background:var(--card); color:var(--ink); box-shadow:var(--shadow-card); min-width:48px; }

#banner { position:fixed; top:-140px; left:50%; transform:translateX(-50%); z-index:30;
          display:flex; align-items:center; gap:10px; max-width:92vw;
          padding:10px 16px 10px 12px; border-radius:16px; background:var(--card);
          box-shadow:var(--shadow-lift); border:2px solid var(--gold); }
#banner.show { top:14px; }
#banner .b-ic { font-size:26px; line-height:1; }
#banner .b-lbl { font-size:10px; letter-spacing:1.2px; text-transform:uppercase; color:var(--muted); font-weight:800; }
#banner .b-nm { font-size:15px; font-weight:800; color:var(--ink); line-height:1.15; }
#banner .b-ds { font-size:12px; color:var(--muted); }

.record { display:none; text-align:center; font-weight:800; color:#1E2A44; background:var(--gold);
          border-radius:999px; padding:6px 18px; width:max-content; margin:10px auto 0; }
.record.show { display:block; }
.endmsg { display:none; text-align:center; font-weight:800; font-size:17px; margin:10px 0 0; color:var(--ink); }
.endmsg.show { display:block; }
.endsub { display:none; text-align:center; color:var(--muted); font-style:italic; margin:4px 0 0; }
.endsub.show { display:block; }

@media (prefers-reduced-motion: no-preference) {
  .say .pop { animation:jSayPop 1.9s ease both; }
  .float { animation:jFloatUp .9s ease-out forwards; }
  .spark { animation:jSparkFly .6s ease-out forwards; }
  .j-jolt { animation:jJolt .35s linear; }
  .j-pop { display:inline-block; animation:jScorePop .25s ease; }
  #banner { transition:top .45s cubic-bezier(.2,1.3,.5,1); }
  @keyframes jSayPop {
    0%   { opacity:0; transform:translateY(-6px) scale(.8); }
    14%  { opacity:1; transform:translateY(0) scale(1.06); }
    26%  { transform:scale(1); }
    100% { opacity:1; }
  }
  @keyframes jFloatUp {
    0%   { opacity:0; transform:translate(-50%,-50%) scale(.8); }
    12%  { opacity:1; transform:translate(-50%,-70%) scale(1); }
    100% { opacity:0; transform:translate(-50%,-260%) scale(.94); }
  }
  @keyframes jSparkFly {
    0%   { opacity:1; transform:translate(-50%,-50%); }
    100% { opacity:0; transform:translate(calc(-50% + var(--dx, 0px)), calc(-50% + var(--dy, 0px))) scale(.4); }
  }
  @keyframes jJolt {
    0%, 100% { transform:translate(0,0); }
    20% { transform:translate(-7px,3px); } 40% { transform:translate(6px,-4px); }
    60% { transform:translate(-4px,2px); } 80% { transform:translate(3px,-2px); }
  }
  @keyframes jScorePop {
    0% { transform:scale(1); } 40% { transform:scale(1.3); } 100% { transform:scale(1); }
  }
}
