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;
  /* Top inset every layout padding uses. K1: in portal portrait full screen (app.js sets .portal-full)
     the portal covers the front camera: twice the phone's reported camera area, or 80px when it reports none
     (owner 2026-10-12 on itch.io: the 40px strip was not enough). */
  --safe-top: env(safe-area-inset-top, 0px);
}
/* (1px - env) * 1000 is >= 80px only when env is 0, so the strip is 80px or twice the reported inset. */
html.portal-full { --safe-top: max(env(safe-area-inset-top, 0px) * 2, min(80px, (1px - env(safe-area-inset-top, 0px)) * 1000)); }

* { 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, var(--safe-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; }
/* Flex column so the tagline, wider than the wordmark, overflows equally on both sides (stays centred). */
.brand { min-width: 0; width: min(100%, 242px); display: flex; flex-direction: column; align-items: center; text-align: center; }
.brand h1 { width: 100%; }
.eyebrow { margin: 0 0 3px; color: var(--wood-dark); font-size: 10px; letter-spacing: .2em; font-weight: 800; }
/* R2.4: approved square icon as the H + geometric BLOKS, generated by scripts/brand-hbloks.mjs.
   Branding uses the fixed approved palette, independently of gameplay look settings.
   The mark is the H (315x48 source); 242 px wide keeps the R2.2 header height (193 / 170 px in the compact layouts). */
.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; }
.best-card { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.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 { position: relative; 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 { position: relative; 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(--gold-dark); }
.rotation-count.pulse { animation: rotation-count-pulse .35s ease; }
@keyframes rotation-count-pulse {
  0% { transform: scale(1); color: var(--gold-dark); }
  40% { transform: scale(1.35); color: var(--gold); }
  100% { transform: scale(1); color: var(--gold-dark); }
}
/* "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(--gold-dark); }
  15%, 45%, 75% { transform: scale(1.3); color: #c0392b; }
  30%, 60% { transform: scale(1); color: var(--gold-dark); }
}
/* R2.2 A1–A3 rotation-credit feedback, started by app.js in the drop task (classes expire with their
   animations). Opacity/transform/filter only, deliberately not in the reduced-motion block. Sized up after
   the owner's phone test (2026-10-12): the first version was too subtle.
   A1 (.rotation-meter.burst, 0–0.7 s): ten gold segments collect left to right into the last one (each
   starts when the sweep reaches it), burst at the counter, "+N" pops above it, and the counter and the
   rotation icon grow and spin.
   A2 (.spend-pop): the rotation icon spins and grows with "−1" at the centre of the placed shape.
   A3: each bar the credit stream delivers jumps once as it lands.
   Stream (.credit-stream): each cleared cell flies as a gold drop into the bar it fills (app.js). */
.rotation-collect { position: absolute; inset: 0; display: flex; align-items: center; gap: 2px; pointer-events: none; }
.rotation-collect i {
  flex: 1; height: 9px; min-width: 2px; border-radius: 999px; opacity: 0;
  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-meter.burst .rotation-collect i {
  animation: rotation-collect calc((9 - var(--i)) * 70ms) calc(var(--i) * 70ms) linear both,
    rotation-collect-out .12s .63s ease-in both;
}
@keyframes rotation-collect {
  from { opacity: 1; transform: none; }
  to { opacity: 1; transform: translateX(calc((9 - var(--i)) * (100% + 2px))); }
}
@keyframes rotation-collect-out { to { opacity: 0; } }
.rotation-burst {
  position: absolute; right: -6px; top: 50%; width: 22px; height: 22px; margin-top: -11px; border-radius: 50%; pointer-events: none;
  background: radial-gradient(circle, #fff 0 20%, var(--gold-light) 35%, var(--gold) 55%, rgba(201, 150, 60, 0) 72%);
  opacity: 0;
}
.rotation-meter.burst .rotation-burst { animation: rotation-burst .55s .6s ease-out both; }
@keyframes rotation-burst {
  0% { opacity: 1; transform: scale(.4); }
  100% { opacity: 0; transform: scale(3.6); }
}
.rotation-meter.burst .rotation-count { animation: rotation-count-burst .7s .6s ease-out; }
@keyframes rotation-count-burst {
  0%, 100% { transform: none; color: var(--gold-dark); }
  35% { transform: scale(1.8); color: var(--gold); }
}
.rotation-meter.burst .rotation-icon { animation: rotation-icon-burst .8s .6s ease-in-out; }
@keyframes rotation-icon-burst {
  0% { transform: none; }
  50% { transform: scale(1.9) rotate(200deg); }
  100% { transform: rotate(360deg); }
}
.rotation-bar .seg.jump { animation: rotation-seg-jump .6s ease; }
@keyframes rotation-seg-jump {
  0%, 100% { transform: none; filter: none; }
  35% { transform: translateY(-5px) scaleY(1.9); filter: brightness(1.5); }
}
.rotation-delta, .spend-pop {
  pointer-events: none; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 0 rgba(255, 255, 255, .6), 0 1px 4px rgba(30, 42, 37, .4);
  opacity: 0; will-change: opacity, transform;
}
.rotation-delta { position: absolute; right: 1px; z-index: 1; width: 30px; text-align: center; font-size: 22px; }
.rotation-delta.gain { bottom: 100%; color: var(--gold-dark); }
.rotation-meter.burst .rotation-delta.gain { animation: rotation-gain-rise 1.4s .6s ease-out both; }
@keyframes rotation-gain-rise {
  0% { opacity: 0; transform: translateY(10px) scale(.4); }
  20% { opacity: 1; transform: scale(1.5); }
  35%, 75% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(-14px); }
}
.spend-pop { position: fixed; left: 0; top: 0; translate: -50% -50%; z-index: 66; display: flex; align-items: center; gap: 4px; padding: 3px 12px 3px 6px; border-radius: 999px; background: rgba(255, 251, 242, .94); box-shadow: 0 2px 10px rgba(30, 42, 37, .35); font-size: 22px; color: #b0432a; }
.spend-pop-icon { width: 26px; height: 26px; color: var(--gold-dark); filter: drop-shadow(0 1px 3px rgba(30, 42, 37, .45)); }
.spend-pop.show { animation: spend-pop .9s ease-out both; }
@keyframes spend-pop {
  0% { opacity: 0; transform: scale(.5); }
  15% { opacity: 1; transform: scale(1.25); }
  30%, 75% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(10px); }
}
@keyframes spend-pop-spin {
  0% { transform: none; }
  50% { transform: scale(1.4) rotate(45deg); }
  100% { transform: rotate(90deg); }
}

.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, var(--safe-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;
  /* The medal popup's dark walnut plaque (owner 2026-10-12); the confetti stays in front of it. */
  padding: 12px 20px;
  border-radius: 12px;
  background: rgba(48, 30, 18, .75);
  border: 1px solid rgba(217, 168, 35, .55);
  box-shadow: 0 4px 16px rgba(30, 42, 37, .4);
}
.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 10px rgba(0, 0, 0, .45), 0 0 24px rgba(217, 168, 35, .45);
}
.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 2px 8px rgba(0, 0, 0, .5);
}
.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)); }
/* R2.2 (owner): a dark walnut plaque, 25% transparent, behind the text: cream title and the bar's gold read on it. */
.medal-pop-plaque { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 12px 18px; border-radius: 12px; background: rgba(48, 30, 18, .75); border: 1px solid rgba(217, 168, 35, .55); box-shadow: 0 4px 16px rgba(30, 42, 37, .4); }
.medal-pop-title, .medal-pop-award { text-align: center; line-height: 1.15; }
/* 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: #fff6e2; }
/* The progress bar's gold, flat (owner 2026-10-12: a gradient darkened the line towards the right). */
.medal-pop-award { color: var(--gold); }
.medal-pop-award { font-size: 24px; font-weight: 900; }
.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%, clearly stronger (45%; was 26% until the 2026-09-29 owner note that the two read alike) 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 a rotation can be bought (Classic mode off and credits left; app.js keeps
   .rotation-on on the tray zone in step). */
