/* ============================================================================
   style.css — ALL styling for the HTML UI.
   Design tokens live in :root — change those first.
   ============================================================================ */

:root {
  /* --------------------------------------------------------------------
     UI SCALE — one factor drives every size below. 1 = the original
     1920x1080 design; the media queries at the bottom of this file dial it
     down on smaller viewports (13" laptops land at ~0.75). Every px value
     that is part of the UI *shape* is written as calc(<base> * --ui-scale),
     so changing the factor rescales the whole interface proportionally.
     -------------------------------------------------------------------- */
  --ui-scale: 1;
  --edge: calc(12px * var(--ui-scale));   /* distance from the viewport edges */
  --gap: 2px;                             /* gap between adjacent controls */
  /* Height of the text fields: their smaller text + their own padding + the
     borders. Fixed, so it no longer depends on each system's font metrics. */
  --field-h: calc(var(--sub-size) * 1.2 + 10px * var(--ui-scale) + 3px);

  /* Core palette — start state: white bg, black UI, white button text */
  --page-bg: #ffffff;
  --ui-bg: #000000;
  --ui-fg: #ffffff;
  --ui-line: #000000;
  --panel-bg: #ffffff;
  --panel-fg: #000000;

  /* Type */
  --ui-font: "Helvetica Neue", Helvetica, Arial, sans-serif;
  --headline-size: calc(15px * var(--ui-scale));   /* section titles + button labels */
  --sub-size: calc(11px * var(--ui-scale));        /* controls inside sections */
  --btn-pad-y: calc(3px * var(--ui-scale));
  --btn-pad-x: calc(8px * var(--ui-scale));

  /* Shape */
  --radius: calc(16px * var(--ui-scale));
  --radius-sm: calc(12px * var(--ui-scale));
  --panel-w: calc(230px * var(--ui-scale));
  --border: 1.5px solid var(--ui-line);

  --toast-bg: #111111;
  --toast-error-bg: #a11111;
  --toast-fg: #ffffff;
  --overlay-bg: rgba(255, 255, 255, 0.92);
}

