@import url("https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=DM+Sans:wght@400;500;600&display=swap");

:root,
:root[data-theme="parchment"] {
  --bg: #2a2520;
  --bg-grad-1: rgba(168, 120, 72, 0.12);
  --bg-grad-2: rgba(122, 158, 126, 0.08);
  --card-bg: #f4ede4;
  --card-border: #d9cfc2;
  --card-shadow: rgba(0, 0, 0, 0.4);
  --notation-bg: #ece5da;
  --notation-border: #d4cabb;
  --status-bg: #e6ddd1;
  --status-border: #d4cabb;
  --status-label: #8a7e70;
  --status-value: #3a3530;
  --staff-color: #6b5e50;
  --note-past: #7a9e7e;
  --note-current: #b86a3e;
  --note-future: rgba(107, 94, 80, 0.65);
  --text: #3a3530;
  --pop-glow: rgba(122, 158, 126, 0.3);
  --toggle-bg: #e6ddd1;
  --toggle-border: #d4cabb;
  --toggle-text: #6b5e50;
}

:root[data-theme="arctic"] {
  --bg: #e8eef4;
  --bg-grad-1: rgba(100, 160, 220, 0.1);
  --bg-grad-2: rgba(140, 180, 220, 0.08);
  --card-bg: #f7fafd;
  --card-border: #d0dce8;
  --card-shadow: rgba(0, 0, 0, 0.12);
  --notation-bg: #edf3f9;
  --notation-border: #d0dce8;
  --status-bg: #e4edf6;
  --status-border: #d0dce8;
  --status-label: #6e8499;
  --status-value: #2c3e50;
  --staff-color: #5a7a94;
  --note-past: #6ea8d4;
  --note-current: #d45d5d;
  --note-future: rgba(90, 122, 148, 0.65);
  --text: #2c3e50;
  --pop-glow: rgba(110, 168, 212, 0.3);
  --toggle-bg: #e4edf6;
  --toggle-border: #d0dce8;
  --toggle-text: #5a7a94;
}

:root[data-theme="espresso"] {
  --bg: #15100b;
  --bg-grad-1: rgba(205, 142, 78, 0.1);
  --bg-grad-2: rgba(130, 158, 120, 0.05);
  --card-bg: #271d14;
  --card-border: #3e3125;
  --card-shadow: rgba(0, 0, 0, 0.7);
  --notation-bg: #1d150e;
  --notation-border: #3e3125;
  --status-bg: #322619;
  --status-border: #3e3125;
  --status-label: #ad9680;
  --status-value: #f4e9d7;
  --staff-color: #bca387;
  --note-past: #9ec18d;
  --note-current: #ec9a44;
  --note-future: rgba(188, 163, 135, 0.75);
  --text: #f4e9d7;
  --pop-glow: rgba(236, 154, 68, 0.24);
  --toggle-bg: #322619;
  --toggle-border: #3e3125;
  --toggle-text: #bca387;
}

:root[data-theme="midnight"] {
  --bg: #0f1524;
  --bg-grad-1: rgba(60, 100, 200, 0.08);
  --bg-grad-2: rgba(100, 60, 180, 0.05);
  --card-bg: #1a2238;
  --card-border: #2a3654;
  --card-shadow: rgba(0, 0, 0, 0.7);
  --notation-bg: #151d30;
  --notation-border: #2a3654;
  --status-bg: #1e2a44;
  --status-border: #2a3654;
  --status-label: #7088b0;
  --status-value: #d0daf0;
  --staff-color: #8098c0;
  --note-past: #5a8ad4;
  --note-current: #e0884a;
  --note-future: rgba(112, 136, 176, 0.75);
  --text: #d0daf0;
  --pop-glow: rgba(90, 138, 212, 0.2);
  --toggle-bg: #1e2a44;
  --toggle-border: #2a3654;
  --toggle-text: #7088b0;
}

:root[data-theme="storm"] {
  --bg: #2e3440;
  --bg-grad-1: rgba(136, 192, 208, 0.06);
  --bg-grad-2: rgba(163, 190, 140, 0.04);
  --card-bg: #3b4252;
  --card-border: #4c566a;
  --card-shadow: rgba(0, 0, 0, 0.5);
  --notation-bg: #353b49;
  --notation-border: #4c566a;
  --status-bg: #434c5e;
  --status-border: #4c566a;
  --status-label: #8a96aa;
  --status-value: #eceff4;
  --staff-color: #a0aec0;
  --note-past: #88c0d0;
  --note-current: #bf616a;
  --note-future: rgba(160, 174, 192, 0.75);
  --text: #eceff4;
  --pop-glow: rgba(136, 192, 208, 0.2);
  --toggle-bg: #434c5e;
  --toggle-border: #4c566a;
  --toggle-text: #8a96aa;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: "DM Sans", "Segoe UI", system-ui, sans-serif;
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  transition: background 0.3s ease, color 0.3s ease;
}

/* Animated ambient background: layered, blended theme-colored glows that drift
   behind the card and parallax with the pointer, plus a few rising note glyphs.
   Colors come from the active theme's variables, so it re-themes itself. Only
   transform/opacity animate (GPU compositor); mix-blend-mode adds the richness. */
