/* The System's interface.
   Everything here is DOM sitting over the WebGL canvas: translucent blue-black
   panels, thin cyan rules, wide letter-spacing. */

:root {
  --cyan: #5fd8ff;
  --cyan-dim: #2b7ea8;
  --violet: #9d5cff;
  --amber: #ffb347;
  --crimson: #ff4d5e;
  --panel: rgba(10, 20, 38, 0.82);
  --panel-edge: rgba(95, 216, 255, 0.55);
  --ink: #dff1ff;
  --sans: 'Avenir Next Condensed', 'Helvetica Neue', 'Arial Narrow', system-ui, sans-serif;
  --mono: 'SF Mono', 'JetBrains Mono', Menlo, monospace;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  height: 100%;
  background: #06040e;
  overflow: hidden;
  font-family: var(--sans);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  cursor: default;
  user-select: none;
}

#stage { position: fixed; inset: 0; }
#view { display: block; width: 100%; height: 100%; }
.hidden { display: none !important; }

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

#hud {
  position: absolute;
  inset: 0;
  pointer-events: none;
  padding: 22px 26px;
}

.bars { width: min(30vw, 340px); }

.bar {
  position: relative;
  height: 17px;
  margin-bottom: 6px;
  background: rgba(4, 10, 22, 0.72);
  border: 1px solid rgba(120, 170, 210, 0.35);
  transform: skewX(-18deg);
  overflow: hidden;
}
.bar i {
  position: absolute;
  inset: 0;
  transform-origin: left;
  transition: transform 0.18s cubic-bezier(0.2, 0.9, 0.3, 1);
}
.bar.hp i { background: linear-gradient(90deg, #2fd6a8, #7ff2c9); box-shadow: 0 0 18px #2fd6a8aa; }
.bar.mp i { background: linear-gradient(90deg, #3f7fe8, #7fd0ff); box-shadow: 0 0 18px #3f7fe8aa; }
.bar.hp.low i { background: linear-gradient(90deg, #d63b52, #ff8a97); box-shadow: 0 0 22px #ff4d5eaa; }
.bar span {
  position: absolute;
  left: 9px;
  top: 1px;
  transform: skewX(18deg);
  font-size: 11px;
  letter-spacing: 0.22em;
  font-weight: 700;
  text-shadow: 0 1px 3px #000;
}
.bar b {
  position: absolute;
  right: 9px;
  top: 1px;
  transform: skewX(18deg);
  font-size: 11px;
  font-family: var(--mono);
  font-weight: 500;
  text-shadow: 0 1px 3px #000;
}

.xp {
  height: 4px;
  width: 62%;
  background: rgba(4, 10, 22, 0.72);
  transform: skewX(-18deg);
  overflow: hidden;
  border: 1px solid rgba(120, 170, 210, 0.22);
}
.xp i {
  display: block;
  height: 100%;
  background: var(--amber);
  box-shadow: 0 0 12px var(--amber);
  transform-origin: left;
  transform: scaleX(0);
  transition: transform 0.3s ease-out;
}

.lvl {
  margin-top: 7px;
  font-size: 12px;
  letter-spacing: 0.3em;
  color: #9fc4dd;
}
.lvl b { color: #fff; font-size: 16px; text-shadow: 0 0 14px var(--cyan); }

/* ---------- Style rank (Devil May Cry's contribution) ---------- */

#rank {
  position: absolute;
  right: 26px;
  top: 20px;
  width: 190px;
  text-align: right;
  opacity: 0;
  transform: translateX(28px);
  transition: opacity 0.22s, transform 0.22s;
}
#rank.on { opacity: 1; transform: none; }
/* Rank-up needs to happen in peripheral vision — the playtest never noticed the
   meter at all until it was pointed out. */
#rank.bump .letter { animation: rankBump 0.42s cubic-bezier(0.16, 1, 0.3, 1); }
@keyframes rankBump {
  0% { transform: scale(1.75); filter: brightness(2.4); }
  60% { transform: scale(0.94); }
  100% { transform: scale(1); filter: brightness(1); }
}
#rank .letter {
  font-size: 104px;
  line-height: 0.82;
  font-weight: 800;
  font-style: italic;
  letter-spacing: -0.03em;
  color: #fff;
  text-shadow: 0 0 26px currentColor, 0 5px 0 rgba(0, 0, 0, 0.45);
}
#rank .word {
  font-size: 16px;
  letter-spacing: 0.26em;
  font-weight: 700;
  margin-top: 2px;
  opacity: 0.92;
}
#rank .meter {
  margin-top: 7px;
  height: 6px;
  background: rgba(4, 10, 22, 0.7);
  transform: skewX(-18deg);
  overflow: hidden;
}
#rank .meter i {
  display: block;
  height: 100%;
  background: currentColor;
  transform-origin: right;
  transform: scaleX(0);
}
/* One source of truth for rank colour: the corner meter and the callout wear
   the same class, so they can never drift apart. */
#rank.r0, .rank-callout.r0 { color: #8fa6b8; }
#rank.r1, .rank-callout.r1 { color: #7fd0ff; }
#rank.r2, .rank-callout.r2 { color: #7ff2c9; }
#rank.r3, .rank-callout.r3 { color: #ffd24d; }
#rank.r4, .rank-callout.r4 { color: #ff8a3d; }
#rank.r5, .rank-callout.r5 { color: #ff4d7e; }

/* Rank-up, announced over the fight.
 *
 * The corner meter was reported unnoticed in round 1, and unnoticed again in
 * round 2 after the letter went 76px -> 104px with a flash on rank-up. Making
 * the thing in the corner louder was answering the wrong question: the player
 * is watching the hunter, not the top-right of the screen, so the meter has to
 * come to them once, at the moment it changes, and then get out of the way.
 *
 * Anchored just above centre and pinned behind the play area's focal point, it
 * inherits the rank colour from #rank via a custom property so the two can
 * never disagree. */
.rank-callout {
  position: absolute;
  left: 50%;
  top: 34%;
  transform: translate(-50%, -50%);
  text-align: center;
  pointer-events: none;
  opacity: 0;
  color: #fff;
}
.rank-callout.on {
  animation: rankCallout 0.95s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.rank-callout b {
  display: block;
  font-size: 128px;
  line-height: 0.85;
  font-weight: 800;
  font-style: italic;
  letter-spacing: -0.04em;
  text-shadow: 0 0 40px currentColor, 0 0 90px currentColor, 0 6px 0 rgba(0, 0, 0, 0.5);
}
.rank-callout span {
  display: block;
  margin-top: 6px;
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 0.42em;
  text-indent: 0.42em;
  text-shadow: 0 0 18px currentColor;
}
@keyframes rankCallout {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(0.55) skewX(-12deg); }
  18% { opacity: 1; transform: translate(-50%, -50%) scale(1.12) skewX(-12deg); }
  34% { transform: translate(-50%, -50%) scale(1) skewX(-12deg); }
  70% { opacity: 0.95; }
  100% { opacity: 0; transform: translate(-50%, -58%) scale(1.04) skewX(-12deg); }
}
@media (prefers-reduced-motion: reduce) {
  .rank-callout.on { animation-duration: 0.6s; }
}

#combo {
  position: absolute;
  right: 30px;
  top: 176px;
  text-align: right;
  opacity: 0;
  transition: opacity 0.2s;
}
#combo.on { opacity: 1; }
#combo b {
  font-size: 40px;
  font-style: italic;
  font-weight: 800;
  color: #fff;
  text-shadow: 0 0 20px var(--cyan);
}
#combo span { font-size: 11px; letter-spacing: 0.3em; margin-left: 5px; color: #9fc4dd; }

