/*
 * Score layout.
 *
 * The renderer emits markup once; everything positional happens here. Systems
 * are a wrapping flex row, bars are flex items that grow with their length, and
 * notes sit at a percentage of their bar — so horizontal position means time,
 * and resizing the window costs no JavaScript.
 *
 * Vertical position comes from --offset, a count of staff spaces below the top
 * line, computed by the renderer from the diatonic step. An accidental never
 * changes it.
 */

@font-face {
  font-family: "Bravura";
  src: url(/fonts/Bravura.woff2) format("woff2");
  font-display: block; /* tofu instead of fallback glyphs while loading */
}

/* Note colours are the keyboard's stickers, and live in notes.css. */
.score {
  --staff-space: 9px;
  --staff-height: calc(var(--staff-space) * 4);
  --staff-gap: calc(var(--staff-space) * 6);
  --line-weight: 1px;
  --stem-weight: 1.3px;
  --signature-width: calc(var(--staff-space) * 8);

  /*
   * A grid rather than a wrapping flex row, so the number of bars on a line is
   * something we choose rather than something that falls out of how wide they
   * happen to be. Too many bars per line makes it unreadable for a child.
   *
   * The trade-off is that bars share the width equally, so a 2/4 bar is as wide
   * as a 4/4 one. Within a bar, notes stay proportional to time — which is what
   * the fill bars depend on.
   */
  --bars-per-line: 1;

  display: grid;
  grid-template-columns: repeat(var(--bars-per-line), 1fr);
  align-items: start;
  row-gap: calc(var(--staff-space) * 7);

  /* Music is read off paper. A light sheet inside the dark app also keeps the
     note colours honest — the sticker palette is chosen against white keys. */
  background: #fbfaf6;
  color: #22201d;
  border-radius: 8px;
  padding: calc(var(--staff-space) * 4) calc(var(--staff-space) * 3);

  /* Bravura is drawn so that 1em is the height of a five-line staff. */
  font-size: var(--staff-height);
}

.glyph {
  position: absolute;
  font-family: "Bravura";
  /* Collapse the line box so the glyph's baseline lands on its own top edge,
     which is where --offset places it. */
  line-height: 0;
  white-space: pre;
  user-select: none;
  top: calc(var(--offset, 0) * var(--staff-space));
}

/* Bars ------------------------------------------------------------------- */

/* Tablet */
@media (min-width: 40rem) {
  .score {
    --bars-per-line: 2;
  }
}

/* Desktop */
@media (min-width: 64rem) {
  .score {
    --bars-per-line: 3;
  }
}

.measure {
  position: relative;
  min-width: 0; /* grid items refuse to shrink past their content without this */
  padding-right: calc(var(--staff-space) * 1.5);
}

.measure__staves {
  display: flex;
  flex-direction: column;
  gap: var(--staff-gap);
  position: relative;
}

/* The brace and the spanning barline that make two staves one grand staff.
   Drawn on the bar that starts a line, and at every barline respectively. */
.measure__staves::after {
  content: "";
  position: absolute;
  right: 0;
  top: 0;
  bottom: 0;
  width: var(--line-weight);
  background: currentColor;
}

.measure:last-child .measure__staves::after {
  width: calc(var(--line-weight) * 3);
}

/* Staves ----------------------------------------------------------------- */

.staff {
  position: relative;
  display: flex;
  height: var(--staff-height);
}

/* Five lines as one element: a repeating gradient, not five divs. */
.staff__lines {
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(
    to bottom,
    currentColor 0,
    currentColor var(--line-weight),
    transparent var(--line-weight),
    transparent var(--staff-space)
  );
  /* The gradient would stop short of the fifth line without this. */
  background-size: 100% calc(var(--staff-height) + var(--line-weight));
  opacity: 0.55;
}

.staff__notes {
  position: relative;
  flex: 1;
  height: 100%;
}

/* Clef, key and time signature -------------------------------------------
   Marked up on every bar, revealed only where a line starts. */

.staff__signature {
  position: relative;
  flex: 0 0 0;
  width: 0;
  overflow: hidden;
  transition: none;
}

.measure--line-start .staff__signature {
  flex-basis: var(--signature-width);
  width: var(--signature-width);
  overflow: visible;
}

.staff__signature .clef {
  left: calc(var(--staff-space) * 0.5);
}

.staff__signature .key-accidental {
  left: calc(var(--staff-space) * 3.4 + var(--index, 0) * var(--staff-space));
}

.time-signature {
  position: absolute;
  left: calc(var(--staff-space) * 5.6);
  top: 0;
}

.time-signature__beats {
  top: calc(var(--staff-space) * 1);
}

.time-signature__type {
  top: calc(var(--staff-space) * 3);
}

