/* ══════════════════════════════════════════════════════════════════════
   MURMUR — V2 Liquid Glass
   A light, quiet sound space. Glass is a control layer floating over
   content, never the material of the page itself.
   Layer order (fixed):
     background → ambient light → content → floating controls → glass panels
   ══════════════════════════════════════════════════════════════════════ */

/* ── Design tokens ─────────────────────────────────────────────────── */
:root {
  /* Surfaces. The room the sound lives in: paper, not a void — a base
     dark enough to flatten the glass would also flatten everything on
     top of it, and white would give the light nowhere to fall. */
  --bg:         #f1f3f7;
  --bg-2:       #e9ecf2;
  --bg-3:       #e0e4ec;

  /* One accent, used only for active sound state and primary control.
     (Its old companion --accent-dim was a dark-theme artefact: at 5.36:1
     it was indistinguishable from --accent on paper.) */
  --accent:     #276a8c;
  --accent-glow: rgba(39, 106, 140, .14);

  /* Type */
  --ink:    #12161b;
  --dim:    #49525d;
  /* --mute carries the 11px captions straight on the page, where the
     ambient lights raise the background to about rgb(178,197,220) in the
     brightest corner. The old #5e6873 dipped to 3.22:1 there; this holds
     ~4.8:1 even in the wash and ~6.7:1 on flat paper. */
  --mute:   #4d565f;
  /* The layer numbers and the footer mark. At #39424c these measured 1.84:1
     against the background -- not quiet, invisible. A secondary index should
     read as secondary, not as a smudge. */
  --faint:  #7d8791;

  /* Hairlines */
  --line:   rgba(0, 0, 0, .07);
  --line-2: rgba(0, 0, 0, .14);

  /* Glass. Low whiteness on purpose: too much white erases what's behind
     it and the material stops reading as glass at all. */
  --glass-bg:     rgba(255, 255, 255, .55);
  --glass-bg-2:   rgba(255, 255, 255, .74);
  --glass-edge:   rgba(0, 0, 0, .06);
  --glass-blur:   26px;
  /* Overlay tokens. Each of these had been written out literally, which is
     why inverting the theme meant touching twenty separate declarations. */
  --hl:    rgba(255, 255, 255, .85);   /* inset top highlight */
  --sheen: rgba(255, 255, 255, .55);   /* soft reflection across glass */
  --ring:  rgba(0, 0, 0, .16);         /* slider thumb outline */
  /* The "on" border for chips, presets and flags. The old ice-blue read at
     1.09:1 against the off border on a light page — a selected state nobody
     could see. Darker, and a token, so it is set once. */
  --accent-line: rgba(39, 106, 140, .45);
  --sh-1:  rgba(0, 0, 0, .12);         /* resting shadow */
  --sh-2:  rgba(0, 0, 0, .07);         /* wide ambient shadow */
  --bloom: rgba(0, 0, 0, .05);

  /* Motion. Short, soft, continuous. Nothing bounces. */
  --ease:      cubic-bezier(.32, .72, .28, 1);
  --ease-out:  cubic-bezier(.16, .84, .44, 1);
  --t-fast:    .18s;
  --t:         .32s;
  --t-slow:    .55s;

  /* Motion multiplier: 1 normally, 3.2 under prefers-reduced-motion, 1.8
     in SLEEP mode. Everything derived from it scales, nothing switches
     off. (--dim-op was its night-mode twin and died with the theme.) */
  --slow: 1;

  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);

  --dock-h: 62px;
  --r-lg: 24px;
  --r-md: 18px;
  --r-sm: 12px;

  color-scheme: light;
}

/* ── Typefaces ───────────────────────────────────────────────────────
   Both files are variable (Inter wght 100–900, JetBrains Mono 400–800 —
   verified against their fvar tables), so one file each covers the run.
   Latin subsets: CJK falls through to Noto Sans SC / PingFang, and the
   system stacks stay in every list as the offline-of-these-files floor. */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-var.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'JetBrains Mono';
  src: url('/fonts/JetBrainsMono.woff2') format('woff2');
  font-weight: 400 800;
  font-style: normal;
  font-display: swap;
}

/* ── Base ──────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* ── The room ───────────────────────────────────────────────────────
   Four soft lights drifting across the paper. It replaces an ambient
   film built from a screen recording; once the page went light, the
   film's job fell to gradients the page itself generates — cool washes
   at low alpha, bright enough to give the room a corner to sit in,
   never enough to name their colour.

   Each light is a radial-gradient, so the falloff is soft by construction.
   That matters: a large element with filter: blur() would look the same
   and cost far more, and the brief rules out stacking blur layers.

   The animation touches transform only -- no background-position, no
   filter, no opacity on the moving element -- so it stays on the
   compositor and costs nothing per frame. Speeds are deliberately
   unrelated (137s, 173s, 211s, 149s) so the four never resync into a
   visible repeating figure.

   --amb is written once per frame from the audio level, which is what
   makes the room's light track the sound rather than merely sit behind
   it. */
.room {
  position: fixed;
  inset: 0;
  z-index: -3;
  overflow: hidden;
  pointer-events: none;
  opacity: calc(.9 * var(--amb, 1));
  transition: opacity 1.4s var(--ease);
}
.room i {
  position: absolute;
  display: block;
  border-radius: 50%;
  will-change: transform;
  backface-visibility: hidden;
}
.room i:nth-child(1) {
  width: 92vmax; height: 92vmax; left: -30vmax; top: -34vmax;
  background: radial-gradient(circle at 50% 50%,
    rgba(146, 170, 205, .26) 0%, rgba(160, 180, 212, .13) 36%, transparent 66%);
  animation: roomDriftA calc(137s * var(--slow)) ease-in-out infinite alternate;
}
.room i:nth-child(2) {
  width: 78vmax; height: 78vmax; right: -26vmax; top: -18vmax;
  background: radial-gradient(circle at 50% 50%,
    rgba(134, 176, 196, .23) 0%, rgba(150, 188, 206, .11) 38%, transparent 68%);
  animation: roomDriftB calc(173s * var(--slow)) ease-in-out infinite alternate;
}
.room i:nth-child(3) {
  width: 104vmax; height: 104vmax; left: -18vmax; bottom: -46vmax;
  background: radial-gradient(circle at 50% 50%,
    rgba(158, 158, 200, .20) 0%, rgba(172, 172, 208, .10) 40%, transparent 70%);
  animation: roomDriftC calc(211s * var(--slow)) ease-in-out infinite alternate;
}
.room i:nth-child(4) {
  width: 64vmax; height: 64vmax; right: -14vmax; bottom: -22vmax;
  background: radial-gradient(circle at 50% 50%,
    rgba(184, 162, 200, .17) 0%, rgba(196, 176, 208, .08) 42%, transparent 72%);
  animation: roomDriftD calc(149s * var(--slow)) ease-in-out infinite alternate;
}
@keyframes roomDriftA {
  0%   { transform: translate3d(0, 0, 0) scale(1); }
  50%  { transform: translate3d(7vmax, 5vmax, 0) scale(1.09); }
  100% { transform: translate3d(13vmax, 11vmax, 0) scale(1.03); }
}
@keyframes roomDriftB {
  0%   { transform: translate3d(0, 0, 0) scale(1.04); }
  50%  { transform: translate3d(-9vmax, 7vmax, 0) scale(1); }
  100% { transform: translate3d(-4vmax, 14vmax, 0) scale(1.08); }
}
@keyframes roomDriftC {
  0%   { transform: translate3d(0, 0, 0) scale(1); }
  50%  { transform: translate3d(10vmax, -7vmax, 0) scale(1.06); }
  100% { transform: translate3d(-6vmax, -12vmax, 0) scale(1.01); }
}
@keyframes roomDriftD {
  0%   { transform: translate3d(0, 0, 0) scale(1.02); }
  50%  { transform: translate3d(-6vmax, -8vmax, 0) scale(1.1); }
  100% { transform: translate3d(6vmax, -3vmax, 0) scale(1); }
}

