/* ── theme ────────────────────────────────────────────────────────────────
   Neon: near-black ground, mint left hand, magenta right hand.

   The two accents are not decoration. Left hand picks the harmony and right
   hand shapes the sound, so each gets its own colour and keeps it everywhere
   — the tracked skeleton, the guide diagrams, the volume and filter meters.
   Once you have seen mint mean "left" on the landing page, the live skeleton
   needs no legend.

   Single palette, no toggle. The design is built on glow against near-black;
   a light variant would have to throw that away and would not be the same
   product. Everything below reads tokens only. */

:root {
  --bg: #08080C;
  --bg-2: #0C0C14;
  --card: #161826;
  --card-2: #1A1C2C;

  --ink: #E8EAF2;
  --ink-2: #C6C9DA;
  --dim: #9296AE;
  --dim-2: #7A7E96;
  --dim-3: #6E7290;

  /* Left hand — harmony. Doubles as the primary accent. */
  --left: #2CF0C0;
  --left-rgb: 44, 240, 192;
  /* Right hand — expression. */
  --right: #FF00E5;
  --right-rgb: 255, 0, 229;

  --accent: var(--left);
  --accent-hi: #5FF6D2;
  --accent-rgb: var(--left-rgb);
  --on-accent: #08080C;

  --rule: #22243A;
  --rule-soft: #1E2030;
  --panel: rgba(8, 8, 12, .78);
  --scrim: rgba(8, 8, 12, .97);
  --shadow: rgba(0, 0, 0, .6);

  /* Diagonal weave under the two accent glows — the design's ground. */
  --stage:
    radial-gradient(56% 50% at 50% 44%, rgba(255, 0, 229, .12), transparent 72%),
    radial-gradient(46% 46% at 30% 46%, rgba(44, 240, 192, .12), transparent 72%),
    repeating-linear-gradient(135deg, #0E0E18 0 7px, #0A0A11 7px 14px);
  color-scheme: dark;
}

:root {
  --display: 'Archivo', 'Helvetica Neue', Arial, sans-serif;
  --head: 'Archivo', 'Helvetica Neue', Arial, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
  --prose: 'Archivo', 'Helvetica Neue', Arial, sans-serif;
}

/* Archivo is variable on both axes. The display sizes run wide and heavy;
   anything smaller stays near normal width so it keeps its counters. */
.wide-900 { font-variation-settings: 'wdth' 125, 'wght' 900; }
.wide-800 { font-variation-settings: 'wdth' 110, 'wght' 800; }

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 15px;
  line-height: 1.6;
}
button, select, input { font: inherit; font-family: var(--mono); }
.sr-only {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* Uppercase mono at a wide tracking is the label voice across the whole site. */
.label-cap {
  font-family: var(--mono); font-size: 10px; font-weight: 500;
  letter-spacing: .2em; text-transform: uppercase;
}

/* ── instrument stage ─────────────────────────────────────────────────── */

#stage {
  position: relative;
  width: 100%;
  height: 100svh;
  overflow: hidden;
  background: var(--stage);
}
#webcam { display: none; }
#overlay {
  position: absolute; inset: 0; width: 100%; height: 100%;
  transition: filter .4s, opacity .4s;
}
#overlay.dimmed { opacity: 0; }

/* ── top bar ──────────────────────────────────────────────────────────── */

.topbar {
  position: absolute; z-index: 18; top: 0; left: 0; right: 0;
  display: flex; align-items: center; gap: 20px;
  height: 60px; padding: 0 28px;
  font-size: 10.5px; letter-spacing: .24em; text-transform: uppercase;
  color: var(--dim-2);
  background: linear-gradient(var(--scrim), transparent 94%);
}
#stage.live .topbar { background: linear-gradient(rgba(8, 8, 12, .88), transparent 96%); }
#stage.live .topnav { display: none; }

.back-btn {
  min-height: 34px; padding: 8px 16px; cursor: pointer; white-space: nowrap;
  border: 1px solid rgba(var(--right-rgb), .5); background: rgba(8, 8, 12, .7);
  color: var(--right); font-family: var(--mono); font-size: 10.5px;
  font-weight: 500; letter-spacing: .2em; text-transform: uppercase;
}
.back-btn:hover { background: rgba(var(--right-rgb), .12); }

.mark {
  margin: 0; font-family: var(--mono); font-weight: 500;
  font-size: 10.5px; letter-spacing: .3em; text-transform: uppercase;
  color: var(--left); white-space: nowrap;
}
.mark .mark-short { display: none; }
#stage.live .mark { display: none; }

/* Live telemetry replaces the page nav once the camera is on. Frames per
   second and the tracker's own cost are the two numbers that explain a
   sluggish instrument, and "0 bytes uploaded" is the privacy claim stated as
   a running measurement rather than a promise in a footer. */
.live-stat { display: none; align-items: center; gap: 9px; color: var(--left); white-space: nowrap; }
#stage.live .live-stat { display: inline-flex; }
.live-stat .pulse {
  width: 7px; height: 7px; border-radius: 50%; background: var(--left);
  animation: gsw-pulse 1.3s ease-in-out infinite;
}
@keyframes gsw-pulse { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }

.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 26px; }
.topnav { display: flex; gap: 26px; }
.topnav a {
  font-family: var(--mono); font-size: 10.5px; font-weight: 400;
  letter-spacing: .24em; text-transform: uppercase;
  color: var(--dim-2); text-decoration: none;
}
.topnav a:hover { color: var(--right); }

