:root {
  /* Graded palette shared with the HUD: violet shadows, warm highlights. */
  --ink: #120a18;
  --panel: rgba(28, 14, 40, 0.94);
  --panel-top: rgba(52, 26, 70, 0.94);
  --panel-edge: #4a2a5a;
  --rim-lit: #a898b4;
  --rim-dark: #3a2a48;
  --bevel-lit: rgba(255, 240, 220, 0.4);
  --bevel-dark: rgba(0, 0, 0, 0.45);
  --btn: #3a1f4a;
  --btn-hover: #4e2c62;
  --btn-focus: #2e5a2a;
  --gold: #ffd23a;
  --fire: #ff8a2a;
  --red: #d0342a;
  --green: #7cff6b;
  --text: #f4ecff;
  --muted: #c4b4d4;
  --font: ui-monospace, "Cascadia Mono", Consolas, "Lucida Console", monospace;
}

* { box-sizing: border-box; }

/* A game page, not a document: no page scroll, pull-to-refresh, double-tap zoom, text selection,
   long-press callout or tap flash. Overlay panels still scroll inside themselves (touch-action pan-y). */
html, body {
  margin: 0;
  height: 100%;
  height: 100dvh;
  background: #000;
  color: var(--text);
  font-family: var(--font);
  overflow: hidden;
  overscroll-behavior: none;
  touch-action: manipulation;
  -webkit-text-size-adjust: 100%;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}

/* The stage tracks the dynamic viewport (iOS toolbar) and keeps the canvas inside the safe area
   (notch, home bar); main.js reads the same insets from #safe-probe to place the touch controls. */
#stage {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  display: grid;
  place-items: center;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
  background: #000;
}
#safe-probe {
  position: absolute;
  width: 0;
  height: 0;
  visibility: hidden;
  pointer-events: none;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}
/* Touch players in portrait: the canvas full width at the top, the controls on a dark deck below. */
html.portrait #stage {
  place-items: start center;
  background: radial-gradient(120% 70% at 50% 100%, #2a1438, #120a18 70%);
}

#game {
  touch-action: none;
  display: block;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  background: #000;
}

canvas, canvas.px, #logo, #badge-face {
  display: block;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

.overlay {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  padding: max(1rem, env(safe-area-inset-top)) max(1rem, env(safe-area-inset-right)) max(1rem, env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left));
}
.overlay.hidden { display: none; }

/* Stepped entrance keeps the motion pixel-chunky. */
@keyframes panel-in {
  0% { transform: translateY(24px) scale(0.92); opacity: 0; }
  100% { transform: none; opacity: 1; }
}
@keyframes logo-in {
  0% { transform: translateY(-40px) scale(1.3); opacity: 0; }
  70% { transform: translateY(4px) scale(0.98); opacity: 1; }
  100% { transform: none; }
}
@keyframes crown-bob {
  50% { transform: translateY(-3px); }
}
.overlay:not(.hidden) .panel { animation: panel-in 220ms steps(5) both; }

/* Scene wipe (wipe() in src/hud.js): a pixel shutter over Start, Rematch and Quit to title. Six ink bands close
   from alternating sides in six hard steps, the scene swaps under full cover, and the bands sweep on out the far
   side (the open's first step is the held frame). Above the canvas and the touch controls (same layer as #touch,
   later in the DOM), under the overlays, so a panel's entrance plays over the opening bands. The bands are only
   visible inside the keyframes: at rest, and under reduced motion's `animation: none`, nothing covers the screen. */
#wipe {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  pointer-events: none;
}
#wipe > i { flex: 1; visibility: hidden; background: var(--ink); --side: 1; }
#wipe > i:nth-child(even) { --side: -1; }
#wipe.close > i { animation: wipe-close 100ms steps(6, jump-start) forwards; }
#wipe.open > i { animation: wipe-open 100ms steps(6); }
@keyframes wipe-close {
  from { visibility: visible; transform: translateX(calc(-100% * var(--side))); }
  to { visibility: visible; transform: none; }
}
@keyframes wipe-open {
  from { visibility: visible; transform: none; }
  to { visibility: visible; transform: translateX(calc(100% * var(--side))); }
}

/* Framed panel, like the HUD meters: ink outline, bevelled metal rim (lit top-left, shaded bottom-right),
   an inner bevel and gold corner rivets. Whole-pixel sizes only, so it stays crisp at any scale. */
