/* ============================================================================
 * Endplay client styles — the warm "sticker" skin (A12 front-end redesign).
 *
 * Design system reconciled from docs/design/DESIGN_HANDOFF.md + the reference
 * build (docs/design/Endplay.dc.html). Accessibility is a requirement, not
 * polish (audience: older adults on a phone):
 *   - All copy/UI type in rem, root font-size never pinned -> honors the OS
 *     text-size setting AND the in-app Text size control. (Chips are fixed
 *     graphical tokens >= 44px; their dark digit passes WCAG AA on every fill.)
 *   - Suit carried by SHAPE as well as color, always on, everywhere suit shows.
 *   - Touch targets >= 44px. prefers-reduced-motion respected.
 *   - Theme is three-state: light (bare :root), dark (media + [data-theme]).
 * ========================================================================== */

/* ---- self-hosted variable fonts (offline, no third-party request) ---- */
@font-face {
  font-family: "Space Grotesk"; font-style: normal; font-weight: 300 700;
  font-display: swap; src: url("fonts/space-grotesk-latin.woff2") format("woff2");
}
@font-face {
  font-family: "Hanken Grotesk"; font-style: normal; font-weight: 100 900;
  font-display: swap; src: url("fonts/hanken-grotesk-latin.woff2") format("woff2");
}

/* ---- theme tokens ---- */
:root {
  /* Uniform gap above the highest element on every scrolling screen (Home/Learn/Archive/Packs/News/...).
     The gap lives HERE, on the .page container, and each screen's header (.masthead/.screen-title/.pk-title)
     contributes no extra top, so all tabs line up. In the native shell the notch inset is added on top. */
  --screen-top: 0.9rem;
  /* warm paper (light) */
  --bg:#f5f0e6; --surface:#fbf8f1; --line:#e2d9c9; --ink:#26221b; --ink2:#3a3327;
  --muted:#7a7060; --faint:#9a9080; --stroke:#26221b; --shadow:#26221b;
  --chip-neutral:#efe7d6;
  --accent-soft:#eaf6f2; --accent-line:#bfe0d8; --accent-ink:#17726a;
  --you:#eef4f0;
  --badge-bg:#efe7d6; --badge-ink:#8a7c60; --slot:#d8cfbd;
  --turn:#1f8f83; --status-bg:#ffffff; --anim-scale:1;
  --dead-bg:#fbeceb; --dead-border:#c9524f; --dead-ink:#a53f3c; --avoid:#b0603a;
  /* fixed accents (both themes) */
  --teal:#1f8f83; --amber:#f0b323; --amber-ink:#26221b; --ramp:#d98a2b;
  /* suit fills — theme-independent (chips are self-colored); dark digit passes AA */
  /* chip fills lightened ~12% (variation 2, settled 2026-08-18): dark digit reads better on lighter
     fills and stays consistent across every chip; the crisp-edge glyph shadow (below) keeps them
     bounded. Fills are theme-independent (only backgrounds change), so this holds in dark mode too. */
  --suit-A:#ea8c8c; --suit-B:#ecc36a; --suit-C:#83b1ea; --suit-D:#6fc699; --suit-E:#d68fce;
  --suit-T:#f2f4f8; --suit-T-ink:#2a2d34; --chip-ink:#16181d;

  --sans:"Hanken Grotesk", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --display:"Space Grotesk", var(--sans);
  --radius:16px; --sticker-shadow:4px 5px 0 var(--shadow);
  --backdrop:#c7c1b2; --phone-body:#0b0b0d;   /* desktop-only phone-preview frame */
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:#1b1712; --surface:#262019; --line:#3a342a; --ink:#f2ece1; --ink2:#e4dccd;
    --muted:#ab9f8c; --faint:#837a68; --stroke:#f4efe6; --shadow:rgba(0,0,0,.6);
    --chip-neutral:#322b20;
    --accent-soft:#14322d; --accent-line:#2c5b53; --accent-ink:#5fd0c1;
    --you:#1e2b27;
    --badge-bg:#322b20; --badge-ink:#b7a887; --slot:#4a4234;
    --turn:#37b6a7; --status-bg:#262019;
    --dead-bg:#3a2320; --dead-border:#e0736f; --dead-ink:#e78c88; --avoid:#e0956f;
    --backdrop:#0d0b09;
    --phone-body:#565a61;   /* subtle graphite in dark mode - a black frame vanishes against the dark backdrop */
  }
}
:root[data-theme="dark"] {
  --bg:#1b1712; --surface:#262019; --line:#3a342a; --ink:#f2ece1; --ink2:#e4dccd;
  --muted:#ab9f8c; --faint:#837a68; --stroke:#f4efe6; --shadow:rgba(0,0,0,.6);
  --chip-neutral:#322b20;
  --accent-soft:#14322d; --accent-line:#2c5b53; --accent-ink:#5fd0c1;
  --you:#1e2b27;
  --badge-bg:#322b20; --badge-ink:#b7a887; --slot:#4a4234;
  --turn:#37b6a7; --status-bg:#262019;
  --dead-bg:#3a2320; --dead-border:#e0736f; --dead-ink:#e78c88; --avoid:#e0956f;
  --backdrop:#0d0b09;
  --phone-body:#565a61;   /* subtle graphite in dark mode - a black frame vanishes against the dark backdrop */
}

/* ---- base ---- */
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; overscroll-behavior: none; }   /* respect user text sizing; no document rubber-band */
/* No double-tap-to-zoom on anything tappable: solving is rapid tapping, and two quick chip taps read as
   a double-tap, which magnifies the page — and a WKWebView never zooms back out. `manipulation` disables
   only the double-tap gesture; PINCH zoom still works, which the low-vision requirement depends on. */
button, a, .chip, .seat, [role="button"], summary, label { touch-action: manipulation; }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font-family: var(--sans); font-size: 1rem; line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  /* Lock the document itself: only .page scrolls. Stops the iOS rubber-band bounce you get by
     dragging a non-scrollable element (the tab bar), which briefly slid the page up into the
     notch band. #app is already 100dvh / overflow:hidden, so nothing here needs to scroll. */
  overscroll-behavior: none;
}
img, svg { display: block; }
button { font-family: inherit; color: inherit; }

/* app shell: a scrolling screen region above a fixed bottom tab bar. On a real phone it fills
   the viewport; on desktop it renders inside a phone body (below) so the web build previews as
   the mobile app. */
#app {
  display: flex; flex-direction: column;
  height: 100vh; height: 100dvh; width: 100%; max-width: 30rem; margin: 0 auto;
  background: var(--bg); overflow: hidden;
}

/* Desktop preview: a thick black phone body with dead space around it. Gated on width so narrow
   (real-phone) viewports stay full-bleed — the eventual native app must fill the screen.
   Recent-iPhone screen ~393x852; +14px bezel each side. aspect-ratio keeps it phone-shaped and
   it shrinks to fit shorter windows. */
@media (min-width: 481px) {
  body { min-height: 100vh; min-height: 100dvh; display: flex; align-items: center;
         justify-content: center; background: var(--backdrop); }
  #app {
    width: auto; max-width: none; aspect-ratio: 421 / 880;
    height: min(880px, calc(100dvh - 24px));
    border: 14px solid var(--phone-body); border-radius: 54px; overflow: hidden;
    box-shadow: 0 24px 60px -12px rgba(0,0,0,.55), inset 0 0 0 1px rgba(255,255,255,.06);
  }
}
#screen { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }

/* simple scrolling screens (home / learn / recent / settings / win) */
.page { flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch;
        padding: var(--screen-top) 1.35rem calc(1.5rem + env(safe-area-inset-bottom)); }
/* .pad-top = the standalone legal pages (privacy/terms), which don't get the data-platform stamp, so
   they reserve the notch inset ON TOP of the uniform --screen-top gap (env is 0 on the web, so there it
   is just --screen-top). In-app screens get the same via the data-platform rule below. */
.page.pad-top { padding-top: calc(var(--screen-top) + env(safe-area-inset-top)); }

/* Native shell (#69): the Capacitor webview fills the whole screen, UNDER the camera notch / status bar at
   the top and the home indicator at the bottom (unlike mobile Safari, where the browser chrome reserves
   those bands). Reserve the top band on the NON-scrolling #screen container, NOT on the scrolling .page:
   padding-top on a scroller scrolls away, letting content slide under the notch. With it on #screen, the
   scroll area starts below the notch and content can never reach it. The scrolling screens then add no extra
   top gap (the reserved band IS the whitespace, which is enough — Jeff, 2026-09-13). The tabbar clears the
   home indicator with a TRIMMED inset: the full env(safe-area-inset-bottom) (~34pt) left the labels visibly
   floating above the bottom, so we subtract 1rem and floor at the base .6rem — enough to sit just above the
   home-indicator pill, no more (Jeff, 2026-09-14). Gated on <html data-platform="ios"> (stamped pre-paint)
   so the web layout is untouched. */
:root[data-platform="ios"] #screen { padding-top: env(safe-area-inset-top); }
:root[data-platform="ios"] .page,
:root[data-platform="ios"] .stack,
:root[data-platform="ios"] .page.pad-top { padding-top: 0; }
:root[data-platform="ios"] .tabbar { padding-bottom: max(.6rem, calc(env(safe-area-inset-bottom) - 1rem)); }

/* stacked screens with fixed chrome (play / tutorial) */
.stack { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
.stack-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch;
                padding: 0.75rem 1rem 0.25rem; }

/* ---- utilities ---- */
.sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden;
           clip:rect(0,0,0,0); white-space:nowrap; border:0; }