.bg,
.notes-layer {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  /* Pointer parallax, fed from JS via --px/--py. The two layers move at
     different rates and opposite directions to fake depth. The transform also
     isolates the blob blend group into its own composited layer. */
  transition: transform 0.6s ease-out;
}

.bg {
  transform: translate(calc(var(--px, 0) * 22px), calc(var(--py, 0) * 22px));
}

.notes-layer {
  transform: translate(calc(var(--px, 0) * -14px), calc(var(--py, 0) * -14px));
}

.blob {
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
  mix-blend-mode: var(--blob-blend, screen);
  transition: background 0.3s ease;
  will-change: transform;
}

/* Light themes read best with multiplied glows; dark themes glow additively. */
:root {
  --blob-blend: multiply;
}

:root[data-theme="espresso"],
:root[data-theme="midnight"],
:root[data-theme="storm"] {
  --blob-blend: screen;
}

.blob-1 {
  top: -20%;
  left: -12%;
  width: 60vw;
  height: 60vw;
  background: radial-gradient(circle at 50% 50%, var(--bg-grad-1), transparent 68%);
  animation: drift-1 28s ease-in-out infinite;
}

.blob-2 {
  right: -14%;
  bottom: -22%;
  width: 55vw;
  height: 55vw;
  background: radial-gradient(circle at 50% 50%, var(--bg-grad-2), transparent 68%);
  animation: drift-2 36s ease-in-out infinite;
}

.blob-3 {
  top: 22%;
  left: 30%;
  width: 42vw;
  height: 42vw;
  filter: blur(55px);
  background: radial-gradient(circle at 50% 50%, var(--pop-glow), transparent 70%);
  animation: drift-3 32s ease-in-out infinite;
}

.blob-4 {
  top: -14%;
  right: -10%;
  width: 48vw;
  height: 48vw;
  filter: blur(85px);
  background: radial-gradient(circle at 50% 50%, var(--bg-grad-2), transparent 70%);
  animation: drift-4 44s ease-in-out infinite;
}

.blob-5 {
  bottom: -16%;
  left: -10%;
  width: 50vw;
  height: 50vw;
  filter: blur(85px);
  background: radial-gradient(circle at 50% 50%, var(--bg-grad-1), transparent 70%);
  animation: drift-5 40s ease-in-out infinite;
}

@keyframes drift-1 {
  0%,
  100% {
    transform: translate(0, 0) scale(1);
  }
  50% {
    transform: translate(18%, 12%) scale(1.18);
  }
}

@keyframes drift-2 {
  0%,
  100% {
    transform: translate(0, 0) scale(1.1);
  }
  50% {
    transform: translate(-16%, -10%) scale(0.92);
  }
}

@keyframes drift-3 {
  0%,
  100% {
    transform: translate(0, 0) scale(1);
    opacity: 0.85;
  }
  33% {
    transform: translate(14%, -12%) scale(1.22);
    opacity: 1;
  }
  66% {
    transform: translate(-12%, 8%) scale(0.9);
    opacity: 0.7;
  }
}

@keyframes drift-4 {
  0%,
  100% {
    transform: translate(0, 0) scale(1.05);
  }
  50% {
    transform: translate(-14%, 14%) scale(0.9);
  }
}

@keyframes drift-5 {
  0%,
  100% {
    transform: translate(0, 0) scale(0.95);
  }
  50% {
    transform: translate(12%, -12%) scale(1.15);
  }
}

/* Rising musical-note particles (built in JS with randomized x/size/speed). */
.note-particle {
  position: absolute;
  bottom: -12%;
  left: var(--x);
  font-size: var(--size);
  line-height: 1;
  color: var(--note-current);
  opacity: 0;
  transition: color 0.3s ease;
  will-change: transform, opacity;
  animation: rise var(--dur) linear var(--delay) infinite;
}

@keyframes rise {
  0% {
    transform: translateY(0) rotate(0deg);
    opacity: 0;
  }
  12% {
    opacity: var(--peak, 0.16);
  }
  88% {
    opacity: var(--peak, 0.16);
  }
  100% {
    transform: translateY(-118vh) rotate(var(--spin, 40deg));
    opacity: 0;
  }
}

.app {
  width: min(720px, 95vw);
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  border-radius: 16px;
  padding: 12px 32px 32px;
  box-shadow:
    0 24px 64px var(--card-shadow),
    0 8px 20px rgba(0, 0, 0, 0.2);
  transition: background 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
}

.toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

/* The way out of every screen but the menu, in the same place on each. Wears
   the gear's shape so the two read as one bar rather than two controls that
   happen to share a row. */