.panel {
  position: relative;
  background: linear-gradient(180deg, var(--panel-top), var(--panel) 40%);
  border: 3px solid;
  border-color: var(--rim-lit) var(--rim-dark) var(--rim-dark) var(--rim-lit);
  box-shadow: 0 0 0 3px var(--ink), inset 2px 2px 0 var(--bevel-lit), inset -2px -2px 0 var(--bevel-dark), inset 0 0 0 5px var(--ink), 9px 9px 0 3px rgba(0, 0, 0, 0.5);
  padding: 20px 26px;
  max-width: min(36rem, 94vw);
  max-height: 94vh;
  max-height: 94dvh;
  overflow: auto;
  overscroll-behavior: contain;
  touch-action: pan-y;
  text-align: center;
}
.panel.wide { max-width: min(48rem, 96vw); }
.panel::before {
  content: "";
  position: absolute;
  inset: 7px;
  pointer-events: none;
  background:
    linear-gradient(var(--gold), var(--gold)) top left / 3px 3px no-repeat,
    linear-gradient(var(--gold), var(--gold)) top right / 3px 3px no-repeat,
    linear-gradient(var(--gold), var(--gold)) bottom left / 3px 3px no-repeat,
    linear-gradient(var(--gold), var(--gold)) bottom right / 3px 3px no-repeat;
}

/* ── Title: box-art layout ── */
#title { align-content: center; }
.title-panel {
  background: linear-gradient(180deg, rgba(36, 14, 46, 0.6), rgba(28, 12, 38, 0.94) 45%);
  border-color: #ffb070 #6a2a22 #6a2a22 #ffb070;
  padding-top: 10px;
}
.logo { margin: 0 auto 0.2rem; }
#logo {
  width: min(32rem, 82vw);
  height: auto;
  margin: 0 auto;
  animation: logo-in 520ms steps(8) both;
  filter: drop-shadow(0 0 18px rgba(255, 120, 40, 0.45));
}

.badges {
  position: absolute;
  top: 1rem;
  left: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  pointer-events: none;
}
.badge {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 8px 10px;
  background: #1a1028;
  border: 2px solid;
  border-color: #ffe27a #8a5a10 #8a5a10 #ffe27a;
  box-shadow: 0 0 0 3px var(--ink), inset 2px 2px 0 var(--bevel-lit), inset -2px -2px 0 var(--bevel-dark), 6px 6px 0 2px rgba(0, 0, 0, 0.5);
}
.badge.arcade { border-color: #ffb070 #8a3a1a #8a3a1a #ffb070; }
.badge.arcade::after {
  content: "";
  width: 100%;
  height: 6px;
  margin-top: 2px;
  background: linear-gradient(90deg, var(--red) 0 33%, var(--fire) 33% 66%, var(--gold) 66%);
}
#badge-face {
  width: 72px;
  height: 56px;
  background: #1e2a44;
  border: 2px solid var(--ink);
}
.badge.players { flex-direction: row; gap: 8px; }

/* Title loading bar: box-art strip, stepped fill; turns gold with a crowned PRESS START when ready. */
.loadbar {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  margin: 0.4rem auto 0.2rem;
  width: min(18rem, 80%);
}
.loadbar .track {
  width: 100%;
  height: 12px;
  background: #2a1a20;
  border: 2px solid;
  border-color: var(--rim-lit) var(--rim-dark) var(--rim-dark) var(--rim-lit);
  box-shadow: 0 0 0 2px var(--ink), inset 0 2px 0 rgba(0, 0, 0, 0.35), 4px 4px 0 2px rgba(0, 0, 0, 0.5);
}
.loadbar .fill {
  width: 0;
  height: 100%;
  background: repeating-linear-gradient(90deg, var(--fire) 0 6px, var(--gold) 6px 8px);
}
.loadbar.ready .track { border-color: #ffe27a #8a5a10 #8a5a10 #ffe27a; }
.loadbar.ready .fill { background: var(--gold); }
.menu button:disabled { opacity: 0.45; cursor: progress; }
/* Boot failure (index.html's boot guard): the message and Retry replace the loader and the menu. */
.bootfail { display: none; }
.boot-failed .bootfail { display: grid; justify-items: center; gap: 0.8rem; margin: 0.8rem auto; }
.boot-failed #loadbar, .boot-failed #title .menu { display: none; }
.bootfail p {
  margin: 0;
  font: bold 18px var(--font);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--gold);
  text-shadow: 2px 2px 0 var(--ink);
}
.bootfail button { min-width: 10rem; min-height: 46px; }
/* No game code ran, so no bitmap logo or legend: the heading shows as text instead of an empty logo box. */
.boot-failed #logo, .boot-failed #title .legend { display: none; }
.boot-failed #title-heading .sr-only {
  position: static;
  width: auto;
  height: auto;
  margin: 0.4rem 0;
  overflow: visible;
  clip: auto;
  clip-path: none;
  white-space: normal;
  font: bold 30px var(--font);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--gold);
  text-shadow: 3px 3px 0 var(--ink);
}
@media (prefers-reduced-motion: no-preference) {
  .loadbar.ready canvas { animation: blink-ready 1s steps(2) infinite; }
}
@keyframes blink-ready { 50% { opacity: 0.55; } }

