* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

/* Bubblegum Sans gives headings, menus and celebrations their character.
   Instructions and small statistics use the system face for readability.
   Canvas display text names Bubblegum Sans itself (UI_FONT in game.js). Vendored rather than pulled from Google's
   CDN: the iPad build is a local bundle with no network guarantee, and a font
   that only arrives when the device is online is not a font, it is a maybe.
   `block` rather than `swap` because a flash of fallback and then a reflow is
   more distracting than a beat of nothing. Split latin / latin-ext the way
   Google serves it, so the accented titles cost 1.5KB and only when needed. */
/* Leland, MuseScore's SMuFL music font: the clef, accidentals and rests on the
   highway. Its glyphs live in the Private Use Area, so nothing else picks it up. */
@font-face {
  font-family: "Leland";
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url("assets/fonts/leland.woff2") format("woff2");
}

@font-face {
  font-family: "Bubblegum Sans";
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url("assets/fonts/bubblegum-sans-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Bubblegum Sans";
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url("assets/fonts/bubblegum-sans-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: #000;
  overflow: hidden;
  touch-action: manipulation;
  overscroll-behavior: none;
  font-family: var(--font);
  /* The face comes in one weight. A browser-thickened "bold" of a rounded
     display face smears its counters shut, so the weights asked for below
     simply read as the regular. */
  font-synthesis: none;
}

/* Form controls do not inherit the page's face on their own. */
button, input, select, textarea {
  font-family: inherit;
}

/* How much livelier the scene is than what the renderers actually draw. Two
   knobs, applied to the four visual layers below and to nothing else: the
   header, the dialogs and the introduction are text on dark panels, and lifting
   those washes them out rather than brightening anything.
       1 leaves a layer exactly as drawn.
   Graded here rather than in each renderer because the scene comes from three
   different places — a WebGL character and two 2D canvases — and one grade over
   the lot is the only way they stay in step. The cost is one cheap shader pass
   per layer at composite time, which is where these layers already are. */
:root {
  --font: "Bubblegum Sans", "Trebuchet MS", sans-serif;
  --font-body: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --scene-saturate: 1.3;
  --scene-brightness: 1.5;
}

/* Layer order, back to front: backdrop (the location, the stage light, the
   fireworks) -> note highway -> character. The highway recedes to the right and
   passes *behind* the player, which is why #game-canvas sits under
   #character-canvas rather than over it. */
#game-canvas,
#character-canvas,
#background-canvas {
  filter: saturate(var(--scene-saturate)) brightness(var(--scene-brightness));
}
/* The perf readout's switch (lib/perf-hud.js), for measuring what the grade costs.
   The game canvas changes every frame, so its grade is redone over the whole
   screen every frame. If that is what makes busy songs drop frames on an iPad,
   the next step is a game canvas only as tall as the highway band (the notation
   layer and highway picture already are; see game.js resize). */
body.perf-no-grade :is(#game-canvas, #character-canvas, #background-canvas) { filter: none; }

#game-canvas,
#character-canvas,
#background-canvas {
  display: block;
  position: fixed;
  top: 0;
}

#game-canvas {
  left: 0;
  width: 100vw;
  height: 100dvh;
  z-index: 2;
}

/* The right-hand slab of the screen, full height — `left`/`width`/`height`
   are set by game.js's resize(). Transparent apart from the character
   themselves, so the highway and backdrop show through around them. */
#character-canvas {
  z-index: 3;
}

/* Full-viewport, the backmost layer — the opaque backdrop everything else is
   drawn over: the location's own art, with the stage light and the fireworks
   on top of it. The light is thrown under the character on the right-hand side
   (see CHARACTER_X_FRACTION in background.js). */
#background-canvas {
  left: 0;
  width: 100vw;
  height: 100dvh;
  z-index: 0;
}

/* Arriving in a song out of the map (arriveInSong in game.js). Everything that
   belongs to the song fades up over the place, which is already on screen. The
   backdrop is not in this list on purpose: it does not move and it does not
   fade, because the map has been showing exactly it. */
body.arriving #game-canvas,
body.arriving #character-canvas,
body.arriving #pause-button {
  animation: arrive-fade 620ms ease-out both;
}
@keyframes arrive-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* The pause button, and only while there is something to pause. The markup is
   parsed and painted long before game.js has run and decided which screen to
   put up, so it starts hidden and is shown by gameplay rather than hidden by
   everything else. body.playing is set in game.js wherever the game comes into
   view. It also steps aside while the pause menu or a result is up: each has
   its own way back.

   Drawn like a circle on the map — the same cream edge and the same lift — so
   it reads as a piece of the same world rather than as browser chrome. */
#pause-button {
  display: none;
}
body.playing #pause-button {
  position: fixed; z-index: 10;
  bottom: max(16px, env(safe-area-inset-bottom)); left: 50%;
  transform: translateX(-50%);
  display: grid; place-items: center;
  width: 60px; height: 60px; padding: 0;
  border-radius: 50%;
  background: #1a2733d9;
  border: 3px solid #f3ead7;
  color: #f3ead7;
  box-shadow: 0 8px 22px #0009;
  cursor: pointer;
  transition: transform 160ms ease-out, box-shadow 160ms ease-out;
}
body.playing #pause-button:hover,
body.playing #pause-button:focus-visible {
  transform: translateX(-50%) scale(1.07); outline: none;
  box-shadow: 0 10px 28px #000c, 0 0 0 6px #f3ead733;
}
#pause-button svg { width: 26px; height: 26px; fill: currentColor; filter: drop-shadow(0 1px 2px #0008); }
body.controls-open #pause-button,
body.result-open #pause-button { display: none; }

/* The song's title, shown across the top while it plays. Same visibility
   rules as the pause button: hidden until gameplay starts, and out of the way
   whenever the pause menu or a result covers the screen. */
#song-title {
  display: none;
}
body.playing #song-title {
  display: block;
  position: fixed; z-index: 10;
  top: max(16px, env(safe-area-inset-top)); left: 50%;
  transform: translateX(-50%);
  margin: 0;
  font-size: 22px;
  color: #fff;
  text-shadow: 0 2px 4px #000a, 0 1px 2px #0009;
  text-align: center;
  pointer-events: none;
}
body.controls-open #song-title,
body.result-open #song-title { display: none; }

/* The pause menu: one of the song cards from the map, grown to hold the
   settings — the same cream stock and dark ink, the same deep shadow, framed
   in the cream edge the circles on the map are drawn with. Above the
   introduction (120), which can be up when the game is paused, but below the
   customization, credits and offer (125-127) it opens itself. */
#controls-dialog[hidden] { display: none; }
#controls-dialog {
  position: fixed; z-index: 121;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(440px, calc(100vw - 32px));
  max-height: calc(100dvh - 32px); overflow: auto;
  padding: 22px 26px 24px;
  background: #f4efe1;
  color: #241f18;
  border: 3px solid #f3ead7;
  outline: 1px solid #00000026; outline-offset: -6px;
  border-radius: 18px;
  box-shadow: 0 18px 50px #000000a6, 0 3px 8px #0000004d;
  animation: controls-in 180ms cubic-bezier(0.3, 1.4, 0.5, 1) both;
}
@keyframes controls-in {
  from { opacity: 0; transform: translate(-50%, -46%) scale(0.94); }
  to { opacity: 1; transform: translate(-50%, -50%); }
}
@media (prefers-reduced-motion: reduce) {
  #controls-dialog { animation: none; }
}
#controls-heading {
  margin: 0 0 14px;
  font-weight: 400;
  font-size: 34px; line-height: 1.1; text-align: center;
}

/* The three things a pause is usually for, as the card's own buttons. */
#controls-actions {
  display: flex; justify-content: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 18px;
}
#controls-actions button,
#controls-footer button,
#customize-open {
  font: inherit; font-size: 15px; font-weight: 600;
  color: #241f18; background: #fffaf0;
  border: 2px solid #241f1840; border-radius: 999px;
  padding: 8px 13px; cursor: pointer;
  box-shadow: 0 2px 0 #241f1826;
  transition: transform 120ms ease-out, background 120ms ease-out;
}
#controls-actions button:hover,
#controls-footer button:hover,
#customize-open:hover { background: #fff; transform: translateY(-1px); }
/* Things you go to rarely, along the bottom edge. */
#controls-footer {
  display: flex; justify-content: center; gap: 8px; flex-wrap: wrap;
  margin: 16px 0 0;
}
#controls-footer button[hidden] { display: none; }
#controls-footer button { font-size: 14px; padding: 6px 14px; }
#controls-actions #resume-button {
  color: #f4efe1; background: #2c4a5e; border-color: #1a2d3a;
}
#controls-actions #resume-button:hover { background: #365a72; }

/* The settings, one to a row, ruled faintly across like the cards are: the
   name on the left, the control on the right. */