html {
  background: var(--bg);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
}

body {
  font-family: "Inter", system-ui, -apple-system, "SF Pro Text", "Noto Sans SC", sans-serif;
  /* No background here, deliberately.

     An opaque background on body paints in the in-flow block phase, which
     is ABOVE every negative z-index layer -- so it covered the ambient
     gradients (z -3), the grain (z -2) and the orb canvas (z -1) all at
     once, and the page rendered as a single flat rectangle of --bg.
     The base colour belongs on html, which paints below all of them. */
  color: var(--ink);
  min-height: 100vh;
  min-height: 100svh;
  line-height: 1.6;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  letter-spacing: .02em;
}

/* The room. Layer 1: base. Layer 2: ambient light. Layer 3: grain. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -3;
  pointer-events: none;
  /* Three lights, all cool, none bright enough to name. The top one is the
     room's ceiling; the two low ones give the floor a direction to fall
     toward, which is what stops a page of flat paper reading as a
     rectangle instead of a room. */
  /* Alphas are cut relative to the first light theme: in the brightest
     corner the ambient wash pushed 10–11px --mute text under 4.5:1. The
     lights stay; they just stop raising the floor under the small type. */
  background:
    radial-gradient(120% 85% at 50% -14%, rgba(104, 150, 190, .26), transparent 62%),
    radial-gradient(90% 70% at 10% 108%, rgba(74, 108, 148, .18), transparent 66%),
    radial-gradient(70% 60% at 92% 96%, rgba(88, 118, 158, .13), transparent 68%);
  /* No base colour here. It used to end with var(--bg), which is opaque
     and therefore covered every ambient layer stacked below it. The base
     colour lives on html, which paints under every negative z-index layer. */
  transition: opacity 1.2s var(--ease);
}

/* Grain: doubles as dither. Without it the large soft gradients band. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  /* .42 was tuned for a near-black page, where soft-light grain reads as
     texture; on paper the same weight lays a visible grey film over the
     light. A fifth of the effect keeps the dither and loses the film. */
  opacity: .20;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.84' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E");
  /* soft-light, not overlay: overlay doubles the base, and on a light
     page its blend over a white ground goes almost invisible at the edges
     where the dither is actually wanted. */
  mix-blend-mode: soft-light;
}

canvas#bgCanvas {
  position: fixed;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  pointer-events: none;
  opacity: .9;
  transition: opacity 1.4s var(--ease);
}

::selection { background: var(--accent); color: #fff; }

a { color: var(--accent); text-decoration: none; }

h1, h2, h3 { font-weight: 500; letter-spacing: .02em; }

button, input { font: inherit; color: inherit; }

/* Present to assistive tech, absent from the page. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Focus: visible, but only for the keyboard. */
:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: 3px;
}
:focus:not(:focus-visible) { outline: none; }

.mono {
  font-family: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
  font-variant-numeric: tabular-nums;
}

/* ══ GLASS MATERIAL ══════════════════════════════════════════════════
   The single definition of glass in this project. Everything glassy
   derives from it so the material stays consistent. Three things make
   it read as a material rather than a grey box:
     1. a faint inner highlight along the top edge (light from above)
     2. a hairline border at 10% — visible, never a line you notice
     3. a large, low shadow so it sits above the content plane
   ══════════════════════════════════════════════════════════════════ */
.glass {
  position: relative;
  background: var(--glass-bg);
  border: 1px solid var(--glass-edge);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.5);
  backdrop-filter: blur(var(--glass-blur)) saturate(1.5);
  box-shadow:
    inset 0 1px 0 var(--hl),
    inset 0 -1px 0 rgba(0, 0, 0, .05),
    0 18px 42px -16px rgba(18, 32, 48, .22);
  transition:
    background var(--t) var(--ease),
    border-color var(--t) var(--ease),
    box-shadow var(--t) var(--ease),
    transform var(--t-fast) var(--ease);
}

/* Inner material variation: gives glass its uneven density. */
.glass::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background:
    radial-gradient(120% 90% at 10% -12%, rgba(255, 255, 255, .34), transparent 58%),
    radial-gradient(80% 60% at 22% 0%, rgba(255, 255, 255, .06), transparent 70%),
    radial-gradient(60% 50% at 100% 100%, rgba(39, 106, 140, .05), transparent 70%);
}

/* Content sits above the material layer, never inside ::after. */
.glass > * { position: relative; z-index: 1; }

/* ══ LAYOUT ═════════════════════════════════════════════════════════ */
.inst {
  width: 100%;
  max-width: 1080px;
  margin: 0 auto;
  padding: calc(var(--safe-t) + 34px) calc(var(--safe-r) + 24px)
           calc(var(--safe-b) + 142px) calc(var(--safe-l) + 24px);
}

.sec { margin: clamp(52px, 7vw, 92px) 0; }

.sec-title {
  font-size: 11px;
  letter-spacing: .36em;
  text-transform: uppercase;
  color: var(--mute);
  font-weight: 500;
  margin-bottom: clamp(18px, 2.6vw, 30px);
}