.claim { color: var(--right); white-space: nowrap; }
#stage.live .claim-idle { display: none; }
.claim-live { display: none; }
#stage.live .claim-live { display: inline; }

/* ── bottom dock ──────────────────────────────────────────────────────── */

/* One group open at a time. The old toolbar unfolded everything at once over
   the camera feed, which meant the moment you wanted to change the key you
   also lost sight of your hands. */
.dock {
  position: absolute; z-index: 6; left: 0; right: 0; bottom: 0;
  transition: transform .22s ease;
}
#stage:not(.live) .dock { display: none; }

/* Collapsed means gone, not folded in place. The tab strip alone was still a
   90px band of chrome across the bottom of a camera feed you are trying to see
   your hands in, so it slides off the screen and leaves a handle. */
.dock.tucked { transform: translateY(calc(100% - 6px)); }
.dock.tucked:hover, .dock.tucked:focus-within { transform: translateY(0); }

/* Fades the camera into the dock instead of cutting it off with a hard edge. */
.dock::before {
  content: ''; position: absolute; left: 0; right: 0; bottom: 100%; height: 60px;
  pointer-events: none; background: linear-gradient(0deg, rgba(8, 8, 12, .92), transparent);
}
.dock.tucked::before { background: none; }
.dock.tucked:hover::before, .dock.tucked:focus-within::before {
  background: linear-gradient(0deg, rgba(8, 8, 12, .92), transparent);
}

/* The reveal strip: invisible, 60px tall, sitting directly above the handle.
   Being a child of .dock means hovering it holds .dock:hover, so the pointer
   never crosses a dead gap on its way in. It only exists while tucked, so it
   never intercepts anything the rest of the time. */
.dock-reveal {
  display: none; position: absolute; left: 0; right: 0; bottom: 100%; height: 60px;
  padding: 0; border: 0; background: none; cursor: pointer;
}
.dock.tucked .dock-reveal { display: block; }
.dock-reveal:focus-visible { outline: 1px solid var(--left); outline-offset: -4px; }

/* The handle. Without it "hover somewhere near the bottom" is an instruction
   nobody is given.
   Pinned to the dock's *top* edge, because that is the strip still on screen —
   anchoring it to the bottom put it below the viewport, where a handle is not
   much use. */
.dock::after {
  content: ''; position: absolute; left: 50%; top: 1px;
  width: 0; height: 4px; margin-left: 0; border-radius: 2px;
  background: var(--left); box-shadow: 0 0 16px rgba(var(--left-rgb), .8);
  opacity: 0; transition: opacity .22s ease;
}
.dock.tucked::after { width: 96px; margin-left: -48px; opacity: .9; }
.dock.tucked:hover::after, .dock.tucked:focus-within::after { opacity: 0; }

/* Near-opaque, not a 60% wash. The Settings group is four columns tall and a
   translucent panel puts small grey labels straight on top of a moving camera
   frame, which is unreadable at any brightness the room happens to be. */
.dock-panel {
  position: relative;
  display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 22px;
  padding: 24px 28px 20px; border-bottom: 1px solid var(--rule-soft);
  background: rgba(8, 8, 12, .96);
}
.dock-panel.collapsed { display: none; }
.dock-body { min-width: 0; display: grid; gap: 12px; align-content: start; }
.dock-body > .group { display: none; }
.dock-body > .group.on { display: grid; gap: 12px; }

/* Mode picker — three cards rather than a dropdown. The mode changes what
   both hands do, so it is worth the space its own description takes. */
.mode-cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }
.mode-card {
  display: flex; flex-direction: column; gap: 8px; text-align: left;
  padding: 16px 18px; cursor: pointer;
  border: 1px solid var(--rule-soft); background: none;
}
.mode-card b {
  font-size: 12px; font-weight: 500; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-2);
}
.mode-card span { font-size: 11.5px; letter-spacing: .02em; color: var(--dim-2); line-height: 1.5; }
.mode-card:hover { border-color: var(--rule); }
.mode-card.on { background: rgba(var(--left-rgb), .12); border-color: rgba(var(--left-rgb), .5); }
.mode-card.on b { color: var(--left); }
.mode-card.on span { color: var(--dim); }

/* ── meters ───────────────────────────────────────────────────────────────
   Volume and filter are readouts, not settings. They start as rails down the
   right-hand edge — beside the hands that actually drive them, and out of the
   way of the camera's middle — and fold into the dock on request. The same
   block moves between two hosts, so there is one set of nodes and one set of
   values whichever shape it is wearing. */

.meters-host.floating {
  position: absolute; z-index: 5; right: 34px; top: 50%; transform: translateY(-50%);
}
#stage:not(.live) .meters-host.floating { display: none; }
#metersDock:empty { display: none; }
/* The dock's fourth column only exists while the meters are actually in it. */
.dock-panel:has(#metersDock:not(:empty)) { grid-template-columns: minmax(0, 1fr) 354px; }
.dock-panel { grid-template-columns: minmax(0, 1fr); }
#metersDock {
  display: flex; flex-direction: column; justify-content: center;
  padding-left: 22px; border-left: 1px solid var(--rule-soft);
}

.meters { display: flex; flex-direction: column; gap: 14px; }
.meters-head {
  margin: 0; font-size: 9.5px; letter-spacing: .26em;
  text-transform: uppercase; color: var(--dim-2);
}
.meters.floating .meters-head { display: none; }
.meters.floating { align-items: flex-end; }

/* The handle between the two homes. Hidden until you go looking for it: this
   is a preference you set once, and a control that never changes has no
   business sitting on top of the picture the rest of the time. */
