/* Deeper plum at the top than at the strike line, so a glowing tile has something dark to
   glow against as it falls, and the pink lift stays where the action is. */
.waterfall {
  position: relative; overflow: hidden;
  touch-action: none;   /* two fingers here are a pinch, not the browser's page zoom */
  /* No border, no radius: full-bleed means there is no card edge to draw. */
  background-color: #1a0f17;
  background-image:
    linear-gradient(90deg, rgba(255,255,255,.028) 0 1px, transparent 1px),
    radial-gradient(135% 56% at 50% 100%, rgba(255,79,154,.13), transparent 72%),
    linear-gradient(180deg, #170d15 0%, #23131e 58%, #2c1826 100%);
  background-size: calc(100% / var(--white-key-count, 18)) 100%, 100% 100%, 100% 100%;
  background-repeat: repeat-x, no-repeat, no-repeat;
}
.waterfall.drop-target { outline: 2px dashed var(--pink-light); outline-offset: -8px; }
/* Tiles used to appear at a hard edge. They now fade in over the top 76px. z-index lifts
   this above the notes, which sit at auto. */
.waterfall::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 76px; z-index: 5;
  pointer-events: none;
  background: linear-gradient(180deg, #170d15 6%, rgba(23,13,21,.7) 46%, transparent);
}
/* There is deliberately no ::after here any more. It used to draw a pink-to-cream gradient
   rule along the bottom edge — the "gradient line on top of the keyboard". With the keys'
   own light gone too (see keyboard.css), the strike reads off the plume and the key body,
   which is one statement instead of three stacked on the same 3px. */

/* Player's own artwork, under everything the score draws. Opacity is the transparency
   control; with no image the layer is a no-op. */
.waterfall-backdrop {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: var(--bg-image, none) center / cover no-repeat;
  opacity: var(--bg-alpha, 0);
}

/* Scale, drawn on the waterfall rather than in the chrome, because it stretches the
   waterfall's own vertical axis and nothing else.
   The handle is the point: it is a NOTE TILE, exactly square at 1.00x, and it stretches the way
   it stretches the tiles falling behind it. The control is a preview of its own effect, so
   there is little to read. It carries the tiles' own lit-glass top lip for the same reason.
   It grows FROM THE CENTRE and never travels. A thumb that also slid up the track was saying
   two things with one object — its position and its size — and they were redundant, since the
   size already tells you where you are in the range. Centred, the track stops being a slider
   rail and becomes a frame: the tile grows inside it until, at 2.00x, it fills it exactly.
   That is what makes the ceiling legible. (No 1.00x tick either: the handle being SQUARE is
   the tick, and the step is 0.05, so 1.00x is easy to land on.)
   It is a preview, not a scale model — see handleHeight() in settings.js.
   It is the quietest thing on screen until reached for: DESIGN.md 1 says the eye has to end up
   on the keyboard, so a tool lying on the picture recedes. The backdrop blur is not decoration
   — it is what keeps a hairline legible over moving tiles. */
.scale-control {
  --meter-h: 172px;
  /* 20px is the track's inner width, so the handle is exactly square at 1.00x. --handle-h is
     written by applyTheme() on the ROOT, which owns the curve (handleHeight() there).
     Do NOT declare --handle-h here as a "default": a custom property set on a descendant
     shadows the inherited one, so the handle would freeze at whatever this said. Fallbacks for
     it belong at the use site, in var(--handle-h, 20px). */
  --handle-w: 20px;
  position: absolute; z-index: 8;
  /* Flush to the window edge; the 7px of .scale-meter padding keeps the drawn track off it. */
  left: 0; top: calc(var(--header-h) + 22px);
  display: flex; flex-direction: column; align-items: center;
  color: var(--pink-soft);
  touch-action: none;
  /* It rides the chrome: a tool lying on the picture goes when the bar goes. Same trick as the
     header — the hit area stays at opacity 0, so hovering it is what brings it back, no JS. */
  opacity: 0; transition: opacity 200ms ease;
}
.app[data-chrome="on"] .scale-control,
.scale-control:hover,
.scale-control:focus-within,
.scale-control[data-live] { opacity: 1; }   /* a live drag outlives the bar's idle timer */

/* The hit box is 44px wide and the visible track is 30px inside it. A 30px-wide target misses
   the 44pt floor, and the honest fix is to widen the target rather than the drawing. */
.scale-meter {
  padding: 0 7px 7px; cursor: ns-resize;
  -webkit-tap-highlight-color: transparent;
}
.scale-track {
  position: relative; display: block;
  width: 30px; height: var(--meter-h);
  border: 1px solid rgba(255,255,255,.16); border-radius: 10px;
  background: rgba(255,255,255,.03);
  backdrop-filter: blur(4px);
  /* Faint until reached for — but only the track. The read-out keeps its own floor below, so
     the number never drops under the contrast bar just because the tool is at rest. */
  opacity: .5; transition: opacity 200ms ease;
}
.scale-control:hover .scale-track,
.scale-control:focus-within .scale-track,
.scale-control[data-live] .scale-track { opacity: 1; }
.scale-control:focus-within .scale-track { outline: 3px solid #ffd2e5; outline-offset: 2px; }

/* top 50% + translateY(-50%) is what makes it grow symmetrically: the transform is constant,
   so every pixel the height gains is split evenly above and below the centre.
   The stretch is a plain `height`, deliberately: the only composited way to change it is
   scaleY, and scaleY squashes the corner radius into ellipses — which is exactly what a note
   tile never does. One 20px-wide absolutely positioned box relaying out per frame is not the
   cost that rule is about. */
.scale-track i {
  position: absolute; top: 50%; left: 4px;
  width: var(--handle-w); height: var(--handle-h, 20px);
  transform: translateY(-50%);
  /* Proportional, not fixed: at 0.50x the handle is 10px tall and a flat 6px radius rounded it
     into a pill — which is the one shape a note tile is not. */
  border-radius: min(6px, calc(var(--handle-h, 20px) * .3));
  background: var(--pink-light);
  box-shadow:
    inset 0 1.5px 0 rgba(255,255,255,.6),
    0 0 10px color-mix(in srgb, var(--pink) 55%, transparent);
  transition: height 240ms cubic-bezier(.16, 1, .3, 1);
}
/* While a drag, wheel or pinch is live the handle sits under the finger, it does not chase it. */
.scale-control[data-live] i { transition: none; }

.scale-control b {
  font-size: var(--text-sm); font-weight: 800;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 3px rgba(0,0,0,.6);
  opacity: .72; transition: opacity 200ms ease;   /* floor: still ~7:1 at rest */
}
.scale-control:hover b,
.scale-control:focus-within b,
.scale-control[data-live] b { opacity: 1; }

/* A brighter rule at every C: the one gridline that tells you where you are. */
.octave-line {
  position: absolute; top: 0; bottom: 0; width: 1px; pointer-events: none;
  background: linear-gradient(180deg, rgba(255,255,255,.015), rgba(255,215,232,.14));
}

/* Anything that rides the fall: notes and measure barlines. Only what is mid-fall is on
   stage — a tile parked at its start position would sit at the top of the waterfall with its
   glow spilling over the fade. */
.falling { position: absolute; top: 0; visibility: hidden; }
.falling.playing {
  animation: fall calc((var(--fall-time, 4.4) * 1s + var(--hold, 0) * 1s) / var(--speed, 1)) linear;
  animation-delay: calc((var(--delay, 0) * 1s - var(--seek, 0) * 1s) / var(--speed, 1));
}
@keyframes fall {
  from { visibility: visible; transform: translate(-50%, 0); }
  to {
    visibility: visible;
    transform: translate(-50%, calc(var(--fall, 400px) + var(--hold, 0) * var(--fall, 400px) / var(--fall-time, 4.4)));
  }
}

/* The measure barline: the horizontal rule that falls with the music and lands on the strike
   line exactly when the bar starts. It is the one cue that turns a stream of tiles back into
   metre — without it a learner can see the notes but not the count. Full width, so it reads
   across the whole range; faint, so it never competes with a tile. */
.barline {
  left: 50%; width: 100%; height: 1px; z-index: 1; pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(255,215,232,.20) 6%, rgba(255,215,232,.20) 94%, transparent);
}
/* Downbeat of a phrase (every 4th bar) reads brighter, the way an engraver thickens a
   system barline — the eye needs somewhere to count from. */
.barline.strong { background: linear-gradient(90deg, transparent, rgba(255,215,232,.42) 6%, rgba(255,215,232,.42) 94%, transparent); }

/* A note is a zero-height anchor at its onset; the bar hangs above it. Keeping the falling
   box height-independent is what makes long notes strike on time. Never give .note a height. */
.note {
  left: 0; height: 0; width: 3%;   /* JS matches it to the key */
  transform: translate(-50%, 0);
  border: 0; padding: 0; background: none; color: white;
  font-size: 11px; font-weight: 850; cursor: pointer;
}
.note::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  /* The bar is exactly as long as the note is held: hold seconds x the fall's px-per-second.
     Speed cancels out, and it re-derives itself on resize because --fall does. */
  height: max(24px, calc(var(--hold, .5) * var(--fall, 400px) / var(--fall-time, 4.4)));
  border-radius: 8px; background: var(--note-fill);
  /* Lit glass, not a flat swatch: a bright top lip, a soft leading edge marking the part
     that actually lands on the line, and two blooms in the tile's own colour. */
  box-shadow:
    inset 0 1.5px 0 rgba(255,255,255,.55),
    inset 0 -4px 5px -2px rgba(255,255,255,.5),
    0 0 12px -2px color-mix(in srgb, var(--note-color) 62%, transparent),
    0 0 30px -6px color-mix(in srgb, var(--note-color) 48%, transparent);
}
.note-label {
  position: absolute; bottom: 6px; left: 0; right: 0; text-align: center;
  font-size: 9.5px; font-weight: 800; letter-spacing: .02em;
  color: rgba(255,255,255,.94); text-shadow: 0 1px 2px rgba(0,0,0,.4);
}
/* Black-key tiles are too narrow for a legible label; their column says which. */
.note.black-note .note-label { display: none; }
.app[data-labels="off"] .note-label { display: none; }