/* ══ TOP BAR ════════════════════════════════════════════════════════ */
.bar {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 46px;
}

.mark {
  font-size: 13px;
  letter-spacing: .36em;
  color: var(--ink);
  font-weight: 500;
  margin-right: auto;
}

.modes { display: flex; gap: 6px; }

.modes button {
  background: transparent;
  border: 1px solid transparent;
  color: var(--mute);
  font-size: 11px;
  letter-spacing: .2em;
  white-space: nowrap;
  flex-shrink: 0;
  padding: 11px 13px;           /* ≥44px touch target with the min-height */
  min-height: 46px;
  border-radius: 13px;
  cursor: pointer;
  transition: color var(--t) var(--ease), background var(--t) var(--ease),
              border-color var(--t) var(--ease);
}
.modes button:hover { color: var(--dim); border-color: var(--glass-edge); }
.modes button.on {
  color: var(--ink);
  background: var(--glass-bg-2);
  border-color: var(--glass-edge);
  box-shadow: inset 0 1px 0 var(--hl), 0 8px 20px -12px rgba(18, 32, 48, .28);
}

.lang {
  background: transparent;
  border: 1px solid var(--glass-edge);
  color: var(--dim);
  font-size: 11px;
  letter-spacing: .12em;
  padding: 11px 14px;
  min-height: 44px;
  border-radius: 13px;
  cursor: pointer;
  flex-shrink: 0;
  transition: color var(--t) var(--ease), border-color var(--t) var(--ease);
}
.lang:hover { color: var(--ink); border-color: var(--line-2); }

/* ══ HERO ═══════════════════════════════════════════════════════════
   Sound, space, light. No navigation, no explanation. The instrument
   is the centre; the type is deliberately secondary to it.
   ══════════════════════════════════════════════════════════════════ */
.hero {
  display: grid;
  justify-items: center;
  text-align: center;
  padding: clamp(34px, 7vh, 78px) 0 clamp(10px, 3vh, 30px);
}

/* The transport. One large glass control, not a row of buttons. */
.field {
  position: relative;
  width: min(56vmin, 300px);
  aspect-ratio: 1;
  border-radius: 50%;
  cursor: pointer;
  display: grid;
  place-items: center;
  background: rgba(255, 255, 255, .52);
  border: 1px solid rgba(255, 255, 255, .62);
  -webkit-backdrop-filter: blur(24px) saturate(1.7);
  backdrop-filter: blur(24px) saturate(1.7);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .95),
    inset 0 -22px 44px -28px rgba(39, 106, 140, .22),
    0 30px 70px -26px rgba(18, 32, 48, .30),
    0 0 0 1px rgba(0, 0, 0, .04);
  transition: transform .5s var(--ease), box-shadow .5s var(--ease),
              border-color .5s var(--ease);
  -webkit-user-select: none;
  user-select: none;
}

.field:hover { border-color: var(--line-2); }
.field:active { transform: scale(.985); }

/* Halo: the room's light responding to the instrument. */
.field::before {
  content: '';
  position: absolute;
  inset: -34%;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(circle, var(--accent-glow), transparent 62%);
  opacity: .35;
  transition: opacity 1.5s var(--ease), transform 1.5s var(--ease);
}
.field[aria-pressed="true"]::before { opacity: 1; transform: scale(1.06); }

.field canvas {
  position: relative;
  z-index: 1;
  width: 100%; height: 100%;
  border-radius: 50%;
  display: block;
}

/* ── The dial ────────────────────────────────────────────────────────
   A hairline circle with one accent arc that goes round once a minute.
   The track stays visible so the ring reads as an instrument face even
   when nothing is playing and the arc is empty. */
.field-dial {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  pointer-events: none;
  /* put twelve o'clock at the start of the sweep */
  transform: rotate(-90deg);
}
.dial-track {
  fill: none;
  stroke: var(--line);
  stroke-width: 1;
  opacity: .9;
}
.dial-arc {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.6;
  stroke-linecap: round;
  /* circumference of r=95 in a 200 viewBox: 2 * pi * 95 */
  stroke-dasharray: 596.9;
  stroke-dashoffset: 596.9;
  opacity: .85;
  /* the offset is written five times a second; the transition is what turns
     that into one continuous glide instead of a tick */
  transition: stroke-dashoffset .22s linear;
}

/* ── Breathing ───────────────────────────────────────────────────────
   Two layers: a halo swelling behind the glass, and a wash of light
   across the glass itself. Transform and opacity only, ~5.6s a cycle.
   Anything faster or brighter stops being a place to rest and starts
   being a display. At rest it drops to a third: a sleeping instrument
   does not pulse like a running one. */
.field { --breath: .34; }
.inst.playing .field { --breath: 1; }

.breath {
  position: absolute;
  inset: -17%;
  z-index: 0;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(circle at 50% 50%,
    var(--accent-glow) 0%, rgba(39, 106, 140, .05) 46%, transparent 66%);
  will-change: transform, opacity;
  animation: breathPulse calc(5.6s * var(--slow)) cubic-bezier(.45, 0, .55, 1) infinite;
}
@keyframes breathPulse {
  0%, 100% { opacity: calc(.20 * var(--breath)); transform: scale(.972); }
  50%      { opacity: calc(.62 * var(--breath)); transform: scale(1.038); }
}

/* The glass draws a little more light on the same cycle, offset slightly so
   the two layers never peak together — a breath, not a metronome. */
.field::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 3;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(circle at 50% 38%,
    rgba(255, 255, 255, .09) 0%, transparent 62%);
  animation: fieldWash calc(5.6s * var(--slow)) cubic-bezier(.45, 0, .55, 1) infinite;
  animation-delay: calc(-1.9s * var(--slow));
}
@keyframes fieldWash {
  0%, 100% { opacity: calc(.25 * var(--breath)); }
  50%      { opacity: calc(.85 * var(--breath)); }
}

.hero-title {
  font-size: clamp(46px, 13vw, 104px);
  letter-spacing: .36em;
  margin: clamp(30px, 6vh, 54px) 0 0;
  padding-left: .3em;             /* optical: balance the trailing tracking */
  color: var(--ink);
  font-weight: 300;
  line-height: 1;
}

.hero-sub {
  margin-top: 20px;
  font-size: clamp(11px, 2.4vw, 13px);
  line-height: 1.9;
  letter-spacing: .12em;
  color: var(--dim);
  max-width: 30ch;
}

.hero-tag {
  margin-top: 18px;
  font-size: 11px;
  letter-spacing: .24em;
  color: var(--dim);
}