.meters-arrow {
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0; cursor: pointer;
  border: 1px solid rgba(232, 234, 242, .22); background: rgba(8, 8, 12, .35);
  backdrop-filter: blur(4px);
  color: var(--ink-2); font-size: 12px; line-height: 1;
  opacity: 0; pointer-events: none;
  transition: opacity .22s ease, background .2s, color .2s, border-color .2s;
}
.meters:hover .meters-arrow, .meters:focus-within .meters-arrow { opacity: 1; pointer-events: auto; }
.meters-arrow:hover { background: var(--left); border-color: var(--left); color: var(--on-accent); }
/* Nothing to hover on a touchscreen, and an invisible control you cannot
   reveal is the same as no control. */
@media (hover: none) {
  .meters-arrow { opacity: .75; pointer-events: auto; }
}

.meters:not(.floating) .meter-row { display: grid; gap: 14px; }
.meters.floating .meter-row { display: flex; gap: 26px; }

/* Docked: label and value on one line, track underneath. Named areas rather
   than auto-placement — the source order is label, track, value (which is what
   the vertical rail needs), and letting the grid flow it would drop the value
   onto a third row. */
.meters:not(.floating) .meter {
  display: grid; gap: 7px 10px; align-items: baseline;
  grid-template-columns: 1fr auto;
  grid-template-areas: 'label value' 'track track';
}
.meters:not(.floating) .meter-label { grid-area: label; }
.meters:not(.floating) .meter-value { grid-area: value; text-align: right; }
.meters:not(.floating) .meter-track { grid-area: track; height: 5px; }

/* Floating: label above the rail, value below it.
   The min-width is load-bearing: the rail is 8px but "FILTER" is not, and a
   centred label on a shrink-to-fit column hangs over the container's right
   edge, where #stage's overflow clips it clean off. */
.meters.floating .meter {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  min-width: 62px;
}
.meters.floating .meter-track { width: 8px; height: min(300px, 34vh); }

.meter-label {
  font-size: 10.5px; letter-spacing: .2em; text-transform: uppercase; color: var(--dim-2);
}
.meters.floating .meter-label { font-size: 9.5px; letter-spacing: .24em; }
.meter-value { font-weight: 400; font-size: 11px; font-variant-numeric: tabular-nums; }
.meter.vol .meter-value { color: var(--left); }
.meter.flt .meter-value { color: var(--right); }
.meter-track { position: relative; background: rgba(22, 24, 38, .75); }
/* Floating, the meters sit directly on the camera picture, so they carry no
   panel — just a glass track and glyphs that hold their own contrast. A solid
   plate here would be a box cut out of the thing you are trying to watch. */
.meters.floating .meter-track {
  background: rgba(232, 234, 242, .13);
  backdrop-filter: blur(3px);
  box-shadow: inset 0 0 0 1px rgba(232, 234, 242, .10);
}
.meters.floating .meter-label,
.meters.floating .meter-value {
  text-shadow: 0 0 6px rgba(8, 8, 12, .95), 0 0 16px rgba(8, 8, 12, .8);
}
.meters.floating .meter-label { color: var(--ink-2); }
.meters.floating .meter-track i { box-shadow: 0 0 12px currentColor; }
.meters.floating .meter.vol .meter-track i { color: var(--left); }
.meters.floating .meter.flt .meter-track i { color: var(--right); }
.meter-track i { position: absolute; display: block; }
.meter.vol .meter-track i { background: var(--left); }
.meter.flt .meter-track i { background: var(--right); }
/* Vertical rails read better as a gradient than a flat block — the design's
   own treatment, and it keeps a near-zero level from vanishing entirely. */
.meters.floating .meter.vol .meter-track i {
  background: linear-gradient(0deg, var(--left), rgba(var(--left-rgb), .35));
}
.meters.floating .meter.flt .meter-track i {
  background: linear-gradient(0deg, var(--right), rgba(var(--right-rgb), .3));
}

.dock-tabs {
  position: relative;
  display: flex; align-items: center; gap: 12px; padding: 16px 28px 20px;
  background: rgba(8, 8, 12, .97);
}
.dock-tab-group {
  display: flex; gap: 1px; background: var(--rule-soft); border: 1px solid var(--rule-soft);
  overflow-x: auto; scrollbar-width: none;
}
.dock-tab-group::-webkit-scrollbar { display: none; }
.dock-tab {
  padding: 14px 22px; border: 0; cursor: pointer; white-space: nowrap;
  background: var(--bg); color: var(--dim);
  font-size: 11px; letter-spacing: .18em; text-transform: uppercase;
}
.dock-tab b { font-weight: 400; color: var(--ink-2); }
.dock-tab:hover { color: var(--ink-2); }
.dock-tab.on { background: var(--card); color: var(--ink); }
.dock-tab.on b { color: var(--left); }
.dock-spacer { flex: 1; }

.dock-tabs button:not(.dock-tab):not(.rec) {
  padding: 14px 20px; cursor: pointer; white-space: nowrap;
  border: 1px solid var(--rule); background: none; color: var(--ink-2);
  font-size: 11px; letter-spacing: .18em; text-transform: uppercase;
}
.dock-tabs button:not(.dock-tab):not(.rec):hover { border-color: var(--left); color: var(--left); }