html, body {
  margin: 0; padding: 0;
  background: var(--page-bg);
  overflow: hidden;
  font-family: var(--ui-font);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
canvas { display: block; }

/* Shared button look */
.ui-btn {
  font-family: var(--ui-font);
  text-transform: uppercase;
  font-size: var(--headline-size);
  padding: var(--btn-pad-y) var(--btn-pad-x);
  color: var(--ui-fg);
  background: var(--ui-bg);
  border: var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  line-height: 1;
  white-space: nowrap;
  transition: opacity 0.15s;
}
.ui-btn:hover { opacity: 0.82; }
.ui-btn:active { transform: translateY(1px); }

/* Layout anchors */
.top-center {
  position: fixed; top: var(--edge); left: 50%; transform: translateX(-50%);
  display: flex; gap: var(--gap); align-items: center; z-index: 5;
}
/* The top-right cluster uses the same flex
   row and gap as the other anchors. */
.top-right {
  position: fixed; top: var(--edge); right: var(--edge); z-index: 5;
  display: flex; gap: var(--gap); align-items: center;
}
.bottom-left {
  position: fixed; left: var(--edge); bottom: var(--edge); z-index: 5;
  display: flex; flex-direction: column; gap: var(--gap);
}
.bottom-center {
  position: fixed; left: 50%; bottom: var(--edge); transform: translateX(-50%); z-index: 5;
  /* PLAY SOUND sets the row height; the round icon buttons stretch to it.
     Their icons are scaled with the UI (see .icon-btn svg), so they can
     never be the tallest thing in the row and push it open. */
  display: flex; gap: var(--gap); align-items: stretch;
}
.bottom-right {
  position: fixed; right: var(--edge); bottom: var(--edge); z-index: 5;
  display: flex; flex-direction: column; gap: var(--gap);
  /* The button row (two equal buttons + 2px gap) is the widest natural child
     and sets the column width. Every other child stretches to 100% of that,
     so the name row and the font-name field end up exactly as wide as the
     button row. Note: "double the EXPORT OTF button + 2px" is, by geometry,
     identical to the full row width — EXPORT OTF is half the row, so
     2*(half) + 2px gap = the whole row. */
  align-items: stretch;
  width: max-content;
}
.btn-row { display: flex; gap: var(--gap); }
/* The two export buttons share the row equally, so each is exactly half the
   row width. This makes the field widths below derivable without guessing
   pixel sizes from the button text. */
.btn-row .corner-btn { flex: 1; min-width: 0; }
.top-btn, .corner-btn { font-size: var(--headline-size); }

/* Apple-style toggle */
/* Toggle sized from the same metrics as .ui-btn.
   All knob geometry uses one variable, --tog-h (the track height), so the
   travel maths stays simple and correct (no nested percentages). */
.switch {
  --tog-h: calc(var(--headline-size) + var(--btn-pad-y) * 2 + 3px * var(--ui-scale));
  --tog-knob: calc(var(--tog-h) - 7px * var(--ui-scale)); /* diameter: track minus margins */
  --tog-gap: calc(3px * var(--ui-scale));  /* equal gap both ends */
  position: relative; display: inline-block;
  height: var(--tog-h);
  width: calc(var(--tog-h) * 2);
  cursor: pointer;
}
.switch input { opacity: 0; width: 0; height: 0; }
.switch-slider {
  position: absolute; inset: 0;
  background: var(--ui-bg);
  border: var(--border);
  border-radius: 999px;
  transition: background 0.2s;
}
.switch-slider::before {
  content: "";
  position: absolute;
  height: var(--tog-knob);
  width: var(--tog-knob);
  left: var(--tog-gap);
  top: 50%;
  transform: translateY(-50%);
  background: var(--page-bg);
  border-radius: 50%;
  transition: transform 0.2s;
}
/* Travel = track width − knob − gap on each side, applied on top of the
   existing translateY so vertical centering is preserved. */
.switch input:checked + .switch-slider::before {
  transform: translateY(-50%)
             translateX(calc(var(--tog-h) * 1.9 - var(--tog-knob) - var(--tog-gap) * 2));
}

/* PLAY SOUND — inverts (swaps fg/bg) when active */
.play-btn { font-size: var(--headline-size); }
/* PLAY SOUND / PAUSE SOUND: same padding as every other button; the two
   labels share one grid cell so the width is the longer label's. */
.play-btn .lbls { display: grid; }
.play-btn .lbl { grid-area: 1 / 1; }
.play-btn .lbl.off { visibility: hidden; }
.play-btn.active { background: var(--ui-fg); color: var(--ui-bg); }

/* Round icon buttons flanking PLAY SOUND (mute on the left, stop on the
   right). The fixed width equals the height so border-radius:50% gives a
   true circle; min-width:0 undoes the .play-btn width. */
.icon-btn {
  min-width: 0;
  width: calc(var(--headline-size) + var(--btn-pad-y) * 2 + 3px * var(--ui-scale));
  padding: 0;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--sub-size);   /* the ◼ glyph is oversized by default */
  line-height: 1;
}
/* The global .hidden only fades elements out; a hidden transport button must
   not reserve space in the flex row, so remove it from layout entirely. */
.icon-btn.hidden { display: none; }
/* One fixed height for the whole PLAY row (label + padding + both 1.5px
   borders), and the round buttons exactly as wide as that, so they are true
   circles. Left to themselves the round buttons took their height from the
   row and their width from a separate formula, and the two drifted apart on
   Retina screens, where the 1.5px borders are not rounded down. */
.bottom-center { --row-h: calc(var(--headline-size) + var(--btn-pad-y) * 2 + 3px); }
.bottom-center .ui-btn { height: var(--row-h); box-sizing: border-box; }
.bottom-center .icon-btn { width: var(--row-h); flex: none; }/* The speaker icon scales with the UI. At a fixed 15px it was taller than a
   button on small screens and stretched the whole PLAY row with it. */
.icon-btn svg { display: block; width: calc(15px * var(--ui-scale)); height: calc(15px * var(--ui-scale)); }

/* Upload buttons */
.upload-wrap { display: flex; align-items: stretch; gap: var(--gap); }
.upload-btn {
  font-size: var(--headline-size);
  max-width: calc(220px * var(--ui-scale)); overflow: hidden; text-overflow: ellipsis;
}
.reset-x { font-size: var(--headline-size); padding: var(--btn-pad-y) var(--btn-pad-x); border-radius: var(--radius); }
.reset-x.hidden { display: none; }

