/* Two faces: Kenney Future (wide, square capitals) for display, the big
   numbers, titles and banners; Nunito (latin subset, variable weight), a
   rounded sans that reads at a glance, for everything else. index.html
   preloads both. Future draws its X like an H, so its X and × come from
   Nunito: display text is set heavy and in capitals, to match. */
@font-face { font-family: 'Future'; src: url('assets/kenney/fonts/Kenney Future.ttf'); font-weight: 100 1000; unicode-range: U+0-57, U+59-77, U+79-D6, U+D8-10FFFF; }
@font-face { font-family: 'Future'; src: url('assets/fonts/Nunito.woff2') format('woff2'); font-weight: 100 1000; size-adjust: 90%; unicode-range: U+58, U+78, U+D7; }
@font-face { font-family: 'Nunito'; src: url('assets/fonts/Nunito.woff2') format('woff2'); font-weight: 200 1000; }

:root {
  --paper: #fdfaf2;
  --accent: #ff6b4a;
  --gold: #ffd166;
  --good: #7ee081;
  --muted: #8a93a6;
  --glass: rgba(20, 25, 38, 0.74);
  --display: 'Future', system-ui, sans-serif;
  --sans: 'Nunito', system-ui, sans-serif;
}

* { box-sizing: border-box; margin: 0; }
/* One screen, no page scroll or zoom: a finger on the game moves nothing but the game. */
html, body { height: 100%; overflow: hidden; background: #232a3b; overscroll-behavior: none; touch-action: none; }
body {
  font-family: var(--sans); font-weight: 600; color: var(--paper);
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; -webkit-tap-highlight-color: transparent;
}
#stage { position: fixed; inset: 0; }
#stage canvas { display: block; width: 100%; height: 100%; touch-action: none; }
button { font: inherit; color: inherit; cursor: pointer; }
[hidden] { display: none !important; }

/* --- HUD ------------------------------------------------------------------- */
/* A grid over the screen: pills on top (the actions beside the clock, so the
   row stays short and the board gets the height), a side column on the right,
   and the board frame (empty) in the space left, where the camera fits the
   board. The clock centres over the board, unless the stats need more room
   than that leaves them. Phones rearrange it below. */
.hud {
  --side: 250px;
  --vh: 1vh; /* 1dvh where the browser has it (below): the height between its toolbars */
  position: fixed; inset: 0; pointer-events: none;
  display: grid; gap: 12px;
  grid-template-columns: minmax(max-content, 1fr) auto minmax(0, 1fr) var(--side);
  grid-template-rows: auto auto minmax(0, 1fr);
  grid-template-areas:
    "stats clock actions base"
    "board board board intel"
    "board board board plans";
  padding: max(18px, env(safe-area-inset-top)) max(22px, env(safe-area-inset-right)) max(18px, env(safe-area-inset-bottom)) max(22px, env(safe-area-inset-left));
}
@supports (height: 1dvh) { .hud { --vh: 1dvh; } }
.hud button, .hud .intel, .hud .plans { pointer-events: auto; }
.stats { grid-area: stats; justify-self: start; align-self: start; }
.base { grid-area: base; justify-self: end; align-self: start; }
.board-frame { grid-area: board; }
.pill {
  background: var(--glass);
  backdrop-filter: blur(6px);
  border-radius: 14px;
  padding: 10px 16px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
  display: flex; align-items: center; gap: 16px;
}
.label { font-size: 11px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.value { font: 700 20px var(--display); text-transform: uppercase; font-variant-numeric: tabular-nums; white-space: nowrap; }
.gold { color: var(--gold); }
.stat { display: flex; flex-direction: column; gap: 2px; min-width: 64px; }
.stats small { white-space: nowrap; }
@media (orientation: landscape) and (min-height: 541px) and (max-width: 1199px) { .stats .planned { display: none; } } /* clear of the clock */
.hpbar { height: 4px; border-radius: 2px; background: rgba(255,255,255,.12); overflow: hidden; }
.hpbar i { display: block; height: 100%; background: var(--good); transition: width .3s; }

/* The clock pill, and the actions (Execute, speeds) beside it. */
.clock { grid-area: clock; align-self: start; flex-direction: column; align-items: center; gap: 2px; padding: 10px 26px; min-width: 260px; }
.clock .label { max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.clock .value { font-size: 30px; letter-spacing: 0.04em; white-space: nowrap; }
.clock .bar { width: 100%; height: 4px; border-radius: 2px; background: rgba(255,255,255,0.12); overflow: hidden; margin-top: 6px; }
.clock .bar[hidden] { display: block !important; visibility: hidden; } /* keeps its room: the pill stays one size */
.clock .bar i { display: block; height: 100%; background: var(--accent); }
.actions { grid-area: actions; align-self: center; justify-self: start; display: flex; align-items: center; gap: 6px; position: relative; z-index: 1; }
.actions .btn.primary { padding: 11px 20px; font-size: 13px; border-radius: 12px; box-shadow: 0 6px 20px rgba(0, 0, 0, .25); }
/* Narrow desktop windows: a slimmer clock (the puzzle's number, par without
   its unit), so the row fits. */
@media (orientation: landscape) and (min-height: 541px) and (max-width: 979px) {
  .clock { min-width: 0; padding-inline: 16px; }
  .clock .value small, .clock-name { display: none; }
}
/* The speeds in a pill of their own (phones give each its own, below). */
@media (orientation: landscape) and (min-height: 541px) {
  [data-speeds] { display: flex; gap: 2px; padding: 5px; border-radius: 12px; background: var(--glass); backdrop-filter: blur(6px); box-shadow: 0 6px 20px rgba(0, 0, 0, .25); }
}

.btn {
  border: 0; border-radius: 10px; padding: 7px 14px; font-size: 13px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
  background: rgba(255,255,255,.12); transition: transform .12s, background .12s;
}
@media (hover: hover) {
  .btn:hover:not(:disabled) { background: rgba(255,255,255,.2); transform: translateY(-1px); }
  .btn.primary:hover { background: #ff8467; }
  .chip.sound:hover, .chip.howto-btn:hover { color: var(--paper); }
}
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:disabled { opacity: .4; cursor: default; }
.btn.primary { background: var(--accent); color: #fff; }
.btn.ghost { background: transparent; outline: 1px solid rgba(255,255,255,.2); }
a.btn { display: inline-flex; align-items: center; justify-content: center; gap: .45em; color: inherit; text-decoration: none; }
.btn .logo { width: 1.1em; height: 1.1em; fill: currentColor; }
.chip { border: 0; background: transparent; padding: 4px 8px; border-radius: 8px; font-size: 13px; font-weight: 800; color: var(--muted); }
.chip.on { background: rgba(255,255,255,.14); color: var(--paper); }
.chip.sound { position: relative; align-self: flex-start; padding: 4px; margin: -4px -8px 0 -6px; line-height: 0; }
.chip.sound::after { content: ''; position: absolute; inset: -6px; } /* a fingertip's reach */

/* Intel folds to its header: open while planning, folded in the battle. */
.intel { grid-area: intel; align-self: start; flex-direction: column; align-items: stretch; gap: 8px; }
.intel-head { display: flex; align-items: center; gap: 6px; border: 0; background: none; padding: 0; text-align: left; }
.intel-head .label { flex: 1; }
.intel:not(.open) .intel-name { display: none; } /* folded: "Intel" and the enemies' icons, on one line */
.intel-head::after { content: '\25BE'; color: var(--muted); font-size: 14px; line-height: 1; transition: rotate .2s; }
.intel:not(.open) .intel-head::after { rotate: 90deg; }
.intel-head img { width: 22px; height: 22px; object-fit: contain; margin: -4px -2px; }
.intel.open .intel-head img { display: none; }
.intel:not(.open) .intel-body { display: none; }
.intel-body { display: flex; flex-direction: column; gap: 8px; }
.intel p { font-size: 15px; font-weight: 800; }
.intel .intel-rule { color: var(--gold); } /* the puzzle's rule, e.g. "Cannons only" */
.intel-row { display: grid; grid-template-columns: 34px 1fr; column-gap: 8px; align-items: center; }
.intel-row img { grid-row: span 2; width: 34px; height: 34px; object-fit: contain; }
.intel-row b { font-weight: 800; font-size: 14px; }
.intel-row small, .muted { color: var(--muted); font-size: 12px; }

/* Radial menu on the clicked tile */
.radial { position: fixed; left: 0; top: 0; pointer-events: none; z-index: 2; } /* over the HUD's panels */
.radial.in .radial-item { animation: radial-in .26s cubic-bezier(.3,1.5,.5,1) both; }
.radial-item {
  position: absolute; left: 0; top: 0; width: 54px; height: 54px; border: 0; border-radius: 50%;
  pointer-events: auto; background: var(--glass); backdrop-filter: blur(6px);
  box-shadow: 0 6px 18px rgba(0, 0, 0, .3); outline: 1px solid rgba(255, 255, 255, .12);
  display: grid; place-items: center;
  transform: translate(-50%, -50%) rotate(var(--a)) translate(66px) rotate(calc(-1 * var(--a)));
  transition: background .12s, outline-color .12s;
}
/* The option under the mouse, or armed by a first tap (a second takes it). */
.radial-item.armed:not(:disabled) { background: rgba(40, 48, 68, .9); outline: 2px solid var(--accent); }
.radial-item:disabled { opacity: .45; cursor: default; }
.radial-item .face { font-size: 20px; line-height: 1; margin-top: -8px; }
.radial-item img { width: 34px; height: 34px; object-fit: contain; }
/* A remove that takes queued upgrades asks first (hud.js). */
.radial-item.sure .face { display: none; }
.radial-item.sure::before { content: 'Sure?'; margin-top: -6px; font: 800 12px var(--sans); color: var(--paper); }
.radial-item .cost {
  position: absolute; bottom: -7px; left: 50%; translate: -50% 0; padding: 1px 6px; border-radius: 7px;
  font: 800 12px var(--sans); color: var(--gold); background: rgba(16, 20, 32, .92); white-space: nowrap;
}
.radial-item.plan .cost { color: var(--muted); outline: 1px dashed rgba(255, 209, 102, .6); }
.radial-item kbd { position: absolute; top: 3px; right: 6px; font: 800 10px var(--sans); color: var(--muted); }
@media (pointer: coarse) { .radial-item kbd { display: none; } }
@keyframes radial-in { from { opacity: 0; transform: translate(-50%, -50%) scale(.4); } }
.radial-caption {
  position: absolute; left: 0; top: 30px; translate: -50% 0;
  display: flex; flex-direction: column; align-items: center; gap: 1px; white-space: nowrap;
  background: var(--glass); backdrop-filter: blur(6px); border-radius: 10px; padding: 5px 16px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .25);
}
.radial.flip .radial-caption { top: 114px; } /* under the arc, clear of the pills on top */
.radial-caption b { font-weight: 800; font-size: 14px; }
.radial-caption small { color: var(--muted); font-size: 12px; }
.radial-caption .confirm { color: var(--gold); }
/* Closes the menu: a round badge on the caption's corner, with a fingertip's hit area. */
.radial-close {
  --size: 24px;
  position: absolute; top: calc(var(--size) / -2 + 4px); right: calc(var(--size) / -2 + 4px); width: var(--size); height: var(--size);
  padding: 0; border: 0; border-radius: 50%; pointer-events: auto;
  background: rgb(52, 60, 82); box-shadow: 0 2px 8px rgba(0, 0, 0, .35); color: var(--paper); font-size: 15px; line-height: var(--size);
}
.radial-close::after { content: ''; position: absolute; inset: calc((var(--size) - 44px) / 2); }
@media (pointer: coarse) { .radial-close { --size: 28px; font-size: 17px; } }
@media (hover: hover) { .radial-close:hover { background: rgb(72, 82, 110); } }

/* Banner, toast, floating numbers. The banner and the toast centre by their
   boxes, not by a translate of -50%: an animated transform keeps what that
   came to when it started, so a line that widens as the font loads would
   paint off centre. The banner's lines centre on a line at 30% of the
   screen's height (a box of no height, which they overflow evenly). */
.banner {
  position: fixed; left: 0; right: 0; top: 30%; height: 0; padding: 0 8px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; pointer-events: none;
  text-shadow: 0 4px 24px rgba(0,0,0,.5);
}
.banner b { font: 700 44px var(--display); text-transform: uppercase; }
.banner span { font-size: 17px; font-weight: 800; color: #dfe6f5; text-align: center; text-wrap: balance; }
.banner span + span { color: var(--gold); text-shadow: 0 1px 2px rgba(0, 0, 0, .55), 0 0 12px rgba(0, 0, 0, .4), 0 4px 24px rgba(0, 0, 0, .5); } /* a puzzle's rule, kept legible over the light board */
.banner.in { animation: banner 2.6s cubic-bezier(.2,1.4,.4,1) both; }
@keyframes banner {
  0% { opacity: 0; transform: scale(.7); }
  12% { opacity: 1; transform: scale(1); }
  80% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: translateY(-10px) scale(.98); }
}
/* A long toast wraps, balanced, rather than run off a narrow screen. hud.js
   sets how long it shows (longer for more words). */
.toast {
  position: fixed; left: 0; right: 0; bottom: 40px; z-index: 3; margin-inline: auto; width: max-content; max-width: calc(100vw - 24px);
  background: rgba(255, 107, 74, .9); padding: 8px 16px; border-radius: 10px; font-size: 14px; font-weight: 700; text-align: center; text-wrap: balance; pointer-events: none;
}
.toast.in { animation: toast 1.6s ease both; }
@keyframes toast { 0% { opacity: 0; translate: 0 10px; } 10%, 80% { opacity: 1; translate: 0 0; } 100% { opacity: 0; } }
.float {
  position: fixed; pointer-events: none; font: 700 18px var(--display);
  text-shadow: 0 2px 8px rgba(0,0,0,.6); animation: float 1s ease-out forwards;
}
@keyframes float {
  0% { opacity: 0; transform: translate(-50%, 0) scale(.6); }
  15% { opacity: 1; transform: translate(-50%, -12px) scale(1.1); }
  100% { opacity: 0; transform: translate(-50%, -52px) scale(1); }
}

/* Outcome */
.outcome {
  position: fixed; inset: 0; display: grid; place-items: center;
  background: radial-gradient(ellipse at center, rgba(20,25,38,.35), rgba(20,25,38,.8));
  animation: fade .6s ease both;
}
.outcome-card {
  background: var(--glass); backdrop-filter: blur(10px); border-radius: 22px; padding: 28px 36px;
  display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center;
  box-shadow: 0 20px 60px rgba(0,0,0,.4); animation: pop .6s cubic-bezier(.2,1.5,.4,1) both;
}
.outcome h1 { font: 700 46px var(--display); text-transform: uppercase; }
.outcome.won h1 { color: var(--good); }
.outcome.lost h1 { color: var(--accent); }
.outcome p { color: #cfd6e6; font-size: 15px; }
.outcome p b { font-weight: 800; color: var(--paper); }
.outcome .grid { font-size: 24px; letter-spacing: 3px; line-height: 1; }
/* A longer key wraps rather than widening the card. */
.outcome .grid-key { max-width: 480px; margin-top: -4px; font-size: 12px; color: var(--muted); }
/* Lines of parts ("Flawless · 680 gold · 1 try") break only between parts. */
.outcome :is(.best, .grid-key) { text-wrap: balance; }
.outcome :is(.best, .grid-key) span { white-space: nowrap; }
/* Rows of buttons: play again, then share. */
.outcome-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-top: 8px; }
.outcome-actions + .outcome-actions { margin-top: -2px; } /* as far apart as the buttons in a row */
@keyframes fade { from { opacity: 0; } }
@keyframes pop { from { opacity: 0; transform: scale(.85); } }

/* Held sideways, the banner and the toast centre on the board, left of the
   side column (main.js sets --board-x); on desktops the result card does
   too, with 12px to spare each side, and one wider than that room starts at
   the left. (After the rules above, so it wins over them.) */
@media (orientation: landscape) {
  .banner, .toast { right: calc(100vw - 2 * var(--board-x, 50vw)); }
  .toast { max-width: calc(2 * var(--board-x, 50vw) - 24px); }
}
@media (orientation: landscape) and (min-height: 541px) {
  .outcome { padding: 0 max(12px, 100vw - 2 * var(--board-x, 50vw) + 12px) 0 12px; place-items: safe center; }
}

/* --- How to play --------------------------------------------------------------- */
/* Cards over a dimmed screen. The hole's shadow is the dim, so what a card
   explains stays lit, ringed in gold. */
.howto { position: fixed; inset: 0; z-index: 40; animation: fade .3s ease both; }
.howto.out { opacity: 0; transition: opacity .2s; }
.howto-hole {
  position: absolute; left: 0; top: 0; border-radius: 18px; pointer-events: none;
  box-shadow: 0 0 0 100vmax rgba(10, 13, 22, .6); outline: 2px solid rgba(255, 209, 102, .85); outline-offset: 2px;
}
.howto-hole.round { border-radius: 50%; }
.howto-hole.none { outline-color: transparent; }
.howto-card {
  --card: rgba(24, 30, 46, .97);
  position: absolute; left: 0; top: 0; width: min(290px, calc(100vw - 20px));
  max-height: calc(100vh - 20px); max-height: calc(100dvh - 20px); display: flex; flex-direction: column;
  background: var(--card); border-radius: 16px; padding: 14px 16px 10px;
  box-shadow: 0 16px 44px rgba(0, 0, 0, .45);
}
.howto-card:focus { outline: none; } /* it takes focus on open, so keys land in the dialog */
/* The card slides between places; the hole slides from howto.js, only between cards. */
.howto.live .howto-card { transition: transform .45s cubic-bezier(.2, .9, .3, 1); }
/* The arrow on the card's edge, pointing at the hole. */
.howto-card::before { content: ''; position: absolute; border: 8px solid transparent; }
.howto-card[data-side=""]::before { display: none; }
.howto-card[data-side=below]::before { bottom: 100%; left: var(--arrow); margin-left: -8px; border-bottom-color: var(--card); }
.howto-card[data-side=above]::before { top: 100%; left: var(--arrow); margin-left: -8px; border-top-color: var(--card); }
.howto-card[data-side=left]::before { left: 100%; top: var(--arrow); margin-top: -8px; border-left-color: var(--card); }
.howto-card[data-side=right]::before { right: 100%; top: var(--arrow); margin-top: -8px; border-right-color: var(--card); }
/* The text scrolls if a screen is too short for it; the buttons below always show. */
.howto-body { display: flex; flex-direction: column; gap: 7px; min-height: 0; overflow: hidden auto; overscroll-behavior: contain; scrollbar-width: thin; }
.howto-body h2 { font: 700 21px var(--display); text-transform: uppercase; }
.howto-body p { font-size: 15px; line-height: 1.4; color: #cfd6e6; }
.howto-body b { font-weight: 800; color: var(--paper); }
.howto-body b.good { color: var(--good); }
.howto-body b.gold { color: var(--gold); }
.howto-body > small { font-size: 13px; line-height: 1.4; color: var(--muted); }
.howto-body i { font-style: normal; } /* the list's buttons, ▲ and ✕; spaced by the text itself */
.howto-card footer { flex: none; display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 10px; }
.howto-dots { display: flex; }
.howto-dots button { width: 18px; height: 22px; border: 0; padding: 0; background: none; display: grid; place-items: center; }
.howto-dots button::after { content: ''; width: 6px; height: 6px; border-radius: 3px; background: rgba(255, 255, 255, .25); transition: width .2s, background .2s; }
.howto-dots button.on::after { width: 14px; background: var(--accent); }
/* The example moves list, drawn like the real one. */
.howto-list { flex: none; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 3px; counter-reset: step; }
.howto-list li {
  display: grid; grid-template-columns: 14px minmax(0, 1fr) auto 46px; align-items: center; gap: 6px; padding: 3px 6px;
  border-radius: 6px; background: rgba(255, 255, 255, .06); font-size: 13px; font-weight: 800; counter-increment: step;
}
.howto-list li::before { content: counter(step); color: var(--muted); font-size: 12px; }
.howto-list em { display: inline-block; min-width: 2.2em; margin-right: 4px; padding: 0 3px; border-radius: 4px; background: rgba(255, 255, 255, .14); color: inherit; font-style: normal; text-align: center; }
.howto-list b { font-variant-numeric: tabular-nums; }
.howto-list .wait :is(span, b) { color: var(--gold); }
.howto-list small { text-align: right; font-size: 12px; font-weight: 700; color: var(--muted); }
/* The card with the list is wider, and where there's room the list takes two
   columns (howto.js narrows a card that fits nowhere, so this asks the card). */
.howto-card.wide { width: min(420px, calc(100vw - 20px)); }
.howto-body { container-type: inline-size; }
@container (min-width: 350px) {
  .howto-list { display: grid; grid-template-rows: repeat(3, auto); grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 3px 6px; }
}
/* The ? that opens the cards again, beside the sound button (or in a corner of its own). */
.chip.howto-btn { position: relative; align-self: flex-start; padding: 4px; margin: -4px -8px 0 -4px; line-height: 0; }
.chip.howto-btn::after { content: ''; position: absolute; inset: -6px -6px -6px 0; } /* a fingertip's reach, short of the sound button */
.hud > .howto-btn { position: fixed; right: 22px; bottom: 18px; }
/* The note by the ? on a first visit to someone's run. */
.howto-note {
  --card: rgba(24, 30, 46, .97);
  position: fixed; z-index: 39; border: 0; padding: 7px 12px; border-radius: 10px; background: var(--card);
  color: #cfd6e6; font-size: 14px; box-shadow: 0 10px 30px rgba(0, 0, 0, .4);
  transform-origin: top right; animation: pop .4s cubic-bezier(.2, 1.5, .4, 1); transition: opacity .3s;
}
.howto-note[data-side=above] { transform-origin: bottom right; }
.howto-note[data-side=left] { transform-origin: right center; translate: 0 -50%; }
.howto-note b { font-weight: 800; color: var(--gold); }
.howto-note.out { opacity: 0; }
.howto-note::before { content: ''; position: absolute; right: var(--arrow); margin-right: -7px; border: 7px solid transparent; }
.howto-note[data-side=below]::before { bottom: 100%; border-bottom-color: var(--card); }
.howto-note[data-side=above]::before { top: 100%; border-top-color: var(--card); }
.howto-note[data-side=left]::before { right: auto; left: 100%; top: 50%; margin: -7px 0 0; border-left-color: var(--card); }
/* Held sideways, too narrow for the note beside the ?'s pill: the side column makes way under it. */
@media (orientation: landscape) {
  body:has(> .howto-note[data-side=below]:not(.out)) .hud > .intel { margin-top: 38px; }
}
/* Once the result card is up, the ? can't open the cards: out of sight. */
body:has(> .outcome) :is(.howto-btn, .howto-note) { visibility: hidden; }
/* The HUD's par, read as gold: "Par 680 gold". */
.clock .value small { font-size: max(10px, .5em); letter-spacing: .1em; color: var(--gold); }
/* Fingers: bigger hit areas, drawn the same. */
@media (pointer: coarse) {
  .chip.howto-btn { padding: 8px; margin: -8px -12px 0 -8px; }
  .howto-card footer .chip { min-height: 36px; padding-right: 16px; }
  .howto-card footer .btn { min-height: 36px; }
  .howto-dots button { width: 22px; height: 36px; }
}
/* The phone and tablet HUD (portrait, or short landscape): the ? sits centred by
   the sound button, and par drops its unit for room. */
@media (orientation: portrait), (max-height: 540px) {
  .chip.howto-btn { align-self: center; margin: -4px -8px -4px 0; }
  @media (pointer: coarse) { .chip.howto-btn { margin: -8px -12px -8px 0; } }
  .clock .value small { display: none; }
}
/* Upright, the base pill's column is also the one beside the par, so the ?
   would take its width from the par: the base pill reaches over instead. */
@media (orientation: portrait) { .hud > .base:has(.howto-btn) { grid-column: 2 / -1; } }
/* Short screens (phones held sideways): wider cards, so the text takes fewer
   lines (unless howto.js narrows one to fit beside what it points at). */
@media (max-height: 440px) {
  .howto-card, .howto-card.wide { width: min(520px, calc(100vw - 20px)); padding: 10px 12px 6px; }
  .howto-body { gap: 5px; }
  .howto-body .label { display: none; } /* "How to play": the dots and buttons say it here */
  .howto-body h2 { font-size: 17px; }
  .howto-body p { font-size: 14px; line-height: 1.35; }
  .howto-card footer { margin-top: 6px; }
}

/* --- Dev panel --------------------------------------------------------------- */
.dev {
  position: fixed; right: 12px; top: 12px; bottom: 12px; width: 300px; z-index: 50;
  background: rgba(12, 15, 24, .92); border-radius: 12px; font: 12px system-ui, sans-serif; color: #d6dbea;
  display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 10px 40px rgba(0,0,0,.5);
}
.dev header { display: flex; align-items: center; gap: 6px; padding: 8px; border-bottom: 1px solid #ffffff14; flex-wrap: wrap; }
.dev.min { bottom: auto; width: auto; }
/* Minimised on a desktop: bottom left, clear of the sound button and the ?. */
@media (orientation: landscape) and (min-height: 541px) { .dev.min { top: auto; bottom: 12px; right: auto; left: 12px; } }
.dev.min nav, .dev.min main, .dev.min [data-reset] { display: none; }
.dev.min header { border-bottom: 0; }
.dev header b { font: 11px var(--display); color: var(--accent); margin-right: 4px; }
.dev nav { display: flex; gap: 2px; flex: 1; flex-wrap: wrap; }
.dev button { border: 0; background: #ffffff12; border-radius: 6px; padding: 4px 8px; font-size: 11px; }
.dev button:hover { background: #ffffff22; }
.dev nav button.on { background: var(--accent); color: #fff; }
.dev main { overflow-y: auto; padding: 8px 10px; }
.dev section { display: flex; flex-direction: column; gap: 6px; }
.dev section > button { align-self: stretch; text-align: left; padding: 6px 8px; }
.dev label { display: grid; grid-template-columns: 1fr 110px 44px; align-items: center; gap: 6px; }
.dev label:has(input[type=checkbox]) { grid-template-columns: 1fr auto; }
.dev input[type=range] { width: 100%; accent-color: var(--accent); }
.dev output { text-align: right; font-variant-numeric: tabular-nums; color: #fff; }
.dev pre { background: #ffffff0a; border-radius: 6px; padding: 8px; white-space: pre-wrap; word-break: break-all; font: 11px ui-monospace, monospace; }

/* Social sieges: your plan list and pins for planned builds */
.plans {
  grid-area: plans; align-self: start; max-height: 100%; min-height: 0;
  flex-direction: column; align-items: stretch; justify-content: flex-start; gap: 6px;
  padding: 8px 12px;
}
.plans ol:empty { display: none; }
.plans ol { flex: 0 1 auto; min-height: 0; }
/* The hint wraps to the list's width instead of widening it. */
.plans [data-plan-hint] { width: 0; min-width: 100%; }
.plans .plan-rule { font-weight: 800; color: var(--gold); } /* a puzzle's rule leads it until the first step */
.plans li { padding: 3px 4px; border-radius: 6px; cursor: default; }
.plans li.lit { background: rgba(255, 255, 255, .08); }
@media (hover: hover) { .plans li:hover { background: rgba(255, 255, 255, .08); } }
.plans ol { margin: 0; padding: 0; list-style: none; overflow: auto; overscroll-behavior: contain; touch-action: pan-y; scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, .2) transparent; display: flex; flex-direction: column; gap: 3px; counter-reset: plan; }
.plans li { display: grid; grid-template-columns: 18px 1fr auto auto; align-items: center; gap: 6px; font-size: 13px; counter-increment: plan; }
.plans li::before { content: counter(plan); color: var(--muted); font-size: 12px; font-weight: 700; }
.plans li b { font-weight: 800; font-variant-numeric: tabular-nums; color: var(--gold); }
.plans li.done { opacity: .45; }
.plans li.placed b { color: var(--paper); }
.plans header { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.plans header .label { white-space: nowrap; }
.hud.battle [data-clear] { display: none; } /* nothing to clear once the siege executes */
.plans .sheet-toggle { display: none; } /* portrait only */
.plans li.next b { color: var(--paper); }
.plans .plan-tools { display: flex; gap: 2px; }
.plans .chip:disabled { opacity: .3; }
.plans li i { color: var(--good); font-style: normal; }
/* Drag to reorder: the row lifts and follows the pointer, a gold line marks where it lands. */
.plans li.movable { cursor: grab; }
.plans li.dragging { position: relative; z-index: 1; cursor: grabbing; background: rgba(60, 70, 96, .95); box-shadow: 0 6px 16px rgba(0, 0, 0, .35); }
.plans li.drop-before { box-shadow: inset 0 2px 0 var(--gold); }
.plans li.drop-after { box-shadow: inset 0 -2px 0 var(--gold); }
.plans .chip { padding: 0 6px; }
.plan-pin {
  position: fixed; left: 0; top: 0; pointer-events: none; white-space: nowrap;
  font: 800 12px var(--sans); color: var(--gold); padding: 2px 7px; border-radius: 8px;
  background: rgba(16, 20, 32, .78); outline: 1px dashed rgba(255, 209, 102, .7);
}
/* Chess-style moves: the tower's piece letter (G, or G+ for an upgrade) as a tag before the square.
   White is paid, gold waits: the move, its tag and its gold. */
.plans .move { font-weight: 800; white-space: nowrap; color: var(--gold); }
.plans li:is(.placed, .done, .next) .move { color: var(--paper); }
.plans .piece { display: inline-block; min-width: 2.2em; margin-right: 4px; padding: 0 3px; border-radius: 4px; background: rgba(255, 255, 255, .14); color: inherit; font-style: normal; text-align: center; }

/* --- Phones and tablets ------------------------------------------------------ */
/* Portrait screens and short landscape ones get slimmer pills, bigger touch
   targets and the actions (Execute, speeds) in thumb reach. The camera turns
   the board to run up a portrait screen (stage.js), so the cells stay big. */
@media (orientation: portrait), (max-height: 540px) {
  .hud {
    gap: 8px;
    padding: max(8px, env(safe-area-inset-top)) max(8px, env(safe-area-inset-right)) max(8px, env(safe-area-inset-bottom)) max(8px, env(safe-area-inset-left));
  }
  .pill { padding: 6px 12px; gap: 12px; border-radius: 12px; }
  .label { font-size: 9px; letter-spacing: .1em; }
  .value { font-size: 17px; }
  .stat { min-width: 0; }
  /* What's spent sits beside the gold left, so the pills stay two lines tall. */
  .stats .stat { display: grid; grid-template-columns: auto auto; justify-content: start; align-items: baseline; }
  .stats .label { grid-column: 1 / -1; white-space: nowrap; }
  .stats small { grid-area: 2 / 2; margin-left: 6px; font-size: 9px; }
  .base { position: relative; }
  .base .hpbar { position: absolute; left: 12px; right: 12px; bottom: 5px; height: 3px; } /* along the pill's foot */
  @media (max-width: 420px), (max-height: 540px) { .stats .planned { display: none; } }
  .clock-name { display: none; } /* just the puzzle's number over the par */
  .chip.sound { padding: 8px; margin: -8px -10px -8px -4px; align-self: center; }
  .clock, .clock.bare { min-width: 0; max-width: 100%; padding: 6px 8px; justify-content: center; }
  .clock .value { font-size: 18px; }
  .clock .bar, .clock .bar[hidden] { display: none !important; }
  .actions { grid-area: actions; margin: 0; height: auto; gap: 4px; }
  .actions .btn.primary { padding: 0 20px; min-height: 44px; font-size: 13px; border-radius: 12px; box-shadow: 0 6px 20px rgba(0, 0, 0, .25); }
  .actions .chip { min-width: 44px; min-height: 44px; border-radius: 12px; background: var(--glass); font-size: 13px; }
  .actions .chip.on { background: rgba(70, 80, 108, .92); }
  [data-speeds] { display: flex; gap: 4px; }

  /* Intel is a chip (its icons) that opens over the board. */
  .intel { padding: 0; gap: 0; backdrop-filter: none; background: rgba(20, 25, 38, .86); position: relative; z-index: 1; } /* opens over the moves' buttons */
  .intel-head { min-height: 44px; padding: 0 12px; gap: 8px; }
  .intel-head .label { flex: none; }
  .intel-name { display: none; }
  .intel.open .intel-head img { display: block; }
  .intel-body {
    position: absolute; left: 0; bottom: calc(100% + 8px); width: min(270px, calc(100vw - 16px)); padding: 12px 14px;
    background: var(--glass); backdrop-filter: blur(8px); border-radius: 12px; box-shadow: 0 10px 30px rgba(0, 0, 0, .35);
  }

  /* The colour hint gives its room to the moves, and shows in the opened sheet. */
  .plans { padding: 8px 10px; }
  .plans:not(.open) ol:not(:empty) ~ [data-plan-hint] { display: none; }

  .radial-caption { width: max-content; max-width: 220px; white-space: normal; text-align: center; }
  .toast { bottom: auto; top: calc(max(8px, env(safe-area-inset-top)) + 64px); padding: 6px 12px; font-size: 13px; }
  .banner b { font-size: clamp(22px, 7.2vw, 44px); white-space: nowrap; }
  .banner span { font-size: 14px; } /* a line too long for the screen wraps, balanced */
}

/* Phones and tablets: finger-sized targets, and no frosted glass. Blurring
   what's behind a panel redoes the blur every frame over the moving board,
   which costs a phone's GPU more than it shows; a denser tint instead. */
@media (pointer: coarse) {
  .pill, .radial-item, .radial-caption, .intel-body, .outcome-card { backdrop-filter: none; }
  .pill, .radial-item, .radial-caption, .intel-body, .outcome-card { background: rgba(20, 25, 38, .86); }
  /* Panels that open over the board hide it fully (no blur here to soften what would show through). */
  .intel-body, .plans.open { background: rgb(20, 25, 38); }
  .plans li { min-height: 44px; padding: 2px 4px; font-size: 13px; }
  .plans .plan-tools { gap: 8px; }
  .plans .chip { min-width: 44px; min-height: 40px; font-size: 14px; }
  .plans header .chip { position: relative; min-height: 30px; }
  .plans header .chip::after { content: ''; position: absolute; inset: -7px -4px; }
}
/* Tablets in the desktop layout: a taller Execute and speeds beside the clock. */
@media (pointer: coarse) and (orientation: landscape) and (min-height: 541px) {
  .actions .btn { height: 44px; }
  .actions .chip { min-width: 40px; height: 36px; }
  [data-speeds] { backdrop-filter: none; background: rgba(20, 25, 38, .86); }
}

/* Portrait: stats on top, the board, then the moves and, at the bottom,
   intel, the clock and the actions. */
@media (orientation: portrait) {
  .hud {
    grid-template-columns: auto minmax(0, 1fr) auto;
    /* The board row takes the height the board needs across the frame's
       width (--frame-w; --fit: the board's height per width; --board, set
       from stage.js, is its taller view, 1.2 for a 16x10 board turned up the
       screen), or what's left; the moves get the rest, at least a strip.
       Neither row depends on what's in it, and the bottom row keeps one
       height in planning and battle, so the board frame keeps one size. */
    --board: 1.2;
    --fit: var(--board);
    --frame-w: calc(100vw - 16px);
    grid-template-rows: auto minmax(0, calc(var(--frame-w) * var(--fit))) minmax(48px, 1fr) minmax(48px, auto);
    grid-template-areas:
      "stats stats base"
      "board board board"
      "plans plans plans"
      "intel clock actions";
  }
  .plans { align-self: end; height: 100%; padding: 6px 10px; gap: 4px; }
  /* Before the first step: the hint, centred in the sheet. */
  .plans.empty [data-plan-hint] { margin: auto 0; padding: 0 16px; text-align: center; font-size: 12px; line-height: 1.6; }
  .plans.empty header .chip { visibility: hidden; } /* nothing to clear or open yet */
  /* Opened by its header or the round ▴, it grows up over the board; the board frame stays put. */
  .plans header { min-height: 28px; }
  .plans header .label { margin-right: auto; }
  .plans .sheet-toggle { display: block; min-width: 34px; min-height: 26px; padding: 0; border-radius: 13px; background: rgba(255, 255, 255, .12); color: var(--paper); font-size: 12px; line-height: 1; }
  .plans .sheet-toggle::before { content: '\25B4'; }
  .plans.open .sheet-toggle::before { content: '\25BE'; }
  .plans.open { height: auto; min-height: 100%; max-height: calc(62 * var(--vh)); box-shadow: 0 -10px 40px rgba(0, 0, 0, .45); }
  /* A sheet too short for a few rows is a strip (hud.js): the steps side by
     side, numbered, like a chess app's move list; white is paid, gold waits.
     A tap on one lights its tower; the round ▴ opens the list to edit it. */
  .plans.strip { flex-direction: row; align-items: center; padding: 6px 6px 6px 8px; gap: 6px; }
  .plans.strip header { order: 1; align-self: stretch; min-height: 0; }
  .plans.strip header .label, .plans.strip [data-clear], .plans.strip.empty header { display: none; }
  .plans.strip ol { flex: 1; align-self: stretch; flex-direction: row; align-items: center; gap: 4px; overflow: auto hidden; touch-action: pan-x; scrollbar-width: none; }
  .plans.strip ol::-webkit-scrollbar { display: none; }
  .plans.strip li { flex: none; display: flex; gap: 6px; height: min(44px, 100%); min-height: 0; padding: 0 10px 0 8px; border-radius: 10px; background: rgba(255, 255, 255, .07); cursor: pointer; }
  .plans.strip li.lit { background: rgba(255, 255, 255, .2); }
  .plans.strip li :is(b, i), .plans.strip .plan-tools { display: none; }
  .plans.strip li.next b { display: inline; } /* in the battle: what the gold is saving for */
  .plans.strip [data-plan-hint] { width: auto; min-width: 0; max-height: 100%; overflow: hidden; flex: 1; margin: 0; padding: 0 8px; font-size: 11px; line-height: 1.35; }
  /* Short of room (hud.js), the hint drops its second sentence and shows two whole lines at most. */
  .plans.strip [data-plan-hint].short { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; }
  .plans.strip [data-plan-hint].short .hint-more { display: none; }
  .plans.strip .sheet-toggle { min-width: 44px; height: min(44px, 100%); border-radius: 12px; }
  .intel { align-self: end; }
  .intel-head { padding: 0 10px; gap: 4px; }
  .intel-head .label { display: none; }
  .hud.battle .intel { display: none; }
  .hud.battle .clock { grid-column: 1 / 3; }
  .clock.bare { grid-column: 2 / 4; } /* a replay's countdown: no actions next to it yet */
  .actions .btn.primary { padding: 0 16px; }
  .clock { align-self: center; justify-self: center; background: none; box-shadow: none; backdrop-filter: none; }
  .actions { justify-self: end; align-self: end; }
  .base { justify-self: end; }
}

/* Narrow phones: slimmer intel and Execute leave the par its room. */
@media (orientation: portrait) and (max-width: 380px) {
  .stats { gap: 8px; padding-inline: 10px; } /* clear of the base pill, which reaches over for the ? */
  .intel-head { padding: 0 8px; gap: 2px; }
  .intel-head img { width: 18px; height: 18px; }
  .actions .btn.primary { padding: 0 12px; font-size: 12px; }
  .clock .value { font-size: 16px; }
}

/* Phones in portrait: the board runs edge to edge. */
@media (orientation: portrait) and (max-width: 599px) {
  .hud { --frame-w: 100vw; }
  .board-frame { margin-inline: -8px; }
}

/* Phones: a pin on the board is just its step's number (the moves name it),
   in full while its row is lit. */
@media (orientation: portrait) and (max-width: 599px), (max-height: 540px) {
  .plan-pin { padding: 1px 6px; }
  .plan-pin:not(.lit) .pin-word { display: none; }
}

/* Portrait tablets: room for full-size pills and the puzzle's name. The
   board keeps its wider view here (its cells are big enough), so the moves
   get the height that view doesn't need. */
@media (orientation: portrait) and (min-width: 600px) {
  .hud { --fit: var(--board-wide, .6); gap: 12px; }
  .clock-name { display: inline; }
  .pill { padding: 10px 16px; gap: 16px; }
  .label { font-size: 11px; letter-spacing: .14em; }
  .value { font-size: 20px; }
  .stats small { font-size: 11px; }
  .clock .value { font-size: 26px; }
  .intel-head .label { display: inline; }
}

/* Short landscape (phones on their side): the actions end the side column. */
@media (orientation: landscape) and (max-height: 540px) {
  .hud {
    --side: 240px; /* most phones (19.5:9) leave the board height-bound here, so the column costs it nothing; 16:9 ones lose some width */
    grid-template-columns: auto auto minmax(0, 1fr) var(--side); /* the par right after the stats */
    grid-template-rows: auto auto minmax(0, 1fr) auto;
    grid-template-areas:
      "stats clock . base"
      "board board board intel"
      "board board board plans"
      "board board board actions";
  }
  .intel { justify-self: stretch; }
  .intel-head { justify-content: space-between; }
  .intel-body { left: auto; right: 0; bottom: auto; top: calc(100% + 8px); }
  .actions { justify-self: stretch; }
  .actions .btn.primary { flex: 1; }
  .actions [data-speeds] { flex: 1; justify-content: space-between; }
  .plans li { column-gap: 4px; }
  .plans .plan-tools { gap: 6px; }
  .plans .chip { min-width: 40px; }
}

/* Result card on small screens. */
@media (max-width: 600px), (max-height: 540px) {
  .outcome { padding: 12px; overflow: auto; touch-action: pan-y; }
  .outcome-card { padding: 22px 18px; max-width: 100%; }
  .outcome h1 { font-size: clamp(30px, 10vw, 46px); }
  .outcome .grid { font-size: 19px; letter-spacing: 2px; }
  .outcome-actions .btn { min-height: 44px; }
  .dev { width: min(300px, calc(100vw - 24px)); }
  .dev main { touch-action: pan-y; }
}
/* Narrow screens: the buttons two to a line, the main one across, and so is
   one left alone at the end of its row. */
@media (max-width: 600px) {
  .outcome-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); align-self: stretch; }
  .outcome-actions > .primary,
  .outcome-actions > .primary ~ :last-child:nth-child(even),
  .outcome-actions > :first-child:not(.primary) ~ :last-child:nth-child(odd),
  .outcome-actions:has(> [data-today]) > .primary ~ * { grid-column: 1 / -1; } /* a replay's: too long to pair */
  .outcome-actions .btn { padding-inline: 8px; }
}
