/* ===========================================================================
   Arcade shared chrome — the visual shell every game reuses.
   Load with: <link rel="stylesheet" href="../shared/arcade.css"> (hub: "shared/…")

   A game supplies only its identity, via three CSS variables in its own :root:
     --chrome        the game's colour (header band, serve/pause/cleared, dots)
     --chrome-deep   a deeper shade   (menu button, timer pill)
     --chrome-body   a lighter tint   (body around the board card, 2nd buttons)
   …plus its wordmark text and its header-shape SVG (inline in the HTML).

   Everything below is identity-independent and keyed on shared class / id
   conventions, so it stays byte-identical across all games. See HEADER.md +
   TRANSITIONS.md for the why.
   =========================================================================== */

@font-face {
  font-family: "Hello Notie";
  /* woff2 (215K), converted from the 575K OTF — much faster to be ready, which
     matters on slower/e-ink tablets where a font swap is very visible. */
  src: url("Hello-Notie.woff2") format("woff2");
  font-weight: 400;
  /* block, NOT swap: swap paints the sans fallback first and swaps in the real
     font a beat later — a flash-of-sans on every screen. block shows nothing
     until the (local, fast) font is ready, so text only ever appears in the real
     font. The screen-wipe hides the brief wait. */
  font-display: block;
}
/* Montserrat — the secondary font (a clean sans for anything the handwritten
   display face doesn't suit). Vendored locally (variable woff2, all weights)
   rather than linked from Google Fonts: the arcade must run fully offline. */
@font-face {
  font-family: "Montserrat";
  src: url("Montserrat.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: block;
}

:root {
  --card: #ffffff;
  --chrome-ink: #2c2c2a;     /* wordmark + chrome text (near-black) */
  --font-display: "Hello Notie", ui-rounded, system-ui, sans-serif;
  --font-secondary: "Montserrat", system-ui, sans-serif;
  --font-secondary-weight: 500;   /* Montserrat is variable — one knob for its weight everywhere it's used (medium; 600 for semibold) */
  /* The one fat checkmark shape, used everywhere a completion tick shows (progress
     dots, sealed jars/shelves) — a mask so it takes the surrounding colour via
     background-color. Same silhouette as Stash's bin CHECK_SVG. */
  --fat-check: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2048%2048'%3E%3Cpath%20d='M20.6357%2021.2255c0.3059%200.3521%200.5806%200.6713%200.8227%200.9544%200.5596%20-0.8096%201.3205%20-1.907%202.2632%20-3.2593%202.1735%20-3.1175%205.3136%20-7.5895%209.1822%20-13.00951%201.5155%20-2.1234%204.44%20-3.36395%207.0809%20-1.8987%201.5704%200.87129%203.4444%202.22303%204.9204%204.2348%201.4639%201.99531%201.0325%204.50381%20-0.1081%206.23181%20-8.5629%2012.9727%20-14.7432%2020.8743%20-18.1836%2024.9856%20-2.2505%202.6896%20-6.1818%202.8762%20-8.7077%200.481%20-3.3184%20-3.1467%20-8.60926%20-8.4635%20-14.11273%20-15.2036%20-1.54807%20-1.8959%20-2.00632%20-4.7727%20-0.30263%20-6.9557%201.10492%20-1.4158%202.35391%20-2.6155%203.4651%20-3.5537%202.45454%20-2.0723%205.88436%20-1.5133%207.91736%200.6403%202.4479%202.593%204.4106%204.7959%205.7629%206.3526Z'/%3E%3C/svg%3E");
}

/* Everything is lowercase — it matches the lowercase wordmark brand. Done once
   here (text-transform, not per-string) so every label — buttons, titles, status
   lines, menus — reads the same, and new copy inherits it. Numbers/emoji are
   unaffected. Loaded by every game; the hub sets the same on its own body. */
body { text-transform: lowercase; }
button, input, select, textarea { text-transform: inherit; }   /* form controls don't inherit it on their own */

/* ---- Shared page scaffolding ----
   Copied byte-identical into every game's inline <style>; lives here now. A game
   supplies only its colour vars (--chrome-body, --ink, --maxw). NOT the hub — it's
   self-contained and doesn't link this file. Stash predates the pattern and keeps
   its own bespoke html/body/* inline (they override these via source order). */
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; margin: 0; overflow: hidden; }  /* no document scroll: stops iOS Safari retracting its toolbar, which shifts 100dvh and floats the app */
body {
  font-family: var(--font-display);
  background: var(--chrome-body);   /* body tint continues past the 430px column on desktop */
  color: var(--ink);
  display: flex;
  justify-content: center;
  overscroll-behavior: none;
  -webkit-user-select: none;
  user-select: none;
}

