:root {
  /* Surfaces */
  --bg-0: #0c0c0c;
  --bg-1: #1a1a1a;
  --bg-2: #232323;

  /* Text */
  --text-primary: #f0f0f0;
  --text-secondary: #999;
  --text-muted: #666;

  /* Borders (foreground-on-surface opacity) */
  --border-default: rgba(255, 255, 255, 0.06);
  --border-mid: rgba(255, 255, 255, 0.09);
  --border-strong: rgba(255, 255, 255, 0.14);

  /* Accent */
  --accent: #2ecc71;
  --accent-dim: rgba(46, 204, 113, 0.1);
  --accent-glow: rgba(46, 204, 113, 0.35);

  /* Status */
  --negative: #e05c5c;
  --negative-dim: rgba(224, 92, 92, 0.1);

  /* Radius */
  --radius-base: 12px;
  --radius-small: 8px;

  /* Layout */
  --control-height: 40px;
}

* {
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Inter', system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  font-size: 14px;
  background: var(--bg-0);
  color: var(--text-primary);
  display: flex;
  justify-content: center;
  padding: 40px 20px;
}

#app {
  max-width: 560px;
  width: 100%;
  background: var(--bg-1);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-base);
  padding: 32px;
}

.nav-back {
  display: inline-block;
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 13px;
  margin-bottom: 20px;
}
.nav-back:hover {
  color: var(--text-primary);
}

section {
  margin-bottom: 0;
}

h2 {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 12px;
}

.status {
  font-size: 12px;
  color: var(--text-muted);
  margin-top: 8px;
  min-height: 16px;
}

/* Wraps the prompt/keypad/contour area (see intervals.html) so it can
   cross-fade as one unit and hold a stable min-height across question
   types — see stabilizeQuestionCard() in intervals.js, which ratchets
   min-height up to the tallest question seen so the card never visibly
   grows/shrinks again after that. The 350ms opacity transition covers both
   the fade-out (fadeOutQuestionUI adds .fading-out, opacity -> 0) and the
   fade-in (clearFadeOutQuestionUI removes it once the next question's
   content is already written in, invisibly, opacity -> 1).

   The single correct-answer signal is .correct-flash: the whole card washes
   green, holds, then fades per the above — one piece, not a separate text
   pop plus a separate full-screen flash. See beginCorrectFlash() in
   intervals.js for the exact sequencing. */
#question-card {
  transition: opacity 350ms ease, background-color 150ms ease;
}

#question-card.fading-out {
  opacity: 0;
}

#question-card.correct-flash {
  background-color: var(--accent-glow);
}

/* ---- Header ---- */

#app-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 32px;
}

h1 {
  font-size: 24px;
  font-weight: 700;
  letter-spacing: -0.2px;
  margin-bottom: 4px;
}

.sub {
  font-size: 13px;
  color: var(--text-secondary);
  line-height: 1.5;
}

/* ---- Settings ---- */

#settings-wrap {
  position: relative;
  flex-shrink: 0;
}

#settings-btn {
  width: var(--control-height);
  padding: 0;
  font-size: 16px;
}

#settings-menu {
  display: none;
  position: absolute;
  top: calc(var(--control-height) + 6px);
  right: 0;
  min-width: 120px;
  background: var(--bg-2);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-small);
  overflow: hidden;
  z-index: 10;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

#settings-menu.open {
  display: block;
}

.settings-menu-item {
  display: block;
  width: 100%;
  padding: 10px 12px;
  background: none;
  border: none;
  color: var(--text-primary);
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
}

.settings-menu-item:hover {
  background: var(--bg-1);
}

/* ---- Panels ---- */

.panel {
  background: var(--bg-2);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-base);
  padding: 24px;
  margin-bottom: 24px;
}

#range-form {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  flex-wrap: wrap;
}

#range-form label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 12px;
  color: var(--text-secondary);
}

#range-form input {
  width: 64px;
  height: var(--control-height);
  padding: 0 10px;
  background: var(--bg-1);
  border: 1px solid var(--border-mid);
  color: var(--text-primary);
  border-radius: var(--radius-small);
  font-family: inherit;
  font-size: 14px;
}

#gestures-form {
  display: flex;
  align-items: center;
  gap: 12px;
}

#gestures-form input[type="range"] {
  flex: 1;
  accent-color: var(--accent);
}

#gestures-form span {
  font-size: 13px;
  color: var(--text-secondary);
  min-width: 42px;
  text-align: right;
}

/* ---- Buttons (equal height, one shared control size) ---- */

.btn {
  height: var(--control-height);
  padding: 0 16px;
  background: var(--bg-2);
  border: 1px solid var(--border-mid);
  color: var(--text-primary);
  border-radius: var(--radius-small);
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.05s ease;
}

.btn:hover {
  border-color: var(--border-strong);
}

.btn:active {
  transform: scale(0.98);
}

.btn-block {
  width: 100%;
  display: block;
  margin-bottom: 12px;
}

.btn-icon {
  width: var(--control-height);
  padding: 0;
  flex-shrink: 0;
}