.skip { position:absolute; left:-999px; top:0; background:var(--teal); color:#fff;
        padding:.5rem .8rem; border-radius:0 0 8px 0; z-index:60; }
.skip:focus { left:0; }
:focus-visible { outline:3px solid var(--turn); outline-offset:2px; border-radius:8px; }
.display { font-family: var(--display); }

/* ============================ TAB BAR ============================ */
.tabbar {
  flex: none; display: flex; align-items: stretch; justify-content: space-around;
  gap: .25rem; padding: .6rem .5rem;   /* symmetric — no safe-area inset (home indicator overlays it) */
  border-top: 1.5px solid var(--line); background: var(--surface);
}
.tab {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: .25rem;
  background: none; border: none; cursor: pointer; padding: .35rem .1rem;
  font-family: var(--sans); font-weight: 600; font-size: .625rem; letter-spacing: .01em;
  color: var(--faint); min-height: 44px; border-radius: 12px;
}
.tab svg { width: 22px; height: 22px; }
.tab[aria-current="page"] { color: var(--teal); }
:root[data-theme="dark"] .tab[aria-current="page"],
:root:not([data-theme="light"]) .tab[aria-current="page"] { color: var(--turn); }

/* ============================ HOME ============================ */
.masthead { text-align: center; padding: 0 0 .95rem; border-bottom: 2px solid var(--stroke); }  /* top gap comes from .page (--screen-top) */
.masthead .suits { display: flex; justify-content: center; gap: 7px; margin-bottom: .7rem; }
.masthead .suits svg { width: 18px; height: 18px; }
.wordmark { font-family: var(--display); font-weight: 700; font-size: 2rem; line-height: 1;
            letter-spacing: .14em; text-transform: uppercase; margin: 0; }
/* tagline now rides in the masthead, directly under the wordmark and above the rule.
   No tight max-width — it wraps to the masthead's own width; balance evens the lines. */
.masthead .tagline { font-weight: 400; font-size: .8125rem; line-height: 1.5; color: var(--muted);
                     margin: .6rem 0 0; text-wrap: balance; }
/* the puzzle number / date, moved down to sit right above the ramp bars */
.home-dateline { text-align: center; font-weight: 500; font-size: .75rem; letter-spacing: .05em;
                 color: var(--muted); margin: 1.15rem 0 0; }

/* weekday ramp */
.ramp { display: flex; justify-content: space-between; align-items: flex-end; margin: 1.15rem .25rem .3rem; }
.home-dateline + .ramp { margin-top: .5rem; }   /* keep the date snug above the bars it labels */
.ramp .day { display: flex; flex-direction: column; align-items: center; gap: .35rem; flex: 1;
             position: relative; background: none; border: none; padding: 0; font: inherit; color: inherit; }
button.day { cursor: pointer; }
/* No icons — the bar itself carries state. Fill = played (solved), outline = unplayed; color says
   which day it is: teal for past days, orange for today, neutral for future. */
.ramp .bar { width: 26px; border-radius: 6px 6px 3px 3px; box-sizing: border-box;
             border: 2px solid transparent; background: var(--chip-neutral);
             transition: transform .12s ease, filter .12s ease; }
.ramp .bar.past          { background: transparent; border-color: var(--teal); }   /* past, unplayed → green outline */
.ramp .bar.past.solved   { background: var(--teal); }                               /* past, played  → green fill    */
.ramp .bar.today         { background: transparent; border-color: var(--ramp); }    /* today, unplayed → orange outline */
.ramp .bar.today.solved  { background: var(--ramp); }                               /* today, played → orange fill   */
.ramp .bar.future        { background: var(--chip-neutral); border-color: transparent; }  /* future → neutral, as-is */
.ramp .lbl { font-weight: 600; font-size: .625rem; line-height: 1; color: var(--faint); text-transform: uppercase; }
.ramp .day.solved .lbl { color: var(--teal); }                    /* past-solved label */
.ramp .day.today .lbl { color: var(--ramp); font-weight: 700; }   /* today label */
.ramp .day.future .lbl { color: var(--faint); }
/* selected day — whose table shows in the card below. A tab-style underline, absolutely positioned
   so it never shifts the bottom-aligned bars; the state label colours (teal/orange) are preserved. */
.ramp .day.sel::after { content: ""; position: absolute; left: 50%; bottom: -.5rem;
             transform: translateX(-50%); width: 16px; height: 3px; border-radius: 2px; background: var(--ink); }
.ramp .day.sel .lbl { font-weight: 700; }
button.day:hover .bar { transform: translateY(-3px); filter: brightness(1.06); }
button.day:active .bar { transform: translateY(-1px); }
.ramp-caption { font-weight: 500; font-size: .6875rem; color: var(--faint); text-align: center; margin: .5rem 0 1.15rem; }

/* today card (the big sticker). Specific class so it does NOT bleed onto other
   "today"-classed elements (the ramp's current-day button, the Recent today row). */
.today-card {
  border: 2px solid var(--stroke); border-radius: 20px; background: var(--surface);
  box-shadow: var(--sticker-shadow); overflow: hidden; cursor: pointer;
  width: 100%; text-align: left; display: block; padding: 0; color: inherit;
}
.today-card:active { transform: translate(1px, 1px); box-shadow: 3px 4px 0 var(--shadow); }
.today-head { display: flex; align-items: center; justify-content: space-between; padding: .95rem 1.1rem .75rem; }
.today-head .kicker { font-family: var(--sans); font-weight: 700; font-size: .75rem;
                      text-transform: uppercase; letter-spacing: .08em; color: var(--muted); }
/* panel title + its matching nav-bar icon, grouped so flex space-between keeps them together (#106) */
.ktitle { display: inline-flex; align-items: center; gap: .4rem; min-width: 0; }
.panel-ic { flex: none; display: inline-flex; width: 17px; height: 17px; color: var(--muted); }
.panel-ic svg { width: 100%; height: 100%; }
.tier-pill { background: var(--amber); border: 1.5px solid var(--stroke); border-radius: 8px;
             padding: .25rem .55rem; font-family: var(--display); font-weight: 700; font-size: .6875rem;
             letter-spacing: .02em; color: var(--amber-ink); text-transform: uppercase; }