.toolbar-back {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 36px;
  padding: 0 14px;
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 500;
  background: var(--toggle-bg);
  border: 1px solid var(--toggle-border);
  border-radius: 8px;
  color: var(--toggle-text);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.toolbar-back:hover {
  color: var(--note-current);
  border-color: var(--note-current);
}

.toolbar-back:active {
  transform: translateY(1px);
}

.toolbar-back-arrow {
  font-size: 1rem;
  line-height: 1;
}

/* Clef and theme are chosen once and then forgotten, so they live behind a
   single gear button instead of taking up screen room for the whole game. */
.app-settings {
  position: relative;
  /* Pushed right by a margin rather than by the toolbar's justification: the
     back button is hidden on the menu screen, and a lone flex child would sit
     left there — the gear belongs in the same corner on every screen. */
  margin-left: auto;
}

.app-settings-toggle {
  background: var(--toggle-bg);
  border: 1px solid var(--toggle-border);
  color: var(--toggle-text);
  height: 36px;
  padding: 0 14px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 0.82rem;
  font-family: inherit;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 8px;
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
}

.app-settings-toggle:hover {
  transform: translateY(-1px);
}

.app-settings-toggle:active {
  transform: translateY(0);
}

.app-settings-toggle .gear {
  font-size: 1rem;
  line-height: 1;
}

.app-settings-toggle[aria-expanded="true"] .gear {
  transform: rotate(45deg);
}

.app-settings-toggle .gear {
  transition: transform 0.2s ease;
}

/* A centered dialog rather than a dropdown: the note-range rows inside need a
   staff's worth of width, which no menu hanging off the toolbar would have. */
.app-settings-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  z-index: 90;
}

.app-settings-menu {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(700px, calc(100vw - 24px));
  max-height: calc(100vh - 24px);
  overflow-y: auto;
  display: grid;
  /* minmax(0, …) rather than the default auto track: the note-range staves are
     drawn to a pixel width, and an auto track would widen to fit one instead of
     holding them to the dialog. */
  grid-template-columns: minmax(0, 1fr);
  gap: 18px;
  justify-items: center;
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  border-radius: 14px;
  padding: 18px;
  box-shadow: 0 24px 64px var(--card-shadow);
  z-index: 100;
  transition: background 0.2s ease, border-color 0.2s ease;
}

/* These set a display of their own, which outranks the `hidden` attribute's —
   so hiding them has to be spelled out. */
.toolbar-back[hidden],
.app-settings-backdrop[hidden],
.app-settings-menu[hidden] {
  display: none;
}

/* Kept in view while the dialog scrolls: the note-range rows below make it
   tall enough that a footer would need scrolling to reach. It stands in for the
   toolbar's back button, which the dialog covers, so it sits where that button
   is rather than opposite it. */
.app-settings-bar {
  position: sticky;
  top: 0;
  z-index: 2;
  justify-self: stretch;
  margin: -18px -18px 0;
  padding: 14px 18px;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  background: var(--card-bg);
  border-radius: 14px 14px 0 0;
  transition: background 0.2s ease;
}

.app-settings-cols {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 18px;
  align-items: start;
  width: 100%;
}

.app-settings-group {
  width: 100%;
}

/* Shaped like the back button it stands in for, arrow and all. */
.app-settings-done {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 36px;
  padding: 0 14px;
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 500;
  background: var(--toggle-bg);
  border: 1px solid var(--toggle-border);
  border-radius: 8px;
  color: var(--toggle-text);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.app-settings-done:hover {
  color: var(--note-current);
  border-color: var(--note-current);
}

.app-settings-done:active {
  transform: translateY(1px);
}

.theme-menu {
  display: block;
}

/* The light/dark grouping is a property of the themes on offer, not of the
   theme in use, so the two boxes follow the active palette like every other
   panel; only the headings say which group is which. */
.theme-menu-section {
  border-radius: 8px;
  padding: 4px;
  background: var(--notation-bg);
  border: 1px solid var(--notation-border);
  transition: background 0.2s ease, border-color 0.2s ease;
}

.theme-menu-section + .theme-menu-section {
  margin-top: 4px;
}

.theme-menu-group {
  padding: 4px 8px 2px;
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--status-label);
}

.theme-menu-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border: none;
  background: none;
  width: 100%;
  border-radius: 6px;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.82rem;
  color: var(--text);
  transition: background 0.15s ease;
}

.theme-menu-item:hover,
.theme-menu-item.active {
  background: var(--status-bg);
}

.theme-menu-item.active {
  font-weight: 600;
}

.theme-menu-item .theme-chip {
  display: block;
  flex-shrink: 0;
  line-height: 0;
}

/* The clef and language menus are short flat lists, so they skip the theme
   menu's grouped boxes. They share every rule below; only the leading glyph
   differs. */
.clef-menu,
.lang-menu {
  display: block;
}

.clef-menu-item,
.lang-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border: none;
  background: none;
  width: 100%;
  border-radius: 6px;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.82rem;
  color: var(--text);
  transition: background 0.15s ease;
}

.clef-menu-item:hover,
.lang-menu-item:hover {
  background: var(--toggle-bg);
}

.clef-menu-item.active,
.lang-menu-item.active {
  font-weight: 600;
  background: var(--toggle-bg);
}

.clef-menu-item .clef-glyph {
  font-size: 1.4rem;
  line-height: 1;
  width: 26px;
  text-align: center;
}

.lang-menu-item .lang-code {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  line-height: 1;
  width: 26px;
  text-align: center;
  color: var(--status-label);
}

.lang-menu-item.active .lang-code {
  color: var(--note-current);
}

.clef-empty-hint {
  margin: 10px 0 0;
  text-align: center;
  font-size: 0.85rem;
  color: var(--note-future);
}

.screen[hidden] {
  display: none;
}

.game {
  display: grid;
  gap: 24px;
  justify-items: center;
}

.progress {
  font-family: "DM Serif Display", Georgia, serif;
  font-size: 1.1rem;
  letter-spacing: 0.02em;
  color: var(--status-value);
}