/* Left accordion */
.left-panel {
  position: fixed; left: var(--edge); top: var(--edge); z-index: 4;
  width: var(--panel-w);
  /* Never let the accordion run past the bottom of the viewport: on short
     screens the open sections scroll inside the panel instead. */
  max-height: calc(100vh - var(--edge) * 2);
  overflow-y: auto; overflow-x: hidden;
  scrollbar-width: none;
  display: flex; flex-direction: column; gap: var(--gap);
}
.left-panel::-webkit-scrollbar { width: 0; height: 0; }
.acc-section {
  background: var(--ui-bg);
  flex: 0 0 auto;
  border: none;
  border-radius: var(--radius);
  overflow: hidden;
}
.acc-header {
  width: 100%;
  display: flex; align-items: center; gap: calc(8px * var(--ui-scale));
  box-sizing: border-box;
  padding: var(--btn-pad-y) var(--btn-pad-x);  /* same as .ui-btn → same height */
  background: var(--ui-bg);
  color: var(--ui-fg);
  border: none; cursor: pointer;
  font-family: var(--ui-font);
  text-transform: uppercase;
  font-size: var(--headline-size);
  line-height: 1;
}
.acc-tri { font-size: calc(13px * var(--ui-scale)); width: calc(16px * var(--ui-scale)); display: inline-block; line-height: 1; transform: translateY(-0.5px); }
.acc-title { font-size: var(--headline-size); }
.acc-body {
  display: none;
  /* top | right | bottom | left — right gets +2px so sliders don't touch the edge */
  padding: calc(16px * var(--ui-scale)) calc(var(--btn-pad-x) + 2px)
           calc(10px * var(--ui-scale)) var(--btn-pad-x);
  flex-direction: column; gap: calc(10px * var(--ui-scale));
  background: var(--ui-bg);
  color: var(--ui-fg);
}
.acc-section.open .acc-body { display: flex; }

/* Sub-controls (inside the filled sections → use on-dark colors) */
.sub-btn {
  font-size: var(--sub-size); align-self: flex-start;
  padding: var(--btn-pad-y) var(--btn-pad-x); border-radius: var(--radius-sm);
  background: var(--ui-fg); color: var(--ui-bg);
  border: 1.5px solid var(--ui-fg);
}
/* FREQ band buttons sit in a row and share the width */
.btn-triple { display: flex; gap: var(--gap); }
.btn-triple .freq-btn { flex: 1; align-self: stretch; text-align: center; }
/* Section action bar (ALL RANDOM / RESET) — two buttons sharing the width */
.btn-pair { display: flex; gap: var(--gap); margin-bottom: calc(4px * var(--ui-scale)); }
.btn-pair .pair-btn { flex: 1; align-self: stretch; text-align: center; }
/* active band = inverted (bg swap) */
.freq-btn.active { background: var(--ui-bg); color: var(--ui-fg); }
.ctl-row { display: flex; flex-direction: column; gap: calc(3px * var(--ui-scale)); }
/* A control that currently has no effect because another one gates it (TWIST
   with DRIFT at 0). Dimmed but still usable on purpose — it can be set up
   before the control that activates it. */
.ctl-inactive { opacity: 0.38; }
.ctl-labelrow { display: flex; justify-content: space-between; align-items: baseline; }
.ctl-label { font-size: var(--sub-size); color: var(--ui-fg); }
.ctl-value { font-size: var(--sub-size); color: var(--ui-fg); }

.ctl-text {
  font-family: var(--ui-font);
  text-transform: none;   /* typed text keeps upper/lowercase as entered */
  font-size: var(--sub-size);
  padding: calc(5px * var(--ui-scale)) var(--btn-pad-x);
  color: var(--ui-bg);
  background: var(--ui-fg);
  border: none;
  border-radius: var(--radius-sm);
  outline: none; box-sizing: border-box; width: 100%;
  /* Fixed line height and field height, and no native field styling: left to
     the browser, a text input sizes itself from the font's own line metrics,
     which differ between systems and browsers (Safari on a Mac draws these
     noticeably taller than Chrome). */
  -webkit-appearance: none; appearance: none;
  line-height: 1.2;
  height: var(--field-h);
  margin: 0;
}
/* Placeholders are UI text → keep them uppercase */
.ctl-text::placeholder { color: var(--ui-bg); text-transform: uppercase; }