/* ══ CURRENT SOUND ══════════════════════════════════════════════════ */
.current {
  font-size: clamp(19px, 4.6vw, 30px);
  letter-spacing: .02em;
  color: var(--ink);
  font-weight: 300;
  line-height: 1.4;
}
.current-empty { font-size: 13px; letter-spacing: .2em; color: var(--mute); }

/* ══ LAYERS — a sound space, not a settings list ════════════════════ */
.layers {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 10px;
}

.layer {
  position: relative;
  display: grid;
  grid-template-columns: auto auto 1fr auto;
  align-items: center;
  /* Explicitly no row gap: the fader is a full-width second grid row and it
     collapses to zero height, but a row gap would still reserve a strip of
     empty space under every single row. Spacing for the open state comes
     from the fader's own margin-top instead. */
  column-gap: 12px;
  row-gap: 0;
  padding: 8px 15px;
  border-radius: var(--r-md);
  background: var(--glass-bg);
  border: 1px solid var(--glass-edge);
  -webkit-backdrop-filter: blur(18px) saturate(1.6);
  backdrop-filter: blur(18px) saturate(1.6);
  box-shadow: inset 0 1px 0 var(--hl), 0 10px 26px -20px rgba(18, 32, 48, .20);
  cursor: pointer;
  transition: background var(--t) var(--ease), border-color var(--t) var(--ease),
              box-shadow var(--t) var(--ease);
  min-height: 60px;
}

.layer:hover { background: var(--glass-bg-2); border-color: var(--line-2); }

/* The dot is the state. Hollow = off, solid = on. Nobody has to be taught. */
.layer .led {
  width: 12px; height: 12px;
  border-radius: 50%;
  border: 1.5px solid var(--faint);
  background: transparent;
  transition: background var(--t) var(--ease), border-color var(--t) var(--ease),
              box-shadow var(--t) var(--ease);
  flex: 0 0 auto;
}

/* The pictogram. Muted while the layer is off, accent while it is on, so the
   drawing participates in the state without a second indicator saying the
   same thing. */
.layer .lico {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  color: var(--mute);
  opacity: .7;
  transition: color var(--t) var(--ease), opacity var(--t) var(--ease);
}
.layer:hover .lico { opacity: 1; }
.layer.on .lico { color: var(--accent); opacity: 1; }

.layer .lname {
  font-size: 12px;
  letter-spacing: .2em;
  color: var(--dim);
  transition: color var(--t) var(--ease);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  min-width: 0;
  /* The row is clickable too, but the button itself must still clear 44px:
     the brief asks for a real touch target, not for one that happens to sit
     inside a bigger one. Padding above was reduced to keep the row the same
     height, so the visible density is unchanged. */
  min-height: 44px;
  text-align: left;
  background: transparent;
  border: 0;
  cursor: pointer;
}
/* The English label gets the ellipsis: a two-column phone row leaves it
   ~100px, and "THUNDERSTORM" wrapping made every other row ragged. */