.rec {
  padding: 14px 24px; border: 1px solid var(--left); cursor: pointer; white-space: nowrap;
  background: var(--left); color: var(--on-accent);
  font-size: 11px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase;
  box-shadow: 0 0 26px rgba(var(--left-rgb), .3);
}
#recordBtn.recording {
  background: var(--right); border-color: var(--right); color: #08080C;
  box-shadow: 0 0 26px rgba(var(--right-rgb), .35);
}
#recordTime { font-variant-numeric: tabular-nums; opacity: .8; }
#recordTime:empty { display: none; }
#midiBtn { border-color: var(--right) !important; color: var(--right) !important; }

/* ── form controls ────────────────────────────────────────────────────── */

select, .dock button.plain {
  min-height: 40px; padding: 9px 14px;
  background: var(--bg); color: var(--ink);
  border: 1px solid var(--rule-soft);
  font-size: 12px; font-weight: 400; letter-spacing: .04em; cursor: pointer;
}
select:hover, .dock button.plain:hover { border-color: var(--left); color: var(--left); }
select {
  appearance: none; padding-right: 34px; min-width: 130px;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--left) 50%),
    linear-gradient(135deg, var(--left) 50%, transparent 50%);
  background-position: calc(100% - 18px) 18px, calc(100% - 13px) 18px;
  background-size: 5px 5px; background-repeat: no-repeat;
}
select:disabled { opacity: .42; cursor: not-allowed; }
.control-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.check {
  display: inline-flex; align-items: center; gap: 9px;
  min-height: 40px; padding: 9px 14px;
  background: var(--bg); border: 1px solid var(--rule-soft);
  color: var(--ink-2); font-size: 12px; cursor: pointer;
}
.check input { accent-color: var(--left); width: 15px; height: 15px; }

.panel {
  display: grid; gap: 11px; max-width: 100%;
  max-height: min(46vh, 420px); overflow-y: auto; padding-right: 4px;
}
.panel hr { border: 0; border-top: 1px solid var(--rule-soft); width: 100%; margin: 4px 0; }
.panel-head {
  margin: 0; font-family: var(--mono); font-size: 9.5px; font-weight: 500;
  letter-spacing: .22em; text-transform: uppercase; color: var(--left);
}
.field { display: grid; gap: 5px; font-size: 11px; color: var(--dim-2); letter-spacing: .07em; }
.field select { width: 100%; }
.field-row { display: flex; gap: 8px; align-items: flex-end; }
.field-row .grow { flex: 1; min-width: 0; }
.field-row .grow select { min-width: 0; }
.panel input[type="range"] { width: 100%; accent-color: var(--left); height: 22px; cursor: pointer; }
.panel-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 11px 18px; }
.tap {
  min-height: 38px; padding: 9px 14px; cursor: pointer;
  border: 1px solid var(--rule); background: transparent;
  color: var(--left); font-size: 10px; font-weight: 500; letter-spacing: .18em;
}
.tap:active { background: var(--left); color: var(--on-accent); }

/* Gesture map — the shape-to-chord table, shown in its own dock group. */
.gesture-guide { display: flex; flex-wrap: wrap; gap: 10px 26px; }
.gesture-guide.hidden, .panel.hidden, .modal.hidden { display: none; }
.guide-row { display: flex; gap: 12px; align-items: center; min-width: 112px; }
.guide-row b { width: 42px; color: var(--left); font-family: var(--head); font-size: 15px; font-weight: 700; }
.guide-row span { font-size: 20px; }
.guide-row span.txt { font-size: 11px; color: var(--dim-2); letter-spacing: .05em; }

/* ── live readouts ────────────────────────────────────────────────────── */

#countdown {
  position: absolute; inset: 0; z-index: 12;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; pointer-events: none; background: rgba(8, 8, 12, .35);
}
#countdown[hidden] { display: none; }
#countdownLabel {
  font-size: 11px; letter-spacing: .3em; text-transform: uppercase;
  color: var(--left); text-shadow: 0 0 24px #000;
}
#countdownNum {
  font-family: var(--display); font-variation-settings: 'wdth' 118, 'wght' 900;
  font-size: clamp(90px, 24vw, 240px);
  color: var(--ink); line-height: 1; text-shadow: 0 0 40px rgba(0, 0, 0, .8);
}

/* Sits above the dock, not at the bottom edge — the dock owns that band now.
   The offset tracks the dock's measured height (app.js keeps --dock-h current)
   because that height changes with the open group and again when it folds; a
   fixed number would either collide with Settings or float mid-screen once the
   dock was collapsed. */
#status {
  position: absolute; left: 0; right: 0; bottom: calc(var(--dock-h, 232px) + 20px);
  z-index: 4; text-align: center; pointer-events: none;
  transition: bottom .22s ease;
}
/* A tucked dock parks the readout near the floor, which is exactly where the
   strip lands when you hover it back. Lift the readout out of the way for as
   long as the dock is out. `:has()` cannot be expressed as a sibling rule
   because #status comes first in the DOM; where it is unsupported the readout
   simply stays put and the dock overlaps it. */
#stage:has(.dock.tucked:hover) #status,
#stage:has(.dock.tucked:focus-within) #status { bottom: 116px; }
#stage:not(.live) #status { display: none; }
.now-playing { font-size: 10px; letter-spacing: .28em; text-transform: uppercase; color: var(--dim-2); }
#chordDisplay {
  margin-top: 10px;
  display: flex; align-items: center; justify-content: center;
  /* Held at the height of a full-size chord name in both states, so the line
     underneath does not jump every time a note starts or stops. */
  min-height: calc(clamp(44px, 7.5vw, 104px) * 0.86);
  font-family: var(--display); font-variation-settings: 'wdth' 118, 'wght' 900;
  font-size: clamp(44px, 7.5vw, 104px); line-height: .86; letter-spacing: -.04em;
  color: var(--ink);
  text-shadow: 0 0 12px var(--bg), 0 0 34px var(--bg), 0 0 70px var(--bg);
}
/* An em dash at 104px and weight 900 is a solid slab, not a placeholder — it
   reads as a graphic someone put there on purpose. Idle drops it to a thin
   dim rule; the line below is what actually explains the silence. */
