html.theme-copper {
  --stone-light: var(--copper-light);
  --stone: var(--copper);
  --stone-dark: var(--copper-dark);
}
html.theme-bronze {
  --stone-light: var(--bronze-light);
  --stone: var(--bronze);
  --stone-dark: var(--bronze-dark);
}
:root {
  --paper: #f5efe3;
  --paper-deep: #e7ddca;
  --ink: #1e2a25;
  --muted: #756f64;
  --wood: #d97848;
  --wood-dark: #ad4f2b;
  --wood-light: #eea06f;
  /* Brick finish: silver is the active choice (bright/cool to pop against the warm paper
     background); copper and bronze kept below as swappable backups.
     To switch, add class="theme-copper" or class="theme-bronze" to the <html> tag. */
  --silver-light: #eef1f3;
  --silver: #a9b4bc;
  --silver-dark: #626d76;
  --copper-light: #f2c39a;
  --copper: #c17d4a;
  --copper-dark: #7a4a24;
  --bronze-light: #e0b877;
  --bronze: #8c6b32;
  --bronze-dark: #574018;
  --stone-light: var(--silver-light);
  --stone: var(--silver);
  --stone-dark: var(--silver-dark);
  --gold-light: #fff0a3;
  --gold: #d9a823;
  --gold-dark: #9d6d08;
  --platinum-light: #f4f6f8;
  --platinum: #cdd4da;
  --platinum-dark: #8e969f;
  --grid: #d8cdb9;
  --panel: rgba(255, 252, 246, 0.72);
  --chest-frame: #875332;
  --chest-wall: #a36c43;
  /* Board geometry: --wall (board border), --pad (board padding = .board-fx inset), --gap (cell gap
     = .board-fx gap), --seam (extra margin at the 3x3 box boundaries), --shell-pad (.app-shell side
     padding). --cell is what's left of the viewport after two shell pads, two walls, two board pads,
     eight cell gaps and two seams, split across 9 cells, capped at 52px. Baked to look code "415a"
     (wall factor 4, the thinnest): the <= 390px query below reassigns only --shell-pad and the shell
     gap, since W=4 already fits 360px. A user-chosen look (look.js, Advanced settings) overrides these
     four plus the colour variables below, inline on <html>, which beats this query at any width. */
  --shell-pad: 20px;
  --wall: 2px;
  --pad: 4px;
  --gap: 1px;
  --seam: 2px;
  --cell: min(calc((100vw - 2 * var(--shell-pad) - 2 * var(--wall) - 2 * var(--pad) - 8 * var(--gap) - 2 * var(--seam)) / 9), 52px);
  /* Stone (silver) surface: edge colour and the two white-gloss alphas on .cell::after. A user-chosen
     look (look.js) overrides these and the pocket variables below inline on <html>; the values here are
     the baked defaults ("415a": today's shade). */
  --stone-edge: rgba(84, 74, 66, .62);
  --stone-gloss: .72;
  --stone-gloss-band: .9;
  /* H-pockets: dark/mid fill the H (areas 1,3-7,9 minus the light column), light fills areas 2 and 8
     and never moves. `.cell.area-*` maps to these; a chosen look overrides them the same way as the stone. */
  --pocket-dark-top: #71472f;
  --pocket-dark-bottom: #4d2d1f;
  --pocket-mid-top: #86624d;
  --pocket-mid-bottom: #674939;
  --pocket-light-top: #c39a72;
  --pocket-light-bottom: #98704f;
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  color: var(--ink);
  background: var(--paper);
  font-synthesis: none;
}

* { box-sizing: border-box; }

html, body { margin: 0; min-height: 100%; overscroll-behavior: none; }