.layer .lname .len {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.layer .lname small {
  display: block;
  font-size: 11px;
  letter-spacing: .02em;
  color: var(--mute);
  /* inherits the body stack — it once named system-ui itself, which read
     differently from every other line of Chinese on the page */
  margin-top: 1px;
}

.layer .lnum {
  font-size: 11px;
  letter-spacing: .2em;
  /* Was --faint, which measured 3.12:1 -- and small text needs more
     contrast than large text, not less. The index is secondary, so it sits
     on --mute rather than dropping to the border colour. */
  color: var(--mute);
}

/* Off state reads dimmed; on state lights, but never shouts. */
.layer.on {
  background: var(--glass-bg-2);
  border-color: rgba(39, 106, 140, .24);
  box-shadow:
    inset 0 1px 0 var(--hl),
    0 0 0 1px rgba(39, 106, 140, .12),
    0 10px 30px -14px rgba(39, 106, 140, .24);
}
.layer.on .led {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 12px var(--accent-glow);
}
.layer.on .lname { color: var(--ink); }
.layer.on .lname small { color: var(--dim); }

/* Fader: revealed on the active row and on hover. */
.layer .lvol {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto auto;
  align-items: center;
  gap: 10px;
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: max-height var(--t-slow) var(--ease), opacity var(--t) var(--ease),
              margin-top var(--t) var(--ease);
}
.layer.on .lvol {
  max-height: 46px;
  opacity: 1;
  margin-top: 10px;
}
/* Hover does not expand the row. A fader that appears on hover changes the
   height of everything below it, and the brief is explicit that hover should
   change the light, not the layout. Off rows keep their fader reachable by
   engaging the layer, which is the state that fader belongs to. */

.flag, .ghost {
  background: transparent;
  border: 1px solid var(--glass-edge);
  color: var(--dim);
  border-radius: 13px;
  cursor: pointer;
  transition: color var(--t) var(--ease), background var(--t) var(--ease),
              border-color var(--t) var(--ease);
  min-height: 46px;
  min-width: 46px;
  display: inline-grid;
  place-items: center;
  font-size: 11px;
  letter-spacing: .12em;
}
.flag:hover, .ghost:hover { color: var(--ink); border-color: var(--line-2); }
.flag[aria-pressed="true"] {
  color: var(--accent);
  border-color: var(--accent-line);
  background: var(--accent-glow);
}
.ghost {
  padding: 0 18px;
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(14px) saturate(1.5);
  backdrop-filter: blur(14px) saturate(1.5);
}

/* Layer readouts and its pair of flags. Mute and solo moved here when the
   mixer was removed: they belong to the layer they act on, and nowhere else
   needs a copy of them. */
.layer .lval {
  font-size: 11px;
  color: var(--mute);
  min-width: 34px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.layer .lflag {
  min-width: 46px;
  min-height: 46px;
  font-size: 11px;
}
/* A soloed layer silences the rest, so they visibly step back. */
/* .42 read the dimmed rows' names at 1.9:1 against their panel — a hint,
   not a state. Enough to step back, enough to still read. */
.layer.solo-off { opacity: .62; }

/* ══ RANGE — glass track ════════════════════════════════════════════ */
input[type=range] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  /* A range input carries an intrinsic minimum width; inside a grid track it
     will refuse to shrink and squeeze its neighbours instead. */
  min-width: 0;
  height: 46px;              /* full-height grab area, thin visible track */
  background: transparent;
  cursor: pointer;
  display: block;
  /* a drag started on the fader is a scrub, not a page scroll */
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
}
/* Both --p and --fill are honoured: older code sets one, newer sets the other,
   and a mismatch just means a slider that fills to zero. */
input[type=range]::-webkit-slider-runnable-track {
  height: 3px;
  border-radius: 2px;
  background: linear-gradient(90deg,
    var(--accent) var(--fill, var(--p, 0%)),
    var(--line)   var(--fill, var(--p, 0%)));
}
input[type=range]::-moz-range-track {
  height: 3px; border-radius: 2px;
  background: linear-gradient(90deg,
    var(--accent) var(--fill, var(--p, 0%)),
    var(--line)   var(--fill, var(--p, 0%)));
}
input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 15px; height: 15px;
  margin-top: -6px;
  border-radius: 50%;
  background: var(--ink);
  border: none;
  box-shadow: 0 2px 10px var(--sh-1), 0 0 0 1px var(--ring);
  transition: transform var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
input[type=range]::-moz-range-thumb {
  width: 15px; height: 15px; border-radius: 50%;
  background: var(--ink); border: none;
  box-shadow: 0 2px 10px var(--sh-1), 0 0 0 1px var(--ring);
}
input[type=range]:hover::-webkit-slider-thumb { transform: scale(1.14); }
input[type=range]:active::-webkit-slider-thumb {
  transform: scale(1.24);
  box-shadow: 0 2px 14px var(--sh-1), 0 0 16px var(--accent-glow);
}

/* ══ TIMER ══════════════════════════════════════════════════════════ */
.timer-chips { display: flex; flex-wrap: wrap; gap: 8px; }

.tc {
  background: var(--glass-bg);
  border: 1px solid var(--glass-edge);
  color: var(--dim);
  font-size: 11px;
  letter-spacing: .12em;
  padding: 12px 16px;
  min-height: 46px;
  border-radius: 13px;
  cursor: pointer;
  transition: color var(--t) var(--ease), background var(--t) var(--ease),
              border-color var(--t) var(--ease);
}
.tc:hover { color: var(--ink); background: var(--glass-bg-2); }
.sheet .tc { flex: 0 0 auto; }
.tc.on { color: var(--ink); border-color: var(--accent-line); background: var(--glass-bg-2); box-shadow: inset 0 1px 0 var(--hl); }

.tcustom {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  letter-spacing: .12em;
  color: var(--mute);
  padding-left: 6px;
}
.tcustom input {
  width: 66px;
  min-height: 44px;
  background: var(--glass-bg);
  border: 1px solid var(--glass-edge);
  border-radius: var(--r-sm);
  color: var(--ink);
  padding: 0 10px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.timer-fade { margin-top: 16px; font-size: 11px; letter-spacing: .2em; color: var(--mute); }

/* ══ ENGINE TABLE ═══════════════════════════════════════════════════ */
.engine {
  list-style: none;
  display: grid;
  gap: 0;
  padding: 2px 20px;
  border-radius: var(--r-md);
  background: var(--glass-bg);
  border: 1px solid var(--glass-edge);
  -webkit-backdrop-filter: blur(18px) saturate(1.6);
  backdrop-filter: blur(18px) saturate(1.6);
  box-shadow: inset 0 1px 0 var(--hl), 0 12px 30px -22px rgba(18, 32, 48, .20);
}
.engine li {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 16px;
  align-items: baseline;
  padding: 13px 2px;
  border-bottom: 1px solid var(--line);
  font-size: 12px;
}
.engine li:last-child { border-bottom: none; }
.engine .ek { color: var(--mute); letter-spacing: .2em; text-transform: uppercase; font-size: 11px; }
.engine .ev { color: var(--dim); text-align: right; }

.presets {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}

.pre {
  display: grid;
  align-content: center;
  gap: 3px;
  text-align: left;
  background: var(--glass-bg);
  border: 1px solid var(--glass-edge);
  color: var(--dim);
  padding: 13px 16px;
  min-height: 64px;
  border-radius: var(--r-md);
  cursor: pointer;
  -webkit-backdrop-filter: blur(18px) saturate(1.6);
  backdrop-filter: blur(18px) saturate(1.6);
  box-shadow: inset 0 1px 0 var(--hl), 0 10px 26px -20px rgba(18, 32, 48, .20);
  transition: color var(--t) var(--ease), background var(--t) var(--ease),
              border-color var(--t) var(--ease), box-shadow var(--t) var(--ease);
}
.pre .p-name {
  font-size: 13px;
  letter-spacing: .06em;
  color: var(--dim);
  transition: color var(--t) var(--ease);
}
.pre .p-cap {
  font-size: 9.5px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--mute);
  opacity: .8;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: color var(--t) var(--ease), opacity var(--t) var(--ease);
}
.pre:hover { background: var(--glass-bg-2); border-color: var(--line-2); }
.pre:hover .p-name { color: var(--ink); }
.pre.on {
  background: var(--glass-bg-2);
  border-color: var(--accent-line);
  box-shadow:
    inset 0 1px 0 var(--hl),
    0 0 0 1px rgba(39, 106, 140, .12),
    0 12px 30px -14px var(--accent-glow);
}
.pre.on .p-name { color: var(--ink); }
.pre.on .p-cap { color: var(--accent); opacity: 1; }

.mine {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 18px;
}
.mine-lab { font-size: 11px; letter-spacing: .2em; color: var(--mute); }
.mine-list { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; }
.mine-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  letter-spacing: .12em;
  color: var(--dim);
  padding: 10px 14px;
  min-height: 44px;
  border-radius: 13px;
  border: 1px solid var(--glass-edge);
  cursor: pointer;
  transition: color var(--t) var(--ease), border-color var(--t) var(--ease);
}
.mine-list li:hover { color: var(--ink); border-color: var(--line-2); }
.mine-list li b {
  color: var(--mute);
  font-weight: 400;
  font-size: 13px;
  padding: 0 3px;
}
.saved { font-size: 10px; letter-spacing: .2em; color: var(--accent); }

/* ══ STATEMENT ══════════════════════════════════════════════════════ */
.statement { text-align: center; padding: clamp(40px, 9vh, 88px) 0; }
.st-mark {
  font-size: 11px;
  letter-spacing: .36em;
  color: var(--mute);
  margin-bottom: 22px;
}
.st-lines {
  font-size: clamp(13px, 3vw, 16px);
  line-height: 2.1;
  letter-spacing: .12em;
  color: var(--dim);
  font-weight: 300;
}

