/* Music Practice theme. Every knob is a custom property in :root, and the
   dark palette overrides the same names, so nothing below hard-codes a colour. */
:root {
  --paper: #fbfbf9;
  --paper-sunk: #f0f0ec;
  --paper-raised: #fff;
  --ink: #16181a;
  --ink-soft: #4a4f55;
  --ink-faint: #7c848c;
  --rule: #deded8;
  --accent: #1f4d5c;
  --accent-ink: #fff;

  /* Feedback colours for the keyboard, the staff and the results. */
  --ok: #1d7a46;
  --bad: #c0392b;
  --target: #2f6fd6;
  --hint: #c9dcf5;
  --early: #b7791f;
  --late: #7b4fc4;

  /* Piano keys stay literal white and black in both themes. */
  --key-white: #fff;
  --key-black: #1b1b1d;
  --key-edge: #9a9a9a;

  --font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  --text: clamp(0.95rem, 0.9rem + 0.2vw, 1.05rem);
  --small: 0.875rem;
  --title: clamp(1.25rem, 1.1rem + 0.8vw, 1.6rem);
  --title-page: clamp(1.6rem, 1.3rem + 1.5vw, 2.4rem);

  --gap-xs: 0.35rem;
  --gap-sm: 0.7rem;
  --gap-md: 1.2rem;
  --gap-lg: 2rem;
  --gap-xl: 3.5rem;

  --radius: 8px;
  --measure: 68ch;
  --page: 1180px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper: #131517;
    --paper-sunk: #1b1e21;
    --paper-raised: #202428;
    --ink: #e8eaec;
    --ink-soft: #b3b9bf;
    --ink-faint: #858d95;
    --rule: #33383d;
    --accent: #7fc4d6;
    --accent-ink: #0d1f24;
    /* ok/early/late checked with the dataviz palette validator against
       this surface: lightness band, chroma, CVD and contrast. */
    --ok: #2fa36b;
    --bad: #ff6b5b;
    --target: #6ea3ff;
    --hint: #2a3f5e;
    --early: #c28630;
    --late: #9a7ede;
  }
}

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

/* The hidden attribute must win over any display a class sets. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  font: var(--text)/1.55 var(--font);
  color: var(--ink);
  background: var(--paper);
}

main { flex: 1; padding-block: var(--gap-lg); }

.inner {
  max-width: var(--page);
  margin-inline: auto;
  padding-inline: var(--gap-md);
}

h1, h2, h3 { text-wrap: balance; line-height: 1.15; margin: 0 0 var(--gap-sm); }
h1 { font-size: var(--title-page); }
h2 { font-size: var(--title); }
p { max-width: var(--measure); }

a { color: var(--accent); }

.site-header {
  border-bottom: 1px solid var(--rule);
  background: var(--paper-raised);

  > .inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--gap-md);
    padding-block: var(--gap-sm);
  }

  .brand {
    display: inline-flex;
    align-items: center;
    gap: var(--gap-sm);
    font-weight: 650;
    color: var(--ink);
    text-decoration: none;
  }

  nav {
    display: flex;
    gap: var(--gap-md);

    a {
      color: var(--ink-soft);
      text-decoration: none;
      padding-block: var(--gap-xs);
      border-bottom: 2px solid transparent;

      &:hover { color: var(--ink); }
      &[aria-current="page"] { color: var(--ink); border-bottom-color: var(--accent); }
    }
  }
}

.site-footer {
  border-top: 1px solid var(--rule);
  color: var(--ink-faint);
  font-size: var(--small);

  p { margin: var(--gap-md) 0; }
}

.notice {
  padding: var(--gap-sm) var(--gap-md);
  border-radius: var(--radius);
  background: var(--paper-sunk);
  border-left: 4px solid var(--accent);

  &.warn { border-left-color: var(--early); }
  &.error { border-left-color: var(--bad); }
}

button, .button {
  padding: var(--gap-xs) var(--gap-md);
  min-height: 2.5rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--accent-ink);
  font: inherit;
  font-weight: 600;
  cursor: pointer;

  &:hover { background: color-mix(in oklab, var(--accent), black 12%); }
  &:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
  &:disabled { opacity: 0.5; cursor: not-allowed; }

  &.secondary {
    background: transparent;
    color: var(--ink);
    border-color: var(--rule);

    &:hover { background: var(--paper-sunk); }
  }
}

select, input {
  font: inherit;
  color: var(--ink);
  background: var(--paper-raised);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: var(--gap-xs) var(--gap-sm);
  min-height: 2.5rem;
}

label { font-size: var(--small); color: var(--ink-soft); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---- Practice page ---------------------------------------------------- */