.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: .45; }
.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: 15.4px; line-height: 1.7; }
/* Privacy notice link (2.0): a quiet line under the button; relative href so it works on hbloks.com, in a portal package and offline. */
.help-legal { margin: 14px 0 0; text-align: center; font-size: 14.3px; }
.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. R2.2: #daily-challenge (Shape of
   the day to the medal legend) is one block that app.js moves between the Daily window and the welcome popup. */
.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, .reward-dialog { position: relative; max-height: min(88vh, 720px); overflow-y: auto; }
.daily-challenge > p.dialog-subtitle { margin: 4px 0 0; font-size: 22px; font-weight: 650; color: var(--muted); line-height: 1.2; }
.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-stats .daily-stats-label { font-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: clamp(12.5px, 3.6vw, 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 shared Daily challenge block, PLAY. */
.dialog > p.reward-text { margin: 6px 0 14px; font-size: 18px; font-weight: 750; color: var(--ink); line-height: 1.35; }
.reward-dialog .daily-goals { margin-bottom: 18px; }
/* The welcome popup shows the tiers as a legend only: no checkboxes. */
.reward-dialog .daily-goal { grid-template-columns: 1fr 34px; }
.reward-dialog .daily-check { display: none; }

@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, .reward-dialog { padding: 20px 16px; } .daily-rotations-preview { gap: 2px; padding-inline: 4px; } }
@media (display-mode: standalone) { .app-shell { padding-top: max(24px, var(--safe-top)); } .toast { top: calc(max(24px, var(--safe-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, var(--safe-top)) max(8px, env(safe-area-inset-bottom)); gap: 8px; }
 .brand { width: min(100%, 193px); }
 .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, var(--safe-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%, 170px); }
 .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; }

.reward-dialog:focus { outline: none; }
.daily-challenge-copy { margin: 14px 0 10px; font-size: 14px; line-height: 1.5; color: var(--muted); }
/* R2.2 credit stream: one gold drop per cleared cell, positioned and animated by app.js (launchStreamDrop); in
   .play-area under the score (65) and spend (66) pops. */
.credit-stream {
  position: fixed; z-index: 64; pointer-events: none; transform-origin: 0 0; border-radius: 4px;
  background: linear-gradient(145deg, var(--gold-light), var(--gold) 55%, var(--gold-dark));
  box-shadow: 0 0 6px rgba(255, 214, 120, .9);
  will-change: transform, opacity;
}

/* R2.3 leaderboard (Stage A design, owner-approved in docs/r2.3-preview/preview.css). Dialog-only: nothing here is
   on the drag path. */
.lb-icon { display: block; width: 24px; height: 24px; color: var(--gold-dark); }
.lb-footer-button .lb-icon { width: 25px; height: 25px; }

/* Welcome popup, Daily challenge window and blocked dialog: a full-width "[podium] Leaderboard" button. */
.dialog .lb-open { display: flex; align-items: center; justify-content: center; gap: 8px; color: var(--ink); font-size: 16px; }
.lb-open .lb-icon { width: 22px; height: 22px; }
.daily-dialog .lb-open { margin-top: 16px; }
#blocked-dialog .lb-open { margin-top: 9px; }

/* Leaderboard dialog. leaderboard.js sets --row-h so 15 rows fit the screen; the font follows the row. */
.lb-backdrop { padding: 10px; }
.lb-dialog {
  --row-h: 20px;
  --rows: 15;
  --lb-font: clamp(10px, min(calc(var(--row-h) * .6), 3.7vw), 15px);
  position: relative;
  width: min(100%, 440px);
  padding: 16px 10px 12px;
  display: flex;
  flex-direction: column;
  text-align: left;
}
.lb-dialog:focus { outline: none; }
.lb-side { display: contents; }
.lb-dialog h2 { order: 0; margin: 6px 44px 8px; font-size: 24px; line-height: 1.2; text-align: center; }
.dialog .lb-close { top: 6px; right: 4px; }
.lb-main { order: 2; display: grid; grid-template-columns: minmax(0, 1fr) 22px; grid-template-rows: auto auto; column-gap: 3px; font-size: var(--lb-font); }
.dialog .lb-nick-button { order: 3; min-height: 40px; margin-top: 8px; }
.dialog .lb-share { order: 4; min-height: 36px; margin-top: 2px; }

.lb-head, .lb-row { display: grid; grid-template-columns: 2.1em minmax(0, 1fr) 4.4em 2.6em repeat(2, 1.85em); align-items: center; column-gap: .25em; padding: 0 .3em; font-variant-numeric: tabular-nums; }
.lb-head { grid-column: 1; grid-row: 1; height: max(24px, var(--row-h)); margin-bottom: 2px; border-bottom: 1px solid #e8dfcd; color: var(--muted); font-weight: 800; }
.lb-head .lb-rank, .lb-head .lb-name { font-size: .9em; letter-spacing: .06em; }
.lb-rows { grid-column: 1; grid-row: 2; display: grid; grid-auto-rows: var(--row-h); align-content: start; min-height: calc(var(--row-h) * var(--rows)); touch-action: none; cursor: grab; }
.lb-row { height: var(--row-h); border-radius: 4px; color: var(--ink); line-height: 1.3; }
.lb-row[hidden] { display: none; }
.lb-row:nth-child(even) { background: rgba(117, 111, 100, .075); }
.lb-row.is-me { background: rgba(217, 168, 35, .34); box-shadow: inset 0 0 0 1.5px rgba(157, 109, 8, .7); font-weight: 800; }
.lb-row.is-auto .lb-name { color: var(--muted); }
.lb-rank { text-align: right; color: var(--muted); }
.lb-row.is-me .lb-rank { color: var(--ink); }
/* Breathing room: rank to name, and BEST to the gold column. */
.lb-main .lb-head > :nth-child(2), .lb-main .lb-row > :nth-child(2) { margin-left: .55em; }
.lb-main .lb-head > :nth-child(4), .lb-main .lb-row > :nth-child(4) { margin-left: .75em; }
.lb-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb-best { text-align: right; }
.lb-medal { text-align: center; }
/* No connection: the message sits over the empty rows. */
.lb-message { grid-column: 1; grid-row: 2; align-self: center; margin: 0; padding: 0 12px; color: var(--muted); font-size: 15px; line-height: 1.45; text-align: center; }
.lb-message[hidden] { display: none; }

/* Column headers sort: tap BEST or a medal; tap the active one again to flip the order. */
.dialog .lb-sort { width: auto; height: 100%; min-height: 0; margin: 0; padding: 0; border: 0; border-radius: 4px; background: none; display: flex; align-items: center; justify-content: center; gap: 0; font: inherit; font-weight: 800; color: var(--muted); cursor: pointer; }
.dialog .lb-sort.lb-best { justify-content: flex-end; font-size: .9em; letter-spacing: .06em; }
.lb-sort img { width: 1.3em; height: 1.3em; object-fit: contain; }
.lb-sort i { font-style: normal; font-size: .62em; width: .75em; text-align: center; }
.lb-sort.lb-best i:empty { width: 0; }
.dialog .lb-sort.is-active { color: var(--ink); box-shadow: inset 0 -2px var(--gold); border-radius: 0; }

/* Scroll bar: single arrows at its ends (one row), double arrows beyond them (home, then top / bottom). */
.lb-bar { grid-column: 2; grid-row: 2; display: flex; flex-direction: column; gap: 2px; }
.dialog .lb-bar button { width: 100%; height: max(22px, var(--row-h)); min-height: 0; margin: 0; padding: 0; border: 0; border-radius: 6px; background: rgba(117, 111, 100, .13); color: var(--ink); display: grid; place-items: center; }
.dialog .lb-bar button:active { background: rgba(217, 168, 35, .45); }
.lb-bar svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.lb-track { position: relative; flex: 1; min-height: 20px; margin: 1px 6px; border-radius: 999px; background: rgba(117, 111, 100, .12); touch-action: none; cursor: pointer; }
.lb-thumb { position: absolute; left: 0; right: 0; top: 0; border-radius: 999px; background: var(--wood); touch-action: none; cursor: grab; }

/* Short landscape screens: title and buttons move to a side column so the table gets the full height. */
@media (max-height: 500px) and (min-aspect-ratio: 1/1) {
  .lb-backdrop { padding: 6px; }
  .lb-dialog { width: min(100%, 620px); display: grid; grid-template-columns: 136px minmax(0, 1fr); column-gap: 12px; padding: 8px 8px 6px; }
  .lb-side { display: flex; flex-direction: column; min-height: 0; }
  .lb-dialog h2 { margin: 44px 0 6px; font-size: 22px; text-align: left; }
  .dialog .lb-close { left: 2px; right: auto; }
  .dialog .lb-nick-button { margin-top: auto; padding: 0 6px; font-size: 14px; }
  .dialog .lb-share { font-size: 14px; padding-inline: 4px; }
}
@media (max-width: 400px) { .lb-backdrop { padding: 6px; } }

/* Advanced settings: Leaderboard section above Reset to defaults, a blank line above it. */
.lb-settings { margin-top: 22px; }
.lb-switch-row .settings-label { font-size: 14px; line-height: 1.25; }
.lb-switch-row { padding: 2px 0; }
.lb-nick-row { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 2px 8px; padding: 4px 0 6px; }
.lb-nick-row .settings-label { flex: 0 0 104px; font-size: 14px; }
.lb-nick-row .nick-input { flex: 1; min-width: 0; height: 38px; }
.lb-nick-row .settings-caption { flex-basis: 100%; margin: 6px 0 0; font-size: 13px; line-height: 1.35; text-align: center; }
.lb-nick-msg.is-error { color: #b0432a; font-weight: 650; }
.nick-input {
  width: 100%; height: 42px; padding: 0 12px;
  border: 1px solid #cfc3ad; border-radius: 9px; background: #fff;
  font: inherit; font-size: 16px; color: var(--ink);
  user-select: text; -webkit-user-select: text;
}
.nick-input:focus { outline: 2px solid var(--wood); outline-offset: 0; border-color: transparent; }
.nick-input::placeholder { color: #a39c90; }