#status.idle #chordDisplay {
  font-variation-settings: 'wdth' 100, 'wght' 300;
  font-size: clamp(26px, 2.6vw, 40px);
  color: var(--dim-3);
  text-shadow: 0 0 8px var(--bg), 0 0 18px var(--bg);
}
/* The tail of a chord name — the 7th, the 9th — in the right-hand colour,
   because on this instrument that extension is what the right hand adds. */
#chordDisplay em { font-style: normal; color: var(--right); }
/* When nothing is sounding this line is the only thing saying why, so it has
   to survive whatever the camera happens to be pointed at. */
#qualityDisplay {
  margin-top: 12px; font-size: 11px; letter-spacing: .24em; text-transform: uppercase;
  max-width: min(620px, 88vw); margin-left: auto; margin-right: auto;
  color: var(--left);
  text-shadow: 0 0 10px var(--bg), 0 0 24px var(--bg), 0 0 44px var(--bg);
}

/* Help lives in the top bar beside Stop, where the design puts it — a
   floating circle in the corner is easy to miss and easy to hit by accident. */
.bar-btn {
  min-height: 34px; padding: 8px 16px; cursor: pointer; white-space: nowrap;
  border: 1px solid var(--rule); background: rgba(8, 8, 12, .7);
  color: var(--ink-2); font-family: var(--mono); font-size: 10.5px;
  font-weight: 400; letter-spacing: .2em; text-transform: uppercase;
}
.bar-btn:hover { border-color: var(--left); color: var(--left); }
#stage:not(.live) #helpButton { display: none; }

/* ── quick guide modal ────────────────────────────────────────────────── */

.modal {
  position: fixed; inset: 0; z-index: 20; display: grid; place-items: start center;
  padding: 40px 18px; overflow-y: auto;
  background: rgba(8, 8, 12, .88); backdrop-filter: blur(8px);
}
.help-card {
  position: relative; width: min(880px, 96vw);
  padding: 34px 36px 30px; border: 1px solid var(--rule); background: var(--bg-2);
  box-shadow: 0 30px 100px rgba(0, 0, 0, .8);
}
.help-head { display: flex; align-items: center; gap: 14px; margin-bottom: 26px; }
.help-card h2 {
  margin: 0; font-family: var(--display); font-variation-settings: 'wdth' 110, 'wght' 800;
  font-size: clamp(26px, 4.4vw, 40px); line-height: 1; letter-spacing: -.02em;
  text-transform: uppercase; color: var(--ink);
}
.help-card h3 {
  font-family: var(--mono); font-size: 10px; font-weight: 500;
  letter-spacing: .22em; text-transform: uppercase; color: var(--left);
  margin: 0 0 10px;
}
.eyebrow {
  margin: 0; font-size: 10px; font-weight: 500; letter-spacing: .24em;
  text-transform: uppercase; color: var(--dim-2);
}
.help-card p { color: var(--dim); font-family: var(--prose); font-size: 13.5px; line-height: 1.65; }
#closeHelp {
  position: absolute; right: 18px; top: 14px; border: 0; background: none;
  color: var(--dim-2); font-size: 26px; cursor: pointer; line-height: 1;
}
#closeHelp:hover { color: var(--right); }

.help-flag {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 12px;
  padding: 14px 16px; margin-bottom: 24px;
  border: 1px solid rgba(var(--right-rgb), .4); background: rgba(var(--right-rgb), .06);
  font-size: 12px; color: var(--dim);
}
.help-flag .tag {
  padding: 3px 9px; background: var(--right); color: #08080C;
  font-size: 9.5px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
}
.help-flag b { color: var(--ink-2); }

.help-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 26px; }
.hand-col { display: grid; gap: 12px; align-content: start; }
.hand-col > h3 { margin: 0; }
.hand-col.right h3 { color: var(--right); }

/* Shape rows: five bars stand for five fingers, tall when the finger is up.
   A picture of the pose beats naming it — "index + pinky" is a sentence you
   have to decode, this is the shape itself. */
.shape-row {
  display: flex; align-items: center; gap: 14px;
  padding: 10px 12px; border: 1px solid var(--rule-soft);
}
.shape-row.on { border-color: rgba(var(--left-rgb), .5); background: rgba(var(--left-rgb), .08); }
.hand-col.right .shape-row.on { border-color: rgba(var(--right-rgb), .5); background: rgba(var(--right-rgb), .07); }
.shape-bars { display: flex; align-items: flex-end; gap: 3px; height: 20px; }
.shape-bars i { display: block; width: 5px; height: 7px; background: var(--rule-soft); transition: height .3s, background .3s; }
.shape-bars i.up { height: 19px; background: var(--dim-3); }
.shape-row.on .shape-bars i.up { background: var(--left); }
.hand-col.right .shape-row.on .shape-bars i.up { background: var(--right); }
.shape-n { min-width: 30px; font-size: 11px; letter-spacing: .12em; color: var(--dim-2); text-transform: uppercase; }
.shape-label { flex: 1; font-size: 12px; color: var(--ink-2); }
.shape-note { font-size: 10.5px; letter-spacing: .1em; color: var(--dim-3); text-transform: uppercase; }
.shape-row.on .shape-label { color: var(--ink); }