/* ---- The app column (shared shell) ----
   The single 430px-max column every game centres in the body tint. Was copied
   byte-for-byte into all 16 games; lives here now. A game supplies only --maxw
   (default 430, widened per-game on desktop). Placed BEFORE the ios-standalone
   rule above's counterpart so `html.ios-standalone #app` still wins on height.
   (Stash predates this and keeps its own bespoke #app inline.) */
#app {
  width: 100%;
  max-width: var(--maxw);
  /* vh FIRST as the fallback, dvh second as the real rule — the same pattern the
     hub's .sheet max-height uses. dvh landed in Chrome 108, and an older WebView
     drops the declaration as invalid, leaving #app with no height at all: it then
     doesn't fill the window and the black window background shows as bars top and
     bottom (seen on an AOSP WebView 101 tablet). A modern engine takes the dvh
     line; html.ios-standalone #app overrides both below, as before. */
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  padding: 0 0 env(safe-area-inset-bottom);   /* top inset handled by the header (chrome bleeds behind notch) */
  position: relative;
  overflow: visible;                            /* header full-bleeds past the column on desktop; fixed overlays need no clipping here */
}

/* Screens (a game shows one at a time: a #menu splash and/or the #game board).
   min-height:0 lets the active screen shrink inside #app rather than overflow — the
   majority variant; harmless to the games that omitted it. (Pop and Stash have no
   screens, so this simply doesn't match there.) */
.screen { display: none; flex: 1; flex-direction: column; min-height: 0; }
.screen.active { display: flex; }

/* The white board card. Opt-in class on whichever element carries the card (the
   .board-wrap for most games, the .board itself for the square/hug games). Just the
   universal look — colour + radius; padding varies per game (14px on the board-wrap
   games, bespoke on jars, none on weave/glide whose grid uses its own inset) so it
   stays local. Sift (12px radius), Tidy (#efe7d9) and Stash (bespoke) don't use it. */
.board-card { background: var(--card); border-radius: 10px; }

/* ---- iOS standalone (added-to-home-screen) full-height ----
   Measured on-device: in standalone the real screen is 100vh, but 100dvh resolves
   to the screen MINUS the top safe-area — so #app (100dvh) lands a status-bar short
   at the bottom (the "bottom bar"). Switch the whole full-screen layer set to 100vh
   TOGETHER — #app AND every fixed takeover (pause, cleared overlay, shuffle intro) —
   so they stay the same height; moving only #app left the overlays short and the
   game's controls peeked out below them. In-browser Safari keeps 100dvh / inset:0
   (the correct visible area there; 100vh would be too tall and hide content behind
   the toolbar). Keyed off the `ios-standalone` class (set in arcade.js from
   `navigator.standalone`), NOT `@media (display-mode: standalone)` — measured
   on-device: that query never matches an Apple-meta home-screen app (VIEWPORT-BUG.md).
   `html.ios-standalone #app` beats the games' inline `#app` rule on specificity.
   html/body must grow to 100vh TOO: the games set `html, body { height:100%;
   overflow:hidden }`, and in standalone 100% is 793 — an 852-tall #app inside a
   793-tall overflow-hidden body gets CLIPPED at 793 and the bar stays (measured
   on-device: vdebug showed #app bottom 852 yet the body-colour band remained). */
html.ios-standalone, html.ios-standalone body,
html.ios-standalone #app,
html.ios-standalone #pause,
html.ios-standalone .overlay,
html.ios-standalone #arcade-timecheck,
html.ios-standalone .arc-intro { height: 100vh; }