.menu-screen,
.chart-screen,
.setup-screen,
.cards-screen,
.results-screen {
  display: grid;
  gap: 20px;
  justify-items: center;
  text-align: center;
  padding: 24px 8px 16px;
}

.screen-title {
  margin: 0;
  font-family: "DM Serif Display", Georgia, serif;
  font-size: 2rem;
  color: var(--status-value);
}

.setup-group {
  display: grid;
  gap: 10px;
  width: 100%;
  justify-items: center;
}

/* --- Note range settings --- */

.settings-panel {
  display: grid;
  gap: 10px;
  justify-items: center;
  width: 100%;
  /* No headings separate the sections any more, so the staves need a little
     extra air above them to read as their own block. */
  margin-top: 10px;
}

.settings-note {
  margin: 0;
  font-size: 0.78rem;
  color: var(--status-label);
}

.range-rows {
  display: grid;
  width: 100%;
  min-width: 0;
}

/* The VexFlow staff fills the panel; the band and the hit areas are absolutely
   positioned over it, the hit areas one per note column. */
.range-staff {
  position: relative;
  width: 100%;
  line-height: 0;
  /* The whole staff is a drag surface, but only sideways: a vertical swipe
     that starts here still scrolls the dialog, which is taller than a phone
     screen. Dragging is handled in script.js. */
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}

/* The range as a band across the staff. The in-range noteheads carry the
   accent colour too, but at phone widths they are a few pixels wide — the band
   is what actually shows where the range ends, and what the player drags. */
.range-band {
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: 10px;
  background: color-mix(in srgb, var(--note-current) 10%, transparent);
  border-left: 2px solid var(--note-current);
  border-right: 2px solid var(--note-current);
  /* Presses belong to the staff underneath, which handles the drag. */
  pointer-events: none;
  transition: left 0.08s ease, width 0.08s ease;
}

/* A grip on each edge, so the band reads as something to take hold of. */
.range-band::before,
.range-band::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 6px;
  height: 34px;
  margin-top: -17px;
  border-radius: 3px;
  background: var(--note-current);
}

.range-band::before {
  left: -4px;
}

.range-band::after {
  right: -4px;
}

/* The band's whole edge again, dimmed, at the note the pointer is over: it
   shows which end a press would take hold of and where that end would land, in
   the one shape the band already speaks in — the line up the full height of the
   staff, the curve away at either end, and the grip across the middle. It is
   one end of a band rather than a band, so it carries a border down one side
   only and the radius on that side's two corners; the width is that radius, the
   room the curve needs, and is mirrored in script.js, which lines the ghost's
   bordered side up with where the band's edge would be — keep the two in step.
   Hidden by default and only ever displayed inside the hover query below: a
   finger has nowhere to hover, and a preview lit by a tap would only be left
   behind under it. */
.range-ghost {
  display: none;
  position: absolute;
  top: 0;
  bottom: 0;
  width: 10px;
  pointer-events: none;
  transition: left 0.08s ease;
}

.range-ghost[data-edge="low"] {
  border-left: 2px solid var(--note-future);
  border-top-left-radius: 10px;
  border-bottom-left-radius: 10px;
}

.range-ghost[data-edge="high"] {
  border-right: 2px solid var(--note-future);
  border-top-right-radius: 10px;
  border-bottom-right-radius: 10px;
}

/* The grip, hung off the bordered side exactly as the band hangs its own. */
.range-ghost::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 6px;
  height: 34px;
  margin-top: -17px;
  border-radius: 3px;
  background: var(--note-future);
}

.range-ghost[data-edge="low"]::after {
  left: -4px;
}

.range-ghost[data-edge="high"]::after {
  right: -4px;
}

@media (hover: hover) and (pointer: fine) {
  .range-ghost:not([hidden]) {
    display: block;
  }
}

/* The pool's note names under the staff, one centred on each notehead. They
   are what makes the range readable without naming the notes off the staff
   first — which is the skill the game is for, not a toll to pay in settings. */
.range-letters {
  position: relative;
  height: 16px;
  margin-top: 2px;
  /* The staff above owns every press; a letter is a label, not a target. */
  pointer-events: none;
}

.range-letter {
  position: absolute;
  top: 0;
  /* Centred on its notehead's x, which is what left is set to. */
  transform: translateX(-50%);
  font-size: 0.7rem;
  line-height: 1;
  color: var(--note-future);
  transition: color 0.15s ease;
}

.range-letter.lit {
  color: var(--note-current);
  font-weight: 600;
}

.range-hit {
  position: absolute;
  top: 0;
  bottom: 0;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: transparent;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* No fill of its own on hover or on a press: the band shows where the range
   is going and the ghost grip above shows where a press would take it, so a
   lit column would be a third thing saying the same. What is left is the focus
   ring, which a keyboard still needs to follow its own place on the staff. */
.range-hit:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--note-current) 55%, transparent);
  outline-offset: -2px;
}

/* --- Main menu --- */
/* The start screen: one entry per thing the app can do, each naming itself and
   saying in a line what it is for. Two columns where there is room, one where
   there is not. */

/* One column per subject: notes, keys, intervals. Each column is a group of
   its own rather than a run of rows, so the pairing reads down the column
   instead of across. */