body {
  min-height: 100dvh;
  background:
    radial-gradient(circle at 82% 12%, rgba(255,255,255,.64), transparent 32%),
    repeating-linear-gradient(45deg, transparent 0 27px, rgba(39,78,54,.038) 28px 29px),
    #dce8df;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}

button { font: inherit; color: inherit; }

.app-shell {
  position: relative;
  z-index: 1;
  width: min(100%, 558px); /* content 518px = board's 52px-cell width at the default --gap/--pad/--wall (9*52 + 8*3 + 2*8 + 2*5), so board/topbar/rack share both edges */
  min-height: 100dvh;
  margin: 0 auto;
  padding-block: max(18px, env(safe-area-inset-top)) max(18px, env(safe-area-inset-bottom));
  padding-inline: var(--shell-pad);
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* Header (1.7D): the brand alone, centred; the streak and score cards moved to .status-row below it
   (before 1.7D they shared the header's row with the brand, which was cramped on phones). */
.topbar { position: relative; z-index: 30; display: grid; justify-items: center; }
.brand { min-width: 0; width: min(100%, 220px); text-align: center; }
.eyebrow { margin: 0 0 3px; color: var(--wood-dark); font-size: 10px; letter-spacing: .2em; font-weight: 800; }
/* Wordmark: the board's H (inline SVG, geometry from scripts/icons.mjs) + B L K as solid wide letters with the
   stone finish. The H's pockets and wall are bound to the --pocket-* / --chest-wall custom properties and the
   letters to --stone-*, so it follows the user's look. The mark is 220 x 40 units and the brand block at most
   220px wide, so the wordmark is 220px on every shell wider than that. The hero line under it is plain
   centred text at its natural width (1.7D; before that an SVG stretched to the wordmark's width). */
.brand h1 { margin: 0; line-height: 0; }
.wordmark { display: block; width: 100%; height: auto; }
.hero-text { margin: 6px 0 0; color: var(--gold-dark); font-size: 17px; line-height: 1.1; font-weight: 750; letter-spacing: .02em; white-space: nowrap; }
/* Status row (1.7D, 2.1A): score and best as two like cards (label over number) plus the daily badge
   (2.1A, label-free, optically as tall as a label + number stack) spread on one line, pulled up a
   little against the shell gap so it reads as part of the header. app.js sets --score-digits on the
   row from the longer of score / best, so the two numbers share one size. */
.status-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-top: -6px; padding-inline: clamp(4px, 4%, 28px); }
.score-card, .best-card { text-align: center; min-width: 0; }
.score-card span, .best-card span { display: block; color: var(--muted); font-size: 15px; letter-spacing: .16em; font-weight: 800; }
.score-card strong, .best-card strong {
  display: block;
  font-size: clamp(18px, 33px - (var(--score-digits, 3) - 3) * 2.6px, 33px);
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
  white-space: nowrap;
}

.play-area { position: relative; isolation: isolate; display: grid; row-gap: 6px; place-items: center; }

/* Rotation meter: a stretched grid row above the board (board's own width sets the track). */
.rotation-meter { justify-self: stretch; display: flex; align-items: center; gap: 9px; width: 100%; padding: 2px 1px; }
/* 2.1B: the icon is a chromeless button (free orientation reset). The hit area is padded out to 34px
   with negative margins so the meter row keeps its 28px height; the spin is one short clockwise turn. */
.rotation-reset { flex: none; display: grid; place-items: center; width: 34px; height: 34px; margin: -6px; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--gold-dark); cursor: pointer; -webkit-tap-highlight-color: transparent; }
.rotation-reset:focus-visible { outline: 2px solid var(--wood-dark); outline-offset: 1px; }
.rotation-icon { flex: none; width: 22px; height: 22px; color: inherit; }
.rotation-reset.spin .rotation-icon { animation: rotation-reset-spin .4s ease; }
@keyframes rotation-reset-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
.rotation-bar { flex: 1; min-width: 0; display: flex; align-items: center; gap: 2px; }
.rotation-bar .seg { flex: 1; height: 9px; min-width: 2px; border-radius: 999px; background: rgba(117, 111, 100, .22); box-shadow: inset 0 1px rgba(35, 17, 8, .16); }
.rotation-bar .seg.on {
  background: linear-gradient(145deg, var(--gold-light), var(--gold) 55%, var(--gold-dark));
  box-shadow: inset 0 1px rgba(255,255,255,.5), 0 0 0 1px rgba(159,111,81,.2);
}
.rotation-count { flex: none; min-width: 30px; text-align: center; font-size: 18px; line-height: 1; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--ink); }
.rotation-count.pulse { animation: rotation-count-pulse .35s ease; }
@keyframes rotation-count-pulse {
  0% { transform: scale(1); color: var(--ink); }
  40% { transform: scale(1.35); color: var(--gold-dark); }
  100% { transform: scale(1); color: var(--ink); }
}
/* "No points" flash: the counter blinks red a few times. */
.rotation-meter.flash .rotation-count { animation: rotation-count-flash .9s ease; }
@keyframes rotation-count-flash {
  0%, 100% { transform: scale(1); color: var(--ink); }
  15%, 45%, 75% { transform: scale(1.3); color: #c0392b; }
  30%, 60% { transform: scale(1); color: var(--ink); }
}

.board {
  position: relative;
  display: grid;
  grid-template-columns: repeat(9, var(--cell));
  grid-template-rows: repeat(9, var(--cell));
  gap: var(--gap);
  padding: var(--pad);
  border: var(--wall) solid #6f4228;
  border-radius: 13px;
  background:
    linear-gradient(115deg, rgba(255,255,255,.2), transparent 25% 72%, rgba(65,31,13,.13)),
    repeating-linear-gradient(88deg, transparent 0 38px, rgba(68,34,17,.08) 39px, transparent 41px),
    var(--chest-wall);
  box-shadow:
    0 14px 30px rgba(61, 34, 18, .22),
    inset 0 2px rgba(255, 235, 207, .42),
    inset 0 -3px rgba(54, 28, 14, .32),
    0 0 0 1px rgba(76, 43, 24, .3);
  touch-action: none;
  /* Own paint layer: a drop re-rasters only the board, not the patterned page background under it. */
  contain: paint;
  will-change: transform;
}
.cell {
  position: relative;
  z-index: 0;
  border: 1px solid rgba(61, 32, 18, .24);
  border-radius: 2px;
  background:
    linear-gradient(145deg, rgba(255,255,255,.13), transparent 35%),
    linear-gradient(160deg, var(--pocket-top), var(--pocket-bottom));
  box-shadow:
    inset 0 6px 7px rgba(35, 17, 8, .38),
    inset 5px 0 6px rgba(41, 21, 10, .23),
    inset 0 -1px rgba(255, 230, 196, .2),
    0 1px rgba(255, 222, 181, .28);
  overflow: visible;
}
.cell.area-dark { --pocket-top: var(--pocket-dark-top); --pocket-bottom: var(--pocket-dark-bottom); }
.cell.area-mid { --pocket-top: var(--pocket-mid-top); --pocket-bottom: var(--pocket-mid-bottom); }
.cell.area-light { --pocket-top: var(--pocket-light-top); --pocket-bottom: var(--pocket-light-bottom); }
.cell:nth-child(3n):not(:nth-child(9n)) { margin-right: var(--seam); }
.cell:nth-child(n+19):nth-child(-n+27), .cell:nth-child(n+46):nth-child(-n+54) { margin-bottom: var(--seam); }
.cell::after {
  content: "";
  position: absolute;
  inset: .5px;
  z-index: 2;
  border: 1px solid var(--stone-edge);
  border-radius: 4px;
  background:
    repeating-linear-gradient(115deg, rgba(255,255,255,.16) 0 1px, transparent 1px 3px),
    linear-gradient(110deg, transparent 24%, rgba(255,255,255,var(--stone-gloss-band)) 44%, transparent 62%) 170% 0 / 220% 100% no-repeat,
    linear-gradient(132deg, rgba(255,255,255,var(--stone-gloss)), transparent 27%),
    linear-gradient(145deg, var(--stone-light), var(--stone) 54%, var(--stone-dark));
  box-shadow:
    inset 0 2px rgba(255,255,255,.5),
    inset 0 -2px rgba(40, 44, 48, .32),
    0 3px 4px rgba(35, 19, 10, .38),
    0 0 0 1px rgba(159, 111, 81, .28);
  opacity: 0;
  transform: scale(.88);
  /* No transition here on purpose: preview highlights toggle every frame during a drag and a
     transitioning ::after forces repaint + re-raster of these gradient-heavy cells for ~120ms each time. */
  pointer-events: none;
}
.cell.filled { z-index: 2; }
.cell.filled::after {
  opacity: 1;
  transform: scale(1);
}
/* Gold plane: one gold brick per cell, each in its own composited layer, stacked over the cells (built
   by app.js as #board > .board-fx). The clear preview and the clear pop-out are class toggles here that
   only change opacity/transform, so neither repaints the board. Before this they were paint changes on
   .cell::after: every gold on/off re-rastered the gradient-heavy board layer under the moving ghost,
   and the pop-out promoted each cleared cell to a new layer at the drop and tore them all down (with
   another board re-raster) when it ended — the stall at the end of every clear on phones. */
.board-fx {
  position: absolute;
  inset: var(--pad); /* = .board padding, so the tracks sit exactly on the cells */
  z-index: 3;
  display: grid;
  grid-template-columns: repeat(9, var(--cell));
  grid-template-rows: repeat(9, var(--cell));
  gap: var(--gap);
  pointer-events: none;
}
.board-fx i {
  position: relative;
  opacity: 0;
  will-change: transform, opacity;
}
.board-fx i:nth-child(3n):not(:nth-child(9n)) { margin-right: var(--seam); }
.board-fx i:nth-child(n+19):nth-child(-n+27), .board-fx i:nth-child(n+46):nth-child(-n+54) { margin-bottom: var(--seam); }
.board-fx i::before {
  content: "";
  position: absolute;
  inset: 1.5px; /* cell border 1px + the .5px inset of .cell::after */
  border: 1px solid rgba(115, 75, 4, .58);
  border-radius: 4px;
  background:
    linear-gradient(110deg, transparent 24%, rgba(255,255,255,.88) 44%, transparent 62%) 170% 0 / 220% 100% no-repeat,
    linear-gradient(132deg, rgba(255,255,255,.72), transparent 29%),
    linear-gradient(145deg, var(--gold-light), var(--gold) 55%, var(--gold-dark));
  box-shadow: inset 0 2px rgba(255,255,255,.62), inset 0 -2px rgba(112,72,4,.24), 0 3px 5px rgba(55,31,7,.36), 0 0 0 1px rgba(255,216,83,.35);
}
.board-fx i.preview { opacity: 1; }
/* `both`: held at full gold through the stagger delay, gone after; the class is dropped by a timer. */
.board-fx i.clearing { animation: clear-pop .18s cubic-bezier(.3,.8,.35,1) var(--clear-delay, 0ms) both; }
/* Shine: a diagonal light band on ::before whose *opacity* is animated. Opacity is compositor-only;
   the old metal-shine moved background-position, which repainted the brick every frame for ~1 s. */
.cell::before {
  content: "";
  position: absolute;
  inset: .5px;
  z-index: 3;
  border-radius: 4px;
  background: linear-gradient(110deg, transparent 30%, rgba(255,255,255,.85) 48%, transparent 66%);
  opacity: 0;
  pointer-events: none;
}
@keyframes glint {
  0% { opacity: 0; }
  35% { opacity: 1; }
  100% { opacity: 0; }
}
/* Landing: the ghost snaps onto the board and settles as one layer, then its bricks give way to bare
   glint bands that fade over the (already filled) cells underneath. Brick, gold twin and band are
   three stacked layers per block (see .drag-ghost .piece-block > i), so the hand-over is an opacity
   switch, not a repaint. A block whose cell is being cleared keeps its gold through the landing and
   shows no band: the gold plane's pop-out is what plays there. */
.drag-ghost.landing { animation: stone-drop .16s cubic-bezier(.22,.84,.42,1) both; }
.drag-ghost.landing.glint { animation: glint .7s ease-out both; }
.drag-ghost.landing.glint .piece-block > .brick,
.drag-ghost.landing.glint .piece-block > .gold { opacity: 0; }
.drag-ghost.landing.glint .piece-block > .band { opacity: 1; }
.drag-ghost.landing.glint .piece-block.cleared > .band { opacity: 0; }
@keyframes stone-drop {
  0% { opacity: .88; transform: translateY(-7px) scale(1.035); }
  72% { opacity: 1; transform: translateY(1px) scale(.985); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes clear-pop {
  0% { opacity: 1; transform: scale(1); }
  42% { opacity: 1; transform: scale(1.08); }
  100% { opacity: 0; transform: scale(.1); }
}

/* #toast lives directly under body (index.html), not inside .app-shell/.topbar: both of those are
   position:relative + z-index elements, i.e. their own stacking contexts, which would otherwise cap
   the toast's painting order below a .dialog-backdrop (z-index 40) no matter how high its own
   z-index went. Fixed + a viewport-relative position keeps it visually where the old absolute/
   topbar-centred toast sat (roughly the header's vertical middle, horizontally centred). */
.toast {
  position: fixed;
  top: calc(max(18px, env(safe-area-inset-top)) + 29px);
  left: 50%;
  translate: -50% -50%;
  padding: 9px 14px;
  border-radius: 999px;
  background: var(--ink);
  color: white;
  font-weight: 800;
  font-size: 13px;
  white-space: nowrap; /* left: 50% leaves a fixed element only half the viewport to shrink-to-fit into; one line, always centred */
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none;
  transition: .2s ease;
  z-index: 80;
  isolation: isolate;
  backface-visibility: hidden;
  will-change: opacity, transform;
}
.toast.show { opacity: 1; transform: translateY(0); }

/* Score popup: no pill background, floats at the placed piece's board cell. */
.score-pop {
  --pop-size: 30px;
  --pop-color: var(--gold-dark);
  --pop-hold: 900ms;
  --pop-tag-size: var(--pop-size);
  --pop-tag-color: var(--platinum);
  position: fixed;
  left: 50%; top: 50%;
  translate: -50% -50%;
  pointer-events: none;
  z-index: 65;
  opacity: 0;
  isolation: isolate;
  will-change: opacity, transform;
}
/* The rebuilt part (points, x-factor tag, ring). */
.score-pop-main {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
.score-pop-points {
  font-size: var(--pop-size);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -.03em;
  color: var(--pop-color);
  text-shadow:
    0 1px 0 rgba(255, 255, 255, .55),
    0 2px 6px rgba(30, 42, 37, .45),
    0 0 1px rgba(30, 42, 37, .6);
}
.score-pop-multiplier {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--pop-tag-size);
  font-weight: 900;
  line-height: 1;
  color: var(--pop-tag-color);
  -webkit-text-stroke: .5px rgba(75, 82, 90, .55);
  text-shadow:
    0 1px 0 rgba(255, 255, 255, .85),
    0 2px 6px rgba(70, 78, 86, .5),
    0 0 10px rgba(205, 212, 218, .6);
}
.score-pop-ring {
  position: absolute;
  left: 50%; top: 50%;
  width: 60px; height: 60px;
  margin: -30px 0 0 -30px;
  border-radius: 999px;
  border: 3px solid var(--gold);
  box-shadow: 0 0 12px rgba(217, 168, 35, .5);
  opacity: 0;
  z-index: -1;
}
/* New BEST celebration: one fixed layer, transform/opacity only, never catches the pointer. */
.best-pop {
  position: fixed;
  left: 50%; top: 42%;
  translate: -50% -50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  pointer-events: none;
  z-index: 70;
  opacity: 0;
  isolation: isolate;
  will-change: opacity, transform;
}
.best-pop-title {
  font-size: 52px;
  font-weight: 900;
  line-height: 1;
  letter-spacing: -.04em;
  white-space: nowrap;
  color: var(--gold);
  -webkit-text-stroke: 1px rgba(157, 109, 8, .7);
  text-shadow:
    0 2px 0 rgba(255, 255, 255, .7),
    0 4px 14px rgba(30, 42, 37, .45),
    0 0 24px rgba(217, 168, 35, .55);
}
.best-pop-score {
  font-size: 30px;
  font-weight: 800;
  line-height: 1;
  color: var(--platinum-light);
  -webkit-text-stroke: .5px rgba(75, 82, 90, .6);
  text-shadow: 0 1px 0 rgba(255, 255, 255, .85), 0 2px 8px rgba(70, 78, 86, .55);
}
.best-pop-burst { position: absolute; left: 50%; top: 50%; width: 0; height: 0; z-index: -1; }
.best-pop-burst i {
  position: absolute;
  left: -4px; top: -6px;
  width: 8px; height: 12px;
  border-radius: 2px;
  opacity: 0;
  will-change: transform, opacity;
}
.best-pop.show { animation: best-pop-anim 2s cubic-bezier(.22, .9, .3, 1) both; }
.best-pop.show .best-pop-burst i { animation: best-confetti 1.7s var(--delay, 0ms) cubic-bezier(.15, .7, .4, 1) both; }
@keyframes best-pop-anim {
  0% { opacity: 0; transform: scale(.5); }
  12% { opacity: 1; transform: scale(1.12); }
  22% { transform: scale(1); }
  80% { opacity: 1; transform: translateY(0) scale(1); }
  100% { opacity: 0; transform: translateY(-20px) scale(.96); }
}
@keyframes best-confetti {
  0% { opacity: 1; transform: translate3d(0, 0, 0) rotate(0deg) scale(.6); }
  20% { opacity: 1; }
  100% { opacity: 0; transform: translate3d(var(--dx), var(--dy), 0) rotate(var(--rot)) scale(1); }
}
/* Medal event (2.1B): the medal grows in and spins about its vertical axis over the board centre, the award text
   beneath; the whole thing holds ~2.5 s and fades. Transform/opacity only, in the same fixed slot as NEW BEST
   (which plays first when both land on one drop; app.js sequences them). */
.medal-pop {
  position: fixed;
  left: 50%; top: 42%;
  translate: -50% -50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: max-content;
  max-width: calc(100vw - 2 * var(--shell-pad));
  pointer-events: none;
  z-index: 70;
  opacity: 0;
  isolation: isolate;
  will-change: opacity, transform;
  perspective: 600px;
}
.medal-pop-badge { display: block; width: 128px; height: 128px; object-fit: contain; filter: drop-shadow(0 6px 14px rgba(30, 42, 37, .45)); }
.medal-pop-title, .medal-pop-award { text-align: center; line-height: 1.15; text-shadow: 0 1px 0 rgba(255, 255, 255, .85), 0 2px 8px rgba(70, 78, 86, .55); }
/* The title sits over the dark wood: the NEW BEST score's platinum treatment keeps it legible there. */
.medal-pop-title { font-size: 19px; font-weight: 800; color: var(--platinum-light); -webkit-text-stroke: .5px rgba(75, 82, 90, .6); }
.medal-pop-award { font-size: 24px; font-weight: 900; color: var(--gold); -webkit-text-stroke: .6px rgba(157, 109, 8, .7); }
.medal-pop.show { animation: medal-pop-anim 2.5s cubic-bezier(.22, .9, .3, 1) both; }
.medal-pop.show .medal-pop-badge { animation: medal-spin 1.1s cubic-bezier(.3, .7, .3, 1) both; }
@keyframes medal-pop-anim {
  0% { opacity: 0; transform: scale(.4); }
  14% { opacity: 1; transform: scale(1.1); }
  24% { transform: scale(1); }
  84% { opacity: 1; transform: translateY(0) scale(1); }
  100% { opacity: 0; transform: translateY(-20px) scale(.96); }
}
@keyframes medal-spin {
  0% { transform: rotateY(0deg) scale(.6); }
  100% { transform: rotateY(720deg) scale(1); }
}

/* Tiered look: bigger, more colorful, longer-held as the multiplier climbs. */
.score-pop[data-tier="1"] { --pop-size: 26px; }
.score-pop[data-tier="2"] { --pop-size: 30px; --pop-tag-size: 32px; }
.score-pop[data-tier="3"] {
  --pop-size: 36px;
  --pop-tag-size: 42px;
  --pop-color: var(--gold-dark);
}
.score-pop[data-tier="4"] {
  --pop-size: 44px;
  --pop-tag-size: 54px;
  --pop-hold: 1400ms;
  --pop-color: var(--gold-dark);
}
.score-pop[data-tier="5"] {
  --pop-size: 54px;
  --pop-tag-size: 70px;
  --pop-tag-color: var(--platinum-light);
  --pop-hold: 1800ms;
  --pop-color: var(--gold-dark);
}

.score-pop.pop { animation: score-pop-anim var(--pop-hold) cubic-bezier(.22, .9, .3, 1) both; }
.score-pop[data-tier="2"].pop .score-pop-multiplier { animation: tag-pop .3s .05s cubic-bezier(.2,1.55,.45,1) both; }
.score-pop[data-tier="3"].pop { animation: score-pop-anim var(--pop-hold) cubic-bezier(.22, .9, .3, 1) both, score-pop-rotate-in .35s ease-out both; }
.score-pop[data-tier="3"].pop .score-pop-multiplier { animation: tag-pop .34s .05s cubic-bezier(.2,1.55,.45,1) both; }
.score-pop[data-tier="4"].pop .score-pop-multiplier,
.score-pop[data-tier="5"].pop .score-pop-multiplier { animation: tag-pop .38s .06s cubic-bezier(.2,1.55,.45,1) both; }
.score-pop[data-tier="4"].pop .score-pop-ring,
.score-pop[data-tier="5"].pop .score-pop-ring { animation: ring-expand var(--pop-hold) ease-out both; }
.score-pop[data-tier="5"].pop .score-pop-points { animation: pop-shake 320ms 140ms ease-in-out; }

/* Fully visible within the first few frames so the points land with the piece; the overshoot
   settle is what reads as the "pop". */
@keyframes score-pop-anim {
  0% { opacity: 0; transform: translateY(0) scale(.7); }
  7% { opacity: 1; transform: translateY(0) scale(1.12); }
  20% { transform: translateY(0) scale(1); }
  72% { opacity: 1; transform: translateY(-8px) scale(1); }
  100% { opacity: 0; transform: translateY(-24px) scale(1); }
}
@keyframes score-pop-rotate-in {
  0% { rotate: -6deg; }
  100% { rotate: 0deg; }
}
@keyframes tag-pop {
  0% { opacity: 0; transform: scale(.25) rotate(-18deg); }
  68% { opacity: 1; transform: scale(1.18) rotate(4deg); }
  100% { opacity: 1; transform: scale(1) rotate(0); }
}
@keyframes ring-expand {
  0% { opacity: .8; transform: scale(.5); }
  60% { opacity: .35; }
  100% { opacity: 0; transform: scale(2); }
}
@keyframes pop-shake {
  0%, 100% { translate: 0 0; }
  20% { translate: -3px 0; }
  40% { translate: 3px 0; }
  60% { translate: -2px 0; }
  80% { translate: 2px 0; }
}

/* Brief full-board gold wash for tier 5+ clears. */
#board.gold-wash::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 5;
  border-radius: 13px;
  background: radial-gradient(circle, rgba(255, 240, 163, .55), rgba(217, 168, 35, .1) 70%, transparent 100%);
  pointer-events: none;
  animation: gold-wash 200ms ease-out;
}
@keyframes gold-wash {
  0% { opacity: 0; }
  40% { opacity: 1; }
  100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .score-pop.pop,
  .score-pop.pop .score-pop-multiplier,
  .score-pop.pop .score-pop-ring,
  .score-pop.pop .score-pop-points {
    animation: score-pop-fade var(--pop-hold) ease both !important;
  }
  .best-pop.show { animation: score-pop-fade 2s ease both !important; }
  .medal-pop.show { animation: score-pop-fade 2.5s ease both !important; }
  .medal-pop.show .medal-pop-badge { animation: none !important; }
  #board.gold-wash::after { animation: none; opacity: 0; }
}
@keyframes score-pop-fade {
  0% { opacity: 0; }
  15% { opacity: 1; }
  85% { opacity: 1; }
  100% { opacity: 0; }
}

.tray-zone { display: grid; row-gap: 6px; }
/* 2.1B: the rack switch column is gone; the bank keeps the width it had beside it ((row - 9px gap) x 3/4) and
   sits centred, so the three bins are equally reachable left- and right-handed. */
.piece-rack { display: grid; grid-template-columns: calc((100% - 9px) * 3 / 4); justify-content: center; height: 92px; }
.spare[hidden] { display: none; }
/* Bin watermark (2.1B): the same clockwise motif as the meter icon, drawn on the bin floor behind each piece slot
   at a subtle 16%, ever so slightly stronger (26%) while the piece is rotated away from its spawn orientation
   (owner ruling 2026-09-16, reversing the "constant" wording of the scope), and only while rotation exists at all
   (Classic mode off; app.js keeps .rotation-on on the tray zone in step with the setting). */
.tray-zone.rotation-on .piece-slot::before {
  content: "";
  position: absolute;
  inset: 4px;
  z-index: 0;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cg fill='none' stroke='%239d6d08' stroke-width='24'%3E%3Cpath d='M110 241 C-3 235 -11 70 110 51'/%3E%3Cpath d='M178 85 L240 147 L178 209 L116 147 Z'/%3E%3C/g%3E%3Cpath d='M109 0 L160 51 L109 102 Z' fill='%239d6d08'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 90%;
  opacity: .16;
  transition: opacity .15s ease;
}
.tray-zone.rotation-on .piece-slot.is-rotated::before { opacity: .26; }
.bank {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  min-width: 0;
  overflow: hidden;
  gap: 3px;
  padding: 4px;
  border: 4px solid #a77e5a;
  border-radius: 12px;
  background:
    linear-gradient(145deg, rgba(255,255,255,.13), transparent 35%),
    linear-gradient(160deg, #c39a72, #98704f);
  box-shadow:
    inset 0 6px 7px rgba(35, 17, 8, .38),
    inset 5px 0 6px rgba(41, 21, 10, .23),
    inset 0 -1px rgba(255, 230, 196, .2),
    0 4px 10px rgba(61,34,18,.12);
}
.piece-slot, .spare {
  display: grid;
  place-items: center;
  min-width: 0;
  background: var(--panel);
}
.bank .piece-slot, .spare::after {
  border-radius: 4px;
  background:
    linear-gradient(180deg, rgba(35, 17, 8, .14), transparent 42%),
    var(--panel);
  box-shadow:
    inset 0 5px 6px rgba(35, 17, 8, .24),
    inset 4px 0 4px rgba(41, 21, 10, .13),
    inset -2px 0 4px rgba(41, 21, 10, .08),
    inset 0 -1px rgba(255, 230, 196, .2);
}
.bank .piece-slot { border: 1px solid rgba(61, 32, 18, .3); }
.piece-slot { position: relative; cursor: grab; touch-action: none; }
.piece-slot:active { cursor: grabbing; }
.piece-slot.drag-source .piece-grid { opacity: 0; transform: translateY(-4px) scale(1.035); }
.piece-grid { position: relative; z-index: 1; display: grid; gap: 1px; pointer-events: none; transform-origin: center; transition: opacity .1s linear, transform .15s ease; }
.piece-block {
  width: var(--block, 17px); height: var(--block, 17px);
  border-radius: 3px;
  border: 1px solid rgba(84, 74, 66, .55);
  background:
    linear-gradient(110deg, transparent 24%, rgba(255,255,255,.9) 44%, transparent 62%) 170% 0 / 220% 100% no-repeat,
    linear-gradient(132deg, rgba(255,255,255,.72), transparent 28%),
    linear-gradient(145deg, var(--stone-light), var(--stone) 54%, var(--stone-dark));
  box-shadow: inset 0 1px rgba(255,255,255,.65), inset 0 -2px rgba(63,55,49,.18), 0 2px 3px rgba(53,36,25,.25), 0 0 0 1px rgba(159,111,81,.2);
}
.piece-block { position: relative; overflow: hidden; }
.piece-block::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(110deg, transparent 30%, rgba(255,255,255,.85) 48%, transparent 66%);
  opacity: 0;
  pointer-events: none;
}
.piece-grid.shine-once .piece-block::after,
.spare.just-dropped .piece-block::after { animation: glint .7s ease-out 1; }
.piece-slot.unplaceable .piece-block,
.drag-ghost.unplaceable .piece-block > .brick {
  animation: none;
  border-color: #6d321f;
  background: linear-gradient(135deg, #f1b085, #b66038 48%, #6e301f 88%);
  box-shadow: inset 0 1px rgba(255,242,225,.5), inset 0 -2px rgba(80,28,14,.3), 0 2px 4px rgba(66,30,18,.28);
}
/* Ghost blocks paint nothing themselves: the silver brick, its gold twin and the landing glint band
   are three stacked children, each its own composited layer (will-change), so the gold preview and
   the landing hand-over only switch opacities and the moving ghost is never repainted mid-drag. */
.drag-ghost .piece-block { overflow: visible; border: 0; background: none; box-shadow: none; }
.drag-ghost .piece-block::after { content: none; }
.drag-ghost .piece-block > i { position: absolute; inset: 0; border-radius: 3px; will-change: opacity; }
.drag-ghost .piece-block > .brick {
  border: 1px solid rgba(84, 74, 66, .55);
  background:
    linear-gradient(110deg, transparent 24%, rgba(255,255,255,.9) 44%, transparent 62%) 170% 0 / 220% 100% no-repeat,
    linear-gradient(132deg, rgba(255,255,255,.72), transparent 28%),
    linear-gradient(145deg, var(--stone-light), var(--stone) 54%, var(--stone-dark));
  /* Lift shadow lives here (painted once into the layer), not as a filter on the moving ghost. */
  box-shadow: inset 0 1px rgba(255,255,255,.65), inset 0 -2px rgba(63,55,49,.18), 0 10px 8px rgba(53,34,18,.3), 0 0 0 1px rgba(159,111,81,.2);
}
.drag-ghost .piece-block > .gold {
  opacity: 0;
  border: 1px solid rgba(115, 75, 4, .58);
  background:
    linear-gradient(110deg, transparent 24%, rgba(255,255,255,.88) 44%, transparent 62%) 170% 0 / 220% 100% no-repeat,
    linear-gradient(132deg, rgba(255,255,255,.72), transparent 29%),
    linear-gradient(145deg, var(--gold-light), var(--gold) 55%, var(--gold-dark));
  box-shadow: inset 0 1px rgba(255,255,255,.62), 0 0 0 2px rgba(217,168,35,.34), 0 3px 5px rgba(80,45,12,.28);
}
.drag-ghost .piece-block > .band {
  opacity: 0;
  background: linear-gradient(110deg, transparent 30%, rgba(255,255,255,.85) 48%, transparent 66%);
}
.drag-ghost .piece-block.preview-clear > .gold,
.drag-ghost .piece-block.cleared > .gold { opacity: 1; }
/* Pulse the whole ghost (already its own layer, compositor-only) rather than its blocks. */
/* piece-lift stays at index 0 in both states: an animation-name that disappears and reappears is a new
   animation, so listing only the pulse here replayed the pickup lift every time the gold preview went away. */
.drag-ghost.will-clear { animation: piece-lift .14s cubic-bezier(.22, .9, .3, 1), ghost-clear-pulse .55s ease-in-out infinite alternate; }
@keyframes ghost-clear-pulse { to { scale: .95; } }
.piece-space { width: var(--block, 17px); height: var(--block, 17px); }
.spare {
  position: relative;
  height: 92px;
  isolation: isolate;
  border: 4px solid #a77e5a;
  border-radius: 12px;
  background:
    linear-gradient(145deg, rgba(255,255,255,.13), transparent 35%),
    linear-gradient(160deg, #c39a72, #98704f);
  box-shadow:
    inset 0 6px 7px rgba(35, 17, 8, .38),
    inset 5px 0 6px rgba(41, 21, 10, .23),
    inset 0 -1px rgba(255, 230, 196, .2),
    0 4px 10px rgba(61,34,18,.12);
  touch-action: none;
}
.spare::after { content: ""; position: absolute; inset: 3px; z-index: 0; pointer-events: none; }
.spare > * { position: relative; z-index: 1; }
.spare.drop-ready { border-color: var(--wood-dark); box-shadow: inset 0 1px rgba(255,235,207,.5), 0 0 0 2px rgba(173,79,43,.16); }
.spare.just-dropped .piece-grid { animation: tray-drop .16s cubic-bezier(.22,.84,.42,1); }
@keyframes tray-drop {
  0% { opacity: .82; transform: translateY(-7px) scale(1.055); filter: drop-shadow(0 7px 4px rgba(53,34,18,.24)); }
  100% { opacity: 1; transform: translateY(0) scale(1); filter: none; }
}
.spare-empty { color: rgba(117, 111, 100, .33); font-size: 30px; line-height: 1; font-weight: 800; }

/* Grow-in + fade-in animate the ghost itself (opacity/scale, compositor-only on one layer). The old
   per-block scale transition promoted every block to its own layer at pickup. */
.drag-ghost { position: fixed; left: 0; top: 0; z-index: 20; pointer-events: none; display: grid; gap: 3px; opacity: 1; animation: piece-lift .14s cubic-bezier(.22, .9, .3, 1); will-change: transform; }
.drag-ghost .piece-block {
  width: calc((var(--cell) - 3px) * .94);
  height: calc((var(--cell) - 3px) * .94);
  place-self: center;
}
.drag-ghost .piece-space { width: calc(var(--cell) - 3px); height: calc(var(--cell) - 3px); }
@keyframes piece-lift {
  from { opacity: .72; scale: .62; }
  to { opacity: 1; scale: 1; }
}

/* 18px (not the original 28px) so "How to play" / "New board" / the settings cog still fit one line at 360px. */
footer { margin-top: auto; display: flex; align-items: center; justify-content: center; gap: 18px; }
.text-button { border: 0; background: none; padding: 10px 12px; color: var(--muted); font-size: 16px; font-weight: 650; text-decoration: underline; text-underline-offset: 4px; cursor: pointer; }
.icon-button { border: 0; background: none; min-width: 44px; min-height: 44px; display: grid; place-items: center; color: var(--muted); cursor: pointer; padding: 0; }

.dialog-backdrop {
  position: fixed; inset: 0; z-index: 40;
  display: grid; place-items: center;
  padding: 22px;
  /* The Advanced settings pane is taller than a short phone: the backdrop scrolls, the card never clips. */
  overflow-y: auto;
  background: rgba(29, 34, 31, .55);
  backdrop-filter: blur(5px);
}
.dialog-backdrop[hidden] { display: none; }
.dialog {
  width: min(100%, 370px);
  padding: 28px;
  border-radius: 16px;
  background: #fffaf1;
  box-shadow: 0 20px 60px rgba(0,0,0,.25);
  text-align: center;
}
.dialog h2 { margin: 5px 0 8px; font-size: 28px; letter-spacing: -.04em; }
.dialog > p:not(.eyebrow) { color: var(--muted); font-size: 14px; line-height: 1.5; }
.dialog > p.dialog-subtitle { margin: 4px 0 0; font-size: 22px; font-weight: 650; color: var(--muted); line-height: 1.2; }
.dialog button { width: 100%; min-height: 46px; margin-top: 9px; border-radius: 9px; font-weight: 800; cursor: pointer; }
.dialog button:disabled { opacity: .45; cursor: default; }
.primary-button { border: 0; background: var(--ink); color: white; }
.secondary-button { border: 1px solid #cfc3ad; background: transparent; }
/* Blocked dialog (1.6): 92px undo button (2x default) and a balance caption keep the
   accidental-tap-prone action big and informative; the extra top margin on #reset-button
   keeps "Start a new board" a deliberate reach away from it. */
#undo-button { min-height: 92px; font-size: 18px; margin-top: 22px; }
.dialog > p.undo-balance { margin: 10px 0 0; font-size: 13px; color: var(--muted); line-height: 1.4; text-align: center; }
.undo-balance span { display: block; }
#reset-button { margin-top: 28px; }
/* Share score (1.7B): a third, lighter-weight control below "Start a new board". .dialog button
   above sets width:100%/min-height:46px/margin-top:9px for the full-width primary/secondary
   actions; ".dialog .text-button" out-specifies it the same way ".dialog .dialog-close" does
   (see the settings dialog below), so the share button keeps .text-button's plain-link look at a
   44px-tall tap target instead of turning into another full block button. */
.dialog .text-button { width: 100%; min-height: 44px; margin-top: 6px; font-weight: 650; }
.help-dialog { text-align: left; }
.help-dialog ol { padding-left: 20px; color: var(--muted); font-size: 14px; line-height: 1.7; }
/* Privacy notice link (2.0): a quiet line under the button; relative href so it works on h-blk.com, in a portal package and offline. */
.help-legal { margin: 14px 0 0; text-align: center; font-size: 13px; }
.help-legal a { color: var(--muted); text-decoration: underline; text-underline-offset: 3px; }

.settings-dialog { text-align: left; position: relative; }
/* .dialog button sets width:100%/min-height:46px/margin-top:9px for the full-width primary/secondary
   actions; the close X and the switches are inline controls, so these use ".dialog X" to out-specificity it. */
.dialog .dialog-close { position: absolute; top: 10px; right: 10px; width: 44px; margin-top: 0; min-height: 44px; font-size: 26px; line-height: 1; color: var(--muted); font-weight: 400; }
.settings-row { display: flex; flex-direction: column; gap: 2px; padding: 12px 0; }
.settings-row + .settings-row { border-top: 1px solid #e8dfcd; }
.settings-label { font-size: 16px; font-weight: 650; color: var(--ink); }
.settings-caption { display: block; margin-top: 2px; font-size: 12px; font-weight: 400; color: var(--muted); }
.settings-caption[hidden] { display: none; } /* the class would otherwise out-specify the UA [hidden] rule */
/* Settings (1.7A, compacted in 1.7A.1): two panes in one card. Main = two switches + Advanced;
   Advanced = a top-left Back beside the title, six one-line slider rows (label left, slider right),
   the Look preview and a Reset link, sized to fit a 360 x 640 viewport without scrolling. Dialog-only,
   nothing here is on the drag path. */
.dialog .settings-nav { width: 100%; min-height: 44px; margin-top: 8px; border-radius: 9px; font-weight: 700; text-align: right; }
.dialog .dialog-back { position: absolute; top: 10px; left: 10px; width: 44px; margin-top: 0; min-height: 44px; font-size: 30px; line-height: 1; color: var(--muted); font-weight: 400; }
#settings-advanced-title { margin: 10px 40px 6px 44px; font-size: 21px; line-height: 1.2; white-space: nowrap; }
.settings-slider-row { flex-direction: row; align-items: center; gap: 8px; padding: 1px 0; }
.settings-slider-row + .settings-slider-row { border-top: 0; }
.settings-slider-row .settings-label { flex: 0 0 104px; font-size: 14px; line-height: 1.15; }
.settings-slider-row .settings-range { flex: 1; min-width: 0; gap: 6px; }
.settings-slider-row .range-end { min-width: 18px; font-size: 11px; }
.settings-slider-row .settings-caption { font-size: 11px; margin-top: 0; }
.dialog .settings-reset { min-height: 36px; margin-top: 4px; padding: 6px; font-size: 14px; font-weight: 650; }
.settings-switch-row { flex-direction: row; align-items: center; justify-content: space-between; gap: 12px; }
.settings-switch-row .settings-label { flex: 1; min-width: 0; }
/* A switch is a 44px-tall hit area drawing a 32px track (::before) and a 26px thumb (::after) itself,
   so it looks like the sliders' thumb rather than the platform checkbox. */
.dialog .switch { appearance: none; -webkit-appearance: none; flex: none; position: relative; width: 56px; min-height: 44px; margin: 0; padding: 0; border: 0; background: transparent; cursor: pointer; }
.dialog .switch::before { content: ""; position: absolute; left: 0; top: 6px; width: 56px; height: 32px; border-radius: 16px; background: #cfc3ad; transition: background .15s; }
.dialog .switch::after { content: ""; position: absolute; left: 3px; top: 9px; width: 26px; height: 26px; border-radius: 50%; background: #fff; box-shadow: 0 1px 4px rgba(0,0,0,.25); transition: transform .15s; }
.dialog .switch:checked::before { background: var(--wood); }
.dialog .switch:checked::after { transform: translateX(24px); }
.dialog .switch:disabled { opacity: .45; cursor: default; }
.dialog .switch:focus-visible::before { outline: 2px solid var(--ink); outline-offset: 2px; }
.settings-heading { margin: 8px 0 2px; font-size: 14px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
/* Look preview: a real .board with 81 real .cell children, so every custom property the three look
   sliders write (--wall/--pad/--gap/--seam, stone and pocket tones) renders here exactly as on the
   board behind the dialog. Only --cell is pinned (the sliders never touch it), which keeps the walls
   and seams at true pixel size - "thick" and "thin" mean the same pixels as on the board. While a
   slider moves, app.js writes the code inline on this element alone (1.7A.1): the board under the
   blurred backdrop does not repaint per step, which was the lag; the board follows on release. */
.dialog .look-preview { --cell: 11px; margin: 4px auto 2px; width: max-content; border-radius: 6px; box-shadow: 0 6px 14px rgba(61, 34, 18, .22), inset 0 1px rgba(255, 235, 207, .42), inset 0 -2px rgba(54, 28, 14, .32); will-change: auto; }
.dialog .look-preview .cell { border-radius: 1px; }
.dialog .look-preview .cell::after { border-radius: 2px; }
.settings-range { display: flex; align-items: center; gap: 10px; }
.range-end { flex: none; min-width: 24px; font-size: 12px; font-weight: 700; color: var(--muted); }
.range-end:last-child { text-align: right; }
.settings-row.is-off .settings-label { color: var(--muted); }
.settings-range input[type="range"] { flex: 1; width: 100%; height: 44px; margin: 0; background: transparent; accent-color: var(--wood); touch-action: pan-y; }
.settings-range input[type="range"]:disabled { opacity: .45; }
.settings-range input[type="range"]::-webkit-slider-runnable-track { height: 6px; border-radius: 3px; background: #cfc3ad; }
.settings-range input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 28px; height: 28px; margin-top: -11px; border-radius: 50%; border: 3px solid #fff; background: var(--wood); box-shadow: 0 1px 4px rgba(0,0,0,.25); }
.settings-range input[type="range"]::-moz-range-track { height: 6px; border-radius: 3px; background: #cfc3ad; }
.settings-range input[type="range"]::-moz-range-thumb { width: 22px; height: 22px; border-radius: 50%; border: 3px solid #fff; background: var(--wood); box-shadow: 0 1px 4px rgba(0,0,0,.25); }
.settings-range input[type="range"] { -webkit-appearance: none; appearance: none; }

/* Daily challenge (2.1A; geometry and type from the approved mockup). The badge is a chromeless button in
   the status row; the Daily window reuses .dialog with its own muted labels: the instruction line is auto-
   fitted by app.js to one line (--daily-label-size) and the Stats header inherits exactly that size. */
.daily-card { appearance: none; border: 0; background: transparent; color: inherit; display: grid; justify-items: center; gap: 2px; padding: 0; cursor: pointer; font: inherit; min-width: 0; }
.daily-card:focus-visible { outline: 2px solid var(--wood-dark); outline-offset: 3px; border-radius: 8px; }
.daily-main-badge { display: block; width: 56px; height: 56px; object-fit: contain; }
.daily-stat-badge { width: 31px; height: 31px; object-fit: contain; vertical-align: middle; }
.daily-dialog { position: relative; max-height: min(88vh, 720px); overflow-y: auto; }
.daily-rotations-preview { display: flex; justify-content: space-between; align-items: center; gap: 6px; min-height: 72px; margin: 10px 0 14px; padding: 10px 8px; border-radius: 10px; background: rgba(117, 111, 100, .1); }
.daily-rotation { display: grid; place-items: center; flex: 1; min-width: 0; height: 62px; }
.daily-piece-grid { justify-content: center; align-content: center; }
.daily-dialog > p.daily-instruction { margin: 20px 0 8px; white-space: nowrap; }
.daily-dialog > p.daily-instruction, .daily-stats .daily-stats-label { font-size: var(--daily-label-size, 14px); font-weight: 800; color: var(--muted); }
.daily-goals { display: grid; gap: 6px; }
.daily-goal { display: grid; grid-template-columns: 1fr 34px 24px; align-items: center; gap: 8px; min-height: 42px; padding: 5px 8px; border: 1px solid rgba(76, 43, 24, .12); border-radius: 9px; text-align: left; }
.daily-goal-copy { display: grid; gap: 1px; }
.daily-goal-copy b { font-size: 14px; }
.daily-goal-badge { width: 31px; height: 31px; object-fit: contain; }
.daily-check { color: var(--muted); font-size: 22px; line-height: 1; text-align: center; }
.daily-goal.is-achieved { background: rgba(117, 111, 100, .1); }
.daily-goal.is-achieved .daily-check { color: var(--wood-dark); font-weight: 900; }
.daily-stats { margin-top: 22px; }
.daily-stats table { width: 100%; border-collapse: collapse; font-size: 13px; }
.daily-stats th, .daily-stats td { padding: 4px 6px; text-align: center; border-bottom: 1px solid rgba(76, 43, 24, .1); }
.daily-stats th:first-child { text-align: left; }
.daily-stats td { font-variant-numeric: tabular-nums; }
/* Reward window (2.1A): the exact copy as the title line, the shape in the app's stone colours, PLAY. */
.dialog > p.reward-text { margin: 6px 0 14px; font-size: 18px; font-weight: 750; color: var(--ink); line-height: 1.35; }
.reward-shape { display: grid; place-items: center; min-height: 72px; margin: 10px 0 6px; padding: 12px 8px; border-radius: 10px; background: rgba(117, 111, 100, .1); }

@media (min-height: 760px) { .app-shell { gap: 24px; } .piece-rack, .spare { height: 100px; } }
@media (max-width: 390px) { :root { --shell-pad: 15px; } .app-shell { gap: 14px; } .daily-dialog { padding: 20px 16px; } .daily-rotations-preview { gap: 2px; padding-inline: 4px; } }
@media (display-mode: standalone) { .app-shell { padding-top: max(24px, env(safe-area-inset-top)); } .toast { top: calc(max(24px, env(safe-area-inset-top)) + 29px); } }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
  body { background: #dce8df; }
}


/* R2.1C: measured viewport budget, compact chrome and landscape arrangement. */
.app-shell > * { flex-shrink: 0; }
.play-area, .tray-zone { min-width: 0; }
.app-shell { padding-left: max(var(--shell-pad), env(safe-area-inset-left)); padding-right: max(var(--shell-pad), env(safe-area-inset-right)); }
footer .text-button { min-height: 44px; }
@media (max-height: 700px) {
 .app-shell { padding-block: max(8px, env(safe-area-inset-top)) max(8px, env(safe-area-inset-bottom)); gap: 8px; }
 .brand { width: min(100%, 176px); }
 .hero-text { display: none; }
 .status-row { margin-top: 0; }
 .score-card span, .best-card span { font-size: 12px; }
 .score-card strong, .best-card strong { font-size: clamp(18px, 26px - (var(--score-digits, 3) - 3) * 2px, 26px); }
 .daily-main-badge { width: 44px; height: 44px; }
 .piece-rack, .spare { height: 84px; }
 footer { gap: 6px; }
 footer .text-button { padding-inline: 8px; font-size: 14px; }
}
@media (max-height: 720px) and (min-aspect-ratio: 1/1) {
 .app-shell { width: min(100%, 820px); display: grid; grid-template-columns: minmax(0, 1fr) minmax(180px, .8fr); grid-template-rows: auto auto 1fr auto; column-gap: 14px; row-gap: 8px; padding-block: max(8px, env(safe-area-inset-top)) max(8px, env(safe-area-inset-bottom)); }
 .play-area { grid-column: 1; grid-row: 1 / 5; align-self: center; }
 .topbar { grid-column: 2; grid-row: 1; }
 .status-row { grid-column: 2; grid-row: 2; padding-inline: 0; margin-top: 0; }
 .tray-zone { grid-column: 2; grid-row: 3; align-self: center; }
 footer { grid-column: 2; grid-row: 4; flex-wrap: wrap; gap: 0; margin-top: 0; }
 .brand { width: min(100%, 154px); }
 .hero-text { display: none; }
 .piece-rack { grid-template-columns: 100%; }
 .medal-pop-badge { width: 80px; height: 80px; }
}

/* Fixed-height trays must not expand to a five-cell piece intrinsic size. */
.piece-rack .bank, .piece-slot { min-height: 0; }
