/* ==========================================================================
   SPINDLE — "a 1981 puzzle toy, re-tooled by a letterpress shop"
   Agent A: markup + style.  Nothing here writes a JS-owned property
   (#orbit/.layer/.cubie/.sticker transform, .glyph transform + transition).
   Nothing here puts a grouping property (filter/opacity/overflow/clip-path/
   mask/mix-blend-mode/isolation/contain) on #stage, #orbit, #cubeRoot,
   .layer, .cubie or .sticker. See CONTRACT.md §7.1.
   ========================================================================== */

/* --------------------------------------------------------------- 0. reset */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
[hidden] { display: none !important; }

/* --------------------------------------------------------------- 1. ink   */
:root {
  --paper:      #E7DCC3;   /* cream stock — never white                     */
  --paper-2:    #D8CBAA;   /* halftone dot                                  */
  --ink-black:  #14110E;   /* warm dead-matte black                         */
  --tile:       #EFE7D4;   /* bone-cream sticker                            */
  --tile-edge:  #C9BE9F;   /* letterpress bite along the die-cut edge        */
  --body-black: #17140F;   /* cube body / grout                             */

  --ink-0: #C0271D;  --on-0: var(--tile);       /* pillar-box red   U */
  --ink-1: #D9A21B;  --on-1: var(--ink-black);  /* mustard          D */
  --ink-2: #1E7A72;  --on-2: var(--tile);       /* teal             F */
  --ink-3: #6B7A2B;  --on-3: var(--tile);       /* olive            B */
  --ink-4: #6E2547;  --on-4: var(--tile);       /* plum             R */
  --ink-5: #DC5B1C;  --on-5: var(--ink-black);  /* orange           L */

  --accent: var(--ink-0);  /* the ONE colour allowed before anything solves */

  --font-display: "Futura","Futura PT","Century Gothic","Avenir Next","Trebuchet MS",system-ui,sans-serif;
  --font-glyph:   "Avenir Next Condensed","Helvetica Neue Condensed","Arial Narrow","Roboto Condensed",system-ui,sans-serif;

  --peek: calc(54px + env(safe-area-inset-bottom, 0px));
  --rule: 1px solid var(--ink-black);

  color-scheme: light;
}

/* --------------------------------------------------------- 2. page shell  */
html, body {
  overflow: hidden;
  overscroll-behavior: none;
  -webkit-text-size-adjust: 100%;
}
html { background: var(--paper); }

body {
  position: fixed;
  inset: 0;
  touch-action: manipulation;
  background: transparent;
  color: var(--ink-black);
  font-family: var(--font-display);
  font-size: 15px;
  line-height: 1.35;
  -webkit-font-smoothing: antialiased;

  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: auto minmax(0, 1fr) auto auto;
  grid-template-areas: "hud" "board" "diff" "ctrl";
  padding-top: env(safe-area-inset-top, 0px);
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
  padding-bottom: var(--peek);
}

/* the paper stock: flat cream + a 6px halftone. never a gradient.
   #hud/#controls/#difficulty are opaque so the cube can never bleed over them. */
#field, #rail, #guess, #hud, #controls, #difficulty {
  background-color: var(--paper);
  background-image: radial-gradient(var(--paper-2) 1px, transparent 1.3px);
  background-size: 6px 6px;
}
#field {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

::selection { background: var(--ink-black); color: var(--paper); }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --------------------------------------------------------------- 3. hud   */
#hud {
  grid-area: hud;
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 9px 14px 8px;
  border-bottom: var(--rule);
}
/* the thin half of a letterpress double rule */
#hud::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -5px;
  height: 1px;
  background: var(--ink-black);
  opacity: .38;
}
body[data-state="scrambling"] #hud::after {
  background: var(--accent);
  opacity: 1;
  animation: pressRun 640ms steps(2, end) infinite;
}
@keyframes pressRun { 50% { opacity: .12; } }