/* ══ GUIDE ══════════════════════════════════════════════════════════ */
.guide { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.guide-item {
  display: grid;
  gap: 7px;
  align-content: start;
  padding: 16px 18px;
  border-radius: var(--r-md);
  background: var(--glass-bg);
  border: 1px solid var(--glass-edge);
  -webkit-backdrop-filter: blur(18px) saturate(1.6);
  backdrop-filter: blur(18px) saturate(1.6);
  box-shadow: inset 0 1px 0 var(--hl), 0 10px 26px -20px rgba(18, 32, 48, .18);
}
.guide-key { font-size: 10.5px; letter-spacing: .22em; color: var(--accent); }
.guide-text { font-size: 12.5px; color: var(--dim); line-height: 1.7; }

/* ══ FOOTER ═════════════════════════════════════════════════════════ */
.foot {
  margin-top: clamp(60px, 10vh, 110px);
  padding-top: 26px;
  border-top: 1px solid var(--line);
  display: grid;
  gap: 14px;
  text-align: center;
}
.keys {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px 20px;
  font-size: 11px;
  letter-spacing: .12em;
  color: var(--mute);
}
.keys b { color: var(--dim); font-weight: 500; }
.foot-mark { font-size: 11px; letter-spacing: .36em; color: var(--mute); }

/* ══ FLOATING GLASS DOCK ════════════════════════════════════════════
   A control layer above the content, never a tab bar: these are
   instruments for the sound, not destinations in an app.
   ══════════════════════════════════════════════════════════════════ */
.dock {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--safe-b) + 16px);
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 6px;
  border-radius: 20px;
  background: var(--glass-bg-2);
  border: 1px solid var(--glass-edge);
  -webkit-backdrop-filter: blur(28px) saturate(1.8);
  backdrop-filter: blur(28px) saturate(1.8);
  box-shadow:
    inset 0 1px 0 var(--hl),
    0 22px 50px -18px rgba(18, 32, 48, .30);
  transition: opacity var(--t-slow) var(--ease), transform var(--t-slow) var(--ease);
}

.dock button {
  background: transparent;
  border: none;
  color: var(--dim);
  font-size: 11px;
  letter-spacing: .12em;
  padding: 0 15px;
  height: 46px;
  min-width: 46px;
  border-radius: 13px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  transition: color var(--t) var(--ease), background var(--t) var(--ease);
}
.dock button:hover { color: var(--ink); background: var(--glass-bg); }
.dock button.on { color: var(--accent); background: var(--glass-bg-2); }
.dock .dsep { width: 1px; height: 22px; background: var(--line); margin: 0 3px; }

.dock .dval {
  font-family: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--mute);
}
.dock button.on .dval { color: var(--accent); }

/* ══ GLASS SHEET — floating panels (timer, volume) ══════════════════
   Opens from the dock button, returns to it. Content layer stays
   visible behind it; no stacked glass.
   ══════════════════════════════════════════════════════════════════ */
.sheet-scrim {
  position: fixed;
  inset: 0;
  z-index: 45;
  background: rgba(28, 38, 52, .22);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t) var(--ease);
}
.sheet-scrim.open { opacity: 1; pointer-events: auto; }

.sheet {
  position: fixed;
  left: 50%;
  bottom: calc(var(--safe-b) + 16px);
  transform: translate(-50%, 16px) scale(.985);
  z-index: 46;
  width: min(460px, calc(100vw - 28px));
  border-radius: var(--r-lg);
  padding: 22px;
  opacity: 0;
  /* opacity:0 alone leaves every control inside tabbable and in the
     a11y tree — a dialog you can walk into with Tab but never see.
     The delayed visibility switch keeps the fade-out intact. */
  visibility: hidden;
  pointer-events: none;
  background: rgba(255, 255, 255, .78);
  border: 1px solid var(--glass-edge);
  -webkit-backdrop-filter: blur(32px) saturate(1.8);
  backdrop-filter: blur(32px) saturate(1.8);
  box-shadow:
    inset 0 1px 0 var(--hl),
    0 30px 70px -20px rgba(18, 32, 48, .35);
  transition: opacity var(--t-slow) var(--ease), transform var(--t-slow) var(--ease),
              visibility 0s linear var(--t-slow);
}
.sheet.open { opacity: 1; visibility: visible; transform: translate(-50%, 0) scale(1); pointer-events: auto; transition-delay: 0s, 0s, 0s; }