.btn-accent {
  background: var(--accent);
  border-color: var(--accent);
  color: #06210f;
  font-weight: 600;
}

.btn-accent:hover {
  box-shadow: 0 0 0 4px var(--accent-glow);
}

/* ---- Play controls row ---- */

#play-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 16px;
}

/* ---- Quiz input rows ---- */

.quiz-input-group {
  margin-bottom: 16px;
}

.answer-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
}

.answer-row input {
  flex: 1;
  min-width: 0;
  height: var(--control-height);
  padding: 0 10px;
  background: var(--bg-2);
  border: 1px solid var(--border-mid);
  color: var(--text-primary);
  border-radius: var(--radius-small);
  font-family: inherit;
  font-size: 14px;
}

/* Picked-note slots for the "name the other two chord tones" step — the
   active (not-yet-filled) slot gets the accent outline, same visual
   language as .keypad-btn.active, so it's clear which pick you're on. */
.tone-slot {
  position: relative;
  flex: 1;
  min-width: 0;
  height: var(--control-height);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-2);
  border: 1px solid var(--border-mid);
  color: var(--text-muted);
  border-radius: var(--radius-small);
  font-size: 15px;
}

/* Small remove affordance in the corner of a filled slot — lets a mis-tap
   be cleared without resetting the whole question. */
.tone-slot-delete {
  position: absolute;
  top: 4px;
  right: 6px;
  width: 18px;
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  padding: 0;
  color: var(--text-muted);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}

.tone-slot-delete:hover {
  color: var(--text-primary);
}

.tone-slot.active {
  border-color: var(--accent);
  color: var(--text-primary);
}

.tone-slot.filled {
  color: var(--text-primary);
}

#input-role .role-btn {
  flex: 1;
}

/* ---- Keypads — standard 3-column phone-dialpad grid (max 12 keys), never
   a wrapping row of variable-width buttons. Every key fills its cell fully
   so the grid stays visually even regardless of label length (1 vs III vs
   Major). ---- */

.keypad {
  margin-bottom: 10px;
}

.keypad-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  /* Without this, the browser's own touch-gesture recognizer (scroll/pan)
     races our pointerdown/pointerup swipe detection: a swipe that starts on
     a button can still resolve to a native "tap" on that same button (fired
     from the original touchstart target, not the pointerup target) even
     after e.preventDefault() in the pointerup handler, because the browser
     already committed to its own gesture before ever seeing that
     preventDefault. touch-action: none hands 100% of gesture interpretation
     to our JS, which is what makes the click-suppression in
     wireKeypadFinalize() actually reliable on touch devices. */
  touch-action: none;
}

.keypad-btn,
.keypad-toggle {
  height: 44px;
  width: 100%;
  padding: 0 6px;
  background: var(--bg-2);
  border: 1px solid var(--border-mid);
  color: var(--text-secondary);
  border-radius: var(--radius-small);
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  touch-action: none;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.keypad-btn:hover,
.keypad-toggle:hover {
  border-color: var(--border-strong);
  color: var(--text-primary);
}

/* Any selected keypad button — base symbol or modifier — not just
   modifiers: the keypad's visible selection state must always mirror
   what's actually composed in the answer field below it. */
.keypad-btn.active {
  background: var(--accent-dim);
  border-color: var(--accent);
  color: var(--accent);
}

.keypad-toggle {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.keypad-toggle.active {
  background: var(--bg-1);
  border-color: var(--border-strong);
  color: var(--text-primary);
}

/* Visual stand-in for haptic feedback on the base-syllable hold-to-flip-case
   gesture (wireBaseCaseHold) — iOS has no Vibration API, so this is what
   actually confirms the hold registered there. Scoped to that one gesture
   only, not a general keypad-press effect. */
@keyframes case-flash {
  0%, 100% { filter: brightness(1); }
  50%      { filter: brightness(1.8); }
}
.keypad-btn.case-flash {
  animation: case-flash 150ms ease;
}

/* ---- Prompt / question text ---- */

#prompt,
#context-line,
#question-prompt {
  font-size: 15px;
  line-height: 1.6;
  color: var(--text-primary);
}

#context-line {
  margin-bottom: 4px;
  color: var(--text-secondary);
}

.info-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  margin-left: 6px;
  vertical-align: middle;
  position: relative;
  background: var(--bg-1);
  border: 1px solid var(--border-mid);
  border-radius: 50%;
  color: var(--text-secondary);
  font-size: 11px;
  font-style: italic;
  font-family: Georgia, serif;
  line-height: 1;
  cursor: pointer;
}

.info-btn::after {
  content: attr(data-tip);
  position: absolute;
  bottom: 130%;
  left: 50%;
  transform: translateX(-50%);
  width: 200px;
  background: var(--bg-2);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-small);
  color: var(--text-primary);
  font-size: 12px;
  font-style: normal;
  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
  text-align: left;
  white-space: normal;
  padding: 8px 10px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
  z-index: 20;
}

.info-btn:hover::after,
.info-btn.show::after {
  opacity: 1;
}

#question-prompt {
  margin-bottom: 20px;
}