#wordmark {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(20px, 6.6vw, 27px);
  line-height: 1;
  letter-spacing: -0.02em;
  text-transform: uppercase;
}
#puzzleNo {
  margin-right: auto;
  align-self: center;
  font: 700 9px/1 var(--font-display);
  letter-spacing: .17em;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid var(--accent);
  padding: 3px 5px 2px;
  white-space: nowrap;
}
/* run counters — same label-over-value idiom as #timer, dialled back so the
   clock stays the loudest thing in the bar. #misses is [hidden] until you miss. */
#moves, #misses {
  text-align: right;
  font: 700 13px/1 var(--font-display);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  opacity: .62;
}
#moves::before, #misses::before {
  display: block;
  font-size: 8px;
  letter-spacing: .2em;
  opacity: .62;
  margin-bottom: 3px;
}
#moves::before  { content: "TURNS"; }
#misses::before { content: "MISS"; }
#misses { color: var(--accent); opacity: 1; }
#misses[hidden] { display: none; }

#timer {
  text-align: right;
  font: 700 17px/1 var(--font-display);
  font-variant-numeric: tabular-nums;
  letter-spacing: .01em;
  white-space: nowrap;
}
#timer::before {
  content: "TIME";
  display: block;
  font-size: 8px;
  letter-spacing: .2em;
  opacity: .5;
  margin-bottom: 3px;
}

/* --------------------------------------------------- 4. board + the cube  */
/* Two rows: the stage takes what's left, #intro takes what it needs. #intro used
   to be absolutely positioned over the bottom of this area, which meant the cube
   centred itself inside a box a third of which was covered — it got sliced through
   the bottom row of tiles on the landing screen with dead cream above it. In flow,
   the stage measures only the space it actually owns. Row 2 collapses to 0 once
   #intro is [hidden], so play states get the full height back. #badge is absolute
   and so takes no grid slot. */
#board {
  grid-area: board;
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr) auto;
}

/* ---- verbatim geometry (CONTRACT.md §7.3) — do not "improve" ------------ */
#stage   { position:relative; overflow:visible; touch-action:none;
           perspective: var(--persp, 900px); perspective-origin:50% 50%; }
#orbit   { position:absolute; left:50%; top:50%; width:0; height:0;
           transform-style:preserve-3d; }
#cubeRoot, .layer { position:absolute; left:0; top:0; width:0; height:0;
           transform-style:preserve-3d; }
.cubie   { position:absolute; width:var(--cubie); height:var(--cubie);
           left:calc(var(--cubie) / -2); top:calc(var(--cubie) / -2);
           transform-style:preserve-3d; }
.sticker { position:absolute; inset:0; transform-style:flat;
           backface-visibility:hidden; }
.tile,.glyph { pointer-events:none; }
.tile    { position:absolute; inset:0; }
.glyph   { position:absolute; inset:0; display:grid; place-items:center; }
/* ---- end verbatim block ------------------------------------------------- */

/* The contract's sizing (§4.5, CUBIE = min(stage w,h)/4.6) makes the projected
   cube ~0.99 x the stage's short axis, so a full-bleed stage clips the cube at
   oblique orbit angles. Inset the stage instead — overflow stays visible, the
   cube keeps a real margin, and cube.js's formula is untouched. */
#stage {
  grid-area: 1 / 1;
  place-self: center;
  width: 82%;
  height: 82%;
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}
#stage:active { cursor: grabbing; }

/* The sticker is the black cube body. Its padding is what insets the tile,
   so the verbatim `.tile{inset:0}` stays true and we still get deep grout. */