/* Tile colours, all derived from the hand colour so the settings panel can repaint a hand
   without leaving four literals behind. White-key tile: the hand colour with a lighter,
   desaturated lip. Black-key tile: the SAME hue, further down the lightness ramp — an
   outline was tried here and removed, the ramp is what separates them.
   The .66 is the whole tuning, and it is squeezed from BOTH sides: raise it and the black
   tile collapses into its white-key pair (l*.68 measured 2.56:1 on the left hand, under the
   2.65:1 the old hand-picked literals reached); lower it and the tile stops reading against
   the sky instead (l*.62 is 2.36:1 against #23131e). .66 measures 2.73:1 / 2.68:1 left and
   2.82:1 / 3.63:1 right. check.py asserts the pair side; DESIGN.md 2 has the workings.
   ponytail: still under the 3:1 non-text bar, and a player who picks a pale hand colour
   squeezes it further — the multiplier scales l rather than clamping it. */
.note.white-note {
  --note-color: var(--hand);
  --note-fill: linear-gradient(180deg, oklch(from var(--hand) calc(l + (1 - l) * .62) calc(c * .55) h) 0, var(--hand) 26px);
}
.note.black-note {
  --note-color: oklch(from var(--hand) calc(l * .86) c h);
  --note-fill: linear-gradient(180deg, oklch(from var(--hand) calc(l * .86) c h) 0, oklch(from var(--hand) calc(l * .66) c h) 26px);
}

.hint {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%);
  margin: 0; z-index: 7; text-align: center;
  padding: 9px 17px; border-radius: 999px;
  color: #d8b4c6; font-size: var(--text-md);
  background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.09);
  backdrop-filter: blur(8px);
}
.hint:empty { display: none; }   /* an empty status pill is worse than no pill */
