/* Name the Dot. A daily game in the newspaper-games manner: a white
   page and a white masthead with a hairline under it, a slab serif for titles
   and Franklin for everything else. The game itself is broadcast hardware
   (black scorebug, turf, control bar) and a guess sheet ruled in ink, coded with
   highlighter: yellow = the guess you're making (and "close" on a comparison
   tile), pink = a miss, green = got it (and "match"). */

:root {
  --page: #ffffff;
  --masthead: #ffffff;
  --masthead-ink: #121212;
  --masthead-muted: #121212;
  --hairline: 1px solid #dcdcdc;
  --card: #ffffff;
  --ink: #0e0f10;
  --ink-2: #4b4f55;
  --rule: 1.5px solid var(--ink);
  --hl-now: #ffe135;
  --hl-miss: #ff6fab;
  --hl-hit: #5fd06e;
  --slab: #0e0f10;          /* scorebug + control bar */
  --slab-ink: #f4f5f5;
  --slab-muted: #b9bcc0;
  /* One family, Libre Franklin: heavy tight caps for every label and row, like
     a newspaper sports section; regular weights for sentences. */
  --caps: "Libre Franklin", "Franklin Gothic Medium", Arial, sans-serif;
  --text: "Libre Franklin", "Franklin Gothic Book", Arial, sans-serif;
  /* Titles: a slab serif, the way the newspaper games name themselves. */
  --serif: "Zilla Slab", "Rockwell", Georgia, serif;
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --fa: 1.6; /* field height ÷ width, set per play from its crop */
  --col: 560px; /* content column; the masthead lines up with it */
}
/* The field always fills the card's width; its height comes from the play's
   crop (see FieldView.crop), which is kept tight so long plays stay short. */