.sheet h3 {
  font-size: 11px;
  letter-spacing: .36em;
  text-transform: uppercase;
  color: var(--mute);
  margin-bottom: 18px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.sheet .x {
  background: transparent;
  border: 1px solid var(--glass-edge);
  color: var(--dim);
  width: 46px; height: 46px;
  border-radius: 50%;
  cursor: pointer;
  display: grid;
  place-items: center;
  font-size: 13px;
  transition: color var(--t) var(--ease), border-color var(--t) var(--ease);
}
.sheet .x:hover { color: var(--ink); border-color: var(--line-2); }

.sheet .srow {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 16px;
  padding: 12px 0;
}
.sheet .srow + .srow { border-top: 1px solid var(--line); }
.sheet .slab { font-size: 11px; letter-spacing: .2em; color: var(--dim); min-width: 68px; }
.sheet .snote {
  font-size: 10px;
  letter-spacing: .2em;
  color: var(--accent);
  text-align: right;
}
.sheet .sval {
  font-family: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  min-width: 54px;
  text-align: right;
}

/* ══ BOOT / NOPE ════════════════════════════════════════════════════ */
.boot {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  background: var(--bg);
  transition: opacity .42s var(--ease);
}
.boot.out { opacity: 0; pointer-events: none; }
.boot span { font-size: 10px; letter-spacing: .36em; color: var(--mute); }

.nope {
  position: fixed;
  inset: 0;
  z-index: 62;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 16px;
  text-align: center;
  padding: 30px;
  background: rgba(244, 246, 250, .94);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
}
.nope[hidden] { display: none; }
.nope p { font-size: 13px; color: var(--dim); max-width: 34ch; line-height: 1.75; }
.nope .mono { font-size: 11px; letter-spacing: .28em; color: var(--ink); }
.nope .nope-act {
  margin-top: 6px;
  background: var(--glass-bg-2);
  border: 1px solid var(--line-2);
  color: var(--ink);
  font-size: 11px;
  letter-spacing: .2em;
  padding: 14px 30px;
  min-height: 48px;
  border-radius: 13px;
  cursor: pointer;
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  transition: background var(--t) var(--ease), transform var(--t-fast) var(--ease);
}
.nope .nope-act:hover { background: var(--bloom); }
.nope .nope-act:active { transform: scale(.97); }

/* ══ FIRST VISIT ════════════════════════════════════════════════════
   The overlay showWelcome() builds. It lost its stylesheet in the
   Liquid Glass rebuild and rendered as stray text under the footer —
   restored here, retinted for the light room. z 70 sits above nope (62)
   and boot (60): first sight of the product should cover the product. */
.welcome {
  position: fixed; inset: 0; z-index: 70;
  display: flex; align-items: center; justify-content: center;
  background: rgba(241, 243, 247, .88);
  -webkit-backdrop-filter: blur(18px) saturate(1.4);
  backdrop-filter: blur(18px) saturate(1.4);
  cursor: pointer;
  opacity: 0;
  transition: opacity .42s var(--ease);
}
.welcome.in { opacity: 1; }
.welcome.out { opacity: 0; }
.welcome-box {
  text-align: center;
  padding: 42px 48px 44px;
  border-radius: var(--r-lg);
  background: rgba(255, 255, 255, .66);
  border: 1px solid var(--glass-edge);
  -webkit-backdrop-filter: blur(20px) saturate(1.6);
  backdrop-filter: blur(20px) saturate(1.6);
  box-shadow:
    inset 0 1px 0 var(--hl),
    0 30px 70px -26px rgba(18, 32, 48, .32);
}
.welcome-mark {
  font-size: 11px; letter-spacing: .5em;
  color: var(--mute);
  margin: 0 0 20px;
}
.welcome-text {
  font-size: 13px; letter-spacing: .12em;
  color: var(--ink);
  margin: 0;
  animation: welcomePulse calc(2.4s * var(--slow)) ease-in-out infinite;
}
@keyframes welcomePulse {
  0%, 100% { opacity: .55; }
  50%      { opacity: 1; }
}

/* ══════════════════════════════════════════════════════════════════
   Interaction motion

   Everything in this block is one-shot: it plays when a state changes and
   then stops. The only looping animation on the site is the breathing,
   because a control that keeps moving after you have touched it is a
   control you cannot stop watching. Transform and opacity only, and the
   durations are short enough to read as acknowledgement rather than as
   performance.
   ══════════════════════════════════════════════════════════════════ */

@keyframes riseIn {
  from { opacity: 0; transform: translateY(11px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ── Entrances ───────────────────────────────────────────────────────
   The switch lives on <html> because an inline script sets it, so with no
   JavaScript at all nothing is hidden in the first place. */
html.js [data-rv] { opacity: 0; }
html.js [data-rv].in {
  animation: riseIn calc(.68s * var(--slow)) cubic-bezier(.16, .84, .44, 1) both;
}

/* The hero arrives in parts rather than as one block. The block itself must
   stay opaque or the two effects multiply into a muddy double fade. */
html.js #hero[data-rv] { opacity: 1; }
html.js #hero[data-rv]:not(.in) .field,
html.js #hero[data-rv]:not(.in) .hero-title,
html.js #hero[data-rv]:not(.in) .hero-sub,
html.js #hero[data-rv]:not(.in) .hero-tag { opacity: 0; }
html.js #hero[data-rv].in .field {
  animation: riseIn calc(.82s * var(--slow)) cubic-bezier(.16, .84, .44, 1) both;
}
html.js #hero[data-rv].in .hero-title {
  animation: riseIn calc(.82s * var(--slow)) cubic-bezier(.16, .84, .44, 1) calc(100ms * var(--slow)) both;
}
html.js #hero[data-rv].in .hero-sub {
  animation: riseIn calc(.82s * var(--slow)) cubic-bezier(.16, .84, .44, 1) calc(190ms * var(--slow)) both;
}
html.js #hero[data-rv].in .hero-tag {
  animation: riseIn calc(.82s * var(--slow)) cubic-bezier(.16, .84, .44, 1) calc(250ms * var(--slow)) both;
}

/* ── A layer waking up ───────────────────────────────────────────────
   The dot fills and sends out one ring; the pictogram lifts a hair. Both
   fire off the class change, so they cost nothing while idle. */
@keyframes engageRing {
  0%   { opacity: .8; transform: scale(.8); }
  100% { opacity: 0;  transform: scale(2.4); }
}
@keyframes iconLift {
  0%   { opacity: .65; transform: translateY(2px); }
  100% { opacity: 1;   transform: translateY(0); }
}
@keyframes ledSettle {
  0%   { transform: scale(1.4); }
  60%  { transform: scale(.94); }
  100% { transform: scale(1); }
}

.layer .led { position: relative; }
.layer .led::after {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: 50%;
  border: 1.5px solid var(--accent);
  opacity: 0;
  transform: scale(.8);
  pointer-events: none;
}
/* One-shots ride --slow too: reduced motion slows, it does not remove. */
.layer.on .led { animation: ledSettle calc(.58s * var(--slow)) cubic-bezier(.16, .84, .44, 1) 1; }
.layer.on .led::after { animation: engageRing calc(.74s * var(--slow)) cubic-bezier(.16, .84, .44, 1) 1; }
.layer.on .lico { animation: iconLift calc(.5s * var(--slow)) cubic-bezier(.16, .84, .44, 1) 1; }

/* ── The mix readout crossfades ──────────────────────────────────────
   Rebuilding the line swaps its text outright, which reads as a glitch.
   A short fade in on the new text makes it read as a change of state. */
@keyframes swapIn {
  from { opacity: 0; transform: translateY(5px); }
  to   { opacity: 1; transform: translateY(0); }
}
.current.swap { animation: swapIn calc(.38s * var(--slow)) cubic-bezier(.16, .84, .44, 1) 1; }

/* ── The field acknowledges the transport ────────────────────────────
   One bloom on the halo when playback starts, settling onto the same
   values the pressed state already uses so nothing snaps. */
@keyframes wake {
  0%   { opacity: .32; transform: scale(.93); }
  36%  { opacity: 1;   transform: scale(1.11); }
  100% { opacity: 1;   transform: scale(1.06); }
}
.inst.playing .field::before { animation: wake calc(.92s * var(--slow)) cubic-bezier(.16, .84, .44, 1) 1; }

/* ── Dock ────────────────────────────────────────────────────────────
   The readout changes length (OFF to PLAYING) which used to resize the
   whole dock. Fixed width for the state word stops that. */
#dockSound .dval {
  /* PLAYING is the longest word this slot ever holds; reserving its width
     means the whole dock stops resizing every time playback toggles. */
  min-width: 55px;
  text-align: center;
}
/* Same guard for the volume readout: 100% is the longest value. */
#dockVol .dval { min-width: 44px; text-align: center; }