#recall-flash {
  font-size: 13px;
  color: var(--text-secondary);
  background: var(--bg-2);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-small);
  padding: 10px 12px;
  margin-bottom: 16px;
}

.accidental {
  visibility: hidden;
}
.accidental img {
  display: inline;
  visibility: visible;
}

/* Scale-degree caret (1̂-7̂) — a real caret accent (^), drawn as two thin
   strokes meeting in a peak, in pure CSS geometry rather than a Unicode
   combining accent or font glyph. Those render inconsistently across fonts
   (position/size drift), which defeats the point of a notation meant to be
   visually unambiguous. This renders identically no matter what font the
   page uses. */
.deg {
  position: relative;
  display: inline-block;
}
.deg::before,
.deg::after {
  content: "";
  position: absolute;
  top: calc(0.02em - 2px);
  width: 0.272em;
  height: 0.072em;
  background: currentColor;
  border-radius: 0.02em;
}
.deg::before {
  left: 50%;
  transform: translate(-92%, -100%) rotate(-38deg);
  transform-origin: right center;
}
.deg::after {
  left: 50%;
  transform: translate(-8%, -100%) rotate(38deg);
  transform-origin: left center;
}

/* ---- Contour step ---- */

#step-contour {
  margin-top: 4px;
}

#contour-label {
  font-size: 15px;
  line-height: 1.6;
  margin-bottom: 12px;
}

#contour-grid {
  background: var(--bg-2);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-small);
  padding: 16px;
  margin-bottom: 16px;
}

.contour-table {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.contour-row {
  display: grid;
  grid-template-columns: 40px repeat(3, 1fr);
  align-items: center;
  gap: 12px;
}

.contour-row-label {
  font-family: 'SF Mono', Menlo, monospace;
  font-size: 11px;
  color: var(--text-muted);
}

.contour-dot {
  justify-self: center;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--border-strong);
}

/* Filled = the tone actually sits at this height. Reference (column 1)
   always stays visible and circled — it's the tone the student already
   heard, not part of the puzzle. The grid itself is the structure the
   student is meant to see and internalize, so it's never blurred — the
   crutch worth hiding until "Check" is the solfège syllable text below
   (see .crutch-hidden), not the shape. */
.contour-dot.filled {
  width: 10px;
  height: 10px;
  background: var(--text-secondary);
}

.contour-dot.filled.reference {
  width: 14px;
  height: 14px;
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-dim);
}

#contour-prompt {
  font-size: 15px;
  line-height: 1.6;
  margin-bottom: 16px;
}

/* The named-syllable answer, not the dot grid, is the crutch — spelling out
   exactly what to sing defeats the point of reading the grid's shape first.
   Reference tone (already heard) stays plain text, never wrapped in this. */
.crutch-hidden {
  filter: blur(4px);
  opacity: 0.6;
  transition: filter 0.3s ease, opacity 0.3s ease;
}

#step-contour.revealed .crutch-hidden {
  filter: none;
  opacity: 1;
}

#grade-buttons {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}

.grade-btn {
  flex: 1;
}

.grade-btn.correct {
  background: var(--accent);
  border-color: var(--accent);
  color: #06210f;
  font-weight: 600;
}

.grade-btn.incorrect {
  background: var(--negative-dim);
  border-color: var(--border-mid);
  color: var(--negative);
}

/* ---- Solfège / accidentals ---- */

/* Bravura Text — SMuFL font built for accidentals sitting inline with body
   text (as opposed to full-size music-engraving Bravura). OFL-licensed,
   redistributed in fonts/ alongside its license (see fonts/OFL-Bravura.txt).
   This is what gives double-flat its own real glyph instead of two flat
   signs glued together. */
@font-face {
  font-family: 'BravuraText';
  src: url('fonts/BravuraText.woff2') format('woff2');
  font-display: swap;
}

.solfege {
  font-family: 'Iowan Old Style', Palatino, Georgia, serif;
  font-style: italic;
}

/* The key phrase inside a question prompt (e.g. "re minor") — bold on top
   of italic since a minor key's lowercase solfège blends into regular-
   weight prose otherwise, especially at a glance. */
.key-phrase {
  font-family: 'Iowan Old Style', Palatino, Georgia, serif;
  font-style: italic;
  font-weight: 700;
}

/* ---- Mobile compaction ----
   Goal: the whole quiz (question + keypad + submit) fits on one screen
   without scrolling. Controls (buttons, inputs, keypad cells) never shrink
   here — --control-height and .keypad-btn sizing are untouched. Only the
   decorative chrome around them (page padding, subtitle, header spacing)
   gets reclaimed, since that's what was pushing the actual interactive
   content below the fold. */
@media (max-width: 480px) {
  body {
    padding: 8px;
  }

  #app {
    padding: 16px;
  }

  .nav-back {
    margin-bottom: 8px;
    font-size: 12px;
  }

  #app-header {
    margin-bottom: 12px;
  }

  h1 {
    font-size: 18px;
    margin-bottom: 0;
  }

  .sub {
    display: none;
  }
}