/* Outline variant (TEXT INPUT field): transparent fill with a border instead
   of the solid look, and a lowercase placeholder. Sits on the dark section
   background, so text + border use the on-dark color (--ui-fg). */
.ctl-text-outline {
  color: var(--ui-fg);
  background: transparent;
  border: 1.5px solid var(--ui-fg);
}
.ctl-text-outline::placeholder {
  color: var(--ui-fg);
  text-transform: none;   /* keep the placeholder as written ("Type your text") */
  opacity: 0.7;
}

/* Range sliders (on the dark section background) */
.ctl-slider {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: calc(3px * var(--ui-scale));
  background: var(--ui-fg); border-radius: 2px; outline: none;
}
.ctl-slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: calc(14px * var(--ui-scale)); height: calc(14px * var(--ui-scale));
  border-radius: 50%;
  background: var(--ui-fg); cursor: pointer;
}
.ctl-slider::-moz-range-thumb {
  width: calc(14px * var(--ui-scale)); height: calc(14px * var(--ui-scale));
  border: none; border-radius: 50%;
  background: var(--ui-fg); cursor: pointer;
}

/* Font-name field (bottom-right) — same width as the two buttons together,
   and styled for the page background rather than a filled panel. */
#fontNameInput {
  width: 100%;
  min-width: 0;   /* don't let a long filename widen the whole column */
  box-sizing: border-box;
  color: var(--panel-fg);
  background: var(--page-bg);
  border: var(--border);
  text-transform: none;   /* filename keeps upper/lowercase as typed */
}
#fontNameInput::placeholder { color: var(--panel-fg); text-transform: none; }

/* First/last name row (above the font-name field) — two equal-width fields
   that together match the font-name field's width. Same page-background look
   as #fontNameInput rather than the filled panel style. */
.name-row {
  display: flex;
  gap: var(--gap);
  width: 100%;
}
.name-field {
  flex: 1;                /* equal share of the row */
  min-width: 0;           /* let them shrink evenly instead of overflowing */
  width: auto;
  color: var(--panel-fg);
  background: var(--page-bg);
  border: var(--border);
  text-transform: none;   /* names keep upper/lowercase as typed */
}
.name-field::placeholder { color: var(--panel-fg); text-transform: none; opacity: 0.7; }
/* Required-but-empty highlight, shown when an export is attempted. */
.name-field.field-invalid {
  border-color: var(--toast-error-bg);
  border-width: 2px;
}

/* About panel — filled like the buttons */
.about-panel {
  position: fixed;
  top: calc(var(--edge) + var(--headline-size) + var(--btn-pad-y) * 2 + 12px);
  right: var(--edge); z-index: 8;
  width: calc(320px * var(--ui-scale)); max-height: 70vh; overflow: auto;
  background: var(--ui-bg); color: var(--ui-fg);
  border: none; border-radius: var(--radius);
  padding: calc(14px * var(--ui-scale)) calc(16px * var(--ui-scale));
}
.about-panel.hidden { display: none; }
.about-head { display: flex; justify-content: space-between; align-items: center; font-size: var(--headline-size); margin-bottom: calc(8px * var(--ui-scale)); }
.about-close {
  font-size: var(--sub-size); padding: var(--btn-pad-y) var(--btn-pad-x);
  border-radius: var(--radius-sm);
  background: var(--ui-fg); color: var(--ui-bg); border: 1.5px solid var(--ui-fg);
}
.about-body { font-size: var(--sub-size); line-height: 1.5; text-transform: none; }
.about-body p { margin: 0 0 calc(10px * var(--ui-scale)); }
/* Imprint · Privacy · licence, at the foot of the about text. Links keep the
   panel's own colour and only get an underline, so they read as part of the
   text rather than as a second kind of button. */
.about-legal { margin-top: calc(14px * var(--ui-scale)); }
.about-legal a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }

/* Sub-heading inside the about text (HOW TO). Matches the section headers in
   the left panel rather than the browser's default h2. */
.about-sub {
  font-size: var(--headline-size); font-weight: normal;
  text-transform: uppercase; letter-spacing: 0.03em;
  margin: calc(16px * var(--ui-scale)) 0 calc(8px * var(--ui-scale));
}

/* Start overlay.
   Transparent and click-through: the tool stays fully usable before playback
   has started, so this is a prompt, not a modal. Only .start-play takes
   pointer events (see below); everything else the user aims at is the real UI
   underneath. The dimming backdrop is gone for the same reason — a veil over
   controls that still work would be a lie about their state. */
#startOverlay {
  position: fixed; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: none;
  pointer-events: none;
  /* Below every UI cluster (5) and every panel (8/12), above the canvas. It
     used to sit at 30, which floated the prompt over an open LIBRARY panel. */
  z-index: 3; transition: opacity 0.3s;
}
#startOverlay .box { text-align: center; color: var(--panel-fg); font-size: calc(18px * var(--ui-scale)); }

/* Play button on the start screen. Sized in vmin as well as px so it stays a
   large, unmissable target on any viewport — the whole point is that the first
   click reads as "play", not as "dismiss".

   Black fill, white outline, white triangle, fixed rather than themed: this
   button appears over whatever colours the artwork happens to be using, and
   the one thing it must never do is disappear into them.

   pointer-events: auto re-enables clicks on just this element — its parent
   turned them off for the whole overlay. */
.start-play,
.start-record {
  width: min(calc(144px * var(--ui-scale)), 20vmin);
  pointer-events: auto;
  cursor: pointer;
  position: relative;
  transition: transform 0.15s, opacity 0.15s;
}
.start-play svg, .start-record svg { width: 100%; height: auto; display: block; }
.start-play:hover, .start-record:hover { transform: scale(1.05); }
.start-play:active, .start-record:active { transform: scale(0.97); opacity: 0.85; }
.start-play:focus-visible, .start-record:focus-visible {
  outline: 2px solid var(--panel-fg); outline-offset: 6px; border-radius: 50%;
}

/* The two start buttons (PLAY, RECORD) side by side.
   The box is a row rather than the single centred button of the public build,
   so PLAY and RECORD read as two equal offers rather than one action with an
   afterthought next to it. */
#startOverlay .box {
  display: flex; align-items: center; justify-content: center;
  gap: min(calc(40px * var(--ui-scale)), 6vmin);
}

/* Idle shows the red dot, recording shows the white square. Swapping the icon
   in place means the target that starts the recording is the target that stops
   it — nothing moves under the visitor's finger mid-gesture. */
.start-record .rec-stop { display: none; }
.start-record.is-recording .rec-dot { display: none; }
.start-record.is-recording .rec-stop { display: block; }

/* Slow pulse while recording. The countdown below gives the exact number; this
   is for someone standing two metres back who just needs to know it is live. */
.start-record.is-recording { animation: recPulse 1.4s ease-in-out infinite; }
@keyframes recPulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

/* RECORD SOUND in the bottom-left stack. Inherits
   .upload-btn so it matches the two buttons under it; only the recording state
   is new. Inverted rather than tinted while live, because at this size a red
   outline on a small pill is easy to miss across a room.

   .bottom-left stretches its direct children to the width of the widest one
   (UPLOAD TYPEFACE) by default. UPLOAD SOUND/TYPEFACE don't show that extra
   width because each sits inside its own .upload-wrap row, which only grows
   the *wrap*, not the button inside it. RECORD SOUND has no such wrap, so
   without this it stretched to the full column width with its (centered,
   per the browser's default button styling) label floating in the middle —
   the "too much space on both sides" look. align-self: flex-start opts this
   one button back out of the stretch, so it sizes to its own text exactly
   like the other two. */
.record-btn { align-self: flex-start; }
.record-btn.is-recording, .record-btn.is-pending {
  background: #e0201b; color: #fff; border-color: #e0201b;
}

/* Seconds left, under the button. Absolutely positioned so that showing and
   hiding it never shifts the buttons themselves. */