.objective {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 34px;
  text-align: center;
  font-size: 13px;
  letter-spacing: 0.3em;
  color: #bcd8ec;
  text-shadow: 0 2px 10px #000;
  opacity: 0;
  transition: opacity 0.4s;
}
.objective.on { opacity: 0.95; }

/* ---------- Boss bar ---------- */

.boss-bar {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 62px;
  width: min(58vw, 720px);
  text-align: center;
}
.boss-name {
  font-size: 13px;
  letter-spacing: 0.42em;
  margin-bottom: 6px;
  color: #e6d2ff;
  text-shadow: 0 0 16px var(--violet);
}
.boss-track {
  height: 11px;
  background: rgba(4, 10, 22, 0.8);
  border: 1px solid rgba(157, 92, 255, 0.5);
  transform: skewX(-18deg);
  overflow: hidden;
}
.boss-track i {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, #6a2fd6, #c9a3ff);
  box-shadow: 0 0 20px #9d5cffcc;
  transform-origin: left;
  transition: transform 0.2s ease-out;
}

/* ---------- System windows ---------- */

#windows {
  position: absolute;
  inset: 0;
  display: grid;
  justify-items: center;
  /* `start` rather than `center`: it packs whatever rows exist — ordinarily
   * one, occasionally two if a second window opens before the first closes —
   * at the top instead of spreading them across the full height. Grid's rows
   * size to their content by default, unlike a flex row's cross-axis, so
   * nothing here stretches a window to fill the container. */
  align-content: start;
  /* Anchored near the top rather than dead centre. The camera frames the
   * hunter around the vertical middle of the screen — see the note on
   * `.rank-callout` — and a window sitting there covers the fight it is
   * reporting on, which is what a phone playtest measured directly: centred,
   * the panel's lower edge crossed the hunter's chest with enemies still
   * live. Up here it clears that band at every width the layout supports. */
  padding-top: 8vh;
  pointer-events: none;
}