/* ---- Header bar: back(menu) · wordmark · (help), colour bleeds behind notch ---- */
.header {
  position: relative;
  background: transparent;               /* the wavy SVG paints the chrome; troughs reveal the body */
  padding: calc(env(safe-area-inset-top) + 14px) 20px 44px;
  flex: 0 0 auto;
  /* Full-bleed the header band to the device edges: on a phone the app already
     fills the width so this is a no-op (100vw == the column, margin 0); on a wider
     screen the band breaks out of the centred 430px column while the status band /
     board / controls below stay in it. Needs #app overflow:visible (set per game).
     The wordmark stays centred (= the column centre); the menu button sits at the
     viewport's left edge. */
  width: 100vw;
  margin-left: calc(50% - 50vw);
}
.header .hdr-bg {                          /* per-game header-shape SVG, stretched to fill */
  position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; display: block;
  pointer-events: none;                   /* decorative — must never swallow the menu-button tap */
}
.header .wordmark {
  position: relative; z-index: 1;
  pointer-events: none;                   /* full-width <h1> overlaps the menu button — don't eat the tap */
  font-family: var(--font-display);
  color: var(--chrome-ink);
  font-size: 64px; line-height: .9; text-align: center; margin: 0;
}
.menu-btn {
  position: absolute; z-index: 1;
  left: 20px; top: calc(env(safe-area-inset-top) + 14px);
  width: 44px; height: 44px; border-radius: 50%;
  border: none; background: var(--chrome-deep);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; cursor: pointer; padding: 0;
}
/* The header full-bleeds to 100vw on desktop, so a plain left:20px would drop the
   button in the far viewport corner. Pin it to the CONTENT column's left inset
   instead (near the board) — matching the menu/splash button, which already sits
   there because its .splash is only column-wide. calc = column-left (50vw − half
   the column) + 20; clamped to 20px on a phone, where column == viewport. Only the
   in-header button needs this; the splash button is already column-framed. */