.main-menu {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  /* One row per entry in the longest column, headings included — the groups
     are subgrids of these rows, so an entry is as tall as the tallest entry
     beside it and the three columns stay in step. Each column sized itself
     before, which held only as long as every description wrapped to the same
     number of lines: Swedish "Alla tonarter på en gång." takes two where the
     notes column's takes one, and everything below it fell out of line. Keep
     the count at the longest column's entries plus its heading. */
  grid-template-rows: repeat(4, auto);
  column-gap: 12px;
  /* Matches .menu-group's own gap, which as a subgrid overrides this one for
     the rows it spans; the two are the same number so the seam never shows. */
  row-gap: 10px;
  width: 100%;
}

.menu-group {
  display: grid;
  /* Every row of .main-menu, taking their heights from it rather than from
     this column's own entries. A group with fewer entries — intervals has
     one — simply leaves the rows past its last one empty. */
  grid-row: 1 / -1;
  grid-template-rows: subgrid;
  gap: 10px;
  align-content: start;
}

/* Three columns inside the 720px card leave each about 210px — close to what
   two of them came to on the narrowest screen that still showed two, so the
   entries read the same. Below that they stack, headings and all. */
@media (max-width: 760px) {
  .main-menu {
    grid-template-columns: 1fr;
    /* Stacked, there is nothing to line up with: each group is a plain grid
       again, sizing its own rows, and the gap goes back to the one between
       groups rather than the one between entries. */
    grid-template-rows: none;
    row-gap: 12px;
  }

  .menu-group {
    grid-row: auto;
    grid-template-rows: none;
  }
}

.menu-group-heading {
  margin: 0;
  padding: 0 2px;
  font-family: inherit;
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--status-label);
  text-align: left;
}

.menu-item {
  display: flex;
  align-items: center;
  gap: 14px;
  font: inherit;
  /* The screen centres its text; an entry reads as a row, so it undoes that. */
  text-align: left;
  padding: 16px 18px;
  background: var(--toggle-bg);
  border: 1px solid var(--toggle-border);
  border-radius: 14px;
  color: var(--status-value);
  cursor: pointer;
  transition: transform 0.12s ease, border-color 0.15s ease, color 0.15s ease;
}

.menu-item:hover:not(:disabled) {
  transform: translateY(-2px);
  border-color: var(--note-current);
}

.menu-item:hover:not(:disabled) .menu-icon {
  color: var(--note-current);
}

.menu-item:active:not(:disabled) {
  transform: translateY(0);
}

.menu-item:focus-visible {
  outline: 2px solid var(--note-current);
  outline-offset: 2px;
}

.menu-icon {
  flex: none;
  width: 2rem;
  text-align: center;
  font-size: 1.9rem;
  line-height: 1;
  color: var(--toggle-text);
  transition: color 0.15s ease;
}

.menu-text {
  display: grid;
  gap: 3px;
  min-width: 0;
}

.menu-name {
  font-family: "DM Serif Display", Georgia, serif;
  font-size: 1.15rem;
  line-height: 1.15;
}

.menu-note {
  font-size: 0.76rem;
  line-height: 1.3;
  color: var(--status-label);
}

/* Not built yet. It stays on the menu rather than being hidden, so the shape
   of the app is visible before the game behind it exists — dimmed, unclickable
   and wearing a badge that says as much. */
.menu-item.locked {
  cursor: default;
  opacity: 0.55;
}

.menu-badge {
  flex: none;
  margin-left: auto;
  align-self: center;
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 3px 7px;
  border-radius: 999px;
  background: var(--note-current);
  color: #fff;
}

.note-count-options {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 10px;
  width: 100%;
}

.note-count-btn {
  position: relative;
  font-family: "DM Serif Display", Georgia, serif;
  font-size: 1.15rem;
  background: var(--toggle-bg);
  border: 1px solid var(--toggle-border);
  color: var(--status-value);
  padding: 14px 0;
  border-radius: 10px;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.12s ease, border-color 0.15s ease, color 0.15s ease;
}

/* Already played to the end. The tick is what carries the meaning — the colour
   only backs it up, so the two states stay tellable apart without it. */
.note-count-btn.played {
  border-color: color-mix(in srgb, var(--note-past) 70%, transparent);
  color: var(--note-past);
}

.note-count-mark {
  position: absolute;
  top: 3px;
  right: 6px;
  font-family: inherit;
  font-size: 0.7rem;
  line-height: 1;
  color: var(--note-past);
}

.note-count-btn:hover {
  transform: translateY(-2px);
  border-color: var(--note-current);
  color: var(--note-current);
}

.note-count-btn:active {
  transform: translateY(0);
}

/* --- Note cards --- */
/* One card at a time, big enough to read across a room: the note on a staff on
   the front, its name on the back, an arrow either side to leaf through the
   deck. Each card's staff is an SVG with a viewBox and no fixed size, so it
   scales to fill whatever the card is given. */

.card-deck {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  width: 100%;
  /* The flip reads as a card turning over rather than squashing sideways. */
  perspective: 1200px;
}

.note-card-slot {
  width: min(340px, 72vw);
}

.note-card {
  font: inherit;
  display: block;
  width: 100%;
  padding: 0;
  border: none;
  background: none;
  aspect-ratio: 1 / 1;
  cursor: pointer;
  border-radius: 18px;
  -webkit-tap-highlight-color: transparent;
}

.note-card-inner {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transition: transform 0.5s cubic-bezier(0.22, 0.7, 0.28, 1);
}