main > .inner { display: flex; flex-direction: column; gap: var(--gap-md); }

.panel {
  background: var(--paper-raised);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: var(--gap-md);
}

.field {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 0;

  select { width: 100%; }
}

.device {
  display: grid;
  grid-template-columns: minmax(0, 22rem) auto 1fr;
  align-items: end;
  gap: var(--gap-sm) var(--gap-md);

  .status { margin: 0; align-self: center; color: var(--ink-soft); font-size: var(--small); }
  .status.warn { color: var(--early); }
  .qwerty-hint { grid-column: 1 / -1; margin: 0; font-size: var(--small); color: var(--ink-faint); }
}

/* Blinks on every incoming MIDI note, so a silent connection is obvious. */
.activity {
  align-self: center;
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 50%;
  background: var(--rule);
  transition: background 0.1s;

  &.on { background: var(--ok); transition: none; }
}

kbd {
  font: 0.8em var(--font-mono);
  padding: 0.05em 0.35em;
  border: 1px solid var(--rule);
  border-bottom-width: 2px;
  border-radius: 4px;
  background: var(--paper-sunk);
}

/* Fills the row with as many columns as fit: five on a desktop, two on a
   phone, with no breakpoints. */
.picker {
  display: grid;
  gap: var(--gap-sm) var(--gap-md);
  grid-template-columns: repeat(auto-fill, minmax(clamp(7.5rem, 35vw, 11rem), 1fr));
}

.exercise {
  display: flex;
  flex-direction: column;
  gap: var(--gap-sm);
  min-width: 0;
}

.exercise-head {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: var(--gap-sm);

  h1 { margin: 0; font-size: var(--title-page); }
  .exercise-status { margin: var(--gap-xs) 0 0; color: var(--ink-soft); min-height: 1.55em; }
}

/* Wide content scrolls inside its own box; the page body never scrolls
   sideways. */
.staff-scroll, .keyboard-scroll {
  overflow-x: auto;
  background: var(--paper-raised);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}

.staff-scroll:empty { display: none; }

/* VexFlow draws in the container's text colour (read at render time), and
   shows about two systems, scrolling to follow the current line. */
.staff-scroll {
  color: var(--ink);
  max-height: 21rem;
  overflow-y: auto;
  scroll-behavior: smooth;

  svg { display: block; }

  /* Step marks are classes on each note's group. CSS beats VexFlow's
     presentation attributes, so no redraw is needed to recolour. */
  .vf-stavenote {
    &.current { --mark: var(--target); }
    &.ok { --mark: var(--ok); }
    &.bad { --mark: var(--bad); }
    &.early { --mark: var(--early); }
    &.late { --mark: var(--late); }

    &:is(.current, .ok, .bad, .early, .late) {
      :not([fill="none"]) { fill: var(--mark); }
      path { stroke: var(--mark); }
    }
  }
}
.keyboard-scroll { padding: var(--gap-sm); }

.piano {
  display: block;
  width: 100%;
  height: auto;
  max-height: 12rem;

  /* Base colours sit inside :where() so they carry no specificity, and any
     state below (target, pressed) wins without needing to out-count them. */
  .key { stroke: var(--key-edge); stroke-width: 1; }
  :where(.key.white) { fill: var(--key-white); }
  :where(.key.black) { fill: var(--key-black); stroke: var(--key-black); }
  :where(.key.white.in-scale) { fill: color-mix(in oklab, var(--key-white), var(--target) 14%); }
  :where(.key.black.in-scale) { fill: color-mix(in oklab, var(--key-black), var(--target) 40%); }
  .key.target { fill: var(--target); }
  .key.down.ok { fill: var(--ok); }
  .key.down.bad { fill: var(--bad); }
  .key.down.neutral { fill: var(--ink-faint); }
  .labels text {
    font: 10px var(--font);
    fill: #6b7075;
    text-anchor: middle;
    pointer-events: none;
  }
}

.results {
  .best {
    margin: 0 0 var(--gap-sm);
    color: var(--ink-soft);

    &.new { color: var(--ok); font-weight: 650; }
  }

  .stats {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(clamp(7rem, 40vw, 10rem), 1fr));
    gap: var(--gap-md);
    margin: 0;

    > div, dt, dd { margin: 0; }
    dt { font-size: var(--small); color: var(--ink-soft); }
    dd { font-size: var(--title); font-weight: 650; font-variant-numeric: tabular-nums; }
  }
  .results-note { margin: var(--gap-sm) 0 0; color: var(--ink-faint); font-size: var(--small); }
}