.header .menu-btn { left: max(20px, calc(50vw - var(--maxw, 430px) / 2 + 20px)); }
.menu-btn span { display: block; width: 16px; height: 2.5px; border-radius: 2px; background: #fff; }
/* Icon variant (the in-game header uses the pause glyph — the button's job there
   is "pause", not "menu"). fill:currentColor so it takes the button's white ink. */
.menu-btn svg { width: 19px; height: 19px; display: block; fill: currentColor; color: #fff; }
.menu-btn:active { transform: translateY(1px); }

/* ---- Status band: left headline · right live indicator (HEADER.md §5) ---- */
.statusband {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 16px 20px 10px; min-height: 44px;
}
/* ---- Hug + align the readout to the board ------------------------------------
   The shared rule for games whose card is a CAPPED, centred board (the square ones,
   where the board is narrower than the widened desktop column). Opt in with class
   "hug-board" on the game screen + a --board-w (the board's card width): the status
   band and board then centre together as one unit, and the band's text lines up with
   the board's left/right edges. Column-filling cards (most games — card on a
   full-width .board-wrap) don't need this; the band already spans the column.
   Desktop-only, so a phone's near-full-width board is left untouched. The board
   itself must be centred (`.board { max-width: var(--board-w); margin: 0 auto }`). */
@media (min-width: 820px), (min-width: 700px) and (min-height: 700px) {
  .hug-board .statusband {
    width: 100%;
    max-width: calc(var(--board-w) + 40px);   /* +40 = the band's 2×20px padding → its TEXT spans the board edges */
    margin: auto auto 0;                       /* top auto hugs the band down onto the board */
  }
  .hug-board .board-wrap { flex: 0 1 auto; margin-bottom: auto; }   /* size to the board; bottom auto centres the unit */
}
.sb-left { font-family: var(--font-display); color: var(--chrome-ink); font-size: 22px; line-height: 1; }
.sb-left .score-n { font-variant-numeric: tabular-nums; }
/* Tutorial instruction text sits in the status band's left cell (goal-banner /
   pairs-left / tri-sub, or written straight into .sb-left). It's small and dense,
   so it takes the readable secondary font. Scoped to .statusband.tut — a class
   present only while a tutorial runs — so the normal band (score, counts, rule)
   keeps the display face, and challenge goals (goal-banner without .tut) do too. */
.statusband.tut .sb-left { font-family: var(--font-secondary); font-weight: var(--font-secondary-weight); }
.sb-right { display: flex; align-items: center; gap: 10px; font-family: var(--font-display); color: var(--chrome-ink); font-size: 22px; }
#score-line { white-space: nowrap; }
#right-value { font-family: var(--font-display); color: var(--chrome-ink); font-size: 20px; font-variant-numeric: tabular-nums; }
/* goals progress dots */
.dots { display: flex; align-items: center; gap: 8px; }
.dots .dot { width: 18px; height: 18px; border-radius: 50%; background: var(--chrome); display: flex; align-items: center; justify-content: center; }
/* A dot getting its ✓ pops in rather than snapping — a small overshoot on the
   disc plus the tick scaling up from nothing. Fires once when the dot gains
   .done (games toggle the class on persistent elements, so it never re-runs on
   an unrelated HUD update). */
.dots .dot.done { background: #fff; animation: dot-pop .38s cubic-bezier(.34, 1.56, .64, 1); }
.dots .dot.done::after { content: ""; width: 11px; height: 11px; background-color: var(--chrome-ink); -webkit-mask: var(--fat-check) center / contain no-repeat; mask: var(--fat-check) center / contain no-repeat; animation: dot-check .34s ease .04s both; }
@keyframes dot-pop   { 0% { transform: scale(1); } 45% { transform: scale(1.4); } 100% { transform: scale(1); } }
@keyframes dot-check { 0% { opacity: 0; transform: scale(.2); } 60% { opacity: 1; transform: scale(1.18); } 100% { transform: scale(1); } }
/* tutorial: instruction centred over the board, not left-hung */
.statusband.tut { justify-content: center; }
.statusband.tut .sb-left { flex: 1; text-align: center; }
.statusband.tut #goal-banner { text-align: center; }

/* ---- Game-area control bar: the one active control (undo / reset / new / serve).
   Bottom of the board, near the thumb (HEADER.md §8). Small pill in chrome colour. ---- */
.control-bar { flex: 0 0 auto; padding: 14px 20px 16px; display: flex; }
.ctrl-btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 12px 16px; border: none; border-radius: 7px;
  background: var(--chrome); color: var(--chrome-ink);
  font-family: var(--font-display); font-size: 22px; line-height: 1; cursor: pointer;
}
.ctrl-btn:active { transform: translateY(1px); }
.ctrl-btn .ico { width: 18px; height: 18px; flex: none; }
/* full-width variant (Bento's serve), vs the default content-width pill (undo) */
.ctrl-btn.wide { width: 100%; justify-content: center; }

/* ---- Collapsible control bar ----------------------------------------------
   Secondary controls (check, reset, hint) were eating ~76px of a phone screen
   permanently, for buttons you touch a few times a round. With `.tuck` the bar
   parks itself off the bottom and leaves a small handle; tapping the handle
   slides it up, and pressing any button inside slides it back down so the board
   is never obstructed by something you've finished using.
   Opt-in per game: add `tuck` to the .control-bar. Games whose bar is part of
   the core loop (Bento's serve) must NOT use it. */
/* Anchored to the very bottom of the app and overlaid on the board (absolute, so
   it never shrinks the board). Its column is [tab, drawer]: closed, the drawer has
   zero height so only the tab shows at the bottom edge; open, the drawer grows and,
   because the whole bar is bottom-anchored, the stack rises — the tab is pulled UP
   with the drawer, whose bottom stays pinned to the screen bottom. */
.control-bar.tuck {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 5;
  padding: 0;
  flex-direction: column; align-items: center; gap: 0;
  pointer-events: none;                 /* only the tab + drawer catch taps; the board shows through the sides */
  --tab-h: 26px;                        /* keep in sync with .tuck-handle height */
  /* Slide the whole piece (tab + drawer) as one, like pulling a drawer out — the
     closed state leaves just the tab poking up; the rest sits below the fold. */
  transform: translateY(calc(100% - var(--tab-h)));
  transition: transform .3s cubic-bezier(.32, .72, 0, 1);
  /* No shadow — the fuzzy drop-shadow read as inconsistent with the flat game
     system; the chrome fill alone separates the drawer from the board. */
}
.control-bar.tuck > * { pointer-events: auto; }
.control-bar.tuck.open { transform: translateY(0); }
/* A tab on the bottom edge that reads as something to pull up: chrome-filled,
   rounded on the top corners only (the Figma "Rectangle 69"), chevron and no
   label, flipping when open. The tab stays IN FLOW so it reserves its own space
   and nothing sits underneath it. */
.control-bar.tuck .tuck-handle {
  width: 137px; height: 26px; flex: none;
  border: none; padding: 0; cursor: pointer;
  background: var(--chrome); color: #fff;   /* white chevron */
  border-radius: 60px 60px 0 0;
  display: flex; align-items: center; justify-content: center;
}
.control-bar.tuck .tuck-handle svg { width: 15px; height: 15px; display: block; transition: transform .22s ease; }
.control-bar.tuck.open .tuck-handle svg { transform: rotate(180deg); }
/* The drawer body sits directly under the tab — same chrome fill and slightly
   rounded top corners so the two read as one piece, the tab its pull-handle. It's
   full height at rest; the container's transform is what tucks it below the fold.
   Extra bottom padding clears the home indicator when it's pulled out. */
.control-bar.tuck .tuck-inner {
  width: 100%;
  display: flex; gap: 10px; justify-content: center; align-items: center;
  background: var(--chrome);
  border-radius: 14px 14px 0 0;
  padding: 14px 20px calc(14px + env(safe-area-inset-bottom));
}

/* ---- Full-screen chrome takeovers: pause + cleared/Done (TRANSITIONS §3–4) ---- */
#pause {
  position: fixed; inset: 0; z-index: 60;   /* fixed → covers the whole viewport incl. the bottom safe-area (absolute-in-#app left a strip) */
  background: var(--chrome);
  display: none; flex-direction: column; align-items: center; justify-content: center;
  padding: max(env(safe-area-inset-top), 24px) 35px env(safe-area-inset-bottom);
  gap: 20px;
}
#pause.active { display: flex; }
.takeover-title { font-family: var(--font-display); color: var(--chrome-ink); font-size: 92px; line-height: .9; text-align: center; margin: 0 0 4px; }
/* Circle buttons, not a stack of bars: three full-width rounded rectangles read
   as an app settings sheet. Each button is disc + caption as ONE tap target, so
   the ids and handlers the games already wire are untouched. Hierarchy is by
   FILL (the primary disc is white), never by size — every disc is 72px, so the
   row reads as a set of equal choices rather than one shouting.
   The disc carries the toy-button depth: a hard bottom edge that COMPRESSES on
   press (Bento's serve button language), which is what sells it as a game
   control rather than a flat app tap-target. */
.takeover-btns {
  /* Figma "paused": 115px discs, 24px between columns, 30px between rows.
     --cols drives the responsive cap below: the 2x2 (254px) fits every phone at
     full size, but three across wants 393px and NO phone has that once #pause's
     35px padding is taken off (428 is still 35px short), so the 3-up screens
     scale their discs down to fit rather than wrapping 2+1. */
  --cols: 3;
  --gapx: 24px;                       /* column gap — ALSO feeds the disc-width calc below */
  width: 100%; max-width: 393px;      /* 3 x 115 + 2 x 24 */
  display: flex; flex-wrap: wrap; gap: 30px var(--gapx);
  justify-content: center; align-items: flex-start;
}
/* Two columns. Any row that isn't three-across wants this — a 2-up, and a 3- or
   4-button row that should grid rather than squeeze. Without it a 2-up keeps the
   default --cols:3 and its discs come out 86px instead of 115, which is what
   happened to the nine pause screens when "new board" was removed from them. */
.takeover-btns.cols-2 { --cols: 2; max-width: 254px; }
/* Cleared screen only: a wider gap than the pause rows, because its two captions
   ("new board", "end shuffle") are the longest in the arcade and overflow their
   discs. max-width tracks the gap (2 x 115 + 36) or the row wraps. */
.overlay .takeover-btns.cols-2 { --gapx: 36px; max-width: 266px; }
.takeover-btn {
  width: min(115px, calc((100% - (var(--cols) - 1) * var(--gapx)) / var(--cols)));
  padding: 0; border: none; background: none;
  display: flex; flex-direction: column; align-items: center; gap: 16px;
  font-family: var(--font-display); color: var(--chrome-ink); cursor: pointer;
}
/* Disc = --chrome-deep (the same tone as the header menu button), pressed edge =
   --chrome-shadow, a deeper shade of the same hue set per game. Both come from
   the palette — never a computed darkening, which lands on muddy off-palette
   colours. Icons are white on top: low contrast by measurement on the lighter
   hues (1.49:1 on light blue, 1.93:1 on yellow) and deliberately so — the caption
   under every disc is what says what the button does, so nothing depends on
   reading the glyph. The rgba fallback only applies if a game forgets the var.
   Sized off the button (not a fixed px) so it shrinks with it on a narrow phone. */
.tk-disc {
  width: 100%; aspect-ratio: 1; border-radius: 50%;
  background: var(--chrome-deep); color: #fff;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 5px 0 var(--chrome-shadow, rgba(0, 0, 0, .18));
  transition: transform .06s, box-shadow .06s;
}
/* The primary's disc IS white, so its icon has to be dark — white on white would
   be nothing at all. */
.takeover-btn.primary .tk-disc { background: #fff; color: var(--chrome-ink); }
.tk-disc svg { width: 44%; height: 44%; display: block; fill: currentColor; }
/* One line always. A caption wider than its disc (e.g. "new board" at 32px)
   overflows symmetrically into the 24px column gap rather than wrapping and
   making one button taller than its neighbours. */
.tk-cap { font-size: 32px; line-height: 32px; text-align: center; white-space: nowrap; }
.takeover-btn:active .tk-disc { transform: translateY(5px); box-shadow: 0 0 0 var(--chrome-shadow, rgba(0, 0, 0, .18)); }

/* ---- Shuffle "time check" -------------------------------------------------
   A calm, full-screen interrupt shown when a timed Shuffle session reaches the
   interval the player chose (see the hub picker + watchShuffleTimer in arcade.js).
   Same language as #pause — chrome takeover, big title, disc buttons — so it reads
   as an intentional beat, not an error. z-index sits above the game overlay/pause
   (60) AND the arrival splash (200), since the deadline can land during a page
   swap. It lives outside #app but reads the game's :root chrome vars. */
#arcade-timecheck {
  position: fixed; inset: 0; z-index: 300;
  /* Dressed as the HOME screen (dark charcoal + white), NOT the current game's
     chrome — the time check is a "step out of the game" moment, so it should read as
     leaving, not as part of whatever you're playing. */
  background: #2c2c2a; color: #fff;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: max(env(safe-area-inset-top), 24px) 35px env(safe-area-inset-bottom);
  gap: 14px;
  /* Parked below the screen; rises up into view and later wipes up-and-off, so it
     never hard-cuts — same upward motion as .overlay / the splash. */
  transform: translateY(100%);
  transition: transform .42s cubic-bezier(.76, 0, .24, 1);
  pointer-events: none;
}
#arcade-timecheck.show { transform: translateY(0); pointer-events: auto; }   /* wiped up into view */
#arcade-timecheck.wipe-off { transform: translateY(-100%); }                  /* continues up and off → reveals the game */
#arcade-timecheck.no-anim { transition: none; }                              /* reset to parked without a visible slide */
#arcade-timecheck .tc-emoji { font-size: 60px; line-height: 1; }
#arcade-timecheck .tc-sub {
  font-family: var(--font-display); color: #fff;
  font-size: 24px; line-height: 1.2; text-align: center; opacity: .85; margin: 0 0 10px;
}
/* Title + button captions are white too (they default to --chrome-ink, unreadable
   on the dark takeover). The disc buttons keep their chrome fills — a coloured/white
   disc reads fine on charcoal, and the captions below them carry the meaning. */