.note-card.flipped .note-card-inner {
  transform: rotateY(180deg);
}

.note-card-face {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 18px;
  overflow: hidden;
  /* Without this the far face shows through the near one mid-flip. */
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  box-shadow: 0 10px 24px color-mix(in srgb, var(--card-shadow) 45%, transparent);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

/* The front is a scrap of the same paper the staff is drawn on elsewhere, so
   a card reads as notation rather than as a button with a picture on it. */
.note-card-front {
  background: var(--notation-bg);
  border: 1px solid var(--notation-border);
  padding: 18px 14px;
}

.note-card-front svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: 100%;
}

/* The back is the answer, so it takes the accent colour the current note wears
   on the staff — the card lights up as it turns. */
.note-card-back {
  transform: rotateY(180deg);
  color: #fff;
  border: 1px solid transparent;
  background: linear-gradient(
    150deg,
    color-mix(in srgb, var(--note-current) 88%, #fff),
    color-mix(in srgb, var(--note-current) 82%, #000)
  );
}

.note-card-name {
  font-family: "DM Serif Display", Georgia, serif;
  font-size: clamp(3rem, 14vw, 5rem);
  line-height: 1;
  letter-spacing: 0.01em;
}

.note-card:hover .note-card-front {
  border-color: var(--note-current);
}

.note-card:hover .note-card-face {
  box-shadow: 0 14px 30px color-mix(in srgb, var(--card-shadow) 60%, transparent);
}

.note-card:focus-visible {
  outline: 2px solid var(--note-current);
  outline-offset: 4px;
}

/* Which way the deck is dealt: straight through, or shuffled. Two buttons
   rather than a switch, because both states have a name — "in order" is a
   choice, not the absence of one. It sits above the card: it decides what the
   next card will be, so it belongs on the way in to the deck. */
.card-order {
  display: flex;
  gap: 4px;
  padding: 3px;
  border: 1px solid var(--toggle-border);
  border-radius: 999px;
  background: var(--notation-bg);
}

.card-order-btn {
  padding: 6px 14px;
  border: none;
  border-radius: 999px;
  background: none;
  font-family: inherit;
  font-size: 0.8rem;
  color: var(--status-label);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.card-order-btn:hover {
  color: var(--text);
}

.card-order-btn.active {
  font-weight: 600;
  background: var(--toggle-bg);
  color: var(--note-current);
}

.card-order-btn:focus-visible {
  outline: 2px solid var(--note-current);
  outline-offset: 2px;
}

.deck-position {
  margin: 0;
  font-size: 0.85rem;
  color: var(--status-label);
}

/* --- Reference charts --- */
/* Lookup pages rather than games: the clef's whole pool on one staff, and every
   key signature drawn in that clef. */

.chart-staff-wrap {
  width: 100%;
  min-width: 0;
  /* Side padding keeps the outermost ledger lines off the box's edge; the
     staff and its letters are both measured inside it, so they stay aligned. */
  padding: 10px 12px 4px;
  border-radius: 12px;
  background: var(--notation-bg);
  border: 1px solid var(--notation-border);
  transition: background 0.3s ease, border-color 0.3s ease;
}

/* One row per staff. A narrow screen gets the pool over several of them —
   eighteen notes across a phone leave a column each about as wide as a
   notehead — so how many there are is decided in script.js, from the measured
   width, and this only has to stack them. */
.chart-staff-rows {
  display: grid;
  width: 100%;
  min-width: 0;
  gap: 4px;
}

.chart-staff-row {
  min-width: 0;
}

/* The chart is a table, not a question: nothing on it is the note being asked
   about, so the note names are ink like the noteheads above them rather than
   wearing the accent colour that means "this one". */
.chart-letters .range-letter.lit {
  color: var(--text);
}

.key-chart {
  display: grid;
  gap: 20px;
  width: 100%;
}

.key-chart-group {
  display: grid;
  gap: 10px;
}

.key-chart-heading {
  margin: 0;
  font-family: inherit;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--status-label);
  text-align: left;
}

.key-chart-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
}

.key-chart-tile {
  display: grid;
  gap: 4px;
  justify-items: center;
  padding: 8px 6px;
  border-radius: 10px;
  background: var(--notation-bg);
  border: 1px solid var(--notation-border);
  transition: background 0.3s ease, border-color 0.3s ease;
}

.key-chart-staff {
  width: 100%;
  line-height: 0;
}

.key-chart-staff svg {
  display: block;
  width: 100%;
  height: auto;
}

.key-chart-name {
  font-family: "DM Serif Display", Georgia, serif;
  font-size: 1.05rem;
  line-height: 1;
  color: var(--status-value);
}

/* --- Key signature game --- */
/* The staff carries only a clef and the accidentals; everything the player has
   to weigh up is in those, so the answers get the room instead. */

/* The one line of the question that is words rather than notation. Shared by
   the key game and the interval game, so it is named for neither. */
.game-prompt {
  margin: 0;
  font-family: "DM Serif Display", Georgia, serif;
  font-size: 1.35rem;
  color: var(--status-value);
}

.key-options {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
  width: 100%;
}

.key-option {
  display: grid;
  place-items: center;
  font: inherit;
  padding: 14px 8px;
  background: var(--toggle-bg);
  border: 1px solid var(--toggle-border);
  border-radius: 12px;
  color: var(--status-value);
  cursor: pointer;
  transition: transform 0.12s ease, border-color 0.15s ease, color 0.15s ease;
}

.key-option:hover {
  transform: translateY(-2px);
  border-color: var(--note-current);
  color: var(--note-current);
}

.key-option:active {
  transform: translateY(0);
}

.key-option:focus-visible {
  outline: 2px solid var(--note-current);
  outline-offset: 2px;
}

.key-option-name {
  font-family: "DM Serif Display", Georgia, serif;
  font-size: 1.5rem;
  line-height: 1;
}

/* A wrong pick shakes where it was pressed, so the answer that was rejected is
   the one that moves — the staff shakes too, but it is not what was aimed at. */
.key-option.wrong,
.interval-option.wrong {
  animation: wrong-shake 400ms ease;
  border-color: var(--note-current);
}

/* The right pick, lit for as long as the signature is held on screen — the
   beat the two chords need to sound. Without it the pause would read as a
   press that did nothing rather than as an answer being heard out. */
.key-option.correct {
  animation: correct-pop 400ms ease;
  border-color: var(--note-current);
  color: var(--note-current);
}

/* The eight interval names, in their own order and never in any other: the
   answer is a count, not one of four things it might be, so the pad stays put
   and 5 is always in the same place. Four across on every screen — eight in a
   row would shrink the names past reading, two would run the pad down the
   page. */
.interval-options {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
  width: 100%;
}

.interval-option {
  display: grid;
  justify-items: center;
  gap: 2px;
  font: inherit;
  padding: 10px 6px;
  background: var(--toggle-bg);
  border: 1px solid var(--toggle-border);
  border-radius: 12px;
  color: var(--status-value);
  cursor: pointer;
  transition: transform 0.12s ease, border-color 0.15s ease, color 0.15s ease;
}

.interval-option:hover {
  transform: translateY(-2px);
  border-color: var(--note-current);
  color: var(--note-current);
}

.interval-option:hover .interval-option-name {
  color: inherit;
}

.interval-option:active {
  transform: translateY(0);
}

.interval-option:focus-visible {
  outline: 2px solid var(--note-current);
  outline-offset: 2px;
}

/* The numeral is the answer and the name is what it is called, so the numeral
   is the one that carries — and it is the key that answers it, too. */
.interval-option-number {
  font-family: "DM Serif Display", Georgia, serif;
  font-size: 1.5rem;
  line-height: 1;
}

.interval-option-name {
  font-size: 0.72rem;
  line-height: 1.2;
  color: var(--status-label);
}

/* Rounds of a fixed length like the numbers above them, but tunes rather than
   numbers, so they get a block of their own under them. */
.setup-melody {
  display: grid;
  gap: 10px;
  width: 100%;
  margin-top: 6px;
}

/* The one heading on the setup screen that is not the title, so it says what
   the list under it is without competing with the question above it. */
.setup-melody-title {
  margin: 0;
  font-family: "DM Serif Display", Georgia, serif;
  font-size: 0.95rem;
  font-weight: 400;
  text-align: center;
  color: var(--status-label);
}

/* The tunes are named, not numbered, so they need room to be read — as many
   per row as fit a title, rather than a fixed count the way the numbers use. */
.melody-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
  width: 100%;
}