.field-wrap { position: relative; display: flex; justify-content: center; background: #1f4426; border-radius: 0 0 10px 10px; overflow: hidden; }
/* Rive moments (flag, GOT IT!, INCOMPLETE) play over the field, never catching taps. */
.field-wrap .moments { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }

* { box-sizing: border-box; }
html { background: var(--page); }
body { margin: 0; min-height: 100dvh; color: var(--ink); background: var(--page);
  font: 16px/1.5 var(--text); -webkit-font-smoothing: antialiased; }
::selection { background: var(--hl-now); color: var(--ink); }
:focus-visible { outline: 2.5px solid var(--ink); outline-offset: 2px; border-radius: 4px; }
a { color: inherit; text-underline-offset: 3px; }
button, input { font: inherit; color: inherit; }
[hidden] { display: none !important; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* ---------- masthead ---------- */
.masthead { position: sticky; top: 0; z-index: 5; display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: max(6px, env(safe-area-inset-top)) 8px 6px 16px; color: var(--masthead-ink);
  background: var(--masthead); border-bottom: var(--hairline); }
.wordmark { font: 700 22px/1 var(--serif); letter-spacing: -.01em; text-decoration: none; white-space: nowrap; }
.masthead-nav { display: flex; }
.masthead-nav a, .masthead-nav button { display: grid; place-items: center; width: 44px; height: 44px; padding: 0; border: 0;
  border-radius: 50%; background: transparent; color: var(--masthead-muted); cursor: pointer;
  transition: background-color .15s ease-out; }
.masthead-nav a:hover, .masthead-nav a[aria-current="page"], .masthead-nav button:hover { background: #f0f0f0; }
#soundBtn .off, #soundBtn[aria-pressed="false"] .on { display: none; }
/* Four icons and the wordmark have to share a 375px phone. */
@media (max-width: 400px) {
  .masthead { padding-left: 12px; padding-right: 4px; gap: 6px; }
  .wordmark { font-size: 19px; }
  .masthead-nav a, .masthead-nav button { width: 40px; }
  .masthead-nav svg { width: 22px; height: 22px; }
}
#soundBtn[aria-pressed="false"] .off { display: block; }
.masthead-nav svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.masthead-nav .dot { fill: currentColor; }

/* ---------- the page column ---------- */
.card { position: relative; width: min(100% - 16px, var(--col)); min-width: min(100% - 16px, 300px); margin: 0 auto 24px;
  padding: 14px 14px 28px; outline: none; background: var(--page); }

.meta { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; margin: 0 2px 10px;
  font: 800 12px/1.2 var(--caps); text-transform: uppercase; letter-spacing: .04em; color: var(--ink-2); }
.meta b { color: var(--ink); font-weight: 900; }
.meta > span:first-child { white-space: nowrap; flex-shrink: 0; }

/* ---------- broadcast frame: scorebug, field, controls ---------- */
.bug { position: relative; display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 12px;
  background: var(--slab); color: var(--slab-ink); border-radius: 10px 10px 0 0; padding: 10px 12px 10px 10px; }
.teams { display: grid; gap: 4px; }
.team { display: grid; grid-template-columns: 6px 1fr auto 28px; align-items: center; gap: 8px;
  font: 800 16px/1.2 var(--caps); letter-spacing: -.005em; }
.team i { width: 6px; height: 22px; border-radius: 2px; background: var(--c); }
.team .score { font-variant-numeric: tabular-nums; text-align: right; }
.team .poss { width: 12px; height: 8px; border-radius: 50%; background: #8a5426; justify-self: center; visibility: hidden; }
.team.has-ball .poss { visibility: visible; }
/* Two 22px rows 4px apart, level with the two team lines. */
.sit { display: grid; grid-template-rows: 22px 22px; gap: 4px; align-items: center; justify-items: end;
  text-align: right; border-left: 1px solid #34373b; padding-left: 12px;
  font: 500 13px/1.3 var(--caps); color: var(--slab-muted); letter-spacing: 0; }
.sit b { display: flex; justify-content: flex-end; align-items: center; gap: 6px; font-weight: 800; font-size: 17px; color: var(--slab-ink); }
.sit .clock { min-width: 50px; text-align: right; font-variant-numeric: tabular-nums; }
/* The live Rive scorebug sits over the HTML one, which stays underneath as the
   fallback until the file loads (and as the screen-reader text). */
.bug-live { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.bug.is-live > :not(.bug-live) { opacity: 0; } /* not visibility: screen readers still read it */

canvas { display: block; width: 100%; }
.field { width: 100%; aspect-ratio: 1 / var(--fa); background: #1f4426; }
/* Its own block under the field, never touching or covering it. */
canvas.field { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.controls { aspect-ratio: 440 / 40; margin-top: 6px; background: var(--slab); border-radius: 8px; touch-action: none; }

.meta .game { font-variant-numeric: tabular-nums; text-align: right; text-wrap: balance; }
.panel > .sheet { margin-top: 14px; }


/* ---------- the guess sheet ---------- */
.sheet { list-style: none; margin: 0; padding: 0; border-top: var(--rule); }
.call { display: grid; grid-template-columns: 46px minmax(0, 1fr) auto; align-items: stretch; min-height: 52px; border-bottom: var(--rule);
  font: 800 16px/1.15 var(--caps); text-transform: uppercase; letter-spacing: -.005em; transition: background-color .3s ease-out; }
.call .num { display: grid; place-items: center; border-right: var(--rule); font-weight: 900; font-size: 20px; font-variant-numeric: tabular-nums; }
.call .name { display: flex; align-items: center; padding: 0 12px; min-width: 0; overflow-wrap: anywhere; }
.call.is-empty { min-height: 44px; }
.call.is-now .num { align-content: center; gap: 1px; }
.call.is-now .num small { font: 700 10px/1 var(--caps); letter-spacing: .02em; }

/* A missed guess: the name, then four tiles comparing it with the answer.
   Green = match, yellow = close, white = off; arrows point toward the answer. */
.call .guess { grid-column: 2 / -1; display: grid; gap: 6px; padding: 8px 10px 9px 12px; min-width: 0; }
.call .guess .name { padding: 0; }
.cmp { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 5px; }
.cmp-cell { display: grid; justify-items: center; gap: 1px; padding: 4px 2px 5px; border: var(--rule); border-radius: 6px;
  background: var(--card); text-decoration: none; }
.cmp-cell small { font: 700 10px/1 var(--caps); letter-spacing: .04em; opacity: .75; }
.cmp-cell b { display: flex; align-items: center; gap: 2px; font: 900 14px/1.1 var(--caps); font-variant-numeric: tabular-nums; }
.cmp-cell svg { width: 9px; height: 9px; fill: currentColor; }
.cmp-cell.is-hit { background: var(--hl-hit); }
.cmp-cell.is-near { background: var(--hl-now); }
.call .got { display: flex; align-items: center; padding: 0 12px; font-size: 13px; letter-spacing: .04em; }

/* Highlighter: a filled row is a stroke of colour; the row that just
   changed gets the stroke swiped in left to right, then its tiles flip in
   one at a time. */
.call.is-miss, .call.is-hit { background: linear-gradient(var(--hl), var(--hl)) left / 100% 100% no-repeat; }
.call.is-miss { --hl: var(--hl-miss); }
.call.is-hit { --hl: var(--hl-hit); }
.call.is-fresh { animation: swipe .6s var(--ease-out) both; }
.call.is-fresh .cmp-cell { animation: flip .5s var(--ease-out) both; animation-delay: calc(.3s + var(--i) * .12s); }
@keyframes swipe { from { background-size: 0% 100%; } }
@keyframes flip { from { transform: perspective(300px) rotateX(-90deg); opacity: 0; } }
.call.is-miss .guess > .name { text-decoration: line-through; text-decoration-thickness: 2px; }
.call.is-hit { background: var(--hl-hit); }
.call.is-now { background: var(--hl-now); }
.call form, .composer form { position: relative; display: flex; align-items: center; gap: 8px; padding: 6px 6px 6px 10px; grid-column: 2 / -1; min-width: 0; }
/* Matches open below the guess box (.is-below), or above it when a phone keyboard leaves no room. */
.picks { position: absolute; left: 10px; right: 6px; bottom: calc(100% - 2px); z-index: 4; margin: 0; padding: 4px; list-style: none;
  border: var(--rule); border-radius: 10px; background: var(--card); box-shadow: 0 -10px 30px rgba(14,15,16,.22);
  animation: rise .18s var(--ease-out); }
.picks.is-below { bottom: auto; top: calc(100% - 2px); box-shadow: 0 10px 30px rgba(14,15,16,.22); }
.picks li { display: flex; align-items: center; min-height: 44px; padding: 0 12px; border-radius: 7px; cursor: pointer;
  font: 800 16px/1.15 var(--caps); text-transform: uppercase; letter-spacing: -.005em; }
.picks li[aria-selected="true"] { background: var(--hl-now); }
.picks li:hover { background: #eef0f0; }
.picks li[aria-selected="true"]:hover { background: var(--hl-now); }
.call input, .composer input { flex: 1; min-width: 0; height: 44px; padding: 0 12px; border: var(--rule); border-radius: 8px; background: var(--card);
  font: 500 16px var(--text); text-transform: none; letter-spacing: 0; caret-color: var(--ink); }
.call input::placeholder, .composer input::placeholder { color: #6a6e74; }
.call input[aria-invalid="true"], .composer input[aria-invalid="true"] { border-color: #b3123f; box-shadow: 0 0 0 2px rgba(179,18,63,.18); }
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 44px; padding: 0 18px;
  border: 0; border-radius: 999px; background: var(--ink); color: var(--card); cursor: pointer; text-decoration: none;
  font: 800 15px/1 var(--caps); text-transform: uppercase; letter-spacing: .02em; transition: background-color .15s ease-out, transform .15s var(--ease-out); }
.btn:hover { background: #2a2d31; }
.btn:active { transform: scale(.97); }
.btn.ghost { background: transparent; color: var(--ink); box-shadow: inset 0 0 0 1.5px var(--ink); }
.btn.ghost:hover { background: #eef0f0; }
:is(.call, .composer) .btn { min-height: 44px; padding: 0 14px; border-radius: 8px; } /* squared to match the box beside it */
.form-error { margin: 6px 2px 0; min-height: 1.4em; font-size: 14px; font-weight: 500; color: #b3123f; }

/* Phones: the guess box in the page is a button (composer.js) that opens a
   bar pinned above the keyboard, with the matches stacked upward over the
   play, best match nearest the box. */
.guess-open { grid-column: 2 / -1; align-self: center; min-width: 0; height: 44px; margin: 6px 6px 6px 10px; padding: 0 12px;
  border: var(--rule); border-radius: 8px; background: var(--card); color: #6a6e74; text-align: left; cursor: text;
  font: 500 16px var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.composer { position: fixed; inset: 0; z-index: 60; }
.composer-backdrop { position: absolute; inset: 0; background: rgba(14,15,16,.5); animation: fade .15s ease-out; }
.composer-panel { position: absolute; left: 0; right: 0; bottom: 0; padding: 4px 4px calc(4px + env(safe-area-inset-bottom));
  background: var(--hl-now); box-shadow: 0 -6px 24px rgba(14,15,16,.25); }
.composer-panel form { padding: 6px; }
.composer-panel .form-error { margin: 4px 10px 0; }
.composer-panel .form-error:empty { display: none; }
.composer .picks, .composer .picks.is-below { top: auto; bottom: calc(100% + 8px); left: 6px; right: 6px;
  display: flex; flex-direction: column-reverse; box-shadow: 0 -10px 30px rgba(14,15,16,.3); }
@keyframes fade { from { opacity: 0; } }

/* The sheet folds shut when the game ends; the reveal opens in its place. */
.sheet.is-folding { overflow: hidden; transition: height .45s var(--ease-out) .25s, opacity .3s ease-out .25s; opacity: 0; }

/* ---------- reveal ---------- */
.reveal { margin-top: 22px; }
/* A fresh finish brings each part in on its own beat (--d); a revisit, or
   anyone who prefers less motion, gets the finished page at once. */
.reveal.is-fresh :is(.answer-meta, .scorecard, .box-row, .share-row, .the-play) { animation: rise .5s var(--ease-out) both; animation-delay: var(--d); }
.reveal.is-fresh .answer { animation: land .8s var(--ease-out) both; animation-delay: var(--d); }
.reveal.is-fresh .share-row .share { animation: pop .45s var(--ease-out) both; animation-delay: calc(var(--d) + .05s); }
.dist.is-growing { animation: rise .4s var(--ease-out) both; animation-delay: var(--d); }
.dist.is-growing .fill { animation: grow .7s var(--ease-out) both; animation-delay: calc(var(--d) + var(--i) * .07s); }
@keyframes rise { from { opacity: 0; transform: translateY(10px); } }
@keyframes pop { 0% { opacity: 0; transform: scale(.6); } 70% { opacity: 1; transform: scale(1.06); } 100% { transform: none; } }
@keyframes grow { from { clip-path: inset(0 100% 0 0 round 4px); } to { clip-path: inset(0 0 0 0 round 4px); } }
.answer { margin: 0; font: 700 clamp(34px, 9vw, 46px)/1.05 var(--serif); letter-spacing: -.01em; text-wrap: balance; overflow-wrap: anywhere; }
@keyframes land { from { clip-path: inset(0 100% 0 0); transform: translateX(-6px); } to { clip-path: inset(0 0 0 0); transform: none; } }
.answer-meta { margin: 6px 0 0; font: 500 15px var(--text); color: var(--ink-2); }
.scorecard { display: block; width: 100%; aspect-ratio: 440 / 110; margin: 18px 0 16px; }
/* The play's call, under the name like a photo caption. */
.the-play { margin: 10px 0 0; font: 400 15px/1.45 var(--text); color: var(--ink); text-wrap: pretty; }

/* Box score: each guess on one line, its number then the name. */
.box { list-style: none; margin: 0; padding: 0; border-top: var(--rule); }
.box-row { display: grid; grid-template-columns: 26px minmax(0, 1fr) auto; align-items: center; gap: 10px; min-height: 34px;
  border-bottom: var(--rule); font: 800 14px/1.15 var(--caps); text-transform: uppercase; }
.box-row .n { font-weight: 900; text-align: center; font-variant-numeric: tabular-nums; }
.box-row .nm { min-width: 0; overflow-wrap: anywhere; }
.box-row.is-miss .nm { color: var(--ink-2); }
.box-row .got { font-size: 11px; letter-spacing: .04em; padding: 3px 7px; border-radius: 4px; background: var(--hl-hit); }


.dist { margin-top: 22px; }
.dist h3, .section-title { margin: 0 0 8px; font: 800 12px var(--caps); text-transform: uppercase; letter-spacing: .04em; color: var(--ink-2); }
.bar { display: grid; grid-template-columns: 30px 1fr; align-items: center; gap: 8px; margin: 5px 0; font: 700 14px var(--caps); }
.bar span:first-child { text-align: center; }
.bar .fill { min-width: 34px; width: var(--w); padding: 3px 8px; border-radius: 4px; background: #e2e4e6; text-align: right;
  font-variant-numeric: tabular-nums; transition: width .6s var(--ease-out); }
.bar.is-you .fill { background: var(--hl-hit); }
.bar.is-miss-row .fill { background: #f4d4e2; }
.bar.is-miss-row.is-you .fill { background: var(--hl-miss); }
.dist p { margin: 8px 0 0; font-size: 14px; color: var(--ink-2); }

/* Like the newspaper games' results: the countdown on the left, a thin rule,
   and the Share button on the right. */
.share-row { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 0 20px; margin-top: 20px; }
.share-row::before { content: ""; grid-column: 2; grid-row: 1; width: 1px; height: 52px; background: var(--ink); }
.share { grid-column: 3; grid-row: 1; justify-self: start; min-width: 150px; min-height: 52px; font-size: 17px; }
.share .morph-in { display: inline-block; animation: label-in .28s var(--ease-out) both; }
@keyframes label-in { from { opacity: 0; transform: translateY(8px); filter: blur(3px); } }
.share-row .next { grid-column: 1; grid-row: 1; justify-self: end; display: grid; text-align: center; }
.share-row .next small { font: 800 12px var(--caps); text-transform: uppercase; letter-spacing: .04em; }
.share-row .next b { font: 400 30px/1.15 var(--text); font-variant-numeric: tabular-nums; }
.toast { margin: 8px 0 0; font-size: 14px; font-weight: 500; }
.toast:empty { display: none; }

/* ---------- pages ---------- */
.page-title { margin: 12px 0 16px; font: 700 34px/1.05 var(--serif); letter-spacing: -.01em; text-wrap: balance; }
.lede { margin: 0 0 20px; font-size: 17px; color: var(--ink-2); max-width: 46ch; }

.page-actions { margin-top: 22px; }

/* Rules: a dialog over today's play, dismissed to start it. */
dialog#rules { width: min(100% - 32px, 400px); max-height: calc(100dvh - 32px); padding: 22px 20px 20px; border: 0;
  border-radius: 8px; background: var(--card); color: var(--ink); box-shadow: 0 4px 23px rgba(0, 0, 0, .2); }
dialog#rules::backdrop { background: rgba(255, 255, 255, .7); }
dialog#rules[open] { animation: deal .32s var(--ease-out); }
@keyframes deal { from { opacity: 0; transform: translateY(14px) scale(.98); } }
dialog#rules .x { position: absolute; top: 10px; right: 10px; margin: 0; }
dialog#rules .x button { display: grid; place-items: center; width: 44px; height: 44px; border: 0; border-radius: 10px; background: transparent; color: var(--ink); cursor: pointer; }
dialog#rules .x button:hover { background: #eef0f0; }
dialog#rules .x svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; }
.call.is-now { scroll-margin-bottom: 12px; }
dialog#rules h2 { margin: 0 0 14px; font: 700 30px/1 var(--serif); letter-spacing: -.01em; }
.rules { list-style: none; margin: 0; padding: 0; border-top: var(--rule); }
.rules li { display: grid; grid-template-columns: 34px 1fr; align-items: start; gap: 0 12px; padding: 12px 0; border-bottom: var(--rule); }
.rules .num { font: 900 18px/1.4 var(--caps); text-align: center; }
.rules p { margin: 0; font-size: 15px; line-height: 1.45; color: var(--ink-2); }
.rules p b { color: var(--ink); font-weight: 700; }
/* The playmaker's dot, sized to the text and centred on its middle. */
.rules .q { display: inline-grid; place-items: center; width: 18px; height: 18px; border-radius: 50%; background: var(--hl-now);
  color: var(--ink); font: 900 11px/1 var(--caps); vertical-align: middle; position: relative; top: -1px; }
.rules .nowrap { white-space: nowrap; }
.rules .cmp { grid-column: 2; margin-top: 10px; }
.rules-note { margin: 14px 0 16px; font-size: 14px; color: var(--ink-2); }
dialog#rules .btn { width: 100%; }

.stat-grid { display: flex; gap: 28px; margin: 0 0 28px; border: 0; }
.stat-grid .call { display: flex; flex-direction: column-reverse; align-items: flex-start; min-height: 0; border: 0; text-transform: none; }
.stat-grid .call .name { padding: 0; font: 500 13px/1.3 var(--text); }
.stat-grid .call .val { font: 400 40px/1.1 var(--text); font-variant-numeric: tabular-nums; letter-spacing: -.01em; }

/* Each cell carries its own rule (centred on its edge), so a launch-day
   archive of one cell still reads as a closed box. */
.archive { display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); padding: 1px; }
.cell { position: relative; display: grid; place-items: center; gap: 2px; aspect-ratio: 1; padding: 6px; text-decoration: none;
  outline: 1.5px solid var(--ink); outline-offset: -0.75px; transition: background-color .15s ease-out; }
.cell:hover { background: #eef0f0; }
.cell b { position: relative; font: 900 30px/1 var(--caps); font-variant-numeric: tabular-nums; }
.cell small { font: 800 11px var(--caps); text-transform: uppercase; letter-spacing: .04em; color: var(--ink-2); }
/* The pen mark is drawn around the numeral (and today's underline), not the cell. */
.cell b svg { position: absolute; left: -18px; top: -12px; width: calc(100% + 36px); height: calc(100% + 24px);
  pointer-events: none; overflow: visible; }
.cell.is-solved svg ellipse { fill: none; stroke: #1a8f36; stroke-width: 3; stroke-linecap: round; }
.cell.is-missed svg line { stroke: #c2185b; stroke-width: 3; stroke-linecap: round; }
.cell.is-open { background: var(--hl-now); }
.cell.is-today b::after { content: ""; display: block; height: 3px; margin-top: 3px; background: var(--ink); border-radius: 2px; }
.legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 14px 0 0; font-size: 14px; color: var(--ink-2); }

.empty { padding: 28px 0; }
.empty p { color: var(--ink-2); max-width: 44ch; }

@media (min-width: 720px) {
  /* A narrower column keeps the control bar in proportion to the field. On a tablet the field must not push the guess box off the screen: the
     column narrows until field, bar and the guess row fit the window. */
  :root { --col: clamp(380px, calc((100dvh - 330px) / var(--fa, 1.4) + 44px), 480px); }
  .card { margin-top: 12px; padding: 20px 22px 32px; }
  /* The masthead's wordmark and icons line up with the column's edges. */
  .masthead { padding-inline: max(16px, calc((100% - var(--col)) / 2)); }
  .masthead-nav { margin-right: -10px; }
}

/* Wide screens: two cards side by side. The play card (date line, scorebug,
   field, bar) stays in view on the left; the guesses card sits beside it and
   is only as tall as what's in it. The field's width follows the window's
   height, so a long play fits and a tall screen gets a bigger field. */
@media (min-width: 900px) {
  :root { --stage: clamp(320px, calc((100dvh - 330px) / var(--fa, 1.4)), 560px); --side: 420px; --gap: 40px;
    --col: calc(var(--stage) + 32px + var(--gap) + var(--side)); }
  body:not(:has(.play-grid)) { --col: 480px; } /* stats and archive keep the single column, masthead included */
  .card:has(.play-grid) { padding: 0; margin-top: 20px; }
  .play-grid { display: grid; grid-template-columns: calc(var(--stage) + 32px) var(--side); gap: var(--gap); align-items: start; }
  .side { display: grid; gap: var(--gap); }
  /* No boxes on the white page: the play and the guesses sit side by side. */
  .stage, .panel { padding: 0; }
  .stage { position: sticky; top: 64px; }
  .panel > .sheet { margin-top: 0; }
  .call { min-height: 60px; }
  .call.is-empty { min-height: 52px; }
  .form-error:empty { display: none; }
  .reveal { margin-top: 0; }
  .answer { font-size: clamp(38px, 4vw, 52px); }
}

/* Local test mode only (see testBar in app.js). */
.test-bar { position: fixed; left: 50%; bottom: 12px; z-index: 20; transform: translateX(-50%); display: flex; align-items: center; gap: 6px;
  padding: 6px 8px 6px 12px; border-radius: 12px; background: #ff6fab; color: var(--ink); box-shadow: 0 8px 24px rgba(0,0,0,.35);
  font: 800 12px var(--caps); text-transform: uppercase; letter-spacing: .03em; white-space: nowrap; }
.test-bar button { min-width: 36px; height: 36px; padding: 0 10px; border: 0; border-radius: 8px; background: var(--ink); color: #fff;
  font: inherit; font-size: 16px; cursor: pointer; }
.test-bar button[data-reset] { font-size: 11px; }
.test-bar button:disabled { opacity: .35; cursor: default; }
body:has(.test-bar) { padding-bottom: 64px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}