.start-record-time {
  position: absolute; left: 0; right: 0; top: 106%;
  /* On a plate in the button colours, not loose text. It sits directly over
     the glyph, and the glyph is usually the same colour as plain text would
     be — the first version was black-on-black the moment the letter grew
     under it. width:max-content + auto margins keep it centred. */
  width: max-content; max-width: 100%;
  margin: 0 auto;
  padding: calc(3px * var(--ui-scale)) calc(10px * var(--ui-scale));
  background: var(--ui-bg); color: var(--ui-fg);
  border-radius: var(--radius-sm);
  font-size: var(--headline-size);
  text-align: center;
  pointer-events: none;
}
/* No empty plate hanging under the button when nothing is being recorded. */
.start-record-time:empty { display: none; }
.hidden { opacity: 0; pointer-events: none; }

/* The start overlay needs more than the generic .hidden above.
   .hidden sets pointer-events:none on the OVERLAY, but .start-play and
   .start-record each set pointer-events:auto on themselves — and a child's
   own value wins over an ancestor's. So once playback started, the two
   buttons were invisible (opacity 0) and still fully clickable, sitting
   dead centre over the artwork.
   visibility:hidden fixes it at the root: a hidden element is not hit-tested
   at all, whatever its descendants ask for. The delayed visibility
   transition lets the 0.3s fade finish before it takes effect. */
#startOverlay.hidden {
  visibility: hidden;
  transition: opacity 0.3s, visibility 0s linear 0.3s;
}
#startOverlay.hidden .start-play,
#startOverlay.hidden .start-record { pointer-events: none; }

/* Hidden native inputs */
.ui-hidden-input { position: absolute; opacity: 0; pointer-events: none; width: 0; height: 0; }

/* A / BG: a button-looking label with the real colour input spread
   invisibly over it, so the click lands on the input (needed for Safari). */
.color-btn { position: relative; display: inline-block; overflow: hidden; }
.color-input {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  margin: 0; padding: 0; border: 0;
  opacity: 0; cursor: pointer;
}

/* Toast */
#uiToast {
  position: fixed; left: var(--edge); bottom: calc(130px * var(--ui-scale)); z-index: 20;
  padding: calc(7px * var(--ui-scale)) calc(12px * var(--ui-scale));
  border-radius: var(--radius-sm);
  font-size: var(--sub-size);
  color: var(--toast-fg); background: var(--toast-bg);
  opacity: 0; transition: opacity 0.25s; pointer-events: none;
  max-width: calc(280px * var(--ui-scale));
}
#uiToast.error { background: var(--toast-error-bg); }

/* ============================================================================
   RESPONSIVE — one knob, --ui-scale, resizes the whole interface.
   The breakpoints use BOTH width and height because laptop screens are short
   before they are narrow: a 13" panel (1280x800 or 1440x900 CSS px) is caught
   by the height clause even when the width clause would miss it.
   The canvas letter is scaled separately, in sketch.js (viewScale), so the
   glyph and the UI shrink together.
   ============================================================================ */

/* Large laptops / small desktops — ~15% smaller */
@media (max-width: 1700px), (max-height: 950px) {
  :root { --ui-scale: 0.85; }
}

/* 13" laptops — ~25% smaller, the target of this pass */
@media (max-width: 1500px), (max-height: 900px) {
  :root { --ui-scale: 0.75; }
}

/* Very short or narrow viewports — ~35% smaller */
@media (max-width: 1200px), (max-height: 760px) {
  :root { --ui-scale: 0.65; }
}

/* ============================================================================
   DESKTOP-ONLY GATE (gate.js adds .is-gated to <html> on touch-only devices)
   ============================================================================ */
.gate-notice { display: none; }
html.is-gated .gate-notice {
  display: flex; align-items: center; justify-content: center;
  position: fixed; inset: 0; z-index: 100;
  background: var(--page-bg); color: var(--panel-fg);
  padding: 24px; box-sizing: border-box; text-align: center;
  font-family: var(--ui-font);
}
/* Everything else stays out of the way: no controls under a finger, no focus
   trap behind the notice. */