.spec { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px;
        background: var(--line); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.spec .cell { background: var(--surface); color: var(--ink);
              display: flex; flex-direction: column; align-items: center; gap: .45rem; padding: .95rem .25rem .8rem; }
.spec .cell .glyphs { display: flex; align-items: center; gap: 3px; min-height: 16px; }
.spec .cell .cap { font-weight: 600; font-size: .625rem; color: var(--faint);
                   text-transform: uppercase; letter-spacing: .05em; }
.spec .meter { display: flex; align-items: flex-end; gap: 2px; }
.spec .meter i { width: 4px; height: 16px; background: var(--line); border-radius: 1px; }
.spec .meter i.on { background: var(--ramp); }
/* the steps icon always tops out at 20px (specGrid scales the tallest step to 20 for any 1-6
   numbers), so reserve that height as a safety net and keep the card a constant height whichever
   day is previewed; bars stay bottom-aligned via align-items: flex-end. */
.spec .steps { display: flex; align-items: flex-end; gap: 2.5px; min-height: 20px; }
.spec .steps i { width: 5px; background: currentColor; border-radius: 1px; }
.today-cta { display: flex; align-items: center; justify-content: center; gap: .5rem;
             background: var(--teal); color: #fff; padding: 1.05rem; border-top: 2px solid var(--stroke);
             font-family: var(--display); font-weight: 700; font-size: 1.125rem; }
.today-cta .cta-play { width: 17px; height: 17px; flex: none; margin-top: 1px; }
.home-note { font-weight: 400; font-size: .8125rem; line-height: 1.55; color: var(--faint);
             text-align: center; margin: 1.25rem .5rem 0; }
/* the "New here?" tutorial card — a header + a green Play/Continue strip, with a corner dismiss */
.home-firsttime-wrap { position: relative; margin: .85rem 0 .2rem; }
.learn-card { border: 2px solid var(--stroke); border-radius: 20px; background: var(--surface);
              box-shadow: var(--sticker-shadow); overflow: hidden; cursor: pointer;
              width: 100%; text-align: left; display: block; padding: 0; color: inherit; }
.learn-card:active { transform: translate(1px, 1px); box-shadow: 3px 4px 0 var(--shadow); }
.learn-head { display: flex; align-items: center; gap: .7rem; padding: .8rem 3rem .78rem 1rem; }
.lc-q { flex: none; width: 40px; height: 40px; border-radius: 10px; background: var(--amber);
        border: 1.5px solid var(--stroke); color: var(--amber-ink); display: inline-flex;
        align-items: center; justify-content: center; font-family: var(--display); font-weight: 800; font-size: 1.1rem; }
.learn-head .txt .lt { font-family: var(--display); font-weight: 700; font-size: .9375rem; color: var(--ink); }
.learn-head .txt .ls { font-size: .8125rem; color: var(--muted); margin-top: 1px; }
.learn-card .today-cta { font-size: 1rem; padding: .85rem; }
.home-dismiss { position: absolute; top: .5rem; right: .5rem; width: 34px; height: 34px;
                display: inline-flex; align-items: center; justify-content: center;
                border: none; background: none; color: var(--faint); cursor: pointer; border-radius: 8px; }
.home-dismiss svg { width: 15px; height: 15px; }
.home-dismiss:hover { color: var(--ink); background: var(--bg); }

/* "Play the archive" — a two-part card like today's: stats head + green strip */
.archive-card { margin-top: .7rem; }
.packs-card { margin-top: .7rem; }
/* packs tile head: kicker left, promo pill right (only the packs card sets this row to space-between) */
.packs-card .today-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.pk-promo { flex: none; font-size: .625rem; font-weight: 700; letter-spacing: .02em;
            color: var(--amber-ink); background: var(--amber); border: 1.5px solid var(--stroke);
            border-radius: 999px; padding: .16rem .5rem; }
/* News (#86). Home SUMMARY card matches the other tiles (today-card + arch-stats); a >=44px dismiss ×
   marks all read and drops the card to the bottom of Home. The dedicated News page shows a full panel
   per item. Calm, high-contrast, no motion. */
/* Home summary card — styled like the tutorial/learn card (icon + title + subtitle + green footer),
   no stats. The card reuses .learn-card; a >=44px × (marks all read) sits in the wrap corner. */
.news-wrap { position: relative; margin-top: .7rem; }
.news-ic { flex: none; width: 40px; height: 40px; border-radius: 10px; background: var(--amber);
           border: 1.5px solid var(--stroke); color: var(--amber-ink); display: inline-flex; align-items: center; justify-content: center; }
.news-ic svg { width: 22px; height: 22px; }
.news-ic.done { background: var(--teal); color: #fff; }   /* green when all stories are read (#86) */

/* The App Store badge on a news item (#231). Apple's artwork ships unmodified and is shown at its
   natural 120x40; the padding keeps the clear space Apple's guidelines ask for around it. */
.news-badge { display: inline-block; margin-top: .9rem; padding: 4px; line-height: 0; border-radius: 8px; }
.news-badge img { display: block; width: 120px; height: 40px; }
.news-badge:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
.news-dismiss { position: absolute; top: .35rem; right: .35rem; width: 44px; height: 44px; display: flex;
                align-items: center; justify-content: center; border: none; background: none;
                color: var(--muted); cursor: pointer; border-radius: 10px; }
.news-dismiss svg { width: 16px; height: 16px; }
.news-dismiss:active { transform: translate(1px, 1px); }
/* unread headlines inside the Home card (amber bullets = unread) */
.news-heads { list-style: none; margin: 0; padding: 0 1rem .95rem; display: flex; flex-direction: column; gap: .4rem; }
.news-heads li { position: relative; padding-left: .95rem; font-size: .8125rem; line-height: 1.35; color: var(--ink); }
.news-heads li::before { content: ""; position: absolute; left: .1rem; top: .5em; width: 6px; height: 6px; border-radius: 50%; background: var(--amber); }
.news-heads li.more { color: var(--muted); font-size: .75rem; }
.news-heads li.more::before { display: none; }
.news-archive { display: flex; flex-direction: column; gap: .7rem; }
/* News-page items: collapsible. Collapsed shows the header (title + date + unread dot + chevron);
   tapping expands the body and marks an unread item read. */
.news-item { background: var(--surface); border: 1.5px solid var(--line); border-left: 3px solid var(--accent-line);
             border-radius: 12px; overflow: hidden; }
.news-item-head { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: .6rem;
                  padding: .75rem .85rem; background: none; border: none; font: inherit; text-align: left; cursor: pointer; color: var(--ink); }
.news-tt { display: flex; flex-direction: column; gap: .12rem; min-width: 0; }
.news-item .news-title { font-family: var(--display); font-weight: 800; font-size: 1rem; color: var(--ink); line-height: 1.25; }
.news-item .news-date { font-size: .6875rem; font-weight: 600; letter-spacing: .02em; color: var(--faint); }
.news-flags { flex: none; display: flex; align-items: center; gap: .5rem; }
.news-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--amber); }
.news-chev { width: 20px; height: 20px; color: var(--muted); transition: transform .15s ease; }
.news-item.open .news-chev { transform: rotate(180deg); }
.news-body-wrap { padding: 0 .85rem .8rem; }
.news-item .news-body { font-size: .8125rem; line-height: 1.55; color: var(--muted); }
.news-item .news-body + .news-body { margin-top: .6em; }
.news-item .news-link { display: inline-block; margin-top: .6rem; font-size: .8125rem; font-weight: 700; color: var(--accent-ink); }
.arch-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px;
              background: var(--line); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.arch-stats .astat { background: var(--surface); color: var(--ink); display: flex; flex-direction: column;
                     align-items: center; gap: .35rem; padding: 1rem .25rem .85rem; }
.arch-stats .anum { font-family: var(--display); font-weight: 800; font-size: 1.75rem; line-height: 1; color: var(--ink); }
.arch-stats .astat .cap { font-weight: 600; font-size: .625rem; color: var(--faint);
                          text-transform: uppercase; letter-spacing: .05em; }

/* ============================ ARCHIVE CALENDAR ============================ */
.cal { margin: 0 0 .7rem; }
.cal-range { text-align: center; font-size: .8125rem; font-weight: 600; color: var(--muted); margin: .3rem 0 .9rem; }   /* free-window date range, above the grid (#115) */
/* one-month header: arrows left/right-justified, month + year centered and larger */
.cal-nav { display: flex; align-items: center; margin: .3rem 0 .8rem; }
.cal-arrow { flex: none; width: 44px; height: 44px; display: flex; align-items: center; justify-content: center;
             background: none; border: none; border-radius: 12px; color: var(--ink); cursor: pointer; }
.cal-arrow svg { width: 22px; height: 22px; }
.cal-arrow:disabled { opacity: .25; cursor: default; }
.cal-arrow:not(:disabled):active { transform: scale(.92); }
.cal-title { flex: 1; text-align: center; font-family: var(--display); font-weight: 700;
             font-size: 1.5rem; letter-spacing: -.01em; color: var(--ink); }
.cal-thismonth { display: flex; align-items: center; gap: .3rem; width: fit-content;
                 margin: .1rem auto 0; padding: .5rem 1.1rem; border-radius: 999px;
                 border: 1.5px solid var(--line); background: var(--surface); color: var(--ink);
                 font-family: var(--sans); font-weight: 600; font-size: .8125rem; cursor: pointer; }
.cal-thismonth .cta-play { width: 15px; height: 15px; flex: none; }
.cal-thismonth:active { transform: translate(1px, 1px); }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; }
.cal-wd { text-align: center; font-size: .6875rem; font-weight: 700; color: var(--faint); padding: .1rem 0 .3rem; }
.cal-cell { aspect-ratio: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
            gap: 2px; border-radius: 10px; border: 1.5px solid transparent; padding: 0; }
.cal-cell.pad { visibility: hidden; }
.cal-cell .cd { font-size: .8125rem; font-weight: 600; color: var(--ink); }
.cal-cell.out .cd { color: var(--faint); }
.cal-cell.day { cursor: pointer; background: var(--surface); border-color: var(--line); color: inherit; }
.cal-cell.day:active { transform: translate(1px, 1px); }
.cal-cell.day.started { border-color: var(--amber); }
.cal-cell.day.complete { background: var(--accent-soft); border-color: var(--accent-line); }
.cal-cell.day.complete .cd { color: var(--accent-ink); }
.cal-cell.day.today { border-color: var(--teal); border-width: 2px; }
.cal-cell .ci { width: 14px; height: 14px; color: var(--accent-ink); }
.cal-cell .ci.dot { width: 6px; height: 6px; border-radius: 50%; background: var(--amber); display: inline-block; }
.cal-cell .ci.emoji { width: auto; height: auto; font-size: 15px; line-height: 1; }   /* 💎/🌟/🎉 solved mark (#62) */
.arch-legend { display: flex; flex-flow: row wrap; justify-content: center; gap: .25rem .7rem; padding: .3rem .1rem 0; margin: 0 0 1.1rem; }   /* key for the marks (#62) — one line (#127) */
.arch-legend .lg { display: inline-flex; align-items: center; gap: .28rem; font-size: .72rem; font-weight: 600; color: var(--muted); white-space: nowrap; }
.arch-legend .lg .e { font-size: 14px; line-height: 1; }
/* First/Best solve segmented toggle for the archive + pack grids (#94) */
.badge-toggle { display: flex; width: fit-content; margin: 0 auto 1.1rem; border: 1.5px solid var(--line); border-radius: 10px; overflow: hidden; }   /* centered (#127) */
.badge-toggle .bt { padding: .4rem .85rem; background: var(--surface); color: var(--muted);
                    font: inherit; font-size: .75rem; font-weight: 700; border: none; cursor: pointer; }
.badge-toggle .bt + .bt { border-left: 1.5px solid var(--line); }
.badge-toggle .bt:active { transform: translate(1px, 1px); }
.badge-toggle .bt.sel { background: var(--accent-soft); color: var(--accent-ink); }

/* ============================ PACKS (progress screen) ============================ */
/* the wrapper is `.page` (shared vertical scroll); only pack-specific styles live here */
.pk-title { font-family: var(--display); font-weight: 800; font-size: 1.5rem; letter-spacing: -.01em; color: var(--ink); margin: .2rem 0 .3rem; }
.page > .pk-title:first-child { margin-top: 0; }   /* Packs: title is the top element; gap comes from .page (--screen-top) */
.pk-sub { font-size: .8125rem; color: var(--muted); margin: 0 0 1rem; line-height: 1.5; }
.pk-sub.pk-count { font-weight: 600; color: var(--ink); margin-top: -.6rem; }   /* the "N / M solved" line under a pack's description */
/* Pack LIST — one full-width tile per pack (name, blurb, free/paid/purchased badge, solved/total). */
.pk-list { display: flex; flex-direction: column; gap: .7rem; margin: 0 0 1.15rem; }
/* Pack tile: the sticker-card look of the Home tiles — 2px stroke, big radius, drop shadow — with the
   green strip moved to the TOP as a header (#137). overflow:hidden clips the teal header to the radius. */