#arcade-timecheck .takeover-title,
#arcade-timecheck .tk-cap { color: #fff; }

/* The win/round-over overlay, dressed as the full-screen "cleared!" Done screen.
   Keeps the shared .overlay > .dialog structure (and every #ov-* id the games drive). */
.overlay {
  position: fixed; inset: 0;           /* fixed → covers the whole viewport incl. the bottom safe-area; parked state hides off-viewport (body overflow:hidden) */
  display: flex;                      /* always laid out; parked off-screen below */
  align-items: center; justify-content: center;
  padding: max(env(safe-area-inset-top), 24px) 35px env(safe-area-inset-bottom);
  z-index: 60;
  transform: translateY(100%);        /* idle: just below the screen */
  transition: transform .5s cubic-bezier(.76, 0, .24, 1);
  pointer-events: none;
}
/* The colour fill lives on a separate layer, NOT on the transformed element, and
   the dialog sits in its own stacking context above it — mirroring the splash
   (.splash-pink / .splash-center), the one wipe whose text demonstrably rides the
   transform. With the fill painted directly on .overlay, the dialog text dropped
   at frame 0 of the wipe-off while the colour slid (OVERLAY-TEXT-BUG.md). */
.overlay::before { content: ""; position: absolute; inset: 0; background: var(--chrome); z-index: 0; }
.overlay.active { transform: translateY(0); pointer-events: auto; }   /* wiped up: colour covers, cleared screen shown */
.overlay.wipe-off { transform: translateY(-100%); }                    /* wiping off upward → reveals the next level */
.overlay.no-anim { transition: none; }                                 /* reset the parked position without a visible slide (see Arcade.wipeToNext) */
.overlay .dialog {
  position: relative; z-index: 1;     /* above the ::before colour layer (a positioned z:0 sibling paints over in-flow content) */
  background: none; box-shadow: none;
  width: 100%; max-width: 360px; padding: 0;
  display: flex; flex-direction: column; align-items: center; text-align: center;
}
.overlay .dialog .big-emoji { display: none; }
.overlay .dialog h2 { font-family: var(--font-display); color: var(--chrome-ink); font-size: 92px; line-height: .9; margin: 0 0 8px; }
/* Cleared screen = title + a one-line result summary + action buttons. The score
   and its label stay hidden (a clean headline, per the user's call); the big-emoji
   is already hidden above. */
