/* crash* — faceplate styles */
:root {
  --outer: #100f0d;
  --plate: #dcd8ce;
  --plate-2: #e2ded4;
  --inset: #e9e6db;
  --paper: #f2efe6;
  --line: #d0cbc0;
  --line-2: #bfb9ad;
  --ink: #1d1c19;
  --ink-2: #3d3a35;
  --muted: #6d6860;
  --muted-2: #857f74;
  --faint: #a39d92;
  --accent: #f2681c;
  --accent-deep: #d9540d;
  --on-accent: #fdfcf8;
  --t-kck: #f2681c; --t-hat: #8f8a7c; --t-snr: #a5684d; --t-bss: #2c6fb0; --t-chd: #4c6a56; --t-led: #9a7426;
  --mono: Archivo, "Helvetica Neue", Helvetica, Arial, sans-serif;
  --sans: Archivo, "Helvetica Neue", Helvetica, Arial, sans-serif;
  --sel: #f2681c;
  --sel-ink: #fdfcf8;
  --r: 4px;
  --gutter: 16px;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; background: var(--plate); }
body { margin: 0; text-transform: lowercase; font-variant-numeric: tabular-nums; font-family: var(--sans); color: var(--ink); background: var(--plate); -webkit-tap-highlight-color: transparent; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
button, select, input { text-transform: inherit; letter-spacing: inherit; }
a { color: inherit; }
img { display: block; max-width: 100%; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
[hidden] { display: none !important; }

.label, .kicker, .panel-bar, .side-head, .tabs a, .status, .foot, .con-bar, .mlabel {
  font-family: var(--mono); letter-spacing: 0.02em;
}

/* ── frame ─────────────────────────────── */
.app { display: grid; align-content: start; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto auto auto auto; min-height: 100vh; }

.top {
  display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 18px;
  height: 54px; padding: 0 var(--gutter); border-bottom: 1px solid var(--line);
}
.logo { font-weight: 800; font-size: 22px; letter-spacing: -0.03em; text-decoration: none; }
.logo span { color: var(--accent); }
.tagline { margin: 0; padding-left: 16px; border-left: 1px solid var(--line-2); font-family: var(--mono); font-size: 13px; letter-spacing: 0.02em; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tagline span + span::before { content: "·"; margin: 0 0.7em; color: var(--faint); }
.status { display: flex; align-items: center; gap: 14px; font-size: 13px; color: var(--muted); }
.meters { display: flex; align-items: flex-end; gap: 2px; height: 14px; }
.meters i { width: 3px; height: 15%; background: var(--line-2); transition: height 60ms linear; }
.meters i.lo { background: var(--accent); } .meters i.mi { background: #c7922d; } .meters i.hi { background: #7d8c80; }
.status-bpm { color: var(--ink); font-weight: 600; min-width: 62px; text-align: right; }
.status-led { width: 7px; height: 7px; border-radius: 50%; background: var(--line-2); }
.status-led.on { background: var(--accent); box-shadow: 0 0 8px var(--accent); }

.tabs { display: flex; border-bottom: 1px solid var(--line); overflow-x: auto; scrollbar-width: none; position: sticky; top: 0; z-index: 20; background: var(--plate); }
.tabs::-webkit-scrollbar { display: none; }
.tabs a { flex: 1 0 auto; display: flex; align-items: center; gap: 9px; min-height: 42px; padding: 0 18px; border-right: 1px solid var(--line); font-size: 15px; font-weight: 600; text-decoration: none; color: var(--ink-2); }
.tabs a b { font-family: var(--sans); font-weight: 700; color: var(--faint); }
.tabs a:hover { background: var(--plate-2); }
.tabs a[aria-current="page"] { background: var(--accent); color: var(--on-accent); }
.tabs a[aria-current="page"] b { color: var(--on-accent); }

/* ── stage / panels ───────────────────── */
.stage { position: relative; padding: 12px var(--gutter); }
.viz { position: absolute; inset: 12px var(--gutter); width: calc(100% - 2 * var(--gutter)); height: calc(100% - 24px); pointer-events: none; z-index: 0; border-radius: 5px; }
.panel { position: relative; z-index: 1; border: 1px solid var(--line); border-radius: 5px; background: transparent; overflow: hidden; margin-bottom: 12px; }
.js .panel { display: none; margin-bottom: 0; }
.js .panel.active { display: grid; grid-template-rows: auto minmax(0, 1fr); animation: rise 260ms ease; }
@keyframes rise { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }

.panel-bar { display: flex; justify-content: space-between; gap: 12px; padding: 5px 10px; font-size: 12px; color: var(--muted); border-bottom: 1px solid var(--line); background: color-mix(in srgb, var(--plate) 85%, transparent); }
.panel-bar span:first-child { display: flex; align-items: center; gap: 8px; color: var(--ink-2); }
.panel-bar span:first-child::before { content: ""; width: 7px; height: 7px; background: var(--accent); border: 1px solid var(--muted); }

.panel-body { display: grid; grid-template-columns: minmax(0, 1fr) 300px; min-height: 0; }
.panel-main { position: relative; z-index: 1; min-width: 0; padding: 18px 20px 22px; display: grid; align-content: start; gap: 12px; }
.panel-body { position: relative; }
/* No backing colour: the mask has to fade the photo out to transparent, not
   to the page colour, or the opaque rectangle behind it slices through the
   asterisk wherever the two overlap. */
.photo { position: absolute; top: 0; right: 300px; bottom: 0; width: min(46%, 600px); z-index: 0; overflow: hidden; pointer-events: none;
  --fade-x: linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(0,0,0,.02) 6%, rgba(0,0,0,.08) 13%, rgba(0,0,0,.18) 20%, rgba(0,0,0,.32) 27%, rgba(0,0,0,.48) 34%, rgba(0,0,0,.64) 41%, rgba(0,0,0,.78) 48%, rgba(0,0,0,.89) 55%, rgba(0,0,0,.96) 62%, #000 70%); }
.photo-img { position: absolute; inset: 0; background-size: cover; background-repeat: no-repeat;
  -webkit-mask-image: var(--fade-x); mask-image: var(--fade-x); }
/* shown whole: the strip takes the picture's own proportions (set from the
   image in main.js), so nothing is cropped away and the fade still falls
   across the image itself */
.photo.photo-fit { width: auto; max-width: 46%; }
.panel-main > * { position: relative; max-width: 640px; }
@media (min-width: 761px) { .panel-main > .lede, .panel-main > .prose, .panel-main > .rig, .panel-main > .posters { max-width: min(640px, 58%) !important; } }

.kicker { margin: 0; font-size: 14px; font-weight: 600; color: var(--accent-deep); }
h1 { margin: 0; font-weight: 700; font-size: clamp(34px, 3.8vw, 54px); line-height: 0.9; letter-spacing: -0.045em; }
.lede { margin: 0; max-width: 52ch; font-size: 14px; line-height: 1.55; color: var(--ink-2); text-wrap: pretty; }
.prose { display: grid; gap: 10px; max-width: 58ch; }
.prose p { margin: 0; font-size: 14px; line-height: 1.6; color: var(--ink-2); text-wrap: pretty; }
.label { margin: 0 0 6px; font-size: 14px; font-weight: 600; color: var(--accent-deep); }

.covers { display: flex; gap: 12px; padding-top: 6px; flex-wrap: wrap; max-width: 700px !important; }
.cover { display: grid; gap: 6px; justify-items: start; width: clamp(84px, 8.4vw, 116px); text-align: left; }
.cover img { width: 100%; height: auto; aspect-ratio: 1; object-fit: cover; border-radius: var(--r); box-shadow: 0 0 0 1px rgba(0,0,0,.08); transition: transform 160ms ease, box-shadow 160ms ease; }
.cover span { font-family: var(--mono); font-size: 12.5px; letter-spacing: 0.04em; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.cover:hover img { transform: translateY(-2px); box-shadow: 0 0 0 2px var(--accent); }
.cover.playing img { box-shadow: 0 0 0 2px var(--accent), 0 6px 20px rgba(242,104,28,.35); }
.cover.playing span { color: var(--accent-deep); }

.player { display: grid; gap: 6px; max-width: 560px !important; }
.player-embed { min-height: 152px; border-radius: 12px; background: rgba(29,28,25,.08); overflow: hidden; }
.player-embed iframe { display: block; border: 0; }
.player-meta { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.player-source { font-family: var(--mono); font-size: 12px; letter-spacing: 0.02em; color: var(--muted); }
.player-source.live { color: var(--accent-deep); }
.btn-text { font-family: var(--mono); font-size: 12px; letter-spacing: 0.01em; color: var(--ink-2); text-decoration: underline; text-underline-offset: 3px; }

.rig ul { list-style: none; margin: 0; padding: 0; max-width: 460px; columns: 2; column-gap: 28px; }
.rig li { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; padding: 7px 0; border-bottom: 1px solid var(--line-2); break-inside: avoid; }
.rig b { font-size: 14px; font-weight: 600; }
.rig span { font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.posters { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; max-width: 720px !important; }
.poster { display: grid; gap: 6px; text-align: left; }
.poster img { width: 100%; height: auto; aspect-ratio: 4 / 5; object-fit: cover; object-position: top; border-radius: var(--r); box-shadow: 0 0 0 1px rgba(0,0,0,.1); transition: transform 160ms ease, box-shadow 160ms ease; }
.poster:hover img { transform: translateY(-2px); box-shadow: 0 0 0 2px var(--accent); }
.poster span { display: grid; font-size: 12.5px; color: var(--muted); line-height: 1.3; }
.poster b { color: var(--ink); font-weight: 700; font-size: 13px; }
.lightbox { padding: 0; border: 0; background: transparent; max-width: min(92vw, 720px); max-height: 92vh; overflow: visible; }
.lightbox::backdrop { background: rgba(16,15,13,.82); }
.lightbox img { display: block; max-width: 100%; max-height: 92vh; border-radius: var(--r); }
.lb-close { position: absolute; top: -40px; right: 0; width: 34px; height: 34px; border-radius: 50%; background: var(--paper); font-size: 20px; line-height: 1; }
.mail { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.mail-link { font-weight: 700; font-size: clamp(20px, 2.3vw, 30px); letter-spacing: -0.02em; text-decoration: none; overflow-wrap: anywhere; }
.mail-link:hover { color: var(--accent-deep); }

.btn { display: inline-flex; align-items: center; gap: 8px; min-height: 36px; padding: 0 14px; border: 1px solid var(--line-2); border-radius: var(--r); background: var(--plate-2); font-family: var(--mono); font-size: 14px; font-weight: 600; letter-spacing: 0.01em; text-decoration: none; color: var(--ink-2); justify-self: start; white-space: nowrap; }
.btn:hover { border-color: var(--ink); color: var(--ink); }
.btn small { font-size: 12px; font-weight: 400; opacity: .7; letter-spacing: .06em; }
.btn-accent { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.btn-accent:hover { background: var(--sel); border-color: var(--sel); color: var(--sel-ink); }

.panel-side { position: relative; z-index: 1; min-height: 0; display: grid; grid-template-rows: auto minmax(0, 1fr) auto; border-left: 1px solid var(--line); background: rgba(242,239,230,.55); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }
.side-head { display: flex; justify-content: space-between; padding: 12px 14px 7px; border-bottom: 1px solid var(--line-2); font-size: 12px; color: var(--muted); }
.rows { list-style: none; margin: 0; padding: 0; overflow-y: auto; }
.rows li > * { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; grid-template-rows: auto auto; column-gap: 12px; align-items: center; width: 100%; padding: 9px 14px; border-bottom: 1px solid var(--line); text-align: left; text-decoration: none; }
.rows li > button:hover, .rows li > a:hover { background: rgba(242,104,28,.08); }
.rows img { grid-row: 1 / 3; width: 40px; height: 40px; aspect-ratio: 1; object-fit: cover; border-radius: 3px; }
.rows-plain li > *, .rows-links li > * { grid-template-columns: minmax(0, 1fr) auto; }
.row-a { font-weight: 600; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.row-b { grid-column: -3 / -2; font-size: 12px; color: var(--muted); }
.rows-plain .row-b, .rows-links .row-b { grid-column: 1; }
.row-tag { grid-row: 1 / 3; grid-column: -2; font-family: var(--mono); font-size: 12px; font-weight: 600; letter-spacing: 0.02em; color: var(--accent-deep); }
.rows li > .is-playing .row-a, .rows button.is-playing .row-a { color: var(--accent-deep); }
.side-links, .side-note { margin: 0; padding: 11px 14px 14px; font-size: 12px; line-height: 1.55; color: var(--muted); }
.side-links { display: flex; flex-wrap: wrap; gap: 6px 14px; font-family: var(--mono); font-size: 13px; font-weight: 600; letter-spacing: 0.02em; }
.side-links a, .side-note a { color: var(--ink-2); }
.facts { margin: 0; }
.facts div { display: grid; grid-template-columns: 76px 1fr; gap: 10px; padding: 10px 14px; border-bottom: 1px solid var(--line); font-size: 13px; }
.facts dt { font-family: var(--mono); font-size: 12px; letter-spacing: 0.02em; color: var(--muted); padding-top: 2px; }
.facts dd { margin: 0; font-weight: 500; }

/* ── footer ─────────────────────────── */
.foot { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; min-height: 42px; padding: 8px var(--gutter); border-top: 1px solid var(--line-2); font-size: 13px; color: var(--muted); }
.foot a { text-decoration: none; color: var(--ink-2); }
.foot a:hover { color: var(--accent-deep); }
.foot nav { display: flex; gap: 14px; flex-wrap: wrap; }
.foot > a { font-weight: 600; }
.foot-keys { margin-left: auto; color: var(--faint); }

.toast { position: fixed; left: 50%; bottom: 24px; transform: translate(-50%, 20px); padding: 10px 16px; border-radius: var(--r); background: var(--ink); color: var(--paper); font-family: var(--mono); font-size: 13px; font-weight: 600; opacity: 0; pointer-events: none; transition: 200ms ease; z-index: 100; }
.toast.show { opacity: 1; transform: translate(-50%, 0); }

/* ── console ─────────────────────────── */
.console { padding: 0 var(--gutter) 12px; }
.no-js .console { display: none; }
.con { border: 1px solid var(--line); border-radius: 5px; background: var(--plate); scroll-margin-top: 50px; }
/* the groovebox's toolbar: transport, what the pattern is, and the fold */
.con-bar { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 16px; padding: 7px 10px; border-bottom: 1px solid var(--line); font-size: 12.5px; color: var(--muted); }
.con-title { display: flex; gap: 10px; align-items: center; }
.con-bar strong { color: var(--ink); font-weight: 700; font-size: 14px; }
.con-transport { display: flex; align-items: center; gap: 8px; }
.con-props { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 16px; }
/* shut, the bar is just a title and a transport — what the pattern is made of
   only matters once it's open */
.con.folded .con-props { display: none; }
.con.folded .cbtn.fold::after { content: "— tap to open"; font-family: var(--mono); font-size: 11.5px; font-weight: 600; color: var(--muted); }
.cbtn.fold { gap: 8px; min-height: 30px; padding: 0 10px; }
.cbtn.fold strong { font-size: 14px; }
.fold-ic { display: inline-flex; align-items: center; color: var(--muted); }
/* nothing to fold in the fixed-height desktop layout, so it reads as a title */
.cbtn.fold:disabled { opacity: 1; padding-left: 0; border-color: transparent; background: none; cursor: default; }
.cbtn.fold:disabled .fold-ic { display: none; }
.con.folded .con-bar { border-bottom: 0; }
.grow { flex: 1; }
.keysel { display: inline-flex; gap: 4px; }
.con-grid { display: grid; grid-template-columns: 1fr; gap: 10px; padding: 10px; }
.mod { min-width: 0; border: 1px solid var(--line); border-radius: var(--r); background: var(--plate-2); padding: 9px; display: grid; gap: 6px; align-content: start; }
.mod-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.mlabel { font-size: 12.5px; color: var(--muted); }

.seg { display: inline-flex; border: 1px solid var(--line-2); border-radius: var(--r); overflow: hidden; background: var(--inset); }
.seg button { min-height: 30px; padding: 0 10px; font-family: var(--mono); font-size: 13px; font-weight: 600; letter-spacing: 0.02em; color: var(--ink-2); border-right: 1px solid var(--line-2); white-space: nowrap; }
.seg button:last-child { border-right: 0; }
.seg button:hover { background: var(--plate); }
.seg button[aria-pressed="true"] { background: var(--sel); color: var(--sel-ink); }
.seg.accent button[aria-pressed="true"] { background: var(--accent); color: var(--on-accent); }
.seg-label { display: inline-flex; align-items: center; gap: 6px; }
.seg-label > span { font-family: var(--mono); font-size: 12.5px; letter-spacing: 0.02em; color: var(--muted); }

.cbtn { display: inline-flex; align-items: center; justify-content: center; gap: 7px; min-height: 34px; padding: 0 12px; border: 1px solid var(--line-2); border-radius: var(--r); background: var(--inset); font-family: var(--mono); font-size: 13.5px; font-weight: 600; letter-spacing: 0.01em; color: var(--ink-2); white-space: nowrap; }
.cbtn:hover { border-color: var(--sel); color: var(--sel); }
.cbtn:disabled { opacity: .4; cursor: default; border-color: var(--line-2); }
.cbtn[aria-pressed="true"] { background: var(--sel); border-color: var(--sel); color: var(--sel-ink); }
.cbtn.hot { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.cbtn.hot:hover { background: var(--sel); border-color: var(--sel); }
.cbtn.play { min-width: 92px; }
.cbtn.play[aria-pressed="true"] { background: var(--sel); border-color: var(--sel); color: var(--sel-ink); }
/* drawn icons: sized in ems so they track the text beside them, and filled
   with the current colour so they invert with their button */
.ico { width: 1.08em; height: 1.08em; flex: 0 0 auto; display: block; fill: currentColor; }
.cbtn { }
.cbtn .ico { width: 1em; height: 1em; }
.cbtn.play .ico { width: 0.92em; height: 0.92em; }
.lane-dice .ico, .sound-dice .ico { width: 15px; height: 15px; }
.fold-ic .ico { width: 11px; height: 11px; }
.chord-clear .ico { width: 11px; height: 11px; }
.cbtn.icon { min-width: 34px; padding: 0; }

.seq-top, .seq-sub { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.barsel { display: inline-flex; align-items: center; gap: 6px; }
.seq-sub { gap: 8px 10px; }
.bpm { display: inline-flex; align-items: baseline; gap: 5px; min-height: 34px; padding: 6px 10px; border: 1px solid var(--line-2); border-radius: var(--r); background: var(--inset); cursor: ns-resize; touch-action: none; user-select: none; }
.bpm b { font-weight: 700; font-size: 15px; min-width: 3ch; text-align: right; }
.bpm small { font-family: var(--mono); font-size: 12px; color: var(--muted); }
select.sel { min-height: 30px; padding: 0 6px; border: 1px solid var(--line-2); border-radius: var(--r); background: var(--inset); font-family: var(--mono); font-size: 13px; font-weight: 600; color: var(--ink-2); }

.lanes { display: grid; gap: 4px; }
.lane { display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: 8px; align-items: stretch; }
.mod-seq { gap: 8px; }
.lane-label { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 4px; padding: 0 4px 0 8px; border-radius: var(--r); background: var(--inset); border: 1px solid var(--line); font-family: var(--mono); font-size: 13px; font-weight: 600; letter-spacing: 0.02em; }
/* both fill the row's full height so they're comfortable to hit; the label
   and glyph stay vertically centred inside them, so nothing moves */
.lane-label .name { display: flex; align-items: center; gap: 6px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; align-self: stretch; }
.lane-label .lane-dice { justify-self: end; align-self: stretch; height: auto; }
.lane-label .name::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--tint); }
.lane.sel .lane-label { background: var(--sel); color: var(--sel-ink); border-color: var(--sel); }
.lane.muted .lane-label .name { opacity: .45; text-decoration: line-through; }
.lane-dice { width: 24px; height: 24px; display: grid; place-items: center; border-radius: 3px; color: inherit; opacity: .7; }
.lane-dice:hover { opacity: 1; background: rgba(242,104,28,.25); }
.cells { display: grid; grid-template-columns: repeat(16, minmax(0, 1fr)); gap: 3px; touch-action: none; }
.cell { position: relative; min-height: 29px; border-radius: 3px; border: 1px solid var(--line); background: var(--inset); display: grid; place-items: center; font-family: var(--mono); font-size: 11.5px; font-weight: 700; color: var(--paper); overflow: hidden; user-select: none; }
.cell:nth-child(4n+1) { background: #e3dfd3; }
.cells .cell:nth-child(4n+1)::after { content: ""; position: absolute; left: 3px; top: 3px; width: 3px; height: 3px; border-radius: 50%; background: var(--line-2); }
/* Velocity is how brightly a trig is lit, like a pad's LED: full colour at
   the top, fading towards the step's own ground as it gets softer. */
.cell.on { border-color: transparent; background-color: color-mix(in srgb, var(--tint) calc(22% + var(--v, 1) * 78%), var(--inset)); }
.cell.on.soft { color: var(--ink); }
.cell.on::after { display: none; }
.cell.tie { border-top-left-radius: 0; border-bottom-left-radius: 0; margin-left: -4px; opacity: .72; }
.cell.slide { background-image: linear-gradient(135deg, transparent 45%, rgba(255,255,255,.45) 46%, rgba(255,255,255,.45) 54%, transparent 55%); }
.cell.now { outline: 2px solid var(--accent); outline-offset: -2px; }
.cell.on.now { filter: brightness(1.2); }
.cell.drag { outline: 2px solid var(--sel); outline-offset: -2px; }
.cells.page-dim .cell { opacity: .5; }
.cell.selc, .rcell.selc { outline: 2px solid var(--sel); outline-offset: -2px; }

/* piano roll */
.roll { display: grid; gap: 6px; --rkey: 88px; }
.roll-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; }
.chord-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.chord-info { display: inline-flex; align-items: baseline; gap: 7px; font-size: 13px; color: var(--muted); }
.chord-scope { font-family: var(--mono); font-size: 11.5px; font-weight: 700; letter-spacing: .04em; padding: 2px 7px; border-radius: 999px; background: var(--line); color: var(--ink-2); }
.chord-info.one .chord-scope { background: var(--accent); color: var(--on-accent); }
.chord-what { font-weight: 700; font-size: 14px; color: var(--ink); }
.chord-clear { width: 20px; height: 20px; display: grid; place-items: center; border-radius: 50%; line-height: 1; color: var(--muted); background: var(--line); }
.chord-clear:hover { background: var(--accent); color: var(--on-accent); }
/* Accent and slide as switches on the note you picked, so neither depends on
   first discovering the tap mode. Pressed state carries the same mark the
   note wears in the roll. */
.note-togs { display: inline-flex; gap: 6px; }
.ntog { height: 26px; padding: 0 10px; border-radius: 999px; border: 1px solid var(--line);
  font-family: var(--mono); font-size: 11.5px; font-weight: 600; letter-spacing: .04em;
  color: var(--ink-2); background: var(--paper); }
.ntog:hover:not(:disabled) { border-color: var(--accent); color: var(--ink); }
.ntog[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.ntog:disabled { opacity: .38; cursor: not-allowed; }
/* what the marks on the notes mean, beside the notes wearing them */
.roll-legend { display: inline-flex; align-items: center; gap: 10px; font-size: 10.5px; color: var(--muted); }
.roll-legend span { display: inline-flex; align-items: center; gap: 4px; }
.roll-legend .lg { width: 13px; height: 11px; border-radius: 2px; background: var(--tint, var(--accent)); }
.roll-legend .lg-vel { width: 22px; background: linear-gradient(to right, color-mix(in srgb, var(--tint, var(--accent)) 25%, var(--inset)), var(--tint, var(--accent))); }
.roll-legend .lg-hold { opacity: .5; border-radius: 0; }
/* the same two marks the cells carry: a dot bottom-left for a parameter
   lock, a corner tick for a trig condition */
.roll-legend .lg-plock { position: relative; background: var(--inset); box-shadow: inset 0 0 0 1px var(--line-2); }
.roll-legend .lg-plock::after { content: ""; position: absolute; left: 2px; bottom: 2px; width: 4px; height: 4px; border-radius: 50%; background: var(--ink); opacity: .7; }
.roll-legend .lg-cnd { position: relative; background: var(--inset); box-shadow: inset 0 0 0 1px var(--line-2); }
.roll-legend .lg-cnd::after { content: ""; position: absolute; right: 2px; bottom: 2px; width: 0; height: 0; border-left: 5px solid transparent; border-bottom: 5px solid var(--ink); opacity: .55; }
.roll-legend .lg-glide { opacity: .5; border-radius: 0;
  background-image: linear-gradient(135deg, transparent 40%, var(--ink) 41%, var(--ink) 59%, transparent 60%); }
/* bar ruler above the roll, column-aligned with the cells below it */
.roll-ruler { display: grid; grid-template-columns: var(--rkey) minmax(0, 1fr); }
.roll-ruler .rkey { border-right: 0; }
.roll-ruler .rcells { grid-template-columns: repeat(16, minmax(0, 1fr)); }
.rtick { font-family: var(--mono); font-size: 10.5px; font-weight: 700; color: var(--faint); padding-left: 3px; line-height: 14px; border-left: 1px solid transparent; }
.rtick.beat { color: var(--muted); border-left-color: rgba(0,0,0,.14); }
.roll-hint { margin: 0; font-size: 11.5px; color: var(--muted); }
.roll-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 3px 14px; }
.roll-body { height: 208px; overflow-y: auto; border: 1px solid var(--line); border-radius: var(--r); background: var(--inset); overscroll-behavior: contain; touch-action: pan-y; }
.rrow { display: grid; grid-template-columns: var(--rkey) minmax(0, 1fr); height: 19px; border-bottom: 1px solid rgba(0,0,0,.05); }
.roll-chords .rrow { height: 29px; }
.rrow.tonic { background: rgba(242,104,28,.07); }
.rkey { display: flex; align-items: center; padding: 0 8px; font-size: 11.5px; font-weight: 600; color: var(--muted); border-right: 1px solid var(--line); white-space: nowrap; overflow: hidden; }
.rrow.tonic .rkey { color: var(--ink); }
.rcells { display: grid; grid-template-columns: repeat(16, minmax(0, 1fr)); touch-action: none; }
.rcell { position: relative; border-left: 1px solid rgba(0,0,0,.05); }
.rcell.beat { border-left-color: rgba(0,0,0,.14); }
.rcell.on { position: relative; background: var(--tint); margin: 2px 0; border-radius: 3px 0 0 3px; border-left-color: transparent; }
.rcell.on.tie { border-radius: 0; opacity: .62; }
/* accent reads as a bar down the front of the note, which survives being 12px
   wide on a phone in a way that a thin ring around the whole cell did not */
/* velocity in the roll: softer notes print paler */
.rcell.on { background: color-mix(in srgb, var(--tint) calc(22% + var(--v, 1) * 78%), var(--inset)); }
/* a tie into a new pitch is a glide, not a hold, so it gets its own mark */
.rcell.on.slide { opacity: .85; background-image: linear-gradient(135deg, transparent 38%, rgba(0,0,0,.45) 39%, rgba(0,0,0,.45) 61%, transparent 62%); }
/* each chord's own type, on the chord, since the row label can only show the
   pattern default */
.qtag { position: absolute; left: 3px; top: 50%; transform: translateY(-50%); pointer-events: none;
  font-family: var(--mono); font-style: normal; font-size: 8.5px; font-weight: 700; letter-spacing: .02em;
  color: rgba(0,0,0,.62); white-space: nowrap; overflow: hidden; }
.rcell.now { background-color: rgba(242,104,28,.12); }
.rcell.on.now { filter: brightness(1.2); }

.bars { display: inline-flex; gap: 3px; }
.bars button { position: relative; width: 34px; min-height: 30px; border: 1px solid var(--line-2); border-radius: var(--r); background: var(--inset); font-family: var(--mono); font-size: 13px; font-weight: 600; color: var(--ink-2); }
.bars button[aria-pressed="true"] { background: var(--sel); color: var(--sel-ink); border-color: var(--sel); }
.bars button:disabled { opacity: .35; cursor: default; }
.bars button .ph { position: absolute; left: 4px; right: 4px; bottom: 3px; height: 2px; border-radius: 2px; background: transparent; }
.bars button.playing .ph { background: var(--accent); }
/* The looped run gets a bracket underneath it, closed at both ends, so a
   two-bar loop reads as one span rather than two separately lit buttons. */
.bars { touch-action: none; }
.bars button.in-loop::after { content: ""; position: absolute; left: -2px; right: -2px; bottom: -6px; height: 3px; background: var(--ink); }
.bars button.in-loop.loop-start::after { left: 2px; border-radius: 2px 0 0 2px; }
.bars button.in-loop.loop-end::after { right: 2px; border-radius: 0 2px 2px 0; }
.loop-btn[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--paper); }

/* six tracks, master, and the shared FX: a divider before master sets the
   two global tabs apart from the voices */
.voice-tracks { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); border: 1px solid var(--line-2); border-radius: var(--r); overflow: hidden; }
.voice-tracks button:nth-child(6) { border-right: 3px solid var(--line-2); }
/* Eight across on a phone leaves each button about 39px — too narrow for
   "chords" and "master". There the six voices keep the top row and master
   and FX share a second one, which also sets the two global tabs apart. */
@media (max-width: 440px) {
  .voice-tracks { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .voice-tracks button:nth-child(6) { border-right: 0; }
  .voice-tracks button:nth-child(7), .voice-tracks button:nth-child(8) { grid-column: span 3; border-top: 1px solid var(--line-2); grid-auto-flow: column; justify-content: center; align-items: center; min-height: 36px; padding-block: 6px; }
  .voice-tracks button:nth-child(7) { border-right: 1px solid var(--line-2); }
}
.voice-tracks button { display: grid; justify-items: center; gap: 4px; padding: 7px 0 6px; font-family: var(--mono); font-size: 12px; font-weight: 600; letter-spacing: 0.02em; border-right: 1px solid var(--line-2); background: var(--inset); }
.voice-tracks button:last-child { border-right: 0; }
.voice-tracks button i { width: 6px; height: 6px; border-radius: 50%; background: var(--line-2); transition: background 60ms; }
.voice-tracks button i.lit { background: var(--tint); box-shadow: 0 0 6px var(--tint); }
.voice-tracks button[aria-pressed="true"] { background: var(--sel); color: var(--sel-ink); }
.engines { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4px; }
.mst-note { grid-column: 1 / -1; margin: 0; min-height: 30px; font-size: 12px; line-height: 1.4; color: var(--muted); }
.engines button { min-height: 30px; border: 1px solid var(--line-2); border-radius: var(--r); background: var(--inset); font-family: var(--mono); font-size: 13px; font-weight: 600; }
.engines button[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
/* A grid, not a flex row: equal columns are what make every dial, label and
   value line up down the panel. Past four knobs it goes to three across and
   two down rather than squeezing six into one row, where the cells fell to
   45px and the dials were wider than the space they sat in. */
.knobs { display: grid; grid-template-columns: repeat(var(--kcols, 4), minmax(0, 1fr)); gap: 8px 4px; padding-top: 2px; justify-items: center; }
/* Two rows of three: the cell is roomy either way, so the dial gives a little
   back to keep the whole column on one screen. */
.knobs.two-row .knob { --dial: 42px; }
.knobs.two-row { gap: 4px; padding-top: 0; }
.knobs.two-row .knob { gap: 4px 3px; }
/* six slots now that engines expose more than four macros; they wrap to a
   second row rather than shrinking past the point of being draggable */
.knobs .knob { width: 100%; min-width: 0; max-width: 118px; }
.sound-dice { margin-left: auto; }
.knob { display: grid; grid-template-rows: auto auto; justify-items: center; align-content: start; gap: 5px 4px; min-width: 0; touch-action: none; user-select: none; cursor: ns-resize; }
.knob svg { display: block; width: 100%; height: auto; aspect-ratio: 1; overflow: visible; }
.knob .track { fill: none; stroke: var(--line-2); stroke-width: 3; stroke-linecap: round; }
.knob .val { fill: none; stroke: var(--accent); stroke-width: 3; stroke-linecap: round; }
.knob .cap { fill: var(--plate); stroke: #9d968c; stroke-width: 1; }
.knob .ptr { stroke: var(--ink); stroke-width: 2.2; stroke-linecap: round; }
.knob:focus-visible { outline: none; }
.knob:focus-visible .cap { stroke: var(--accent); stroke-width: 2; }
/* the labels must be allowed to shrink, or a six-knob row's min-content
   width forces it to wrap onto a second row and the fixed desktop layout
   stops fitting the window */
/* Label and value share one line under the dial. Stacked, they cost every
   knob row an extra 27px, which on a two-row page is what pushed the panel
   past the window. */
.k-foot { display: flex; align-items: baseline; justify-content: center; gap: 4px; max-width: 100%; min-width: 0; }
.knob .k-label { min-width: 0; font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.02em; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.knob .k-val { flex: 0 0 auto; font-family: var(--mono); font-size: 11.5px; font-weight: 600; color: var(--ink); background: var(--inset); padding: 1px 4px; border-radius: 3px; white-space: nowrap; }
.knob.empty { display: none; }
/* REVERSE and LOOP are parameters, so they sit in the knob grid with the
   other six — a switch where the others have a dial, but the same cell, the
   same label and the same value underneath, so the rows still line up. */
.knob-togs { display: contents; }
.ktog { appearance: none; border: 0; background: none; padding: 0; cursor: pointer; }
.ktog .sw-track { fill: var(--inset); stroke: #9d968c; stroke-width: 2; }
.ktog .sw-cap { fill: var(--plate); stroke: #9d968c; stroke-width: 1.5; transition: cx 90ms ease; }
.ktog[aria-pressed="true"] .sw-track { fill: var(--accent); stroke: var(--accent); }
.ktog[aria-pressed="true"] .sw-cap { fill: #fff; stroke: var(--accent); }
.ktog:hover .sw-track { stroke: var(--accent); }
.ktog:focus-visible .sw-track { stroke: var(--accent); stroke-width: 3; }
.ktog.locked .sw-track { stroke: var(--sel); stroke-width: 3; }
/* The wrapper hugs the dial rather than the cell. It used to stretch the full
   cell width with the dial left-aligned inside it, which is what put every
   dial up to 25px to the left of the label and value underneath it. Sizing
   both from one token also keeps the jack socket on the dial's edge. */
.k-dial { position: relative; width: min(var(--dial, 58px), 100%); margin-inline: auto; }
/* The socket sits in the gap the arc leaves at the bottom of the dial —
   centred under the knob, clear of both the cap and the ring — the way a
   jack sits under a knob on hardware. Tucked into the dial's corner it
   overlapped the ring and read as part of the knob. It scales with the dial
   so it never reaches the cap on the smaller two-row knobs. */
.k-jack { position: absolute; z-index: 6; left: 50%; bottom: -3px; transform: translateX(-50%);
  width: clamp(8px, calc(var(--dial, 58px) * 0.2), 12px); height: clamp(8px, calc(var(--dial, 58px) * 0.2), 12px);
  border-radius: 50%; background: var(--plate); border: 1.5px solid #9d968c; display: grid; place-items: center; cursor: crosshair; touch-action: none; }
.k-jack i { width: 4px; height: 4px; border-radius: 50%; background: transparent; }
.k-jack.patched { border-color: var(--ink-2); }
.k-jack:hover { border-color: var(--accent); }
.mod-dot { stroke: var(--plate); stroke-width: 1; }
.mod-voice { position: relative; }
/* above the sockets, so a lead ends in a plug sitting in its hole rather
   than disappearing behind it */
.cables { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; z-index: 7; }
.patch-bay { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 5px 10px; border-top: 1px solid var(--line); }
.jack-wrap { display: flex; align-items: center; gap: 7px; min-width: 0; }
.jack { position: relative; z-index: 6; width: 18px; height: 18px; flex: 0 0 auto; border-radius: 50%; background: var(--plate); border: 2px solid var(--mc); display: grid; place-items: center; cursor: grab; touch-action: none; }
.jack i { width: 6px; height: 6px; border-radius: 50%; background: var(--mc); opacity: .35; transition: opacity 120ms; }
.jack.on i, .jack.armed i { opacity: 1; }
.jack.armed { box-shadow: 0 0 0 3px color-mix(in srgb, var(--mc) 35%, transparent); }
.jack-text { display: grid; line-height: 1.15; min-width: 0; }
.jack-name { text-align: left; font-weight: 700; font-size: 13px; }
.jack-name:hover { color: var(--accent-deep); }
.jack-sub { font-size: 11.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 84px; }
.mod-edit { display: grid; gap: 5px; min-height: 0; }
/* The page-specific block above the knobs. Height is content-driven while the
   page scrolls (mobile); on the fixed desktop layout it is pinned so the
   groovebox is the same height on every page. */
/* A column, so whichever display the page is showing — the modulator scope,
   the amp envelope, the sample's waveform — takes the space the fixed-size
   rows above and below it don't want. */
.voice-page { display: flex; flex-direction: column; gap: 9px; min-width: 0; }
.voice-page > * { flex: 0 0 auto; }
/* the scope is the point of these two pages, so it takes the room the page
   box has left rather than sitting in a 64px letterbox */
.mod-viz { position: relative; min-height: 64px; border: 1px solid var(--line); border-radius: var(--r); background: var(--inset); }
.mod-viz canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.knobs.small .knob { --dial: 42px; }

.scope { position: relative; height: 92px; border: 1px solid var(--line); border-radius: var(--r); background: var(--inset); overflow: hidden; }
.scope canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.scope .mlabel { position: absolute; left: 8px; top: 6px; }
.bands { position: absolute; right: 8px; top: 8px; bottom: 8px; display: flex; gap: 5px; align-items: flex-end; }
.bands div { display: grid; justify-items: center; gap: 3px; height: 100%; grid-template-rows: 1fr auto; }
.bands .bar { width: 8px; height: 100%; background: var(--line); border-radius: 2px; position: relative; overflow: hidden; }
.bands .bar i { position: absolute; left: 0; right: 0; bottom: 0; height: 0; background: var(--c); }
.bands span { font-family: var(--mono); font-size: 10px; color: var(--muted); }
.mixer { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 4px; min-height: 150px; }
.mod-mix { grid-template-rows: auto minmax(0, 1fr); align-content: stretch; }
.strip { display: grid; grid-template-rows: auto auto minmax(0, 1fr) auto; justify-items: center; gap: 5px; padding: 6px 0; border: 1px solid var(--line); border-radius: var(--r); background: var(--inset); }
/* The strip's pan: a small bipolar knob showing only its value — the strip's
   name above it already says whose it is. */
.strip .pan-knob { --dial: 28px; width: 100%; gap: 2px; cursor: ns-resize; }
.strip .pan-knob .k-label { display: none; }
.strip .pan-knob .k-val { font-size: 10.5px; padding: 0 3px; min-width: 26px; text-align: center; }
.strip .pan-knob .k-jack { display: none; }
.strip .s-name { font-family: var(--mono); font-size: 11.5px; font-weight: 600; letter-spacing: 0.02em; }
.fader { position: relative; width: 30px; height: 100%; min-height: 88px; touch-action: none; cursor: ns-resize; }
.fader::before { content: ""; position: absolute; left: 50%; top: 0; bottom: 0; width: 4px; margin-left: -2px; border-radius: 2px; background: var(--line-2); }
.fader .lvl { position: absolute; left: 50%; bottom: 0; width: 4px; margin-left: -2px; border-radius: 2px; background: var(--tint); opacity: .8; }
.fader .cap { position: absolute; left: 3px; right: 3px; height: 14px; margin-bottom: -7px; border-radius: 3px; background: var(--plate); border: 1px solid #9d968c; box-shadow: 0 1px 0 rgba(0,0,0,.08); }
.fader .cap::after { content: ""; position: absolute; left: 3px; right: 3px; top: 5px; height: 2px; background: var(--tint); }
.fader:focus-visible { outline: none; }
.fader:focus-visible .cap { border-color: var(--accent); }
.mute { width: 26px; height: 20px; border-radius: 3px; border: 1px solid var(--line-2); font-family: var(--mono); font-size: 11px; font-weight: 700; color: var(--muted); }
.mute[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }

.help, .menu-wrap { position: relative; }
.menu { position: absolute; right: 0; top: calc(100% + 6px); z-index: 30; display: grid; min-width: 230px; padding: 4px; border-radius: var(--r); background: var(--paper); border: 1px solid var(--line-2); box-shadow: 0 12px 30px rgba(0,0,0,.18); }
.menu button { display: grid; gap: 1px; padding: 8px 10px; border-radius: 3px; text-align: left; }
.menu button b { font-size: 13px; font-weight: 600; }
.menu button span { font-size: 11.5px; color: var(--muted); }
.menu button:hover { background: var(--sel); color: var(--sel-ink); }
.menu button:hover span { color: var(--sel-ink); opacity: .8; }
/* Anchored above the "?" on a roomy screen. It is a long list, so it always
   caps its own height and scrolls rather than running off the top. */
.help-pop { position: absolute; right: 0; bottom: calc(100% + 8px); z-index: 30; width: min(360px, 86vw); max-height: min(68vh, 540px); overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; padding: 14px 16px; border-radius: var(--r); background: var(--ink); color: var(--paper); font-family: var(--sans); font-size: 12.5px; line-height: 1.5; letter-spacing: 0; text-transform: none; box-shadow: 0 12px 30px rgba(0,0,0,.25); }
.help-pop dl { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 6px 12px; }
.help-pop dt { font-family: var(--mono); font-size: 11px; font-weight: 600; color: #ffb27d; letter-spacing: .08em; padding-top: 1px; }
.help-pop dd { margin: 0; color: #d8d3c8; }

/* ── desktop "instrument" layout: everything on one screen ── */
@media (min-width: 1100px) and (min-height: 760px) {
  body { overflow: hidden; }
  .app { height: 100vh; height: 100dvh; min-height: 0; grid-template-rows: 54px 42px minmax(0, 1fr) auto 42px; }
  .tabs { position: static; }
  .tabs a { flex: 0 0 auto; min-width: 150px; }
  .stage { display: grid; min-height: 0; }
  .js .panel.active { min-height: 0; }
  .panel-main { overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; }
  .panel-side { overflow: hidden; }
  .con-grid { grid-template-columns: 310px 290px minmax(0, 1fr); }
  .mod-voice { order: 1; } .mod-mix { order: 2; } .mod-seq { order: 3; }
  /* the whole instrument has to fit the window here, so the roll gives back
     height on a short screen and takes more of it on a tall one */
  .roll-body { height: clamp(88px, 14vh, 300px); }
  /* The groovebox had been taking whatever it wanted, which on a 760px-tall
     window left the panel above it 29 pixels of daylight. The page keeps its
     share and the groovebox scrolls inside what's left. */
  .app { grid-template-rows: 54px 42px minmax(0, 1fr) auto 42px; }
  /* The groovebox gets a set share of the window and the page keeps the rest.
     Fixing its outer height is what stops the page above jumping when you
     switch to the Mod tab — but it must not then stretch its own contents to
     fill that height, which is how four knobs ended up floating in the middle
     of a 367px block while the releases list above was squeezed to nothing. */
  /* 460px is what the tallest page actually needs — a sampler track's two
     knob rows, its display and the patch bay — so that is the floor. Above
     that the groovebox takes a smaller share than it used to: the extra
     height was going into air under the knobs, and the page above it is
     worth more than a slightly taller fader. */
  .con { display: grid; grid-template-rows: auto minmax(0, 1fr); height: clamp(460px, 44vh, 540px); }
  /* The columns scroll on their own, not the groovebox as a whole: on a short
     window the voice column can give a little without the sequencer beside it
     sliding out from under the mouse. */
  .con-body { min-height: 0; overflow: hidden; }
  .con-grid { height: 100%; min-height: 0; align-items: stretch; }
  .con-grid > .mod { min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; align-content: start; }
  /* Any height the groovebox has over what its contents need goes where it is
     worth having — taller lanes to aim at, taller faders to ride — rather
     than pooling at the bottom of a column as a hole. The patch bay sits at
     the foot of the voice column so that column reads as finished too. */
  .con-grid > .mod-voice { display: flex; flex-direction: column; }
  .con-grid > .mod-voice > .patch-bay { margin-top: auto; }
  .con-grid > .mod-seq { display: flex; flex-direction: column; }
  .con-grid > .mod-seq > .lanes { flex: 1 1 auto; align-content: stretch; }
  .con-grid > .mod-mix { align-content: stretch; }
  /* The voice column is fixed rows plus one stretchy one. Only the display
     grows or shrinks, so a page with little to show gives it the room instead
     of leaving a hole, and a page with a lot takes the room back from it
     instead of pushing two rows of knobs down over the patch bay. */
  .con-grid > .mod-voice > * { flex: 0 0 auto; }
  .con-grid > .mod-voice > .mod-viz { flex: 1 1 70px; min-height: 70px; }
  /* A track can carry any number of routes, so the list gives way and
     scrolls inside itself rather than pushing the patch bay out of the
     column. One route always shows. */
  .con-grid > .mod-voice > .routes { flex: 0 1 auto; min-height: 30px; overflow-y: auto; overscroll-behavior: contain; }
  .voice-page { display: contents; }
  /* one row of engine buttons; the display is what takes the spare height */
  .con-grid > .mod-voice .engines { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .mod-viz { height: auto; }
}
/* The narrow end of the desktop layout: the roll's own controls are the
   widest row in the sequencer column, so they get tighter rather than
   wrapping into extra lines the fixed-height layout has no room for. */
@media (min-width: 1100px) and (max-width: 1399px) and (min-height: 760px) {
  .roll-head { gap: 6px 10px; }
  .chord-pick > span { display: none; }
  .chord-tools .seg button { padding: 0 7px; font-size: 11.5px; }
  .chord-what { font-size: 12.5px; }
  .ntog { height: 24px; padding: 0 8px; font-size: 11px; }
  .roll-legend { display: none; }
  /* The voice column is about 310px here and the window is short, so the rows
     that set its height give a little back — otherwise a sampler track's
     second knob row runs off the bottom and the patch bay scrolls away. */
  .con-grid > .mod-voice .knobs.two-row .knob { --dial: 34px; gap: 3px; }
  .con-grid > .mod-voice .knobs.two-row { gap: 3px; }
  .con-grid > .mod-voice .knob .k-label, .con-grid > .mod-voice .knob .k-val { font-size: 11px; }
  .con-grid > .mod-voice .knob .k-val { padding: 1px 4px; }
  .con-grid > .mod-voice > .mod-viz { flex: 1 1 60px; min-height: 60px; }
  .con-grid > .mod-voice > .patch-bay { gap: 10px; padding: 4px 8px; }
  .seq-sub { row-gap: 3px; }
  .con-grid > .mod-seq > .roll-foot { font-size: 10.5px; }
  /* the route list scrolls in place rather than pushing the patch bay off
     the bottom of a short window */
  .con-grid > .mod-voice > .routes { max-height: 56px; overflow-y: auto; }
}
/* The sequencer has width to spare once the window is wide — its cells were
   running to 60 and 100px, far past useful — so the voice column takes some
   of it back and stops being the cramped one. */
@media (min-width: 1400px) and (min-height: 760px) {
  .con-grid { grid-template-columns: 364px 316px minmax(0, 1fr); }
}
@media (min-width: 1800px) and (min-height: 760px) {
  .con-grid { grid-template-columns: 412px 324px minmax(0, 1fr); }
}

/* ── tablet / phone ── */
@media (max-width: 1099px), (max-height: 759px) {
  .panel-main { min-height: 300px; }
  .con-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  /* the sequencer is what you came for, so it leads once the page scrolls */
  .mod-seq { grid-column: 1 / -1; order: 1; }
  .mod-voice { order: 2; } .mod-mix { order: 3; }
  .con-sub { display: none; }
  /* The "?" sits near the top of the page here, so anchoring the panel above
     it ran it off the screen. Off the desktop layout it becomes a sheet
     pinned to the bottom of the window, which always has somewhere to go. */
  .help-pop { position: fixed; left: 10px; right: 10px; bottom: 10px; top: auto;
    width: auto; max-width: 520px; margin: 0 auto; max-height: 62vh; max-height: 62dvh; }
  /* Same problem, same answer: anchored to the right of its button, the export
     menu hung off the left edge of a phone screen and couldn't be reached. */
  .menu { position: fixed; left: 10px; right: 10px; bottom: 10px; top: auto;
    width: auto; min-width: 0; max-width: 420px; margin: 0 auto;
    max-height: 60vh; max-height: 60dvh; overflow-y: auto; overscroll-behavior: contain; }
  .menu button { padding: 12px 12px; }
  .help-pop::after { content: ""; position: sticky; bottom: -14px; display: block;
    height: 18px; margin: -18px -16px -14px; background: linear-gradient(transparent, var(--ink)); }
}
/* Touch: grow the hit area of the small controls without moving anything.
   The overlay only ever grows downward/upward inside the control's own
   column, so neighbouring controls can't have their taps stolen. */
@media (max-width: 760px) {
  :root { --gutter: 12px; }
  .top { grid-template-columns: auto 1fr; height: auto; min-height: 52px; gap: 12px; }
  .status { display: none; }
  .tagline span:not(:first-child) { display: none; }
  .tabs a { padding: 0 14px; flex: 1 0 auto; justify-content: center; }
  /* narrow phones: let all four tabs share the width instead of the last
     one hanging off the right edge behind a scroll */
  @media (max-width: 410px) {
    .tabs a { flex: 1 1 0; min-width: 0; padding: 0 6px; gap: 5px; font-size: 14px; }
  }
  .panel-body { grid-template-columns: minmax(0, 1fr); }
  .con-bar .seg-label { max-width: 100%; }
  .con-bar .seg { max-width: calc(100vw - 90px); overflow-x: auto; scrollbar-width: none; }
  /* style picker only: full width, no label, every style visible */
  .con-bar .style-pick { width: 100%; }
  .con-bar .style-pick > span { display: none; }
  .con-bar .style-pick > .seg { flex: 1; max-width: none; }
  .con-bar .style-pick > .seg button { flex: 1 1 auto; padding: 0 6px; font-size: 12px; }
  /* the groovebox's control rows carry a lot of small segments; tighten them
     so they stack into fewer lines on a phone */
  .seq-top, .seq-sub { gap: 6px 10px; }
  .seq-sub .seg button { padding: 0 9px; font-size: 12.5px; }
  .seg-label > span { font-size: 11.5px; }
  .con-grid { gap: 8px; }
  .mod { padding: 8px; gap: 8px; }
  .rig ul { columns: 1; }
  .panel-main { min-height: 0; padding: 16px 14px 18px; }
  .photo { position: relative; right: auto; width: auto; height: 200px;
    --fade-x: linear-gradient(180deg, #000 0%, #000 40%, rgba(0,0,0,.93) 50%, rgba(0,0,0,.78) 60%, rgba(0,0,0,.56) 70%, rgba(0,0,0,.32) 80%, rgba(0,0,0,.12) 90%, rgba(0,0,0,0) 100%); }
  .mod-seq { order: -1; }
  .panel-side { border-left: 0; border-top: 1px solid var(--line); }
  .covers { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
  .posters { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cover { width: auto; }
  .con-grid { grid-template-columns: 1fr; padding: 8px; }
  .lane { grid-template-columns: 72px minmax(0, 1fr); gap: 5px; }
  .lane-label { padding: 0 0 0 6px; font-size: 11.5px; gap: 2px; }
  .lane-label .name::before { display: none; }
  .lane-dice { width: 18px; font-size: 12px; }
  .cells { gap: 2px; }
  .cell { min-height: 34px; font-size: 10px; }
  .roll-body { height: 236px; }
  .roll { --rkey: 56px; }
  .rrow { height: 22px; }
  .rkey { font-size: 10.5px; padding: 0 5px; }
  .foot-keys { display: none; }
  .knob { --dial: 52px; }
}
@media (prefers-reduced-motion: reduce) {
  .js .panel.active { animation: none; }
}

/* modulation routes */
.cable-hit { pointer-events: stroke; cursor: grab; }
.routes { display: grid; gap: 3px; }
.route { display: grid; grid-template-columns: minmax(0, 1fr) 90px 42px 22px; align-items: center; gap: 8px; padding: 4px 8px; border: 1px solid var(--line); border-radius: var(--r); background: var(--inset); }
.route-name { font-size: 12.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.route input[type=range] { width: 100%; accent-color: var(--accent); }
.route-amt { font-size: 12px; font-weight: 600; text-align: right; }
.route-rm { width: 22px; height: 22px; border-radius: 3px; font-size: 15px; color: var(--muted); }
.route-rm:hover { background: var(--accent); color: var(--on-accent); }
.route-empty { margin: 0; font-size: 12px; color: var(--muted); }
.knob svg .ranges path { opacity: .9; }

/* ─── touch ─────────────────────────────────────────────
   Fingers are blunter than a mouse, so on a touch screen the small controls
   grow rather than get an invisible overlay — growing pushes neighbours
   apart, an overlay would sit on top of them and swallow their taps. Last in
   the file so it wins over the width breakpoints above. */
@media (pointer: coarse) {
  .foot { gap: 16px 18px; }
  .foot > a, .foot nav a, .side-links a { display: inline-block; padding-block: 12px; }
  .side-links { gap: 2px 16px; }
  .mail-link { display: inline-block; padding-block: 10px; }
  /* inline inside running text, where padding can't grow the line box */
  /* opening the leading first gives the overlay a line of its own to sit in,
     so it isn't fighting the lines above and below for the tap */
  .side-note { line-height: 2.6; }
  .side-note a { position: relative; z-index: 2; }
  .side-note a::after { content: ""; position: absolute; z-index: 2; left: -4px; right: -4px; top: 50%; height: 31px; transform: translateY(-50%); }
  .logo { display: inline-flex; align-items: center; min-height: 44px; }
  .jack-name { position: relative; z-index: 1; padding: 7px; margin: -7px; }
  .mute { width: 32px; height: 30px; }
  /* the step grid is what you actually poke at, so give it room */
  .cell { min-height: 34px; }
  .lane-dice { width: 24px; }
}

/* ── per-step edits: parameter locks, trig conditions, the sampler ──
   A step can carry its own knob values and its own condition, so the grid
   marks which ones do and the armed step is unmistakable — otherwise a
   pattern hides its per-step work and you edit the wrong thing. */
.cell.plock::after, .rcell.plock::after { content: ""; position: absolute; left: 3px; bottom: 3px; width: 4px; height: 4px; border-radius: 50%; background: var(--ink); opacity: .7; }
.cell.cnd::before, .rcell.cnd::before { content: ""; position: absolute; right: 3px; bottom: 3px; width: 0; height: 0; border-left: 5px solid transparent; border-bottom: 5px solid var(--ink); opacity: .55; }
.cell.armed, .rcell.armed { outline: 2px solid var(--sel); outline-offset: -2px; box-shadow: 0 0 0 2px color-mix(in srgb, var(--sel) 35%, transparent); }
.knob.locked .cap { stroke: var(--sel); stroke-width: 2; }
/* Live lock: the playhead is standing on a step that holds this knob at a
   value of its own. The dial keeps showing the track's setting and a second,
   darker pointer shows where the step is taking it, so you can read both at
   a glance instead of wondering whether a knob moved on its own. */
.knob .lock-arc { fill: none; stroke: var(--ink); stroke-width: 3.4; stroke-linecap: round; opacity: .8; }
.knob .lock-ptr { stroke: var(--ink); stroke-width: 2.6; stroke-linecap: round; }
.knob.live-lock .cap { stroke: var(--ink); stroke-width: 1.8; }
.knob.live-lock .k-label { color: var(--ink); font-weight: 700; }
.knob.live-lock .k-val { box-shadow: inset 0 0 0 1.5px var(--ink); }
.ktog.live-lock .sw-track { stroke: var(--ink); stroke-width: 3; }
.ktog.live-lock.live-on .sw-track { fill: var(--accent); }
.ktog.live-lock.live-on .sw-cap { fill: #fff; }
.ktog.live-lock .k-val { box-shadow: inset 0 0 0 1.5px var(--ink); }
.ktog.live-lock .k-label { color: var(--ink); font-weight: 700; }
@media (prefers-reduced-motion: reduce) { .knob .lock-arc, .knob .lock-ptr { transition: none; } }
.knob.locked .k-label { color: var(--accent-deep); font-weight: 700; }
.knob.locked .k-val { background: var(--sel); color: var(--sel-ink); }
.ntog.locked { border-color: var(--sel); box-shadow: inset 0 0 0 1px var(--sel); }
/* One line, always: wrapping onto a second pushed the lanes out of the
   column. The two descriptions give way first (full text on hover); the
   step number, the picker and the button never shrink. */
.step-bar { margin-bottom: 2px; flex-wrap: nowrap; min-width: 0; }
.step-bar > * { flex: 0 0 auto; }
.step-bar .chord-info { flex: 0 1 auto; min-width: 0; overflow: hidden; }
.step-bar .chord-info .chord-what { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; font-size: 13px; }
.step-bar .chord-info .chord-scope { flex: 0 0 auto; }
.step-bar .cond-what { flex: 1 1 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12.5px; color: var(--muted); }
.step-bar .cond-what.set { color: var(--ink); }
.step-bar.prompt .chord-scope { background: var(--line); color: var(--ink-2); }
.step-bar.prompt .cond-pick { opacity: .45; }
.cond-pick .sel { min-width: 92px; }
/* with the step bar up the lanes give it the room: a few px off each row
   rather than the column scrolling on a 900px-tall window */
.mod-seq:has(.step-bar:not([hidden])) .lanes .cell { min-height: 24px; }

/* The file line rides on its own waveform rather than taking a row above it.
   That row was the one that pushed a sampler track's second knob row onto
   the patch bay. */
.smp-bar { position: absolute; z-index: 2; left: 5px; right: 5px; top: 5px;
  display: flex; flex-wrap: nowrap; align-items: center; gap: 6px; min-width: 0; }
.smp-bar .cbtn { flex: 0 0 auto; height: 22px; padding: 0 9px; font-size: 11px; }
/* both sit on the waveform, so both carry their own backing or the name
   disappears into the loudest part of the sample */
.smp-bar .cbtn { background: var(--plate); }
.smp-bar .smp-name { padding: 2px 6px; border-radius: 3px; background: color-mix(in srgb, var(--inset) 84%, transparent); }
.smp-name { flex: 0 1 auto; min-width: 0; font-family: var(--mono); font-size: 12px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* a pattern can ask for a sample this browser hasn't got: say so rather than
   playing nothing and leaving you guessing */
.smp-name.missing { color: var(--accent-deep); font-weight: 700; }
.smp-name.missing::before { content: "missing · "; }

/* velocity pickers: the one beside Tap and the picked note's own */
.vel { display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 8px; border: 1px solid var(--line-2); border-radius: var(--r); background: var(--inset); }
.vel input[type=range] { width: 76px; margin: 0; accent-color: var(--accent); cursor: pointer; }
.vel-val { min-width: 3ch; font-family: var(--mono); font-size: 12.5px; font-weight: 700; text-align: right; color: var(--ink); }
.note-togs .vel { height: 26px; }
.note-togs .vel input[type=range] { width: 64px; }
@media (min-width: 1100px) and (max-width: 1399px) and (min-height: 760px) {
  .vel { padding: 0 6px; gap: 4px; } .vel input[type=range] { width: 58px; }
  .vel-pick > span { display: none; }
}

/* ── full screen ── */
.con-tools { display: flex; align-items: center; gap: 6px; }
.fs-btn .ico, .midi-btn .ico { width: 15px; height: 15px; }
/* Expanded: the page's sections step aside and the groovebox takes the room
   between the tabs and the footer. */
html.gb-big .stage { display: none; }
.fs-btn[aria-label^="Back"] { background: var(--ink); color: var(--paper); border-color: var(--ink); }
@media (min-width: 1100px) and (min-height: 760px) {
  html.gb-big .app { grid-template-rows: 54px 42px minmax(0, 1fr) 42px; }
  html.gb-big .console { display: grid; min-height: 0; padding-top: 12px; }
  .con.big { height: auto; min-height: 0; }
  /* laid out at one comfortable size and scaled to fit (see fitBig), so the
     columns are the ones that size was designed with */
  .con.big .con-body { position: relative; overflow: hidden; }
  .con.big .con-grid { grid-template-columns: 330px 300px minmax(0, 1fr); }
  .con.big .roll-head { gap: 6px 10px; }
  .con.big .chord-pick > span { display: none; }
  .con.big .chord-tools .seg button { padding: 0 7px; font-size: 11.5px; }
  .con.big .ntog { height: 24px; padding: 0 8px; font-size: 11px; }
  .con.big .roll-legend { display: none; }
  .con.big .vel { padding: 0 6px; gap: 4px; } .con.big .vel input[type=range] { width: 58px; }
  .con.big .vel-pick > span { display: none; }
  /* spare height: the roll grows, and its rows with it */
  .con.big .roll-body { height: auto; flex: 1 1 auto; min-height: 120px; display: flex; flex-direction: column; }
  .con.big .mod-seq > .roll:not([hidden]) { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
  .con.big .rrow { flex: 1 0 auto; height: auto; min-height: 19px; }
  .con.big .roll-chords .rrow { min-height: 29px; }
}

/* ── MIDI ── */
.midi { position: relative; display: flex; }
.midi-btn.live { color: var(--accent); }
.midi-btn[aria-expanded="true"] { background: var(--ink); color: var(--paper); }
/* a sheet in the corner, clear of the controls it maps */
.midi-pop { position: fixed; right: 12px; top: 12px; z-index: 1100; width: min(340px, calc(100vw - 24px));
  max-height: min(70vh, 520px); overflow-y: auto; overscroll-behavior: contain; padding: 12px 14px 14px;
  border-radius: var(--r); background: var(--ink); color: var(--paper); font-family: var(--sans); font-size: 12.5px;
  line-height: 1.45; letter-spacing: 0; text-transform: none; box-shadow: 0 12px 30px rgba(0,0,0,.28); }
.midi-pop[hidden] { display: none; }
.midi-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.midi-head .grow { flex: 1; }
.midi-head strong { font-family: var(--mono); font-size: 11px; letter-spacing: .12em; color: #ffb27d; }
.midi-learn-btn { min-width: 64px; justify-content: center; }
.midi-learn-btn[aria-pressed="true"] { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.midi-status, .midi-hint { margin: 0 0 8px; color: #d8d3c8; }
.midi-hint:empty { display: none; }
.midi-hint { color: #ffd9bd; }
.midi-opt { display: flex; gap: 8px; align-items: flex-start; margin: 0 0 8px; color: #d8d3c8; cursor: pointer; }
.midi-opt input { margin: 2px 0 0; accent-color: var(--accent); flex: none; }
.midi-list { list-style: none; margin: 10px 0 0; padding: 8px 0 0; border-top: 1px solid rgba(255,255,255,.14); display: grid; gap: 2px; }
.midi-list li { display: grid; grid-template-columns: 104px minmax(0, 1fr) 22px; align-items: center; gap: 8px; }
.midi-list .midi-empty { display: block; color: #9c978d; }
.midi-msg { font-family: var(--mono); font-size: 10.5px; color: #ffb27d; white-space: nowrap; }
.midi-to { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.midi-pop .route-rm { color: #9c978d; }
.midi-foot { display: flex; justify-content: flex-end; margin-top: 8px; }
.midi-foot .cbtn[hidden] { display: none; }
/* while learning, the sheet shrinks to what you need and the controls say
   they can be picked */
.con.midi-learning .midi-pop .midi-opt,
.con.midi-learning .midi-pop .midi-list, .con.midi-learning .midi-pop .midi-foot { display: none; }
.con.midi-learning .knob:not(.empty), .con.midi-learning .fader, .con.midi-learning [data-midi],
.con.midi-learning .cell { cursor: crosshair; }
.con.midi-learning .knob:not(.empty), .con.midi-learning .fader, .con.midi-learning .con-body [data-midi],
.con.midi-learning .con-bar [data-midi] { outline: 1px dashed color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: 1px; }
.con.midi-learning .midi-mapped { outline: 2px solid color-mix(in srgb, var(--accent) 70%, transparent) !important; outline-offset: 1px; }
.con.midi-learning .midi-armed { outline: 2px solid var(--accent) !important; outline-offset: 2px;
  animation: midi-arm 0.9s ease-in-out infinite alternate; }
@keyframes midi-arm { to { outline-color: transparent; } }
@media (prefers-reduced-motion: reduce) { .con.midi-learning .midi-armed { animation: none; } }
/* full screen, the top holds the bar it maps, so the sheet goes low; on a
   phone it's a sheet along the bottom like the help */
@media (max-width: 760px) {
  .midi-pop { top: auto; left: 10px; right: 10px; bottom: 10px; width: auto; max-width: 520px; margin: 0 auto; max-height: 62dvh; }
}
/* the bar holds the MIDI and full-screen buttons too, so the tagline steps
   aside before it would push the style picker onto a second row */
@media (max-width: 1599px) { .con-sub { display: none; } }

/* ── step keys: the eight steps 1–8 play, over the lanes ── */
.kb-row { display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: 8px; margin: 0 0 -2px; user-select: none; }
.kb-row[hidden] { display: none; }
.kb-label { font-family: var(--mono); font-size: 9.5px; letter-spacing: .08em; color: var(--faint); align-self: center; padding-left: 4px; }
.kb-ticks { display: grid; grid-template-columns: repeat(16, minmax(0, 1fr)); gap: 3px; }
.kb-ticks span { height: 15px; display: grid; place-items: center; font-family: var(--mono); font-size: 9.5px; font-weight: 700; color: var(--faint); border-radius: 2px; }
.kb-ticks span.on { color: var(--ink); box-shadow: inset 0 -2px 0 var(--tint, var(--accent)); }
.kb-ticks span.hold { background: var(--tint, var(--accent)); color: var(--paper); box-shadow: none; }
.cell.held { outline: 2px solid var(--ink); outline-offset: -2px; }
.rtick.kb { box-shadow: inset 0 -2px 0 var(--accent); }
/* no keyboard, no key row */
@media (hover: none), (pointer: coarse), (max-width: 760px) { .kb-row { display: none !important; } }
.midi-acts { margin: 4px 0 10px; }
.midi-acts p { margin: 0 0 6px; color: #9c978d; font-size: 11.5px; }
.midi-act-row { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; margin-bottom: 5px; }
.midi-act-row > span { font-family: var(--mono); font-size: 10px; color: #9c978d; margin: 0 2px 0 4px; }
.midi-act-row > span:first-child { margin-left: 0; min-width: 58px; }
.midi-act { min-width: 24px; height: 24px; padding: 0 6px; border-radius: 3px; border: 1px solid rgba(255,255,255,.2); color: var(--paper); font-family: var(--mono); font-size: 11px; font-weight: 700; }
.midi-act:hover { border-color: var(--accent); }
.midi-act.all { font-weight: 500; color: #ffd9bd; }
.midi-act.midi-mapped { background: color-mix(in srgb, var(--accent) 35%, transparent); border-color: var(--accent); outline: none !important; }
.midi-act.midi-armed { background: var(--accent); border-color: var(--accent); color: var(--on-accent); outline: none !important; animation: none; }