.pk-tile { position: relative; width: 100%; border: 2px solid var(--stroke); border-radius: 20px;
           background: var(--surface); box-shadow: var(--sticker-shadow); overflow: hidden;
           color: var(--ink); font: inherit; cursor: pointer; text-align: left; }
.pk-tile:active { transform: translate(1px, 1px); box-shadow: 3px 4px 0 var(--shadow); }
.pk-tile.locked { border-style: dashed; opacity: .82; }
/* Full-cover primary action (open the pack page). Transparent; the pill sits above it (price-pill z-index). */
.pk-stretch { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; padding: 0; background: none;
              border: none; border-radius: inherit; cursor: pointer; z-index: 1; }
.pk-stretch:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
/* green header: pack name left, pill + play glyph right */
.pk-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem;
           background: var(--teal); color: #fff; padding: .7rem .9rem; border-bottom: 2px solid var(--stroke); }
.pk-tile-name { font-family: var(--display); font-weight: 800; font-size: 1.05rem; color: #fff; min-width: 0; }
.pk-head .pk-go { color: #fff; }
/* on the teal header, the price pill flips to an amber chip (matching the Home packs promo pill) so it
   stays legible + tappable against the teal */
.pk-head .price-pill { background: var(--amber); border-color: var(--stroke); color: var(--amber-ink); }
.pk-body { display: flex; flex-direction: column; gap: .35rem; padding: .7rem .9rem .8rem; }
.pk-tile-desc { font-size: .8125rem; color: var(--muted); line-height: 1.45; }
.pk-tile-foot { font-size: .75rem; font-weight: 600; color: var(--muted); font-variant-numeric: tabular-nums; }
/* free / paid / purchased indicator */
.pk-badge { flex: none; font-size: .625rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
            padding: .18rem .45rem; border-radius: 999px; border: 1.5px solid var(--line); color: var(--muted); }
.pk-badge.free { color: var(--accent-ink); border-color: var(--accent-line); background: var(--accent-soft); }
.pk-badge.owned { color: var(--accent-ink); border-color: var(--accent-line); background: var(--accent-soft); }
.pk-badge.paid { color: var(--ink); border-color: var(--stroke); background: var(--amber); }

/* Gate indicators (#129). An App-Store-style PRICE PILL marks purchase-gated content: a static indicator on
   the packs list, an interactive button (opens the app-feature modal) on the archive + pack pages. The ">"
   play glyph on every pack tile opens that pack's page. */
.price-pill { position: relative; z-index: 2;   /* on the packs list, sit above the full-cover stretch button so a direct pill click opens the modal */
              flex: none; display: inline-flex; align-items: center; justify-content: center; font-family: var(--sans);
              font-weight: 700; font-size: .75rem; letter-spacing: .01em; font-variant-numeric: tabular-nums;
              padding: .2rem .55rem; border-radius: 999px; border: 1.5px solid var(--stroke);
              background: var(--amber); color: var(--amber-ink); min-height: 26px; white-space: nowrap; }
button.price-pill { cursor: pointer; }
/* Grow the TAP target to the settled 44px minimum (motor a11y) without enlarging the visible pill: a
   centered, transparent overlay that is part of the button, so taps in the taller band still register. */
button.price-pill::after { content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
                           width: 100%; height: 100%; min-height: 44px; }
button.price-pill:active { transform: translate(1px, 1px); }
/* "Owned" pill (#72/#74) — shown in place of a buy pill once an à-la-carte item is purchased. A calm teal
   chip (the "Owned" text distinguishes it from a "$0.99" price pill). Placed after the .pk-head amber rule
   so it also wins inside a teal pack header. Non-interactive (no 44px tap band). (Subscription-only items
   get an "Unlocked" variant in #95.) */
.price-pill.owned { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-ink); }
.pk-head .price-pill.owned { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-ink); }
.price-pill.lg { font-size: .95rem; min-height: 34px; padding: .35rem .8rem; margin: 0 auto .2rem; }
.pk-tile-actions { display: flex; align-items: center; gap: .45rem; flex: none; }
.pk-go { flex: none; display: inline-flex; color: var(--accent-ink); }
.pk-go svg { width: 20px; height: 20px; }

/* The app-feature modal (#129) — the one overlay every gated tap opens. Backdrop-centered card, theme-aware. */
.modal-back { position: fixed; inset: 0; z-index: 80; display: flex; align-items: center; justify-content: center;
              padding: 1.25rem; background: rgba(0,0,0,.5); }
.modal-card { width: 100%; max-width: 22rem; background: var(--surface); color: var(--ink); border: 1.5px solid var(--line);
              border-radius: 18px; padding: 1.25rem 1.15rem 1.1rem; box-shadow: 0 18px 48px rgba(0,0,0,.35); text-align: center; }