.overlay .dialog .result-label,
.overlay .dialog .result { display: none; }
/* The summary line under the title ("Cleared with 4 top-ups.", "Home in 6 slides.",
   "Tidied in 8 swaps.", etc. — every game already writes one). It was hidden in the
   redesign because Hello Notie was illegible small; restored now on the readable
   secondary font, which is what the earlier removal was waiting on. */
.overlay .dialog p {
  font-family: var(--font-secondary);
  font-weight: var(--font-secondary-weight);
  color: var(--chrome-ink);
  opacity: .82;
  font-size: 18px;
  line-height: 1.4;
  max-width: 300px;
  margin: 4px auto 0;
  text-align: center;
}
/* The tutorial's finish screen has no summary line — it's just "got it!" and Play.
   Collapsing an EMPTY p here means those 16 tutorialComplete()s can simply write ""
   rather than each toggling display (and each remembering to restore it for a real
   win, which is the bit that would rot). */
.overlay .dialog p:empty { display: none; }
/* The cleared screen's buttons are the same icon discs as the pause screen (they
   were full-width bars, which read as an app sheet); a little breathing room
   under the title. */
.overlay .dialog .takeover-btns { margin-top: 18px; }

/* ---- Mode-select splash (Figma "mode selection") ---- */
.splash {
  position: relative;
  justify-content: center; align-items: center;
  padding: 0 35px; gap: 0;
  background: var(--chrome-body);
  overflow: visible;   /* let the chrome block/wave full-bleed on desktop (they're 100vw); no-op on a phone */
}
/* The chrome block + wave full-bleed to the device edges (like the in-game header),
   while the title + mode cards stay in the centred .splash-center column. 100vw
   centred on the splash → on a phone that's exactly the column (a no-op). */