.sticker {
  --ink: var(--ink-black);
  --ink-on: var(--tile);
  padding: 4%;
  background-color: var(--body-black);
  background-image: linear-gradient(rgba(0, 0, 0, var(--shade, .1)) 0 0);
}
.sticker.is-blank { background-color: #0E0C09; }

/* light from above-front-left; alphas fixed by CONTRACT.md §7.4 */
.sticker[data-wf="0"] { --shade: 0;   }  /* U */
.sticker[data-wf="2"] { --shade: .07; }  /* F */
.sticker[data-wf="5"] { --shade: .12; }  /* L */
.sticker[data-wf="4"] { --shade: .18; }  /* R */
.sticker[data-wf="3"] { --shade: .22; }  /* B */
.sticker[data-wf="1"] { --shade: .26; }  /* D */

/* home face → spot colour + the type colour that survives on top of it */
.sticker[data-hf="0"], .cat[data-face="0"] { --ink: var(--ink-0); --ink-on: var(--on-0); }
.sticker[data-hf="1"], .cat[data-face="1"] { --ink: var(--ink-1); --ink-on: var(--on-1); }
.sticker[data-hf="2"], .cat[data-face="2"] { --ink: var(--ink-2); --ink-on: var(--on-2); }
.sticker[data-hf="3"], .cat[data-face="3"] { --ink: var(--ink-3); --ink-on: var(--on-3); }
.sticker[data-hf="4"], .cat[data-face="4"] { --ink: var(--ink-4); --ink-on: var(--on-4); }
.sticker[data-hf="5"], .cat[data-face="5"] { --ink: var(--ink-5); --ink-on: var(--on-5); }

.tile {
  background-color: var(--tile);
  border-radius: 11%;
  box-shadow:
    inset 0 0 0 0 rgba(20, 17, 14, 0),
    inset 0 0 0 1px var(--tile-edge),
    inset 0 1.5px 0 rgba(255, 255, 255, .5),
    inset 0 -1.5px 0 rgba(20, 17, 14, .1);
  transition:
    background-color 170ms ease var(--ink-delay, 0ms),
    box-shadow 170ms ease var(--ink-delay, 0ms);
}

/* directional shading — leaf only, never an ancestor of a preserve-3d node */
.tile::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: #000;
  opacity: var(--shade, 0);
  transition: opacity 180ms ease;
}

/* Registration dot: this pair is physically home. It hangs off .glyph, not
   .tile, because .glyph carries the JS-owned counter-rotation — on .tile the
   dot would land in a different corner per cubie and leak piece orientation. */
.glyph::after {
  content: "";
  position: absolute;
  left: 9%; top: 9%;
  width: max(3px, calc(var(--cubie, 60px) * .11));
  height: max(3px, calc(var(--cubie, 60px) * .11));
  border-radius: 50%;
  background: currentColor;
  opacity: 0;
  transition: opacity 150ms ease;
}
.sticker.is-placed:not(.is-ink) .glyph::after { opacity: .5; }
.sticker.is-placed:not(.is-ink) .tile {
  box-shadow:
    inset 0 0 0 1.5px rgba(20, 17, 14, .5),
    inset 0 0 0 1px var(--tile-edge),
    inset 0 1.5px 0 rgba(255, 255, 255, .5),
    inset 0 -1.5px 0 rgba(20, 17, 14, .1);
}
#cubeRoot.is-turning .glyph::after { transition: none; }

/* the stamp coming down */
.sticker.is-ink .tile { background-color: var(--ink); }

.glyph {
  font-family: var(--font-glyph);
  font-weight: 700;
  font-size: calc(var(--cubie, 60px) * .40);
  line-height: 1;
  letter-spacing: -0.04em;
  color: var(--ink-black);
  text-shadow: 0 1px 0 rgba(255, 255, 255, .5);
  user-select: none;
  -webkit-user-select: none;
}
.sticker.is-ink .glyph { color: var(--ink-on); text-shadow: 0 1px 0 rgba(0, 0, 0, .18); }
/* hold charcoal until the ink has actually landed, then knock the type out */
.sticker.is-ink .glyph { animation: inkFlip 170ms steps(1, end) var(--ink-delay, 0ms) both; }
@keyframes inkFlip {
  from { color: var(--ink-black); text-shadow: 0 1px 0 rgba(255, 255, 255, .5); }
  to   { color: var(--ink-on);    text-shadow: 0 1px 0 rgba(0, 0, 0, .18); }
}