h2, h3 {
  margin: 0.2rem auto 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--gold);
  text-shadow: 2px 2px 0 var(--ink);
  display: flex;
  justify-content: center;
}
h2 { font-size: 1.8rem; }
h3 { font-size: 1rem; margin-top: 1rem; }

.tag, .sub, .hint, .hiscore { color: var(--muted); margin: 0.35rem 0; text-wrap: balance; }
.tag { font-weight: bold; color: var(--text); text-shadow: 2px 2px 0 var(--ink); }
.hiscore { color: var(--gold); font-weight: bold; }

.menu {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  margin: 1rem auto;
  width: min(17rem, 100%);
}
.menu.row { flex-direction: row; justify-content: center; width: auto; }

/* Bevelled buttons: ink edge, lit top-left / shaded bottom-right bevel, a gloss row and a hard drop shadow. */
button, select, .keymap button {
  position: relative;
  font: bold 16px var(--font);
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--text);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.1) 0 3px, transparent 3px), var(--btn);
  border: 3px solid var(--ink);
  box-shadow: inset 2px 2px 0 var(--bevel-lit), inset -2px -3px 0 var(--bevel-dark), 4px 4px 0 var(--ink);
  padding: 8px 16px;
  cursor: pointer;
  transition: transform 60ms steps(2), background-color 60ms steps(2);
}
button[data-pixel] { display: flex; justify-content: center; align-items: center; min-height: 46px; }
button:hover { background-color: var(--btn-hover); }
button:active { transform: translate(2px, 2px); box-shadow: inset 2px 2px 0 var(--bevel-dark), 2px 2px 0 var(--ink); }

:focus-visible,
button:focus,
select:focus,
input:focus {
  outline: 3px solid var(--green);
  outline-offset: 2px;
}
button:focus {
  background-color: var(--btn-focus);
  color: #fff;
  box-shadow: inset 2px 2px 0 rgba(200, 255, 170, 0.55), inset -2px -3px 0 var(--bevel-dark), 4px 4px 0 var(--ink);
  transform: translateX(4px);
}
/* Pixel pointer beside the focused menu entry. */
.menu button:focus::before {
  content: "";
  position: absolute;
  left: -22px;
  top: 50%;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 8px 0 8px 12px;
  border-color: transparent transparent transparent var(--green);
  transform: translateY(-50%);
  animation: crown-bob 600ms steps(2) infinite;
}

/* Controls legend (title and pause): a recessed socket like the settings controls, holding a
   two-column key table (keys in gold, right-aligned) and the gamepad line. */
.legend {
  width: max-content;
  max-width: 100%;
  margin: 12px auto 6px;
  padding: 8px 14px 4px;
  background: rgba(18, 10, 24, 0.6);
  border: 2px solid;
  border-color: var(--rim-dark) var(--rim-lit) var(--rim-lit) var(--rim-dark);
  box-shadow: 0 0 0 2px var(--ink);
}
.controls {
  list-style: none;
  padding: 0;
  margin: 0 0 6px;
  display: grid;
  grid-template-columns: auto auto;
  gap: 4px 14px;
  justify-content: center;
}
.controls li { display: contents; }
/* The title shows the same full legend as pause, compacted (tighter rows and margins) so the panel
   still fits 16:9 without scrolling. */