#controls-fields {
  display: flex; flex-direction: column;
  border-top: 1px solid #241f1826;
}
#controls-fields > div,
#advanced-controls > div {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; min-height: 46px; padding: 6px 2px;
  border-bottom: 1px solid #241f1826;
}
#controls-fields > div[hidden],
#advanced-controls > div[hidden] { display: none; }
#controls-fields label {
  display: flex; align-items: baseline; gap: 8px;
  font-size: 13px; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase;
  white-space: nowrap;
}
/* The tuning controls, folded away at the bottom: most players never need them. */
#advanced-controls { border-bottom: 1px solid #241f1826; }
#advanced-controls > summary {
  display: flex; align-items: center; justify-content: space-between;
  min-height: 46px; padding: 6px 2px; cursor: pointer; list-style: none;
  font-size: 13px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
}
#advanced-controls > summary::-webkit-details-marker { display: none; }
#advanced-controls > summary::after {
  content: ""; width: 8px; height: 8px; margin-right: 4px;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg); transition: transform 120ms ease-out;
}
#advanced-controls[open] > summary::after { transform: rotate(-135deg); }
#advanced-controls > div:last-child { border-bottom: 0; }
#gain-value,
#speed-value,
#music-value {
  font-weight: 400; letter-spacing: 0; text-transform: none;
  color: #241f18a6;
  font-variant-numeric: tabular-nums;
}
#gain-slider,
#speed-slider,
#music-slider {
  width: min(180px, 42vw);
  accent-color: #2c4a5e;
}
/* A caveat under the toggle, only while it is on. */
#melody-audio-note {
  display: none; margin: 0; padding: 6px 2px 8px;
  font-size: 12px; line-height: 1.35; color: #241f18a6;
  border-bottom: 1px solid #241f1826;
}
#melody-audio-control:not([hidden]):has(#melody-audio-toggle:checked) + #melody-audio-note { display: block; }
#melody-audio-toggle {
  width: 22px; height: 22px; margin: 0;
  accent-color: #2c4a5e;
  cursor: pointer;
}
#character-select,
#daily-goal-select,
#slide-numbers-select,
#colors-select,
#perspective-select {
  font: inherit; font-size: 14px;
  color: #241f18; background: #fffaf0;
  border: 2px solid #241f1840; border-radius: 10px;
  padding: 5px 8px;
  max-width: min(220px, 50vw);
}
#controls-fields > #trombone-control { flex-direction: column; align-items: stretch; gap: 8px; padding: 10px 2px; }
#trombone-control .label-text {
  font-size: 13px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
}
#trombone-fields { padding: 4px 3px 2px; }
#character-control { flex-wrap: wrap; }
#character-control label { margin-right: auto; }
#customize-open { font-size: 13px; padding: 5px 12px; }

/* Short windows — a phone on its side — fit more menu by taking the air out. */
@media (max-height: 520px) {
  #controls-dialog { padding: 14px 20px 16px; }
  #controls-heading { font-size: 26px; margin-bottom: 10px; }
  #controls-actions { margin-bottom: 10px; }
  #controls-fields > div,
  #advanced-controls > div { min-height: 38px; padding: 3px 2px; }
}

/* End-of-song result. Centred rather than tucked to one side like the
   customization panel: this one interrupts, so it belongs in the middle. */
#result-dialog {
  position: fixed; z-index: 110;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  background: #f4efe1; color: #241f18;
  border: 3px solid #f3ead7;
  outline: 1px solid #00000026; outline-offset: -6px;
  border-radius: 18px; padding: 28px 32px;
  width: min(360px, 84vw); text-align: center;
  box-shadow: 0 18px 50px #000000a6, 0 3px 8px #0000004d;
  animation: controls-in 180ms cubic-bezier(0.3, 1.4, 0.5, 1) both;
}
@media (prefers-reduced-motion: reduce) {
  #result-dialog { animation: none; }
}
/* Three stars in a shallow arc: the outer pair tilted away from centre and
   sitting lower, the middle one raised and a little larger. The arc is what
   makes three stars read as a score rather than as a row of icons. */
#result-stars {
  display: flex; justify-content: center; align-items: flex-start;
  gap: 6px; margin: 0 0 14px; height: 86px;
}
.star-slot {
  position: relative; width: 62px; height: 62px; flex: none;
}
.star-slot:first-child  { transform: translateY(14px) rotate(-20deg); }
.star-slot:nth-child(2) { transform: translateY(-6px) scale(1.16); }
.star-slot:last-child   { transform: translateY(14px) rotate(20deg); }

.star-slot svg { position: absolute; inset: 0; width: 100%; height: 100%; }

/* The unfilled slot: a dashed outline, deliberately not a grey star. It reads
   as somewhere a star goes rather than as a star you have been denied. */
.star-empty {
  fill: #241f180d;
  stroke: #241f1859; stroke-width: 0.72; stroke-dasharray: 1.7 1.45; stroke-linejoin: round;
}

/* The earned star, hidden until its slot is marked earned. */
.star-fill {
  opacity: 0; transform: scale(0.2);
  filter: drop-shadow(0 3px 8px #f2a00766);
}
.star-slot.earned .star-fill {
  animation: star-pop 520ms cubic-bezier(0.3, 1.5, 0.5, 1) both;
  animation-delay: var(--star-delay, 0ms);
}

/* Overshoots and settles — the star lands in the slot rather than fading in. */
@keyframes star-pop {
  0%   { opacity: 0; transform: scale(0.2) rotate(-50deg); }
  55%  { opacity: 1; transform: scale(1.25) rotate(8deg); }
  100% { opacity: 1; transform: scale(1) rotate(0deg); }
}

/* Someone who has asked for less motion still needs to see the result, so the
   stars appear — they just stop flying in. */
@media (prefers-reduced-motion: reduce) {
  .star-slot.earned .star-fill { animation: star-fade 200ms linear both; }
  @keyframes star-fade { to { opacity: 1; transform: scale(1); } }
}
#result-dialog #result-detail { margin: 0 0 20px; font-size: 14px; color: #241f18a6; }
/* The first song's banner, over the stars. */
#result-heading { margin: 0 0 6px; font-size: 26px; font-weight: normal; color: #b0700d; }
#result-heading[hidden] { display: none; }
#result-dialog #result-note {
  margin: -8px 0 20px; font-size: 12px; color: #8a5a12; line-height: 1.5;
}
#result-actions { display: flex; justify-content: center; gap: 8px; }
#result-dialog button {
  font: inherit; font-size: 15px; font-weight: 600;
  color: #241f18; background: #fffaf0;
  border: 2px solid #241f1840; border-radius: 999px;
  padding: 8px 20px; cursor: pointer;
  box-shadow: 0 2px 0 #241f1826;
  transition: transform 120ms ease-out, background 120ms ease-out;
}
#result-dialog button:hover { background: #fff; transform: translateY(-1px); }
#result-dialog button[hidden] { display: none; }
#result-dialog button.primary { color: #f4efe1; background: #2c4a5e; border-color: #1a2d3a; min-width: 110px; }
#result-dialog button.primary:hover { background: #365a72; }

/* Credits: the published page, framed, in the same card as the pause menu. */
#credits-dialog {
  position: fixed; z-index: 126;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(560px, calc(100vw - 32px)); height: min(640px, calc(100dvh - 32px));
  display: flex; flex-direction: column; gap: 12px;
  padding: 22px 26px 24px;
  background: #f4efe1; color: #241f18;
  border: 3px solid #f3ead7;
  outline: 1px solid #00000026; outline-offset: -6px;
  border-radius: 18px;
  box-shadow: 0 18px 50px #000000a6, 0 3px 8px #0000004d;
}
#credits-dialog[hidden] { display: none; }
#credits-heading {
  margin: 0; font-weight: 400;
  font-size: 34px; line-height: 1.1; text-align: center;
}
#credits-frame {
  flex: 1; min-height: 0; width: 100%; border: 0;
  border-top: 1px solid #241f1826; border-bottom: 1px solid #241f1826;
  background: transparent;
}
#credits-close {
  align-self: center; font: inherit; font-size: 15px; font-weight: 600;
  color: #f4efe1; background: #2c4a5e;
  border: 2px solid #1a2d3a; border-radius: 999px;
  padding: 8px 28px; cursor: pointer;
  box-shadow: 0 2px 0 #241f1826;
  transition: transform 120ms ease-out, background 120ms ease-out;
}
#credits-close:hover { background: #365a72; transform: translateY(-1px); }

/* The World Tour offer: the pause menu's card, over the map or the menu. */
#offer-dialog {
  position: fixed; z-index: 127;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(460px, calc(100vw - 32px));
  max-height: calc(100dvh - 32px); overflow: auto;
  padding: 22px 26px 24px; text-align: center;
  background: #f4efe1; color: #241f18;
  border: 3px solid #f3ead7;
  outline: 1px solid #00000026; outline-offset: -6px;
  border-radius: 18px;
  box-shadow: 0 18px 50px #000000a6, 0 3px 8px #0000004d;
  animation: controls-in 180ms cubic-bezier(0.3, 1.4, 0.5, 1) both;
}
#offer-dialog[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) {
  #offer-dialog { animation: none; }
}
#offer-title {
  margin: 0 0 14px; font-weight: 400;
  font-size: 34px; line-height: 1.1;
}
#offer-dialog p { margin: 0 0 12px; font-size: 16px; line-height: 1.45; }
#offer-note { color: #6b6152; font-size: 14px !important; }
#offer-status { color: #8a3b12; font-weight: 600; }
#offer-status[hidden] { display: none; }
.offer-actions { display: flex; justify-content: center; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
.offer-actions button {
  font: inherit; font-size: 15px; font-weight: 600;
  color: #241f18; background: #fffaf0;
  border: 2px solid #241f1840; border-radius: 999px;
  padding: 8px 16px; cursor: pointer;
  box-shadow: 0 2px 0 #241f1826;
  transition: transform 120ms ease-out, background 120ms ease-out;
}
.offer-actions button[hidden] { display: none; }
.offer-actions button:hover:not(:disabled) { background: #fff; transform: translateY(-1px); }
.offer-actions button:disabled { opacity: 0.55; cursor: default; }
.offer-actions #offer-buy { color: #f4efe1; background: #2c4a5e; border-color: #1a2d3a; }
.offer-actions #offer-buy:hover:not(:disabled) { background: #365a72; }

/* Microphone refused. Same frame as the result dialog, and above everything
   — the introduction (120) and customization (125) included — because nothing else works until it is read. */
#mic-dialog {
  position: fixed; z-index: 130;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  background: #0e272c; color: #eee; border: 1px solid #555565;
  border-radius: 14px; padding: 24px 28px;
  width: min(400px, 84vw); text-align: center;
  box-shadow: 0 12px 48px #000a;
}
#mic-dialog[hidden] { display: none; }
#mic-dialog h2 { margin: 0 0 12px; font-size: 20px; }
#mic-dialog p { margin: 0 0 12px; font-size: 14px; line-height: 1.5; color: #b8b8c8; }
#mic-dialog .mic-hint { color: #d9a441; margin-bottom: 20px; }
.mic-actions { display: flex; justify-content: center; flex-wrap: wrap; gap: 8px; }
#mic-dialog button {
  font: inherit; color: #eee; background: #30303d; border: 1px solid #686879;
  border-radius: 6px; padding: 8px 16px; cursor: pointer;
}
#mic-dialog button[hidden] { display: none; }
#mic-dialog button.primary { background: #3a5fa8; border-color: #5b82d0; }