.help-other { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 16px; margin-top: 26px; }
.help-other div { border-top: 1px solid var(--rule-soft); padding-top: 12px; }
.help-other b { display: block; font-size: 10px; letter-spacing: .2em; text-transform: uppercase; color: var(--right); }
.help-other span { display: block; margin-top: 6px; font-size: 12px; color: var(--dim); }

.help-foot {
  display: flex; flex-wrap: wrap; align-items: center; gap: 12px;
  margin-top: 26px; padding-top: 18px; border-top: 1px solid var(--rule-soft);
  font-size: 11px; letter-spacing: .06em; color: var(--dim-2);
}
.help-foot .grow { flex: 1; }
.help-foot button { padding: 13px 22px; cursor: pointer; font-size: 11px; letter-spacing: .18em; text-transform: uppercase; }
#openSettingsFromHelp { border: 1px solid var(--rule); background: none; color: var(--ink-2); }
#gotItHelp { border: 1px solid var(--left); background: var(--left); color: var(--on-accent); font-weight: 700; }

.legend { display: grid; gap: 6px; margin: 0; }
.legend-row { display: flex; gap: 12px; align-items: baseline; font-size: 12px; color: var(--dim); }
.legend-row kbd { min-width: 84px; }

kbd {
  display: inline-block; padding: 2px 7px; border: 1px solid var(--rule);
  border-bottom-width: 2px; background: var(--card);
  color: var(--left); font-family: var(--mono); font-size: 11px; font-weight: 500;
}

/* Non-blocking strip: the camera preview stays visible behind it. */
#loading {
  position: absolute; z-index: 16; left: 50%; top: 74px; transform: translateX(-50%);
  display: flex; align-items: center; gap: 10px;
  padding: 9px 16px; border: 1px solid var(--rule); background: var(--panel);
  font-size: 10px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--dim); white-space: nowrap;
}
#loading[hidden] { display: none; }
#loading .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--left); animation: gsw-pulse 1.1s ease-in-out infinite; }
#loading .label { color: var(--ink-2); }
#loading .bar { display: block; width: min(140px, 26vw); height: 3px; background: var(--card); overflow: hidden; }
#loading .bar i { display: block; height: 100%; width: 0; background: var(--left); transition: width .2s linear; }
#loadingPct { color: var(--left); min-width: 34px; text-align: right; font-variant-numeric: tabular-nums; }

/* ── landing ──────────────────────────────────────────────────────────── */

#startOverlay {
  position: absolute; inset: 0; z-index: 15; overflow-y: auto;
  display: flex; flex-direction: column;
  padding: 78px 56px 40px;
}
#startOverlay[hidden] { display: none; }
/* Faint mint verticals and magenta horizontals — a drafting grid, not a
   texture. Both accents are present before a single hand is tracked. */
#startOverlay::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background:
    repeating-linear-gradient(90deg, rgba(var(--left-rgb), .055) 0 1px, transparent 1px 133px),
    repeating-linear-gradient(0deg, rgba(var(--right-rgb), .045) 0 1px, transparent 1px 128px);
}
/* One glow per accent, in opposite corners, so the two colours frame the page
   before either has anything to label. */
#startOverlay::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(620px 620px at -140px -120px, rgba(var(--left-rgb), .16), transparent 68%),
    radial-gradient(640px 640px at calc(100% + 160px) calc(100% + 180px), rgba(var(--right-rgb), .14), transparent 68%);
}

.start-card {
  position: relative; z-index: 1; flex: 1; min-height: 0;
  display: grid; grid-template-columns: minmax(0, 1fr) 372px; gap: 56px;
  /* Centred, not stretched. Pinning the call to action to the floor matches
     the mock at its own 900px height and opens a hole in the middle of any
     taller window; centring holds the composition together at every height. */
  align-items: center;
}
.hero-copy { display: flex; flex-direction: column; align-items: flex-start; }
.hero-eyebrow {
  display: flex; gap: 20px; align-items: baseline; margin: 0;
  font-size: 11px; letter-spacing: .26em; text-transform: uppercase; color: var(--left);
}
.hero-title {
  margin: 22px 0 0;
  font-family: var(--display); font-variation-settings: 'wdth' 125, 'wght' 900;
  font-size: clamp(46px, 7.4vw, 104px); line-height: .84; letter-spacing: -.035em;
  text-transform: uppercase; color: var(--ink);
}
/* Outlined rather than filled: the word is the loudest thing on the page and
   a second solid mass at that size fights the headline instead of finishing it. */
.hero-title em {
  font-style: normal; color: transparent;
  -webkit-text-stroke: 2px var(--right); text-stroke: 2px var(--right);
}
.hero-sub {
  margin: 26px 0 0; max-width: 52ch;
  font-family: var(--prose); font-size: 15px; line-height: 1.7; color: var(--dim);
  text-wrap: pretty;
}
.hero-sub b { color: var(--ink-2); font-weight: 500; }
.start-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 32px; }
.hero-copy .start-note { margin-bottom: 0; }
.start-actions button {
  padding: 16px 28px; cursor: pointer;
  font-size: 12.5px; font-weight: 500; letter-spacing: .18em; text-transform: uppercase;
  border: 1px solid var(--rule); background: none; color: var(--ink-2);
}
.start-actions .primary {
  border-color: var(--left); background: var(--left); color: var(--on-accent);
  font-weight: 700; box-shadow: 0 0 34px rgba(var(--left-rgb), .35);
}
.start-actions .ghost { border-color: var(--right); color: var(--right); }
.start-actions button:disabled { opacity: .5; cursor: progress; }
.start-note { margin: 22px 0 0; max-width: 52ch; font-size: 11px; line-height: 1.8; color: var(--dim-2); }
.start-note b { color: var(--ink-2); font-weight: 500; }