.sys-window {
  /* Scales with the viewport — the idiom `.bars` and `.boss-bar` already use —
   * rather than being pinned in pixels. `78vw` does not bind until the
   * viewport drops under ~423px (min) / ~564px (max), so nothing changes at
   * desktop widths; at 360px wide it caps the panel at 78% of the screen,
   * against the 94% the fixed 330px `min-width` alone measured there. */
  min-width: min(330px, 78vw);
  max-width: min(440px, 78vw);
  /* Lower than it was — 0.93 down to 0.85 — so the fight reads through it
   * now that there is no blur behind it — see below. */
  background: rgba(10, 20, 38, 0.85);
  border: 1px solid var(--panel-edge);
  box-shadow: 0 0 40px rgba(50, 160, 220, 0.28), inset 0 0 30px rgba(40, 120, 190, 0.14);
  padding: 20px 26px 22px;
  text-align: center;
  animation: sysIn 0.28s cubic-bezier(0.16, 1, 0.3, 1);
  /* Nothing here promotes this element onto its own compositor layer, and that
   * is deliberate — it was the frame-skip.
   *
   * This window used to carry `backdrop-filter: blur(3px)` plus the
   * `will-change` and `translateZ(0)` that accelerate it. Over a full-screen
   * WebGL canvas an own-layer element makes the browser keep a separate texture
   * and re-composite it every frame the window is alive, so the cost lasts as
   * long as the window rather than being paid once when it opens. Removing the
   * filter alone changed nothing; removing the layer with it fixed it.
   *
   * Do not reintroduce `will-change`, `translateZ(0)`, `backdrop-filter` or a
   * `filter` here without measuring. See `docs/DECISIONS.md`. */
}
.sys-window.out { animation: sysOut 0.24s ease-in forwards; }
/* Unlike every other `.sys-window`, this one holds until clicked rather than
 * timing itself out, so it needs the clicks `#windows` otherwise blocks —
 * see the note on `#windows` above `pointer-events: none`. Stacks directly
 * below whatever `_fireBeats('cleared')` opened, via DOM order in the same
 * grid column, rather than fighting it for centre screen. */
.boss-rest { pointer-events: auto; margin-top: 14px; }
.boss-rest .cta { margin-top: 4px; }
/* A paged story-beat window's NEXT button needs the same click-through as
 * `.boss-rest` above — see the note on `#windows` above `pointer-events: none`. */
.sys-window.paging { pointer-events: auto; }
/* The System reporting that it has no record of a place, rather than the
 * game failing to render one — a stutter in the window's own opacity, not in
 * the frame around it. Only `opacity`/`transform` move, same as `sysIn` next
 * to it: the note above is explicit that `filter`/`backdrop-filter` on this
 * element cost a frame-skip once already. */