/* ------------------------------------------------------------- 5. badge   */
#badge {
  position: absolute;
  top: 4px;
  right: 6px;
  z-index: 2;
  width: clamp(62px, 19vw, 88px);
  aspect-ratio: 1;
  pointer-events: none;
  transform: rotate(-9deg);
  transition: opacity 400ms ease;
}
#badge svg { display: block; width: 100%; height: 100%; }
#badge polygon { fill: var(--accent); }
#badge text {
  text-anchor: middle;
  fill: var(--tile);
  font-family: var(--font-display);
  font-weight: 800;
}
.badge__big   { font-size: 28px; letter-spacing: -1.2px; }
.badge__small { font-size: 10.5px; letter-spacing: 1.7px; }
body[data-state="solved"] #badge,
body[data-state="gaveup"] #badge { opacity: 0; }

/* ------------------------------------------------------------- 6. intro   */
#intro {
  position: relative;   /* in grid row 2, not overlaying the cube (see #board) */
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 9px;
  padding: 15px 16px 17px;
  background-color: var(--paper);
  background-image: radial-gradient(var(--paper-2) 1px, transparent 1.3px);
  background-size: 6px 6px;
  border-top: var(--rule);
}
#intro::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: -5px;
  height: 1px;
  background: var(--ink-black);
  opacity: .38;
}
body:not([data-state="intro"]) #intro { display: none; }

.intro__lead {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(19px, 5.6vw, 23px);
  line-height: 1.05;
  letter-spacing: -0.015em;
  text-wrap: balance;
}
.intro__sub {
  max-width: 46ch;
  font: 400 13px/1.42 var(--font-display);
  opacity: .78;
}

/* -------------------------------------------------------- 7. difficulty   */
#difficulty {
  grid-area: diff;
  position: relative;
  z-index: 1;
  border: 0;
  border-top: var(--rule);
  padding: 8px 14px 9px;
}
/* float+100% takes <legend> out of its special "notch the border" rendering
   so the fieldset's top rule draws unbroken */
#difficulty legend {
  float: left;
  width: 100%;
  padding: 0 0 6px;
  font: 700 9px/1 var(--font-display);
  letter-spacing: .2em;
  color: var(--accent);
}
#difficulty label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-right: 14px;
  font: 700 11px/1 var(--font-display);
  letter-spacing: .13em;
  cursor: pointer;
}
#difficulty input {
  appearance: none;
  -webkit-appearance: none;
  width: 13px; height: 13px;
  flex: 0 0 auto;
  border: 1.5px solid var(--ink-black);
  background: var(--tile);
  cursor: pointer;
}
#difficulty input:checked {
  background: var(--accent);
  box-shadow: inset 0 0 0 2px var(--tile);
}
#difficulty label:has(:checked) { color: var(--accent); }

/* ---------------------------------------------------------- 8. controls   */
#controls {
  grid-area: ctrl;
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 9px 14px;
  border-top: var(--rule);
  min-height: 52px;
}
#giveUpBtn { margin-left: auto; }
/* in terminal states #giveUpBtn is hidden, so #shareBtn needs the auto margin */
#shareBtn { margin-left: auto; }
#againBtn { margin-left: 8px; }
body:not([data-state="playing"]) #turnpadToggle,
body:not([data-state="playing"]) #giveUpBtn {
  visibility: hidden;
  pointer-events: none;
}
body:not([data-state="playing"]) #turnpad { display: none; }
/* Overlaid, never in flow: opening the pad must not resize #stage, because
   cube.js only re-measures on window resize. */
#turnpad {
  position: absolute;
  left: 0; right: 0; bottom: 100%;
  z-index: 4;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 5px;
  padding: 10px 14px 11px;
  background-color: var(--paper);
  background-image: radial-gradient(var(--paper-2) 1px, transparent 1.3px);
  background-size: 6px 6px;
  border-top: var(--rule);
}

/* ------------------------------------------------------------ 9. buttons  */
button {
  font-family: var(--font-display);
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
button:disabled { opacity: .3; cursor: default; }

/* primary — black block with a red overprint slipped out of register */
#startBtn, #guessSubmit, #winShare {
  align-self: flex-start;
  background: var(--ink-black);
  color: var(--paper);
  font: 800 13px/1 var(--font-display);
  letter-spacing: .16em;
  text-transform: uppercase;
  padding: 14px 24px 13px;
  box-shadow: 4px 4px 0 var(--accent);
  transition: transform 90ms ease, box-shadow 90ms ease;
}
#startBtn:active, #guessSubmit:active, #winShare:active {
  transform: translate(4px, 4px);
  box-shadow: 0 0 0 var(--accent);
}

/* secondary — hairline outline that inverts on press */
#turnpadToggle, #giveUpBtn, #shareBtn, #againBtn, #guessClose, #winFree,
.cat__guess, .cat__hint, .turnbtn {
  background: transparent;
  color: var(--ink-black);
  border: var(--rule);
  font: 800 11px/1 var(--font-display);
  letter-spacing: .14em;
  text-transform: uppercase;
  padding: 11px 12px 10px;
  transition: background-color 110ms ease, color 110ms ease;
}
#turnpadToggle:hover, #giveUpBtn:hover, #shareBtn:hover, #againBtn:hover, #guessClose:hover,
#winFree:hover, .cat__guess:hover, .cat__hint:hover, .turnbtn:hover,
#turnpadToggle:active, #giveUpBtn:active, #shareBtn:active, #againBtn:active, #guessClose:active,
#winFree:active, .cat__guess:active, .cat__hint:active, .turnbtn:active {
  background: var(--ink-black);
  color: var(--paper);
}
#turnpadToggle[aria-expanded="true"] { background: var(--ink-black); color: var(--paper); }

.turnbtn {
  padding: 0;
  height: 44px;
  font: 700 16px/1 var(--font-glyph);
  letter-spacing: 0;
}

/* --------------------------------------------------------- 10. clue rail  */
#rail {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  min-height: 0;
  max-height: 72vh;
  max-height: 72dvh;
  border-top: 2px solid var(--ink-black);
  transform: translateY(calc(100% - var(--peek)));
  transition: transform 280ms cubic-bezier(.22, .68, .34, 1);
}
body[data-sheet="open"] #rail { transform: translateY(0); }
/* belt-and-braces: honour aria-expanded too (separate rule so an engine
   without :has() drops only this one) */
#rail:has(#railHandle[aria-expanded="true"]) { transform: translateY(0); }

#railHandle {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: var(--peek);
  padding: 0 16px env(safe-area-inset-bottom, 0px);
  text-align: left;
  border-bottom: var(--rule);
}
.rail__label {
  font: 800 10px/1 var(--font-display);
  letter-spacing: .22em;
  color: var(--accent);
}
#railSummary {
  margin-right: auto;
  font: 800 13px/1 var(--font-display);
  letter-spacing: .1em;
  font-variant-numeric: tabular-nums;
}
.rail__chev {
  width: 11px; height: 11px;
  border-right: 2px solid var(--ink-black);
  border-bottom: 2px solid var(--ink-black);
  transform: rotate(-135deg) translate(-2px, -2px);
  transition: transform 260ms cubic-bezier(.22, .68, .34, 1);
}
body[data-sheet="open"] .rail__chev { transform: rotate(45deg) translate(-2px, -2px); }

#railList {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding-bottom: calc(20px + env(safe-area-inset-bottom, 0px));
}

/* --------------------------------------------------- 11. category cards   */
.cat {
  --ink: var(--ink-black);
  --ink-on: var(--paper);
  padding: 12px 16px 14px;
  border-bottom: 1px solid rgba(20, 17, 14, .3);
  transition: background-color 260ms ease, color 260ms ease, box-shadow 140ms ease;
}
.cat__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.cat__name {
  font: 800 16px/1 var(--font-display);
  letter-spacing: .06em;
  text-transform: uppercase;
}
.cat__count {
  font: 700 10px/1 var(--font-display);
  letter-spacing: .14em;
  font-variant-numeric: tabular-nums;
  opacity: .55;
}
.cat__rows {
  list-style: none;
  margin-top: 11px;
  display: grid;
  gap: 7px;
}
.cat__row {
  display: flex;
  align-items: center;
  gap: 7px;
}
.cat__row::before {
  content: "";
  flex: 0 0 auto;
  width: 6px; height: 6px;
  border: 1px solid currentColor;
  opacity: .4;
  transition: background-color 160ms ease, opacity 160ms ease;
}
.cat__row.is-solved::before { background: currentColor; opacity: 1; }

.slot {
  position: relative;
  flex: 1 1 0;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 28px;
  font: 700 16px/1 var(--font-glyph);
  letter-spacing: .05em;
  color: transparent;
  border-bottom: 2px solid rgba(20, 17, 14, .26);
  transition: color 140ms ease, border-color 140ms ease;
}
.slot::before {
  content: "\00B7\00B7";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 17px;
  letter-spacing: .28em;
  color: var(--ink-black);
  opacity: .4;
}
.slot.is-known { color: var(--ink-black); border-bottom-color: var(--ink-black); }
.slot.is-placed {
  color: var(--ink);
  border-bottom-color: var(--ink);
  border-bottom-width: 3px;
}
.slot.is-placed::after {
  content: "";
  position: absolute;
  top: 1px; right: 2px;
  width: 4px; height: 4px;
  border-radius: 50%;
  background: var(--ink);
}
.slot.is-known::before, .slot.is-placed::before { content: none; }

.cat__acts {
  display: flex;
  gap: 8px;
  margin-top: 11px;
}
.cat__guess, .cat__hint { padding: 9px 11px 8px; }
.cat__hint { letter-spacing: .1em; opacity: .8; }

.cat.is-target { box-shadow: inset 5px 0 0 var(--accent); }

.cat.is-solved {
  background: var(--ink);
  color: var(--ink-on);
  border-bottom-color: var(--ink);
}
.cat.is-solved .cat__count { opacity: .75; }
.cat.is-solved .slot {
  color: var(--ink-on);
  border-bottom-color: currentColor;
  border-bottom-width: 2px;
}
.cat.is-solved .slot::after { background: var(--ink-on); }
.cat.is-solved .cat__acts { display: none; }

/* -------------------------------------------------------- 12. guess sheet */
#guess {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding:
    calc(7vh + env(safe-area-inset-top, 0px))
    calc(18px + env(safe-area-inset-right, 0px))
    18px
    calc(18px + env(safe-area-inset-left, 0px));
}
#guessForm {
  width: min(420px, 100%);
  display: grid;
  grid-template-columns: 1fr auto;
  align-content: start;
  column-gap: 10px;
}
#guessForm::before {
  content: "NAME THE WORD";
  grid-column: 1 / -1;
  font: 700 10px/1 var(--font-display);
  letter-spacing: .24em;
  color: var(--accent);
  margin-bottom: 9px;
}
#guessCat {
  grid-column: 1 / -1;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(30px, 10vw, 42px);
  line-height: .94;
  letter-spacing: -0.015em;
  text-transform: uppercase;
}
#guessClue {
  grid-column: 1 / -1;
  margin-top: 14px;
  font-family: var(--font-glyph);
  font-weight: 700;
  font-size: clamp(24px, 8.4vw, 32px);
  line-height: 1;
  letter-spacing: .12em;
}
#guessClue b {
  background: var(--ink-black);
  color: var(--paper);
  padding: .05em .16em;
}
#guessInput {
  grid-column: 1 / -1;
  width: 100%;
  margin-top: 22px;
  font-family: var(--font-glyph);
  font-weight: 800;
  font-size: clamp(26px, 9vw, 34px);
  line-height: 1.15;
  letter-spacing: .24em;
  text-indent: .24em;
  text-align: center;
  text-transform: uppercase;
  color: var(--ink-black);
  background: transparent;
  border: 0;
  border-bottom: 3px solid var(--ink-black);
  border-radius: 0;
  appearance: none;
  -webkit-appearance: none;
  padding: 6px 0 8px;
}
#guessInput:focus { outline: none; border-bottom-color: var(--accent); }
#guessInput.shake, #guessInput.is-shake, #guessInput.is-wrong,
#guessInput[data-shake] { animation: shake 380ms cubic-bezier(.36, .07, .19, .97); }
@keyframes shake {
  10%, 90% { transform: translateX(-2px); }
  20%, 80% { transform: translateX(4px); }
  30%, 50%, 70% { transform: translateX(-7px); }
  40%, 60% { transform: translateX(7px); }
}
#guessError {
  grid-column: 1 / -1;
  min-height: 1.3em;
  margin: 10px 0 18px;
  font: 700 11px/1.3 var(--font-display);
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--accent);
}
#guessSubmit { grid-column: 1; justify-self: start; align-self: center; }
#guessClose  { grid-column: 2; align-self: center; }

/* ---------------------------------------------------------- 13. win card  */
#win {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: grid;
  align-content: center;
  justify-items: center;
  text-align: center;
  padding:
    calc(24px + env(safe-area-inset-top, 0px))
    calc(18px + env(safe-area-inset-right, 0px))
    calc(24px + env(safe-area-inset-bottom, 0px))
    calc(18px + env(safe-area-inset-left, 0px));
  /* light veil, not a blackout: the inked cube behind it IS the payoff */
  background: rgba(231, 220, 195, .68);
}
#winTitle {
  position: relative;
  z-index: 1;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(46px, 16vw, 80px);
  line-height: .9;
  letter-spacing: -0.025em;
  text-transform: uppercase;
}
#winTitle::before {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: min(80vw, 330px);
  aspect-ratio: 1;
  z-index: -1;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: repeating-conic-gradient(from 0deg, var(--accent) 0deg 8deg, transparent 8deg 16deg);
  opacity: .17;
  animation: burst 46s linear infinite;
}
@keyframes burst { to { transform: translate(-50%, -50%) rotate(360deg); } }

/* flat screen-printed ink chips in face-solve order — deliberately NOT emoji */
#winStrip {
  margin-top: 18px;
  display: flex;
  justify-content: center;
  gap: 7px;
  line-height: 1;
}
#winStrip .chip {
  width: clamp(24px, 7.5vw, 32px);
  aspect-ratio: 1;
  background: transparent;
  border: 2px solid var(--ink-black);
}
#winStrip .chip[data-ink="0"] { background: var(--ink-0); }
#winStrip .chip[data-ink="1"] { background: var(--ink-1); }
#winStrip .chip[data-ink="2"] { background: var(--ink-2); }
#winStrip .chip[data-ink="3"] { background: var(--ink-3); }
#winStrip .chip[data-ink="4"] { background: var(--ink-4); }
#winStrip .chip[data-ink="5"] { background: var(--ink-5); }
#winStats {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: end;
  gap: 0 18px;
  margin-top: 20px;
  min-width: min(300px, 84vw);
  text-align: left;
  /* a printed label laid over the cube, so the stats stay crisp */
  background: rgba(231, 220, 195, .88);
  border-top: 1px solid rgba(20, 17, 14, .45);
  padding: 0 15px 3px;
}
#winStats > div { display: contents; }
#winStats dt, #winStats dd {
  align-self: end;
  padding: 11px 0 9px;
  border-bottom: 1px solid rgba(20, 17, 14, .45);
}
#winStats dt {
  grid-column: 1;
  font: 700 10px/1 var(--font-display);
  letter-spacing: .2em;
  text-transform: uppercase;
}
#winStats dd {
  grid-column: 2;
  text-align: right;
  font: 800 15px/1 var(--font-display);
  font-variant-numeric: tabular-nums;
}
#winShare { margin-top: 26px; justify-self: center; align-self: auto; }
#winFree  { margin-top: 12px; }

/* -------------------------------------------------------------- 14. toast */
#toast {
  position: fixed;
  z-index: 80;
  left: 50%;
  bottom: calc(var(--peek) + 14px);
  transform: translateX(-50%);
  max-width: calc(100vw - 32px);
  padding: 11px 16px 10px;
  background: var(--ink-black);
  color: var(--paper);
  font: 800 11px/1 var(--font-display);
  letter-spacing: .2em;
  text-transform: uppercase;
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  transition: opacity 180ms ease, visibility 0s linear 180ms;
}
#toast:not(:empty) { opacity: 1; visibility: visible; transition-delay: 0s; }

