/* piano · dark stage.
   Anatomy borrowed from Soundtrap's instrument view: the keyboard is the page,
   everything above it is one thin, quiet console. Single accent, and the accent
   is whichever instrument is loaded, so the room changes colour with the sound.
   No cards, no rails, no chrome that isn't doing a job. */

:root {
    color-scheme: dark;

    --bg: #08080b;
    --bg-2: #0d0d12;
    --panel: #15151b;
    --panel-2: #1e1e26;
    --line: rgba(255, 255, 255, .08);
    --line-2: rgba(255, 255, 255, .16);
    --text: #edecf2;
    --muted: #a2a1b0;

    /* the grand's ivory gold, overridden per instrument on <body> */
    --accent: #e8c37e;
    --accent-rgb: 232 195 126;

    --r-sm: 8px;
    --r-md: 12px;
    --r-lg: 18px;

    --ease-out: cubic-bezier(.23, 1, .32, 1);
    --ease: cubic-bezier(.4, 0, .2, 1);

    --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

/* Per instrument accent. Kept to one hue each, all at roughly the same
   lightness so text sitting on the accent never changes contrast class. */
body[data-inst="piano"]     { --accent: #e8c37e; --accent-rgb: 232 195 126; }
body[data-inst="harmonium"] { --accent: #f2913d; --accent-rgb: 242 145  61; }
body[data-inst="epiano"]    { --accent: #5fd0bd; --accent-rgb:  95 208 189; }
body[data-inst="hammond"]   { --accent: #e8737a; --accent-rgb: 232 115 122; }
body[data-inst="pipeorgan"] { --accent: #9aa8ff; --accent-rgb: 154 168 255; }
body[data-inst="accordion"] { --accent: #93d363; --accent-rgb: 147 211  99; }
body[data-inst="synth"]     { --accent: #b48cff; --accent-rgb: 180 140 255; }
body[data-inst="musicbox"]  { --accent: #86c8f7; --accent-rgb: 134 200 247; }

* { box-sizing: border-box; }

/* class rules like .ctl { display: flex } outrank the UA [hidden] rule */
[hidden] { display: none !important; }

html, body {
    height: 100%;
    margin: 0;
    overscroll-behavior: none;
}

body {
    position: relative;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--bg);
    color: var(--text);
    font-family: var(--font);
    font-size: 14px;
    line-height: 1.5;
    -webkit-tap-highlight-color: transparent;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

button, select, input { font: inherit; color: inherit; touch-action: manipulation; }

/* iOS ignores user-scalable=no. manipulation on the whole page kills double-tap
   zoom wherever a fast tap lands (gate, plate, gaps); the keys and the felt keep
   their own touch-action: none, which is more specific and unaffected. */
html { touch-action: manipulation; }

/* An iPhone zooms the page toward any form control whose font is under 16px the
   moment it opens. Only the Sa select qualifies; pay the bigger type on touch
   screens instead of the zoom. */
@media (pointer: coarse) {
    select { font-size: 16px; }
}

svg { width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }

/* One ring everywhere, so focus never has to be guessed at. */
:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 4px;
}

/* ---------- gesture gate ---------- */
/* The gate sits on top of the finished instrument rather than in front of a
   blank page: the board is already drawn behind the blur, so dismissing it
   reveals the thing you came for instead of loading it. */

.gate {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: grid;
    place-items: center;
    padding: 24px;
    background: rgba(6, 6, 9, .64);
    -webkit-backdrop-filter: blur(11px) saturate(.9);
    backdrop-filter: blur(11px) saturate(.9);
    text-align: center;
    transition: opacity .26s var(--ease-out), backdrop-filter .26s var(--ease-out);
}

.gate::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 44%;
    width: min(620px, 92vw);
    height: 320px;
    transform: translate(-50%, -50%);
    background: rgb(var(--accent-rgb) / .16);
    -webkit-mask-image: radial-gradient(closest-side, #000, transparent);
    mask-image: radial-gradient(closest-side, #000, transparent);
    pointer-events: none;
}

.gate.gone {
    opacity: 0;
    pointer-events: none;
    -webkit-backdrop-filter: blur(0) saturate(1);
    backdrop-filter: blur(0) saturate(1);
}

.gate-inner { position: relative; max-width: 30rem; }

.gate-title {
    margin: 0 0 14px;
    font-size: clamp(46px, 13vw, 86px);
    font-weight: 600;
    letter-spacing: -.045em;
    line-height: 1;
    color: var(--accent);
}

.gate-sub {
    margin: 0 0 26px;
    color: var(--muted);
    font-size: clamp(14px, 3.6vw, 16px);
    text-wrap: balance;
}

.gate-line {
    margin: 0;
    color: var(--text);
    font-size: 15px;
}

.gate-hi { color: var(--accent); }

.gate-status { margin: 0; color: var(--accent); font-size: 15px; }
.gate-status.err { color: #ff9d8b; }

.coarse { display: none; }

@media (pointer: coarse) {
    .fine { display: none; }
    .coarse { display: inline; }
}

/* ---------- top bar ---------- */

.topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 11px 18px;
    padding-left: max(18px, env(safe-area-inset-left));
    padding-right: max(18px, env(safe-area-inset-right));
    flex: 0 0 auto;
}

.wordmark {
    font-size: 14px;
    font-weight: 600;
    letter-spacing: -.012em;
    white-space: nowrap;
}

.wordmark .host { color: var(--muted); font-weight: 400; }
.sep { color: var(--muted); }

.topbar-actions { display: flex; align-items: center; gap: 8px; }

.icon-btn {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    padding: 8px;
    border: 1px solid var(--line);
    border-radius: 50%;
    background: var(--panel);
    color: var(--muted);
    cursor: pointer;
    transition: color .14s var(--ease), border-color .14s var(--ease), transform .12s var(--ease-out);
}

.icon-btn.is-q {
    padding: 0;
    font-size: 14px;
    font-weight: 600;
    line-height: 1;
}

.icon-btn:active { transform: scale(.94); }

@media (hover: hover) and (pointer: fine) {
    .icon-btn:hover { color: var(--accent); border-color: rgb(var(--accent-rgb) / .55); }
}

/* the two fullscreen glyphs swap, so only one is ever in the box */
#fsBtn .i-shrink { display: none; }
body.is-fs #fsBtn .i-expand { display: none; }
body.is-fs #fsBtn .i-shrink { display: block; }

/* ---------- instrument rail ---------- */

.rail {
    display: flex;
    gap: 8px;
    padding: 2px 18px 12px;
    padding-left: max(18px, env(safe-area-inset-left));
    padding-right: max(18px, env(safe-area-inset-right));
    overflow-x: auto;
    scrollbar-width: none;
    flex: 0 0 auto;
}

.rail::-webkit-scrollbar { display: none; }

.inst {
    flex: 0 0 auto;
    padding: 7px 14px;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--panel);
    color: var(--muted);
    font-size: 13px;
    white-space: nowrap;
    cursor: pointer;
    transition: color .16s var(--ease), background-color .16s var(--ease),
                border-color .16s var(--ease), transform .12s var(--ease-out);
}

.inst:active { transform: scale(.97); }

@media (hover: hover) and (pointer: fine) {
    .inst:hover:not(.is-active) { color: var(--text); border-color: var(--line-2); }
}

.inst.is-active {
    background: rgb(var(--accent-rgb) / .14);
    border-color: rgb(var(--accent-rgb) / .5);
    color: var(--accent);
    cursor: default;
    transform: none;
}

.inst[disabled] { opacity: .5; cursor: not-allowed; }

/* Loading a set is a few megabytes, so the control has to say something. It
   breathes rather than growing a spinner, because the rail scrolls sideways and
   anything that changes a chip's width would shove the others along. The sur
   toggle borrows it: the tanpura is its own download. */
.inst.is-loading, .pill.is-loading {
    color: var(--accent);
    border-color: rgb(var(--accent-rgb) / .35);
    animation: chip-wait 1.1s var(--ease) infinite;
}

@keyframes chip-wait {
    50% { opacity: .45; }
}

.inst.is-error { border-color: #e2564d; color: #ff9d8b; }

/* ---------- controls ---------- */
/* One line, always. The tanpura group is pushed to the far right so that its
   appearing and disappearing with the harmonium cannot nudge anything else. */

.controls {
    display: flex;
    align-items: center;
    gap: 22px;
    min-height: 44px;
    padding: 0 18px 12px;
    padding-left: max(18px, env(safe-area-inset-left));
    padding-right: max(18px, env(safe-area-inset-right));
    overflow-x: auto;
    scrollbar-width: none;
    flex: 0 0 auto;
}

.controls::-webkit-scrollbar { display: none; }

.ctl { display: flex; align-items: center; gap: 9px; flex: 0 0 auto; }
.ctl-group { display: flex; align-items: center; gap: 22px; flex: 0 0 auto; }

/* Volume is the last thing in the chain, so it is pinned to the far end and the
   tanpura group appears into the slack in the middle. That is the whole trick
   for the harmonium not shoving the console around when it arrives. */
.ctl.vol { margin-left: auto; }

.ctl-label {
    font-size: 10.5px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: .1em;
    color: var(--muted);
    white-space: nowrap;
}

.stepper {
    display: flex;
    align-items: center;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--panel);
    overflow: hidden;
}

.step {
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    padding: 8px;
    border: 0;
    background: none;
    color: var(--muted);
    cursor: pointer;
    transition: color .14s var(--ease), background-color .14s var(--ease);
}

.step:focus-visible { outline-offset: -2px; }

@media (hover: hover) and (pointer: fine) {
    .step:hover:not([disabled]) { background: var(--panel-2); color: var(--accent); }
}

.step[disabled] { opacity: .3; cursor: not-allowed; }

.stepper .value {
    min-width: 30px;
    text-align: center;
    font-variant-numeric: tabular-nums;
    font-size: 13px;
    border-left: 1px solid var(--line);
    border-right: 1px solid var(--line);
    line-height: 30px;
}

.pill {
    height: 30px;
    padding: 0 13px;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--panel);
    color: var(--text);
    font-size: 13px;
    cursor: pointer;
    min-width: 82px;
    transition: color .14s var(--ease), background-color .14s var(--ease),
                border-color .14s var(--ease), transform .12s var(--ease-out);
}

.pill:active { transform: scale(.97); }

@media (hover: hover) and (pointer: fine) {
    .pill:hover:not([disabled]) { border-color: var(--line-2); color: var(--accent); }
}

.pill[disabled] { opacity: .4; cursor: not-allowed; }

/* lit while the tanpura is actually running */
.pill.is-on {
    background: rgb(var(--accent-rgb) / .14);
    border-color: rgb(var(--accent-rgb) / .5);
    color: var(--accent);
}

/* Sa picker. Left a real select so a phone opens its own wheel, restyled only
   enough to sit in the same row as the pills without the grey OS chrome. */
.pill.sa {
    min-width: 62px;
    padding: 0 10px;
    text-align: center;
    appearance: none;
    -webkit-appearance: none;
}

/* the dropdown itself is drawn by the OS, and on a dark page it needs telling */
.pill.sa option { background: var(--panel); color: var(--text); }

.vol input[type="range"] {
    width: 112px;
    height: 20px;
    margin: 0;
    accent-color: var(--accent);
    cursor: pointer;
}

/* ---------- stage ---------- */

.stage {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    /* the glow is deliberately wider than the page, and without this it would
       hand a narrow screen a horizontal scrollbar it has no use for */
    overflow: hidden;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* The plate's auto top margin already eats every pixel of free space, so on
       a roomy screen this is the same picture as centring. It only shows up
       when there is no free space left, and then it decides which end the
       shortfall comes off: the empty air above, never the keys. */
    justify-content: flex-end;
    gap: 16px;
    padding: 8px 16px calc(16px + env(safe-area-inset-bottom));
}

/* What you are holding. It is the only thing standing between the console and
   the keys, so it does the job the empty air was not doing: it names the sound,
   says in one line what it is for, and gives switching instruments a visible
   result above the board as well as under your fingers. */
.plate {
    position: relative;
    /* Never shrinks. A shrinking box whose text does not shrink with it is how
       a short screen ends up with the blurb printed across the keys; when the
       height really runs out the short rules below take the plate away
       outright instead of squeezing it into nothing. */
    flex: 0 0 auto;
    /* Sits low, just above the keys, so the plate and the board read as one
       object and the leftover height stacks up as headroom instead of splitting
       into two puddles of nothing. */
    margin-top: auto;
    margin-bottom: max(18px, 6vh);
    padding: 8px 0 4px;
    text-align: center;
}

.plate-name {
    margin: 0 0 8px;
    font-size: clamp(24px, 3.4vw, 38px);
    font-weight: 600;
    letter-spacing: -.035em;
    line-height: 1.1;
    text-wrap: balance;
}

.plate-line {
    margin: 0;
    max-width: 34rem;
    color: var(--muted);
    font-size: 14px;
    text-wrap: balance;
}

.plate.swap { animation: plate-in .24s var(--ease-out); }

@keyframes plate-in {
    from { opacity: 0; transform: translateY(5px); }
}

/* The room takes the instrument's colour. One flat block behind a radial mask,
   because a background-colour is the only part of a gradient that will actually
   transition when the instrument changes. */
.stage::before {
    content: "";
    position: absolute;
    left: 50%;
    bottom: -12%;
    width: min(1500px, 130%);
    height: min(760px, 108%);
    transform: translateX(-50%);
    background: rgb(var(--accent-rgb) / .11);
    -webkit-mask-image: radial-gradient(closest-side, #000, transparent);
    mask-image: radial-gradient(closest-side, #000, transparent);
    transition: background-color .4s var(--ease);
    pointer-events: none;
}

/* Capped so a wide monitor gets a keyboard, not a mural: past about 38px a
   white key stops looking like a white key. */
.board-frame {
    position: relative;
    width: 100%;
    max-width: 1370px;
    min-height: 0;
    flex: 0 0 auto;
}

/* The strip of felt every upright has behind the keys. Takes a breath of the
   instrument's colour so the board is never a flat white slab on black. */
/* Also the only place on the instrument you are allowed to grab: the keys own
   every touch that lands on them, so without this a phone could never reach the
   part of the board that is off the side of the screen. */
.felt {
    position: relative;
    height: 11px;
    border-radius: 4px 4px 0 0;
    background: rgb(var(--accent-rgb) / .34);
    box-shadow:
        inset 0 4px 6px -3px rgba(0, 0, 0, .85),
        inset 0 -3px 4px -2px rgba(0, 0, 0, .6),
        0 3px 7px rgba(0, 0, 0, .65);
    transition: background-color .4s var(--ease);
    touch-action: none;
}

/* the grip only turns up when there is somewhere to drag to */
.can-l .felt, .can-r .felt { cursor: grab; }
.can-l .felt:active, .can-r .felt:active { cursor: grabbing; }

.can-l .felt::after, .can-r .felt::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: 42px;
    height: 3px;
    transform: translate(-50%, -50%);
    border-radius: 2px;
    background: rgba(255, 255, 255, .34);
}

/* Rounded parent clips, inner div scrolls. */
.board-scroll {
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, .14) transparent;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
}

.board-scroll::-webkit-scrollbar { height: 6px; }
.board-scroll::-webkit-scrollbar-track { background: transparent; }
.board-scroll::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .14); border-radius: 3px; }

/* Says "there is more board off to the side" without a control to explain. */
.edge {
    position: absolute;
    top: 9px;
    bottom: 0;
    width: 28px;
    pointer-events: none;
    opacity: 0;
    transition: opacity .2s var(--ease);
}

.edge-l { left: 0; background: linear-gradient(90deg, var(--bg), transparent); }
.edge-r { right: 0; background: linear-gradient(270deg, var(--bg), transparent); }

.board-frame.can-l .edge-l, .board-frame.can-r .edge-r { opacity: .85; }

.hint {
    margin: 0;
    flex: 0 0 auto;   /* same reason as .plate: shrink the box, keep the words */
    color: var(--muted);
    font-size: 12.5px;
    text-align: center;
}

.pedal { transition: color .12s var(--ease); }
body.sustain .pedal { color: var(--accent); }

/* ---------- the keyboard ---------- */
/* Whites are a flex row, blacks ride on top of the boundaries, so the 2 then 3
   grouping falls out of the note layout for free. The 5.63 ratio is 36 white
   keys at the real 23.5 : 150 proportion, so the board is the shape of an
   actual keyboard at whatever width it gets. */

.board {
    --whites: 36;
    --wk-min: 0px;
    position: relative;
    width: 100%;
    min-width: calc(var(--whites) * var(--wk-min));
    aspect-ratio: 5.63;
    max-height: min(250px, 42vh);
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
}

/* iOS counts vh against the viewport it would have with the toolbars hidden,
   which is not the viewport the keys are drawn in. svh is the one that shrinks
   with the toolbars, and everywhere else it is the same number. */
@supports (height: 1svh) {
    .board { max-height: min(250px, 42svh); }
}

.whites { display: flex; height: 100%; }

.blacks { position: absolute; inset: 0; pointer-events: none; }

/* Only the travel is animated. The light under a struck key is instant, the
   way a hammer is instant, and leaving colour out of the transition keeps a
   ten-finger chord off the paint path entirely. */
.key {
    position: relative;
    cursor: pointer;
    transition: transform .09s var(--ease-out);
}

/* A real key hinges at the back, so it tips rather than slides. */
.key.lit { transform: perspective(340px) rotateX(1.8deg); }

.key .cap, .key .oct {
    position: absolute;
    left: 0;
    right: 0;
    text-align: center;
    line-height: 1;
    pointer-events: none;
}

.key .cap:empty, .key .oct:empty { display: none; }

.key.white {
    flex: 1 1 0;
    min-width: 0;
    transform-origin: 50% 0;
    border-right: 1px solid #a8a8b6;
    border-radius: 0 0 5px 5px;
    background: linear-gradient(#fdfdff 0%, #f3f3f7 74%, #dededf 100%);
    box-shadow:
        inset 0 -8px 9px -7px rgba(20, 20, 30, .5),
        inset 2px 0 3px -3px rgba(20, 20, 30, .5),
        inset -2px 0 3px -3px rgba(20, 20, 30, .5);
}

.whites .key.white:last-child { border-right: 0; }

.key.white .cap { bottom: 19px; color: #55545f; font-size: 11px; }

/* C markers keep their own line right at the front of the key, so the labels
   above them stay on one straight row across the whole board. */
.key.white .oct { bottom: 5px; color: #5f5d6b; font-size: 9px; font-variant-numeric: tabular-nums; }

/* in notes mode the label already says C4, so the marker would say it twice */
.board[data-labels="notes"] .key .oct { display: none; }

.key.black {
    position: absolute;
    top: 0;
    height: 62%;
    width: calc(100% / var(--whites) * .60);
    transform: translateX(-50%);
    transform-origin: 50% 0;
    pointer-events: auto;
    border-radius: 0 0 4px 4px;
    background: linear-gradient(#41414d 0%, #22222b 52%, #0c0c11 100%);
    box-shadow:
        0 4px 6px rgba(0, 0, 0, .6),
        inset 0 -3px 3px -2px rgba(255, 255, 255, .14),
        inset 2px 0 2px -2px rgba(255, 255, 255, .1);
    z-index: 2;
}

.key.black.lit { transform: translateX(-50%) perspective(340px) rotateX(1.8deg); }

.key.black .cap { bottom: 8px; color: #d5d4e0; font-size: 9.5px; }

/* Letters mode draws the typing key as the keycap it actually is. Note and
   sargam names stay plain type, because they are music, not hardware. */
.board[data-labels="letters"] .key .cap {
    left: 50%;
    right: auto;
    transform: translateX(-50%);
    min-width: 16px;
    padding: 3px 3.5px 2.5px;
    border-radius: 4px;
    font-family: var(--mono);
    font-size: 10px;
    font-weight: 500;
}

/* A keycap sitting on ivory has to be lighter than the key it sits on, or the
   two just merge. White fill, real border, dark letter. */
.board[data-labels="letters"] .key.white .cap {
    background: #fff;
    border: 1px solid #a7a6b6;
    color: #3c3b47;
    box-shadow: 0 1px 1.5px rgba(30, 30, 45, .18);
}

.board[data-labels="letters"] .key.black .cap {
    background: rgba(255, 255, 255, .16);
    border: 1px solid rgba(255, 255, 255, .22);
    color: #eceaf5;
    bottom: 7px;
}

/* Keys the QWERTY rows can reach right now: lit from underneath rather than
   badged, so 32 of them at once still read as one region of the board. */
.key.white.in-window {
    box-shadow:
        inset 0 -17px 14px -12px rgb(var(--accent-rgb) / .95),
        inset 0 -8px 9px -7px rgba(20, 20, 30, .38),
        inset 2px 0 3px -3px rgba(20, 20, 30, .5),
        inset -2px 0 3px -3px rgba(20, 20, 30, .5);
}

.key.black.in-window {
    box-shadow:
        0 4px 6px rgba(0, 0, 0, .6),
        inset 0 -10px 9px -8px rgb(var(--accent-rgb) / .85),
        inset 2px 0 2px -2px rgba(255, 255, 255, .1);
}

.key.lit.white {
    background: linear-gradient(#ffffff 0%, rgb(var(--accent-rgb) / .34) 62%, rgb(var(--accent-rgb) / .62) 100%);
    box-shadow: inset 0 -12px 12px -8px rgb(var(--accent-rgb) / .55);
}

.key.lit.black {
    background: linear-gradient(#3d3c49 0%, #2a2935 46%, rgb(var(--accent-rgb) / .5) 100%);
    box-shadow: 0 2px 4px rgba(0, 0, 0, .6), inset 0 -12px 11px -7px rgb(var(--accent-rgb) / .75);
}

/* ---------- help sheet ---------- */

.scrim {
    position: fixed;
    inset: 0;
    z-index: 70;
    background: rgba(5, 5, 8, .68);
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
    opacity: 0;
    transition: opacity .18s var(--ease-out);
}

.scrim.in { opacity: 1; }

.sheet {
    position: fixed;
    z-index: 71;
    left: 50%;
    top: 50%;
    width: min(600px, calc(100vw - 32px));
    max-height: min(78vh, 720px);
    display: flex;
    flex-direction: column;
    border: 1px solid var(--line-2);
    border-radius: var(--r-lg);
    background: #101015;
    box-shadow: 0 32px 80px rgba(0, 0, 0, .7), 0 0 0 1px rgba(0, 0, 0, .4);
    transform: translate(-50%, -50%) scale(.97);
    opacity: 0;
    transition: opacity .18s var(--ease-out), transform .18s var(--ease-out);
}

.sheet.in { transform: translate(-50%, -50%) scale(1); opacity: 1; }

/* it only ever takes focus programmatically, and a ring round the whole panel
   would just look like an error */
.sheet:focus:not(:focus-visible) { outline: none; }

.sheet-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 16px 16px 12px 20px;
    border-bottom: 1px solid var(--line);
    flex: 0 0 auto;
}

.sheet-head h2 {
    margin: 0;
    font-size: 15px;
    font-weight: 600;
    letter-spacing: -.01em;
}

.sheet-body {
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 4px 20px 22px;
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, .16) transparent;
}

.sheet-body::-webkit-scrollbar { width: 8px; }
.sheet-body::-webkit-scrollbar-track { background: transparent; }
.sheet-body::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .16); border-radius: 4px; }

.hsec { padding: 18px 0; border-bottom: 1px solid var(--line); }
.hsec:last-child { border-bottom: 0; padding-bottom: 2px; }

.hsec h3 {
    margin: 0 0 10px;
    font-size: 10.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .12em;
    color: var(--accent);
}

.hsec p { margin: 0 0 10px; color: var(--muted); font-size: 13.5px; line-height: 1.65; }
.hsec p:last-child { margin-bottom: 0; }
.hsec b { color: var(--text); font-weight: 600; }
.hsec .note { color: #8b8a99; font-size: 12.5px; }

.rows {
    display: grid;
    grid-template-columns: minmax(78px, max-content) 1fr;
    gap: 10px 14px;
    margin: 0;
    align-items: baseline;
}

.rows dt { display: flex; flex-wrap: wrap; gap: 4px; }
.rows dd { margin: 0; color: var(--muted); font-size: 13.5px; line-height: 1.6; }

kbd, .kbd-word {
    display: inline-block;
    padding: 2px 7px;
    border: 1px solid var(--line-2);
    border-bottom-width: 2px;
    border-radius: 5px;
    background: var(--panel-2);
    color: var(--text);
    font-family: var(--mono);
    font-size: 11.5px;
    line-height: 1.35;
    white-space: nowrap;
}

.kbd-word { font-family: var(--font); font-size: 12px; }

.credits { margin: 0; padding-left: 17px; color: var(--muted); font-size: 12.5px; line-height: 1.75; }

/* the mini board: the real mapping, drawn small, so it can never drift from it */
.mini {
    position: relative;
    height: 88px;
    margin-top: 14px;
    border-radius: 3px 3px 6px 6px;
    background: #0a0a0e;
    overflow: hidden;
}

.mini-whites { display: flex; height: 100%; }
.mini-blacks { position: absolute; inset: 0; }

.mini .mk {
    position: relative;
    border-right: 1px solid #a8a8b6;
    border-radius: 0 0 3px 3px;
    background: linear-gradient(#fdfdff 0%, #f1f1f5 78%, #dcdce0 100%);
    flex: 1 1 0;
}

.mini .mk:last-child { border-right: 0; }

.mini .mk.b {
    position: absolute;
    top: 0;
    height: 60%;
    width: calc(100% / var(--mw, 19) * .6);
    transform: translateX(-50%);
    border-right: 0;
    border-radius: 0 0 2px 2px;
    background: linear-gradient(#41414d 0%, #22222b 55%, #0c0c11 100%);
    flex: none;
}

.mini .mcap {
    position: absolute;
    left: 50%;
    bottom: 6px;
    transform: translateX(-50%);
    min-width: 13px;
    padding: 2px 2px 1.5px;
    border-radius: 3px;
    font-family: var(--mono);
    font-size: 8.5px;
    line-height: 1;
    text-align: center;
}

.mini .mk .mcap { background: #e7e6ee; border: 1px solid #c2c1cf; color: #43424e; }
.mini .mk.b .mcap { background: rgba(255, 255, 255, .18); border: 1px solid rgba(255, 255, 255, .24); color: #eceaf5; }

/* ---------- narrow ---------- */

@media (max-width: 900px) {
    /* Below this the 36 white keys would be thinner than a fingertip, so the
       board keeps a real key width and slides sideways instead. */
    .board { --wk-min: 33px; }
}

@media (max-width: 700px) {
    .topbar { padding-top: 10px; padding-bottom: 9px; }
    .wordmark { font-size: 13px; }
    .rail { gap: 7px; padding-bottom: 10px; }
    .inst { padding: 6px 12px; font-size: 12.5px; }
    .controls { gap: 16px; min-height: 40px; padding-bottom: 10px; }
    .ctl { gap: 7px; }
    .ctl-group { gap: 16px; }
    .vol input[type="range"] { width: 88px; }
    .stage { gap: 12px; }
    .sheet { width: calc(100vw - 20px); max-height: 84vh; border-radius: 16px; }
    .sheet-head { padding: 14px 12px 11px 16px; }
    .sheet-body { padding: 2px 16px 20px; }
    .rows { grid-template-columns: 1fr; gap: 3px 0; }
    .rows dt { margin-top: 8px; }
    .rows dt:first-child { margin-top: 0; }
    .mini { height: 74px; }
}

/* A phone cannot fit Octave, Labels and Volume side by side and still say what
   they are, so the words go above the controls instead of beside them. Costs
   one line of height, buys the whole row fitting on screen. */
@media (max-width: 560px) {
    .controls { align-items: flex-end; gap: 14px; min-height: 48px; }
    .ctl { flex-direction: column; align-items: flex-start; gap: 4px; }
    .ctl-group { gap: 14px; align-items: flex-end; }
    .vol input[type="range"] { width: 104px; }
}

/* No physical arrow keys and no space bar, so the line under the board stops
   talking about them and says the one thing a finger needs instead. */
@media (pointer: coarse) {
    .stage { padding-bottom: calc(10px + env(safe-area-inset-bottom)); }
    .felt { height: 16px; }

    /* The strip is the only way to reach the two thirds of the board that are
       off the side of a phone, so it has to be grabbable, and 16px is under the
       24px minimum. 24px of visible felt would be a cushion rather than a felt,
       so the extra 8px is invisible and reaches up into the empty air above the
       board. A press on a pseudo element is a press on .felt, so the drag
       wiring does not change. */
    .felt::before {
        content: "";
        position: absolute;
        left: 0;
        right: 0;
        top: -8px;
        height: 8px;
    }
}

/* ---------- short ---------- */
/* The width pass was only half the story. A phone in landscape, which is the
   natural way to hold a keyboard, has width to spare and almost no height, and
   nothing here was reading height at all. Height comes off in the order the
   keys care least about: the plate's headroom, then its blurb, then the plate
   itself, then the line under the board. The board keeps its proportions all
   the way down and nothing is ever painted over it. */

@media (max-height: 700px) {
    .stage { gap: 12px; padding-top: 4px; }
    .plate { margin-bottom: max(12px, 2.5vh); padding: 4px 0 2px; }
    .plate-name { font-size: clamp(21px, 2.8vw, 30px); margin-bottom: 5px; }
}

@media (max-height: 560px) {
    /* The console gives up its padding here rather than lower down, because a
       narrow screen stacks its labels above its controls and that costs the row
       another line just as the height is running out. */
    .topbar { padding-top: 7px; padding-bottom: 7px; }
    .rail { padding-bottom: 8px; }
    .controls { min-height: 38px; padding-bottom: 8px; }
    .stage { gap: 10px; }
    .plate { margin-bottom: 10px; padding: 0; }
    /* the name still earns its line; the sentence under it does not */
    .plate-name { font-size: clamp(18px, 2.4vw, 24px); margin-bottom: 0; }
    .plate-line { display: none; }
}

@media (max-height: 460px) {
    /* Nothing above the keys survives this. The lit chip in the rail already
       says what is playing, so the plate would only be repeating it. */
    .stage { gap: 8px; padding-bottom: calc(8px + env(safe-area-inset-bottom)); }
    .plate { display: none; }
    .board { max-height: min(250px, 46vh); }
}

@supports (height: 1svh) {
    @media (max-height: 460px) {
        .board { max-height: min(250px, 46svh); }
    }
}

@media (max-height: 370px) {
    /* Below this the line under the board is the last thing left to give, and
       a board with room to be a board beats a sentence about it. */
    .hint { display: none; }
}

/* The ladder above was measured with a mouse on a desktop, and a phone spends
   about 26px that no height query can see: the felt is 16px on a touch screen
   instead of 11, and viewport-fit=cover hands back a safe area at the bottom
   that is around 21px on a notched phone in landscape. So the tiers arrive too
   late there, and because nothing shrinks any more, the whole shortfall lands
   on the felt, which on a phone is the only way to reach the rest of the board.
   Same rules, same values, just triggered earlier where the cost is higher. */

@media (pointer: coarse) and (max-height: 500px) {
    .stage { gap: 8px; padding-bottom: calc(8px + env(safe-area-inset-bottom)); }
    .plate { display: none; }
    .board { max-height: min(250px, 46vh); }
}

/* restated in svh for the same reason as the 460 tier, and it has to sit after
   that tier's own @supports block or the vh value above would win */
@supports (height: 1svh) {
    @media (pointer: coarse) and (max-height: 500px) {
        .board { max-height: min(250px, 46svh); }
    }
}

@media (pointer: coarse) and (max-height: 410px) {
    .hint { display: none; }
}

/* ---------- reduced motion ---------- */

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

    .inst.is-loading, .pill.is-loading { animation: none !important; opacity: .6; }

    /* keep the state readable, drop the movement */
    .key.lit, .key.black.lit { transform: none; }
    .key.black.lit { transform: translateX(-50%); }
    .sheet { transform: translate(-50%, -50%) scale(1); }
}