.hero-side { display: flex; flex-direction: column; gap: 18px; min-width: 0; }
/* A drawing of the instrument view, not a live feed: you should be able to
   see what you are agreeing to before you grant a camera.
   `margin: 0` because this is a <figure>, and the UA sheet's 40px side margins
   quietly pulled it narrower than the fact table directly beneath it. */
.preview-frame {
  position: relative; margin: 0; height: 290px;
  border: 1px solid var(--rule-soft); background: var(--bg-2);
}
.preview-frame svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.preview-frame .lh { stroke: var(--left); fill: none; stroke-width: 1.4; }
.preview-frame .lj { fill: var(--left); }
.preview-frame .rh { stroke: var(--right); fill: none; stroke-width: 1.4; }
.preview-frame .rj { fill: var(--right); }
.preview-tag {
  position: absolute; top: 12px; font-size: 9.5px; letter-spacing: .24em; text-transform: uppercase;
}
.preview-tag.l { left: 14px; color: var(--left); }
.preview-tag.r { right: 14px; color: var(--right); }

.hero-facts {
  display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
  background: var(--rule-soft); border: 1px solid var(--rule-soft); font-size: 11px;
}
.hero-facts span { background: var(--bg); padding: 13px 16px; }
.hero-facts .k { color: var(--dim-2); letter-spacing: .2em; text-transform: uppercase; }
.hero-facts .v { color: var(--ink-2); }
.hero-facts .v.mint { color: var(--left); }
.hero-facts .v.magenta { color: var(--right); }

.hero-strip {
  position: relative; z-index: 1;
  display: flex; flex-wrap: wrap; align-items: center; gap: 14px 34px;
  margin-top: 26px; padding-top: 24px; border-top: 1px solid var(--rule-soft);
  font-size: 11px; letter-spacing: .2em; text-transform: uppercase; color: var(--dim-2);
}
.hero-strip i { font-style: normal; color: var(--left); }
.hero-strip .stack { margin-left: auto; color: var(--right); }
.hero-strip a { color: var(--dim-2); text-decoration: none; }
.hero-strip a:hover { color: var(--left); }

#toast {
  position: absolute; z-index: 22; left: 50%; bottom: 128px; transform: translateX(-50%) translateY(10px);
  padding: 11px 20px; border: 1px solid var(--rule); background: var(--card);
  color: var(--ink); font-size: 12px; letter-spacing: .04em; white-space: nowrap;
  max-width: 92vw; overflow: hidden; text-overflow: ellipsis;
  opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s;
}
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ── SEO content ──────────────────────────────────────────────────────── */

.page { background: var(--bg); padding: 72px 0 0; border-top: 1px solid var(--rule-soft); }
.page-inner { max-width: 840px; margin: 0 auto; padding: 0 22px; }
.page h2 {
  font-family: var(--display); font-variation-settings: 'wdth' 110, 'wght' 800;
  font-size: clamp(30px, 5vw, 52px); line-height: 1.02; letter-spacing: -.025em;
  text-transform: uppercase; color: var(--ink); margin: 0 0 14px; text-wrap: balance;
}
.page h3 {
  font-family: var(--head); font-variation-settings: 'wdth' 105, 'wght' 700;
  font-size: 22px; line-height: 1.25; letter-spacing: -.01em;
  color: var(--ink); margin: 0 0 12px;
}
.page h4 {
  font-family: var(--mono); font-size: 10px; font-weight: 500; letter-spacing: .22em;
  text-transform: uppercase; color: var(--left); margin: 26px 0 8px;
}
.page p, .page li { color: var(--dim); font-family: var(--prose); font-size: 16px; }
/* No rule of its own — the first .card supplies one, and two 40px apart reads
   as a mistake. */
.lede {
  color: var(--ink) !important; font-size: 19px !important; line-height: 1.55;
  margin-bottom: 24px;
}
.card {
  border-top: 1px solid var(--rule-soft);
  padding: 32px 0; margin: 0;
  scroll-margin-top: 72px;
}
.card ol, .card ul { padding-left: 20px; margin: 12px 0; }
.card li { margin-bottom: 6px; }
.card code {
  font-family: var(--mono); background: var(--card); padding: 1px 6px;
  border-radius: 2px; color: var(--accent); font-size: 13px;
}
.card strong { color: var(--ink); font-weight: 500; }
.note {
  border-left: 2px solid var(--right); padding-left: 14px;
  font-size: 14.5px !important; color: var(--dim-2) !important;
}

.page table {
  width: 100%; border-collapse: collapse; font-family: var(--mono); font-size: 12.5px;
  margin: 12px 0 4px; display: block; overflow-x: auto;
}
.page th {
  text-align: left; padding: 9px 12px 9px 0; color: var(--left);
  font-size: 9.5px; font-weight: 500; letter-spacing: .18em; text-transform: uppercase;
  border-bottom: 1px solid var(--rule); white-space: nowrap;
}
.page td {
  padding: 9px 12px 9px 0; color: var(--dim);
  border-bottom: 1px solid var(--rule-soft); white-space: nowrap;
}
.page td:first-child { color: var(--ink); }