html.is-gated body > *:not(.gate-notice):not(script) { display: none !important; }
.gate-box { max-width: 22em; }
.gate-title { font-size: 28px; font-weight: 500; letter-spacing: 0.04em; margin: 0; }
.gate-sub   { font-size: 14px; margin: 6px 0 28px; opacity: 0.75; }
.gate-msg   { font-size: 18px; margin: 0 0 28px; }
.gate-links { font-size: 13px; margin: 0; }
.gate-links a { color: inherit; text-underline-offset: 2px; }

/* ---- Animated sliders (STYLE (GLYPH)) -------------------------------------
   A play button beside the track (left or right: ANIM_BTN_SIDE in Menu.js). It takes its room out of the track, not
   out of the panel: the track is flex:1 next to it. The label row is indented
   by the same amount so the pole words stay over the ends of the track.
   Button size = the slider thumb (14px), so triangle, circle and handle
   read as one family. */
.ctl-row.animatable {
  --anim-size: calc(14px * var(--ui-scale));
  --anim-gap: calc(7px * var(--ui-scale));
}
.ctl-row.anim-left  .ctl-labelrow { padding-left:  calc(var(--anim-size) + var(--anim-gap)); }
.ctl-row.anim-right .ctl-labelrow { padding-right: calc(var(--anim-size) + var(--anim-gap)); }
.ctl-row.anim-right .anim-btn { order: 2; }
.ctl-track { display: flex; align-items: center; gap: var(--anim-gap); }
.ctl-track .ctl-slider { flex: 1; min-width: 0; width: auto; }
.anim-btn {
  flex: none;
  width: var(--anim-size); height: var(--anim-size);
  padding: 0; margin: 0; border: none;
  background: var(--ui-fg);
  cursor: pointer;
  /* play triangle, pointing right, optically centred in its square */
  clip-path: polygon(14% 0, 100% 50%, 14% 100%);
  border-radius: 0;
  transition: clip-path 0.12s ease, border-radius 0.12s ease;
}
.anim-btn.active { clip-path: none; border-radius: 50%; }
.anim-btn:disabled { cursor: default; }
.anim-btn:focus-visible { outline: 1.5px solid var(--ui-fg); outline-offset: 2px; }
/* The slider is locked while the sound drives it: greyed out, handle still
   visibly moving. The button stays at full strength — it is the way out. */
.ctl-row.ctl-animated .ctl-labelrow,
.ctl-row.ctl-animated .ctl-slider { opacity: 0.38; }
.ctl-row.ctl-animated .ctl-slider::-webkit-slider-thumb { cursor: default; }
.ctl-row.ctl-animated .ctl-slider::-moz-range-thumb { cursor: default; }

/* ---- Donation: ABOUT link + pop-up after EXPORT OTF ----------------------- */
/* White pill on the black panel, like the sub-buttons in the left panel. */
.donate-btn {
  display: inline-block;
  font-family: var(--ui-font); font-size: var(--sub-size);
  text-transform: uppercase; letter-spacing: 0.03em; line-height: 1;
  padding: calc(5px * var(--ui-scale)) var(--btn-pad-x);
  border-radius: var(--radius-sm);
  background: var(--ui-fg); color: var(--ui-bg);
  text-decoration: none; white-space: nowrap;
}
.donate-btn:hover { opacity: 0.82; }
.about-body .donate-btn { text-decoration: none; }
/* Transparent full-screen layer: only there so a click beside the window
   closes it. No dimming — the typeface on the canvas stays visible. */
.donate-backdrop {
  position: fixed; inset: 0; z-index: 20;
  display: flex; align-items: center; justify-content: center;
}
.donate-backdrop.hidden { display: none; }
.donate-box {
  background: var(--ui-bg); color: var(--ui-fg);
  border-radius: var(--radius);
  padding: calc(14px * var(--ui-scale)) calc(16px * var(--ui-scale));
  min-width: calc(260px * var(--ui-scale));
}
.donate-head {
  display: flex; justify-content: space-between; align-items: center;
  gap: calc(16px * var(--ui-scale));
  font-size: var(--headline-size);
}
.donate-msg {
  font-size: var(--sub-size); text-transform: none;
  margin: calc(12px * var(--ui-scale)) 0;
}
.donate-box .donate-btn { font-size: var(--headline-size); padding: var(--btn-pad-y) var(--btn-pad-x); border-radius: var(--radius); }
