:root {
  --tile-bg: #0c1222;
  --tile-outline: rgba(98, 230, 255, 0.28);
  --tile-highlight: rgba(181, 109, 255, 0.42);
}

#game-canvas {
  image-rendering: crisp-edges;
  image-rendering: pixelated;
}

.tile-legend {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin: 14px 0 2px;
  padding: 11px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: rgba(12, 18, 34, 0.7);
  text-align: left;
}

.tile-legend div {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: 9px;
  color: var(--muted);
}

.tile-badge {
  display: inline-grid;
  place-items: center;
  min-width: 44px;
  min-height: 28px;
  border-radius: 8px;
  padding: 4px 6px;
  font: 700 0.78rem/1 'SFMono-Regular', 'Menlo', Consolas, 'Liberation Mono', monospace;
  border: 1px solid rgba(255, 255, 255, 0.3);
  background: rgba(255, 255, 255, 0.035);
}

.tile-badge-script { color: #7ff0f7; border-color: #67d7ef; }
.tile-badge-loop { color: #ffd45b; border-color: #ffc95e; }
.tile-badge-array { color: #b46cff; border-color: #d3a3ff; }
.tile-badge-logo { color: #ffd266; border-color: #ffcf5a; }

/* The legend shows the REAL pieces, cropped straight out of the sheet the
   board draws from. It used to print the old code glyphs ({ }, [ ], fn, IA),
   so the key taught the player symbols the game no longer contains. Driving
   both from one file means the legend cannot drift from the board again. */
.tile-badge-art {
  min-width: 38px;
  min-height: 38px;
  padding: 0;
  border: 0;
  background: url('sprites/tiles.svg') no-repeat;
  /* 7 frames wide: each step is 1/6 of the travel between first and last. */
  background-size: 700% 100%;
  background-position: calc(var(--tile-frame, 0) * 100% / 6) 0;
}

.game-note {
  margin-top: 10px;
  color: var(--muted);
}

.progress-fill {
  transition: width 220ms ease;
}

/* ---------------------------------------------------------------------------
   One layout, every format.

   Exploder used to run three readouts stacked in the same strip of the stage:
   the shell's floating `.hud-row` chips, the canvas chrome the game draws, and
   the shell's centred `.stage-callout`. On a desktop that was merely redundant.
   On a phone the chips sat directly on top of the move clock and the combo
   titles, which is exactly the information a player has to read in the second
   they have to read it.

   The canvas is now the single HUD. It scales with the board, survives
   fullscreen, and cannot drift out of alignment with the board it describes,
   because it is measured from the same rect. The DOM chips stay in the markup
   as the screen-reader surface only - `aria-live` cannot read a canvas - so
   they are taken out of the visual layer rather than deleted.
   --------------------------------------------------------------------------- */
.hud .hud-row {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The shell's centred shout and its progress rail are both drawn by the canvas
   now - the shout in a reserved band under the board, the target progress as
   the bar beside the score. Left in the DOM they printed over the bottom rows
   of blocks, and the rail said the same thing twice. Same treatment as the
   chips: out of the visual layer, still readable by a screen reader. */
.stage-callout,
.hud .progress-track.stage-rail {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
  opacity: 1;
  transform: none;
}

.game-page {
  width: min(1820px, calc(100% - 12px));
}

/* Both axes constrain the column. Height alone drove this before, so a short
   landscape phone still asked for a stage far taller than its own viewport. */
.game-column {
  width: min(100%, calc((100svh - 96px) * 16 / 9));
  max-width: none;
}

/* No fixed floor. A 560px minimum on a 390px-tall landscape phone pushed the
   whole board below the fold and made the page scroll to play. */
.game-stage {
  min-height: min(560px, calc(100svh - 96px));
}

/* Landscape phone: the viewport is short, not narrow, so the width-keyed mobile
   query never fired here at all - a 844x390 phone was served the full desktop
   header and a stage taller than the whole screen.

   The budget is explicit: page padding (6) + topbar (~34) + a little breathing
   room is the 56px subtracted from the viewport height below, and the 16:9
   stage takes what is left. That keeps the entire board on screen without
   scrolling, which is the only thing that matters on a phone held sideways. */
@media (max-height: 560px) and (orientation: landscape) {
  .game-page {
    padding-top: 6px;
    padding-bottom: 10px;
  }

  .game-column {
    width: min(100%, calc((100svh - 62px) * 16 / 9));
  }

  .game-stage {
    min-height: 0;
    border-radius: 12px;
  }

  /* The shell sets a 58px min-height on the bar. That floor is what kept the
     stage 8px off the bottom of the screen no matter how small its contents
     were made, so it is lifted here rather than worked around. */
  .game-topbar {
    min-height: 0;
    padding-block: 2px;
  }

  .game-brand img {
    width: 26px;
    height: 26px;
  }

  /* The strapline, the session chip and the text links are all reachable from
     the portrait layout; on a sideways phone they cost board rows. */
  .game-brand small,
  .game-topbar .session-chip,
  .game-topbar .text-button {
    display: none;
  }

  .game-note,
  .tile-legend {
    display: none;
  }
}

.game-stage canvas {
  cursor: grab;
  touch-action: none;
}

.game-stage canvas:active {
  cursor: grabbing;
}

@media (max-width: 900px) {
  .tile-legend {
    grid-template-columns: 1fr;
    gap: 8px;
  }
}

/* The music widget docks itself beside the site title now and measures its own
   position, so the per-page corner overrides that used to live here would only
   fight it. */
@media (max-width: 760px) {
  .game-stage {
    min-height: min(440px, calc(100svh - 96px));
  }
}

/* The board IS the control on a phone, so the dock shrinks to the one button
   that has nowhere else to go. Pinned to the right so it clears the board,
   which reserves matching space for it in applyTouchScale(). */
.touch-controls.touch-controls-minimal {
  justify-content: flex-end;
  pointer-events: none;
}

.touch-controls.touch-controls-minimal .touch-cluster {
  margin-left: auto;
  pointer-events: auto;
}

/* Portrait: Exploder has no control dock - the board IS the control surface,
   tapped directly on the canvas - so the shell's reserved control band is dead
   space here, and paying for it in canvas height shrank the tiles to 34px, well
   under a comfortable tap target. Reclaiming the band and taking a portrait
   canvas suits an 8x10 board: the grid is the shape of the screen. */
@media (pointer: coarse) and (orientation: portrait) {
  .game-stage { --dock-band: 0px; }
  /* 5:8 rather than a squarer box: the board is height-constrained here (ten
     rows against eight columns, minus the header and the callout strip), so
     every pixel of canvas height goes straight into tile size. This is what
     lifts the tiles from 34px to a thumb-sized target. */
  .game-stage canvas { aspect-ratio: 5 / 8; }
}