.melody-btn {
  position: relative;
  display: grid;
  gap: 2px;
  align-content: center;
  justify-items: center;
  width: 100%;
  font-family: inherit;
  padding: 12px 14px;
  background: var(--toggle-bg);
  border: 1px solid var(--toggle-border);
  border-radius: 10px;
  color: var(--status-value);
  cursor: pointer;
  transition: transform 0.12s ease, border-color 0.15s ease, color 0.15s ease;
}

.melody-btn:hover {
  transform: translateY(-2px);
  border-color: var(--note-current);
  color: var(--note-current);
}

.melody-btn:active {
  transform: translateY(0);
}

.melody-btn:focus-visible {
  outline: 2px solid var(--note-current);
  outline-offset: 2px;
}

.melody-btn.played {
  border-color: color-mix(in srgb, var(--note-past) 70%, transparent);
  color: var(--note-past);
}

.melody-name {
  font-family: "DM Serif Display", Georgia, serif;
  font-size: 1.05rem;
  line-height: 1.15;
  text-align: center;
  /* Long titles wrap rather than widening their column: the buttons share a
     grid, so one of them stretching would stretch the whole row. */
  overflow-wrap: anywhere;
}

.melody-composer {
  font-size: 0.72rem;
  text-align: center;
  color: var(--status-label);
}

.results-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  width: 100%;
}

/* Reaction emoji shown above the end-of-game stats. */
.results-face {
  font-size: 4.5rem;
  line-height: 1;
  animation: face-pop 0.4s ease both;
}