.modal-title { font-family: var(--display); font-weight: 800; font-size: 1.2rem; line-height: 1.25; color: var(--ink); margin: .35rem 0 .5rem; }
.modal-body { font-size: .875rem; line-height: 1.55; color: var(--muted); margin: 0 0 1.1rem; }
.modal-actions { display: flex; flex-direction: column; gap: .5rem; }
.modal-cta { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; padding: .6rem 1rem;
             font: inherit; font-weight: 700; text-decoration: none; border: none; border-radius: 12px; cursor: pointer;
             background: var(--teal); color: #fff; }
.modal-cta:active { transform: translate(1px, 1px); }
.modal-close { min-height: 44px; padding: .5rem 1rem; font: inherit; font-weight: 600; color: var(--muted);
               background: none; border: none; border-radius: 12px; cursor: pointer; }
/* In-app purchase (#72), native-only: the buy sheet's Restore button + status line, and the More-page Store rows. */
.modal-restore { min-height: 44px; padding: .5rem 1rem; font: inherit; font-weight: 700; color: var(--teal);
                 background: none; border: 1.5px solid var(--line); border-radius: 12px; cursor: pointer; }
.modal-restore:active { transform: translate(1px, 1px); }
.modal-msg { font-size: .8125rem; line-height: 1.4; color: var(--amber-ink); background: var(--amber);
             border-radius: 10px; padding: .5rem .7rem; margin: 0 0 .8rem; }
.shop-row { display: flex; align-items: center; justify-content: space-between; gap: .75rem; padding: .95rem 1rem; }
.shop-row + .shop-row, .shop-row + .grow.link { border-top: 1px solid var(--line); }
.shop-info { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.shop-info .gtitle { font-weight: 600; font-size: .9375rem; }
.shop-desc { font-size: .8125rem; line-height: 1.35; color: var(--faint); }
.shop-buy { flex: none; min-height: 44px; padding: .5rem .95rem; font: inherit; font-weight: 700; white-space: nowrap;
            color: #fff; background: var(--teal); border: none; border-radius: 12px; cursor: pointer; }
.shop-buy:active { transform: translate(1px, 1px); }
.shop-owned { flex: none; font-weight: 700; font-size: .875rem; color: var(--teal); white-space: nowrap; }

/* Pack DETAIL — back link, header row, locked note, dev-only unlock toggle. */
.pk-back { display: inline-flex; align-items: center; gap: .15rem; margin: 0 0 .5rem -.25rem; padding: .3rem .4rem;
           background: none; border: none; color: var(--muted); font: inherit; font-weight: 600; font-size: .8125rem; cursor: pointer; }
.pk-back svg { width: 18px; height: 18px; }
.pk-detail-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.cal-cell.day.locked { cursor: default; background: var(--surface); border-style: dashed; border-color: var(--line); opacity: .6; }
.cal-cell.day.locked:active { transform: none; }
button.cal-cell.day.locked { cursor: pointer; }   /* a padlocked teaser (archive day or pack level) opens the modal (#129) */
.cal-cell .ci.lock { width: auto; height: auto; font-size: 12px; line-height: 1; }
.pk-tier { margin: 0 0 1.2rem; }
.pk-tier-head { display: flex; align-items: center; justify-content: space-between; margin: 0 0 .5rem; }
.pk-prog { font-size: .8125rem; font-weight: 600; color: var(--muted); font-variant-numeric: tabular-nums; }
.pk-tier.focus .pk-prog { color: var(--accent-ink); }
.pk-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; }   /* 5 per row (cells stay >=44px down to 320px) */
.pk-grid .cal-cell .cd { font-size: .875rem; }

/* ============================ TOP BARS ============================ */
.topbar { display: flex; align-items: center; justify-content: space-between; gap: .5rem;
          padding: .75rem 1.1rem .6rem; border-bottom: 1.5px solid var(--line); flex: none; }
.topbar .mid { font-family: var(--display); font-weight: 700; font-size: .9375rem; letter-spacing: .02em; margin: 0; }
.midwrap { display: flex; flex-direction: column; align-items: center; gap: 2px; }
/* deliberately faint + small: an elapsed readout, not a countdown, not a pressure clock */
.clock { font-size: .625rem; font-weight: 600; color: var(--faint); letter-spacing: .06em;
         font-variant-numeric: tabular-nums; }
/* Play-page header: difficulty (left) · number/date over the shape-order key (center) · timer (right).
   A 1fr-auto-1fr grid centers the number/date whatever the flanks' widths; align-items:center keeps
   the pill and clock vertically centered against the whole two-line header. */
.topbar.play-bar { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: .5rem; }
.play-bar > .play-back { justify-self: start; }
.play-bar > .hdr-right { justify-self: end; }
/* contextual back link (left), replacing the old difficulty pill; label = where it returns to */
.play-back { display: inline-flex; align-items: center; gap: .1rem; min-width: 0; padding: .3rem .35rem .3rem 0;
             background: none; border: none; color: var(--muted); font: inherit; font-weight: 600;
             font-size: .8125rem; cursor: pointer; }
.play-back svg { width: 17px; height: 17px; flex: none; }
.play-back span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.play-back:active { transform: translate(1px, 1px); }
/* right cluster: timer (+ pause) on top, difficulty pill below */
.hdr-right { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
.hdr-right-top { display: flex; align-items: center; gap: 5px; }
.hdr-right > .tier-pill { padding: .12rem .4rem; font-size: .625rem; }
.pausebtn { border: none; background: none; color: var(--faint); cursor: pointer; padding: 4px 2px;
            display: inline-flex; align-items: center; min-height: 0; }
.pausebtn:hover { color: var(--muted); }
.pausebtn svg { width: 15px; height: 15px; display: block; }
/* Manual pause: board + prompt go monochrome, translucent and inert; the header stays lit (frozen
   timer + the pause/resume toggle), and a Resume button floats over the dimmed board. */
.stack { position: relative; }
.stack.paused .status, .stack.paused .stack-scroll { filter: grayscale(1); opacity: .4; pointer-events: none; }
.stack.paused .topbar { position: relative; z-index: 3; }
.pause-overlay { position: absolute; inset: 0; z-index: 2; display: flex; align-items: center;
                 justify-content: center; background: transparent; border: none; cursor: pointer; padding: 0; }
.pause-overlay .po-badge { display: inline-flex; align-items: center; gap: .4rem; background: var(--teal);
                 color: #fff; border: 2px solid var(--stroke); border-radius: 999px; padding: .6rem 1.2rem;
                 font-family: var(--display); font-weight: 700; font-size: 1rem; box-shadow: var(--sticker-shadow); }
.pause-overlay .po-badge svg { width: 18px; height: 18px; }
.linkbtn { display: inline-flex; align-items: center; gap: .35rem; border: none; background: none;
           color: var(--muted); font-weight: 600; font-size: .875rem; cursor: pointer;
           min-height: 44px; padding: 0 .2rem; }
.linkbtn svg { width: 18px; height: 18px; }
.topbar .spacer { min-width: 3.4rem; }

/* ============================ STATUS LINE ============================ */
.status { margin: .75rem 1rem .5rem; border-radius: 14px; padding: .4rem .2rem .5rem .95rem;
          font-weight: 600; font-size: .875rem; line-height: 1.3; display: flex; align-items: center; gap: .4rem;
          background: var(--status-bg); border: 1.5px solid var(--line); color: var(--ink); flex: none; }
.status-text { flex: 1; min-width: 0; }   /* takes the room; wraps if long (tools stay pinned right) */
.status-tools { display: flex; gap: 0; flex: none; }
.toolbtn { width: 40px; height: 44px; flex: none; display: flex; align-items: center; justify-content: center;
           border: none; background: none; border-radius: 11px; color: inherit; cursor: pointer; }
.toolbtn svg { width: 21px; height: 21px; }
.toolbtn:disabled { opacity: .3; cursor: default; }
.toolbtn:not(:disabled):active { background: color-mix(in srgb, currentColor 12%, transparent); }
/* Hint is a plain icon like undo/restart (#149) - the old teal tint read as a stuck on/off toggle. */
.status.dead, .status.lost { background: var(--dead-bg); border-color: var(--dead-border); color: var(--dead-ink); }
.status.won { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-ink); }
/* radar-ping flash on the resolved states — ported from the prototype: three pulses, green for
   solved, red for not-solved (dead / lost). The ring expands and fades; a slight scale bump too. */
.status.won { --flash: color-mix(in srgb, var(--turn) 55%, transparent); }
.status.dead, .status.lost { --flash: color-mix(in srgb, var(--dead-border) 55%, transparent); }
.status.won, .status.dead, .status.lost { animation: status-flash calc(.55s * var(--anim-scale)) ease-out 3; }
@keyframes status-flash {
  0%   { transform: scale(1);     box-shadow: 0 0 0 0    var(--flash); }
  20%  { transform: scale(1.025); box-shadow: 0 0 0 4px  var(--flash); }
  100% { transform: scale(1);     box-shadow: 0 0 0 12px transparent; }
}
/* the led-suit shape sits inline with the prompt text — drop it so the shape's bottom edge lands on
   the text baseline (bottom of non-descender letters), not floating and not sunk below the line */
.status .swatch { display: inline-flex; vertical-align: -0.2em; }
.status .swatch .tok { width: 17px; height: 17px; }

/* shape-order key — chips only, sits in the top bar's left slot */
.suitkey-h { display: inline-flex; align-items: center; flex: none; }
.suitkey-h .sk-shape { display: inline-flex; }
.suitkey-h .sk-shape svg { width: 15px; height: 15px; }
.suitkey-h .sk-lt { color: var(--faint); font-size: 1rem; margin: 0 2px; }
.suitkey-h .sk-end { color: var(--faint); font-size: .6rem; text-transform: uppercase; letter-spacing: .04em; }
.suitkey-h .sk-end:first-child { margin-right: 4px; }
.suitkey-h .sk-end:last-child { margin-left: 4px; }

/* ============================ BOARD / SEATS ============================ */
.seat { background: var(--surface); border: 1.5px solid var(--line); border-radius: 16px;
        padding: .75rem .8rem; margin-bottom: .7rem; }
.seat.you { background: var(--you); }
.seat.turn { border-color: var(--turn); box-shadow: 0 0 0 2px var(--turn); }
.shead { display: flex; align-items: center; gap: .5rem; margin-bottom: .6rem; flex-wrap: wrap; }
.shead .name { font-family: var(--display); font-weight: 700; font-size: 1rem; letter-spacing: -.01em; }
.badge { font-weight: 600; font-size: .625rem; line-height: 1; text-transform: uppercase; letter-spacing: .04em;
         border: none; border-radius: 20px; padding: .3rem .5rem; }
.badge.policy { color: var(--badge-ink); background: var(--badge-bg); }
button.badge.policy { display: inline-flex; align-items: center; cursor: help; font-family: inherit; }
.policy-tip, .goal-tip { font-weight: 400; font-size: .78125rem; line-height: 1.4; text-transform: none; letter-spacing: normal;
              color: var(--muted); background: var(--bg); border: 1px solid var(--line); border-radius: 10px;
              padding: .5rem .65rem; margin: -.1rem 0 .6rem; }
/* Tap-to-explain wrapper (issue #27): a bare button around the goal label; margin-left:auto moves to
   the button so the label still sits at the right of the header. */
.goalbtn { margin-left: auto; display: inline-flex; align-items: center; background: none; border: none;
           padding: 0; cursor: help; font: inherit; text-align: left; }
/* help cursor over the whole goal, including the chip token — .goalbtn .chip outranks the
   equal-specificity `.chip { cursor: default }` that otherwise wins by source order (#37) */
.goalbtn *, .goalbtn .chip { cursor: help; }
.shead .name.tappable { cursor: help; }         /* the player name opens its rule tip (#37) */
.goalbtn .goal { margin-left: 0; }
/* The goal explanation shows its shape as a picture, the same token / mini chip as the goal label
   (spec §1.5), sized to the tip's text like the lesson prose's inline chips. */
/* em-sized, not px (#76 final review, finding 2): the tip's font-size already follows the in-app
   text-size setting (which scales the root font-size), so sizing the picture off its OWN em keeps the
   digit legible at the largest setting instead of staying a fixed 8px in a growing line of text. The tip
   is .78125rem = 12.5px at the default root size, so 1.44em/.64em/1.28em reproduce today's 18px/8px/16px
   pixel-for-pixel there and grow together with it above the default. */
.goal { margin-left: auto; display: flex; align-items: center; gap: .35rem;
        font-weight: 600; font-size: .8125rem; color: var(--ink2); }
.goal .g-verb { color: var(--ink2); }
.goal .g-verb.avoid { color: var(--avoid); }
.goal .mark { font-weight: 800; font-size: .95rem; }
.goal .mark.met { color: var(--turn); }
.goal .mark.failed { color: var(--dead-border); }
/* A goal pulses with a green glow the moment it is satisfied during play, and every goal pulses together
   when the puzzle is solved (issue #119). Like the trick-collect animation it rides --anim-scale, so it
   follows the Animation-speed setting and collapses to nothing at "instant"/reduced-motion (the global
   rule near the bottom of this file zeroes every animation there). transform-origin right so the scale
   grows from the ✓ mark and the right-aligned goal never pushes into the seat name. */
.goal.just-met { transform-origin: right center; animation: goal-met calc(.42s * var(--anim-scale)) cubic-bezier(.34,1.56,.64,1) 3; }
@keyframes goal-met {
  0%, 100% { transform: scale(1); filter: none; }
  50% { transform: scale(1.18); filter: drop-shadow(0 0 6px var(--turn)); }
}
/* The solve finale (all goals at once) is a TRIPLE pulse like the mid-hand one, but a touch grander — a
   wider scale and a stronger glow — so completing the puzzle reads as the bigger moment (Jeff, 2026-09-09).
   Same --anim-scale rule, and the JS holds play for its full length (FINALE_HOLD) before the banner. */
.goal.all-met { transform-origin: right center; animation: goal-finale calc(.5s * var(--anim-scale)) cubic-bezier(.34,1.56,.64,1) 3; }
@keyframes goal-finale {
  0%, 100% { transform: scale(1); filter: none; }
  50% { transform: scale(1.26); filter: drop-shadow(0 0 10px var(--turn)); }
}
/* The counterpart of the green pulse for a goal that just became impossible (#141): a RED pulse. When a
   goal is lost the JS pulses only the lost goal(s) with this and suppresses the green "met" pulses, so a
   loss never reads as a win. Defined AFTER .just-met so it wins if both classes are ever present. Same
   --anim-scale rule and transform-origin as the green pulse. */
.goal.just-lost { transform-origin: right center; animation: goal-lost calc(.42s * var(--anim-scale)) cubic-bezier(.34,1.56,.64,1) 3; }
@keyframes goal-lost {
  0%, 100% { transform: scale(1); filter: none; }
  50% { transform: scale(1.18); filter: drop-shadow(0 0 6px var(--dead-border)); }
}
/* goal icons enlarged for readability — the mini chip's 9px digit was too small to read */
.goal .chip.mini { width: 30px; height: 30px; border-radius: 9px; }
.goal .chip.mini .rank { font-size: 14px; }
.goal .tok { width: 25px; height: 25px; }
/* count-goal target as a mini trick badge (matches .wonbadge, issue #18) */
.goal .gcount { display: inline-flex; align-items: center; gap: 0; }
.goal .wonbadge.gt { width: 18px; height: 24px; font-size: 12px; border-width: 1.25px; }
.goal .gcount .gplus { font-family: var(--display); font-weight: 800; font-size: 14px; color: var(--ink2); margin-left: 1px; }

.seat-main { display: flex; align-items: center; gap: .75rem; }
.hand { display: flex; flex-wrap: wrap; gap: 7px; flex: 1; min-width: 0; }
.playzone { flex: none; }
/* won-count badge on the head line — a small vertical rectangle with the count inside, shown only
   when >0. Strong ink outline + ink number on a white (light) / dark (dark) box, so it reads as a
   counter against the seat card. Replaces the old per-seat won-tricks strip (space reclaimed). */
.wonbadge { flex: none; display: inline-flex; align-items: center; justify-content: center;
            width: 20px; height: 28px; padding: 0; border-radius: 4px; border: 1.5px solid var(--ink);
            background: var(--status-bg); color: var(--ink);
            font-family: var(--display); font-weight: 700; font-size: 15px; line-height: 1; }
button.wonbadge.tap { cursor: pointer; -webkit-appearance: none; appearance: none; }   /* tap to pop open won tricks (#56) */
/* the won-count badge pops as the trick chips land in it (trick-collect fly, #56 follow-up) */
.wonbadge.pulse { animation: wonpulse .34s cubic-bezier(.34,1.56,.64,1); }
@keyframes wonpulse { 0% { transform: scale(1); } 45% { transform: scale(1.4); } 100% { transform: scale(1); } }
/* the trick chips flying to the winner's badge: fixed clones over the board, no interaction */
.flylayer { position: fixed; inset: 0; z-index: 60; pointer-events: none; }
.flychip { position: fixed; margin: 0; will-change: transform, opacity; }
/* The won tricks, either popped open in-pane during play (#56) or shown at the end of the hand. */
.wonstrip { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; margin-top: .4rem; }
.wonstrip .gap { width: 7px; }
/* pop-open panel: a bordered box below the head, holding the strip (matches the policy/goal tips) */
.won-panel { border: 1px solid var(--line); border-radius: 10px; background: var(--bg);
             padding: .35rem .5rem; margin: -.1rem 0 .6rem; }
.won-panel .wonstrip { margin-top: 0; }
/* a chip the human played, wherever it landed among the won tricks — green outline + a labelled aria */
.chip.mine { box-shadow: 0 0 0 2px var(--turn); }

/* ============================ CHIPS ============================ */
/* A fixed-size graphical token: a shaped, self-colored glyph behind a dark digit.
   Sizes are px (touch geometry); the digit passes WCAG AA on every fill. */
.chip {
  position: relative; width: 46px; height: 46px; flex: none;
  display: flex; align-items: center; justify-content: center;
  border: none; background: transparent; padding: 0; margin: 0; border-radius: 12px;
  cursor: default;
}
.chip.tap { cursor: pointer; }
.chip.tap:disabled { cursor: default; }
/* crisp edge: a tight dark halo outlines the shape (definition on the cream, harmless on dark where
   the fill already contrasts) plus a soft depth shadow. Variation 2. */
.chip .glyph { position: absolute; width: 96%; height: 96%; overflow: visible;
               filter: drop-shadow(0 0 1px rgba(0,0,0,.5)) drop-shadow(0 2px 2px rgba(0,0,0,.2)); }
/* The digit's colour and vertical nudge come from the chip's style (CHIP_THEMES in app.js), set by
   chipEl as --rank-ink / --rank-dy (the standard triangle's optical-centre nudge is 0.16em). --rank-dy
   deliberately has NO fallback: when chipEl leaves it unset, the declaration is invalid at computed-value
   time and transform computes to `none`, exactly what an un-nudged digit had before (#76). */
.chip .rank { position: relative; z-index: 1; font-family: var(--display); font-weight: 700;
              font-size: 19px; line-height: 1; color: var(--rank-ink, var(--chip-ink));
              transform: translateY(var(--rank-dy)); }
.chip.dim { opacity: .36; }                              /* teaching by constraint: illegal grayed */
.chip.dim .glyph { filter: grayscale(.35) drop-shadow(0 1px 1px rgba(0,0,0,.2)); }
.chip.tap:not(:disabled):hover .glyph { filter: drop-shadow(0 2px 3px rgba(0,0,0,.4)); }
.chip.sm { width: 34px; height: 34px; }
.chip.sm .rank { font-size: 14px; }
.chip.mini { width: 22px; height: 22px; border-radius: 7px; }
.chip.mini .glyph { filter: drop-shadow(0 0 .75px rgba(0,0,0,.5)) drop-shadow(0 1px 1.5px rgba(0,0,0,.18)); }   /* same crisp edge, scaled for goal/won/hint chips */
.chip.mini .rank { font-size: 9px; }
/* Adaptive hand: chips flex to fill one line (no wrap), each capped at its normal size and kept
   square, so a large hand shrinks instead of wrapping to a second line — and only shrinks when it
   is too wide to fit at full size (no penalty for normal hands). The rank digit scales with the
   chip via container-query units so it stays legible. Scoped to .hand: the play zone, goals,
   won-strip and hint chips keep their fixed sizes. */
.hand { flex-wrap: nowrap; }
.hand .chip { flex: 1 1 0; min-width: 0; width: auto; height: auto; aspect-ratio: 1 / 1;
              max-width: 46px; container-type: inline-size; }   /* all hands the same max size */
.hand .chip .rank { font-size: 41cqw; }                /* scales with the chip (matches 19/46) */
/* the chip a hint recommends: a clear ring (not color alone — it is a ring on a specific chip) */
.chip.hinted { box-shadow: 0 0 0 3px var(--turn), 0 0 11px 1px var(--turn); border-radius: 13px; }

/* the empty play-zone slot */
.slot { width: 46px; height: 46px; border-radius: 12px; border: 1.5px dashed var(--slot); flex: none; }
/* #162: the play-zone frame is the persistent .slot (a stationary dashed target); a played chip nests
   inside and fills it, so at high chip counts a played chip reads as "played", not part of the hand. */
.slot.playzone > .chip.playzone { width: 100%; height: 100%; }

/* per-suit fill, applied to the glyph's shape via currentColor of the class */
.suit-A { --fill: var(--suit-A); }
.suit-B { --fill: var(--suit-B); }
.suit-C { --fill: var(--suit-C); }
.suit-D { --fill: var(--suit-D); }
.suit-E { --fill: var(--suit-E); }
.suit-T { --fill: var(--suit-T); }

/* small inline suit token (goal labels, spec grid, masthead legend) */
.tok { position: relative; display: inline-flex; flex: none; width: 20px; height: 20px; vertical-align: -.28rem; }
.tok svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }

/* ============================ CONTROLS ============================ */
.controls { display: flex; gap: .55rem; padding: .65rem 1rem calc(1.05rem + env(safe-area-inset-bottom));
            border-top: 1.5px solid var(--line); flex: none; }
.btn { flex: 1; min-height: 48px; border: 1.5px solid var(--stroke); background: var(--surface);
       color: var(--ink); border-radius: 13px; font-family: var(--display); font-weight: 700;
       font-size: .9375rem; cursor: pointer; }
.btn:disabled { opacity: .4; cursor: default; }
/* tutorial Undo/Restart: the daily's tool icon to the left of the text (issue #9) */
.btn.ico { display: flex; align-items: center; justify-content: center; gap: .45rem; }
.btn.ico svg { width: 1.15em; height: 1.15em; flex: none; }
/* Hint sits alongside Undo/Restart, calmer than either so it reads as an aid, not a primary control */
.btn.hint { border-color: var(--accent-line); background: var(--accent-soft); color: var(--accent-ink); font-size: .875rem; }
/* the hint line under the board — NOT a live region (the status line owns that role) */
.hintmsg { display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; margin: .5rem 1rem .1rem;
           padding: .5rem .7rem; border-radius: 12px; background: var(--accent-soft);
           border: 1.5px solid var(--accent-line); color: var(--accent-ink);
           font-size: .8125rem; font-weight: 600; line-height: 1.35; }
.hintmsg.dead { background: var(--dead-bg); border-color: var(--dead-border); color: var(--dead-ink); }
.hintmsg .hl { font-family: var(--display); font-weight: 700; text-transform: uppercase;
               letter-spacing: .04em; font-size: .6875rem; }
.hintmsg .chip { display: inline-flex; vertical-align: middle; }
.btn.primary { background: var(--teal); border-color: var(--stroke); color: #fff; box-shadow: 3px 4px 0 var(--shadow); }
.btn.primary:active { transform: translate(1px,1px); box-shadow: 2px 3px 0 var(--shadow); }
.btn.ghost { border-color: var(--line); background: var(--surface); color: var(--muted); box-shadow: none; }
/* destructive action — a clear red for a confirm button (kept as a reusable style; "Reset all data" that
   used it was removed 2026-09-20) */
.btn.danger { background: var(--dead-border); border-color: var(--dead-border); color: #fff;
              box-shadow: 3px 4px 0 var(--shadow); }
.btn.danger:active { transform: translate(1px, 1px); box-shadow: 2px 3px 0 var(--shadow); }
.scoring-note .btn { display: block; width: 100%; flex: none; min-height: 44px; margin-top: .85rem; }

/* ============================ TUTORIAL ============================ */
.teach { margin: .85rem 1rem .15rem; background: var(--accent-soft); border: 1.5px solid var(--accent-line);
         border-radius: 14px; padding: .8rem .95rem; flex: none; }
.teach.dead { background: var(--dead-bg); border-color: var(--dead-border); }
.teach .t-title { font-family: var(--display); font-weight: 700; font-size: .875rem;
                  color: var(--accent-ink); margin-bottom: .3rem; }
.teach.dead .t-title { color: var(--dead-ink); }
.teach .t-body { font-weight: 500; font-size: .84375rem; line-height: 1.45; color: var(--ink); }
/* Inline chip in the teach prose: sized to the text (a full mini chip is ~1.6x the line and reads
   as dropped-in), centered on the line. Scoped here so goal/won/hint mini chips keep their size. */
.teach .t-body .chip { display: inline-flex; vertical-align: middle; width: 18px; height: 18px; border-radius: 6px; }
.teach .t-body .chip .rank { font-size: 8px; }
/* collapsible lesson: tap the title to fold the body away (auto-folds once play begins) */
.teach.collapsible .t-title { display: flex; align-items: center; justify-content: space-between;
                              gap: .5rem; cursor: pointer; margin-bottom: 0; min-height: 24px; }
.teach.collapsible:not(.collapsed) .t-title { margin-bottom: .3rem; }
.teach .t-chev { width: 20px; height: 20px; flex: none; color: var(--accent-ink);
                 transition: transform .15s ease; }
.teach.collapsed .t-chev { transform: rotate(-90deg); }
.teach.collapsed .t-body { display: none; }
.tut-solved { margin: .85rem 1rem .15rem; background: var(--accent-soft); border: 1.5px solid var(--accent-line);
              border-radius: 14px; padding: .7rem .9rem; display: flex; align-items: center;
              justify-content: space-between; gap: .6rem; flex: none; }
.tut-solved span { font-family: var(--display); font-weight: 700; font-size: .875rem; color: var(--accent-ink); }
.tut-solved button { border: 2px solid var(--stroke); background: var(--teal); color: #fff; border-radius: 11px;
                     padding: .7rem .95rem; min-height: 44px; font-family: var(--display); font-weight: 700;
                     font-size: .8125rem; cursor: pointer; white-space: nowrap; display: inline-flex;
                     align-items: center; gap: .3rem; }
.tut-solved button svg { width: 15px; height: 15px; }

/* ============================ LEARN ============================ */
.screen-title { padding: 0 0 1rem; }   /* top gap comes from .page (--screen-top) */
.screen-title .title-row { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.screen-title .tut-reset { flex: none; }
.screen-title h1 { font-family: var(--display); font-weight: 700; font-size: 1.625rem; letter-spacing: -.01em; margin: 0; }
.screen-title p { font-weight: 400; font-size: .875rem; line-height: 1.5; color: var(--muted); margin: .5rem 0 0; }
.lessons { display: flex; flex-direction: column; gap: .55rem; }
.lesson { display: flex; align-items: center; gap: .8rem; border-radius: 15px; padding: .85rem 1rem;
          border: 1.5px solid var(--line); background: var(--surface); text-align: left; width: 100%;
          color: inherit; font-family: inherit; }
.lesson .num { width: 34px; height: 34px; flex: none; border-radius: 10px; display: flex; align-items: center;
               justify-content: center; font-family: var(--display); font-weight: 700; font-size: .9375rem; }
.lesson .txt { flex: 1; min-width: 0; }
.lesson .lt { font-family: var(--display); font-weight: 700; font-size: .9375rem; line-height: 1.2; }
.lesson .ls { font-weight: 400; font-size: .75rem; line-height: 1.3; color: var(--muted); margin-top: .1rem; }
.lesson .chev { width: 20px; height: 20px; flex: none; color: var(--muted); }
.lesson.done { border-color: var(--accent-line); background: var(--accent-soft); }
.lesson.done .num { background: var(--teal); color: #fff; }
.lesson.done .lt { color: var(--accent-ink); }
.lesson.done .ls { color: var(--accent-ink); opacity: .85; }
.lesson.next { border: 2px solid var(--stroke); box-shadow: 3px 4px 0 var(--shadow); cursor: pointer; }
.lesson.next .num { background: var(--amber); border: 1.5px solid var(--stroke); color: var(--amber-ink); }
.lesson.locked { opacity: .6; }
.lesson.locked .num { background: var(--chip-neutral); color: var(--faint); }
.lesson.locked .lt { color: var(--muted); }
button.lesson { cursor: pointer; }
.learn-progress { font-weight: 500; font-size: .75rem; color: var(--faint); text-align: center; margin-top: 1rem; }
/* Tutorial-complete hand-off card (top of the Learn screen once all lessons are done) */
.learn-done-card { margin: .25rem 0 1.1rem; }
.learn-done-msg { padding: .1rem 1.1rem 1rem; font-weight: 500; font-size: .9375rem;
                  line-height: 1.5; color: var(--muted); }
.intro-lesson .num { background: var(--ramp); border: 1.5px solid var(--stroke); color: #fff; }   /* the "0" on the Learn list */

/* quiet opt-in link to the written rules, below the tutorial */
.rules-link { display: flex; align-items: center; justify-content: center; gap: .35rem; width: 100%;
              margin: 1.15rem 0 .2rem; padding: .5rem; background: none; border: none; cursor: pointer;
              font-family: var(--display); font-weight: 600; font-size: .9375rem; color: var(--teal); }
.rules-link svg { width: 15px; height: 15px; flex: none; }

/* the written-rules reference screen */
.rules { display: flex; flex-direction: column; gap: 1.15rem; }
.rules section h2 { font-family: var(--display); font-weight: 700; font-size: .9375rem; color: var(--ink);
                    margin: 0 0 .35rem; }
.rules section p { font-weight: 400; font-size: .875rem; line-height: 1.55; color: var(--muted); margin: 0; }
.rules section p + p { margin-top: .5rem; }
.rules ul + p { margin-top: .6rem; }
.rules ul { margin: .4rem 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: .5rem; }
.rules li { font-weight: 400; font-size: .875rem; line-height: 1.5; color: var(--muted);
            padding-left: 1rem; position: relative; }
.rules li::before { content: ""; position: absolute; left: .15rem; top: .55em; width: 5px; height: 5px;
                    border-radius: 50%; background: var(--teal); }
.rules b, .rules strong { color: var(--ink); font-weight: 700; }
.rules a { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 2px; font-weight: 600; }
/* Static pages (privacy.html) built on the app shell: an inline "back" button, not a full-width one */
.static-back { text-align: center; margin: 1.4rem 0 .5rem; }
.static-back .btn { display: inline-flex; flex: none; min-height: 0; padding: .7rem 1.3rem;
                    border-radius: 12px; text-decoration: none; }
/* "Last updated" line on the static legal pages (terms.html). */
.rules-updated { font-size: .8125rem; color: var(--muted); margin: 1.4rem 0 0; }

/* ============================ RECENT ============================ */
.rows { display: flex; flex-direction: column; gap: .55rem; margin-top: .5rem; }
.row { display: flex; align-items: center; gap: .75rem; border: 1.5px solid var(--line); background: var(--surface);
       border-radius: 14px; padding: .8rem .95rem; text-align: left; width: 100%; color: inherit;
       font-family: inherit; cursor: pointer; }
.row .rn { font-family: var(--display); font-weight: 700; font-size: 1.0625rem; min-width: 2.75rem; }
.row .rmeta { font-weight: 600; font-size: .8125rem; color: var(--muted); }
.row .rres { margin-left: auto; display: inline-flex; align-items: center; gap: .3rem;
             font-weight: 700; font-size: .75rem; }
.row .rres.solved { color: var(--teal); }
.row .rres.solved svg { width: 14px; height: 14px; }
.row .rres.none { color: var(--faint); font-weight: 600; }
.row .rres.today { color: var(--ramp); font-family: var(--display); }
.row.today { border: 2px solid var(--stroke); box-shadow: var(--sticker-shadow); }
:root[data-theme="dark"] .row .rres.solved, :root:not([data-theme="light"]) .row .rres.solved { color: var(--turn); }

/* ============================ SETTINGS ============================ */
.group-label { font-weight: 700; font-size: .6875rem; text-transform: uppercase; letter-spacing: .09em;
               color: var(--faint); margin: 1.35rem .15rem .6rem; }
/* the FIRST group-label sits right under the (subtitle-less) title — don't stack its top margin on
   the title's bottom padding, or the gap looks like a hole. Later group-labels keep the full margin. */
.screen-title + .group-label { margin-top: .25rem; }
.group { border: 1.5px solid var(--line); background: var(--surface); border-radius: 16px; overflow: hidden; }
.grow { display: flex; align-items: center; justify-content: space-between; gap: .75rem; padding: .95rem 1rem; }
.grow + .grow { border-top: 1px solid var(--line); }
.grow .gtitle { font-weight: 600; font-size: .9375rem; }
.grow.link { cursor: pointer; width: 100%; text-align: left; background: none; border: none; color: inherit; font-family: inherit; }
.grow.link svg { width: 18px; height: 18px; color: var(--faint); flex: none; }
.grow.col { flex-direction: column; align-items: stretch; gap: .75rem; }
.setting-hint { font-weight: 400; font-size: .8125rem; line-height: 1.4; color: var(--faint); margin-top: -.15rem; }
.seg { display: flex; border: 1.5px solid var(--line); border-radius: 11px; overflow: hidden; flex: none; }
.seg button { padding: .6rem .9rem; border: none; cursor: pointer; background: transparent; color: var(--muted);
              font-family: var(--display); font-weight: 700; font-size: .8125rem; min-height: 44px; }
.seg button[aria-pressed="true"] { background: var(--teal); color: #fff; }
.sizes { display: flex; gap: .5rem; align-items: flex-end; }
.sizes button { flex: 1; text-align: center; border: 1.5px solid var(--line); border-radius: 9px;
                background: transparent; color: var(--muted); cursor: pointer; font-family: var(--display);
                font-weight: 700; min-height: 44px; }
.sizes button.s { font-size: .75rem; } .sizes button.m { font-size: .9375rem; }
.sizes button.l { font-size: 1.125rem; } .sizes button.xl { font-size: 1.375rem; }
.sizes button[aria-pressed="true"] { border: 2px solid var(--stroke); color: var(--ink); background: var(--accent-soft); }
/* animation-speed slider (More -> Appearance) */
.anim-val { font-family: var(--display); font-weight: 700; font-size: .8125rem; color: var(--accent-ink); }
.anim-range { -webkit-appearance: none; appearance: none; width: 100%; min-height: 44px; margin: .35rem 0;
              background: transparent; cursor: pointer; accent-color: var(--teal); touch-action: pan-y; }  /* min-height (not padding) guarantees a >=44px hit area under border-box at every text size; pan-y keeps page scroll */
.anim-range::-webkit-slider-runnable-track { height: 8px; border-radius: 999px; background: var(--line); }
.anim-range::-moz-range-track { height: 8px; border-radius: 999px; background: var(--line); }
.anim-range::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 26px; height: 26px; margin-top: -9px;
              border-radius: 50%; background: var(--teal); border: 3px solid var(--surface); box-shadow: 0 1px 3px rgba(38,34,27,.3); }
.anim-range::-moz-range-thumb { width: 26px; height: 26px; border-radius: 50%; background: var(--teal);
              border: 3px solid var(--surface); box-shadow: 0 1px 3px rgba(38,34,27,.3); }
.anim-range:focus-visible { outline: 2px solid var(--turn); outline-offset: 4px; }
.anim-ends { display: flex; justify-content: space-between; font-weight: 500; font-size: .75rem; color: var(--muted); margin-top: -.1rem; }
.about-foot { text-align: center; font-weight: 500; font-size: .75rem; line-height: 1.5; color: var(--faint); margin-top: 1.25rem; }
.scoring-note { font-weight: 400; font-size: .8125rem; line-height: 1.5; color: var(--muted);
                padding: 0 1rem .95rem; }
.scoring-note .note-link { color: var(--teal); font-weight: 600; text-decoration: none; white-space: nowrap; }
/* #182 — web->app progress migration (copy code / paste code) */
.scoring-note.migrate p { margin: 0 0 .75rem; }
.migrate-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .85rem; }
/* font-size: NEVER below 16px. iOS auto-zooms the page when a text field under 16px takes focus, and
   inside a WKWebView it does not zoom back out — the app is stuck magnified until it is killed. max()
   keeps it scaling UP with the text-size control (1rem is 14px at the smallest setting) while holding
   the floor that stops the zoom. This is the only focusable text field in the app. */
.migrate-code { width: 100%; box-sizing: border-box; margin-top: .75rem; resize: vertical; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
                font-size: max(16px, .875rem); line-height: 1.35; word-break: break-all; padding: .55rem .65rem; border: 1px solid var(--line);
                border-radius: 8px; background: var(--surface); color: var(--ink); }
.migrate-code:focus-visible { outline: 2px solid var(--turn); outline-offset: 2px; }
.migrate-msg { margin-top: .7rem; font-weight: 600; font-size: .8125rem; line-height: 1.4; }
.migrate-msg.ok { color: var(--teal); }
.migrate-msg.err { color: #d64541; }   /* legible on both the cream and the dark surface */
.store-msg { margin: 0; padding: 0 1rem .95rem; }   /* #228: Restore Purchases outcome, inside the Store group */
/* "Our use of AI" statement: readable paragraphs + a subtle signature */
.ai-note p { margin: 0 0 .65rem; }
.ai-note p:last-child { margin-bottom: 0; }
.ai-note .ai-sig { font-weight: 600; color: var(--ink); margin-top: .85rem; }

/* ============================ RESULT BANNER ============================ */
/* The solved daily's end state now lives in the top status panel (see resultBanner()), not a
   separate page. The banner reuses the .status.won accent skin, laid out as a column. */
/* margin: match the seat cards — no side margin (the scroll's own padding insets both equally, so
   the banner is the seat width), and a .7rem bottom gap so it doesn't butt against the first seat. */
.status.result { flex-direction: column; align-items: stretch; gap: .7rem; padding: 1rem; margin: 0 0 .7rem; }
.result-title { display: flex; align-items: center; gap: .5rem; font-family: var(--display);
                font-weight: 700; font-size: 1.25rem; color: var(--accent-ink); }
.result-title svg { width: 20px; height: 20px; color: var(--turn); flex: none; }
.result-score { display: flex; align-items: center; gap: .35rem; font-weight: 700; font-size: .9375rem;
                color: var(--accent-ink); margin: .1rem 0; }   /* two rows now: 1st + best (#94) */
.result-score .rs-label { flex: none; min-width: 2.1rem; font-size: .625rem; font-weight: 800;
                          letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.result-score .rs-badge { flex: none; font-size: 1rem; }
/* "Play <next day>" — the encouraged forward action (teal), placed above the quiet "Play again". */
.result-next { width: 100%; min-height: 48px; border-radius: 15px; border: 2px solid var(--stroke);
               background: var(--teal); color: #fff; box-shadow: 3px 4px 0 var(--shadow);
               font-family: var(--display); font-weight: 700; font-size: 1rem; cursor: pointer;
               display: inline-flex; align-items: center; justify-content: center; gap: .4rem; }
.result-next svg { width: 18px; height: 18px; flex: none; }
.result-next:active { transform: translate(1px, 1px); box-shadow: 1px 2px 0 var(--shadow); }
.result-again { width: 100%; min-height: 48px; border-radius: 15px; border: 2px solid var(--stroke);
                background: var(--surface); color: var(--ink); box-shadow: var(--sticker-shadow);
                font-family: var(--display); font-weight: 700; font-size: 1rem; cursor: pointer; }
.result-again:active { transform: translate(1px, 1px); box-shadow: 3px 4px 0 var(--shadow); }
.result-foot { font-weight: 500; font-size: .75rem; line-height: 1.45; color: var(--faint); }

/* Share / Copy buttons (used by the result banner) */
.win-actions { display: flex; gap: .625rem; }
.win-actions button { flex: 1; min-height: 50px; border-radius: 15px; border: 2px solid var(--stroke);
                      font-family: var(--display); font-weight: 700; font-size: 1rem; cursor: pointer;
                      display: inline-flex; align-items: center; justify-content: center; gap: .5rem; }
/* Share is the encouraged action -> green primary; Copy is the quiet white fallback. */
.win-actions .share { background: var(--teal); color: #fff; box-shadow: 3px 4px 0 var(--shadow); }
.win-actions .share svg { width: 17px; height: 17px; }
.win-actions .copy { background: var(--surface); color: var(--ink); box-shadow: 3px 4px 0 var(--shadow); }

/* the banner's own flash: a green ring pulse WITHOUT the scale bump (scaling a tall banner reads
   worse than scaling the old one-line status). More specific than .status.won, so it wins. */
@keyframes banner-flash {
  0%   { box-shadow: 0 0 0 0    var(--flash); }
  25%  { box-shadow: 0 0 0 5px  var(--flash); }
  100% { box-shadow: 0 0 0 16px transparent; }
}
.status.won.result { animation: banner-flash calc(.55s * var(--anim-scale)) ease-out 3; }

/* ============================ MOTION ============================ */
/* the chip FLIP glide is applied inline in animateFlip() and scaled by animScale() — chips carry a
   data-flip ATTRIBUTE, not a .flip class, so there is deliberately no static (unscaled) .flip rule here. */
@keyframes pop { 0% { transform: scale(.6); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}