.timing-chart {
  margin-top: var(--gap-md);

  .timing-plot { display: block; max-width: 100%; height: auto; }
  .band { fill: var(--paper-sunk); }
  .beat { stroke: var(--rule); stroke-width: 1; }
  .axis { font: 11px var(--font); fill: var(--ink-faint); text-anchor: end; }
  .dot {
    circle { stroke: var(--paper-raised); stroke-width: 2; }
    &.on circle { fill: var(--ok); }
    &.early circle { fill: var(--early); }
    &.late circle { fill: var(--late); }
    &.missed path { stroke: var(--ink-soft); stroke-width: 2; stroke-linecap: round; }
    .hit { fill: transparent; stroke: none; }
    &:hover circle:not(.hit) { stroke: var(--ink); }
  }
}

.timing-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap-xs) var(--gap-md);
  margin: var(--gap-xs) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--small);
  color: var(--ink-soft);

  li::before {
    content: "";
    display: inline-block;
    width: 0.65rem;
    height: 0.65rem;
    margin-right: 0.35rem;
    border-radius: 50%;
    vertical-align: -0.05rem;
  }
  .on::before { background: var(--ok); }
  .early::before { background: var(--early); }
  .late::before { background: var(--late); }
  .missed::before { content: "×"; width: auto; height: auto; color: var(--ink-soft); font-weight: 700; }
}

/* A touch-only device has no keyboard to play on. */
@media (hover: none) and (pointer: coarse) {
  .qwerty-hint { display: none; }
}

@media (max-width: 40rem) {
  .device { grid-template-columns: 1fr auto; }
  .device .status { grid-column: 1 / -1; }
}

/* ---- Progress page ------------------------------------------------------ */

.summary-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(clamp(8rem, 40vw, 12rem), 1fr));
  gap: var(--gap-md);
  margin: 0 0 var(--gap-md);

  > div { margin: 0; }
  dt { font-size: var(--small); color: var(--ink-soft); }
  dd { margin: 0; font-size: var(--title); font-weight: 650; font-variant-numeric: tabular-nums; }
}

#history { display: flex; flex-direction: column; gap: var(--gap-md); }
#history[hidden] { display: none; }

.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: var(--gap-sm);
  margin-bottom: var(--gap-sm);

  h2 { margin: 0; }
  .field { min-width: min(16rem, 100%); }
}

.trend-chart {
  .trend-plot { display: block; max-width: 100%; height: auto; }
  .grid { stroke: var(--rule); stroke-width: 1; }
  .axis { font: 11px var(--font); fill: var(--ink-faint); }
  .line { fill: none; stroke: var(--target); stroke-width: 2; stroke-linejoin: round; }
  .mark {
    circle { stroke-width: 2; }
    &.notes circle { fill: var(--target); stroke: var(--paper-raised); }
    &.tempo circle { fill: var(--paper-raised); stroke: var(--target); }
    .hit { fill: transparent !important; stroke: none !important; }
    &:hover circle:not(.hit) { stroke: var(--ink); }
  }
}

.trend-legend {
  display: flex;
  gap: var(--gap-md);
  margin: var(--gap-xs) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--small);
  color: var(--ink-soft);

  li::before {
    content: "";
    display: inline-block;
    width: 0.6rem;
    height: 0.6rem;
    margin-right: 0.35rem;
    border-radius: 50%;
    border: 2px solid var(--target);
    vertical-align: -0.05rem;
  }
  .notes::before { background: var(--target); }
}

.table-scroll { overflow-x: auto; }

table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--small);
  font-variant-numeric: tabular-nums;

  th, td { padding: var(--gap-xs) var(--gap-sm); text-align: left; border-bottom: 1px solid var(--rule); white-space: nowrap; }
  th { color: var(--ink-soft); font-weight: 600; }
  .num { text-align: right; }
  tbody tr:hover { background: var(--paper-sunk); }
}

.actions { display: flex; flex-wrap: wrap; gap: var(--gap-sm); }

/* A label styled as a button, wrapping a visually hidden file input so the
   control stays keyboard-reachable. */
.file-button {
  display: inline-flex;
  align-items: center;
  position: relative;
  font-size: var(--text);

  input {
    position: absolute;
    inset: 0;
    opacity: 0;
    cursor: pointer;
  }
  &:focus-within { outline: 2px solid var(--ink); outline-offset: 2px; }
}

button.danger { background: var(--bad); color: #fff; }
button.secondary.danger { background: transparent; color: var(--bad); border-color: color-mix(in oklab, var(--bad), transparent 50%); }

.data-status { margin: var(--gap-sm) 0 0; min-height: 1.55em; color: var(--ink-soft); font-size: var(--small); }
.empty p { margin: 0; }