@keyframes face-pop {
  from {
    opacity: 0;
    transform: scale(0.6);
  }
  60% {
    transform: scale(1.08);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .results-face {
    animation: none;
  }

  .key-option.wrong,
  .key-option.correct,
  .interval-option.wrong {
    animation: none;
  }
}

.primary-btn {
  font-family: inherit;
  font-weight: 600;
  font-size: 0.95rem;
  padding: 12px 24px;
  border-radius: 10px;
  border: 1px solid transparent;
  background: var(--note-current);
  color: #fff;
  cursor: pointer;
  transition: transform 0.12s ease, opacity 0.15s ease;
}

.primary-btn:hover {
  transform: translateY(-2px);
  opacity: 0.92;
}

.primary-btn:active {
  transform: translateY(0);
}

.screen-hint {
  margin: 0;
  color: var(--status-label);
  font-size: 0.85rem;
}

.screen-hint kbd {
  font-family: inherit;
  font-size: 0.78rem;
  padding: 2px 6px;
  border-radius: 5px;
  background: var(--toggle-bg);
  border: 1px solid var(--toggle-border);
  color: var(--toggle-text);
}

.notation-area {
  width: 100%;
  border-radius: 12px;
  overflow: hidden;
  background: var(--notation-bg);
  border: 1px solid var(--notation-border);
  padding: 12px 0;
  position: relative;
  isolation: isolate;
  transition: background 0.3s ease, border-color 0.3s ease;
}

.notation-area svg {
  display: block;
  margin: 0 auto;
}

.notation-area.anim-correct {
  animation: correct-pop 400ms ease;
}

.notation-area.anim-wrong {
  animation: wrong-shake 350ms ease;
}

@keyframes correct-pop {
  0% {
    transform: scale(1);
    box-shadow: 0 0 0 rgba(122, 158, 126, 0);
  }
  45% {
    transform: scale(1.012);
    box-shadow: 0 0 24px var(--pop-glow);
  }
  100% {
    transform: scale(1);
    box-shadow: 0 0 0 rgba(122, 158, 126, 0);
  }
}

@keyframes wrong-shake {
  0%,
  100% {
    transform: translateX(0);
  }
  20% {
    transform: translateX(-5px);
  }
  40% {
    transform: translateX(5px);
  }
  60% {
    transform: translateX(-3px);
  }
  80% {
    transform: translateX(3px);
  }
}

.status {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  width: 100%;
}

.status-row {
  background: var(--status-bg);
  border: 1px solid var(--status-border);
  padding: 14px 16px;
  border-radius: 10px;
  text-align: center;
  display: grid;
  gap: 4px;
  transition: background 0.3s ease, border-color 0.3s ease;
}

.status-row span {
  font-size: 0.8rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--status-label);
}

.status-row strong {
  font-family: "DM Serif Display", Georgia, serif;
  font-size: 1.5rem;
  color: var(--status-value);
}

@media (prefers-reduced-motion: reduce) {
  .notation-area.anim-correct,
  .notation-area.anim-wrong {
    animation: none;
  }

  .blob,
  .note-particle {
    animation: none;
  }

  .bg,
  .notes-layer {
    transform: none;
  }

  /* The band should still land where the finger is, just without easing. The
     cards still turn over — they simply arrive at the far side at once. */
  .range-band,
  .range-ghost,
  .note-card-inner {
    transition: none;
  }
}

@media (max-width: 600px) {
  .app {
    padding: 12px 18px 20px;
  }

  .status,
  .results-stats {
    grid-template-columns: 1fr;
  }

  .note-count-options {
    grid-template-columns: repeat(3, 1fr);
  }

  /* Four across leaves each answer too narrow to read at a glance. */
  .key-options {
    grid-template-columns: repeat(2, 1fr);
  }

  .key-chart-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .note-card-slot {
    width: min(300px, 78vw);
  }

  .app-settings-cols {
    grid-template-columns: 1fr;
    gap: 12px;
  }
}

/* On-screen note answer buttons. Hidden by default and styled only inside the
   touch-device query, so the keyboard-only desktop layout is untouched at any
   window width. */
.note-pad {
  display: none;
}

@media (hover: none) and (pointer: coarse) {
  /* Fit the game in a portrait viewport: tighter vertical spacing, and "safe"
     centering so a layout taller than the screen aligns to the top and stays
     scrollable instead of being clipped by flex centering. */
  body {
    align-items: safe center;
    padding: 10px;
  }

  .toolbar {
    margin-bottom: 4px;
  }

  .game {
    gap: 14px;
  }

  .notation-area {
    padding: 6px 0;
  }

  /* Compact status chips, leaving room for a bigger answer pad. */
  .status {
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
  }

  .status-row {
    padding: 6px 8px;
    gap: 2px;
  }

  .status-row span {
    font-size: 0.6rem;
  }

  .status-row strong {
    font-size: 1rem;
  }

  .note-pad {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 8px;
    width: 100%;
  }

  .note-pad-btn {
    font-family: "DM Serif Display", Georgia, serif;
    font-size: 1.55rem;
    min-height: 72px;
    padding: 0;
    background: var(--toggle-bg);
    border: 1px solid var(--toggle-border);
    color: var(--status-value);
    border-radius: 10px;
    cursor: pointer;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    transition: background 0.12s ease, transform 0.08s ease, border-color 0.12s ease, color 0.12s ease;
  }

  .note-pad-btn:active {
    transform: translateY(1px) scale(0.97);
    background: var(--status-bg);
    border-color: var(--note-current);
    color: var(--note-current);
  }

  /* The "press Tab" hint is meaningless without a keyboard. Only that one —
     the note cards' hint is about tapping, so it belongs here most of all. */
  .results-screen .screen-hint {
    display: none;
  }
}
