/*!
 * Treble Invaders — look.
 *
 * A bright field, not outer space. Everything decorative is drawn from
 * Prodigies colours in CSS and SVG so nothing is borrowed and nothing is
 * loaded from another origin (children reach this page — COPPA).
 *
 * The notehead is the most readable thing on screen. Every other decision here
 * serves that: the sky pales towards the staff, the mountains are low-contrast,
 * the HUD is dark navy so it reads as chrome rather than scenery, and the
 * invader carries a white halo so it separates from any hill behind it.
 *
 * PLACEHOLDER ART. Rob draws the launcher, the trees and the invader faces;
 * these are honest stand-ins with the right silhouettes and colours.
 */

:root {
  --navy: #1b233e;
  --navy-2: #263258;
  --ink: #1f1d1a;
  --ivory: #fdf8ee;
  --green: #6abb4a;
  --green-edge: #4f9636;
  --gold: #ffd25b;
  --red: #e72829;
  --teal: #009999;
  --sky-top: #63b8ee;
  --sky-mid: #9ed6f5;
  --sky-low: #d9f0fb;
  --invader-ink: #512d1b;
  --invader-face: #8d7b6e;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  min-height: 100%;
  background: var(--sky-low);
  color: var(--ink);
  font-family: 'Nunito', 'Avenir Next Rounded', 'Trebuchet MS', sans-serif;
  overscroll-behavior: none;
}

#game { display: block; }

.ti-screen {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  overflow: hidden;
  isolation: isolate;
}

/* ══════════════════════════════════════════════════════════════════════════
   THE FIELD
   Rob's own "Piano Field - Full" artwork, self-hosted. It replaced a CSS sky,
   mountains, hills and trees — the drawn scene is better and it is one request.
   `cover` on a 2200px JPEG, anchored to the bottom so the grass and the path
   stay under the bell row at every aspect ratio.
   ══════════════════════════════════════════════════════════════════════════ */

.ti-field {
  position: absolute;
  inset: 0;
  /* Behind everything in this screen. `.ti-screen` isolates, so a negative
     index cannot escape and slide under the page. */
  z-index: -1;
  pointer-events: none;
  background-image: url("./piano-field.jpg");
  background-size: cover;
  background-position: center bottom;
  background-repeat: no-repeat;
  background-color: var(--sky-mid);
}

/* ══════════════════════════════════════════════════════════════════════════
   TITLE + GAME OVER
   ══════════════════════════════════════════════════════════════════════════ */

.ti-title, .ti-over { display: grid; place-items: center; padding: 24px 16px 40px; }

.ti-title-card {
  position: relative; z-index: 1;
  width: min(100%, 480px);
  background: rgba(255, 255, 255, 0.94);
  border: 3px solid var(--navy);
  border-radius: 26px;
  padding: 24px 22px 26px;
  text-align: center;
  box-shadow: 0 14px 34px rgba(27, 35, 62, 0.22);
}

.ti-eyebrow {
  margin: 0 0 6px;
  font-size: 10px; font-weight: 900; letter-spacing: 2.4px;
  text-transform: uppercase; color: var(--teal);
}

.ti-logo {
  margin: 0 0 8px;
  font-size: clamp(34px, 9vw, 50px); font-weight: 900; line-height: 0.94;
  color: var(--navy); letter-spacing: -1px;
}
.ti-logo span { display: block; color: var(--red); }

.ti-tag { margin: 0 0 18px; font-size: 14.5px; font-weight: 700; color: #5c6480; }

.ti-modes { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 16px; }
.ti-mode {
  display: grid; gap: 4px; justify-items: center;
  padding: 12px 8px 10px;
  border: 2px solid #d5dced; border-radius: 16px;
  background: #fff; font: inherit; cursor: pointer;
  transition: border-color 0.15s, transform 0.15s, background 0.15s;
}
.ti-mode:hover { transform: translateY(-2px); }
.ti-mode.is-on { border-color: var(--teal); background: #e7f7f7; }
.ti-mode b { font-size: 14px; font-weight: 900; color: var(--navy); }
.ti-mode small { font-size: 11px; font-weight: 700; color: #7b849e; }
.ti-mode-art { display: flex; align-items: center; justify-content: center; gap: 3px; height: 30px; }
.ti-mode-art i {
  width: 13px; height: 17px; border-radius: 50% 50% 30% 30%;
  background: var(--c);
}
.ti-mode-art svg {
  width: 26px; height: 26px; fill: none;
  stroke: var(--navy); stroke-width: 2; stroke-linecap: round;
}

.ti-play-btn {
  border: none; border-radius: 999px;
  padding: 14px 34px;
  background: var(--green); box-shadow: 0 5px 0 var(--green-edge);
  color: #fff; font: inherit; font-size: 19px; font-weight: 900; cursor: pointer;
}
.ti-play-btn:hover { transform: translateY(-2px); box-shadow: 0 7px 0 var(--green-edge); }
.ti-play-btn:active { transform: translateY(2px); box-shadow: 0 2px 0 var(--green-edge); }

.ti-ghost-btn {
  border: 2px solid #d5dced; border-radius: 999px;
  padding: 11px 20px; background: #fff;
  font: inherit; font-size: 14px; font-weight: 900; color: #5c6480; cursor: pointer;
}
.ti-ghost-btn:hover { border-color: #9aa4c0; color: var(--navy); }

/* ── Wave select ── */
.ti-pick {
  margin: 4px 0 8px;
  font-size: 9px; font-weight: 900; letter-spacing: 1.8px;
  text-transform: uppercase; color: var(--navy); opacity: 0.55;
}
.ti-levels {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 7px;
  margin-bottom: 10px;
}
@media (max-width: 420px) { .ti-levels { grid-template-columns: repeat(4, 1fr); gap: 5px; } }
.ti-level {
  display: grid; justify-items: center; gap: 1px;
  padding: 8px 4px 7px;
  border: 2px solid #d5dced; border-radius: 13px;
  background: #fff; font: inherit; cursor: pointer;
  transition: transform 0.13s, border-color 0.13s;
}
.ti-level:hover { transform: translateY(-2px); border-color: var(--teal); }
.ti-level-num { font-size: 17px; font-weight: 900; color: var(--navy); line-height: 1; }
.ti-level-name {
  font-size: 8.5px; font-weight: 800; color: #7b849e;
  line-height: 1.15; text-align: center;
}
.ti-level-stars { font-size: 8px; line-height: 1; }
.ti-level-stars i { font-style: normal; color: #d5dced; }
.ti-level-stars i.on { color: var(--gold); }

.ti-endless {
  display: grid; gap: 1px; width: 100%;
  padding: 11px 14px;
  border: 2px solid var(--navy); border-radius: 15px;
  background: var(--navy); color: #fff;
  font: inherit; cursor: pointer;
  transition: transform 0.13s;
}
.ti-endless:hover { transform: translateY(-2px); }
.ti-endless b { font-size: 15px; font-weight: 900; }
.ti-endless small { font-size: 10.5px; font-weight: 700; opacity: 0.75; }

.ti-stars i { font-style: normal; color: #d5dced; }
.ti-stars i.on { color: var(--gold); }
.ti-stars.big i { font-size: 30px; }
.ti-over-head {
  margin: 0 0 8px;
  font-size: clamp(20px, 5.4vw, 27px); font-weight: 900; color: var(--navy);
  line-height: 1.15;
}

.ti-bests {
  display: flex; justify-content: center; gap: 18px;
  margin: 18px 0 10px;
}
.ti-bests div { display: grid; gap: 1px; }
.ti-bests dt {
  font-size: 9px; font-weight: 900; letter-spacing: 1.4px;
  text-transform: uppercase; color: #97a0b8;
}
.ti-bests dd { margin: 0; font-size: 19px; font-weight: 900; color: var(--navy); }

.ti-note { margin: 0; font-size: 11.5px; font-weight: 700; color: #97a0b8; line-height: 1.45; }

.ti-over-score {
  margin: 0;
  font-family: 'JetBrains Mono', monospace;
  font-size: clamp(40px, 12vw, 58px); font-weight: 800; color: var(--navy);
}
.ti-over-actions { display: grid; gap: 9px; justify-items: center; margin-top: 6px; }
.ti-practise { margin: 4px 0 0; font-size: 13px; font-weight: 800; color: #5c6480; }
.ti-practise b {
  display: inline-grid; place-items: center;
  width: 26px; height: 26px; margin: 0 2px;
  border-radius: 8px; background: var(--c); color: #fff; font-size: 14px;
}

/* ══════════════════════════════════════════════════════════════════════════
   PLAY
   ══════════════════════════════════════════════════════════════════════════ */

.ti-play {
  display: grid;
  /* HUD, then the arena taking ALL the slack, then launcher and bells. The
     arena is 1fr so the staff grows into whatever the screen gives it: bigger
     invaders, and no dead band above and below the notation. */
  grid-template-rows: auto minmax(0, 1fr) auto auto;
  gap: 6px;
  /* Only 6px under the bells: the number used to live below them and reserved a
     line there, so with it moved up the squad drops closer to the grass. */
  padding: 10px 12px 6px;
  height: 100%;
}

/* ── HUD ── */
.ti-hud {
  display: flex; align-items: center; gap: 8px;
  flex-wrap: wrap;
}
.ti-lives, .ti-stat, .ti-icon-btn {
  background: var(--navy);
  border-radius: 14px;
  box-shadow: 0 3px 0 rgba(12, 17, 34, 0.5);
  color: #fff;
}
.ti-lives { display: flex; align-items: center; gap: 5px; padding: 8px 12px; }
.ti-heart svg { width: 20px; height: 20px; fill: var(--red); display: block; }
.ti-heart.is-gone svg { fill: #47507a; }

.ti-stat { padding: 5px 14px 7px; text-align: center; min-width: 74px; }
.ti-stat dt {
  font-size: 8.5px; font-weight: 900; letter-spacing: 1.6px;
  text-transform: uppercase; color: #9aa7d0;
}
.ti-stat dd {
  margin: 0;
  font-family: 'JetBrains Mono', monospace;
  font-size: 17px; font-weight: 800; line-height: 1.15;
}
#streak.is-hot { color: var(--gold); }

.ti-icon-btn {
  margin-left: auto;
  width: 42px; height: 42px;
  display: grid; place-items: center;
  border: none; padding: 0; cursor: pointer;
}
.ti-icon-btn + .ti-icon-btn { margin-left: 0; }
.ti-icon-btn svg { width: 20px; height: 20px; fill: #fff; }
#muteBtn.is-muted .ti-wave { display: none; }
#muteBtn.is-muted { opacity: 0.6; }

/* ── The staff ──────────────────────────────────────────────────────────────
   `.ti-arena` is the white slab and centres the staff in the space the grid
   gives it. `.ti-staff` is EXACTLY the five lines: top line 0%, bottom line
   100%. Notes and the clef hang outside it, so nothing here may clip. */
.ti-arena {
  position: relative;
  align-self: stretch;
  min-height: 0;
  display: grid;
  align-items: center;
  padding: 0 clamp(8px, 2vw, 26px);
  overflow: visible;
}

.ti-staff {
  position: relative;
  /* Sized from the arena, so a taller screen gives a bigger staff and bigger
     invaders — which was the point of filling the vertical space. The cap is
     what stops a very tall window from making the clef taller than the arena:
     the clef is 1.55x the staff and hangs 0.38x below it, so the whole
     assembly is about 1.93x this number. */
  height: min(62%, 310px);
  margin: 0 auto;
  width: 100%;
}

/* A soft band behind the notation. Kept faint and feathered at the edges: at
   0.58 flat it tinted Rob's blue mountains grey, which is a real cost for a
   legibility gain the lines can earn themselves (see the halo below). */
.ti-staff::before {
  content: "";
  position: absolute;
  left: -1%; right: -1%;
  top: -46%; bottom: -46%;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.34) 16%,
    rgba(255, 255, 255, 0.34) 84%,
    rgba(255, 255, 255, 0) 100%
  );
  z-index: -1;
}

/* The halo is what lets the staff sit on artwork instead of on a white slab:
   a 1px white edge above and below each line, so a navy line stays readable
   over a green hill or a grey mountain. */
/* `left` is set per line in the engine, from a measurement of the clef: each
   line stops where the clef's leaning central stroke is at that height. */
.ti-line {
  position: absolute;
  right: 0;
  height: 2.6px;
  margin-top: -1.3px;
  border-radius: 2px;
  background: var(--navy);
  box-shadow: 0 -1.2px 0 rgba(255, 255, 255, 0.75), 0 1.2px 0 rgba(255, 255, 255, 0.75);
}

/* Natural aspect ratio, always. Stretching the staff used to stretch the clef
   with it; this is an <img> with only a height, so the width follows the
   drawing. `bottom` is negative because the clef hangs below the bottom line. */
.ti-clef {
  position: absolute;
  left: 0;
  width: auto;
  pointer-events: none;
}

/* Cross it and the note lands. Position comes from DEFENCE_X in the engine, so
   the line a child sees and the line the rules use cannot drift apart. */
.ti-defence {
  position: absolute;
  top: -14%;
  bottom: -14%;
  left: var(--defence, 14%);
  width: 7px;
  margin-left: -3.5px;
  border-radius: 4px;
  background: linear-gradient(180deg, #7ede55, #37a018);
  box-shadow: 0 0 12px rgba(74, 190, 40, 0.75);
}

.ti-invaders {
  position: absolute;
  inset: 0;
  overflow: visible;
  pointer-events: none;
}

/* ── The invader ────────────────────────────────────────────────────────────
   A notehead with a face: tilted ellipse, stem, and motion blur trailing to
   the right because it is travelling left.

   No flag. It used to carry an eighth-note flag built from a rounded box,
   which read as a filled capital D rather than as a flag — so the invader is
   a quarter note now, which is both cleaner and the more honest notation. */
/* Every invader marches in the same red — see spawn() in the engine for why a
   chroma-coloured invader would hand a Prodigies child the answer. `--c` is the
   marching colour; `--chroma` is the note's real colour, swapped in only once
   the note has been answered. */
.ti-invader {
  position: absolute;
  /* One line gap tall, always. `--gap` is set in px by the engine from the
     staff's real height, because a notehead that is not the size of the gap it
     sits in reads as the wrong note. */
  width: 1.28em; height: 0.95em;
  font-size: var(--gap, 56px);
  margin: -0.475em 0 0 -0.64em;
  --c: var(--invader-ink);
  transition: none;
}
.ti-invader.show-chroma { --c: var(--chroma); }
.ti-invader-head {
  position: absolute; inset: 0;
  border-radius: 50%;
  background: var(--c);
  transform: rotate(-20deg);
  box-shadow: 0 0 0 0.07em rgba(255, 255, 255, 0.92), 0 0.06em 0.12em rgba(27, 35, 62, 0.25);
}
.ti-invader-stem {
  position: absolute; right: 0.02em; top: -1.15em;
  width: 0.12em; height: 1.3em;
  border-radius: 0.06em; background: var(--c);
}
.ti-eye {
  position: absolute; top: 0.22em;
  width: 0.22em; height: 0.27em; border-radius: 50%;
  background: #fff; z-index: 2;
}
.ti-eye.left { left: 0.25em; }
.ti-eye.right { left: 0.6em; }
/* Grey, not black: on a dark brown head, black eyes and brows disappear. */
.ti-eye::after {
  content: ""; position: absolute; left: 0.05em; top: 0.07em;
  width: 0.12em; height: 0.15em; border-radius: 50%; background: var(--invader-face);
}
/* Angled brows are the whole "mischievous" read — cheap and effective. */
.ti-brow {
  position: absolute; top: 0.12em;
  width: 0.3em; height: 0.085em; border-radius: 0.05em;
  background: var(--invader-face); z-index: 3;
}
.ti-brow.left  { left: 0.17em; transform: rotate(20deg); }
.ti-brow.right { left: 0.57em; transform: rotate(-20deg); }

.ti-invader-blur {
  position: absolute; left: 100%; top: 42%;
  width: 1.1em; height: 0.28em;
  background: linear-gradient(90deg, var(--c), rgba(255, 255, 255, 0));
  opacity: 0.45; border-radius: 0.14em;
}

/* Middle C's ledger line. */
.ti-ledger {
  position: absolute; left: -0.22em; right: -0.22em; top: 50%;
  height: 0.062em; margin-top: -0.031em;
  background: var(--navy); opacity: 0.92;
}

.ti-invader.is-hit { animation: ti-pop 0.28s ease-out forwards; }
@keyframes ti-pop {
  0% { transform: scale(1); opacity: 1; }
  40% { transform: scale(1.4) rotate(8deg); opacity: 1; }
  100% { transform: scale(0.2) rotate(-20deg); opacity: 0; }
}

.ti-invader.is-landed { animation: ti-land 0.6s ease-in forwards; }
@keyframes ti-land {
  0% { transform: translateY(0); opacity: 1; }
  100% { transform: translateY(80px) rotate(-30deg); opacity: 0; }
}

.ti-points {
  position: absolute;
  margin: -34px 0 0 -20px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 15px; font-weight: 800; color: var(--navy);
  text-shadow: 0 1px 0 #fff, 0 0 6px #fff;
  animation: ti-rise 0.9s ease-out forwards;
  pointer-events: none;
}
@keyframes ti-rise {
  0% { transform: translateY(0); opacity: 0; }
  20% { opacity: 1; }
  100% { transform: translateY(-38px); opacity: 0; }
}

/* ── The laser ──────────────────────────────────────────────────────────────
   A bolt that actually travels: it grows from the launcher to the invader
   (scaleX from a left origin, so it is one cheap composited transform), then a
   burst pops where it lands and a flash sits at the muzzle. The first cut was a
   full-length bar that faded, which read as a flicker rather than a shot. */
.ti-beam {
  position: absolute; left: 0; top: 0;
  width: 0; height: 0;
  z-index: 3; pointer-events: none;
}

/* The bolt. */
.ti-beam.is-firing::before {
  content: "";
  position: absolute;
  left: var(--x); top: var(--y);
  width: var(--len); height: 9px;
  margin-top: -4.5px;
  transform-origin: 0 50%;
  border-radius: 5px;
  background: linear-gradient(90deg,
    rgba(255, 255, 255, 0) 0%,
    #fff 12%,
    var(--beam) 55%,
    #fff 100%);
  box-shadow: 0 0 10px var(--beam), 0 0 26px var(--beam);
  animation: ti-bolt 0.34s cubic-bezier(0.2, 0.7, 0.3, 1) forwards;
}
@keyframes ti-bolt {
  0%   { transform: rotate(var(--angle)) scaleX(0);    opacity: 1; }
  45%  { transform: rotate(var(--angle)) scaleX(1);    opacity: 1; }
  70%  { transform: rotate(var(--angle)) scaleX(1);    opacity: 0.9; }
  100% { transform: rotate(var(--angle)) scaleX(1);    opacity: 0; }
}

/* The impact, on the invader. */
.ti-beam.is-firing::after {
  content: "";
  position: absolute;
  left: var(--x2); top: var(--y2);
  width: 16px; height: 16px;
  margin: -8px 0 0 -8px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0%, var(--beam) 45%, rgba(255, 255, 255, 0) 72%);
  animation: ti-burst 0.36s ease-out 0.14s forwards;
  opacity: 0;
}
@keyframes ti-burst {
  0%   { transform: scale(0.3); opacity: 0; }
  30%  { transform: scale(2.6); opacity: 1; }
  100% { transform: scale(4.6); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .ti-beam.is-firing::before,
  .ti-beam.is-firing::after { animation-duration: 0.01ms; }
}

/* ── The heat gauge ─────────────────────────────────────────────────────────
   The visible half of the anti-mash rule. It has to be legible at a glance and
   unmistakable when it blows, or the lockout feels like a bug. */
/* The bells' heat, under the bells. Blasts travel upward from the bell row, so
   nothing ever crosses the gauge down here — it used to sit mid-field where
   every beam cut through it. */
.ti-heat {
  position: relative;
  justify-self: center;
  margin-top: 2px;
  display: flex; align-items: center; gap: 8px;
  padding: 5px 12px;
  background: var(--navy); border-radius: 999px;
  box-shadow: 0 3px 0 rgba(12, 17, 34, 0.5);
  color: #fff;
}
.ti-heat-label {
  font-size: 8.5px; font-weight: 900; letter-spacing: 1.6px;
  text-transform: uppercase; color: #9aa7d0;
}
.ti-heat-track {
  width: clamp(90px, 24vw, 160px); height: 8px;
  border-radius: 999px; background: #47507a; overflow: hidden;
}
.ti-heat-track i {
  display: block; height: 100%; width: 0;
  border-radius: 999px;
  background: linear-gradient(90deg, #6abb4a, #ffd25b);
  transition: width 0.12s linear, background 0.2s;
}
.ti-heat.is-hot .ti-heat-track i { background: linear-gradient(90deg, #ffd25b, #ee5524); }
.ti-heat.is-locked .ti-heat-track i { background: linear-gradient(90deg, #ee5524, #e72829); }

/* Above the bar now: the gauge sits at the very bottom of the screen, so there
   is no room below it. */
.ti-heat-warn {
  position: absolute; left: 50%; bottom: calc(100% + 5px);
  transform: translate(-50%, 6px);
  padding: 5px 12px; border-radius: 999px;
  background: var(--red); color: #fff;
  font-size: 11px; font-weight: 900; white-space: nowrap;
  opacity: 0; pointer-events: none;
  transition: opacity 0.15s, transform 0.15s;
}
.ti-heat.is-locked .ti-heat-warn { opacity: 1; transform: translate(-50%, 0); }
/* The bells sit right underneath, so the pill must float over them. */
.ti-heat { z-index: 6; }
.ti-heat.is-blown { animation: ti-heat-shake 0.36s ease-in-out; }
@keyframes ti-heat-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-5px); }
  60% { transform: translateX(5px); }
}

/* The bells are the guns, so the bells are what go dim and sooty while the
   heat vents. Never hidden — a child must still see which bell is which. */
.ti-bells.is-overheated .ti-bell-art { filter: grayscale(0.55) brightness(0.82); }
.ti-bells.is-overheated { animation: ti-vent 0.6s ease-in-out infinite; }
@keyframes ti-vent {
  0%, 100% { filter: brightness(1); }
  50% { filter: brightness(0.92); }
}

@media (prefers-reduced-motion: reduce) {
  .ti-heat.is-blown, .ti-bells.is-overheated { animation: none; }
}

/* ── Microphone strip ── */
.ti-mic {
  justify-self: center;
  display: flex; align-items: center; gap: 9px;
  padding: 6px 14px;
  background: var(--navy); border-radius: 999px;
  box-shadow: 0 3px 0 rgba(12, 17, 34, 0.5);
  color: #fff;
}
.ti-mic.is-off { opacity: 0.7; }
.ti-mic-icon svg {
  width: 17px; height: 17px; display: block;
  fill: none; stroke: #fff; stroke-width: 2; stroke-linecap: round;
}
.ti-mic-heard { font-size: 12px; font-weight: 900; min-width: 88px; }
.ti-mic-level { width: 74px; height: 7px; border-radius: 999px; background: #47507a; overflow: hidden; }
.ti-mic-level i {
  display: block; height: 100%; width: 0;
  border-radius: 999px; background: var(--green);
  transition: width 0.1s linear;
}

/* ── The bell row ───────────────────────────────────────────────────────────
   FREE bells — the bell art with no coloured base, so they read as characters
   standing in the field rather than an instrument on a table. They are also the
   weapon: the blast comes out of the bell that rang, so each one carries an aura
   that flares as it fires. */
/* These are the bell DEFENDERS: they do the shooting, so they have to look big
   and important rather than like a toolbar.

   The row is sized FROM the bell, not the other way round. A bell can only grow
   so tall before it climbs into the staff the invaders march across, so the
   height cap is the real constraint — and once height is capped, a wider row
   just adds gaps between bells instead of bigger bells. So the width is derived:
   eight bells plus seven gaps, or the screen, whichever is smaller. The bells
   come out flush at every size.

   `justify-self: center`, not stretch: a stretched grid item that is narrower
   than its track sits at the START of it, so on any monitor wider than the row
   the whole squad drifted to the left edge and left two thirds of the field
   empty. */
.ti-bells {
  --bell-max: clamp(84px, 16vh, 150px);
  --bell-gap: clamp(2px, 0.4vw, 8px);
  display: grid;
  grid-template-columns: repeat(8, minmax(0, 1fr));
  gap: var(--bell-gap);
  width: min(98%, calc(8 * var(--bell-max) + 7 * var(--bell-gap)));
  justify-self: center;
  align-items: end;
  /* The bell buttons themselves already clear the iPad home indicator by ~36px
     (measured); it is the decorative aura glow that reaches the edge. This is
     belt-and-braces for shorter viewports, where the row does get close.

     The var is a TEST SEAM: headless Chrome always reports env() insets as 0,
     so there is no way to prove this rule wins the cascade without one — and
     it did not win the first two attempts, both times silently. */
  padding-bottom: max(var(--ti-safe-bottom, 0px), env(safe-area-inset-bottom));
}
.ti-bell {
  position: relative;
  border: none; background: none; padding: 0;
  cursor: pointer;
  display: block;
  -webkit-tap-highlight-color: transparent;
}
.ti-bell-art {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  /* The ceiling that drives the row's width — see --bell-max above. */
  max-height: var(--bell-max);
  margin: 0 auto;
}
.ti-bell-layer {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  pointer-events: none;
  /* A free bell has no base to ground it, so it gets a soft shadow of its own
     or it looks pasted on. */
  filter: drop-shadow(0 3px 3px rgba(27, 35, 62, 0.28));
  transition: transform 0.09s ease;
}
.ti-bell:active .ti-bell-layer { transform: translateY(2px) scale(0.96); }

/* The ki aura: sits behind the bell, invisible until it charges. */
.ti-bell-aura {
  position: absolute;
  left: 50%; top: 52%;
  width: 150%; height: 150%;
  margin: -75% 0 0 -75%;
  border-radius: 50%;
  background: radial-gradient(circle, var(--c) 0%, rgba(255, 255, 255, 0) 62%);
  opacity: 0;
  pointer-events: none;
  z-index: -1;
}

/* Firing: the bell dips, flares, and throws the aura upward as it lets go. */
.ti-bell.is-firing .ti-bell-layer { animation: ti-bell-fire 0.42s ease-out; }
.ti-bell.is-firing .ti-bell-aura { animation: ti-bell-charge 0.42s ease-out; }
@keyframes ti-bell-fire {
  0%   { transform: translateY(0) scale(1); }
  22%  { transform: translateY(4px) scale(0.94); }
  46%  { transform: translateY(-5px) scale(1.1); }
  100% { transform: translateY(0) scale(1); }
}
@keyframes ti-bell-charge {
  0%   { opacity: 0; transform: scale(0.5); }
  30%  { opacity: 0.85; transform: scale(0.9); }
  60%  { opacity: 0.6; transform: scale(1.25); }
  100% { opacity: 0; transform: scale(1.6); }
}

.ti-bell.is-ringing .ti-bell-layer { animation: ti-bell-ring 0.4s ease-out; }
@keyframes ti-bell-ring {
  0% { transform: rotate(0deg); }
  30% { transform: rotate(-6deg); }
  65% { transform: rotate(4deg); }
  100% { transform: rotate(0deg); }
}

/* The 1-8 key, above and a touch right of its bell rather than underneath it.
   Out of flow entirely (absolute), which is what lets the bells sit lower — a
   label under them was reserving a whole line of the screen's shortest axis.
   Small and quiet on purpose: it is a hint, not a caption.

   Nunito Black, white, white stroke. The faint dark shadow is the one liberty —
   white on a white stroke vanishes where the pale path curves behind the row. */
.ti-bell-key {
  position: absolute;
  /* Tuned against the ARTWORK, not the image box. bell_c.png is opaque from 11%
     to 84% vertically and 14% to 84% horizontally, so the bell's visible right
     edge is at about 82% of its box — measuring from the box's edge put the
     number out in the 60px gap between two bells, where it read as belonging to
     the next one. At 19% its centre straddles its own bell's right shoulder and
     its bottom just laps the dome, which is what makes it look attached. */
  top: 4%;
  right: 19%;
  z-index: 2;
  pointer-events: none;
  font-family: 'Nunito', sans-serif;
  font-size: clamp(10px, 1.6vh, 15px);
  font-weight: 900;
  color: #fff;
  -webkit-text-stroke: 1.6px #fff;
  paint-order: stroke fill;
  text-shadow: 0 1px 2px rgba(27, 35, 62, 0.45);
  line-height: 1;
}
/* The shake, the fire and the ring all move the ART, never the button — so the
   number underneath holds still and the bell reads as independently animated
   rather than the whole label wobbling with it. */
.ti-bell.is-wrong .ti-bell-art { animation: ti-shake 0.3s ease-in-out; }
@keyframes ti-shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-5px); }
  75% { transform: translateX(5px); }
}

/* ── Toast + pause ── */
.ti-toast {
  position: absolute; left: 50%; top: 15%;
  transform: translate(-50%, -8px);
  padding: 9px 18px; border-radius: 999px;
  background: var(--navy); color: #fff;
  font-size: 15px; font-weight: 900;
  opacity: 0; pointer-events: none;
  transition: opacity 0.2s, transform 0.2s;
  z-index: 3;
}
.ti-toast b { color: var(--gold); }
.ti-toast.is-on { opacity: 1; transform: translate(-50%, 0); }
.ti-toast.is-miss { background: var(--red); }

.ti-pause-veil {
  position: absolute; inset: 0; z-index: 4;
  display: grid; place-items: center;
  background: rgba(27, 35, 62, 0.55);
  backdrop-filter: blur(3px);
}
/* display:grid outranks the UA [hidden] rule, so hiding it needs saying. */
.ti-pause-veil[hidden] { display: none; }
.ti-pause-card {
  background: #fff; border: 3px solid var(--navy);
  border-radius: 26px; padding: 30px 34px;
  display: grid; gap: 12px; justify-items: center; text-align: center;
  box-shadow: 0 18px 40px rgba(12, 17, 34, 0.35);
}
.ti-pause-card h2 { margin: 0 0 4px; font-size: 34px; font-weight: 900; color: var(--navy); }

/* ── Small screens ── */
@media (max-width: 560px) {
  .ti-hud { gap: 6px; }
  .ti-stat { min-width: 58px; padding: 4px 8px 6px; }
  .ti-stat dd { font-size: 15px; }
  .ti-lives { padding: 6px 9px; }
  .ti-heart svg { width: 17px; height: 17px; }
  .ti-icon-btn { width: 36px; height: 36px; }
  .ti-bell-key { display: none; }
}

/* A landscape phone has almost no vertical room. The decoration goes first and
   the notation never does. */
@media (max-height: 520px) {
  .ti-bells { width: min(100%, 460px); }
  .ti-bell-key { display: none; }
}


/* ── Clef chooser (title screen) ───────────────────────────────────────────
   A quieter control than the Bells/Mic pair above it: the clef changes what
   you READ, not how you answer, so it reads as a setting rather than a mode. */
.ti-clefs {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin: 2px 0 4px;
}
.ti-clefs-label {
  font-size: 10px; font-weight: 900; letter-spacing: 2px;
  text-transform: uppercase; color: rgba(255, 255, 255, 0.6);
}
.ti-clef-btn {
  appearance: none; cursor: pointer; font-family: inherit;
  border: 2px solid rgba(255, 255, 255, 0.28);
  background: rgba(255, 255, 255, 0.08);
  color: #fff; font-size: 13px; font-weight: 900;
  padding: 7px 16px; border-radius: 999px;
}
.ti-clef-btn.is-on { background: #fff; border-color: #fff; color: #1b233e; }

/* The bass clef stands INSIDE the staff (its bulb on the F line) rather than
   hanging below it the way the treble clef does, so it needs no drop. */
.ti-clef.is-bass { bottom: 0; }