@keyframes dockSettle {
  0%   { transform: scale(1.05); }
  100% { transform: scale(1); }
}
.dock button.on { animation: dockSettle calc(.44s * var(--slow)) cubic-bezier(.16, .84, .44, 1) 1; }

/* A sheet opening gets a touch of lift on its heading. */
.sheet.open h3 { animation: riseIn calc(.46s * var(--slow)) cubic-bezier(.16, .84, .44, 1) 1; }

/* ── Press ───────────────────────────────────────────────────────────
   Every pressable thing depresses slightly. Fast, because this is the one
   piece of motion that must keep up with the finger. */
:where(.pre, .tc, .flag, .ghost, .modes button, .lang, .sheet .x,
       .dock button, .nope-act, .mine-list li):active {
  transform: scale(.955);
  transition-duration: .09s;
}
.layer:active { background: var(--glass-bg-2); }

/* ══ RESPONSIVE ═════════════════════════════════════════════════════ */

/* Tablet and below: dock stays, content gets more room. */
@media (max-width: 860px) {
  .inst { padding-left: calc(var(--safe-l) + 18px); padding-right: calc(var(--safe-r) + 18px); }
  /* 168px here packed four columns into a landscape phone and left the fader
     12px wide. Two or three roomier columns beat four unusable ones. */
  .layers { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 8px; }
  .layer { padding: 12px 13px; min-height: 56px; }
  .guide-item { grid-template-columns: 1fr; gap: 6px; }
}

/* Phones. Fluid type, safe area, and the dock does the heavy lifting. */
@media (max-width: 620px) {
  .inst {
    padding-top: calc(var(--safe-t) + 22px);
    padding-bottom: calc(var(--safe-b) + 128px);
    padding-left: calc(var(--safe-l) + 16px);
    padding-right: calc(var(--safe-r) + 16px);
  }
  .hero { padding-top: 16px; }
  .bar { gap: 8px; }
  .modes { gap: 2px; }
  .modes button { padding: 11px 9px; letter-spacing: .14em; }
  .field { width: min(62vmin, 268px); }
  .hero-title { letter-spacing: .28em; padding-left: .26em; }

  .layers { grid-template-columns: 1fr 1fr; gap: 7px; }
  .presets { gap: 8px; }
  .pre { padding: 12px 13px; min-height: 60px; }
  .pre .p-name { font-size: 12.5px; }
  .guide { grid-template-columns: 1fr; }
  .layer {
    grid-template-columns: auto auto 1fr;
    column-gap: 9px;
    row-gap: 0;
    padding: 5px 12px;
    min-height: 54px;
  }
  .layer .lico { width: 18px; height: 18px; }
  /* The dock's master mute never covered per-layer solo, so hiding the
     flags outright took a whole function off phones. The fader row gives
     up the numeric readout instead: 38px flags, fader keeps the rest. */
  .layer .lvol { grid-template-columns: 1fr auto auto auto; gap: 6px; }
  .layer .lval { display: none; }
  .layer .lflag { min-width: 38px; font-size: 10px; }
  :where(.pre, .tc, .flag, .dock button):active { transform: scale(.97); }
  .layer .lnum { display: none; }
  .layer .lname { font-size: 11px; letter-spacing: .12em; }
  .layer .lname small { font-size: 10px; }
  .layer .lvol { grid-column: 1 / -1; }

  .sec { margin: 44px 0; }
  .sheet { padding: 18px; border-radius: var(--r-lg); }

  .dock { gap: 2px; padding: 5px; }
  .dock button { padding: 0 12px; height: 48px; min-width: 48px; font-size: 10px; }

}

/* Very small phones: nothing may overflow sideways. */
@media (max-width: 380px) {
  .inst { padding-left: calc(var(--safe-l) + 12px); padding-right: calc(var(--safe-r) + 12px); }
  .mark { font-size: 11px; letter-spacing: .22em; }
  .modes button { padding: 11px 7px; }
  .lang { padding: 11px 10px; }
  .layers { grid-template-columns: 1fr; }
  .presets { gap: 6px; }
  .dock button { padding: 0 10px; font-size: 10px; letter-spacing: .12em; }
  .hero-title { letter-spacing: .2em; padding-left: .2em; }
}

/* Short landscape: the field must not push controls off screen. */
@media (max-height: 560px) and (orientation: landscape) {
  .field { width: min(38vmin, 200px); }
  .hero { padding-top: 8px; }
  .hero-title { font-size: clamp(34px, 8vh, 54px); margin-top: 18px; }
}

/* ══ MODES ══════════════════════════════════════════════════════════
   The three mode buttons are not decoration: setMode puts a mode-* class
   on <html> and this is where that class changes the room. SLEEP takes
   away the technical noise — engine telemetry, saved-mix list — and dims
   and slows the light; FOCUS drops the preset shelf and pushes the timer
   forward. display:none also removes what is hidden from the tab order
   and the a11y tree: the mode is not just quieter, it is smaller.
   ══════════════════════════════════════════════════════════════════ */
html.mode-sleep { --slow: 1.8; }
html.mode-sleep #engineSec { display: none; }
html.mode-sleep .mine { display: none; }
html.mode-sleep .room { opacity: calc(.55 * var(--amb, 1)); }

html.mode-focus #presetSec { display: none; }
html.mode-focus #engineSec { display: none; }
html.mode-focus #dockTimer .dval { color: var(--accent); }
html.mode-focus #timerSheet h3 { color: var(--accent); }

/* ══ REDUCED MOTION ═════════════════════════════════════════════════
   Brand policy: motion never disappears on this site — it slows and
   flattens. Nothing here removes the ambience, it just stops moving.
   ══════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  /* Motion is slowed here, never removed. This site's ambience IS the
     product, so a blanket `animation-duration: .01ms` — which is what used
     to be here — deleted the thing the user came for. The breathing keeps
     going at roughly a third speed, the dial keeps sweeping, and only the
     shortest press feedback is flattened. */
  /* html.mode-* (0,1,1) would out-rank :root (0,1,0), so SLEEP's 1.8 has
     to lose here on purpose: list the mode selectors in this same block
     and the later rule wins for everyone. */
  :root, html.mode-sleep, html.mode-focus { --slow: 3.2; }
  /* Entrances slow down with --slow. Press and state feedback stay crisp on
     purpose: they are acknowledgement, not decoration, and a laggy button is
     a broken button. Nothing is switched off. */
  .field:active { transform: none; }
  .dial-arc { transition: none; }
}