#customize-dialog button {
  font: inherit; color: #eee; background: #30303d; border: 1px solid #686879;
  border-radius: 6px; padding: 6px 10px; cursor: pointer;
}
#customize-dialog { background: #0e272c; color: #eee; border: 1px solid #555565;
  border-radius: 14px; padding: 24px; width: min(340px, 80vw); }
#customize-dialog::backdrop { background: #0005; }
#customize-dialog h2 { margin: 4px 0 24px; font-size: 20px; }
#customize-dialog .close-customize { float: right; }
#appearance-fields .swatch-group { border: 0; padding: 0; margin: 16px 0; min-width: 0; }
#appearance-fields legend { padding: 0; margin-bottom: 8px; }
#appearance-fields .swatches, #trombone-fields .swatches { display: flex; flex-wrap: wrap; gap: 10px; }
/* The circle is the label; the radio inside it is hidden but still takes focus,
   so the ring shows for keyboard users through :focus-visible. */
#customize-dialog .swatch, #trombone-fields .swatch { position: relative; width: 30px; height: 30px; border-radius: 50%;
  background: var(--swatch); box-shadow: inset 0 0 0 1px #fff3; cursor: pointer;
  outline: 2px solid transparent; outline-offset: 3px; transition: transform .12s, outline-color .12s; }
#customize-dialog .swatch:hover, #trombone-fields .swatch:hover { transform: scale(1.1); }
#customize-dialog .swatch input, #trombone-fields .swatch input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
#customize-dialog .swatch:has(input:checked) { outline-color: #d9a441; }
#trombone-fields .swatch:has(input:checked) { outline-color: #2c4a5e; }
#customize-dialog .swatch:has(input:focus-visible) { outline-color: #fff; }
#trombone-fields .swatch:has(input:focus-visible) { outline-color: #241f18; }
#appearance-fields .chips { display: flex; flex-wrap: wrap; gap: 8px; }
#appearance-fields .chip { position: relative; padding: 5px 11px; border-radius: 999px;
  background: #30303d; border: 1px solid #686879; cursor: pointer; font-size: 15px;
  outline: 2px solid transparent; outline-offset: 2px; transition: border-color .12s, background .12s; }
#appearance-fields .chip:hover { border-color: #9a9aab; }
#appearance-fields .chip input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
#appearance-fields .chip:has(input:checked) { background: #4a3d1f; border-color: #d9a441; }
#appearance-fields .chip:has(input:focus-visible) { outline-color: #fff; }
/* Not yet earned (lib/unlocks.js): dimmed, with a padlock, and not pickable.
   The title says which song opens it. */
#customize-dialog .lock, #trombone-fields .lock { display: none; }
#customize-dialog .locked .lock, #trombone-fields .locked .lock { display: block; width: 14px; height: 14px;
  fill: #e8e8f0; stroke: #e8e8f0; stroke-width: 2.2; stroke-linecap: round; }
#customize-dialog .locked input, #trombone-fields .locked input { cursor: not-allowed; }
#customize-dialog .swatch.locked, #trombone-fields .swatch.locked { filter: grayscale(.85) brightness(.45); }
#customize-dialog .swatch.locked:hover, #trombone-fields .swatch.locked:hover { transform: none; }
#customize-dialog .swatch.locked .lock, #trombone-fields .swatch.locked .lock { position: absolute; inset: 0; margin: auto; filter: drop-shadow(0 1px 1px #000); }
#appearance-fields .chip.locked { display: inline-flex; align-items: center; gap: 6px;
  color: #8a8a99; border-style: dashed; cursor: not-allowed; }
#appearance-fields .chip.locked .lock { fill: #8a8a99; stroke: #8a8a99; }
#appearance-fields .chip.locked:hover { border-color: #686879; }
/* Earned and not yet looked at. Goes when the panel closes. */
#customize-dialog .is-new::after, #trombone-fields .is-new::after { content: ""; position: absolute; top: -3px; right: -3px;
  width: 10px; height: 10px; border-radius: 50%; background: #ff4d5e; box-shadow: 0 0 0 2px #171721; }
/* And on each way into the panel while there is anything new in it. */
#customize-open, #intro-customize, #pause-button { position: relative; }
#customize-open.has-new::after, #intro-customize.has-new::after, #pause-button.has-new::after {
  content: ""; position: absolute; top: 2px; right: 2px; width: 10px; height: 10px;
  border-radius: 50%; background: #ff4d5e; box-shadow: 0 0 0 2px #0008; pointer-events: none; }
#customize-dialog [type=checkbox] { accent-color: #d9a441; }

/* Above the introduction's box (120) too, since the introduction can open it. */
#customize-dialog { position: fixed; z-index: 125; top: 50%; left: 50%; transform: translate(-50%, -50%); max-height: calc(100dvh - 32px); overflow: auto; box-shadow: 0 8px 32px #0008; }

/* ---------------------------------------------------------------------------
   Getting started (onboarding.js)
   --------------------------------------------------------------------------- */

/* While the introduction is up there is nothing to read on the highway: the
   player, the light on them and the words. The controls are already gone —
   they are shown by body.playing, which the introduction never sets. */
body.intro #game-canvas {
  display: none;
}

/* Just left of centre, with the player stood just right of it. Anchored by its
   right edge so the gap between the words and the character holds as the
   window changes, rather than the box growing across the middle of the screen. */
#onboarding {
  position: fixed; z-index: 120;
  top: 50%; transform: translateY(-50%);
  right: 52vw; width: min(40vw, 560px);
  max-height: 90dvh; overflow: auto;
  color: #f4f1ea;
  font-size: 40px;
  line-height: 1.3;
  background: #0c2227ee;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border: 1px solid #ffffff1f;
  border-radius: 18px;
  padding: 32px 36px;
  box-shadow: 0 16px 56px #000a;
  text-shadow: 0 2px 12px #000a;
  text-align: center;
}
#onboarding-text p { margin: 0 0 0.8em; }
#onboarding-text p:not(:first-child) {
  font-family: var(--font-body); font-size: 0.65em;
  line-height: 1.5; text-shadow: none; color: #e4ecec;
}
#onboarding-help { font-family: var(--font-body); }
#onboarding-text p:last-child { margin-bottom: 0; }

/* What the microphone is hearing, on the sound-check screen. Sized in ems so
   the strip keeps its proportions against the type when the type steps down on
   a small screen. The explicit `display: none` is because the block below
   would otherwise win over the `hidden` attribute. */
#onboarding-wave {
  display: block;
  width: 100%;
  height: 2.2em;
  margin: 1em 0 0;
}
#onboarding-wave[hidden] { display: none; }
#onboarding-wave.tuner { height: 2.6em; }

/* The hold: a bar that fills while the note sounds and empties when it stops.
   Scaled rather than resized, so filling it every frame is not a layout. */
#onboarding-meter {
  height: 10px; margin: 0.6em 0 0;
  background: #ffffff14; border-radius: 999px; overflow: hidden;
}
#onboarding-meter[hidden] { display: none; }
#onboarding-meter span {
  display: block; height: 100%;
  background: linear-gradient(90deg, #f0c04a, #ffe08a);
  box-shadow: 0 0 12px #f0c04a99;
  transform: scaleX(0); transform-origin: left;
}

/* The note a screen is about, large over the words: its name and, under it,
   where the slide goes. It lands with a little overshoot, like the stars. */