details { border-bottom: 1px solid var(--rule-soft); padding: 14px 0; }
details summary {
  cursor: pointer; color: var(--ink); list-style: none;
  font-family: var(--head); font-variation-settings: 'wdth' 100, 'wght' 600; font-size: 17px;
}
details summary::-webkit-details-marker { display: none; }
details summary::before { content: '+ '; color: var(--accent); font-family: var(--mono); font-size: 13px; }
details[open] summary::before { content: '– '; }
details p { margin: 10px 0 0; font-size: 15px; }

.page a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 3px; }
.site-footer {
  border-top: 1px solid var(--rule-soft); margin-top: 34px; padding: 34px 0 48px;
}
.site-footer p { font-size: 12px; color: var(--dim); font-family: var(--mono); line-height: 1.8; }

/* ── responsive ───────────────────────────────────────────────────────── */

@media (max-width: 1100px) {
  /* Below this the fourth column has nowhere to go, so the docked meters
     become a row under the mode cards instead of a column beside them. */
  .dock-panel:has(#metersDock:not(:empty)) { grid-template-columns: minmax(0, 1fr); }
  #metersDock {
    padding-left: 0; padding-top: 16px;
    border-left: 0; border-top: 1px solid var(--rule-soft);
  }
  .meters:not(.floating) .meter-row { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 900px) {
  #startOverlay { padding: 70px 24px 32px; }
  .start-card { grid-template-columns: 1fr; gap: 30px; }
  .hero-side { order: 2; }
  .topnav { gap: 16px; }
  .topnav a { font-size: 9.5px; letter-spacing: .14em; }
  .mode-cards { grid-template-columns: 1fr; }
  .help-grid { grid-template-columns: 1fr; }
}

@media (max-width: 640px) {
  .topbar { height: 52px; padding: 0 14px; gap: 10px; font-size: 9px; letter-spacing: .16em; }
  /* Everything optional leaves the bar. What remains is the live reading, the
     way to the guide and the way out — three things, which is what fits. */
  /* The live claim needs beating on its own terms — `#stage.live .claim-live`
     outranks a bare `.claim` no matter which block it sits in. */
  .topnav, .claim, #modeToggle { display: none; }
  #stage.live .claim-live { display: none; }
  /* Two words, not three: "Stop camera" wrapped to a second line and pushed
     the bar taller than everything beside it. */
  #backBtn { font-size: 0; }
  #backBtn::after { content: '← Stop'; font-size: 9.5px; letter-spacing: .12em; }
  #toolbarToggle { font-size: 0 !important; }
  #toolbarToggle::after { content: '▾'; font-size: 13px; }
  .bar-btn { padding: 7px 12px; font-size: 9.5px; letter-spacing: .12em; }
  .topbar-right { gap: 8px; }
  .mark .mark-long { display: none; }
  .mark .mark-short { display: inline; }
  .back-btn { padding: 7px 12px; font-size: 9.5px; letter-spacing: .12em; }

  /* The dock is the whole control surface on a phone, so it gets the room:
     tighter padding, tabs that scroll sideways, one column inside. */
  .dock-panel { padding: 16px 14px 14px; gap: 14px; }
  /* A 300px rail down the side of a phone covers the hands it is measuring. */
  .meters-host.floating { right: 10px; }
  .meters.floating .meter-track { height: 26vh; }
  .meters.floating .meter-row { gap: 4px; }
  .meters.floating .meter { min-width: 56px; }
  /* Titles only. On a phone the dock is already eating half the screen the
     instrument needs, and the descriptions are in the guide anyway. */
  .mode-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
  .mode-card { padding: 12px 10px; }
  .mode-card span { display: none; }
  .mode-card b { font-size: 10.5px; letter-spacing: .1em; }
  .dock-tabs { padding: 12px 14px 16px; gap: 8px; }
  .dock-tab { padding: 12px 15px; font-size: 10px; letter-spacing: .12em; }
  .dock-tab b { display: none; }
  .dock-spacer { display: none; }
  .dock-tabs button:not(.dock-tab):not(.rec) { padding: 12px 14px; font-size: 10px; letter-spacing: .12em; }
  .rec { padding: 12px 16px; font-size: 10px; letter-spacing: .12em; }
  .panel { max-height: 40vh; }
  .panel-grid { grid-template-columns: 1fr; }
  select, .check { min-height: 38px; font-size: 11px; padding: 8px 12px; }
  select { padding-right: 30px; background-position: calc(100% - 15px) 17px, calc(100% - 10px) 17px; min-width: 104px; }

  #status { bottom: auto; top: 92px; }
  #chordDisplay { text-shadow: 0 0 10px var(--bg), 0 0 26px var(--bg); }

  .help-card { padding: 24px 18px; }
  .modal { padding: 20px 12px; }
  .help-flag { font-size: 11px; }

  #startOverlay { padding: 62px 16px 26px; }
  #startOverlay::before, #startOverlay::after { display: none; }
  .start-actions button { flex: 1 1 100%; }
  .start-note, .hero-sub { max-width: none; }
  .preview-frame { height: 220px; }
  .hero-strip { gap: 10px 18px; font-size: 10px; letter-spacing: .14em; }
  .hero-strip .stack { margin-left: 0; }
  #toast { bottom: 96px; }

  .page { padding-top: 44px; }
  .page-inner { padding: 0 16px; }
  .card { padding: 24px 0; }
  .lede { font-size: 17px !important; margin-bottom: 30px; padding-bottom: 26px; }
}

@media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }
