/* ============================================================
   Adderspell — style.css
   Copyright (c) 2026 Jess Page. All rights reserved.
   Third-party components and their licences: see NOTICE.md.
   Layout target: iPhone 13, portrait, Safari with bottom URL bar.
   Everything must fit one screen with no page scrolling.
   ============================================================ */

/* Press Start 2P, Copyright 2012 The Press Start 2P Project Authors.
   SIL Open Font License 1.1 — full text in NOTICE.md. */
@font-face {
  font-family: 'Press Start 2P';
  font-style: normal;
  font-weight: 400;
  font-display: block;          /* the layout is tuned for this face; do not
                                   flash a proportional fallback first */
  src: url('press-start-2p.woff2') format('woff2');
}

:root {
  --bg:        #0e0f13;
  --panel:     #171a21;
  --line:      #333846;
  --text:      #e9ebf2;
  --muted:     #8b93a7;

  /* Violet + amber, chosen on measured colour-blind separation rather than
     convention: worst-case separation across protan/deutan/tritan is 96.6,
     against 44.2 for a red/amber/green scheme. Every tile also clears 3:1
     contrast with white text. See DESIGN-NOTES.md for the full comparison. */
  --correct:   #7b5bd6;
  --present:   #c07c12;
  --absent:    #2f3440;

  --snake:     #1fb8b0;
  --snakehead: #5ef0e6;

  /* A letter the guesses have ruled out: still collectable, but visibly
     spent, so the board carries the deduction the history used to. */
  --deadtile:  #191c24;
  --deadline:  #22262f;
  --deadtext:  #4a505e;

  /* A guess row you have not used yet — present, but well behind the row
     you are building. */
  --ghost:     #242833;
  --tilebg:    #232833;
  --wall:      #798296;

  /* Tile sizing: shrinks on small screens, capped on large ones. */
  --tile:  clamp(23px, 6.8vw, 38px);
  --ctile: clamp(27px, 8.2vw, 44px);
  --gap:   4px;

  --radius: 0;             /* hard corners: a pixel game has no curves */
  --pad-r:  34px;          /* thumb-pad ring radius */

  /* The board is aspect-locked, so on a wide screen it is NARROW while the
     viewport is huge. Sizing overlay type off vw therefore blows it up inside
     a narrow column and forces a scroll. game.js keeps --board-w equal to the
     board's real width; the overlay scales off that instead. */
  --board-w: 300px;

  /* ---- Type ----------------------------------------------------------
     One family everywhere, including the letters painted on the canvas
     (game.js reads this back off the body so the two can never diverge).
     The title's treatment sets the tone; everything else is the same
     face at the same weight with tracking scaled down to stay readable
     at small sizes. Single-character tiles use no tracking, because
     letter-spacing adds space AFTER the glyph and shifts it off-centre. */
  --font: 'Press Start 2P', ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* Press Start 2P ships one weight. Asking for 800 makes the browser
     synthesise bold, which smears a pixel face — keep it at 400. */
  --weight: 400;

  --track-title: 0.06em;         /* the face is already wide; tracking is light */
  --track-ui:    0.04em;
  --track-hud:   0.02em;
  --track-tile:  0;              /* single letters — must stay centred */

  color-scheme: dark;
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;              /* the whole game fits: never scroll */
  overscroll-behavior: none;     /* no rubber-band bounce on iOS */
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-weight: var(--weight);
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
}

#app {
  position: relative;            /* the pad positions against this now */
  height: 100vh;                 /* fallback */
  height: 100svh;                /* the small viewport always fits */
  height: 100dvh;                /* dynamic: accounts for Safari's bars */
  max-height: 100dvh;
  overflow: hidden;              /* clip at the bottom, never push the top */
  touch-action: none;            /* the whole screen steers; nothing scrolls */
  max-width: 520px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding:
    calc(6px + env(safe-area-inset-top))
    calc(8px + env(safe-area-inset-right))
    calc(6px + env(safe-area-inset-bottom))
    calc(8px + env(safe-area-inset-left));
}

/* ---------- Header ---------- */

#header {
  flex: 0 0 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

#title {
  margin: 0;
  font-size: clamp(11px, 3.5vw, 17px);
  font-weight: var(--weight);
  letter-spacing: var(--track-title);
  color: var(--text);
}

/* No longer a control: it reads out where you are. A bordered key cap said
   "press me", which is exactly what made it ambiguous — a label that looks
   like a button cannot say whether it names the mode you are IN or the one
   you would switch TO. */
#mode {
  margin: 0 0 0 auto;
  font-family: var(--font);
  font-size: clamp(8px, 2.4vw, 11px);
  font-weight: var(--weight);
  letter-spacing: var(--track-ui);
  text-transform: uppercase;
  color: var(--muted);
  background: transparent;
  border: 0;
  padding: 4px 0;
  white-space: nowrap;
}

/* The one that counts wears the snake's own colour. */
#mode[data-state="scored"] { color: var(--snakehead); }
#mode[data-state="retry"]  { color: var(--present); }
#mode[data-state="done"]   { color: var(--correct); }

#reset {
  margin-left: 6px;              /* far right, always */
  font-family: var(--font);
  font-size: clamp(9px, 2.7vw, 12px);
  font-weight: var(--weight);
  letter-spacing: var(--track-ui);
  text-transform: uppercase;
  color: var(--text);
  background: var(--panel);
  border: 2px solid var(--line);
  border-radius: 0;
  padding: 6px 12px;
  cursor: pointer;
  touch-action: manipulation;
  /* The label changes with the state, and a two-word one wrapped to two
     lines on a 320px screen, which grew the header and shoved the board
     down. Every label is one short word; this makes sure of it. */
  white-space: nowrap;
}

#reset:active { background: var(--line); }

/* ---------- Board ---------- */

#board {
  position: relative;
  flex: 1 1 auto;
  min-height: 140px;             /* never collapse to nothing */

  /* Match the 12x16 grid so the container IS the play area — no dead
     letterbox margin between the visible frame and the playable cells.
     align-self stops the flex row from stretching it back to full width. */
  aspect-ratio: 12 / 16;
  align-self: center;
  max-width: 100%;

  /* The wall is drawn on the canvas so it lands exactly on the grid edge. */
  background: transparent;
  border: 0;

  /* Critical: the canvas claims swipe gestures, Safari must not scroll. */
  touch-action: none;
}

#canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* Overlay: countdowns and end-of-game panel, drawn over the board so that
   it costs no vertical layout space. */
#overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 12px;
  pointer-events: none;          /* swipes still reach the canvas */
  overflow-y: auto;              /* never spill past the board, whatever
                                    the screen size — scroll as a last resort */
  -webkit-overflow-scrolling: touch;

  /* Same trap as the guess history: being a scroll container makes this the
     element where touch-action resolves, so #app's `none` stops applying and
     Chrome claims drags over it. This one covers the board during every
     countdown, which is exactly when a player is lining up their next turn. */
  touch-action: none;
}

#overlay.dim {
  background: rgba(9, 9, 10, 0.93);
  pointer-events: auto;          /* the Share button needs to be tappable */
}
#overlay.hidden { display: none; }

/* Full width, not content width: as a content-sized flex item a long
   unbreakable word overflows and drags the centring off-axis. */
#overlayInner {
  width: 100%;
  max-width: 100%;
  margin: auto;                  /* stay centred until it needs to scroll */
}

/* Small label above the big countdown glyph, so "RESUMING IN" never has
   to share a line with the number and overflow the narrow board. */
#overlayKicker {
  margin: 0 0 2px;
  font-size: clamp(8px, calc(var(--board-w) * 0.034), 11px);
  font-weight: var(--weight);
  letter-spacing: var(--track-title);
  margin-right: calc(-1 * var(--track-title));   /* see note below */
  text-transform: uppercase;
  color: var(--muted);
}

/* Letter-spacing adds its space AFTER the last glyph, so centred tracked
   text sits half a tracking-unit left of true centre. Pulling the right
   margin back by one tracking unit cancels it. */

#overlayTitle {
  margin: 0;
  font-size: clamp(11px, calc(var(--board-w) * 0.057), 20px);
  font-weight: var(--weight);
  letter-spacing: var(--track-title);
  margin-right: calc(-1 * var(--track-title));
  text-transform: uppercase;
  text-wrap: balance;
  overflow-wrap: break-word;
}

/* The countdown is a single glyph (3 / 2 / 1) or GO!, so no tracking —
   a trailing space would shift one character visibly off centre. */
#overlay.count #overlayTitle {
  font-size: clamp(30px, calc(var(--board-w) * 0.18), 64px);
  letter-spacing: 0;
  margin-right: 0;
  line-height: 1.05;
  text-shadow: 0 2px 14px rgba(0, 0, 0, 0.85);
}

#overlayWord {
  margin: 6px 0 0;
  font-size: clamp(9px, calc(var(--board-w) * 0.0435), 14px);
  font-weight: var(--weight);
  letter-spacing: var(--track-title);
  margin-right: calc(-1 * var(--track-title));
  text-transform: uppercase;
  color: var(--muted);
}

#overlayStats {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  font-size: clamp(8px, calc(var(--board-w) * 0.041), 13px);
  font-weight: var(--weight);
  letter-spacing: var(--track-hud);
  margin-right: calc(-1 * var(--track-hud));
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  line-height: 1.6;
  color: var(--text);
}

#overlayHint {
  margin: 8px 0 0;
  font-size: clamp(8px, calc(var(--board-w) * 0.034), 11px);
  font-weight: var(--weight);
  letter-spacing: var(--track-hud);
  margin-right: calc(-1 * var(--track-hud));
  text-transform: uppercase;
  color: var(--muted);
  /* A pixel face has tall caps and no descenders to spare. The default
     leading is fine on one line and collides on two — and the start hint
     is the first two-line hint the panel has ever had. */
  line-height: 1.75;
  max-width: 30ch;
  margin-left: auto;
  margin-right: auto;
  text-wrap: balance;
}

.hidden { display: none !important; }

/* ---------- Steering surface ----------
   Everything under the board. It claims touch gestures so the thumb pad
   can live here instead of on the grid, and it is the positioning context
   for the pad itself. */

/* Controls opt out of steering, so a tap on them is still a tap. */
button {
  touch-action: manipulation;
}

#lower {
  flex: 0 0 auto;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  touch-action: none;
}

#thumbpad {
  position: absolute;
  left: 0;
  top: 0;
  width: calc(var(--pad-r) * 2);
  height: calc(var(--pad-r) * 2);
  border-radius: 0;
  border: 2px solid rgba(233, 233, 234, 0.30);
  background: rgba(18, 18, 19, 0.55);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  pointer-events: none;           /* never swallows a touch itself */
  will-change: transform;
  z-index: 5;
}

/* Over the playfield the ring steps back so it does not hide the letters
   or the snake. Obscuring the grid was the whole reason the pad moved off
   the board originally; the surface is back, the graphic stays polite. */
#thumbpad.over-board {
  opacity: 0.42;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  background: transparent;
}

.pad-dot {
  position: absolute;
  width: 4px;
  height: 4px;
  margin: -2px 0 0 -2px;
  border-radius: 0;
  background: rgba(233, 233, 234, 0.40);
  transition: background 90ms linear, transform 90ms linear;
}

.pad-dot.up    { left: 50%; top: 9px; }
.pad-dot.down  { left: 50%; top: calc(100% - 9px); }
.pad-dot.left  { top: 50%; left: 9px; }
.pad-dot.right { top: 50%; left: calc(100% - 9px); }

#thumbpad[data-dir="up"]    .pad-dot.up,
#thumbpad[data-dir="down"]  .pad-dot.down,
#thumbpad[data-dir="left"]  .pad-dot.left,
#thumbpad[data-dir="right"] .pad-dot.right {
  background: var(--snakehead);
  transform: scale(2);
}

#padKnob {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 24px;
  height: 24px;
  margin: -12px 0 0 -12px;
  border-radius: 0;
  background: rgba(233, 233, 234, 0.72);
  will-change: transform;
}

#thumbpad[data-dir] #padKnob { background: var(--snakehead); }

@media (prefers-reduced-motion: reduce) {
  .pad-dot { transition: none; }
}

/* ---------- HUD ---------- */

/* The status strip now lives inside the board canvas, drawn as pixel art —
   see drawHud() in game.js. Nothing a player needs mid-game may sit in
   #lower, because that is the steering surface their thumb covers.

   #status carries the same numbers as text for screen readers, since a
   canvas says nothing to one. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* HUD colours, resolved by the canvas through cssVar(). */
:root {
  --crashwarn: #e06c5a;
}

/* ---------- End-of-game result ---------- */

#overlayResult {
  margin: 10px 0 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

#overlayDay {
  margin: 0 0 2px;
  font-size: clamp(8px, calc(var(--board-w) * 0.035), 11px);
  font-weight: var(--weight);
  letter-spacing: var(--track-title);
  margin-right: calc(-1 * var(--track-title));
  text-transform: uppercase;
  color: var(--muted);
}

#overlayDay.hidden { display: none !important; }

/* The key cap is reserved for ONE action per panel — the thing a player
   most likely wants next. Two caps compete and neither leads. */
#start,
#keepGoing,
#share {
  margin: 10px auto 0;
  display: block;
  font-family: var(--font);
  font-size: clamp(9px, calc(var(--board-w) * 0.038), 12px);
  font-weight: var(--weight);
  letter-spacing: var(--track-ui);
  text-transform: uppercase;
  color: var(--bg);
  background: var(--snakehead);
  border: 0;
  border-radius: 0;
  padding: 9px 16px;
  cursor: pointer;
  touch-action: manipulation;
  box-shadow: 3px 3px 0 0 var(--snake);   /* hard offset, no blur */
}

/* Same arcade treatment as Share, but bigger: it is the first thing a
   visitor is asked to do. Declared AFTER the shared rule so these win. */
#start {
  margin: 14px auto 0;
  padding: 13px 22px;
  font-size: clamp(10px, calc(var(--board-w) * 0.046), 14px);
}

#start:active,
#keepGoing:active,
#share:active {
  background: var(--snake);
  box-shadow: 0 0 0 0 var(--snake);
  transform: translate(3px, 3px);         /* the key presses in */
}
#share.done { background: var(--present); }

/* Giving up is a real option, not a temptation. It gets no key cap and no
   fill — a quiet underlined line under the buttons that mean to be pressed.
   Declared after the shared rule so it wins the cascade. */
#giveUp,
#switchMode {
  margin: 12px auto 0;
  display: block;
  font-family: var(--font);
  font-size: clamp(7px, calc(var(--board-w) * 0.028), 9px);
  font-weight: var(--weight);
  letter-spacing: var(--track-ui);
  text-transform: uppercase;
  color: var(--muted);
  background: none;
  border: 0;
  box-shadow: none;
  padding: 6px 10px;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  touch-action: manipulation;
}
#giveUp:active,
#switchMode:active { color: var(--text); }
#giveUp.hidden,
#switchMode.hidden,
#keepGoing.hidden { display: none !important; }

/* ---- Three tiers, not two ------------------------------------------
   A filled key cap is the primary. An OUTLINED key is still obviously a
   button, just second in line — the same treatment the header's own button
   already wears, so it costs the design nothing new. Plain underlined lines
   are the rest.

   Share lives in tier two whenever Continue outranks it. It was a plain
   line for one build and that was wrong: sharing is how the game travels,
   and a text link does not read as something you press. */
#share.second {
  color: var(--text);
  background: transparent;
  border: 2px solid var(--line);
  box-shadow: none;
  padding: 8px 14px;
  margin: 12px auto 0;
  font-size: clamp(9px, calc(var(--board-w) * 0.036), 12px);
}
#share.second:active {
  background: var(--line);
  transform: none;
  box-shadow: none;
}
#share.second.done { background: transparent; color: var(--present); border-color: var(--present); }

/* The switch is an invitation, not an exit, so it sits a touch brighter
   than Give up and above it in the stack. */
#switchMode { margin-top: 12px; color: var(--snake); }
#giveUp    { margin-top: 12px; color: var(--muted); }

#overlayScoreRow {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 10px;
  flex-wrap: wrap;
}

#overlayRank {
  font-size: clamp(18px, calc(var(--board-w) * 0.102), 38px);
  font-weight: var(--weight);
  letter-spacing: 0;
  line-height: 1.1;
  color: var(--correct);
}

#overlayRank[data-rank="S"] { color: var(--snakehead); }
#overlayRank[data-rank="A"] { color: var(--correct); }
#overlayRank[data-rank="B"] { color: var(--present); }
#overlayRank[data-rank="C"] { color: var(--muted); }
#overlayRank[data-rank="D"] { color: var(--muted); }

#overlayRank::before {
  content: 'RANK ';
  font-size: clamp(9px, 2.8vw, 12px);
  letter-spacing: var(--track-title);
  color: var(--muted);
  vertical-align: middle;
}

#overlayScore {
  font-size: clamp(11px, calc(var(--board-w) * 0.057), 18px);
  font-weight: var(--weight);
  letter-spacing: var(--track-hud);
  margin-right: calc(-1 * var(--track-hud));
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

/* ---------- Current row ---------- */

#currentSection {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  /* obvious separation from the guess board below */
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 4px;
}

/* The word stays optically centred over the row; the counter rides in the
   right-hand column so it can never shift it. */
.label > .labeltext { grid-column: 2; }

#left {
  grid-column: 3;
  justify-self: end;
  color: var(--snake);
}

#left.low { color: var(--present); }

.label {
  margin: 0;
  align-self: stretch;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  font-size: clamp(7px, 2.1vw, 9px);
  font-weight: var(--weight);
  letter-spacing: var(--track-title);
  margin-right: calc(-1 * var(--track-title));
  text-transform: uppercase;
  color: var(--muted);
}

.row {
  display: grid;
  grid-template-columns: repeat(5, var(--ctile));
  gap: var(--gap);
}