#onboarding-note {
  display: flex; flex-direction: column; align-items: center;
  margin: 0 0 0.35em;
  color: #ffe08a;
}
#onboarding-note[hidden] { display: none; }
.onboarding-note-name { font-size: 2.2em; line-height: 1; text-shadow: 0 0 24px #f0c04a88, 0 2px 12px #000a; }
.onboarding-note-position { font-size: 0.6em; color: #f4f1eacc; letter-spacing: 0.04em; }
.onboarding-note-position:empty { display: none; }
#onboarding-note.arrived { animation: onboarding-note-in 520ms cubic-bezier(0.3, 1.5, 0.5, 1) both; }
@keyframes onboarding-note-in {
  0%   { opacity: 0; transform: scale(0.3); }
  60%  { opacity: 1; transform: scale(1.15); }
  100% { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  #onboarding-note.arrived { animation: none; }
}

/* Said under a screen nobody has played past in a while: quieter than the
   instruction, since it is an aside. */
#onboarding-help { margin: 0.8em 0 0; font-size: 0.55em; line-height: 1.4; color: #f4f1eabf; }
#onboarding-help[hidden] { display: none; }

/* The last screen stands beside the highway's line rather than over it: the
   note it points at is on that line. Right of the line and clear of the
   player, whose light is at the right-hand edge. */
#onboarding.onboarding-stage {
  right: auto; left: 40vw; width: min(30vw, 440px);
  font-size: 28px; padding: 24px 28px;
}

/* The button is part of the same sentence as the text above it, so it is set
   in the same face — just smaller, the way a spoken reply is quieter than the
   question. */
#onboarding-next {
  font-family: inherit;
  font-size: 0.7em;
  margin-top: 1em;
  color: #fff;
  background: #3a5fa8;
  border: 1px solid #7ea2e8;
  border-radius: 10px;
  padding: 0.35em 1.1em;
  cursor: pointer;
}
#onboarding-next:hover { background: #4a72c4; }

/* The player's name over their shins, centred under them — on the same point
   the character is framed on (--intro-stands-at, set by game.js). A small pill
   rather than a panel: it is a caption on the character, not a second box. */
#intro-character { display: none; }
body.intro #intro-character {
  position: fixed; z-index: 115;
  left: var(--intro-stands-at, 74vw); bottom: 9dvh; transform: translateX(-50%);
  display: flex; align-items: center; gap: 6px;
  color: #f4f1ea; font-size: 20px;
  text-shadow: 0 2px 8px #000a;
}
#intro-character-name {
  display: flex; align-items: center;
  min-width: 4.5em; justify-content: center;
  background: #0c2227cc;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border: 1px solid #ffffff1f; border-radius: 999px;
  padding: 4px 14px;
}
#intro-character button {
  display: grid; place-items: center;
  width: 34px; height: 34px; padding: 0;
  color: #f4f1ea; background: #0c2227cc;
  border: 1px solid #ffffff2a; border-radius: 50%;
  cursor: pointer;
}
#intro-character button:hover:not(:disabled) { background: #30303dee; }
#intro-character button:disabled { opacity: .45; cursor: default; }
#intro-character button[hidden] { display: none; }
#intro-character svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
#intro-customize svg { stroke-width: 1.8; }

/* Landscape only, and no portrait case: the app is locked to landscape on iOS
   (UISupportedInterfaceOrientations in ios/App/App/Info.plist) and the web
   version assumes a window at least as wide as the 720px below. A narrower or
   taller-than-wide window still runs — it is simply not a shape this is
   designed for.

   What is designed for is a landscape window with little height: a phone held
   sideways is only a few hundred pixels tall and has no room for 40px type and
   the screen it is set on. Type that has to be scrolled to be read is worse
   than type a few sizes down, so it comes down rather than the box growing a
   scrollbar. */
@media (max-height: 620px), (max-width: 720px) {
  #onboarding { font-size: 26px; padding: 22px 26px; }
  #onboarding.onboarding-stage { font-size: 20px; padding: 16px 20px; }
}

/* A word from the coach during the first song: a pill at the top of the
   highway, there for a few seconds and gone. Over the game, under dialogs. */
#coach-tip {
  position: fixed; z-index: 60;
  top: 14dvh; left: 36vw; transform: translateX(-50%);
  max-width: min(46vw, 520px);
  margin: 0; padding: 10px 20px;
  color: #241f18; background: #f4efe1;
  border: 2px solid #f0c04a; border-radius: 999px;
  font-size: 20px; line-height: 1.3; text-align: center;
  box-shadow: 0 10px 30px #0008;
  animation: coach-in 260ms cubic-bezier(0.3, 1.4, 0.5, 1) both;
  pointer-events: none;
}
#coach-tip[hidden] { display: none; }
@keyframes coach-in {
  from { opacity: 0; transform: translate(-50%, -12px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}
@media (prefers-reduced-motion: reduce) {
  #coach-tip { animation: none; }
}

/* ---------------------------------------------------------------------------
   The campaign map (map.js)

   Two views on one screen, switched by [data-view]. The world is a map with a
   circle at every location reached; a location is that circle opened out until
   its arc has left the window, with the songs over it. Nothing fades: the
   backdrop is at full strength the whole way, and so is the map under it.
   Locked locations are not styled because they are not drawn — a map shows
   where you have been.

   The stacking runs map (1), backdrop and its ring (2), songs (3), chrome (4).
   The backdrop is over the map rather than under it because the map no longer
   gets out of the way; being covered IS how it leaves.

   No frame, no title, no buttons on the world view. The map is the interface:
   the circles are what you press, and pressing anything else leaves.
   --------------------------------------------------------------------------- */

#map-screen[hidden] { display: none; }

#map-screen {
  position: fixed; inset: 0; z-index: 105;
  /* The map's own ocean. The map keeps its own proportions and is framed on the
     locations rather than on the window, so there is usually something left
     over at one edge or another; this is what it is made of — the same water,
     which is what lets a fixed-aspect image read as full bleed. */
  background: #2c4a5e;
  color: #f3ead7;
  overflow: hidden;
}

/* The note highway goes. The staff, the song title and the scrolling notes are
   gameplay, and half-legible gameplay under a menu reads as a rendering fault
   rather than as depth. The free-play controls go with it, by not being shown:
   they are the alternative to the map, and two ways to pick a song on screen at
   once is one too many. */
body.mapping #game-canvas { display: none; }

/* ---- The world ---------------------------------------------------------- */

/* Equirectangular, so 2:1 — the projection in chapters.js assumes it, and
   stretching the image to any other shape would put every pin in the wrong
   place. Full width, centred in the window. */
/* Sized and placed entirely from map.js (frameWorld), in window pixels: the map
   is zoomed to the locations on it, which is a measurement and not something a
   stylesheet can express. Never stretched — the height always follows the
   image's own proportions, so a pin lands where the projection says it does. */
#map-world {
  position: absolute; z-index: 1;
  touch-action: none; cursor: grab;
}
#map-world.is-panning, #map-world.is-panning .map-pin { cursor: grabbing; }

/* Under an open location. The map is not going anywhere and is not dimmed; it
   is simply behind a backdrop that now covers the window, and the only thing
   that has to change is that it can no longer be clicked through. */
#map-world.is-covered { pointer-events: none; }

/* Making room for a location that has just opened (startReveal in map.js).
   The duration is set from JS. Eased in and out, unlike the zoom into a
   location: this one starts and ends on a frame the player is looking at. */
#map-world.is-reframing {
  transition-property: width, height, left, top;
  transition-timing-function: cubic-bezier(0.45, 0, 0.25, 1);
  pointer-events: none;
}

/* Zooming in from the splash (zoomInFrom in map.js). Nothing is on the map
   while it moves; then the circles pop in one after another, the same pop as a
   location arriving, and the dots and leader lines fade up under them. */
#map-world.is-landing .map-pin,
#map-world.is-landing .map-dot,
#map-world.is-landing .map-leaders { opacity: 0; }
#map-world.is-settling .map-pin-disc {
  animation: pin-reveal 680ms var(--land-delay, 0ms) both;
}
#map-world.is-settling .map-dot,
#map-world.is-settling .map-leaders {
  animation: map-land-fade 400ms 200ms both;
}
@keyframes map-land-fade { from { opacity: 0; } }

#map-world-art {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: fill;
  display: block;
}
#map-world-art[hidden] { display: none; }

/* Stands in until a map image exists: a graticule at 30 degree spacing, which
   is a map of a sort and puts the pins somewhere legible in the meantime. */
#map-world.is-artless {
  background-image:
    repeating-linear-gradient(to right, #ffffff14 0 1px, transparent 1px 8.3333%),
    repeating-linear-gradient(to bottom, #ffffff14 0 1px, transparent 1px 16.6667%);
}

/* The dots and leader lines, under every pin, for the circles the dodge in
   map.js had to move. preserveAspectRatio is none so the viewBox stretches with
   the 2:1 map instead of letterboxing inside it. */
.map-leaders {
  position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none;
}
.map-leaders line {
  stroke: #f3ead7; stroke-opacity: 0.5;
  stroke-width: 1.5; stroke-dasharray: 5 5;
  vector-effect: non-scaling-stroke;
}
.map-dot {
  position: absolute; z-index: 1;
  width: 10px; height: 10px; margin: -5px 0 0 -5px;
  border-radius: 50%;
  background: #f3ead7; opacity: 0.85;
  box-shadow: 0 1px 4px #0008;
  pointer-events: none;
}

.map-pin {
  position: absolute; z-index: 1;
  transform: translate(-50%, -50%);
  padding: 0; background: none; border: none; color: inherit; font: inherit;
  cursor: pointer;
}
.map-pin:hover, .map-pin:focus-visible { z-index: 2; outline: none; }

.map-pin-disc {
  position: relative; display: grid; place-items: center; overflow: hidden;
  width: clamp(96px, 11.5vw, 168px); aspect-ratio: 1;
  border-radius: 50%;
  border: 3px solid #f3ead7;
  box-shadow: 0 8px 26px #0009;
  transition: transform 160ms ease-out, box-shadow 160ms ease-out;
}
.map-pin:hover .map-pin-disc,
.map-pin:focus-visible .map-pin-disc {
  transform: scale(1.05);
  box-shadow: 0 10px 34px #000c, 0 0 0 7px #f3ead733;
}
.map-pin-disc img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.map-pin.is-complete .map-pin-disc { border-color: #ffd166; }

/* A location that has just opened (revealPin in map.js). The circle grows out
   of a single pixel where it stands, runs on to a quarter bigger than itself
   and drops back — a pop rather than a fade, because the place was not there
   and now it is. --reveal-from is one pixel as a fraction of the disc, set from
   JS, since the disc is sized by a clamp(). Held at that size through the delay,
   so the map is on screen a beat before anything arrives on it. */
.map-pin.is-revealing .map-pin-disc {
  animation: pin-reveal 760ms var(--reveal-delay, 450ms) both;
}
@keyframes pin-reveal {
  0%   { transform: scale(var(--reveal-from, 0.01)); animation-timing-function: cubic-bezier(0.2, 0.7, 0.4, 1); }
  62%  { transform: scale(1.25); animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1); }
  100% { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .map-pin.is-revealing .map-pin-disc { animation: pin-reveal-fade 300ms 200ms both; }
  @keyframes pin-reveal-fade { from { opacity: 0; } to { opacity: 1; } }
}

/* The name rides on the picture. A scrim under it rather than a shadow alone:
   these backdrops run from a near-black concert hall to a white-floored band
   room, and text that survives both needs something to sit on. */
.map-pin-label {
  position: relative; z-index: 1;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  width: 100%; padding: 6px 8px;
  background: linear-gradient(#061a1e00, #061a1eb3 22%, #061a1eb3 78%, #061a1e00);
  text-align: center;
}
.map-pin-name {
  font-size: clamp(18px, 1.725vw, 25.5px); font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase; line-height: 1.15;
  text-shadow: 0 2px 6px #000c;
}
.map-stars { font-size: 11px; letter-spacing: 1px; color: #ffd166; white-space: nowrap; }
.map-stars-total { opacity: 0.6; }
.map-pin-label .map-stars { font-size: 16.5px; }

/* ---- The travelling backdrop -------------------------------------------- */

/* The journey, in two parts, both set from JS on every frame of it (see paint
   in map.js) and neither of them a fade. The hole is a clip-path circle that
   starts as the pin's disc and ends outside the window. The backdrop under it
   is the whole window, cover-fitted exactly as the canvas draws it, scaled and
   shifted to be the rectangle that hole needs — which is the circle on the map
   at the start and the window itself at the end, so the picture grows out of
   the pin you pressed and hands over to the canvas as the same pixels.

   Full opacity throughout. No dimming: the wash the canvas puts over a scene
   is for reading notes against, and there are no notes on a location screen. */
#map-zoom {
  position: fixed; inset: 0; z-index: 2;
  will-change: clip-path;
}
#map-zoom[hidden] { display: none; }
#map-zoom-art {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transform-origin: center;
  will-change: transform;
}

/* The circle on the map is drawn with a ring, and the hole is that same circle
   opened out, so it carries the ring with it — sized and placed from JS on
   every frame, and dropped once it is wider than the window and there is no arc
   left on screen. Same border and lift as .map-pin-disc, because it is the same
   edge: were they to differ, the moment the pin is swapped for the hole would
   be a visible change of costume. */
#map-zoom-ring {
  position: fixed; z-index: 2;
  border-radius: 50%;
  border: 3px solid #f3ead7;
  box-shadow: 0 8px 26px #0009;
  pointer-events: none;
}
#map-zoom-ring.is-complete { border-color: #ffd166; }
#map-zoom-ring[hidden] { display: none; }

/* ---- A location's songs -------------------------------------------------- */

/* Where you have arrived, for a screen reader only. On screen the place is the
   backdrop itself, and a name printed over it said the same thing twice. */
#map-location-name {
  position: absolute; width: 1px; height: 1px; margin: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* The way back is the map itself, small: the same cream edge and the same lift
   the circles on it have, so it reads as a piece of the map rather than as a
   control that happens to sit over one. No word on it — a picture of where a
   button goes says it better than "Map" does, and it is the only button here. */
#map-back {
  position: fixed; z-index: 4;
  top: max(14px, env(safe-area-inset-top)); left: max(16px, env(safe-area-inset-left));
  width: clamp(72px, 9vw, 116px); aspect-ratio: 2;
  padding: 0; overflow: hidden;
  background: #2c4a5e;
  border: 2px solid #f3ead7; border-radius: 8px;
  box-shadow: 0 6px 18px #0009;
  cursor: pointer;
  transition: transform 160ms ease-out, box-shadow 160ms ease-out;
}
#map-back:hover, #map-back:focus-visible {
  transform: scale(1.05); outline: none;
  box-shadow: 0 8px 24px #000c, 0 0 0 5px #f3ead733;
}
#map-back-art { width: 100%; height: 100%; object-fit: cover; display: block; }
#map-back[hidden] { display: none; }

#map-music {
  position: fixed; z-index: 4;
  top: max(14px, env(safe-area-inset-top)); right: max(16px, env(safe-area-inset-right));
  width: 48px; height: 48px; padding: 10px;
  color: #f3ead7; background: #2c4a5e;
  border: 2px solid #f3ead7; border-radius: 50%;
  box-shadow: 0 6px 18px #0009;
  cursor: pointer;
  transition: transform 160ms ease-out, box-shadow 160ms ease-out;
}
#map-music:hover, #map-music:focus-visible {
  transform: scale(1.05); outline: none;
  box-shadow: 0 8px 24px #000c, 0 0 0 5px #f3ead733;
}
#map-music svg { width: 100%; height: 100%; display: block; }
#map-music[aria-pressed="true"] .slash { display: none; }
#map-music[aria-pressed="false"] .note { opacity: 0.55; }

/* Practice, along the bottom of the world view, low enough to leave the map
   the map: the warm-up as a button of its own, and beside it the week on a
   panel of the song cards' cream stock, the whole panel the way into the
   calendar. */
#practice-card {
  position: fixed; z-index: 4;
  left: max(16px, env(safe-area-inset-left)); bottom: max(16px, env(safe-area-inset-bottom));
  display: flex; align-items: center; gap: 14px;
}
#map-screen[data-view="location"] #practice-card { display: none; }
.practice-panel {
  display: flex; align-items: center; gap: 16px;
  padding: 10px 14px 10px 14px;
  background: #f4efe1; color: #241f18; font: inherit; text-align: left;
  border: 3px solid #f3ead7;
  outline: 1px solid #00000026; outline-offset: -6px;
  border-radius: 16px;
  box-shadow: 0 12px 32px #0009, 0 3px 8px #0000004d;
}
.practice-heading {
  font-size: 12px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: #6b6152;
}
#practice-calendar button {
  font: inherit; font-size: 15px; font-weight: 600;
  color: #241f18; background: #fffaf0;
  border: 2px solid #241f1840; border-radius: 999px;
  padding: 8px 14px; cursor: pointer;
  box-shadow: 0 2px 0 #241f1826;
}
/* The warm-up: the one big button on the map, navy like every "go" in the
   game, with a flame for the warming. */
