  /* ============================================================
     OUTWORD DESIGN SYSTEM v2 — light + dark, cream & ink.
     Dark is default. Two grounds; roles swap by mode. Contrast is
     symmetric, so one pair covers both. See outword-ds v2.
     ============================================================ */
  :root {
    /* base reference — the real values both modes derive from */
    --dk-ground:#202845; --dk-raise:rgba(242,240,234,.08); --dk-raise-solid:#313852;
    --dk-fg:#F2F0EA; --dk-fg-soft:#A5ABC4; --dk-fg-faint:#6C7597;
    --dk-rule:rgba(242,240,234,.17); --dk-rule-strong:rgba(242,240,234,.32);
    --dk-tile:#F2F0EA; --dk-tile-ink:#202845; --dk-die-edge:#7E828F;
    --dk-select:#202845; --dk-select-ink:#F2F0EA; --dk-select-stroke:rgba(242,240,234,0.5);
    --dk-lit:#3D445C; --dk-lit-ink:#F2F0EA; --dk-lit-edge:#2D354F; --dk-trace:#F2F0EA;
    --dk-earned:#7BE0A3; --dk-danger:#F2B8BC; --dk-focus:#7E9BE0;
    --dk-earned-wash:rgba(123,224,163,.12); --dk-danger-wash:rgba(242,184,188,.14);
    --dk-earned-card-bg:rgba(123,224,163,.06); --dk-earned-card-border:rgba(123,224,163,.45);
    --dk-mark-dot:#9698A0;
    --dk-shadow:rgba(10,14,34,.38); --dk-shadow-lift:rgba(10,14,34,.28); --dk-scrim:rgba(10,14,34,.72);

    --lt-ground:#F2F0EA; --lt-raise:#FAF9F6; --lt-raise-solid:#FAF9F6;
    --lt-fg:#202845; --lt-fg-soft:#5F6987; --lt-fg-faint:#98A0B8;
    --lt-rule:rgba(32,40,69,.14); --lt-rule-strong:rgba(32,40,69,.30);
    --lt-tile:#202845; --lt-tile-ink:#F2F0EA; --lt-die-edge:#9496A0;
    --lt-select:#F2F0EA; --lt-select-ink:#202845; --lt-select-stroke:rgba(32,40,69,0.5);
    --lt-lit:#D5D4D3; --lt-lit-ink:#202845; --lt-lit-edge:#E5E3E0; --lt-trace:#202845;
    --lt-earned:#276A43; --lt-danger:#8E2430; --lt-focus:#2F58D0;
    --lt-earned-wash:rgba(39,106,67,.10); --lt-danger-wash:rgba(142,36,48,.10);
    --lt-earned-card-bg:#E6EEE3; --lt-earned-card-border:#9FC2A9;
    --lt-mark-dot:#686D7D;
    --lt-shadow:rgba(32,40,69,.16); --lt-shadow-lift:rgba(32,40,69,.20); --lt-scrim:rgba(32,40,69,.55);

    /* active layer — DARK by default */
    --ground:var(--dk-ground); --raise:var(--dk-raise); --raise-solid:var(--dk-raise-solid);
    --fg:var(--dk-fg); --fg-soft:var(--dk-fg-soft); --fg-faint:var(--dk-fg-faint);
    --rule:var(--dk-rule); --rule-strong:var(--dk-rule-strong);
    --tile:var(--dk-tile); --tile-ink:var(--dk-tile-ink); --die-edge:var(--dk-die-edge);
    --select:var(--dk-select); --select-ink:var(--dk-select-ink); --select-stroke:var(--dk-select-stroke);
    --lit:var(--dk-lit); --lit-ink:var(--dk-lit-ink); --lit-edge:var(--dk-lit-edge); --trace:var(--dk-trace);
    --earned:var(--dk-earned); --danger:var(--dk-danger); --focus:var(--dk-focus);
    --earned-wash:var(--dk-earned-wash); --danger-wash:var(--dk-danger-wash);
    --earned-card-bg:var(--dk-earned-card-bg); --earned-card-border:var(--dk-earned-card-border);
    --mark-dot:var(--dk-mark-dot);
    --shadow:var(--dk-shadow); --shadow-lift:var(--dk-shadow-lift); --scrim:var(--dk-scrim);

    /* legacy aliases — resolve to canonical so nothing breaks in one commit */
    --tray:var(--ground); --tray-raise:var(--raise); --tray-line:var(--rule);
    --die:var(--tile); --ink:var(--tile-ink);
    --paper:var(--fg); --paper-dim:var(--fg-soft);
    --gold:var(--fg); --gold-ink:var(--ground); --valid:var(--fg);
    --ok-green:var(--earned); --ok-line:var(--earned); --ok-ink:var(--ground);
    --bad-fill:var(--danger); --bad-line:var(--danger); --bad-ink:var(--ground);
    --cta:var(--fg); --cta-ink:var(--ground);
    --error-text:var(--danger); --error-surface:var(--danger-wash); --error-border:var(--danger);
    --urgent:var(--danger);

    --bar-pad:16px;
    --font-display:'Arial Black', system-ui, sans-serif;
    --font-body:system-ui, -apple-system, 'Segoe UI', sans-serif;
    --font-word:'Arial Black','Helvetica Neue', system-ui, sans-serif;
    --font-die:var(--font-word);
  }
  :root[data-mode="light"] {
    --ground:var(--lt-ground); --raise:var(--lt-raise); --raise-solid:var(--lt-raise-solid);
    --fg:var(--lt-fg); --fg-soft:var(--lt-fg-soft); --fg-faint:var(--lt-fg-faint);
    --rule:var(--lt-rule); --rule-strong:var(--lt-rule-strong);
    --tile:var(--lt-tile); --tile-ink:var(--lt-tile-ink); --die-edge:var(--lt-die-edge);
    --select:var(--lt-select); --select-ink:var(--lt-select-ink); --select-stroke:var(--lt-select-stroke);
    --lit:var(--lt-lit); --lit-ink:var(--lt-lit-ink); --lit-edge:var(--lt-lit-edge); --trace:var(--lt-trace);
    --earned:var(--lt-earned); --danger:var(--lt-danger); --focus:var(--lt-focus);
    --earned-wash:var(--lt-earned-wash); --danger-wash:var(--lt-danger-wash);
    --earned-card-bg:var(--lt-earned-card-bg); --earned-card-border:var(--lt-earned-card-border);
    --mark-dot:var(--lt-mark-dot);
    --shadow:var(--lt-shadow); --shadow-lift:var(--lt-shadow-lift); --scrim:var(--lt-scrim);
  }

  * { box-sizing: border-box; }
  .app {
    min-height: 100vh;          /* fallback for old browsers */
    min-height: 100dvh;         /* dynamic viewport — Safari address bar safe */
    background: var(--tray); color: var(--paper);
    font-family: var(--font-body);
    /* safe-area insets + breathing room (4/8-pt grid). Clears notch, Dynamic
       Island, home indicator; falls back to comfortable padding without them. */
    padding-top: calc(env(safe-area-inset-top) + 32px);
    padding-left: max(20px, env(safe-area-inset-left));
    padding-right: max(20px, env(safe-area-inset-right));
    padding-bottom: calc(env(safe-area-inset-bottom) + 20px);
    max-width: 480px; margin: 0 auto;
    user-select: none; -webkit-user-select: none;
  }
  header { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; min-height: 52px; }
  .brand-mark {
    display: inline-flex; align-items: center; flex: 1;
    font-family: var(--font-display); font-weight: 700; text-transform: none;
    letter-spacing: -0.5px; color: var(--fg); line-height: 1; user-select: none;
  }
  /* the SVG carries its own crisp geometry; it just needs to sit on the cap line */
  .brand-mark .bm-svg { display: block; flex-shrink: 0; }
  /* Ratios measured off the approved lockup: the tile stands 1.70x the cap height
     and sits 0.14 of a tile away from the word. Anchoring the word to the mark's
     own font-size keeps that true at any size. */
  .brand-mark .bm-word { margin-left: -0.125em; font-size: 1.00em; }
  .brand-die {
    width: 34px; height: 34px; border-radius: 8px; background: var(--die); color: var(--ink);
    font-family: var(--font-die); font-weight: 700; font-size: 20px;
    display: grid; place-items: center; box-shadow: none; border: 1.5px solid var(--rule);
  }
  h1 { font-family: var(--font-display); font-size: 16px; letter-spacing: 1px; margin: 0; text-transform: uppercase; flex: 1; }
  h2 { font-family: var(--font-display); font-size: 16px; text-transform: uppercase; letter-spacing: 1px; margin: 8px 0; }
  .dict-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--paper-dim); }
  /* Icon secondary button — canonical pattern: outlined, radius-m (12px),
     control-lg height (52px) to match all action buttons. Icon + label. */
  .icon-secondary {
    display: inline-flex; align-items: center; gap: 6px;
    background: none; border: 1px solid var(--tray-line); color: var(--paper-dim);
    border-radius: 12px; padding: 0 14px 0 12px; height: 44px;
    font: 600 14px var(--font-body); cursor: pointer; white-space: nowrap;
  }
  .icon-secondary svg { display: block; }
  .icon-secondary:hover { color: var(--paper); border-color: var(--paper-dim); }
  .icon-secondary:disabled { opacity: 0.4; }
  .icon-secondary:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
  /* Header home: icon-only, compact outlined square (44px hit area, 38px visual) */
  .home-icon-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; flex-shrink: 0;
    background: none; border: 1px solid var(--tray-line); color: var(--paper-dim);
    border-radius: 12px; cursor: pointer;
  }
  .home-icon-btn.help-q { font: 600 20px var(--font-body); }
  /* Recovery code sheet — built from system tokens: --raise fill, --rule border,
     radius 12, --font-body. Matches the .field / .home-icon-btn language. */
  .rec-codebox { display: flex; align-items: center; justify-content: center; gap: 10px;
    background: var(--raise); border: 1.5px solid var(--rule-strong); border-radius: 12px;
    padding: 14px 16px; margin: 0 0 12px; }
  .rec-code { font: 500 17px var(--font-body); letter-spacing: 0.02em; color: var(--fg); word-break: break-all; }
  .rec-actions { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin: 0 0 14px; }
  .rec-act { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 13px 6px;
    background: none; border: 1.5px solid var(--rule); border-radius: 12px; cursor: pointer;
    font: 500 12px var(--font-body); color: var(--fg); -webkit-tap-highlight-color: transparent; }
  /* Icons are --fg-soft (neutral), NOT --earned: green is reserved for "earned"
     in this system (one hue, one meaning). These are plain utility actions. */
  .rec-act svg { color: var(--fg-soft); }
  .rec-act:hover { border-color: var(--rule-strong); }
  .rec-act:hover svg { color: var(--fg); }
  .rec-act:active { transform: scale(0.97); }
  .rec-link { display: block; width: 100%; background: none; border: none; cursor: pointer;
    font: 500 14px var(--font-body); color: var(--fg-soft); text-align: center;
    padding: 14px 4px 2px; }
  .rec-link:hover { color: var(--fg); }
  /* ---- Recovery code, save nudges, home-screen guide (3.21.0) ----
     The code is six tiles in two groups of three, outlined in --fg; empty slots
     are dashed --rule-strong, the way an unfilled board reads. The key's "not
     saved yet" state is the outlined key plus a dot: icon-only, so the header
     never changes width. Its dot pops and rings twice once per visit, then rests. */
  .key-btn { position: relative; }
  .key-btn.attn { border: 1.5px solid var(--fg); color: var(--fg); }
  .key-dot { position: absolute; top: -5px; right: -5px; width: 12px; height: 12px; border-radius: 50%;
    background: var(--fg); box-shadow: 0 0 0 2.5px var(--ground); }
  .key-ring { position: absolute; top: -5px; right: -5px; width: 12px; height: 12px; border-radius: 50%;
    border: 2px solid var(--fg); box-sizing: border-box; opacity: 0; pointer-events: none; }
  .attn-anim .key-dot { animation: kd-pop 240ms cubic-bezier(.3,1.4,.5,1) 300ms both; }
  .attn-anim .key-ring { animation: kd-ring 900ms cubic-bezier(.2,.8,.2,1) 560ms 2; }
  @keyframes kd-pop { from { transform: scale(.4); opacity: 0 } to { transform: scale(1); opacity: 1 } }
  @keyframes kd-ring { 0% { opacity: .7; transform: scale(1) } 100% { opacity: 0; transform: scale(2.6) } }
  .rc-panel { width: 100%; box-sizing: border-box; background: var(--raise); border: 1px solid var(--rule); border-radius: 14px;
    padding: 22px 16px 14px; display: flex; flex-direction: column; align-items: center; gap: 14px; color: var(--fg);
    font: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; }
  div.rc-panel { cursor: default; }
  .rc-panel:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
  .rc-tiles { display: flex; gap: 6px; }
  .rct { width: 42px; height: 42px; box-sizing: border-box; border-radius: 10px; border: 1.5px solid var(--fg);
    display: flex; align-items: center; justify-content: center; font: 900 21px var(--font-display); color: var(--fg); }
  .rct.empty { border-style: dashed; border-color: var(--rule-strong); }
  .rct-gap { width: 8px; }
  .rc-tiles.small { gap: 4px; } .rc-tiles.small .rct { width: 26px; height: 26px; font-size: 13px; border-radius: 7px; }
  .rc-tiles.small .rct-gap { width: 5px; }
  .rc-tiles.who .rct { width: 34px; height: 34px; font-size: 17px; border-radius: 8px; }
  /* Under 360px six 42px tiles are wider than the panel (296 in 286 at 320): 38px, as the help deck's tiles step down */
  @media (max-width: 360px) { .rc-panel .rc-tiles:not(.small):not(.who) .rct { width: 38px; height: 38px; font-size: 19px; } }
  .rc-panel.making .rct.empty { background: var(--raise); }
  .rc-entry.bad .rct { border-color: var(--danger); }
  /* The tiles are the field: the label is their row, 4px over and under so it's 50px to tap.
     The input is see-through but not opacity 0: iPhone won't offer Paste on an invisible field. */
  .rc-type { position: relative; display: block; cursor: text; padding: 4px 0; margin: -4px 0; }
  .rc-type-in { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 1; border: 0; padding: 0; margin: 0;
    font-size: 16px; color: transparent; -webkit-text-fill-color: transparent; background: transparent; caret-color: transparent; cursor: text; }
  .rc-type-in:focus-visible { outline: none; } /* the tile with the caret shows where you type */
  .rc-type-in::selection { background: transparent; }
  .rct.cur { border-style: solid; border-color: var(--fg); }
  .rct-caret { width: 2px; height: 20px; border-radius: 1px; background: var(--fg); animation: rc-blink 1s steps(1) infinite; }
  @keyframes rc-blink { 50% { opacity: 0 } }
  @media (prefers-reduced-motion: reduce) { .rct-caret { animation: none; } }
  .rc-soft { opacity: .55; }
  .rc-hint { display: flex; align-items: center; gap: 6px; min-height: 20px; font: 600 13px var(--font-body); color: var(--fg-soft); text-align: center; }
  .rc-hint.on { color: var(--fg); font-weight: 700; }
  .rc-hint svg { width: 16px; height: 16px; flex-shrink: 0; }
  .rc-plain { font: 700 26px ui-monospace, Menlo, monospace; letter-spacing: .12em; color: var(--fg); user-select: all; -webkit-user-select: all; }
  .rc-plain.rc-long { font: 500 16px var(--font-body); letter-spacing: .02em; word-break: break-all; text-align: center; }
  .rc-select .icon-secondary { min-height: 44px; }
  .rc-saves { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 14px; }
  .rc-saves button { width: 100%; min-height: 52px; font-size: 15px; padding: 13px 8px; }
  .rc-done { display: inline-flex; align-items: center; justify-content: center; gap: 6px; font-weight: 700 !important; color: var(--fg) !important; }
  .rc-entry + .lbl { margin-top: 16px; }
  .rc-wide { width: 100%; margin-top: 14px; display: flex; align-items: center; justify-content: center; text-decoration: none; box-sizing: border-box; }
  .rc-center { text-align: center; }
  .rc-who { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 6px 0 2px; }
  .rc-who-name { font: 800 20px var(--font-body); color: var(--fg); }
  .rc-warn { margin: 14px 0 0; padding: 12px 14px; border-radius: 12px; border: 1px dashed var(--rule-strong);
    font-size: 13.5px; line-height: 1.45; color: var(--fg-soft); }
  .rc-photo { display: block; width: 100%; max-width: 300px; margin: 0 auto; border-radius: 16px; border: 1px solid var(--rule);
    -webkit-touch-callout: default; }
  .rc-note { height: 180px; resize: none; font: 500 14px/1.5 var(--font-body); padding: 12px 14px; }
  .rc-toast { position: fixed; left: 16px; right: 16px; top: calc(env(safe-area-inset-top, 0px) + 14px); z-index: 90;
    max-width: 480px; margin: 0 auto; display: flex; align-items: center; gap: 10px; background: var(--tile); color: var(--tile-ink);
    border-radius: 12px; padding: 14px 16px; font: 700 14px var(--font-body); box-shadow: 0 8px 30px rgba(10,14,34,.4);
    animation: rc-toast-in 220ms cubic-bezier(.2,.8,.2,1); }
  @keyframes rc-toast-in { from { opacity: 0; transform: translateY(-8px) } to { opacity: 1; transform: none } }
  .welcome-card { display: flex; align-items: center; gap: 12px; background: var(--raise); border: 1px solid var(--fg);
    border-radius: 14px; padding: 12px 8px 12px 14px; margin: 0 0 12px; animation: rc-toast-in 320ms cubic-bezier(.2,.8,.2,1); }
  .wc-mid, .a2hs-mid { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
  .wc-title, .a2hs-title { font: 700 15px var(--font-body); color: var(--fg); }
  .wc-sub, .a2hs-sub { font: 600 12px var(--font-body); color: var(--fg-soft); }
  .a2hs-card { display: flex; align-items: center; gap: 12px; background: var(--raise); border: 1px solid var(--rule);
    border-radius: 14px; padding: 10px 4px 10px 14px; margin: 16px 0 0; }
  .a2hs-ic { width: 36px; height: 36px; flex-shrink: 0; box-sizing: border-box; border-radius: 10px; border: 1.5px solid var(--rule-strong);
    display: flex; align-items: center; justify-content: center; color: var(--fg); }
  .a2hs-ic svg { width: 18px; height: 18px; }
  .a2hs-how { background: none; border: none; min-height: 44px; padding: 0 6px; font: 700 14px var(--font-body); color: var(--fg); cursor: pointer; }
  .a2hs-x { width: 44px; height: 44px; flex-shrink: 0; background: none; border: none; color: var(--fg-soft); display: flex; align-items: center; justify-content: center; cursor: pointer; }
  .a2hs-steps { list-style: none; margin: 0; padding: 0; }
  .a2hs-steps li { display: flex; gap: 14px; padding: 16px 0; border-top: 1px solid var(--rule); }
  .a2hs-steps li:first-child { border-top: none; padding-top: 4px; }
  .a2hs-n { width: 22px; flex-shrink: 0; font: 900 20px/22px var(--font-display); color: var(--fg-soft); }
  .a2hs-body { flex: 1; display: flex; flex-direction: column; gap: 10px; min-width: 0; }
  .a2hs-t { flex: 1; font: 700 16px/26px var(--font-body); color: var(--fg); }
  .a2hs-code { display: flex; align-items: center; gap: 12px; background: var(--raise); border: 1px solid var(--rule); border-radius: 12px; padding: 8px 8px 8px 12px; }
  .a2hs-code .home-icon-btn { margin-left: auto; }
  .a2hs-code .home-icon-btn svg { width: 18px; height: 18px; }
  .a2hs-chip { display: inline-flex; align-items: center; gap: 4px; padding: 0 8px; margin: 0 1px; border-radius: 8px;
    border: 1px solid var(--rule-strong); font-weight: 700; white-space: nowrap; line-height: 24px; }
  .a2hs-chip svg { width: 14px; height: 14px; }
  .first-open { position: fixed; inset: 0; z-index: 70; background: var(--ground); display: flex; flex-direction: column; align-items: center;
    padding: calc(env(safe-area-inset-top, 0px) + 110px) 16px calc(env(safe-area-inset-bottom, 0px) + 28px); box-sizing: border-box; }
  .fo-slots { margin-top: 64px; }
  .fo-slots .rct { width: 44px; height: 44px; }
  .fo-title { margin: 36px 0 0; font: 800 24px var(--font-body); color: var(--fg); text-align: center; }
  .fo-sub { margin: 8px 0 0; max-width: 290px; font: 500 15px/1.45 var(--font-body); color: var(--fg-soft); text-align: center; }
  .fo-actions { margin-top: auto; width: 100%; max-width: 480px; display: flex; flex-direction: column; gap: 4px; }
  .fo-actions .primary { width: 100%; }
  .sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

  /* ---- Update reel (3.21.0) ----
     A fixed 350×640 card, scaled down to fit short screens. Every piece is a
     real component's tokens: tiles, traced tiles, rows, rank circles, podium
     columns, the crown and the confetti. Light mode needs nothing of its own. */
  .scrim.wn-scrim { align-items: center; }
  .wn-card { position: relative; width: 350px; height: 640px; flex-shrink: 0; box-sizing: border-box; background: var(--tray);
    border: 1px solid var(--tray-line); border-radius: 22px; box-shadow: 0 24px 60px rgba(10,14,34,.55); overflow: hidden;
    color: var(--fg); --crown-cut: var(--tray); transform-origin: 50% 50%; }
  .wn-head { position: absolute; left: 20px; right: 12px; top: 12px; display: flex; align-items: center; justify-content: space-between; z-index: 5; }
  .wn-eyebrow { font: 700 11px var(--font-body); letter-spacing: 2px; text-transform: uppercase; color: var(--fg-soft); }
  .wn-stage { position: absolute; left: 0; right: 0; top: 64px; height: 340px; }
  .wn-o { position: absolute; left: 50%; top: 50%; width: 0; height: 0; }
  .wn-w { transition-property: opacity, transform, filter, top, left, background-color, color, border-color;
    transition-timing-function: cubic-bezier(.2,.8,.2,1); transition-duration: 560ms; }
  .wn-bt { position: absolute; width: 52px; height: 52px; box-sizing: border-box; border-radius: 13px; background: var(--tile); color: var(--tile-ink);
    display: flex; align-items: center; justify-content: center; font: 900 24px var(--font-display); }
  .wn-trace { position: absolute; left: -146px; top: -116px; overflow: visible; z-index: 3; pointer-events: none; }
  .wn-trace polyline { fill: none; stroke: var(--trace); stroke-opacity: .55; stroke-width: 10; stroke-linecap: round; stroke-linejoin: round; }
  .wn-row { position: absolute; left: -140px; width: 280px; height: 48px; box-sizing: border-box; border-radius: 12px; background: var(--raise);
    border: 1px solid var(--rule); display: flex; align-items: center; padding: 0 14px 0 52px; gap: 8px; transform-origin: 0 50%; }
  .wn-row.me, .wn-back { border: 1.5px solid var(--fg); }
  .wn-rn { flex: 1; font: 700 15px var(--font-body); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .wn-you { font: 600 12px var(--font-body); color: var(--fg-soft); }
  .wn-rp { font: 600 13px var(--font-body); color: var(--fg-soft); }
  .wn-circ { position: absolute; box-sizing: border-box; display: flex; align-items: center; justify-content: center;
    font-family: var(--font-display); font-weight: 900; transition-property: all; }
  .wn-circ.is-tile { background: var(--tile); color: var(--tile-ink); border: 1.5px solid var(--tile); }
  .wn-circ.is-traced { background: var(--select); color: var(--select-ink); border: 1.5px solid var(--select-stroke); }
  .wn-circ.is-soft { background: var(--fg-soft); color: var(--ground); border: 0 solid transparent; }
  .wn-ghost { position: absolute; left: -144px; top: -28px; width: 288px; height: 56px; box-sizing: border-box; border-radius: 15px; border: 1.5px dashed var(--rule-strong); }
  .wn-key { position: absolute; left: -24px; top: -118px; width: 48px; height: 48px; box-sizing: border-box; border-radius: 12px;
    border: 1.5px solid var(--fg); display: flex; align-items: center; justify-content: center; transition-duration: 700ms; }
  .wn-key svg { width: 22px; height: 22px; }
  .wn-dot { position: absolute; top: -6px; right: -6px; width: 13px; height: 13px; border-radius: 50%; background: var(--fg);
    box-shadow: 0 0 0 2.5px var(--tray); transition-duration: 260ms; transition-timing-function: cubic-bezier(.3,1.4,.5,1); }
  .wn-ring { position: absolute; top: -6px; right: -6px; width: 13px; height: 13px; border-radius: 50%; border: 2px solid var(--fg);
    box-sizing: border-box; opacity: 0; animation: kd-ring 900ms cubic-bezier(.2,.8,.2,1) 800ms 2; }
  .wn-ct { position: absolute; top: -22px; width: 44px; height: 44px; box-sizing: border-box; border-radius: 11px; display: flex; align-items: center;
    justify-content: center; font: 900 22px var(--font-display); transform-origin: 0 0; transition-property: all; transition-duration: 560ms;
    transition-timing-function: cubic-bezier(.3,1.15,.5,1); background: var(--tray); color: var(--fg); border: 1.5px solid var(--fg); }
  .wn-ct.filled { background: var(--tile); color: var(--tile-ink); border-color: var(--tile); }
  .wn-ct.slot { border-style: dashed; border-color: var(--rule-strong); color: transparent; transition-duration: 300ms; }
  .wn-codecard { position: absolute; left: -80px; top: -112px; width: 160px; height: 172px; box-sizing: border-box; border-radius: 16px;
    background: var(--ground); border: 1.5px solid var(--rule-strong); box-shadow: 0 16px 36px rgba(10,14,34,.35); transition-duration: 640ms; }
  .wn-cc-label { position: absolute; left: 0; right: 0; top: 16px; text-align: center; font: 700 10px var(--font-body); letter-spacing: 1.5px;
    text-transform: uppercase; color: var(--fg-soft); }
  .wn-cc-code { position: absolute; left: 0; right: 0; bottom: 16px; text-align: center; font: 700 13px ui-monospace, Menlo, monospace; letter-spacing: 2px; }
  .wn-badge { position: absolute; left: 34px; top: -126px; height: 26px; padding: 0 10px; border-radius: 13px; background: var(--tile); color: var(--tile-ink);
    display: flex; align-items: center; gap: 5px; font: 700 12px var(--font-body); white-space: nowrap; transition-duration: 300ms;
    transition-timing-function: cubic-bezier(.3,1.4,.5,1); }
  .wn-back { position: absolute; left: -140px; top: 46px; width: 280px; height: 48px; box-sizing: border-box; border-radius: 12px; background: var(--raise);
    display: flex; align-items: center; gap: 12px; padding: 0 14px 0 10px; }
  .wn-circ-s, .wn-chk { border-radius: 50%; background: var(--tile); color: var(--tile-ink); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
  .wn-circ-s { width: 32px; height: 32px; font: 900 15px var(--font-display); }
  .wn-chk { width: 24px; height: 24px; }
  .wn-pod { position: absolute; left: -130px; top: -110px; width: 260px; height: 230px; }
  .wn-colmask { position: absolute; bottom: 62px; width: 72px; overflow: hidden; border-radius: 12px; }
  .wn-col { width: 72px; box-sizing: border-box; border-radius: 12px; display: flex; justify-content: center; padding-top: 10px; font: 900 24px var(--font-display); }
  .wn-col.p1 { background: var(--tile); color: var(--tile-ink); }
  .wn-col.p2 { background: var(--select); border: 2px solid var(--select-stroke); color: var(--select-ink); }
  .wn-col.p3 { background: var(--lit); border: 1.5px solid var(--select-stroke); color: var(--lit-ink); }
  .wn-pcrown { position: absolute; left: 80px; top: 8px; width: 44px; line-height: 0; transform-origin: 50% 100%; transition-duration: 440ms;
    transition-timing-function: cubic-bezier(.3,1.4,.5,1); }
  .wn-plates { position: absolute; left: 0; right: 0; bottom: 8px; display: flex; justify-content: space-between; padding: 0 2px; }
  .wn-plates span { width: 82px; display: flex; flex-direction: column; align-items: center; gap: 1px; }
  .wn-plates b { font: 600 14px var(--font-body); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .wn-plates i { font: 600 12px var(--font-body); font-style: normal; color: var(--fg-soft); }
  .wn-plates .me i { color: var(--fg); }
  .wn-recap { position: absolute; top: -64px; width: 140px; display: flex; flex-direction: column; align-items: center; text-align: center; }
  .wn-recap.l { left: -150px; } .wn-recap.r { left: 10px; }
  .wn-recap b { margin-top: 16px; font: 800 16px var(--font-body); }
  .wn-recap i { margin-top: 4px; font: 500 13px/1.4 var(--font-body); font-style: normal; color: var(--fg-soft); }
  .wn-rk { width: 60px; height: 60px; box-sizing: border-box; border-radius: 14px; border: 1.5px solid var(--fg); display: flex; align-items: center; justify-content: center; }
  .wn-rk svg { width: 24px; height: 24px; }
  .wn-r1 { position: relative; width: 60px; height: 60px; border-radius: 50%; background: var(--tile); color: var(--tile-ink);
    display: flex; align-items: center; justify-content: center; font: 900 26px var(--font-display); }
  .wn-rcrown { position: absolute; left: -16px; top: -21px; width: 45px; line-height: 0; transform-origin: 50% 100%; transition-duration: 460ms;
    transition-timing-function: cubic-bezier(.3,1.4,.5,1); }
  .wn-caps { position: absolute; left: 24px; right: 24px; top: 424px; height: 110px; }
  .wn-cap { position: absolute; left: 0; right: 0; top: 0; text-align: center; }
  .wn-h { font: 800 23px/1.2 var(--font-body); }
  .wn-word { display: inline-block; white-space: pre; transition-duration: 460ms; }
  .wn-s { margin-top: 8px; font: 500 15px/1.4 var(--font-body); color: var(--fg-soft); }
  .wn-rail { position: absolute; left: 20px; right: 20px; bottom: 26px; display: flex; gap: 10px; }
  .wn-rail > div { flex: 1; display: flex; flex-direction: column; gap: 8px; }
  .wn-rail span { font: 700 11px var(--font-body); letter-spacing: 1.2px; text-transform: uppercase; white-space: nowrap; color: var(--fg-faint); transition: color 400ms; }
  .wn-rail .on span { color: var(--fg); }
  .wn-rail i { display: block; height: 3px; border-radius: 2px; background: var(--rule); overflow: hidden; }
  .wn-rail b { display: block; height: 3px; background: var(--fg); }
  .wn-end { position: absolute; left: 20px; right: 20px; bottom: 14px; display: flex; flex-direction: column; gap: 2px; }
  .wn-end .primary { width: 100%; }
  @media (prefers-reduced-motion: reduce) {
    .wn-w { transition-duration: 1ms !important; transition-delay: 0ms !important; }
    .wn-ring, .attn-anim .key-dot, .attn-anim .key-ring, .rc-toast, .welcome-card { animation: none !important; }
  }
  .home-icon-btn svg { display: block; width: 20px; height: 20px; }
  .home-icon-btn:hover { color: var(--fg); border-color: var(--rule-strong); }
  .home-icon-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
  .primary.with-icon { display: inline-flex; align-items: center; justify-content: center; gap: 8px; }
  .primary.with-icon svg { display: block; }
  .rooms-list { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
  .room-row {
    display: flex; align-items: center; gap: 16px; width: 100%; text-align: left;
    background: var(--tray-raise); border: 1px solid var(--tray-line); color: var(--paper);
    border-radius: 14px; padding: 14px 16px; min-height: 64px; cursor: pointer; font: 500 14px var(--font-body);
  }
  .room-row:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
  .room-row .room-code { flex-shrink: 0; font: 700 20px var(--font-body); letter-spacing: 4px; color: var(--paper); }
  .room-meta { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
  .room-host { font-size: 14.5px; font-weight: 600; color: var(--paper); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .room-sub { font-size: 12.5px; color: var(--paper-dim); }
  .room-join { flex-shrink: 0; color: var(--ok-green); font-weight: 700; font-size: 14px; }
  /* Public games. A rule above it, because it is a second way in and not a
     footnote to the code field. Bottom margin is the sheet body's own breathing
     room; .modal-foot owns everything below the divider. */
  .open-block { margin: 18px 0 4px; padding-top: 14px; border-top: 1px solid var(--tray-line); }
  .open-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
  /* .home-icon-btn, not a smaller one-off. The design system is explicit: icon buttons
     are always 44x44 radius 12 in --fg-soft, and it documents exactly one exception
     (the 36px sheet close). A second exception is how "every icon button is this size"
     stops being true. Scoped subclass per the rule about never overriding a global
     class from inside a feature -- this only adds the disabled state. */
  .home-icon-btn.open-refresh { flex: 0 0 auto; }
  .home-icon-btn.open-refresh:disabled { opacity: .45; cursor: default; }

  /* Solo best. Track and fill are the tutorial rail's values: 4px, --rule under --fg.
     No new tokens, and nothing here is a pill -- the design system reserves those for
     a single live value, which this is not. */
  /* Top collapses against .solo-score's 10px bottom margin, so this resolves to 20
     rather than adding to it. The bottom is doing real work: .solo-lw is inline-flex
     and sat flush against the caption with nothing between them. */
  .best-rail { margin: 20px auto; max-width: 260px; }
  .best-track { position: relative; height: 4px; border-radius: 2px; background: var(--rule); }
  .best-fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 2px; background: var(--fg); }
  .best-tick { position: absolute; top: -5px; bottom: -5px; width: 2px; border-radius: 1px; background: var(--fg-soft); }
  .best-cap { display: flex; justify-content: space-between; margin-top: 7px;
    font: 600 11px var(--font-body); letter-spacing: 1.2px; text-transform: uppercase; color: var(--paper-dim); }
  .open-t { font: 700 14px var(--font-body); color: var(--paper); margin: 0 0 2px; }
  .open-sub { font: 500 12.5px var(--font-body); color: var(--paper-dim); margin: 0 0 10px; line-height: 1.45; }
  /* Dashed, so an empty list reads as a slot waiting to fill rather than a card
     with nothing in it. Same corner and inset as .room-row. */
  .open-empty { font: 500 13px var(--font-body); color: var(--paper-dim); line-height: 1.5;
    border: 1px dashed var(--tray-line); border-radius: 12px; padding: 16px; text-align: center; margin: 0; }
  .rooms-list + .open-empty { margin-top: 8px; }
  .disclosure-row {
    width: 100%; display: flex; align-items: center; justify-content: space-between;
  }
  .chev-toggle {
    background: none; border: none; padding: 0; cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; margin: -15px -10px -15px 0; color: var(--fg-soft);
  }
  .chev-toggle:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; border-radius: 8px; }
  .chev { color: var(--paper-dim); display: inline-flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; flex-shrink: 0; transition: transform 0.15s; }
  .chev svg { display: block; }
  .chev.open { transform: rotate(180deg); }
  .chev:hover { color: var(--fg); }
  @media (prefers-reduced-motion: reduce) { .chev { transition: none; } }
  .dict-dot.ready { background: var(--valid); }
  .dict-dot.loading { animation: pulse 1.2s infinite; }
  @keyframes pulse { 50% { opacity: 0.3; } }
  @media (prefers-reduced-motion: reduce) { .dict-dot.loading { animation: none; } }

  .eyebrow { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 2px; color: var(--paper-dim); margin: 0 0 8px; }
  .center { text-align: center; }
  .hint { color: var(--paper-dim); font-size: 13.5px; margin: 8px 0; line-height: 1.45; }
  /* Sits in .hint's slot and inherits its metrics exactly, because it replaces the hint
     rather than joining it. .err is a page-level alert -- surface, border, padding --
     which is far too much object for a note about the field directly above it. */
  .field-err { color: var(--error-text); font-size: 13.5px; margin: 8px 0 0; line-height: 1.45; }
  .meta { color: var(--paper-dim); font-size: 13px; margin: 12px 0; line-height: 1.5; }
  /* One rhythm down the lobby: 14 between blocks, 16 either side of the settings
     group. It was 8 under the code and 10 under the toggle, which is why four
     lines of small print read as one grey smudge — nothing had room to be a
     separate thought. */
  /* ===== TEAM MODE ===== */
  /* No colour to separate the two sides with: --gold resolves to --fg, so the palette
     has one foreground and one ground. Teams are told apart by position and label. */
  .team-tools { margin: 4px 0 12px; }
  .team-seg { width: 100%; }
  .team-group + .team-group { margin-top: 14px; }
  .team-head { display: flex; align-items: baseline; justify-content: space-between;
    padding: 0 4px 6px; border-bottom: 1px solid var(--tray-line); margin-bottom: 6px; }
  .team-name { font: 700 12px var(--font-body); letter-spacing: 1.4px; text-transform: uppercase; color: var(--paper); }
  .team-count { font: 600 12px var(--font-body); color: var(--paper-dim); }
  .lobby-row.tappable { cursor: pointer; }
  .team-move { font: 600 12px var(--font-body); color: var(--paper-dim); flex-shrink: 0; }
  .team-block + .team-block { margin-top: 22px; }
  .team-bar { display: flex; align-items: baseline; gap: 8px; padding: 0 4px 8px;
    border-bottom: 1px solid var(--tray-line); margin-bottom: 8px; }
  .team-bar-name { font: 700 12px var(--font-body); letter-spacing: 1.4px; text-transform: uppercase; color: var(--paper); }
  .team-bar-n { flex: 1; font: 600 11.5px var(--font-body); color: var(--paper-dim); }
  .team-bar-pts { font: 800 20px var(--font-body); color: var(--paper); font-variant-numeric: tabular-nums; }
  .team-bonus { margin: -2px 0 8px; padding: 0 4px; font: 600 12px var(--font-body); color: var(--ok-green); }
  .team-shared { margin-top: 6px; background: var(--tray-raise); border-radius: 12px; padding: 4px 0 6px; }
  .team-shared-h { margin: 0; padding: 10px 16px 4px; font: 600 11.5px var(--font-body); color: var(--paper-dim); }
  .rc-row.static { cursor: default; }
  .rc-pts em.shared { border-color: var(--tray-line); color: var(--paper-dim); }

  .setting-chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin: 16px 0 0; }
  /* v3.13 lobby settings as rows: label-left micro, value-right 16/600, hint 13.5/500.
     No icons (no kit yet); type carries the hierarchy. Reads as a settings summary,
     and doubles as the rules for a code-joiner. */
  .set-rows { border: 1px solid var(--rule); border-radius: 16px; overflow: hidden; margin: 16px 0 0; }
  /* v3.14.4: settings as a compact 2-col grid instead of stacked full-width rows.
     Reference info the host set — glanceable, not skimmed. Room privacy is pulled
     OUT of here into its own toggle up by the code (hosts flip it often). */
  .set-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin: 0; }
  .set-cell { background: var(--raise); border-radius: 12px; padding: 10px 12px; }
  .set-cell .set-k { display: block; font: 700 10px var(--font-body); letter-spacing: 0.1em; text-transform: uppercase; color: var(--fg-faint); margin: 0 0 3px; }
  .set-cell .set-cv { font-weight: 500; font-size: 14px; color: var(--fg); line-height: 1.2; }
  /* Privacy toggle — host-only live control, sits under the room code. A guest
     sees the read-only .room-priv label instead (built from the same state). */
  /* Segmented control — per the design system: the ON segment fills with --fg
     (reads as loud as a primary), --ground ink; OFF segments are --fg-soft on
     transparent. 44px tall, radius 10. This is the "active/primary" fill, NOT the
     recessed --select treatment used for traced tiles — a segment sits inside a
     same-colored track with no board behind it, so it needs the solid fill to
     read. (Previously mis-used --select here, which vanished into the track.) */
  /* Private/Public uses .seg as-is; this only sizes and centres it under the code. */
  .room-seg { width: 200px; margin: 6px auto 0; }
  .room-seg button { flex: 1 1 0; min-width: 0; padding: 0 10px; line-height: 1; margin: 0;
    -webkit-appearance: none; appearance: none; -webkit-tap-highlight-color: transparent; }
  .room-priv { display: flex; align-items: center; justify-content: center; gap: 6px; margin: 6px 0 0;
    font: 500 13px var(--font-body); color: var(--fg-soft); }
  .set-head { display: flex; align-items: center; justify-content: space-between; margin: 20px 2px 9px; }
  .edit-settings-btn { font: 700 12.5px var(--font-body); color: var(--fg); background: none; border: 1.5px solid var(--rule-strong); border-radius: 10px; padding: 7px 14px; cursor: pointer; -webkit-tap-highlight-color: transparent; }
  .set-row { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; padding: 14px 16px; border-bottom: 1px solid var(--rule); }
  .set-row:last-child { border-bottom: none; }
  .set-k { font: 700 11px var(--font-body); letter-spacing: 0.1em; text-transform: uppercase; color: var(--fg-soft); flex: none; }
  .set-v { font-weight: 500; font-size: 14px; color: var(--fg); text-align: right; }
  .set-v span { font-weight: 500; color: var(--fg-faint); font-size: 12.5px; }
  .brand-btn { background: none; border: none; padding: 0; margin: 0; cursor: pointer; display: flex; flex: 1; align-items: center; justify-content: flex-start; -webkit-tap-highlight-color: transparent; }
  .lobby-hint { margin-top: 14px; }
  /* one confirm pattern, used by both the remove-player row and the new-code
     bar: question stacked over two EQUAL-width buttons. The remove confirm used
     to jam all three onto one row, so Cancel shrank to its text and the danger
     button sprawled to fill the rest — that was the lopsided pair in the shot. */
  .bar-confirm { margin-top: 8px; border: 1px solid var(--tray-line); background: var(--tray-raise); border-radius: 12px; padding: 12px; }
  .lobby-row.confirm { flex-direction: column; align-items: stretch; gap: 10px; padding: 12px 4px; }
  .confirm-q { margin: 0; color: var(--paper); font: 600 14px var(--font-body); line-height: 1.4; }
  .confirm-q.center { text-align: center; }
  /* 8px between the question and its buttons. They were flush, which read as one
     block and made the question feel like a button label. Same gap as between the
     buttons themselves, so the group is evenly spaced in both directions. */
  .confirm-row { display: flex; gap: 8px; margin-top: 8px; }
  /* danger follows the same rule as .secondary — outline, not a filled slab.
     It fills only on :active, i.e. the tap that actually commits. */
  .btn-confirm { flex: 1; min-height: 52px; border-radius: 12px; padding: 13px 16px;
    font: 600 15px var(--font-body); cursor: pointer;
    border: 1.5px solid var(--tray-line); background: none; color: var(--paper); }
  .btn-confirm.primary-confirm { background: var(--cta); border-color: var(--cta); color: var(--cta-ink);
    font-weight: 700; box-shadow: 0 2px 6px var(--shadow); }
  .btn-confirm.danger { border-color: var(--bad-line); color: var(--error-text); }
  .btn-confirm.danger:active { background: var(--bad-fill); border-color: var(--bad-line); color: var(--bad-ink); }
  .board-note { color: var(--paper-dim); font-size: 12px; margin: 8px 0 0; line-height: 1.4; opacity: 0.85; }
  .err { background: var(--error-surface); border: 1px solid var(--error-border); color: var(--error-text); font-size: 13.5px; margin: 12px 0 0; border-radius: 10px; padding: 10px 12px; line-height: 1.45; }
  /* A real <button>: iOS Safari does not reliably dispatch click on non-interactive
     elements, so the seven-tap gesture silently did nothing on a <p>. The padding
     buys a usable tap target out of 11.5px text. */
  .foot { display: block; width: 100%; background: none; border: none; cursor: pointer;
    color: var(--paper-dim); font: 11.5px/1.6 var(--font-body); margin-top: 24px;
    text-align: center; padding: 14px 0; -webkit-tap-highlight-color: transparent; }

  .tagline { padding: 8px 0 16px; }
  .hero-line { font-family: var(--font-die); font-size: 24px; line-height: 1.35; margin: 0; color: var(--paper); }

  .lbl { display: block; font: 700 12px var(--font-body); text-transform: uppercase; letter-spacing: 1.5px; color: var(--paper-dim); margin-bottom: 8px; }
  .field {
    width: 100%; background: var(--tray-raise); border: 1px solid var(--tray-line); color: var(--paper);
    border-radius: 12px; padding: 0 14px; font: 500 16px var(--font-body); height: 52px; line-height: 1;
  }
  .field.code { text-transform: uppercase; letter-spacing: 6px; font: 700 20px var(--font-body); text-align: center; height: 52px; }

  .fieldset { margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--tray-line); }
  /* mode-row's margin-bottom is 0 on purpose: nothing for the card's margin to
     collapse against, so Classic's first toggle and Survival's card start level. */
  .mode-row { padding-bottom: 14px; margin-bottom: 0; border-bottom: 1px solid var(--tray-line); }
  /* The classic rows ARE the slot. The survival card is taken OUT of the height
     calculation entirely (absolute, inset 0) rather than stacked in the same
     grid cell — stacked, the cell was as tall as the TALLER panel, so Classic
     rendered ~35px of nothing under Min letters: the hidden card's shadow.
     Absolute means it cannot push the slot on any phone, whatever the font
     metrics do, so the alignment holds by construction instead of by luck. */
  .mode-panels { position: relative; margin: 12px 0; }
  .classic-panel { display: flex; flex-direction: column; gap: 12px; }
  /* the panel owns the rhythm now, so the rows stop bringing their own */
  .mode-panels .opt-row { margin: 0; }
  /* 3.23: Trivia has its own rows (Round, Difficulty, rules), so whichever panel is showing
     sets the height and the hidden one is lifted out of flow. */
  .mode-panels > .off { visibility: hidden; pointer-events: none; position: absolute; left: 0; right: 0; top: 0; }
  .surv-panel { padding: 0; display: flex; flex-direction: column; gap: 12px; }
  .surv-panel .mode-card { height: auto; }
  .opt-col { display: flex; flex-direction: column; gap: 8px; }
  .seg-full button { padding: 10px 4px; font-size: 12.5px; }
  .mode-card {
    background: var(--tray-raise); border: 1px solid var(--tray-line); border-radius: 12px;
    padding: 14px; height: 100%; box-sizing: border-box;
    display: flex; flex-direction: column;
  }
  /* Five rules and a footer have to live inside three toggle rows. This is the
     price of the alignment: if it ever reads too small, cut a rule — do not
     grow the slot, or the gap comes back. */
  .mc-rules { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
  .mc-tl { display: flex; align-items: flex-start; margin-top: auto; padding: 2px 2px 10px; }
  .mc-step { flex: 1; text-align: center; position: relative; }
  .mc-rail { position: absolute; top: 3px; left: 50%; right: -50%; height: 1px; background: var(--tray-line); }
  .mc-dot { display: block; width: 7px; height: 7px; border-radius: 50%; background: var(--gold); margin: 0 auto 7px; position: relative; }
  .mc-t { display: block; font: 800 12px var(--font-body); color: var(--gold); line-height: 1; font-variant-numeric: tabular-nums; }
  .mc-l { display: block; font: 500 11px var(--font-body); color: var(--paper-dim); line-height: 1.25; margin-top: 3px; }
  .mc-rules li { display: flex; align-items: flex-start; gap: 10px; }
  .mc-n { flex: 0 0 16px; height: 16px; border-radius: 50%; background: var(--tray);
    border: 1px solid var(--tray-line); color: var(--paper-dim);
    font: 700 11px var(--font-body); display: grid; place-items: center; margin-top: 0.5px; }
  /* 13.5px: the sheet's own labels are 14px, so 12.5 read as fine print next to
     the thing it explains. These are the only instructions in the app. */
  .mc-r { font: 500 13px var(--font-body); color: var(--paper); line-height: 1.3; }
  .mc-r b { font-weight: 800; }
  .mc-r b.g { color: var(--ok-green); }
  .mc-r em { font-style: normal; font-weight: 800; color: var(--gold); font-variant-numeric: tabular-nums; }
  .mc-foot { margin: 0; padding-top: 10px; border-top: 1px solid var(--tray-line);
    font: 500 11px var(--font-body); color: var(--paper-dim); text-align: center; line-height: 1.35; }
  .opt-row { display: flex; align-items: center; justify-content: space-between; margin: 12px 0; gap: 12px; }
  .opt-lbl { font: 600 14px var(--font-body); }
  .seg { display: flex; align-items: stretch; border: 1px solid var(--tray-line); border-radius: 10px; overflow: hidden; }
  .seg button { flex: 1; display: flex; align-items: center; justify-content: center;
    background: none; border: none; color: var(--paper-dim); padding: 10px 13px;
    font: 600 13px var(--font-body); line-height: 1; cursor: pointer; min-height: 44px; white-space: nowrap;
    -webkit-appearance: none; appearance: none; margin: 0; }
  .seg button.on { background: var(--gold); color: var(--gold-ink); }

  .primary {
    background: var(--cta); color: var(--cta-ink); border: none; border-radius: 12px;
    padding: 15px 18px; font: 700 16px var(--font-body); cursor: pointer; min-height: 52px;
    box-shadow: 0 2px 6px var(--shadow-lift);
  }
  .primary.big { width: 100%; font-size: 17px; margin-top: 16px; }
  .primary:disabled { opacity: 0.4; }
  .text-btn { background: none; border: none; color: var(--paper-dim); font: 600 14px var(--font-body); cursor: pointer; min-height: 44px; width: 100%; }
  .text-btn.with-icon { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
  .text-btn.with-icon svg { display: block; }
  .text-btn:hover { color: var(--fg); }
  .text-btn.left { width: auto; text-align: left; padding-left: 0; }
  .text-btn.small { width: auto; min-height: 32px; font-size: 13px; }
  button:focus-visible, input:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
  /* Shared press state — subtle dip on tap, consistent across every button.
     Applies to all buttons EXCEPT the swipe tiles (.tile), where a press dip
     would fight the trace. Reduced-motion users get a faint dim, no movement. */
  .primary:active, .secondary:active, .danger:active, .home-icon-btn:active, .icon-secondary:active,
  .text-btn:active, .room-row:active, .chev-toggle:active, .seg button:active,
  .standing-head:active, .modal-x:active, .tv-sum-btn:active {
    transform: scale(0.98);
    filter: brightness(0.94);
    transition: transform 0.07s ease, filter 0.07s ease;
  }
  .primary, .secondary, .danger, .home-icon-btn, .icon-secondary, .text-btn,
  .room-row, .chev-toggle, .seg button, .standing-head, .modal-x, .tv-sum-btn {
    transition: transform 0.07s ease, filter 0.07s ease;
  }
  /* Score cards press as ONE piece — the whole card, not just the inner button,
     so the border compresses with the content. Light weight (half the old travel).
     Gated on :has() support; older browsers keep the inner-button press above. */
  @supports selector(:has(*)) {
    .standing-head:active { transform: none; filter: none; }
    .standing { transition: transform 0.09s ease, filter 0.09s ease; }
    .standing:has(.standing-head:active) { transform: scale(0.99); filter: brightness(0.97); }
  }
  @media (prefers-reduced-motion: reduce) {
    .primary:active, .secondary:active, .danger:active, .home-icon-btn:active, .icon-secondary:active,
    .text-btn:active, .room-row:active, .chev-toggle:active, .seg button:active,
    .standing-head:active, .modal-x:active, .tv-sum-btn:active { transform: none; filter: brightness(0.9); }
    @supports selector(:has(*)) {
      .standing:has(.standing-head:active) { transform: none; filter: brightness(0.9); }
    }
  }

  .join-row { display: flex; gap: 8px; }
  .join-row .field { flex: 1; min-width: 0; }
  .home-links { display: flex; gap: 8px; margin-top: 24px; }
  .dict-btn { margin-top: 16px; }
  .dict-divider { border: none; border-top: 1px solid var(--tray-line); margin: 24px 0 16px; }
  /* action row: Create (primary) + Practice (secondary), always visible */
  .action-duo { display: flex; gap: 8px; margin-top: 24px; }
  .action-duo .primary, .action-duo .secondary { flex: 1; }
  .hint b { color: var(--fg); font-weight: 700; }
  .secondary.full { width: 100%; }
  .secondary {
    flex: 1; background: transparent; border: 1.5px solid var(--tray-line); color: var(--paper);
    border-radius: 12px; padding: 13px 16px; font: 600 15px var(--font-body); cursor: pointer; min-height: 52px;
  }
  .secondary:disabled, .danger:disabled { opacity: 0.4; }
  .secondary:focus-visible, .danger:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

  .room-code { font-family: var(--font-display); font-size: 54px; letter-spacing: 14px; text-align: center; margin: 0; padding-left: 14px; }
  .lobby-row { display: flex; align-items: center; gap: 8px; padding: 12px 4px; border-bottom: 1px solid var(--tray-line); min-height: 56px; }
  .lobby-row.confirm { gap: 10px; }
  .lobby-confirm { flex: 1; font: 600 14px var(--font-body); color: var(--paper); }
  .row-x { width: 44px; height: 44px; display: grid; place-items: center; background: none; border: none;
    color: var(--paper-dim); cursor: pointer; border-radius: 12px; margin-right: -8px; flex-shrink: 0; }
  .row-x:hover { color: var(--bad-ink); background: var(--bad-fill); }

  .removed-wrap { text-align: center; padding: 48px 8px; }
  .removed-t { font: 800 21px var(--font-display); color: var(--paper); margin: 0 0 8px; line-height: 1.25; }
  .lobby-name { flex: 1; font: 600 15px var(--font-body); display: inline-flex; align-items: center; gap: 6px; }
  /* Win-streak flame — monochrome, count beside it, explicit SVG size so it never balloons. */
  .sn-row { display: flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%; align-self: flex-start; }
  .sn-row .sn-name { min-width: 0; }
  .streak { display: inline-flex; align-items: center; gap: 2px; color: var(--fg-soft); flex: 0 0 auto; }
  .streak svg { flex: 0 0 auto; display: block; }
  .streak-n { font: 800 12px var(--font-body); color: var(--fg-soft); line-height: 1; }
  .host-tag, .you-tag { font: 700 10px var(--font-body); text-transform: uppercase; letter-spacing: 1px; border-radius: 999px; padding: 3px 8px; }
  .host-tag { background: var(--gold); color: var(--gold-ink); }
  .you-tag { border: 1px solid var(--tray-line); color: var(--paper-dim); }

  .countdown { padding-top: 16vh; text-align: center; }
  .count-ready { font: 700 22px var(--font-body); color: var(--paper-dim); margin: 0; letter-spacing: 1px; }
  .count-num { font-family: var(--font-display); font-size: 110px; margin: 0; color: var(--paper); }
  .count-num.pop { animation: pop 0.25s ease-out; }
  @keyframes pop { from { transform: scale(1.35); opacity: 0.3; } to { transform: scale(1); opacity: 1; } }
  @media (prefers-reduced-motion: reduce) { .count-num.pop { animation: none; } }
  .timer.done { font-family: var(--font-display); font-size: 44px; color: var(--valid); margin: 0 0 6px; }

  .game-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
  .pill { border-radius: 999px; padding: 8px 16px; font: 800 16px var(--font-body); font-variant-numeric: tabular-nums; }
  .timer-pill { background: var(--tray-raise); border: 1px solid var(--tray-line); color: var(--paper); letter-spacing: 1px; }
  /* --ground, never #fff. --danger is a LIGHT rose in dark mode (#F2B8BC), so white
     on it lands near 1.4:1. Type on any semantic fill is --ground: ink on the rose in
     dark, cream on #8E2430 in light, legible in both. */
  .timer-pill.urgent { background: var(--urgent); border-color: var(--urgent); color: var(--ground); }
  .score-pill { background: var(--gold); color: var(--gold-ink); }

  /* Room between the timer row and the board — the swipe area is its own thing,
     not a lid on the grid. */
  /* ── Sticky action bar ─────────────────────────────────────────────────────
     The results screen is ~66% review content — board, standings, word list —
     with the only action underneath all of it, LAST in reading order. Moving it
     up helps until the content grows or the phone shrinks; pinning it is the
     only version that can't break. On an iPhone SE the old button sat ~200px
     below the fold.
     Fixed to the viewport, not absolute in .app — .app is min-height:100vh, so
     absolute would pin it to the bottom of the PAGE, which is the bug we're
     fixing. Matches .app's 480px cap and 20px gutters so it lines up with the
     content it belongs to. */
  /* Results normally scrolls with the page body. But this screen is reached
     mid-multiplayer-round, right after any Sheet/tutorial's body-scroll-lock
     (useBodyScrollLock) may have been active — and if that lock doesn't release
     cleanly in some transition, the whole page stops scrolling, and the last
     leaderboard rows end up trapped under the fixed .actionbar with no way to
     reach them. Giving this screen its own bounded, independently-scrolling
     region means it works even if body scroll is stuck: this box scrolls on
     its own regardless of what document.body can or can't do. 140px clears the
     header's typical height (safe-area + 32px padding + 52px + 20px margin). */
  /* Reserve EXACTLY the action bar's real height (measured at runtime into
     --bar-h), not a hardcoded guess. The old 100dvh-minus-300px assumed one bar
     height; the host's two-button bar plus iOS Safari's dynamic toolbar made the
     real bar taller than 300px on some phones, so the last leaderboard rows hid
     under it. Measuring kills the guesswork — host or guest, any phone, toolbar
     shown or not, the reserve matches the bar. The 96px is breathing room. */
  .results-main { max-height: calc(100dvh - var(--bar-h, 200px) - 96px); overflow-y: auto; -webkit-overflow-scrolling: touch; }
  .actionbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
    padding-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--bar-pad));
    /* A section, not a floating shelf. border-top on the tray surface is the
       app's own divider — the same one .fieldset uses to open a section and
       .modal-foot uses to close a bottom sheet. A gradient had no edge, so the
       buttons read as hovering over the roster rather than living somewhere.
       The cost, accepted: content is cut at the line instead of fading out. */
    background: var(--tray);
    border-top: 1px solid var(--tray-line);
  }
  .actionbar-in {
    max-width: 480px; margin: 0 auto;
    /* same gutters as .app, so the buttons line up with the content above them */
    padding: var(--bar-pad) max(20px, env(safe-area-inset-left)) 0 max(20px, env(safe-area-inset-right));
  }
  .actionbar-in .primary, .actionbar-in .secondary { margin: 0; }
  .actionbar-in .secondary { margin-top: 8px; }
  .actionbar-wait { margin: 0 0 8px; text-align: center; color: var(--paper-dim);
    font: 600 12.5px var(--font-body); line-height: 1.35; }
  /* Reserves the scroll the bar covers, so the last row can still be read.
     Heights are per-role because the bars aren't the same size. */
  .action-pad { height: 82px; }
  .action-pad.host { height: 150px; }
  .action-pad.guest { height: 142px; }
  /* the lobby's guest bar is status text with no button under it */
  .action-pad.waitonly { height: 60px; }
  .swipe-zone { margin-top: 20px; margin-bottom: 12px; }
  /* Names the zone at rest; reports the verdict after a word. Plain text with no
     box — which is exactly why the verdict lives here: it can change length
     without anything jumping. 12px below, matching the pill's 12px to the grid,
     so the pill sits evenly between the two instead of crowded upward. */
  .swipe-hint { font: 700 10px var(--font-body); letter-spacing: 2px; text-transform: uppercase;
    color: var(--paper-dim); text-align: center; margin: 0 0 12px; min-height: 12px; }
  .swipe-hint.ok { color: var(--ok-green); }
  .swipe-hint.bad { color: var(--error-text); }
  .swipe-hint.short { color: var(--gold); }
  .swipe-hint.dup { color: var(--paper-dim); }
  /* Hugs the word as you spell — that growth is the character of the thing, not a
     bug. It holds ONLY the word, so releasing never changes its width: the verdict
     went to the caption. No floor, no eased width; nothing to ease. */
  .word-pill {
    display: block; margin: 0 auto 12px; width: fit-content; max-width: 100%;
    border-radius: 999px; padding: 9px 18px; min-height: 40px;
    font: 800 17px var(--font-body); letter-spacing: 2px; text-align: center;
    background: var(--tray-raise); border: 1px solid var(--tray-line); color: var(--paper-dim);
    transition: background 0.12s, border-color 0.12s, color 0.12s;
  }
  /* Resting: no box, so touching a tile has nothing to collapse. min-height holds
     the slot or the grid would slide up and down as you trace.
     transition:none matters more than it looks. Arriving, the pill fades from
     grey to green and that is lovely. LEAVING, the text is swapped by React and
     border-color is not transitioned — both go instantly — while the background
     was still easing out. Result: a green blob with no word, hanging around for
     ~7 frames after the word had gone. Same disease as the tiles: some properties
     eased, some not, so the thing comes apart on its way out. Arriving fades,
     leaving doesn't. */
  .word-pill.empty { background: transparent; border-color: transparent; transition: none; }
  .word-pill.neutral { color: var(--paper); }
  .word-pill.ok { background: var(--ok-green); border-color: var(--ok-green); color: var(--ok-ink); }
  .word-pill.dup { background: var(--tray-raise); color: var(--paper-dim); }
  .word-pill.short { background: var(--tray-raise); color: var(--gold); }
  .word-pill.bad { background: var(--bad-fill); border-color: var(--bad-line); color: var(--bad-ink); }
  /* found words: hero (latest) + mini tags. Option X: no box when empty. */
  /* The letter bar. Same uppercase micro-label as .words-head, so it reads as
     part of the app rather than a survival-only invention. It goes gold for a
     beat when the rule changes, then goes quiet again. */
  /* The phase moved into .swipe-hint's rest state — above the board, where the
     eye already is. The 16/16 spacing this used to need went with it. */
  .words-head { display: flex; justify-content: space-between; font-size: 10px; letter-spacing: 2px; text-transform: uppercase; color: var(--paper-dim); font-weight: 700; margin-top: 24px; margin-bottom: 8px; }
  .hero-empty { min-height: 20px; display: flex; align-items: center; justify-content: center; }
  .hero-rule { width: 40px; height: 2px; background: var(--tray-line); border-radius: 2px; }
  /* quiet word list (option A): newest subtly green, rest white, no loud box */
  .wlist { display: flex; flex-direction: column; }
  .wrow { display: flex; justify-content: space-between; align-items: baseline; padding: 9px 2px; border-top: 1px solid var(--tray-line); }
  .wrow:first-child { border-top: none; }
  .wword { font-size: 16px; font-weight: 600; letter-spacing: 0.3px; color: var(--paper); }
  .wpts { font-size: 12.5px; font-weight: 600; color: var(--paper-dim); white-space: nowrap; }
  .wrow.latest .wword { color: var(--ok-green); }
  @media (prefers-reduced-motion: reduce) { .word-pill { transition: none; } }
  /* ── Update reel player (3.22.0) ── */
  /* Full-screen takeover: a solid ground behind the card, so nothing from Home shows
     through under it on tall phones (it read as a second card at the bottom). */
  .scrim.wn-scrim { background: #202845; animation: none; }
  .wn-reel [data-r="card"] { border-color: transparent !important; }
  .wn-reel { position: relative; width: 350px; height: 640px; flex-shrink: 0; transform-origin: center; color: #F2F0EA;
    font-family: system-ui, -apple-system, 'Segoe UI', sans-serif; -webkit-user-select: none; user-select: none; }
  .wn-reel-host { position: absolute; inset: 0; }
  /* ── Home · Compete cards (3.22.0) ── */
  .play-sec .action-duo .secondary { min-width: 0; }
  .cc-head { display: flex; align-items: baseline; justify-content: space-between; }
  .cc-date { font: 600 12.5px var(--font-body); color: var(--paper-dim); }
  .cc-card { position: relative; width: 100%; box-sizing: border-box; border-radius: 14px; padding: 14px; text-align: left; font: inherit; color: var(--paper);
    background: var(--tray-raise); border: 1px solid var(--tray-line); cursor: pointer; -webkit-tap-highlight-color: transparent; }
  .cc-card + .cc-card, .cc-card + .cc-pair { margin-top: 10px; }
  .cc-card.play { border-color: color-mix(in srgb, var(--select-stroke) 100%, transparent); }
  .cc-card.done { background: color-mix(in srgb, var(--ok-green) 7%, var(--tray-raise)); border-color: color-mix(in srgb, var(--ok-green) 45%, transparent); }
  .cc-card.done .cc-arrow { color: var(--ok-green); }
  .cc-card:disabled { opacity: .5; cursor: default; }
  .cc-row, .cc-wide { display: flex; align-items: center; gap: 16px; }
  .cc-row { gap: 14px; }
  .cc-row .place { width: 40px; height: 40px; font-size: 17px; flex-shrink: 0; }
  .cc-txt { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: flex-start; }
  .cc-eb { display: inline-flex; align-items: center; gap: 5px; font: 700 11px var(--font-body); letter-spacing: 1.2px; text-transform: uppercase; color: var(--paper); }
  .cc-eb.ok { color: var(--ok-green); }
  .cc-meta { margin-top: 4px; font: 600 13px var(--font-body); color: var(--paper-dim); }
  .cc-big { margin-top: 3px; font: 800 22px/1.15 var(--font-body); color: var(--paper); font-variant-numeric: tabular-nums; }
  .cc-arrow { width: 24px; height: 24px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; color: var(--paper); }
  /* Arrow sits in the top-right corner of every card, tucked close to the corner (not centred, not inside the padding). */
  .cc-card .cc-arrow { position: absolute; top: 8px; right: 8px; }
  .cc-row, .cc-wide { padding-right: 36px; }
  .cc-top { padding-right: 22px; }
  .cc-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
  .cc-pair .cc-card { margin-top: 0; }
  .cc-tall { display: flex; flex-direction: column; min-height: 140px; min-width: 0; }
  .cc-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 6px; min-height: 40px; margin-bottom: 14px; }
  .cc-tall .cc-txt { margin-top: auto; width: 100%; }
  /* trivia card (3.23.2): rank circle + "2nd today / of 14"; one quiet line that truncates */
  .cc-rank { display: flex; align-items: center; gap: 8px; min-width: 0; }
  .cc-rank .place { width: 32px; height: 32px; font-size: 15px; font-weight: 800; }
  .cc-rank-t { display: flex; flex-direction: column; font: 700 12px/1.25 var(--font-body); color: var(--paper-dim); white-space: nowrap; }
  .cc-rank-t.ok { color: var(--ok-green); }
  .cc-rank-t span { font-weight: 600; color: var(--paper-dim); }
  .cc-trline { align-self: stretch; margin-top: 3px; font: 600 12.5px var(--font-body); color: var(--paper-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
  .cc-who { margin-top: 10px; display: inline-flex; align-items: center; gap: 7px; font: 700 13.5px var(--font-body); color: var(--paper); max-width: 100%; }
  .cc-who b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .cc-cover { display: flex; flex-direction: column; gap: 3px; animation: cc-in 600ms cubic-bezier(.2,.8,.2,1) both; }
  @keyframes cc-in { from { opacity: 0; } to { opacity: 1; } }
  .cc-line { display: flex; gap: 3px; }
  .cc-t { width: var(--s, 17px); height: var(--s, 17px); box-sizing: border-box; border-radius: calc(var(--s, 17px) * .28); display: flex; align-items: center; justify-content: center;
    font: 700 calc(var(--s, 17px) * .55) var(--font-body); line-height: 1; background: var(--tile); color: var(--tile-ink); border: 1.5px solid var(--tile); }
  .cc-t.sel { background: var(--select); color: var(--select-ink); border-color: var(--select-stroke); }
  .cc-t.empty { background: none; border: 1.5px solid var(--tray-line); }
  .cc-t.lock { background: none; border: none; color: var(--paper-dim); }
  .cc-q { flex-direction: row; align-items: center; gap: 4px; }
  .cc-qt { width: 32px; height: 32px; border-radius: 9px; display: flex; align-items: center; justify-content: center; background: var(--tile); color: var(--tile-ink);
    font: 900 17px var(--font-display); }
  .cc-q .cc-t { --s: 13px; }
  /* 3.24.0: a 320px screen gives each half card 107px. Five 17px slots and the arrow need 119, so
     the arrow sat on the Longest card's fifth slot; the Trivia card's place and "1st today" ran 5px
     under its arrow. Under 360px the slots are 14px and the place is the standard 28px circle. */
  @media (max-width: 359px) {
    .cc-slots { --s: 14px; }
    .cc-rank { gap: 6px; }
    .cc-rank .place { width: 28px; height: 28px; font-size: 14px; }
    .cc-rank-t { font-size: 11px; }
  }
  @media (prefers-reduced-motion: reduce) { .cc-cover { animation: none; } }
  /* ── Daily trivia (3.22.0) ── */
  .tv-label { margin-top: 0; }
  .tv-top { margin: 10px 0 0; }
  .tv-qn { margin: 14px 0 0; text-align: center; font: 700 10.5px var(--font-body); letter-spacing: 1.5px; text-transform: uppercase; color: var(--paper-dim); }
  /* 24px either side of the question (main gives 16), and no balanced wrapping, so it runs wide */
  .tv-q { margin: 6px 8px 0; text-align: center; font: 800 20px/1.3 var(--font-body); color: var(--paper); }
  /* 3.23.4: one cell, as tall as the run's longest question, so the board never moves.
     3.23.6: the cell is a card, styled like the home cards (raise, rule, 14px corners). Label and
     question sit centred in it with equal space above and below, so a short question reads as a
     padded card instead of a gap. */
  .tv-qs { display: grid; align-items: center; margin-top: 14px; padding: 14px 16px; border-radius: 14px;
    background: var(--raise); border: 1px solid var(--rule); }
  .tv-qs > * { grid-area: 1 / 1; }
  .tv-qs > .ghost { visibility: hidden; }
  .tv-qs .tv-qn { margin: 0; }
  .tv-qs .tv-q { margin: 6px 0 0; }
  /* the pill sits dead centre between the question and the tiles */
  .tv-slot { height: 40px; margin: 18px 0; display: flex; align-items: center; justify-content: center; }
  .tv-slot .word-pill { margin: 0; }
  .tv-rest { font: 700 10px var(--font-body); letter-spacing: 2px; text-transform: uppercase; color: var(--paper-dim); }
  .tv-acts { margin-top: 22px; display: flex; justify-content: center; gap: 10px; }
  .tv-btn { height: 44px; padding: 0 16px 0 12px; border-radius: 12px; display: inline-flex; align-items: center; gap: 8px; background: none;
    border: 1.5px solid var(--paper); color: var(--paper); font: 700 15px var(--font-body); cursor: pointer; }
  .tv-btn:disabled { opacity: .5; cursor: default; }
  .tv-btn.used { border: 1px solid var(--tray-line); color: var(--paper-dim); cursor: default; }
  .tv-sum-head { margin-top: 22px; display: flex; flex-direction: column; align-items: center; gap: 4px; }
  .tv-sum-n { font: 900 54px/1.05 var(--font-display); color: var(--paper); }
  .tv-sum-sub { font: 600 14px var(--font-body); color: var(--paper-dim); }
  .tv-sum-list { margin-top: 20px; display: flex; flex-direction: column; }
  .tv-sum-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 2px; border-top: 1px solid var(--tray-line); }
  .tv-sum-row .cc-t:not(.sel) { background: none; color: var(--paper-dim); border: 1.5px dashed var(--tray-line); }
  .tv-sum-k { font: 700 11px var(--font-body); letter-spacing: 1.2px; text-transform: uppercase; color: var(--paper-dim); }
  .tv-sum-k.ok { color: var(--ok-green); }
  .tv-sum-foot { margin-top: 24px; }
  /* 3.25.5: a row is a button that opens its question; 48px to tap, a right chevron says it opens */
  .tv-sum-btn { width: 100%; min-height: 48px; box-sizing: border-box; background: none; border: 0; border-top: 1px solid var(--rule); border-radius: 0;
    color: inherit; font: inherit; text-align: left; cursor: pointer; -webkit-tap-highlight-color: transparent; }
  .tv-sum-end { display: inline-flex; align-items: center; gap: 4px; }
  .tv-sum-end .chev { width: 24px; height: 24px; }
  .tv-sum-end .chev svg { width: 18px; height: 18px; }
  .chev.right { transform: rotate(-90deg); }
  /* the answer, replayed: the question, the word pill, then the daily board leaderboard's board and trace */
  .tv-ans-q { margin: 4px 8px 0; text-align: center; font: 800 17px/1.35 var(--font-body); color: var(--fg); }
  .tv-ans-board { display: block; width: 100%; padding: 0; margin: 0 0 24px; background: none; border: 0; cursor: pointer; -webkit-tap-highlight-color: transparent; }
  .tv-ans-board .daily-mini { margin: 0 auto; }
  /* a small phone is short too: a smaller board and a tighter slot keep the sheet on screen (320×568) */
  @media (max-width: 360px) { .tv-ans-board .daily-mini { max-width: 232px; } .modal .tv-slot { margin: 12px 0; } }
  .tv-who { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 4px; }
  .tv-who span { padding: 2px 8px; border-radius: 999px; border: 1px solid var(--tray-line); font: 700 11.5px var(--font-body); color: var(--paper); }
  .tv-who span.me { color: var(--ok-green); }
  .tv-who i { font-size: 12px; color: var(--paper-dim); }
  .tv-q.tv-q-lb { min-height: 0; margin: 0 4px 14px; font-size: 17px; }
  .tv-cap { margin: 14px 0 12px; }
  /* 3.23.2: the hint letters are a soft filled tag that never shrinks, with a real gap before the
     rest of the word. The old outlined chip got squeezed by the flex row and its edge cut through
     the next letters. Fill, not outline, so it reads on neutral, red and green alike. */
  /* 3.23.3: the bulb + letters sit dead centre in the tag, 6px of visible space on every side (measured:
     the bulb icon carries ~2.7px of its own space, the letters ~1px after, and caps sit ~1px high), and
     2px between the tag and the next letter: nothing touches, and AD·EL still reads as one word */
  .tv-pill { display: flex; align-items: center; justify-content: center; gap: 2px; }
  .tv-chip { flex-shrink: 0; display: inline-flex; align-items: center; gap: 4px; margin: -3px 0 -3px -10px; padding: 3.17px 3.5px 2.17px 3.83px; border-radius: 999px;
    background: color-mix(in srgb, currentColor 16%, transparent); letter-spacing: 2px; }
  .tv-chip svg { flex-shrink: 0; }
  .tv-chip:last-child { margin-right: -10px; padding-right: 3.5px; }
  /* text snaps to whole pixels; a transform doesn't — the last third of a pixel to dead centre */
  .tv-chip > svg, .tv-chip-t { transform: translateY(-0.33px); }
  .tv-rest-w { flex-shrink: 0; }
  .tile.tv-fade { opacity: .18; transition: opacity 900ms cubic-bezier(.2,.8,.2,1); }
  .tv-trace { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; }
  .tv-foot { margin-top: 24px; display: flex; flex-direction: column; align-items: center; gap: 8px; }
  .tv-hint { position: relative; overflow: hidden; height: 44px; padding: 0 16px 0 12px; border-radius: 12px; display: inline-flex; align-items: center; gap: 8px;
    background: none; border: 1.5px solid var(--paper); color: var(--paper); font: 700 15px var(--font-body); cursor: pointer; }
  .tv-hint > * { position: relative; }
  .tv-hint.charging { border-color: var(--tray-line); color: var(--paper-dim); cursor: default; }
  .tv-hint .tv-meter { position: absolute; left: 0; top: 0; bottom: 0; background: var(--tray-raise); transition: width 250ms linear; }
  .tv-cost { font-weight: 600; font-size: 13px; color: var(--paper-dim); }
  .tv-result { display: flex; flex-direction: column; align-items: center; gap: 4px; margin-bottom: 4px; }
  .tv-r-label { display: inline-flex; align-items: center; gap: 6px; font: 800 12px var(--font-body); letter-spacing: 2px; text-transform: uppercase; color: var(--paper-dim); }
  .tv-r-label.first { color: var(--ok-green); animation: tv-rise 500ms cubic-bezier(.2,.8,.2,1) both; }
  @keyframes tv-rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
  .tv-r-time { font: 900 34px/1.1 var(--font-display); font-variant-numeric: tabular-nums; color: var(--paper); }
  .tv-wide { width: 100%; }
  /* short phones (Safari with its bars): keep the whole board and the hint on one screen */
  @media (max-height: 760px) { .tv-q { font-size: 18px; } .tv-slot { margin: 12px 0; } .tv-acts { margin-top: 14px; } .tv-grid { max-width: 300px; margin: 0 auto; } .tv-top { margin-top: 6px; } .tv-qn { margin-top: 8px; } .tv-qs { margin-top: 8px; padding: 12px 14px; } }
  .tv-stats { display: grid; grid-template-columns: repeat(3, 1fr); border-top: 1px solid var(--tray-line); }
  .tv-stats span { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 12px 4px; }
  .tv-stats span + span { border-left: 1px solid var(--tray-line); }
  .tv-stats b { font: 800 18px var(--font-body); color: var(--paper); }
  .tv-stats i { font: 700 10.5px var(--font-body); font-style: normal; letter-spacing: 1.2px; text-transform: uppercase; color: var(--paper-dim); }
  @media (prefers-reduced-motion: reduce) { .tile.tv-fade, .tv-r-label.first, .tv-hint .tv-meter { transition: none; animation: none; } }

  .grid { display: grid; gap: 8px; position: relative; }
  /* .lit changes background AND box-shadow. Transitioning only the background
     means the 3D edge SNAPS to its new colour while the face fades — five tiles
     mid-swipe, five mismatched flickers, and the whole lot snapping back the
     instant you lift. Both properties move together or neither should. */
  /* Resting is a solid fill (var(--tile)/--tile-ink), not the transparent-outline
     look — a real user found the outline read as unfinished/placeholder rather
     than a tile. Selection needs its OWN distinct state now that resting is no
     longer "empty": dark mode's resting fill is already the light color, so
     selection uses a genuinely different accent (--select, a soft powder blue)
     to still read as a change. Light mode's resting fill is already dark, so
     selection just swaps to the light tile-ink color — no new hue needed there.
     Both get a stroke in --select-ink (the same color as the selected letter),
     since a plain fill wasn't enough on its own: dark's powder-on-cream is a
     close, easy-to-miss shift, and light's white tile can visually merge with
     the grid gaps without a defined edge. Border-width stays constant across
     states (box-sizing: border-box) so selecting a tile never shifts the grid. */
  .tile { position: relative; min-width: 0; height: 0; padding-bottom: 100%;
    box-sizing: border-box;
    background: var(--tile); border: 2.5px solid var(--tile); border-radius: 14px;
    transition: background 0.08s, border-color 0.08s, opacity 0.08s; }
  .tile { container-type: size; }
  .tile-face { position: absolute; inset: 0; display: grid; place-items: center;
    font-family: var(--font-body); font-weight: 500;
    /* Sized to the TILE, not the viewport. 52cqw = 52% of the tile's own width,
       so a letter is the same fraction of its tile on every phone (SE → Pro Max).
       The old 42vw keyed off raw window width, which the 480px .app cap and
       varying phone widths made drift — letters shrank on big phones, crowded on
       small. clamp() keeps an absolute floor/ceiling for extreme cases. The vw
       fallback covers any engine without container-query units. */
    font-size: clamp(24px, 42vw / var(--cols, 4), 46px);
    font-size: clamp(24px, 52cqw, 46px);
    line-height: 1; color: var(--tile-ink); pointer-events: none; }
  .tile.lit { background: var(--select); border-color: var(--select-stroke); }
  .tile.lit .tile-face { color: var(--select-ink); }
  @media (prefers-reduced-motion: reduce) { .tile { transition: none; } }
  /* v2 mode toggle — home screen only, keeps the icon ceiling at 2 */
  /* mode toggle is a header icon button, matched to the ? — the half-filled disc
     reads as "appearance" without borrowing the iOS system glyph. */
  .mode-toggle svg { width: 20px; height: 20px; display: block; }
  /* mode cross-fade: the whole app eases between grounds */
  body, .tile, .tile-face, .field, .primary, .secondary, .pill, .card, main, section {
    transition: background-color .5s ease, color .5s ease, border-color .5s ease, box-shadow .5s ease; }
  @media (prefers-reduced-motion: reduce) {
    body, .tile, .tile-face, .field, .primary, .secondary, .pill, .card, main, section { transition: none; }
  }
  .trace-line { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
  .trace-line { display: none; } /* v3.12: filled tiles carry the word on the GAME board — no trace needed there */
  /* v3.14.1: the name board is the exception. Its traced letters now RECEDE (they
     wear the selected page-background + stroke look, not a bright fill), so the
     connecting trace line earns its place again — it shows the letters are one
     path and hints that the whole board is swipe-to-spell. Game board stays clean. */
  .ng-trace { display: block; }
  /* .tile.valid / .tile.invalid lived here. Deleted with the reveal fix in v3.1.1:
     liveKind can only ever be neutral or dup now, so nothing could set them. The
     trace-line variants below are kept — setLast still drives those. */
  .tile.dupe { opacity: 0.72; }
  /* v2: trace stays neutral. feedback is carried by the pill, not the line. */

  .chips { display: flex; flex-wrap: wrap; gap: 6px; }
  .chip { background: var(--tray-raise); border: 1px solid var(--tray-line); color: var(--paper);
    border-radius: 8px; padding: 7px 10px; font: 500 14px var(--font-body); }
  .chip i { font-style: normal; color: var(--paper-dim); font-size: 12px; }
  .chip.uniq { border-color: var(--gold); }
  .chip.uniq i { color: var(--gold); font-weight: 700; }

  .result-card { border: 1px solid var(--tray-line); background: var(--tray-raise); border-radius: 12px; padding: 12px 16px; margin-bottom: 12px; }
  .result-card.winner { border-color: var(--fg); background: var(--raise); }
  .rc-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
  .rc-head strong { flex: 1; font-size: 15px; }
  .rank { font-family: var(--font-display); font-size: 16px; color: var(--paper-dim); width: 18px; }
  .result-card.winner .rank { color: var(--fg); }
  .len-badge { background: var(--gold); color: var(--gold-ink); border-radius: 999px; padding: 5px 11px; font: 700 13px var(--font-body); }
  .rc-words { display: flex; flex-direction: column; }
  .rc-row { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; padding: 8px 2px; border-top: 1px solid var(--tray-line); }
  .rc-row:first-child { border-top: none; }
  .rc-word { font: 600 16px var(--font-body); letter-spacing: 0.3px; }
  .rc-pts { font: 600 13px var(--font-body); color: var(--paper-dim); white-space: nowrap; display: flex; align-items: baseline; gap: 8px; }
  .rc-row.uniq .rc-pts { color: var(--gold); }
  .rc-pts em { font: 700 10.5px var(--font-body); font-style: normal; text-transform: uppercase; letter-spacing: 1px; border: 1px solid var(--gold); border-radius: 999px; padding: 2px 7px; margin-right: 2px; }

  /* winner podium */
  .winner-banner { text-align: center; padding: 20px 16px 16px; background: linear-gradient(180deg, rgba(184,204,239,0.16), rgba(184,204,239,0.04)); border: 1px solid var(--gold); border-radius: 16px; margin-bottom: 16px; }
  .trophy { font-size: 34px; line-height: 1; color: var(--gold); }
  .winner-name { font: 700 24px var(--font-body); color: var(--paper); margin: 8px 0 2px; }
  .winner-sub { font-size: 13px; color: var(--gold); text-transform: uppercase; letter-spacing: 1.5px; margin: 0; }

  /* headword (longest word) card, pinned near top */
  .headword-card { background: var(--tray-raise); border: 1px solid var(--tray-line); border-radius: 16px; padding: 16px; margin-bottom: 16px; }
  .headword { font-family: var(--font-display); font-size: 40px; color: var(--gold); margin: 4px 0 8px; letter-spacing: 1px; text-transform: lowercase; }
  .headword-def { font-size: 14.5px; line-height: 1.5; color: var(--paper); font-style: italic; margin: 0 0 10px; }
  .headword-line { font-size: 13.5px; line-height: 1.5; color: var(--paper-dim); margin: 0; }
  .headword-line b { color: var(--paper); }

  /* collapsed standings */
  .standing { border: 1px solid var(--tray-line); border-radius: 12px; margin-bottom: 8px; overflow: hidden; }
  .standing.winner { border-color: var(--fg); }
  .standing-head { width: 100%; display: flex; align-items: center; gap: 12px; background: var(--tray-raise); border: none; cursor: pointer; padding: 12px 14px; min-height: 56px; color: var(--paper); }
  .standing-head:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
  .place { flex-shrink: 0; width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font: 700 14px var(--font-body); background: var(--fg-soft); color: var(--ground); }
  .place-1 { background: var(--gold); color: var(--gold-ink); }
  .standing-name { flex: 1; text-align: left; font: 600 16px var(--font-body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .standing-count { font-size: 12.5px; color: var(--paper-dim); flex-shrink: 0; }
  .standing-pts { font: 700 18px var(--font-body); color: var(--paper); flex-shrink: 0; min-width: 32px; text-align: right; }
  .standing-time { display: inline-flex; align-items: center; gap: 5px; font: 700 16px var(--font-body); color: var(--paper); flex-shrink: 0; min-width: 44px; justify-content: flex-end; }
  .standing-time .clock-ic { color: var(--paper-dim); flex-shrink: 0; }
  .standing .chev { margin-right: -8px; margin-left: -8px; }  /* 12px flex gap - 8px = 4px score->chevron */
  .standing .rc-words { padding: 4px 14px 12px; }

  /* ---- daily board ---- */
  .daily-card { display: flex; justify-content: space-between; align-items: center; gap: 12px; width: 100%; text-align: left; background: var(--tray-raise); border: 1px solid var(--tray-line); border-radius: 14px; padding: 14px 16px; cursor: pointer; color: var(--paper); }
  .compete-title { margin: 0 0 12px; }
  /* Reads as a standing, not a board you play — no chips, no Play. */
  /* Guest note: a sibling of the standing card, never a child — inside, it fights
     the .open word-list expansion and the .standing-head padding rules. */
  /* The code is the whole point of the row — same monospace treatment as the
     code field so it reads as something you type, not something you read. */
  /* ── Bottom sheet ─────────────────────────────────────────────────────────
     The app's first modal. Rises from the bottom because that's where thumbs
     already are. Scrim dismisses; the ✕ is for people who don't know that.
     position:fixed + inset:0 so it ignores page scroll behind it. */
  /* --kb is the keyboard's height when one covers the page (set from visualViewport): the sheet
     sits on top of it instead of behind it, and gives up the home indicator's room while it's up. */
  .scrim { position: fixed; inset: 0; z-index: 60; background: rgba(6,16,32,0.72);
    overscroll-behavior: contain; padding-bottom: var(--kb, 0px);
    display: flex; align-items: flex-end; justify-content: center;
    animation: scrim-in 160ms ease-out; }
  @keyframes scrim-in { from { opacity: 0 } to { opacity: 1 } }
  .modal { width: 100%; max-width: 520px; background: var(--tray);
    border: 1px solid var(--tray-line); border-bottom: none;
    border-radius: 18px 18px 0 0; box-shadow: 0 -8px 40px var(--shadow);
    max-height: calc(88vh - var(--kb, 0px)); display: flex; flex-direction: column;
    animation: sheet-up 220ms cubic-bezier(0.22, 1, 0.36, 1); }
  @keyframes sheet-up { from { transform: translateY(100%) } to { transform: translateY(0) } }
  @media (prefers-reduced-motion: reduce) { .scrim, .modal { animation: none; } }
  /* Every gap in the head is a multiple of 4: 20 above the title, 4 to the sub,
     16 down to the rule, 12 under it. 12/8 read as cramped against a 44px close. */
  .modal-head { display: flex; align-items: flex-start; justify-content: space-between;
    gap: 12px; padding: 24px 16px 20px; border-bottom: 1px solid var(--tray-line); }
  .modal-title { font: 800 17px var(--font-body); color: var(--paper); margin: 0; letter-spacing: 0.2px; }
  .modal-sub { font: 500 12.5px var(--font-body); color: var(--paper-dim); margin: 4px 0 0; line-height: 1.45; }
  .modal-x { background: transparent; border: 1px solid var(--tray-line); border-radius: 12px;
    width: 44px; height: 44px; flex: 0 0 auto; color: var(--paper-dim);
    display: inline-flex; align-items: center; justify-content: center; }
  .modal-x svg { display: block; }
  .modal-body { padding: 16px 16px 4px; overflow-y: auto; -webkit-overflow-scrolling: touch;
    flex: 1 1 auto; }
  /* A sheet with no foot ends on its body: it keeps clear of the home indicator too (3.25.4: in the
     home-screen app, "I have an older, longer code" sat under it). No inset, no change. */
  .modal-body:last-child { padding-bottom: calc(4px + max(0px, env(safe-area-inset-bottom, 0px) - var(--kb, 0px))); }
  /* 8px above the home indicator, which already reserves its own room. The max()
     is the floor for anything with no inset to sit on: desktop, an SE, Android with
     hardware buttons. Not --bar-pad, because .actionbar shares that and wants 16. */
  .modal-foot { padding: var(--bar-pad) 16px max(16px, calc(8px + env(safe-area-inset-bottom, 0px) - var(--kb, 0px)));
    border-top: 1px solid var(--tray-line); flex: 0 0 auto; }
  /* Inside a bar the bar owns the spacing. Without this the button's own margin
     stacks on the bar's padding and the top gap can never equal the bottom one. */
  .actionbar .primary.big, .modal-foot .primary.big { margin-top: 0; }
  /* Smaller than the game board — this sits above the name field on the screen
     everyone opens, and a full-size board would eat it. */
  /* Periwinkle, not red: a fire shouting on every row is worse than no fire, and
     this colour already belongs to the scoreboard. */
  .streak-txt { color: var(--gold); }
  .name-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 6px;
    position: relative; margin-bottom: 14px; cursor: pointer; }
  /* CONSISTENT with the game board: the NAME is the "traced" path, so it wears the
     exact same SELECTED-tile treatment as gameplay (page-background fill via --select
     + --select-stroke), while the filler wears the RESTING-tile treatment (bright
     solid --tile). Same mapping as the real board — traced letters recede with a
     stroke, untouched letters stay bright — so the home-screen name board reads as a
     mini board with your name pre-traced, and selecting feels identical everywhere. */
  .name-grid .tile { border-radius: 9px; background: var(--tile);
    border: 1.5px solid var(--tile); box-shadow: none; }
  .name-grid .tile-face { font-size: 20px; color: var(--tile-ink); }
  .name-grid .tile.lit { background: var(--select); border-color: var(--select-stroke); box-shadow: none; }
  .name-grid .tile.lit .tile-face { color: var(--select-ink); }
  /* .secondary, wearing the error colours. Every measurement below is copied from
     .secondary on purpose — a destructive button is not a smaller button. */
  .danger {
    flex: 1; background: var(--error-surface); border: 1.5px solid var(--error-border); color: var(--error-text);
    border-radius: 12px; padding: 13px 16px; font: 600 15px var(--font-body); cursor: pointer; min-height: 52px;
  }
  .standing.removing { border-color: var(--error-border); }
  /* ── Monthly podium + crown (3.20.0) ─────────────────────────────────────
     The crown only ever means "won last month". It hangs off the top-left of
     that player's rank circle, tilted -14deg, at 75% of the circle's width.
     Its cut layer is a halo in the surface colour behind it. Podium columns
     are the three tile states. Confetti pieces are board parts. All tokens. */
  .place { position: relative; }
  .crown-perch { position: absolute; left: -8px; top: -10px; width: 21px; line-height: 0;
    transform: rotate(-14deg); transform-origin: 50% 100%; pointer-events: none; }
  .crown { display: block; overflow: visible; }
  .crown-cut { fill: var(--crown-cut, var(--raise-solid)); stroke: var(--crown-cut, var(--raise-solid));
    stroke-width: 5.6; stroke-linejoin: round; }
  .crown-ink { fill: var(--fg); stroke: var(--fg); stroke-width: 1.2; stroke-linejoin: round; }
  .season-banner { --crown-cut: var(--ground); }
  .pod-hero { padding: 36px 0 4px; }
  .podium { display: flex; justify-content: center; align-items: flex-end; gap: 10px; height: 150px; }
  .pcol-wrap { position: relative; }
  .pcol { width: 72px; border-radius: 12px; box-sizing: border-box; display: flex; justify-content: center;
    overflow: hidden; font-family: var(--font-display); transform-origin: 50% 100%; }
  .pcol span { padding-top: 10px; font-size: 24px; line-height: 1; }
  .pcol.p1 { background: var(--tile); color: var(--tile-ink); }
  .pcol.p2 { background: var(--select); border: 2px solid var(--select-stroke); color: var(--select-ink); }
  .pcol.p3 { background: var(--lit); border: 1.5px solid var(--select-stroke); color: var(--lit-ink); }
  .pod-crown { position: absolute; left: -14px; top: -28px; width: 44px; line-height: 0; --crown-cut: var(--ground);
    transform: rotate(-14deg); transform-origin: 50% 100%; pointer-events: none; }
  .pod-crown-drop { display: block; }
  .pplates { display: flex; justify-content: center; gap: 10px; margin-top: 10px; }
  .pplate { width: 72px; display: flex; flex-direction: column; align-items: center; gap: 2px; min-width: 0; }
  .pplate .pn { max-width: 100%; font: 600 14px var(--font-body); color: var(--paper); white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis; }
  .pplate .pp { font: 600 12px var(--font-body); color: var(--paper-dim); white-space: nowrap; }
  .pplate.me .pp { color: var(--paper); }
  .pod-list { margin-top: 20px; }
  .pod-list .standing-head { cursor: default; }
  .standing.pod-you { border-color: var(--fg); }
  .pod-gap { display: flex; justify-content: center; gap: 5px; padding: 0 0 8px; }
  .pod-gap i { display: block; width: 4px; height: 4px; border-radius: 50%; background: var(--fg-faint); }
  .pod-note { text-align: center; margin: 4px 0 8px; }
  .pod-list:last-child, .pod-note { margin-bottom: 12px; }
  /* Home card for the rest of the 1st: a tiny podium, the crown on the middle. */
  .podium-card { margin: 0 0 10px; }
  .podium-card .daily-left { flex: 1; min-width: 0; }
  .pc-mini { display: flex; align-items: flex-end; gap: 3px; height: 40px; padding-top: 8px; flex-shrink: 0; }
  .pcm { display: block; width: 16px; border-radius: 4px; box-sizing: border-box; }
  .pcm-1 { height: 32px; background: var(--tile); }
  .pcm-2 { height: 22px; background: var(--select); border: 1.5px solid var(--select-stroke); }
  .pcm-3 { height: 14px; background: var(--lit); border: 1px solid var(--select-stroke); }
  .pcm-wrap { position: relative; }
  .pcm-crown { left: -6px; top: -13px; width: 18px; --crown-cut: var(--ground); }
  .pc-sub { font: 600 12px var(--font-body); color: var(--paper-dim); margin-top: 3px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* Confetti: a fixed layer above the sheet so pieces can fly into the scrim. */
  .cf-layer { position: fixed; inset: 0; z-index: 61; pointer-events: none; overflow: hidden; }
  .cf { position: absolute; left: 0; top: 0; will-change: transform, opacity; opacity: 0; }
  .cf-sw { display: block; }
  .cf-in { display: flex; align-items: center; justify-content: center; margin: -7px 0 0 -7px; }
  /* 3.23.3: confetti tiles are board tiles, scaled down — same ~22% corner, same 500-weight letter at
     ~52% of the tile, same tokens: plain = --tile, ring = the selected tile (--select + --select-stroke). */
  .cf-tile { width: 12px; height: 12px; border-radius: 22%; background: var(--tile); }
  .cf-ring { width: 12px; height: 12px; border-radius: 22%; background: var(--select); border: 1.5px solid var(--select-stroke); box-sizing: border-box; }
  .cf-let { width: 20px; height: 20px; border-radius: 22%; background: var(--tile); color: var(--tile-ink);
    font: 500 10.5px/1 var(--font-body); }
  .cf-crown { width: 16px; height: 14px; }
  .cf-rib { width: 26px; height: 68px; margin: -34px 0 0 -13px; }
  .cf-rib path { fill: none; stroke: var(--trace); stroke-opacity: .62; stroke-width: 3.6; stroke-linecap: round;
    stroke-dasharray: 130; }
  .cf-s1 { animation: cf-sway 3.4s ease-in-out infinite alternate; }
  .cf-s2 { animation: cf-sway 4.2s ease-in-out infinite alternate-reverse; }
  @keyframes cf-sway { from { transform: translateX(-22px) rotate(-7deg) } to { transform: translateX(22px) rotate(7deg) } }
  /* The flip catches light: brighter facing you, dimmer edge-on. Tile colours only. */
  .cf-g1 { animation: cf-glint-x 2.8s linear infinite; }
  .cf-g2 { animation: cf-glint-y 3.4s linear infinite; }
  .cf-g3 { animation: cf-glint-z 2.4s linear infinite; }
  @keyframes cf-glint-x { 0% { transform: rotateX(0deg); filter: brightness(1) } 22% { filter: brightness(1.18) }
    50% { transform: rotateX(180deg); filter: brightness(.5) } 78% { filter: brightness(1.18) }
    100% { transform: rotateX(360deg); filter: brightness(1) } }
  @keyframes cf-glint-y { 0% { transform: rotateY(0deg); filter: brightness(1) } 22% { filter: brightness(1.18) }
    50% { transform: rotateY(180deg); filter: brightness(.5) } 78% { filter: brightness(1.18) }
    100% { transform: rotateY(360deg); filter: brightness(1) } }
  @keyframes cf-glint-z { 0% { transform: rotate3d(1,1,0,0deg); filter: brightness(1) }
    50% { transform: rotate3d(1,1,0,180deg); filter: brightness(.55) } 100% { transform: rotate3d(1,1,0,360deg); filter: brightness(1) } }
  @media (prefers-reduced-motion: reduce) { .cf-layer { display: none; } .cf-sw, .cf-in { animation: none; } }
  .season-banner { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left; cursor: pointer;
    background: rgba(184,204,239,0.10); border: 1px solid rgba(184,204,239,0.28); border-radius: 12px; padding: 12px 14px; margin: 0 0 10px; color: var(--paper); }
  .sb-mid { display: flex; flex-direction: column; gap: 3px; flex: 1; min-width: 0; }
  .sb-title { font: 700 15px var(--font-body); color: var(--paper); }
  .sb-sub { font: 600 12px var(--font-body); color: var(--paper-dim); }
  .sb-pts { color: var(--paper); }
  .place.place-none { background: rgba(184,204,239,0.18); color: var(--paper-dim); }
  .sb-gap { color: var(--paper-dim); }
  /* 3.25.0 · a month-board row, opened: each game with its record (your row adds the game's points),
     your last days with points as bars in three inks of one colour, each part's points on it, then
     the month as a mark a day. */
  .mg i { display: inline-flex; align-items: center; gap: 5px; }
  .stat-list { padding: 0 0 4px; }
  .stat-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 16px; border-top: 1px solid var(--rule); }
  .stat-label { font: 600 12.5px var(--font-body); color: var(--fg-soft); }
  .stat-val { display: inline-flex; align-items: center; gap: 5px; font: 700 13.5px var(--font-body); color: var(--fg); flex-shrink: 0; }
  .stat-val .unlock-ic { color: var(--earned); }      /* the lock you opened stays green wherever it shows: it was earned */
  .ps-ink { display: inline-block; width: 8px; height: 8px; border-radius: 2px; flex-shrink: 0; }
  /* 3.25.1 · How points work: a text button under the leaderboard's title, and its sheet */
  .hp-open { margin: -6px 0 10px; }
  .hp-open .text-btn { width: auto; margin: 0 auto; color: var(--fg); }
  .hp-open .text-btn span { text-decoration: underline; text-underline-offset: 3px; }
  .hp-row { padding: 12px 0; border-top: 1px solid var(--rule); }
  .hp-row:first-child { border-top: 0; padding-top: 4px; }
  .hp-g { display: flex; align-items: center; gap: 7px; margin: 0; font: 700 10.5px var(--font-body); letter-spacing: 1.2px; text-transform: uppercase; color: var(--fg-soft); }
  /* 3.25.2 · each row plays its game's scoring: a small board (the help deck's tiles at 30px), the
     word as a pill or Longest word's slots, and the points it earns */
  .hp-show { display: flex; align-items: center; gap: 16px; }
  .hp-show .hs-board { --hs-tile: 30px; --hs-gap: 4px; margin: 0; }
  /* a row presses as one piece, like a leaderboard card */
  .hp-show.can-play { cursor: pointer; transition: transform 0.09s ease, filter 0.09s ease; }
  .hp-show.can-play:active { transform: scale(0.99); filter: brightness(0.97); }
  @media (prefers-reduced-motion: reduce) { .hp-show.can-play:active { transform: none; filter: brightness(0.9); } }
  .hp-board-btn { position: relative; display: block; flex: none; padding: 0; margin: 0; border: 0; background: none; color: inherit; border-radius: 8px; cursor: pointer; }
  .hp-board-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
  .hp-show .hs-tile { border-radius: 8px; }
  .hp-show .hs-face { font-size: 14px; font-weight: 700; }
  .hp-play { display: flex; flex-direction: column; align-items: flex-start; gap: 5px; min-width: 0; }
  .hp-show .hs-pill { margin: 0; height: 32px; padding: 5px 14px; font-size: 15px; }
  .hp-rule { margin: 0; font: 600 13px/1.35 var(--font-body); color: var(--fg); }
  .hp-demo { display: flex; flex-direction: column; align-items: flex-start; gap: 5px; margin-top: 2px; }
  .hp-line { display: flex; align-items: center; gap: 10px; }
  .hp-q { margin: 0; font: 600 13px var(--font-body); color: var(--fg-soft); }
  .hp-slots { --s: 17px; align-items: center; height: 24px; }
  .hp-slots .cc-t.lock.open { color: var(--earned); }
  .hp-val { display: flex; gap: 10px; margin: 0; font: 800 17px var(--font-body); font-variant-numeric: tabular-nums; color: var(--fg-soft); }
  .hp-val .ok { color: var(--earned); }
  @media (max-width: 359px) { .hp-show .hs-board { --hs-tile: 26px; } .hp-show { gap: 14px; } .hp-play, .hp-demo { gap: 4px; } .hp-demo { margin-top: 0; } }
  .hp-foot { margin: 6px 0 0; padding-top: 12px; border-top: 1px solid var(--rule); font: 600 12.5px/1.45 var(--font-body); color: var(--fg-soft); }
  /* 3.25.3: the third part is an outline, not a third shade: --fg-soft and --fg-faint were too close to
     tell apart, and the Trivia numbers on --fg-faint were 4.0:1 in dark mode */
  .ps-ink1 { background: var(--fg); } .ps-ink2 { background: var(--fg-soft); } .ps-ink3 { background: transparent; box-shadow: inset 0 0 0 1.5px var(--fg); }
  .ps-days { display: grid; padding: 6px 0; border-top: 1px solid var(--rule); }
  .ps-row { position: relative; display: grid; grid-template-columns: 52px minmax(0, 1fr) 34px; align-items: center; gap: 10px; min-height: 32px;
    padding: 0 16px; color: var(--fg-soft); font: 600 12.5px var(--font-body); font-variant-numeric: tabular-nums; }
  .ps-row b { text-align: right; color: var(--fg); font-weight: 700; }
  .ps-bar { display: flex; gap: 2px; height: 18px; min-width: max-content; }
  .ps-bar i { display: grid; place-items: center; flex-basis: 0; min-width: calc(var(--d, 1) * 1ch + 8px); overflow: hidden; border-radius: 4px;
    font: 700 11px var(--font-body); font-style: normal; color: var(--ground); }
  .ps-bar i.ps-ink3 { color: var(--fg); }
  .ms { padding: 12px 16px 14px; border-top: 1px solid var(--rule); }
  .ms-h { display: flex; justify-content: space-between; gap: 10px; margin: 0 0 8px; font: 600 12.5px var(--font-body); color: var(--fg-soft); }
  .ms-h b { color: var(--fg); font-weight: 700; }
  .ms-s { display: grid; gap: 2px; }
  .ms-s i { height: 14px; border-radius: 2px; background: var(--rule); }
  .ms-s i.on { background: var(--fg-soft); }
  .ms-s i.today { background: none; box-shadow: inset 0 0 0 1.5px var(--fg-soft); }
  .ms-s i.later { opacity: .5; }
  .daily-card + .daily-card { margin-top: 10px; }
  .daily-card.play { border-color: rgba(184,204,239,.55); }
  .daily-card.done { border-color: var(--earned-card-border); background: var(--earned-card-bg); }
  .daily-left { display: flex; flex-direction: column; }
  .daily-eyebrow { font: 700 11px var(--font-body); letter-spacing: 1.5px; text-transform: uppercase; color: var(--gold); margin: 0 0 3px; }
  .daily-card.done .daily-eyebrow { color: var(--ok-green); }
  .daily-title { font: 800 18px var(--font-body); color: var(--paper); margin: 0; }
  .daily-sub { font: 500 13px var(--font-body); color: var(--paper-dim); margin: 4px 0 0; }
  .daily-chips { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 10px; }
  .daily-chip { background: rgba(255,255,255,0.06); border: 1px solid var(--tray-line); color: var(--paper); border-radius: 12px; padding: 8px 14px; font: 600 13px var(--font-body); line-height: 1; }
  /* status chips read as news, not config — tinted so they separate from size/time */
  .daily-chip.locked, .daily-chip.cracked { display: inline-flex; align-items: center; gap: 5px; }
  .daily-chip.locked { background: rgba(184,204,239,0.16); border-color: rgba(184,204,239,0.5); color: var(--gold); }
  .daily-chip.cracked { background: var(--earned-wash); border-color: var(--earned-card-border); color: var(--ok-green); }
  /* the one card in the app that turns green — cracking it should feel like something */
  .daily-card.unlocked { background: rgba(123,224,163,0.10); border-color: rgba(123,224,163,0.42); }
  .daily-card.unlocked .daily-eyebrow { color: var(--ok-green); display: inline-flex; align-items: center; gap: 5px; }
  .daily-cta { font: 700 14px var(--font-body); color: var(--gold); white-space: nowrap; flex-shrink: 0; }
  .daily-card.done .daily-cta { color: var(--ok-green); }
  .daily-mini { display: grid; gap: 8px; max-width: 272px; margin: 0 auto 12px; position: relative; }
  /* Post-game reveal board — matches the game board and name board exactly now:
     resting tiles are a solid --tile fill (not the old transparent/outline look a
     user flagged), and the revealed longest-word path wears the SELECTED treatment
     (page-background --select fill + --select-stroke), same as tracing in-game. The
     --trace line over it reads as the path, consistent with the name board. */
  .daily-mini-tile { position: relative; z-index: 1; aspect-ratio: 1; box-sizing: border-box; background: var(--tile); border: 1.5px solid var(--tile); color: var(--tile-ink); border-radius: 12px; box-shadow: none; display: flex; align-items: center; justify-content: center; font: 500 22px var(--font-body); }
  .daily-mini-tile.lit { background: var(--select); border-color: var(--select-stroke); color: var(--select-ink); box-shadow: none; }
  /* Every traced word, the longest-on-board included, uses the plain pressed (.lit)
     state above. 3.21.1 removed the green stroke: green means earned, and showing a
     word on the board earns nothing. */
  .daily-trace { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 2; }
  /* daily leaderboard + end-screen word list: tight light head, count+tip header, capped scroll */
  .daily-standing .standing-head { min-height: 0; padding: 12px 14px; }
  .daily-standing.open .standing-head { padding-bottom: 6px; }
  .daily-standing .rc-words { padding: 0 0 4px; }
  .daily-standing .rc-headrow { display: flex; justify-content: space-between; align-items: baseline; padding: 8px 16px; }
  .daily-standing .rc-count { font: 800 11px var(--font-body); color: var(--paper-dim); letter-spacing: 1px; text-transform: uppercase; }
  .daily-standing .rc-tip { font: 500 12px var(--font-body); color: var(--paper-dim); opacity: 0.8; }
  .daily-standing .rc-scroll { max-height: 172px; overflow-y: auto; -webkit-overflow-scrolling: touch; }
  .daily-standing .rc-scroll::-webkit-scrollbar { width: 8px; }
  .daily-standing .rc-scroll::-webkit-scrollbar-thumb { background: rgba(184,204,239,0.25); border-radius: 8px; }
  .daily-standing .rc-row { width: 100%; background: none; border: none; border-top: 1px solid var(--tray-line); border-radius: 0; cursor: pointer; text-align: left; padding: 12px 16px; color: inherit; }
  .daily-standing .rc-row:first-child { border-top: 1px solid var(--tray-line); }
  .daily-standing .rc-row.active { background: rgba(184,204,239,0.16); }
  /* longest-on-board card (daily leaderboard + game/practice results) */
  .lob-card { display: flex; align-items: center; justify-content: center; gap: 10px; background: rgba(255,255,255,0.045); border: 1px solid var(--tray-line); border-radius: 12px; padding: 8px 16px; margin: 0 0 16px; }
  .lob-label { font: 800 11px var(--font-body); letter-spacing: 1px; text-transform: uppercase; color: var(--paper-dim); }
  .lw-pill.gold { background: var(--gold); border: 1px solid var(--gold); color: var(--tray); border-radius: 999px; padding: 3px 12px; font: 700 13.5px var(--font-body); }
  .lw-pill.tappable { cursor: pointer; -webkit-tap-highlight-color: transparent; transition: transform 0.1s, box-shadow 0.15s; }
  .lw-pill.tappable:active { transform: scale(0.96); }
  /* When active (its word is showing on the board), the pill flips exactly like a
     pressed tile: --select fill, --select-stroke outline, --select-ink text. Not
     green (3.21.1): green means earned, and tapping a word to see it earns nothing. */
  .lw-pill.gold.active, .lw-pill.neutral.active {
    background: var(--select); border-color: var(--select-stroke); color: var(--select-ink); }
  /* solo (practice) results score header */
  .solo-head { text-align: center; margin: 2px 0 16px; }
  .solo-eyb { color: var(--paper-dim); font: 800 12px var(--font-body); letter-spacing: 2px; text-transform: uppercase; margin: 0 0 6px; }
  .solo-score { color: var(--paper); font: 800 26px var(--font-display); margin: 0 0 10px; }
  .solo-lw { display: inline-flex; align-items: center; gap: 8px; }
  .solo-lw .lw-label { color: var(--paper-dim); font: 700 11px var(--font-body); letter-spacing: 1px; text-transform: uppercase; }
  /* stacked name (name over the green longest-word pill), used on the daily leaderboard */
  .standing-name.stacked { display: flex; flex-direction: column; gap: 5px; white-space: normal; overflow: visible; }
  .standing-name.stacked .sn-name { font: 600 16px var(--font-body); color: var(--paper); line-height: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .lw-line { display: flex; align-items: center; gap: 7px; }
  .lw-label { font: 500 11px var(--font-body); text-transform: uppercase; letter-spacing: 1px; color: var(--paper-dim); }
  .lw-pill { display: inline-block; font: 700 12.5px var(--font-body); text-transform: lowercase; letter-spacing: .3px; border-radius: 999px; padding: 2px 11px; line-height: 1; }
  .lw-pill.green { background: var(--ok-green); border: 1px solid var(--ok-green); color: var(--ok-ink); }
  /* Green is earned, not decorative: it means "longest word of anyone", and only on
     the longest-word board. Everywhere else a longest word is just information, so it
     takes the neutral pill. (The old .blue was hardcoded periwinkle from the v1 palette.) */
  .lw-pill.neutral, .lw-pill.blue { background: var(--raise); border: 1px solid var(--rule-strong); color: var(--fg); }
  .lw-pill.locked { display: inline-flex; align-items: center; gap: 5px; }
  .place.place-unlock { background: var(--ok-green); color: var(--ok-ink); }
  /* Whoever opened the board gets a green stroke — same meaning the green pill
     already carries. Selecting a card overrides it in white, matching the trace. */
  .standing.unlocker { border-color: var(--ok-green); }
  .standing.sel { border-color: var(--paper); }
  .sn-name .unlock-ic { color: var(--ok-green); margin-left: 5px; vertical-align: -1px; }
  /* the you-row is a regular card; it is identified by "(you)" in the name, not a wash. */
  .rc-pts em { font: 700 10.5px var(--font-body); font-style: normal; text-transform: uppercase; letter-spacing: 1px; border: 1px solid var(--gold); border-radius: 999px; padding: 2px 7px; }

  .long-word-line { font-size: 15px; line-height: 1.6; margin: 0; }
  .long-word { font-family: var(--font-die); font-weight: 700; font-size: 26px; letter-spacing: 1px; color: var(--paper); display: block; margin-bottom: 4px; text-shadow: 0 2px 0 var(--die-edge), 0 3px 8px var(--shadow); }

  .verdict { position: relative; margin-top: 16px; padding: 16px; border-radius: 16px; display: flex; flex-direction: column; gap: 4px; }
  .verdict.yes { background: rgba(184,204,239,0.12); border: 1px solid var(--valid); }
  .verdict.no { background: var(--error-surface); border: 1px solid var(--error-border); }
  .v-pts { position: absolute; top: 12px; right: 12px; background: var(--gold); color: var(--gold-ink); border-radius: 999px; padding: 5px 11px; font: 700 14px var(--font-body); }
  .v-word { font-family: var(--font-die); font-weight: 700; font-size: 30px; letter-spacing: 1px; }
  .verdict.yes .v-word { color: var(--valid); }
  .verdict.no .v-word, .verdict.no .v-line { color: var(--error-text); }
  .v-line { font-weight: 600; font-size: 15px; }
  .v-def { color: var(--paper); font-size: 14.5px; line-height: 1.5; font-style: italic; }

  .log { margin-top: 24px; }
  .log-head { display: flex; align-items: baseline; justify-content: space-between; }
  .log-head h3 { font: 700 11px var(--font-body); text-transform: uppercase; letter-spacing: 1.5px; color: var(--paper-dim); margin: 0; }
  .log-row { border-top: 1px solid var(--tray-line); padding: 11px 0; }
  .log-row:first-of-type { border-top: none; }
  .log-top { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
  .log-word { background: none; border: none; padding: 0; cursor: pointer; font-family: var(--font-die); font-weight: 700; font-size: 18px; color: var(--paper); }
  .log-word.struck { color: var(--paper-dim); text-decoration: line-through; }
  .log-when { color: var(--paper-dim); font-size: 12px; white-space: nowrap; }
  .log-def { color: var(--paper-dim); font-size: 13.5px; line-height: 1.5; margin: 3px 0 0; }

  /* ================= HELP SHEET ================= */
  /* The help sheet IS the app's .modal: docked to the bottom, full width, top
     corners only. .hs-modal adds nothing but padding and the home-indicator clearance. */
  .update-bar { display: block; width: 100%; margin: 0 0 14px; padding: 11px 14px;
    background: var(--tray-raise); border: 1px solid var(--gold); border-radius: 12px;
    color: var(--gold); font: 600 13.5px var(--font-body); cursor: pointer; text-align: center;
    min-height: 44px; }

  /* The help sheet swipes between measured cards, so it keeps the fixed floor the
     other sheets just dropped. Without it the deck resizes under your thumb. */
  /* Bottom is .modal-foot's expression verbatim. The help sheet owns its own padding
     rather than using a foot bar, and it had drifted 20px higher off the home bar
     than every other sheet in the app. */
  .hs-modal {
    padding: 24px 18px max(16px, env(safe-area-inset-bottom, 0px));
    overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; }

  /* Narrow phones: a little relief so nothing crowds the edges. */
  @media (max-width: 360px) {
    .update-bar { display: block; width: 100%; margin: 0 0 14px; padding: 11px 14px;
    background: var(--tray-raise); border: 1px solid var(--gold); border-radius: 12px;
    color: var(--gold); font: 600 13.5px var(--font-body); cursor: pointer; text-align: center;
    min-height: 44px; }

  .hs-modal { padding: 20px 16px max(16px, env(safe-area-inset-bottom, 0px)); }
    .hs-modal .hs-board { --hs-tile: 44px; }
    .hs-modal .hs-face { font-size: 19px; }
  }
  /* NOTE: no max-height media query here on purpose. Mobile Safari changes the
     viewport height whenever the URL bar hides or shows, so any height-based query
     flips on and off while you scroll and visibly resizes the board. .modal already
     caps at 88vh and scrolls, which handles short screens without the flicker. */

  .hs-head { display: flex; align-items: center; justify-content: space-between; flex: 0 0 auto; }
  .hs-title { font: 800 17px var(--font-body); color: var(--paper); letter-spacing: 0.2px; margin: 0; }
  .hs-track { overflow: hidden; margin-top: 8px; flex: 0 0 auto; }
  .hs-cards { display: flex; align-items: stretch; transition: transform .26s cubic-bezier(.2,.8,.2,1); }
  .hs-card { min-width: 100%; text-align: center; }
  .hs-card-t { font-family: var(--font-display); font-weight: 800; font-size: 16px; text-transform: uppercase; letter-spacing: 1px; color: var(--paper); margin: 0 0 12px; }
  .hs-hud { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
  /* Copied off .pill / .timer-pill / .score-pill, not approximated. The HUD in help
     has to be the HUD you meet in a round or the demo is teaching a different app. */
  .hs-timer { background: var(--tray-raise); border: 1px solid var(--tray-line); border-radius: 999px;
    padding: 8px 16px; font: 800 16px var(--font-body); color: var(--paper); letter-spacing: 1px; font-variant-numeric: tabular-nums; }
  .hs-pts { background: var(--gold); color: var(--gold-ink); border-radius: 999px; padding: 8px 16px; font: 800 16px var(--font-body); font-variant-numeric: tabular-nums; }
  .hs-pts.plain { background: var(--tray-raise); color: var(--paper); border: 1px solid var(--tray-line); }
  .hs-caption { text-align: center; font: 700 10px var(--font-body); letter-spacing: 2px; text-transform: uppercase;
    color: var(--paper-dim); margin: 0 0 12px; min-height: 12px; white-space: nowrap; overflow: hidden; }
  .hs-caption.ok { color: var(--ok-green); }
  .hs-caption.bad { color: var(--error-text); }
  .hs-pill { display: block; width: fit-content; max-width: 100%; margin: 0 auto 12px; border-radius: 999px; line-height: 22px;
    padding: 9px 18px; height: 40px; white-space: nowrap; overflow: hidden; font: 800 17px var(--font-body); letter-spacing: 2px; text-align: center;
    background: var(--tray-raise); border: 1px solid var(--tray-line); color: var(--paper-dim);
    transition: background .12s, border-color .12s, color .12s; line-height: 20px; }
  /* Mid-trace the real .word-pill is .neutral and reads --paper. Help was leaving it
     dim, so the letters you were spelling looked greyed out. */
  .hs-pill.neutral { color: var(--paper); }
  .hs-pill.ok { background: var(--ok-green); border-color: var(--ok-green); color: var(--ok-ink); }
  .hs-pill.bad { background: var(--bad-fill); border-color: var(--bad-line); color: var(--bad-ink); }
  .hs-pill.empty { visibility: hidden; }
  /* Geometry is declared, not computed: fixed pixel columns and an explicit tile
     height. Using 1fr + aspect-ratio makes the browser divide free space and then
     derive a height, and those two roundings can disagree between frames on a
     high-DPR screen, which shifts the centred letter by a subpixel. */
  .hs-board { --hs-tile: 52px; --hs-gap: 8px; position: relative; display: grid;
    grid-template-columns: repeat(var(--hs-cols, 3), var(--hs-tile));
    gap: var(--hs-gap); width: max-content; margin: 0 auto; flex: none; }
  .hs-board.mini { --hs-tile: 52px; }
  /* Help board matches the live board's figure/ground: the MEANINGFUL letters
     (the word being spelled, .lit) pop as the bright --tile fill — like a traced
     word on the real board — and the filler recedes into the --select recessed
     fill + --select-stroke outline. Same as the NameGrid. No trace line in help. */
  /* Identical to the canonical tile in the design system:
     resting = --tile fill + --tile-ink letter; selected/lit = --select recessed
     fill + --select-stroke border + --select-ink letter. Same as the real .tile. */
  .hs-tile { position: relative; width: var(--hs-tile); height: var(--hs-tile); box-sizing: border-box; background: var(--tile); border: 1.5px solid var(--tile); border-radius: 14px;
    box-shadow: none; display: grid; place-items: center;
    transition: background 0.08s, border-color 0.08s, opacity 0.08s; }
  .hs-tile.lit { background: var(--select); border-color: var(--select-stroke); box-shadow: none; }
  .hs-face { position: absolute; inset: 0; display: grid; place-items: center;
    font-family: var(--font-body); font-weight: 500; font-size: 22px; line-height: 1;
    color: var(--tile-ink); pointer-events: none; }
  .hs-tile.lit .hs-face { color: var(--select-ink); }
  .hs-trace { position: absolute; inset: 0; pointer-events: none; display: none; } /* v3.12: filled tiles carry the word, matching the game board */
  .hs-foot { display: flex; align-items: baseline; justify-content: space-between; margin-top: 14px; padding-bottom: 6px; border-bottom: 1px solid var(--tray-line); }
  .hs-foot-lbl { font: 700 9px var(--font-body); letter-spacing: 2px; text-transform: uppercase; color: var(--paper-dim); }
  .hs-words { height: 39px; overflow: hidden; margin-top: 10px; border-top: 1px solid var(--tray-line); }
  .hs-wrow { display: flex; justify-content: space-between; align-items: baseline; padding: 8px 2px; border-bottom: 1px solid var(--tray-line); }
  .hs-w { font: 600 15px var(--font-body); color: var(--paper); }
  .hs-wrow.latest .hs-w { color: var(--ok-green); }
  .hs-wp { font: 600 12.5px var(--font-body); color: var(--paper-dim); white-space: nowrap; }
  .hs-xfade { position: relative; }
  .hs-xlayer { transition: opacity .5s ease; }
  .hs-xlayer.lb { position: absolute; inset: 0; }
  .hs-lb { margin-top: 2px; }
  .hs-lb.tall { margin-top: 6px; }
  .hs-lbrow { display: flex; align-items: center; gap: 10px; padding: 8px 2px; border-top: 1px solid var(--tray-line); }
  .hs-lbrow:first-child { border-top: none; }
  .hs-lbname { flex: 1; text-align: left; font: 600 15px var(--font-body); color: var(--paper); }
  .hs-lbname.me { color: var(--gold); }
  .hs-lbpts { font: 700 16px var(--font-body); color: var(--paper); flex-shrink: 0; }
  .hs-line { font: 500 13.5px var(--font-body); color: var(--paper-dim); text-align: center; line-height: 1.45; margin: 12px auto 0; max-width: 300px; }
  .hs-line b { color: var(--paper); font-weight: 700; }
  .hs-line b.g { color: var(--ok-green); }

  /* card 4: results scorecard, mirroring the app's .standing and .rc-* */
  .hs-sc { margin-top: 12px; text-align: left; }
  .hs-standing { border: 1px solid var(--tray-line); border-radius: 12px; margin-bottom: 6px; overflow: hidden; }
  .hs-standing-head { width: 100%; display: flex; align-items: center; gap: 10px; background: var(--tray-raise);
    border: none; cursor: pointer; padding: 9px 12px; min-height: 44px; color: var(--paper); }
  .hs-standing-name { flex: 1; text-align: left; font: 600 15px var(--font-body); }
  .hs-standing-pts { font: 700 16px var(--font-body); color: var(--paper); flex-shrink: 0; min-width: 28px; text-align: right; }
  .hs-chev { width: 15px; height: 15px; color: var(--paper-dim); flex: none; transition: transform .22s; }
  .hs-chev.open { transform: rotate(180deg); }
  .hs-rc-headrow { display: flex; justify-content: space-between; align-items: baseline; padding: 6px 10px 4px; }
  .hs-rc-count { font: 800 11px var(--font-body); color: var(--paper-dim); letter-spacing: 1px; text-transform: uppercase; }
  .hs-rc-tip { font: 500 12px var(--font-body); color: var(--paper-dim); opacity: 0.8; white-space: nowrap; }
  .hs-rc-words { display: flex; flex-direction: column; }
  .hs-rc-row { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; padding: 7px 10px;
    background: none; border: none; width: 100%; text-align: left; cursor: pointer; }
  .hs-rc-row.active { background: var(--tray-raise); }
  .hs-rc-word { font: 600 16px var(--font-body); letter-spacing: 0.3px; color: var(--paper); }
  .hs-rc-row.active .hs-rc-word { color: var(--gold); }
  .hs-rc-pts { font: 600 13px var(--font-body); color: var(--paper-dim); white-space: nowrap; display: flex; align-items: baseline; gap: 7px; }
  .hs-rc-row.uniq .hs-rc-pts { color: var(--gold); }
  .hs-rc-pts em { font: 700 9px var(--font-body); font-style: normal; text-transform: uppercase; letter-spacing: 1px;
    border: 1px solid var(--gold); border-radius: 999px; padding: 2px 6px; }
  /* Only the deck needs a floor: it swipes between measured cards and must not resize
     under a thumb. Contents has no deck, and inheriting 62vh left it rendering an empty
     half-screen below two cards. */
  .hs-modal.deck { min-height: 62vh; }
  /* Progress rail. Two groups, 4 cards then 3, so both sections are visible from
     card 1 and you can see how much is left. Every gap here is a multiple of 4. */
  .hs-rail { display: flex; gap: 10px; margin-top: 16px; flex: 0 0 auto; }
  .hs-rail-g { display: flex; gap: 4px; }
  .hs-seg { display: block; flex: 1; height: 4px; border-radius: 2px; background: var(--rule); }
  .hs-seg.on { background: var(--fg); }
  /* The tap area grows UPWARD only. Downward would sit on top of the section
     labels and swallow their taps. */
  .hs-seg-hit { flex: 1; display: flex; background: none; border: none; cursor: pointer;
    padding: 12px 0 4px; margin: -12px 0 -4px; }
  .hs-seg-hit.on .hs-seg { background: var(--fg); }
  .hs-labels { display: flex; gap: 10px; margin-top: 4px; flex: 0 0 auto; }
  /* --fg-soft, not --fg-faint: faint is a stronger contrast between the two states
     but lands near 3:1 at 11px. The rail fill already says where you are. */
  .hs-section { margin: 0; padding: 0; background: none; border: none; text-align: left;
    min-height: 28px; cursor: default;
    font: 700 11px var(--font-body); letter-spacing: 1.5px; text-transform: uppercase; color: var(--fg-soft); }
  button.hs-section { cursor: pointer; }
  .hs-section.on { color: var(--fg); }
  /* Contents. Two covers, not a list: every card in this sheet is a board with a word
     traced across it, and a row of text with a chevron throws all of that away. */
  .hs-menu { flex: 1 1 auto; margin-top: 16px; }
  .hs-sec { display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
    background: var(--tray-raise); border: none; border-radius: 12px; padding: 16px; cursor: pointer; }
  .hs-sec + .hs-sec { margin-top: 10px; }
  .hs-sec-txt { min-width: 0; flex: 1; }
  /* .daily-cta's treatment: the covers are the same kind of object as the Compete
     cards, so the thing you press should look the same on both. */
  .hs-sec-cta { flex-shrink: 0; font: 700 14px var(--font-body); color: var(--gold); white-space: nowrap; }
  .hs-sec-t { display: block; margin-bottom: 2px; font: 800 16px var(--font-body); color: var(--paper); }
  .hs-sec-s { display: block; font: 500 12.5px var(--font-body); color: var(--paper-dim); line-height: 1.4; }
  /* Every value here is the real tile's, scaled by 24/52: radius .269, edge .096,
     gap .154, face .423. Guessing at these is what makes a shrunken board look wrong. */
  .hs-board.thumb { --hs-tile: 24px; --hs-gap: 4px; }
  .hs-board.thumb .hs-tile { border-radius: 6px;
    box-shadow: none; }
  .hs-board.thumb .hs-tile.lit { box-shadow: none; }
  .hs-board.thumb .hs-face { font-size: 10px; }
  /* CSS beats the presentation attribute, so the trace scales without touching helpTrace. */
  .hs-board.thumb .hs-trace polyline { stroke-width: 3; }
  .hs-head-btns { display: flex; gap: 8px; flex: 0 0 auto; }
  /* First run gets one exit and it says what it does. */
  .hs-skip { background: none; border: none; padding: 0 4px; min-height: 44px; cursor: pointer;
    font: 600 15px var(--font-body); color: var(--paper-dim); }
  .hs-nav { display: flex; gap: 10px; margin-top: 16px; align-items: stretch; flex: 0 0 auto; }
  .hs-back { flex: 0 0 auto; min-width: 110px; display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
  .hs-back svg { width: 16px; height: 16px; }
  .hs-next { flex: 1; margin-top: 0; }