.cell {
  width: var(--ctile);
  height: var(--ctile);
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--line);
  border-radius: var(--radius);
  font-size: calc(var(--ctile) * 0.46);
  font-weight: var(--weight);
  letter-spacing: var(--track-tile);
  line-height: 1;
  color: var(--text);
  background: transparent;
}

.cell.filled {
  border-color: #6b6d70;
  animation: pop 120ms ease-out;
}

@keyframes pop {
  from { transform: scale(0.88); }
  to   { transform: scale(1); }
}

/* ---------- Guess history board ---------- */

#guessSection {
  flex: 0 0 auto;
  display: flex;
  justify-content: center;
}

/* Three rows visible, newest first, the rest scrolled. Capping the height
   here is what gives the board its extra ~20% — a six-row grid was the
   single biggest consumer of vertical space on a phone.

   The grid stays part of the steering surface: a drag over it turns the
   snake and a tap opens the full list. Handing these touches to a scroll
   container instead left only ~70px of pad room, against a ring 68px
   across. */
#guessGrid {
  display: grid;
  grid-template-columns: repeat(5, var(--tile));
  grid-auto-rows: var(--tile);
  gap: var(--gap);
  /* A FIXED three rows, reserved from the first frame whether or not any
     guesses exist yet. Letting an empty grid collapse handed the opening
     board a burst of extra height and left the thumb pad about 80px to
     live in — and it meant the board then shrank under you as guesses
     arrived. A constant reservation keeps the board one size for the whole
     run and the steering surface honest from the start. */
  height: calc(var(--tile) * 3 + var(--gap) * 2);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;

  /* CRITICAL on Android. `touch-action` is resolved by walking up from the
     touched element to the nearest SCROLL CONTAINER — and because this
     element is one (overflow-y: auto), the walk stops here and #app's
     `touch-action: none` never applies to it. Chrome then hands vertical
     drags to the compositor and fires touchcancel, killing the turn. The
     history sits right where a thumb rests, so that ate a lot of steering
     on a Pixel while looking fine on an iPhone. Touch never needs to scroll
     it — history opens on a tap — so refuse the gesture outright. */
  touch-action: none;
}

#guessGrid::-webkit-scrollbar { display: none; }

/* Tap the history to see every guess. It lifts over the steering surface
   rather than pushing the board, so nothing below it moves, and the next
   result collapses it again. */
#guessGrid.expanded {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 0;
  z-index: 5;
  height: auto;
  max-height: none;
  padding: var(--gap);
  background: var(--bg);
  outline: 2px solid var(--line);
}

.tile {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--line);
  border-radius: var(--radius);
  font-size: calc(var(--tile) * 0.46);
  font-weight: var(--weight);
  letter-spacing: var(--track-tile);
  line-height: 1;
  color: var(--text);
  background: transparent;
}

.tile.ghost   { border-color: var(--ghost); background: transparent; }

.tile.correct { background: var(--correct); border-color: var(--correct); }
.tile.present { background: var(--present); border-color: var(--present); }
.tile.absent  { background: var(--absent);  border-color: var(--absent); }

/* ---------- D-pad (desktop only) ---------- */

#dpad {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: repeat(3, 48px);
  grid-template-rows: repeat(2, 40px);
  gap: 5px;
  justify-content: center;
  margin-top: 6px;
}

.pad {
  font-family: var(--font);
  font-size: 16px;
  font-weight: var(--weight);
  color: var(--text);
  background: var(--panel);
  border: 2px solid var(--line);
  border-radius: 0;
  cursor: pointer;
  touch-action: manipulation;
}

.pad:active { background: var(--line); }

.pad.up    { grid-column: 2; grid-row: 1; }
.pad.left  { grid-column: 1; grid-row: 2; }
.pad.down  { grid-column: 2; grid-row: 2; }
.pad.right { grid-column: 3; grid-row: 2; }

#footer {
  flex: 0 0 auto;
  text-align: center;
  font-size: 11px;
  font-weight: var(--weight);
  letter-spacing: var(--track-hud);
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 4px;
}

/* ---------- Phones: swipe replaces the D-pad ---------- */

@media (hover: none) and (pointer: coarse) {
  #dpad, #footer { display: none; }
}

/* Belt and braces: narrow AND short means a phone, whatever the browser
   reports about pointers. Wide desktop windows keep the D-pad. */
@media (max-width: 600px) and (max-height: 900px) {
  #dpad, #footer { display: none; }
}

@media (max-height: 680px) {
  :root { --tile: clamp(20px, 6vw, 30px); --ctile: clamp(23px, 7vw, 34px); --gap: 3px; }
  #app { gap: 4px; }
  #currentSection { padding-bottom: 5px; }
}