.splash-pink { position: absolute; top: 0; left: 50%; width: 100vw; transform: translateX(-50%); height: 68%; background: var(--chrome); z-index: 0; }
.splash-wave { position: absolute; top: 68%; left: 50%; width: 100vw; height: 40px; transform: translateX(-50%) translateY(-1px); z-index: 0; display: block; }
.splash-center { position: relative; z-index: 1; width: 100%; max-width: 358px; display: flex; flex-direction: column; align-items: center; gap: 24px; }
.splash-title { font-family: var(--font-display); color: var(--chrome-ink); font-size: 92px; line-height: .9; text-align: center; margin: 0; }
.mode-cards { display: flex; gap: 8px; width: 100%; }
.mode-card {
  flex: 1; background: var(--card); border: none; border-radius: 7px;
  padding: 24px 0; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
.mode-card:active { transform: translateY(1px); }
/* (No "last played" highlight — the games still toggle a .current class on the
   remembered tier, but it's deliberately left unstyled; the ring was unwanted.) */
.mode-emoji { height: 60px; display: flex; align-items: center; justify-content: center; font-size: 40px; line-height: 1; }
.mode-label { font-family: var(--font-display); font-size: 22px; color: var(--chrome-ink); }
/* Difficulty + mode cards show an illustration instead of an emoji glyph — one place
   for every tiered game (data-difficulty) and mode game (data-mode zen/goals). The
   emoji stays in the markup but is hidden (font-size:0); the image rides as a
   background so no per-game HTML edit is needed. url() resolves from arcade.css
   (shared/), so ../themes points at the root themes/ folder. (The mode-timed.png
   rule went with Timed mode's removal, July 2026.) */
.mode-card[data-difficulty] .mode-emoji,
.mode-card[data-mode] .mode-emoji {
  font-size: 0; width: 72px;
  background-repeat: no-repeat; background-position: center; background-size: contain;
}
.mode-card[data-difficulty="easy"]   .mode-emoji { background-image: url(../themes/difficulty-easy.png); }
.mode-card[data-difficulty="medium"] .mode-emoji { background-image: url(../themes/difficulty-medium.png); }
.mode-card[data-difficulty="hard"]   .mode-emoji { background-image: url(../themes/difficulty-hard.png); }
.mode-card[data-mode="zen"]          .mode-emoji { background-image: url(../themes/mode-zen.png); }
.mode-card[data-mode="goals"]        .mode-emoji { background-image: url(../themes/mode-challenge.png); }
.tut-link {
  position: absolute; z-index: 1; left: 0; right: 0;
  bottom: calc(env(safe-area-inset-bottom) + 32px);
  text-align: center; background: none; border: none; cursor: pointer;
  font-family: var(--font-display); font-size: 22px; color: var(--chrome-ink);
}

/* ---- Screen-change wipe (Arcade.showScreen) ----
   Every screen change in a menu game wipes instead of hard-cutting: the outgoing
   screen slides up and off to reveal the incoming one — the same upward motion as
   the shuffle intro's Start. Positioned within #app (position:relative) so it matches
   the app column on desktop; a .splash's own .splash-pink/wave still full-bleed via
   their 100vw. Opaque background so a game-screen cover (transparent by default)
   doesn't show the incoming screen through it mid-slide. z below pause/overlay (60). */
.screen.screen-wipe {
  position: absolute; inset: 0; z-index: 50;
  background: var(--chrome-body);
  transform: translateY(0);
  transition: transform .45s cubic-bezier(.76, 0, .24, 1);
}
.screen.screen-wipe-off { transform: translateY(-100%); }

/* ---- Shuffle "now playing" splash (Arcade.intro) ----
   Shown when Shuffle lands on a game, in the mode-select splash's own visual
   language (chrome pink block + wave + centred title) instead of the old centred
   card on a dark scrim — so arriving at a shuffled game reads as the same screen
   family as choosing a mode. It's a fixed takeover (the game is dealt underneath;
   Start reveals it), where the mode-select .splash is an in-page .screen; the
   inner .splash-pink/.splash-wave/.splash-center/.splash-title are reused as-is. */
.arc-intro {
  position: fixed; inset: 0; z-index: 200;
  display: none; flex-direction: column; align-items: center; justify-content: center;
  padding: 0 35px;
  background: var(--chrome-body);
  overflow: hidden;
  /* Wipe, don't fade (TRANSITIONS §0/§8): the splash rises up into view over the
     board — and on Start rises off the top to reveal it — matching the arcade's
     everything-wipes-upward language. .open gates display so the parked state
     never adds body scroll; .show/.wipe-off drive the transform. */
  transform: translateY(100%);
  transition: transform .45s cubic-bezier(.76, 0, .24, 1);
}
.arc-intro.open { display: flex; }
.arc-intro.show { transform: translateY(0); }         /* wipe up into view */
.arc-intro.wipe-off { transform: translateY(-100%); } /* wipe up and off → board revealed */
.arc-intro .splash-center { gap: 16px; }
/* lowercase to match the lowercase wordmark brand (game names are passed capitalised) */
.arc-intro .splash-title { text-transform: lowercase; }
.arc-intro-emoji { font-size: 54px; line-height: 1; }
/* The game's illustration in a circle tinted with its homepage well colour (bg set
   inline per game). font-size:0 kills the emoji-fallback glyph when art is present. */
.arc-intro-emoji.has-art { width: 128px; height: 128px; border-radius: 50%; font-size: 0; display: flex; align-items: center; justify-content: center; }
.arc-intro-emoji.has-art img { width: 78%; height: 78%; object-fit: contain; display: block; }
.arc-intro-desc { margin: 0; max-width: 300px; font-family: var(--font-secondary); font-weight: var(--font-secondary-weight); color: var(--chrome-ink); opacity: .82; font-size: 20px; line-height: 1.4; text-align: center; }
/* Start is the same white play disc as resume — it's the "go" button of the
   shuffle splash, and it was the last full-width bar in the arcade. Two notes:
   it sits on its own rather than in a .takeover-btns row, so it sets its own
   width (the inherited rule sizes off --cols, which only exists on the row);
   and it carries NO caption, unlike every other disc. A caption landed inside
   the splash wave — and nudging it clear would only break again on a game with
   a longer one-line rule. Captions exist to tell several discs apart; this
   screen has one action, on a page that already names the game. */
.arc-intro-btn { width: 115px; margin-top: 14px; }
/* When shuffle deals a game you've never opened, the splash also shows `.tut-link` —
   the very same "or play the tutorial" control the menu games carry on their own
   splashes (see its rule above). Reusing it rather than inventing a link is the whole
   point: this screen is a splash like theirs, so it should speak their language. */