/* ------------------------------------------------------- 15. back to home */
/* back-link.js pins this to the top-left corner, which is exactly where the
   SPINDLE wordmark sits — it printed straight through the S-P-I and put a
   navigate-away tap target on the site's identity mark. Drop it below the HUD
   rule into the board's empty top-left. (back-link.js already hides it during
   data-state="playing".) */
body #bloff-back {
  mix-blend-mode: normal;
  color: var(--ink-black);
  opacity: .5;
  top: 60px;
  left: max(10px, env(safe-area-inset-left));
}

/* ============================== 16. mobile sheet behaviour =============== */
@media (max-width: 879.98px) {
  /* keep the closed sheet out of the tab order and out of the safe-area gap */
  #railList { visibility: hidden; transition: visibility 0s linear 300ms; }
  body[data-sheet="open"] #railList { visibility: visible; transition-delay: 0s; }
  #railHandle[aria-expanded="true"] ~ #railList { visibility: visible; transition-delay: 0s; }
}

/* ==================================== 17. desktop: two columns =========== */
@media (min-width: 880px) {
  body {
    grid-template-columns: minmax(0, 1fr) clamp(300px, 31vw, 384px);
    grid-template-areas:
      "hud   hud"
      "board rail"
      "diff  rail"
      "ctrl  rail";
    padding-bottom: 0;
    font-size: 16px;
  }

  #hud { padding: 12px 22px 11px; gap: 12px; }
  #wordmark { font-size: 30px; }
  #timer { font-size: 19px; }

  #rail {
    position: static;
    grid-area: rail;
    transform: none;
    max-height: none;
    border-top: 0;
    border-left: 2px solid var(--ink-black);
  }
  #railHandle {
    min-height: 0;
    padding: 15px 20px 14px;
    cursor: default;
  }
  .rail__chev { display: none; }
  #railList { padding-bottom: 24px; }
  .cat { padding: 14px 20px 16px; }

  #controls { padding: 12px 22px; }
  #intro { padding: 20px 22px 22px; }
  #difficulty { padding: 10px 22px 11px; }
  #badge { top: 10px; right: 14px; width: 96px; }

  /* the cube is much larger relative to the type here — veil it harder */
  #win { background: rgba(231, 220, 195, .85); }
  #guess { align-items: center; background: rgba(231, 220, 195, .93); }
  #guessForm {
    background-color: var(--paper);
    background-image: radial-gradient(var(--paper-2) 1px, transparent 1.3px);
    background-size: 6px 6px;
    border: 2px solid var(--ink-black);
    padding: 26px 28px 28px;
    box-shadow: 9px 9px 0 rgba(20, 17, 14, .18);
  }
  #toast { bottom: 22px; }
}

/* ============================ 18. short viewports (landscape phones) ===== */
/* Sub-375px phones: the HUD needs ~359px with both counters, and the page
   cannot scroll (html,body are overflow:hidden), so an overflow would clip the
   clock outright. Drop the miss counter first — it is the only supplementary
   readout, since a miss already shows up as +15s on the clock itself. */
@media (max-width: 379.98px) {
  #hud { gap: 6px; padding-left: 10px; padding-right: 10px; }
  #misses { display: none; }
}

@media (max-height: 520px) {
  :root { --peek: calc(44px + env(safe-area-inset-bottom, 0px)); }
  #hud { padding: 6px 12px 5px; }
  #timer::before { display: none; }
  #controls { padding: 6px 12px; min-height: 44px; }
  .turnbtn { height: 36px; }
  #badge { display: none; }
  #intro { padding: 11px 14px 12px; gap: 6px; }
  .intro__sub { display: none; }
  #turnpad { padding: 7px 12px 8px; }
  #guess { padding-top: calc(14px + env(safe-area-inset-top, 0px)); }
}
@media (max-height: 520px) and (max-width: 879.98px) {
  #rail { max-height: 88vh; max-height: 88dvh; }
}

/* ================================== 19. reduced motion =================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
}