#title .legend { margin: 6px auto 2px; padding: 5px 12px 2px; }
/* Combo routes are listed in the pause legend only (the title has no room; in-match hints teach them). */
#title .combo { display: none; }
#title .controls { row-gap: 1px; margin-bottom: 2px; }
#title .legend .hint { margin: 0; }
#title .legend .hint canvas.px { margin: 0 auto 2px; }
#title .hiscore { margin: 2px 0 0; }
#title .menu { margin: 8px auto; }
.controls .k canvas.px { margin: 0 0 0 auto; }
.controls .d canvas.px { margin: 0; }

.settings-grid {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.6rem 1rem;
  align-items: center;
  text-align: left;
}
.settings-grid small { color: var(--muted); font-size: 0.75em; }
/* Range: the input is a transparent hit box as tall as the thumb; its track is a recessed meter trough like the
   HUD bars, and the thumb a bevelled block centred on it. Track and thumb are drawn for both engines. */
.settings-grid input[type="range"] {
  appearance: none;
  width: 192px;
  height: 22px;
  margin: 0;
  background: none;
  cursor: pointer;
}
.settings-grid input[type="range"]::-webkit-slider-runnable-track {
  box-sizing: border-box;
  height: 14px;
  background: #2a1a20;
  border: 2px solid;
  border-color: var(--rim-dark) var(--rim-lit) var(--rim-lit) var(--rim-dark);
  box-shadow: 0 0 0 2px var(--ink), inset 0 2px 0 rgba(0, 0, 0, 0.4);
}
.settings-grid input[type="range"]::-moz-range-track {
  box-sizing: border-box;
  height: 14px;
  background: #2a1a20;
  border: 2px solid;
  border-color: var(--rim-dark) var(--rim-lit) var(--rim-lit) var(--rim-dark);
  box-shadow: 0 0 0 2px var(--ink), inset 0 2px 0 rgba(0, 0, 0, 0.4);
}
.settings-grid input[type="range"]::-webkit-slider-thumb {
  appearance: none;
  box-sizing: border-box;
  width: 16px;
  height: 22px;
  margin-top: -6px;   /* centred on the track's 10 px inner height: ( 10 − 22 ) / 2 */
  background: var(--green);
  border: 2px solid var(--ink);
  box-shadow: inset 2px 2px 0 rgba(255, 255, 255, 0.5), inset -2px -2px 0 rgba(0, 0, 0, 0.35);
}
.settings-grid input[type="range"]::-moz-range-thumb {
  box-sizing: border-box;
  width: 16px;
  height: 22px;
  border-radius: 0;
  background: var(--green);
  border: 2px solid var(--ink);
  box-shadow: inset 2px 2px 0 rgba(255, 255, 255, 0.5), inset -2px -2px 0 rgba(0, 0, 0, 0.35);
}
/* Checkbox: bevelled socket, green block when on. */
.settings-grid input[type="checkbox"] {
  appearance: none;
  width: 22px;
  height: 22px;
  margin: 0;
  justify-self: start;
  background: #2a1a20;
  border: 2px solid;
  border-color: var(--rim-dark) var(--rim-lit) var(--rim-lit) var(--rim-dark);
  box-shadow: 0 0 0 2px var(--ink);
  cursor: pointer;
}
.settings-grid input[type="checkbox"]:checked { background: linear-gradient(var(--green), var(--green)) center / 10px 10px no-repeat, #2a1a20; }
/* Select: the button's ink edge, bevel and press, with a drawn 2× pixel chevron (gold over fire, ink outline, like
   the bitmap labels) in place of the OS arrow. The open option list stays the OS's. */
select {
  appearance: none;
  padding: 6px 40px 6px 10px;
  font-size: 14px;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='6' shape-rendering='crispEdges'%3E%3Cpath fill='%23120a18' d='M1 0h7v1H1zM0 1h1v1H0zM8 1h1v1H8zM1 2h1v1H1zM7 2h1v1H7zM2 3h1v1H2zM6 3h1v1H6zM3 4h1v1H3zM5 4h1v1H5zM4 5h1v1H4z'/%3E%3Cpath fill='%23ffd23a' d='M1 1h7v1H1zM2 2h5v1H2z'/%3E%3Cpath fill='%23ff8a2a' d='M3 3h3v1H3zM4 4h1v1H4z'/%3E%3C/svg%3E") right 12px center / 18px 12px no-repeat,
    linear-gradient(180deg, rgba(255, 255, 255, 0.1) 0 3px, transparent 3px),
    var(--btn);
}
select:hover { background-color: var(--btn-hover); }
select:active { transform: translate(2px, 2px); box-shadow: inset 2px 2px 0 var(--bevel-dark), 2px 2px 0 var(--ink); }
select:focus { background-color: var(--btn-focus); color: #fff; box-shadow: inset 2px 2px 0 rgba(200, 255, 170, 0.55), inset -2px -3px 0 var(--bevel-dark), 4px 4px 0 var(--ink); }
select option { background: var(--btn); color: var(--text); }
/* One width for every settings selector, so the separate grids (general, stage, touch) line up. */
/* (35vw keeps the general grid, whose long effects label sets its first column, inside a landscape phone.) */
.settings-grid select { width: min(19.5em, 35vw); justify-self: end; }
.sub canvas.px { margin: 0 auto; }

.keymap {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.4rem;
}
/* One action per row: the label and bound keys on the left, the muted alt key on the right. */
.keymap button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 34px;
  font-size: 13px;
  padding: 6px 10px;
  text-align: left;
}
.keymap button:focus { transform: none; }
.keymap button[aria-pressed="true"] { background: #6a4a10; }

.result {
  display: grid;
  grid-template-columns: auto auto;
  gap: 0.25rem 1.2rem;
  justify-content: center;
  margin: 0.6rem 0;
}
.result dt { color: var(--muted); text-align: right; }
.result dt canvas.px { margin-left: auto; }
.tag canvas.px, .hint canvas.px { margin: 0 auto 4px; }
.result dd { margin: 0; color: var(--gold); text-align: left; font-weight: bold; }
/* The tally lands top-down, 90 ms a row, SCORE last. Visibility only (the panel never reflows), and hidden
   only inside the keyframe, so reduced motion's `animation: none` shows every row at once. */
.result dt, .result dd { animation: row-in 1ms steps(1) both; }
.result dt:nth-of-type(1), .result dd:nth-of-type(1) { animation-delay: 120ms; }
.result dt:nth-of-type(2), .result dd:nth-of-type(2) { animation-delay: 210ms; }
.result dt:nth-of-type(3), .result dd:nth-of-type(3) { animation-delay: 300ms; }
.result dt:nth-of-type(4), .result dd:nth-of-type(4) { animation-delay: 390ms; }
.result dt:nth-of-type(5), .result dd:nth-of-type(5) { animation-delay: 480ms; }
@keyframes row-in { from { visibility: hidden; } }

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before { animation: none !important; transition: none !important; }
}
@media (max-height: 560px) {
  .badges { display: none; }
  .legend { display: none; }
}
@media (max-width: 700px) {
  .badges { display: none; }
}

/* ── Touch: legends, settings, menus ── */
.touch-list, .touch-settings, .menu button.fs-only { display: none; }
html.touch .touch-list { display: grid; }
html.touch .key-list { display: none; }
html.touch-capable .touch-settings { display: block; }
html.touch-only .key-settings { display: none; }
html.fs.touch-capable .menu button.fs-only { display: flex; }
/* Title on touch: Start full width, Settings and Fullscreen side by side. */
html.fs.touch-capable #title .menu { flex-direction: row; flex-wrap: wrap; }
html.fs.touch-capable #title .menu [data-action="start"] { flex-basis: 100%; }
html.fs.touch-capable #title .menu button:not([data-action="start"]) { flex: 1; }
/* Narrow (phone portrait): the 2× bitmap lines shrink to fit the panel. */
@media (max-width: 560px) {
  .legend { zoom: 0.7; }
  .tag { zoom: 0.75; }
  .panel { padding: 16px 14px; overflow-x: hidden; }
  /* Settings stack: each label above its control. */
  .settings-grid { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .settings-grid select { width: min(19.5em, 100%); justify-self: start; }
  .panel .settings-grid label { zoom: 0.8; min-height: 36px; margin-top: 6px; }
  .panel .settings-grid input[type="range"] { width: 100%; }
  .keymap button { zoom: 0.8; }
}
@media (max-width: 380px) {
  .legend { zoom: 0.62; }
  .panel .settings-grid label { zoom: 0.7; }
}
/* Short landscape screens (phones): a smaller logo, no tagline, tighter menus. */
@media (max-height: 500px) {
  #logo { width: min(32rem, 82vw, 44dvh); }
  #title .tag { display: none; }
  #title .menu, .menu { margin: 6px auto; gap: 0.45rem; }
  .panel { padding: 12px 20px; }
  h2 { margin-bottom: 0.4rem; }
  .settings-grid label { zoom: 0.8; }
}
/* Coarse pointers: 44 px+ targets, 16 px form text (iOS zooms on smaller), thumb-sized sliders. */
@media (pointer: coarse) {
  button[data-pixel] { min-height: 48px; }
  select { font-size: 16px; min-height: 44px; }
  .keymap button { font-size: 16px; min-height: 44px; }
  .settings-grid { gap: 0.8rem 1rem; }
  .settings-grid label { min-height: 44px; display: flex; align-items: center; }
  .settings-grid input[type="range"] { width: min(220px, 30vw); height: 44px; }
  .settings-grid input[type="range"]::-webkit-slider-runnable-track { height: 28px; }
  .settings-grid input[type="range"]::-moz-range-track { height: 28px; }
  .settings-grid input[type="range"]::-webkit-slider-thumb { width: 32px; height: 44px; margin-top: -10px; }
  .settings-grid input[type="range"]::-moz-range-thumb { width: 32px; height: 44px; }
  .settings-grid input[type="checkbox"] { width: 44px; height: 44px; }
  .settings-grid input[type="checkbox"]:checked { background-size: 20px 20px; }
}

/* ── Touch controls (src/touch.js) ── */
#touch {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: none;
  pointer-events: none;
  touch-action: none;
}
#touch.on { display: block; }
.t-zone, .t-pad { position: absolute; pointer-events: auto; touch-action: none; }

/* Stick: a dark well with a lit rim sector naming the direction being read, and a bevelled knob. */
.t-stick {
  position: absolute;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(18, 10, 24, 0.2) 0 38%, rgba(18, 10, 24, 0.55) 70%);
  border: 3px solid rgba(244, 236, 255, 0.45);
  box-shadow: 0 0 0 2px var(--ink);
  opacity: calc(var(--o, 0.6) * 0.5);
  pointer-events: none;
}
.t-stick.live { opacity: calc(var(--o, 0.6) + 0.3); }
.t-dir {
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  background: conic-gradient(from -22.5deg, #ffe27a 0 45deg, transparent 0);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 6px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 6px));
  opacity: 0;
}
.t-stick.aim .t-dir { opacity: 1; }
.t-knob {
  position: relative;
  width: 44%;
  height: 44%;
  border-radius: 50%;
  background: #e8dcf4;
  border: 3px solid var(--ink);
  box-shadow: inset 3px 3px 0 rgba(255, 255, 255, 0.7), inset -3px -4px 0 rgba(0, 0, 0, 0.3);
}
.t-stick.aim .t-knob { background: #fff4c8; }
/* Idle in landscape (the ring sits over the lane's lower-left corner): a second after release the well and rim fade
   and the knob shrinks to a dot; a touch brings it back at full strength at once (the fade lives on .idle only). */
@media (orientation: landscape) {
  .t-stick.idle { background: transparent; border-color: transparent; box-shadow: none; transition: border-color 0.3s, box-shadow 0.3s; }
  .t-stick.idle .t-knob { width: 8px; height: 8px; border-width: 2px; box-shadow: none; transition: width 0.3s, height 0.3s; }
}

/* Buttons are drawn here; touches land on .t-pad (src/touch.js picks the nearest button). */
.t-btn {
  position: absolute;
  display: grid;
  place-items: center;
  border-radius: 50%;
  pointer-events: none;
  background: color-mix(in srgb, var(--c) 55%, #120a18);
  border: 3px solid var(--ink);
  box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--c) 85%, #fff), inset 4px 5px 0 3px rgba(255, 255, 255, 0.25), inset -4px -5px 0 3px rgba(0, 0, 0, 0.3);
}
.t-btn .t-label { position: relative; }
.t-btn .t-label canvas.px { display: block; }
/* Pressed: full strength, sunk one step, bevel inverted. */
#touch .t-btn.down {
  opacity: 1 !important;
  animation: none;
  transform: translateY(2px) scale(0.94);
  background: var(--c);
  box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--c) 60%, #fff), inset 4px 5px 0 3px rgba(0, 0, 0, 0.3), inset -4px -5px 0 3px rgba(255, 255, 255, 0.2);
}
.t-btn.flash::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  background: #fff;
  opacity: 0;
  animation: t-flash 140ms steps(3);
  pointer-events: none;
}
@keyframes t-flash { 0% { opacity: 0.7; } 100% { opacity: 0; } }
/* Progress ring around a button: DODGE's cooldown. */
.t-ring {
  position: absolute;
  inset: -8px;
  border-radius: 50%;
  background: conic-gradient(var(--rc, #fff) calc(var(--k, 0) * 1turn), transparent 0);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 4px), #000 calc(100% - 4px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 4px), #000 calc(100% - 4px));
}
/* STRIKE's charge: the face fills from the bottom through a deliberate hold, coloured by level, with a notch at
   levels 1 and 2 (--n1/--n2 from RULES.chargeLevels); level 3 is the "release now" look below. */
.t-fill { position: absolute; inset: 3px; border-radius: 50%; }
.t-strike[data-level] .t-fill {
  background:
    linear-gradient(to top, transparent calc(var(--n1) * 100% - 1px), #fff 0 calc(var(--n1) * 100% + 1px), transparent 0),
    linear-gradient(to top, transparent calc(var(--n2) * 100% - 1px), #fff 0 calc(var(--n2) * 100% + 1px), transparent 0),
    linear-gradient(to top, var(--fc) calc(var(--k, 0) * 100%), rgba(18, 10, 24, 0.45) 0);
}
.t-strike[data-level="0"] { --fc: #9ac8ff; }
.t-strike[data-level="1"] { --fc: #ff8a2a; }
.t-strike[data-level="2"] { --fc: #ffb43a; }
.t-strike[data-level="3"] { --fc: #ffd23a; }
.t-sidestep { --rc: #ffb09a; }
.t-sidestep.cool { background: color-mix(in srgb, var(--c) 22%, #2a2430); }
.t-sidestep.cool .t-ring { background: conic-gradient(var(--rc) calc(var(--k, 0) * 1turn), rgba(18, 10, 24, 0.7) 0); }
.t-roar:not(.ready) { background: color-mix(in srgb, var(--c) 22%, #2a2430); }
/* STRIKE is held when charged, so its "release now" look outranks the pressed look. */
.t-roar.ready, #touch .t-strike.ready { background: color-mix(in srgb, var(--c) 85%, #120a18); box-shadow: 0 0 0 3px #ffd23a, 0 0 18px 4px rgba(255, 210, 58, 0.8), inset 0 0 0 3px #fff; }
@media (prefers-reduced-motion: no-preference) {
  .t-roar.ready { animation: t-glow 500ms steps(2) infinite; }
  .t-btn.pop { animation: t-pop 200ms steps(4); }
  .t-roar.ready.pop { animation: t-pop 200ms steps(4), t-glow 500ms 200ms steps(2) infinite; }
  .t-btn.swap .t-label { animation: t-swap 220ms steps(4); }
}
@keyframes t-glow { 50% { box-shadow: 0 0 0 3px #ff8a2a, 0 0 10px 2px rgba(255, 138, 42, 0.7), inset 0 0 0 3px #ffe060; } }
@keyframes t-pop { 0% { transform: scale(1.18); } 100% { transform: none; } }
@keyframes t-swap { 0% { transform: scale(1.4); filter: brightness(2.2); } 100% { transform: none; filter: none; } }

button.t-pause {
  position: absolute;
  padding: 0;
  pointer-events: auto;
  touch-action: none;
  opacity: 0.8;
  background: linear-gradient(#f4ecff, #f4ecff) 35% 50% / 6px 18px no-repeat, linear-gradient(#f4ecff, #f4ecff) 65% 50% / 6px 18px no-repeat, var(--btn);
}
button.t-pause:focus { transform: none; }
.t-rotate {
  position: absolute;
  display: flex;
  align-items: center;
  gap: 6px;
  max-width: calc(100% - 80px);
  padding: 2px 2px 2px 10px;
  pointer-events: auto;
  background: rgba(18, 10, 24, 0.85);
  border: 2px solid var(--rim-dark);
  box-shadow: 0 0 0 2px var(--ink);
}
.t-rotate[hidden] { display: none; }
.t-rotate .t-label { zoom: 0.75; }
button.t-rotate-x { min-width: 44px; min-height: 44px; padding: 0; font-size: 20px; box-shadow: none; border-width: 2px; }
button.t-rotate-x:focus { transform: none; }