.sys-window.glitch { animation: sysIn 0.28s cubic-bezier(0.16, 1, 0.3, 1), sysGlitch 0.42s steps(1) 0.28s 2; }
@keyframes sysGlitch {
  0%, 100% { opacity: 1; transform: none; }
  35% { opacity: 0.55; transform: translateX(-3px); }
  60% { opacity: 0.85; transform: translateX(2px); }
}
.sys-window h3 {
  font-size: 13px;
  letter-spacing: 0.42em;
  color: var(--cyan);
  font-weight: 700;
  margin-bottom: 12px;
}
.sys-window .divider {
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--panel-edge), transparent);
  margin: 10px 0 12px;
}
/* `docs/DECISIONS.md` § "A Warden's intro and defeat are a scene, not a
 * line" — `.big` used to render as an oversized, glowing headline against
 * `p`'s plain paragraph, so a two-sentence beat read as a title plus a
 * caption. `h3` above is the only element in this window still allowed a
 * distinct treatment — it names who is speaking, once, per window. Every
 * sentence of what they actually say, however many a beat carries, renders
 * identically: this rule, not just this one selector. */
.sys-window p, .sys-window .big {
  font-size: 15px;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: 0.04em;
  margin: 0 0 6px;
  text-shadow: none;
}
.sys-window p:last-child, .sys-window .big:last-child { margin-bottom: 0; }
.sys-window ul { list-style: none; font-family: var(--mono); font-size: 13px; }
.sys-window li { display: flex; justify-content: space-between; padding: 3px 0; }
.sys-window li span { color: #8fb4cc; }
.sys-window li b { color: #fff; }
.sys-window li b.up { color: #7ff2c9; }

@keyframes sysIn {
  from { opacity: 0; transform: translateY(14px) scale(0.94); }
  to { opacity: 1; transform: none; }
}
@keyframes sysOut {
  to { opacity: 0; transform: translateY(-10px) scale(0.97); }
}

/* ---------- Toasts (level up, quest ticks) ---------- */

#toasts {
  position: absolute;
  left: 50%;
  top: 15%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  pointer-events: none;
}
.toast {
  background: var(--panel);
  border-left: 3px solid var(--cyan);
  padding: 8px 20px;
  font-size: 13px;
  letter-spacing: 0.2em;
  max-width: 86vw;
  text-align: center;
  box-shadow: 0 0 26px rgba(50, 160, 220, 0.3);
  animation: toastIn 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.toast.warn { border-left-color: var(--crimson); }
.toast.gold { border-left-color: var(--amber); }
.toast.out { animation: toastOut 0.35s ease-in forwards; }
@keyframes toastIn { from { opacity: 0; transform: translateX(-26px); } to { opacity: 1; transform: none; } }
@keyframes toastOut { to { opacity: 0; transform: translateY(-14px); } }

/* ---------- The training hall's SKIP control ---------- */

/* Fixed rather than laid out with the HUD: it has to survive every prompt the
   hall shows, including the very first one, and a HUD element positioned
   among the vitals bars would fight `#windows` for the same corner of the
   screen those already occupy. See `main.js`'s `enterTutorial`. */
#tutorial-skip {
  position: fixed;
  top: 18px;
  right: 18px;
  z-index: 6;
}

/* ---------- Full screens ---------- */

.screen {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 22px;
  /* Denser than it was, because there is no blur behind it any more.
   *
   * These overlays carried `backdrop-filter: blur(2px)`, which is a per-frame
   * composite over whatever the canvas is drawing — and the title screen draws
   * a slowly drifting camera behind it for as long as the page is open. That
   * makes it a permanent cost on the first thing a visitor sees, for a 2px
   * blur nobody is looking at. The gradient was already doing the separation
   * work; it now does all of it.
   *
   * Not to be confused with the frame-skip, which was the compositor layer on
   * `.sys-window` and is fixed. See the note there. */
  background: radial-gradient(ellipse at 50% 45%, rgba(20, 10, 46, 0.72), rgba(4, 3, 10, 0.97));
  z-index: 10;
}

#boot { background: #06040e; gap: 14px; }
#boot .boot-mark { font-size: 40px; color: var(--violet); animation: pulse 1.5s ease-in-out infinite; }
#boot p { font-size: 12px; letter-spacing: 0.5em; color: #6f8ba0; }
@keyframes pulse { 50% { opacity: 0.25; } }

.title-art { text-align: center; }
.title-art .sub {
  font-size: 12px;
  letter-spacing: 0.62em;
  color: var(--cyan);
  margin-bottom: 10px;
  text-shadow: 0 0 18px var(--cyan);
}
.title-art h1 {
  font-size: clamp(46px, 9vw, 108px);
  font-weight: 800;
  line-height: 0.88;
  letter-spacing: 0.02em;
  text-shadow: 0 0 50px rgba(157, 92, 255, 0.65), 0 6px 0 rgba(0, 0, 0, 0.4);
}
.title-art h1 span {
  display: block;
  color: transparent;
  background: linear-gradient(180deg, #e9d9ff, #9d5cff 62%, #4a1f8f);
  -webkit-background-clip: text;
  background-clip: text;
}
.title-art .rule {
  height: 1px;
  margin: 20px auto 14px;
  width: 62%;
  background: linear-gradient(90deg, transparent, var(--panel-edge), transparent);
}
.tag { font-size: 13px; letter-spacing: 0.26em; color: #9fb6c8; }

/* A phone held landscape is ~410 CSS px tall, and the title screen can carry
 * five buttons there — NEW GAME once a save moves past gate 1, PRIVACY
 * OPTIONS in the Android shell where consent law needs it. At the desktop
 * spacing that column is ~490 px and the logo and the last button fall off
 * both edges, so short viewports tighten the column rather than drop a
 * button: the logo scales with the height and the gaps close up. */
@media (max-height: 520px) {
  #title { gap: 8px; }
  #title .cta.secondary { margin-top: 0; padding: 7px 24px; }
  .title-art h1 { font-size: min(clamp(46px, 9vw, 108px), 15vh); }
  .title-art .sub { margin-bottom: 4px; }
  .title-art .rule { margin: 10px auto 8px; }
}

h2 { font-size: clamp(34px, 6vw, 62px); font-weight: 800; letter-spacing: 0.06em; }
h2.fail { color: var(--crimson); text-shadow: 0 0 44px rgba(255, 77, 94, 0.6); }
h2.win { color: #7ff2c9; text-shadow: 0 0 44px rgba(127, 242, 201, 0.55); }

/* The System's buttons: a lit panel with a cyan edge, bracketed at two
 * corners like the windows' own frames, and a sheen that crosses on hover.
 * No `filter`, `backdrop-filter` or `will-change` — see `.sys-window`. */
.cta {
  position: relative;
  pointer-events: auto;
  background:
    linear-gradient(105deg, transparent 38%, rgba(170, 235, 255, 0.16) 50%, transparent 62%) -160% 0 / 250% 100% no-repeat,
    linear-gradient(180deg, rgba(26, 58, 96, 0.94), rgba(8, 18, 38, 0.94));
  border: 1px solid rgba(95, 216, 255, 0.62);
  border-radius: 3px;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.34em;
  padding: 14px 44px 14px calc(44px + 0.34em);
  cursor: pointer;
  text-shadow: 0 0 12px rgba(95, 216, 255, 0.45);
  box-shadow:
    inset 0 1px 0 rgba(170, 235, 255, 0.28),
    inset 0 -2px 0 rgba(0, 0, 0, 0.35),
    0 6px 20px rgba(0, 0, 0, 0.45),
    0 0 24px rgba(50, 160, 220, 0.22);
  transition: background-position 0.5s ease, box-shadow 0.16s, transform 0.12s, border-color 0.16s;
}
/* Corner brackets, top-left and bottom-right. */
.cta::before,
.cta::after {
  content: '';
  position: absolute;
  width: 10px;
  height: 10px;
  border: 2px solid var(--cyan);
  pointer-events: none;
  opacity: 0.9;
}
.cta::before { top: -3px; left: -3px; border-right: 0; border-bottom: 0; }
.cta::after { bottom: -3px; right: -3px; border-left: 0; border-top: 0; }
.cta:hover {
  background-position: 160% 0, 0 0;
  border-color: var(--cyan);
  box-shadow:
    inset 0 1px 0 rgba(190, 240, 255, 0.4),
    inset 0 -2px 0 rgba(0, 0, 0, 0.35),
    0 8px 24px rgba(0, 0, 0, 0.5),
    0 0 36px rgba(95, 216, 255, 0.5);
  transform: translateY(-1px);
}
.cta:active {
  transform: translateY(1px);
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.5), 0 0 18px rgba(95, 216, 255, 0.35);
}
.cta:focus-visible { outline: 2px solid var(--cyan); outline-offset: 4px; }
.cta.secondary {
  margin-top: 12px;
  padding: 9px 30px 9px calc(30px + 0.34em);
  font-size: 12px;
  color: #bcd6e8;
  background:
    linear-gradient(105deg, transparent 38%, rgba(170, 235, 255, 0.12) 50%, transparent 62%) -160% 0 / 250% 100% no-repeat,
    linear-gradient(180deg, rgba(18, 36, 62, 0.82), rgba(8, 16, 32, 0.82));
  border-color: rgba(95, 216, 255, 0.34);
  text-shadow: none;
  box-shadow: inset 0 1px 0 rgba(170, 235, 255, 0.14), 0 4px 14px rgba(0, 0, 0, 0.35);
}
.cta.secondary::before,
.cta.secondary::after { width: 7px; height: 7px; border-width: 1px; opacity: 0.55; }
.cta.secondary:hover { color: var(--ink); border-color: rgba(95, 216, 255, 0.7); }
/* The second wind on the death screen. Two lines: the verb, and what
 * pressing it costs — the word "ad" is on the button, not behind it. Gold
 * rather than cyan, so the offer reads as its own thing beside RISE AGAIN. */
.cta.second-wind {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  border-color: rgba(255, 179, 71, 0.7);
  background:
    linear-gradient(105deg, transparent 38%, rgba(255, 220, 160, 0.18) 50%, transparent 62%) -160% 0 / 250% 100% no-repeat,
    linear-gradient(180deg, rgba(74, 48, 20, 0.94), rgba(26, 16, 8, 0.94));
  text-shadow: 0 0 12px rgba(255, 179, 71, 0.5);
  box-shadow:
    inset 0 1px 0 rgba(255, 220, 160, 0.3),
    inset 0 -2px 0 rgba(0, 0, 0, 0.35),
    0 6px 20px rgba(0, 0, 0, 0.45),
    0 0 26px rgba(255, 179, 71, 0.25);
}
.cta.second-wind::before,
.cta.second-wind::after { border-color: var(--amber); }
.cta.second-wind:hover { border-color: var(--amber); box-shadow: inset 0 1px 0 rgba(255, 220, 160, 0.4), 0 8px 24px rgba(0, 0, 0, 0.5), 0 0 36px rgba(255, 179, 71, 0.5); }
.cta.second-wind small { font-size: 10px; font-weight: 500; letter-spacing: 0.22em; opacity: 0.75; text-transform: uppercase; }

.controls-hint {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  justify-content: center;
  max-width: 620px;
  font-size: 11px;
  letter-spacing: 0.16em;
  color: #8ba4b8;
}
kbd {
  display: inline-block;
  min-width: 22px;
  padding: 2px 7px;
  margin-right: 5px;
  border: 1px solid rgba(140, 180, 210, 0.4);
  border-radius: 3px;
  background: rgba(10, 22, 40, 0.75);
  font-family: var(--mono);
  font-size: 10px;
  color: #cfe6f5;
}

.stats {
  font-family: var(--mono);
  font-size: 14px;
  line-height: 2;
  min-width: 300px;
}
.stats div { display: flex; justify-content: space-between; gap: 40px; }
.stats span { color: #8fb4cc; }
.stats b { color: #fff; }

/* ---------- Touch controls ---------- */

/* Sizes and positions are not here. Every number lives in the layout
   descriptor in src/ui/touch.js, which writes `--b` and the per-target offsets
   as inline styles — because tools/touchcheck.js has to be able to read the
   layout and answer whether all seven verbs are reachable at four screen
   sizes, and it cannot read a stylesheet. What is left here is appearance.

   Below `.screen`'s z-index, so the title, pause, death and clear overlays sit
   over the controls rather than under them. */
#touch {
  position: absolute;
  inset: 0;
  z-index: 5;
  /* The container catches nothing: only the targets do, so the clear space
     between them is not a wall in front of the fight. */
  pointer-events: none;
  touch-action: none;
  /* Absolutely positioned children resolve against the padding box, so this is
     what keeps a button out from under a notch or a home indicator. */
  padding: 0 env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

.touch-target {
  position: absolute;
  pointer-events: auto;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
  /* Resting translucency is deliberate and load-bearing: the play plane runs
     behind the bottom of the frame, and a control you cannot see through is a
     control that hides the enemy walking under it. */
  background: radial-gradient(circle at 50% 32%, rgba(60, 110, 170, 0.3), rgba(10, 20, 38, 0.26) 70%);
  border: 1.5px solid rgba(120, 170, 210, 0.36);
  box-shadow: 0 0 16px rgba(6, 12, 26, 0.5), inset 0 0 0 3px rgba(10, 20, 38, 0.25), inset 0 1px 0 rgba(170, 225, 255, 0.18);
  transition: background 0.08s, border-color 0.08s, box-shadow 0.08s, transform 0.08s;
}

.touch-btn {
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: #cfe6f5;
}
/* DASH's shoulder bar — a rounded rectangle rather than the round face
   buttons below it, the way a controller's L1/R1 reads against its diamond. */
.touch-btn.shoulder { border-radius: calc(var(--b) * 0.3); }
.touch-btn b {
  font-weight: 700;
  letter-spacing: 0.1em;
  text-shadow: 0 1px 3px #000;
  pointer-events: none;
}

/* Colour carries the grouping a thumb learns by position: the two swings cyan,
   the bolt blue like the mana bar, PUKAR violet like the shard it claims. */
.touch-btn.tone-plain { border-color: rgba(150, 178, 200, 0.36); color: #c2d4e2; }
.touch-btn.tone-cyan { border-color: rgba(95, 216, 255, 0.42); }
.touch-btn.tone-blue { border-color: rgba(63, 127, 232, 0.46); color: #bcd8ff; }
.touch-btn.tone-violet { border-color: rgba(157, 92, 255, 0.5); color: #dcc9ff; }

.touch-btn.on {
  background: rgba(36, 88, 142, 0.72);
  border-color: var(--cyan);
  box-shadow: 0 0 26px rgba(95, 216, 255, 0.5), inset 0 0 22px rgba(95, 216, 255, 0.22);
  transform: scale(0.93);
}
.touch-btn.tone-violet.on {
  background: rgba(78, 42, 132, 0.78);
  border-color: var(--violet);
  box-shadow: 0 0 26px rgba(157, 92, 255, 0.55), inset 0 0 22px rgba(157, 92, 255, 0.25);
}

/* The arrow pad's four targets are `.touch-btn`s like any other — see
   DECISIONS.md § Backlog: playtest round 2, "touch steer becomes a four-way
   arrow pad, not a knob". Round 3's playtest read the pad as asymmetric;
   DECISIONS.md § Backlog: playtest round 3 traced it to the four Unicode
   arrows (◀▶▼▲), whose glyph ink a font is free to centre differently per
   character. `.pad-arrow` is one triangle, pointing up, rotated per
   direction — the same shape turned four ways can't disagree with itself
   the way four separate glyphs could.

   A full circle (`.touch-btn`'s own 50%) inscribed in a square hit-box gives
   up its corners, and the pad's four hit-boxes only touch at their corners
   (`PAD_GAP`, `touch.js`) to keep the cross frame square — so two diagonal
   circles read as far apart even though their hit-boxes barely are. Rounding
   the pad's targets instead of circling them keeps the same hit-box math
   `tools/touchcheck.js` checks for overlap, while closing most of that
   dead corner space visually. */
.touch-btn:has(.pad-arrow) { border-radius: calc(var(--b) * 0.08); }
.pad-arrow {
  width: calc(var(--b) * 0.34);
  height: calc(var(--b) * 0.34);
  background: currentColor;
  clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
  filter: drop-shadow(0 1px 2px #000);
  pointer-events: none;
}
.pad-arrow.dir-down { transform: rotate(180deg); }
.pad-arrow.dir-left { transform: rotate(-90deg); }
.pad-arrow.dir-right { transform: rotate(90deg); }

/* The keyboard legend on the title screen is a list of keys the device does
   not have. The buttons are labelled; nothing replaces it. */
body.touch .controls-hint { display: none; }

/* The two pieces of HUD that live along the bottom of the frame move up above
   the controls, keeping the order they had.

   Not cosmetic. The objective line is centred and the boss bar is 58vw wide, so
   on a phone both run under the button cluster — the boss's health, which is
   the one number the fight is read from, behind a thumb. `--b` is the touch
   layout's own unit, written onto the document by src/ui/touch.js, so this
   tracks the controls at every screen size instead of guessing at a gap. */
body.touch .objective { bottom: calc(var(--b) * 3.3); }
body.touch .boss-bar { bottom: calc(var(--b) * 3.95); }