/* Events ------------------------------------------------------------------ */

.event {
  position: absolute;
  left: var(--start);
  top: 0;
  height: 100%;
  /* --start and --length are percentages of the bar, so the event must carry
     the width itself: anything sizing off a percentage inside it needs a
     non-zero containing block to resolve against. */
  width: var(--length);
}

.notehead {
  /* Centre the notehead on its time position rather than letting it start there. */
  margin-left: calc(var(--staff-space) * -0.58);
}

.accidental {
  margin-left: calc(var(--staff-space) * -2.1);
}

.dot {
  margin-left: calc(var(--staff-space) * (0.9 + var(--dot-index, 0) * 0.55));
}

.ledger {
  position: absolute;
  top: calc(var(--offset) * var(--staff-space));
  left: calc(var(--staff-space) * -1);
  width: calc(var(--staff-space) * 2);
  height: var(--line-weight);
  background: currentColor;
  opacity: 0.55;
}

/* Stems ------------------------------------------------------------------- */

.stem {
  position: absolute;
  width: var(--stem-weight);
  background: currentColor;
}

.stem--up {
  /* From the lowest notehead's right side, up past the highest. */
  left: calc(var(--staff-space) * 0.55);
  top: calc((var(--from) - 3.2) * var(--staff-space));
  height: calc((var(--to) - var(--from) + 3.2) * var(--staff-space));
}

.stem--down {
  left: calc(var(--staff-space) * -0.55);
  top: calc(var(--from) * var(--staff-space));
  height: calc((var(--to) - var(--from) + 3.2) * var(--staff-space));
}

.flag {
  left: 0;
}

.stem--up .flag {
  top: calc((var(--from) - 3.2) * var(--staff-space));
}

.stem--down .flag {
  top: calc((var(--to) + 3.2) * var(--staff-space));
}

/* Hold bars ---------------------------------------------------------------
   The instruction "keep holding", drawn as the note itself extending: one bar
   per note, at that note's own height and in that note's own colour.
   --completion is the only thing the practice engine touches, so nothing is
   redrawn during play.

   Thickness carries the same meaning as fill: what is still to play is a thin
   line, what has been played is a full-height bar. So a note visibly grows
   under the finger, and a bar's thickness alone says how far it has got. */

.hold {
  /* The track, and the played bar that grows along it. Both are centred on the
     note's own height, so thickening is symmetrical about the notehead. */
  --hold-thin: calc(var(--staff-space) * 0.36);
  --hold-thick: calc(var(--staff-space) * 0.84);

  position: absolute;
  /* Starts at the note's own time position — the notehead is centred there —
     and runs for exactly the note's duration. */
  left: 0;
  top: calc(var(--offset) * var(--staff-space) - var(--hold-thin) / 2);
  width: 100%;
  height: var(--hold-thin);
  border-radius: calc(var(--hold-thin) / 2);

  /* The track: the note's colour, showing how far there is to go. Faint enough
     to read as "not yet", solid enough that two stickers as close as grey and
     black stay apart — below about a third they converge on the paper. */
  background: color-mix(in srgb, currentColor 42%, transparent);
}

.hold--rest {
  color: var(--key-rest);
}

.hold__fill {
  position: absolute;
  left: 0;
  /* Negative: half the difference, which centres the thick bar on the thin
     track rather than hanging it below. */
  top: calc((var(--hold-thin) - var(--hold-thick)) / 2);
  width: calc(var(--completion, 0) * 100%);
  height: var(--hold-thick);
  border-radius: calc(var(--hold-thick) / 2);
  background: currentColor;
}

/* A note's fill is set every frame from how long the key has been held, so it
   must not be transitioned — CSS would lag behind the finger. A rest has
   nothing to hold, so it is set to full in one go and the transition is what
   makes it progressive. Silence never thickens either: a rest is time passing,
   not something being played. */
.hold--rest .hold__fill {
  top: 0;
  height: var(--hold-thin);
  border-radius: inherit;
  transition: width 400ms linear;
}

/* States ------------------------------------------------------------------ */

/* The note to play next. Marked by growing it rather than recolouring it —
   the colour is the thing being matched to a sticker. */
.event--current .notehead {
  transform: scale(1.18);
  transform-origin: center;
}

.event--current .hold {
  background: color-mix(in srgb, currentColor 62%, transparent);
}

/* A note that has been played stays in full colour, and its bar stays full:
   what the child has done is worth keeping visible. */
.event--done .hold__fill {
  opacity: 1;
}

.event--missed .notehead {
  color: #c0392b;
}

/* Rests ------------------------------------------------------------------- */

.event--rest .rest {
  opacity: 0.75;
}