#practice-warmup {
  display: flex; align-items: center; gap: 8px;
  font: inherit; font-size: 18px; font-weight: 600; white-space: nowrap;
  color: #f4efe1; background: #2c4a5e;
  border: 3px solid #f3ead7; border-radius: 999px;
  padding: 10px 22px 10px 14px; cursor: pointer;
  box-shadow: 0 12px 32px #0009, 0 4px 0 #1a2d3a;
  transition: transform 140ms ease-out, background 140ms ease-out;
}
#practice-warmup:hover { background: #365a72; transform: translateY(-2px); }
.practice-warmup-icon { width: 30px; height: 30px; flex: none; filter: drop-shadow(0 1px 1px #0006); }

.practice-week-panel { cursor: pointer; transition: transform 140ms ease-out; }
.practice-week-panel:hover { transform: translateY(-2px); }
/* The week as seven rings, Monday first, each day's letter in its middle. The
   faint green track is the day's goal; the bright arc drawn round it from the
   top is how much of it was played. A practice day fills solid, the letter
   turned white on it. Today is ringed in navy, and the days still to come are
   faint. */
.practice-days { display: flex; gap: 6px; margin: 0; padding: 0; list-style: none; }
.practice-days li { position: relative; width: 38px; height: 38px; }
.practice-ring { display: block; width: 100%; height: 100%; overflow: visible; border-radius: 50%; }
.practice-ring-track { fill: none; stroke: #3fb34a; stroke-opacity: 0.22; stroke-width: 4; }
.practice-ring-arc { fill: none; stroke: #2fa043; stroke-width: 4; stroke-linecap: round; }
.practice-ring-disc { fill: url(#sq-day-green); stroke: #1d6b30; stroke-width: 1.5; }
.practice-ring-glint { fill: #ffffff; opacity: 0.35; }
.practice-letter {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-size: 13px; font-weight: 700; line-height: 1; color: #6b6152;
}
.practice-days li.is-met .practice-letter { color: #fff; text-shadow: 0 1px 1px #1d6b30; }
.practice-days li.is-today .practice-ring { box-shadow: 0 0 0 2px #f4efe1, 0 0 0 4px #2c4a5e; }
.practice-days li.is-today:not(.is-met) .practice-letter { color: #2c4a5e; }
.practice-days li.is-future { opacity: 0.7; }
.practice-days li.is-new .practice-ring-disc,
.practice-days li.is-new .practice-ring-glint {
  transform-box: fill-box; transform-origin: center;
  animation: day-fill 650ms cubic-bezier(0.3, 1.6, 0.5, 1) 300ms both;
}
.practice-days li.is-new .practice-letter { animation: day-letter 300ms ease-out 500ms both; }
@keyframes day-fill {
  0% { transform: scale(0); }
  60% { transform: scale(1.18); }
  100% { transform: scale(1); }
}
@keyframes day-letter { from { color: #6b6152; } to { color: #fff; } }
@media (prefers-reduced-motion: reduce) {
  .practice-days li.is-new .practice-ring-disc,
  .practice-days li.is-new .practice-ring-glint,
  .practice-days li.is-new .practice-letter { animation: none; }
}
.practice-week-text { display: flex; flex-direction: column; gap: 2px; white-space: nowrap; }
.practice-week { font-size: 15px; font-weight: 600; }
.practice-streak { font-size: 13px; font-weight: 700; color: #a8791e; }
.practice-streak:empty { display: none; }
.practice-calendar-icon { flex: none; width: 22px; height: 22px; color: #6b6152; }

/* The practice report: the pause menu's card, a month to a page. The month in
   numbers down the left, with the player's name on the line at the top the way
   a practice sheet has one; the days on the right, a week to a row, each row
   ending in that week's total. */
#practice-calendar {
  position: fixed; z-index: 126;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  /* Clear of the notch on a phone on its side, which takes the place of the
     usual 16px margin on both sides. */
  width: min(900px, calc(100vw - max(16px, env(safe-area-inset-left)) - max(16px, env(safe-area-inset-right))));
  max-height: calc(100dvh - 32px); overflow: auto;
  display: grid; grid-template-columns: minmax(200px, 250px) 1fr; column-gap: 28px; align-items: start;
  padding: 22px 26px;
  background: #f4efe1; color: #241f18;
  border: 3px solid #f3ead7;
  outline: 1px solid #00000026; outline-offset: -6px;
  border-radius: 18px;
  box-shadow: 0 18px 50px #000000a6, 0 3px 8px #0000004d;
}
#practice-calendar[hidden] { display: none; }
.report-side { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
#report-heading { margin: 0; font-weight: 400; font-size: 30px; line-height: 1.1; }
#report-name {
  font: inherit; font-size: 17px; font-weight: 600; color: #241f18;
  background: transparent; border: 0; border-bottom: 2px dashed #241f1840; border-radius: 0;
  padding: 4px 2px; outline: none; width: 100%; box-sizing: border-box;
}
#report-name:focus { border-bottom-color: #2c4a5e; }
#report-name::placeholder { color: #241f1866; }
.calendar-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
#practice-calendar-title { margin: 0; font-size: 20px; font-weight: 600; text-align: center; }
#practice-calendar .calendar-head button { width: 38px; height: 38px; padding: 0; font-size: 22px; line-height: 1; flex: none; }
#practice-calendar .calendar-head button:disabled { opacity: 0.3; cursor: default; }
.report-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 0; }
.report-stats > div { background: #fffaf0; border: 1px solid #241f1814; border-radius: 12px; padding: 8px 10px; }
.report-stats dt { font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: #6b6152; }
.report-stats dd { margin: 2px 0 0; font-size: 20px; font-weight: 600; white-space: nowrap; }
.calendar-summary { margin: 0; font-size: 13px; color: #6b6152; }
.report-actions { display: flex; gap: 8px; }
.report-actions button { flex: 1; }
#practice-calendar-close { color: #f4efe1 !important; background: #2c4a5e !important; border-color: #1a2d3a !important; }
#report-status { margin: 0; font-size: 13px; color: #2c4a5e; }
#report-status:empty { display: none; }

/* The month: seven days and the week's total to a row. Each day is the map's
   ring with the date in it and the minutes under it; days that belong to the
   months either side are there for their week's total, faintly. */
.calendar-grid {
  --ring: 40px;
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)) minmax(72px, auto);
  gap: 6px 4px; align-items: center;
}
.calendar-weekday { font-size: 12px; font-weight: 700; letter-spacing: 0.06em; color: #6b6152; text-align: center; }
.calendar-day { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.calendar-ring { position: relative; width: var(--ring); height: var(--ring); }
.calendar-date {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: calc(var(--ring) * 0.34); font-weight: 700; line-height: 1;
}
.calendar-day.is-met .calendar-date { color: #fff; text-shadow: 0 1px 1px #1d6b30; }
.calendar-day.is-today .practice-ring { box-shadow: 0 0 0 2px #f4efe1, 0 0 0 4px #2c4a5e; }
/* Clear of today's navy ring, which stands 4px proud of its circle. */
.calendar-day small { min-height: 1.2em; margin-top: 3px; font-size: 10px; font-weight: 600; color: #6b6152; white-space: nowrap; }
.calendar-day.is-future { opacity: 0.7; }
.calendar-day.is-outside { opacity: 0.65; }
.calendar-week-total {
  align-self: stretch;
  display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 1px;
  background: #fffaf0; border: 1.5px solid #241f1814; border-radius: 10px; padding: 2px 8px;
}
.calendar-week-total strong { font-size: 14px; white-space: nowrap; }
.calendar-week-total small { font-size: 10px; font-weight: 600; color: #6b6152; white-space: nowrap; }
.calendar-week-total.is-reached { background: #fdf0c8; border-color: #e0a93a; }
.calendar-week-total.is-reached small { color: #8a5d12; }
.calendar-week-total.is-future { opacity: 0.7; }

/* A phone on its side. The map is only a few hundred pixels tall there, so the
   warm-up button and the week come down small enough to stay inside the
   margin map.js keeps clear of circles (MAP_MARGIN); the week's heading goes,
   since what it says is plain from what is beside it. The practice report
   keeps its two columns and comes down in size, the rings measured off the
   height so that a six-week month fits without scrolling. */
@media (max-height: 520px) {
  #practice-card { gap: 8px; }
  /* Apple's 44-point minimum for anything tapped, on the smallest phones too. */
  .practice-panel { gap: 10px; padding: 5px 10px; border-radius: 14px; min-height: 44px; box-sizing: border-box; }
  .practice-panel .practice-heading { display: none; }
  #practice-warmup { font-size: 16px; gap: 6px; padding: 8px 18px 8px 12px; border-width: 2px; min-height: 48px; box-sizing: border-box; }
  .practice-warmup-icon { width: 22px; height: 22px; }
  .practice-days { gap: 4px; }
  .practice-days li { width: 32px; height: 32px; }
  .practice-letter { font-size: 11px; }
  .practice-week { font-size: 12px; }
  .practice-streak { font-size: 11px; }
  .practice-calendar-icon { width: 18px; height: 18px; }

  #practice-calendar { grid-template-columns: minmax(180px, 210px) 1fr; column-gap: 16px; padding: 12px 16px; }
  .report-side { gap: 7px; }
  #report-heading { font-size: 20px; }
  #report-name { font-size: 14px; padding: 2px; }
  #practice-calendar-title { font-size: 15px; }
  #practice-calendar .calendar-head button { width: 40px; height: 40px; font-size: 20px; }
  .report-stats { gap: 5px; }
  .report-stats > div { padding: 4px 8px; border-radius: 9px; }
  .report-stats dt { font-size: 9px; }
  .report-stats dd { font-size: 15px; margin: 0; }
  .calendar-summary { font-size: 11px; }
  #practice-calendar button { font-size: 15px; padding: 5px 12px; min-height: 44px; }
  /* Six weeks of rings and their minutes in what is left of the height. */
  .calendar-grid { --ring: clamp(20px, calc((100dvh - 150px) / 6), 36px); gap: 2px 3px; grid-template-columns: repeat(7, minmax(0, 1fr)) minmax(58px, auto); }
  .calendar-weekday { font-size: 10px; }
  .calendar-day { gap: 0; }
  .calendar-day small { font-size: 9px; }
  .calendar-week-total { padding: 1px 6px; border-radius: 8px; }
  .calendar-week-total strong { font-size: 11px; }
  .calendar-week-total small { font-size: 9px; }
}
/* The smallest phones have no room for the week's line beside its rings. */
@media (max-height: 520px) and (max-width: 600px) {
  .practice-week-text { display: none; }
}

#map-songs {
  position: fixed; inset: 0; z-index: 3;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 22px;
  padding: 72px 24px 24px;
  pointer-events: none;
}
#map-songs[hidden] { display: none; }
#map-songs > * { pointer-events: auto; }

/* A chapter is a hand of cards dealt face up, side by side. Every location
   deals the same size of card (layOutCards in map.js sets --card-w and the
   row's width), so a location with more songs wraps onto a second, balanced
   row rather than squeezing its cards or running off the screen. */
.map-song-grid {
  display: flex; flex-wrap: wrap;
  justify-content: center; align-items: center;
  gap: clamp(8px, 1.5vw, 20px);
  max-width: 100%;
  padding: 12px 0 32px;
}

/* Playing cards: portrait, cream, dark ink, ruled faintly across like the paper
   a part is printed on. Dealt face up on a dark photograph, which is what the
   light stock is for — and a card is a thing you pick up, which is what this
   whole screen is asking you to do.
   The width caps at a card's width on a wide window and at a fraction of the
   height on a short one, so five of them are always a row of cards and never a
   row of banners or a stack that runs off the bottom. 5:7 is a playing card. */
.map-song {
  flex: 0 0 var(--card-w, 126px); width: var(--card-w, 126px);
  aspect-ratio: 5 / 7;
  display: flex; flex-direction: column; align-items: center; justify-content: space-between;
  padding: clamp(10px, 1.6vh, 18px) clamp(5px, 0.6vw, 10px);
  border-radius: 10px;
  background-color: #f4efe1;
  border: 1px solid #00000026;
  color: #241f18; font: inherit; text-align: center;
  /* Keep each card in the same shape as its neighbours. */
  overflow: hidden;
  box-shadow: 0 12px 30px #00000073, 0 2px 6px #0000004d;
  cursor: pointer;
  transition: transform 140ms ease-out, box-shadow 140ms ease-out;
}
.map-song:hover:not(:disabled) {
  transform: translateY(-6px);
  box-shadow: 0 20px 40px #0000008c, 0 3px 8px #00000059;
}
/* Not open yet, or not written yet: the same card, printed on grey stock. Not
   a faded copy of a colour one — a card you cannot play is a different card,
   and dimming the whole thing takes the title with it. */
.map-song:disabled {
  cursor: default;
  background-color: #b9b4ab;
  color: #4a463f;
  box-shadow: 0 6px 16px #00000059;
}
.map-song.is-passed { border-color: #a8791e80; }
.map-song.is-recommended { border: 3px solid #2c4a5e; background: #fffaf0; }
.map-continue {
  display: inline-block; flex: none; padding: 5px 10px; border-radius: 999px;
  background: #2c4a5e; color: #fffaf0;
  font: 700 12px/1.2 var(--font-body); letter-spacing: 0;
}
.map-pin-label .map-continue { margin-top: 6px; }
.map-pin.is-recommended .map-pin-disc { border-color: #ffe1a0; }
.map-song:focus-visible { outline: 3px solid #fffaf0; outline-offset: 4px; }
#controls-fields, .practice-week-text, .calendar-day, .calendar-week-total,
.calendar-summary, .report-stats { font-family: var(--font-body); }
.calendar-day, .calendar-week-total { font-variant-numeric: tabular-nums; }
/* For sale: the card is playable stock, and the offer is printed on it as a
   ribbon rather than in the faint status type, since it is the thing to tap. */
.map-song-status.map-song-offer {
  opacity: 1; font-weight: 700; color: #f4efe1;
  background: #2c4a5e; border-radius: 999px; padding: 0.35em 0.9em;
}

/* The card, top to bottom: the title, then the stars along the bottom edge.
   There is no number in the corner — the cards are dealt left to right in the
   order they unlock, which says the same thing without printing it. */
.map-song-title {
  flex: 1 1 0; min-height: 0; min-width: 0; width: 100%;
  display: flex; align-items: center; justify-content: center;
  padding: 6px 0;
  /* Every title uses the same responsive size, regardless of its length. */
  font-size: clamp(18px, 2.1vw, 26px); font-weight: 600; line-height: 1.15;
}
/* At this size a long word can be wider than the card. Break it rather than
   letting it hang over the edge. */
.map-song-title-text { overflow-wrap: break-word; hyphens: auto; }
.map-song-status {
  font-size: clamp(11px, 1.05vw, 14px); letter-spacing: 0.06em; opacity: 0.6;
  text-transform: uppercase;
}
/* Locked reads faster as a shape than as a word — the same reason the stars
   are glyphs and not a fraction. */
.map-song-lock { display: block; opacity: 0.55; }
.map-song-lock svg { width: clamp(20px, 2.4vw, 28px); height: clamp(20px, 2.4vw, 28px); fill: currentColor; }
/* Along the bottom edge, big enough to count at a glance from across the row.
   Earned ones in gold, the rest as empty outlines in the card's own ink — the
   pair has to read as "two of three" without anybody counting twice. */
.map-song .map-stars {
  font-size: clamp(24px, 3vw, 42px); line-height: 1;
  display: inline-flex; gap: 0.06em;
}
/* Drawn stars (starIcon in map.js), sized by the font so every place that
   sets a star size in em keeps working. The earned one sits on a soft shadow
   so it reads as a raised token; the hollow one is pressed into the card. */
.map-star { width: 1em; height: 1em; display: inline-block; vertical-align: -0.14em; }
.map-star-earned { filter: drop-shadow(0 0.06em 0.05em #0000004d); }
.map-star-empty { opacity: 0.85; }
/* Locked: no title, just the padlock in the middle of the card. */
.map-song.is-locked { justify-content: center; }
.map-song.is-locked .map-song-lock svg { width: 42%; height: auto; min-width: 28px; }
.map-song.is-locked .map-song-lock { width: 100%; display: flex; justify-content: center; }

/* Picking one. It comes to the middle of the screen and grows; everything else
   on the location goes at once, because what is being looked at now is the
   choice. Then it is held (map.js times that), and then it goes too, leaving
   the place itself on screen for the game to arrive onto. */
.map-song.is-chosen {
  position: relative; z-index: 2;
  pointer-events: none;
  transition: transform 420ms cubic-bezier(0.2, 0, 0, 1), opacity 320ms ease-in;
}
.map-song.is-chosen.is-read { opacity: 0; }
#map-songs.is-choosing .map-song:not(.is-chosen) {
  opacity: 0; pointer-events: none;
  transition: opacity 200ms ease-out;
}

/* Phones and short windows: the cards come out smaller (layOutCards fits two
   rows into the height), so what is printed on them comes down too. */
@media (max-width: 720px), (max-height: 560px) {
  .map-song { border-radius: 8px; }
  .map-song-title { font-size: clamp(12px, 2.6vw, 16px); }
  .map-pin-label .map-stars { display: none; }
}

/* A phone on its side: one row of cards, as tall as the window allows, that
   scrolls sideways under a finger (layOutCards in map.js, which uses the same
   query). The row runs edge to edge so cards slide out from under the screen's
   sides; the card cut off at the edge is what says there is more. The space
   after the last card is a pseudo-element because Safari drops a scroller's
   end padding, and the auto margins centre a row too short to scroll. */
@media (max-height: 520px) {
  #map-songs { padding-left: 0; padding-right: 0; }
  .map-song-grid {
    --row-gap: 14px;
    width: 100%;
    flex-wrap: nowrap; justify-content: flex-start; gap: var(--row-gap);
    padding-left: max(24px, env(safe-area-inset-left));
    overflow-x: auto; overscroll-behavior-x: contain;
    scroll-snap-type: x proximity; scroll-padding-inline: max(24px, env(safe-area-inset-left));
    scrollbar-width: none;
  }
  .map-song-grid::-webkit-scrollbar { display: none; }
  .map-song-grid::after {
    content: ""; flex: 0 0 calc(max(24px, env(safe-area-inset-right)) - var(--row-gap));
    margin-left: auto; align-self: stretch;
  }
  .map-song-grid > .map-song:first-child { margin-left: auto; }
  /* Type and stars in the same proportion to the card as on an iPad, where
     a 165-point card carries 26-point titles and 41-point stars: measured off
     the card's width (--card-w, which layOutCards sets) instead of the window. */
  .map-song {
    scroll-snap-align: start; border-radius: 10px;
    padding: calc(var(--card-w) * 0.1) calc(var(--card-w) * 0.05);
  }
  .map-song-title { font-size: calc(var(--card-w) * 0.158); }
  .map-song .map-stars { font-size: calc(var(--card-w) * 0.25); }
  .map-song-status { font-size: calc(var(--card-w) * 0.085); }
  .map-song-lock svg { width: calc(var(--card-w) * 0.17); height: calc(var(--card-w) * 0.17); }
  /* Choosing a card: the row stops scrolling so the card can grow past it. */
  .map-song-grid.is-held { overflow: visible; }
}

/* Somebody who has asked for less movement gets the arrival without the
   journey: the travel is scenery, not information. */
@media (prefers-reduced-motion: reduce) {
  .map-pin-disc, .map-song { transition-duration: 1ms; }
}

/* The splash (index.html). Laid out on a 4:3 stage that covers the window the
   way the iPad's launch image covers its screen — scaled to fill, centred, the
   overflow cut off — so the launch image, which is this element captured at
   2732x2048, lines up with it exactly on an iPad. Everything is sized in --u, a
   hundredth of the stage's width, except where a window is so wide or so tall
   that the stage would cut the title or the horn off: there it is held to the
   window instead. */
#splash {
  --stage-w: max(100vw, 133.333vh);
  --stage-h: max(75vw, 100vh);
  --u: calc(min(var(--stage-w), 177.778vh, 125vw) / 100);
  position: fixed; inset: 0; z-index: 1000;
  overflow: hidden;
  background: #0a2150;
  opacity: 1;
  transition: opacity 600ms ease; /* FADE_MS in index.html */
}
#splash.leaving { opacity: 0; pointer-events: none; }

#splash-stage {
  position: absolute; left: 50%; top: 50%;
  width: var(--stage-w); height: var(--stage-h);
  transform: translate(-50%, -50%);
}
#splash-scene {
  display: block; width: 100%; height: 100%;
  object-fit: cover;
}

#splash-content {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  display: flex; flex-direction: column; align-items: center;
}
#splash-title {
  margin: 0;
  font-size: calc(var(--u) * 15);
  font-weight: 400;
  line-height: 1;
  color: #fff;
  white-space: nowrap;
  -webkit-text-stroke: calc(var(--u) * 1.6) #1b2f8f;
  paint-order: stroke fill;
  /* Lit like the scene's neon: a cyan haze round the outline. */
  filter: drop-shadow(0 calc(var(--u) * 0.6) calc(var(--u) * 0.5) rgba(0, 0, 20, 0.55))
    drop-shadow(0 0 calc(var(--u) * 1.4) rgba(80, 210, 255, 0.75));
}
#splash-trombone {
  display: block;
  width: calc(var(--u) * 74);
  height: auto;
  margin-top: calc(var(--u) * 2.5);
  /* Brightened, with a gold halo hugging the brass so it stands off the staves. */
  filter: brightness(1.4) saturate(1.2)
    drop-shadow(0 0 calc(var(--u) * 0.5) rgba(255, 236, 170, 0.95))
    drop-shadow(0 0 calc(var(--u) * 1.6) rgba(255, 205, 110, 0.9))
    drop-shadow(0 0 calc(var(--u) * 4) rgba(255, 180, 70, 0.6));
}

/* --- Celebrations (celebration.js) ---------------------------------------
   Full screen, over the result and the map: a location's picture fading up
   under a shade dark enough to read over. An item turns on the left with its
   name on the right; a new location has its words in the middle. */
#celebration {
  position: fixed; inset: 0; z-index: 128;
  display: grid; grid-template-columns: 1.25fr 1fr; align-items: center;
  padding: max(24px, env(safe-area-inset-top)) max(32px, env(safe-area-inset-right))
    max(24px, env(safe-area-inset-bottom)) max(32px, env(safe-area-inset-left));
  overflow: hidden; color: #fff;
  /* The location's palette, until its picture arrives or if it has none. */
  background: linear-gradient(var(--celebration-sky, #22304a), var(--celebration-ground, #4b3b2a));
}
#celebration[hidden] { display: none; }
#celebration .celebration-art {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0; scale: 1.08; transition: opacity 1400ms ease-out, scale 9s ease-out;
}
#celebration .celebration-art.loaded { opacity: 1; scale: 1; }
#celebration::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: radial-gradient(ellipse at center, #000a 0, #0006 50%, #000a 100%);
}
/* For an item, a little lighter behind the piece so it stands out. */
#celebration[data-variant="item"]::before {
  background: radial-gradient(circle at 25% 50%, #0003 0, #0007 40%, #000b 100%);
}

/* Over everything, the words included, and out of the way of every tap. */
#celebration .celebration-confetti {
  position: absolute; inset: 0; z-index: 3; width: 100%; height: 100%;
  pointer-events: none;
}
#celebration .celebration-confetti[hidden] { display: none; }

#celebration-stage { position: relative; z-index: 2; grid-column: 1; grid-row: 1; align-self: stretch; min-height: 0; }
#celebration-stage canvas {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; transition: opacity 600ms ease-out;
  /* The same gold halo as the splash horn, so the piece stands off the picture. */
  filter: brightness(1.15)
    drop-shadow(0 0 0.4vmin rgba(255, 236, 170, 0.95))
    drop-shadow(0 0 1.2vmin rgba(255, 205, 110, 0.9))
    drop-shadow(0 0 3vmin rgba(255, 180, 70, 0.6));
}
#celebration-stage.ready canvas { opacity: 1; }

#celebration .celebration-text {
  position: relative; z-index: 2; grid-column: 2; grid-row: 1;
  display: flex; flex-direction: column; align-items: flex-start;
}
#celebration-title {
  margin: 0; font-size: clamp(26px, 4vw, 44px); line-height: 1; color: #f3ead7;
  text-shadow: 0 2px 0 #000, 0 2px 10px #000;
}
#celebration-name {
  margin: 10px 0 0; font-size: clamp(44px, 8vw, 96px); line-height: 1; font-weight: normal;
  color: #fff; text-shadow: 0 3px 0 #000, 0 4px 18px #000c;
}
#celebration .celebration-ribbon {
  margin: 12px 0 0; display: flex; gap: 18px; align-items: baseline;
  font-size: clamp(18px, 2.4vw, 26px); color: #d9a441; text-shadow: 0 2px 6px #000;
}
#celebration .celebration-ribbon[hidden] { display: none; }
#celebration .celebration-count { color: #c9c9d6; }
#celebration .celebration-description {
  margin: 18px 0 0; max-width: 30em; font-size: clamp(18px, 2.5vw, 28px); line-height: 1.3;
  text-shadow: 0 2px 0 #000, 0 2px 10px #000;
}
#celebration .celebration-description[hidden] { display: none; }
/* The app's own buttons, as on the result. */
#celebration .celebration-actions { display: flex; gap: 10px; margin-top: 28px; }
#celebration .celebration-actions button {
  font: inherit; font-size: 18px; color: #eee; background: #30303d; border: 1px solid #686879;
  border-radius: 6px; padding: 10px 22px; min-width: 120px; cursor: pointer;
}
#celebration .celebration-actions button:hover { background: #3c3c4b; }
#celebration .celebration-actions button[hidden] { display: none; }
#celebration-select { background: #3a5fa8 !important; border-color: #5b82d0 !important; }
#celebration-select:hover { background: #4a72c4 !important; }

/* The words come in after the piece has begun to arrive. */
#celebration .celebration-text > * { opacity: 0; translate: 0 14px; transition: opacity 450ms ease-out, translate 450ms ease-out; }
#celebration.shown .celebration-text > * { opacity: 1; translate: 0 0; }
#celebration.shown #celebration-title { transition-delay: 250ms; }
#celebration.shown #celebration-name { transition-delay: 450ms; }
#celebration.shown .celebration-ribbon,
#celebration.shown .celebration-description { transition-delay: 650ms; }
#celebration.shown .celebration-actions { transition-delay: 900ms; }

#celebration[data-variant="location"] { grid-template-columns: 1fr; justify-items: center; text-align: center; }
#celebration[data-variant="location"] #celebration-stage { display: none; }
#celebration[data-variant="location"] .celebration-text { grid-column: 1; align-items: center; }

@media (prefers-reduced-motion: reduce) {
  #celebration .celebration-art { scale: 1; transition: opacity 600ms; }
  #celebration .celebration-text > * { translate: none; }
}
/* Narrow windows (the web version only; the app is landscape): stacked. */
@media (max-width: 640px) {
  #celebration { grid-template-columns: 1fr; grid-template-rows: 40% auto; }
  #celebration .celebration-text { grid-column: 1; grid-row: 2; align-items: center; text-align: center; }
}

/* Miniature stage is selected; URL variants remain available for comparison. */
html[data-art-direction="storybook"] {
  --scene-saturate: 0.92; --scene-brightness: 1.15;
  --art-paper: #eee3c9; --art-ink: #344c4b; --art-edge: #b69b6a;
}
html[data-art-direction="stage"] {
  --scene-saturate: 1.02; --scene-brightness: 1.18;
  --art-paper: #26313e; --art-ink: #f4e7cb; --art-edge: #bd995c;
}
html[data-art-direction="storybook"] .map-song,
html[data-art-direction="stage"] .map-song {
  background: var(--art-paper); color: var(--art-ink);
  border: 2px solid var(--art-edge); box-shadow: 0 4px 0 #17252a55;
  border-radius: 8px;
}
html[data-art-direction="storybook"] .map-song:disabled { background: #c8c1ad; color: #666d60; border-color: #a39b83; }
html[data-art-direction="stage"] .map-song { background: #f4efe1; color: #26313e; }
html[data-art-direction="stage"] .map-song:disabled { background: #c8c1ad; color: #666459; border-color: #a39b83; }
html[data-art-direction="storybook"] .map-song.is-recommended,
html[data-art-direction="stage"] .map-song.is-recommended { border: 3px solid #e8bc65; }
html[data-art-direction="storybook"] .map-continue { background: #344c4b; color: #fff2d6; }
html[data-art-direction="stage"] .map-continue { background: #e8bc65; color: #26313e; }
html[data-art-direction="storybook"] :is(#pause-button, #map-back, #map-music),
html[data-art-direction="stage"] :is(#pause-button, #map-back, #map-music) {
  background: var(--art-paper); color: var(--art-ink);
  border: 2px solid var(--art-edge); box-shadow: 0 3px 0 #17252a55;
}
html[data-art-direction="storybook"] .map-star-earned { filter: none; }
html[data-art-direction="storybook"] #sq-star-earned polygon { fill: #e1ad49; stroke: #b88432; }
html[data-art-direction="storybook"] #sq-star-earned ellipse { opacity: 0; }
html[data-art-direction="stage"] .map-star-earned { filter: saturate(0.7); }
html[data-art-direction="storybook"] #song-title { color: #fff2d6; text-shadow: 0 2px 0 #263b3b; }
html[data-art-direction="stage"] #song-title { color: #f6dfb5; text-shadow: 0 2px 3px #101824; }

/* Preserve skin and cloth values while the backdrop uses its own softer grade. */
html[data-art-direction="storybook"] #character-canvas { filter: saturate(0.86) brightness(1.4); }
html[data-art-direction="stage"] #character-canvas { filter: saturate(0.95) brightness(1.38); }
