/* ============================================================================
   JAS-I — site.css

   Paper, not screen. A warm grey-beige page, near-black text, and one orange for
   anything that acts. The only dark areas on the site are the plugin itself and
   the plate the wordmark stands on — so the instrument is the brightest, most
   saturated thing anywhere, and the page around it stays quiet.

   Type: the Fira family. Fira Sans for everything, Fira Mono for labels.
   Goldman is display only and used for two words: JAS-I and KHORLO.

   ALL THREE ARE SELF-HOSTED, so neither page nor plugin makes a third-party
   request. Goldman and Fira Mono are byte-for-byte the files compiled into the
   plugin, copied from Khorlo/public/fonts, so the wordmark renders identically
   in both. The body face has moved twice: the design arrived asking Google
   Fonts for Hind, the plugin's body face at the time; the site then followed
   the plugin to Instrument Sans; and on 2026-09-28 Aaron moved the site to
   Fira Sans, so the whole page is one family. The plugin still sets its body
   in Instrument Sans.

   Fira Sans is a static family, one file per weight, so the three weights the
   page uses (400, 500, 600) are three downloads each for latin and latin-ext.

   When picking a subset from Google's CSS, select it by unicode-range and never
   by position in the file. Choosing by index once shipped a face with no A–Z at
   all — it loaded, reported success, and silently rendered as the system font.
   ========================================================================= */

@font-face {
  font-family: 'Goldman';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  /* Relative to THIS stylesheet, so it resolves both on jas-i.com and when the
     folder is opened locally. */
  src: url('fonts/goldman-400.woff2') format('woff2');
}

/* Fira Sans, one file per weight and subset. The latin file carries the page;
   latin-ext only downloads when a page shows a character that needs it. */
@font-face {
  font-family: 'Fira Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/firasans-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fira Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/firasans-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fira Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/firasans-600-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fira Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/firasans-400-latinext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Fira Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/firasans-500-latinext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Fira Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/firasans-600-latinext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Fira Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/firamono-400.woff2') format('woff2');
}

:root {
  /* ground — a warm grey that falls away to black down the page, so the light
     bar reads as the top of something rather than a strip on a flat field */
  --ground-top: #4a4946;
  --ground-mid: #24231f;
  --ground-bot: #0d0d0c;
  /* The top bar. Same value as --plate and --ground-bot today, kept as its own
     token because they are different jobs: chrome, the instrument's ground, and
     the foot of the page gradient. Moving one should not drag the others. */
  --bar:    #0d0d0c;
  --panel:  rgba(255,255,255,0.07);   /* content panels, over the ground */
  --plate:  #0d0d0c;
  /* The signup band's surface. Same value as --plate in the dark, but a
     DIFFERENT JOB: --plate is the instrument's ground (the illustration band, a
     terminal block) and stays dark in both themes, while this is a page surface
     and has to turn over with the page. They were one token, and light mode
     made the signup band a black slab with dark type on it. */
  --band:   #0d0d0c;
  /* The band the explanation runs on. Split from --plate so it can invert with
     the page while --plate stays dark for the terminal blocks, which are a
     different job. Its ink and linework travel with it. */
  --stage:      #0d0d0c;
  --stage-ink:  #eef1f6;
  --stage-ink-2:rgba(238,241,246,0.5);
  --stage-line: rgba(215,225,240,0.30);
  --stage-mark: #eef1f6;   /* the string markers — heavier than the hoops, lighter than type */
  --ring-flare: #ffffff;   /* the flash as an impulse crosses a string */

  /* ink, on dark */
  --ink:    #f0eeea;
  --ink-2:  #cfccc6;
  --muted:  #a5a29c;
  --faint:  #86837d;
  --bar-ink: #f0eeea;  /* type on the bar */

  --rule:   rgba(255,255,255,0.13);
  --rule-up:rgba(255,255,255,0.26);

  /* The illustration is on dark everywhere now. */
  --ring-line: rgba(215,225,240,0.30);
  --ring-mark: var(--stage-mark);  /* the stage re-declares both; this is only the default */

  /* one colour for anything that acts */
  --orange: #c4491a;
  --orange-hi: #d4552a;

  /* the instrument's own purple, used only inside the wordmark */
  --purple: #a781f7;

  /* ---- the colours that were painted in place ---------------------------
     Named for the JOB, not the value, so a second palette only has to answer
     the same questions. Anything the INSTRUMENT owns (the plate, the type on
     it, the screenshots' own edge) is deliberately a separate token from the
     page's own ink: those stay dark whatever the page does. */
  --on-orange:   #fff;                  /* type sitting on an orange action */
  --mark-o:      #fff;                  /* the wordmark's counter-letter — a brand value, see README */

  --sheen:       rgba(255,255,255,0.10);/* the lift down the ground, and a hovered card */
  --sheen-soft:  rgba(255,255,255,0.08);/* the same lift, quieter */
  --select:      rgba(196,73,26,0.4);   /* selected text */
  --bar-edge:    rgba(240,238,234,0.28);/* hairline drawn ON the bar */

  /* the instrument's own ground, and what reads on it */
  --shot-ground: #0d0d0f;               /* sampled from the captures themselves */
  --plate-ink:   #eef1f6;               /* type on the plate */
  --plate-ink-2: rgba(238,241,246,0.5); /* quieter type on the plate */
  --plate-edge:  rgba(238,241,246,0.3); /* hairline on the plate */
  --overlay:     rgba(13,13,13,0.94);   /* the full-screen ground a zoomed shot sits on */
  --scrim:       rgba(0, 0, 0, 0.62);   /* behind a dialog */

  /* the carousel's round controls, which sit over the panel */
  --control:     #d9d7d3;
  --control-ink: #2a2926;
  --control-hi:  #fff;                  /* one under the cursor */
  --dot:         #f2f0ec;

  --cmd-ink:     #e8b4a0;               /* the terminal line's type */
  --err:         #9a2b12;               /* a refused form message */

  --font: 'Fira Sans', ui-sans-serif, -apple-system, BlinkMacSystemFont,
          'Segoe UI', sans-serif;
  --mono: 'Fira Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 18px;
  --s5: 26px; --s6: 40px; --s7: 64px; --s8: 104px;

  --barh:    74px;    /* the pinned frame reserves this */
  --gutter:  clamp(20px, 3.4vw, 34px);
  --page:    1480px;
}

/* System appearance is CSS-owned, including with JavaScript disabled.
   One token block keeps the normal and plain-document views in agreement. */
@media (prefers-color-scheme: light) {
:root {
  --ground-top: #fbfaf8;
  --ground-mid: #f4f2ee;
  --ground-bot: #eae7e1;

  /* The bar stays the near-black Aaron picked. On a light page it is the one
     dark edge, which is the same job it did in reverse when the page was dark. */

  --panel:  rgba(0,0,0,0.045);

  --ink:    #1f1d1a;
  --ink-2:  #3f3b35;
  --muted:  #5f5a53;
  /* 4.47:1 on the signup band and 5.17:1 on the page. The small mono labels
     (EMAIL ADDRESS, the eyebrows, the zip sizes) are 10px, so they need 4.5 —
     the obvious lighter grey measured 3.12 and would have shipped unreadable. */
  --faint:  #6a655e;

  --rule:   rgba(0,0,0,0.12);
  --rule-up:rgba(0,0,0,0.24);

  /* #c4491a measures 3.90:1 on this cream, which is fine for a 30px wordmark and
     fails AA for a 15px label. add-site-bar.py already solved this once for the
     manual's bar and landed on #b24217; the same value, for the same reason. The
     brighter original becomes the hover. */
  --orange:    #b24217;
  --orange-hi: #c4491a;

  /* THE WORDMARK IS THE ONE BRAND EXCEPTION, and it is forced. #A781F7 measures
     2.41:1 on the panel and the white O measures 1.22:1 — the O would simply not
     be there, and KHORLO would read as KHORL. #6b4fd0 is 4.70:1 and is not a new
     colour: it is the violet the manual itself was authored in, which
     scripts/restyle-manual.py still maps FROM. Revert both lines and the mark is
     the shipped one again. */
  --purple: #6b4fd0;
  --mark-o: #1f1d1a;

  --sheen:      rgba(0,0,0,0.05);
  --sheen-soft: rgba(0,0,0,0.04);
  --select:     rgba(196,73,26,0.22);

  /* The carousel's controls sit over a light panel now, so they invert. */
  --control:     #33302b;
  --control-ink: #f2f0ec;
  --control-hi:  #0d0d0c;
  --dot:         #cbc6bd;

  --band: #e6e2db;   /* the signup band, a shade under the page */

  /* Aaron's mock: the bar and the explanation band are the same grey, and the
     linework inverts to ink on it. #d9d7d3 is not a new colour — it is what the
     carousel's controls are already made of in the dark. The illustration needs
     no new values: ring.js has carried light fallbacks since it was drawn for a
     light page, and these are them. */
  --bar:        #d9d7d3;
  --bar-ink:    #1f1d1a;
  --bar-edge:   rgba(25,25,23,0.28);

  --stage:      #d9d7d3;
  --stage-ink:  #1f1d1a;
  --stage-ink-2:rgba(31,29,26,0.62);
  --stage-line: rgba(60,58,54,0.34);
  /* The markers are NOT the caption ink. Painted with it they came out near
     black and far heavier than the hoops they sit against; this is a grey in
     the hoops' own family, a step stronger because a string is the thing being
     struck. */
  --stage-mark: #6a655e;
  /* THE FLARE STAYS WHITE. It is a blurred copy of the marker behind itself —
     a glow, not a mark — so a dark value does not read as light arriving, it
     reads as a drop shadow. White on grey is a lift; that is the effect. */
  --ring-flare: #ffffff;
}

}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ground-bot);
  /* Fixed, so the fall to black belongs to the page rather than to whichever
     section happens to be passing. */
  background-image:
    radial-gradient(120% 70% at 8% 0%, var(--sheen), transparent 60%),
    linear-gradient(var(--ground-top) 0%, var(--ground-mid) 46%, var(--ground-bot) 82%);
  background-attachment: fixed;
  background-repeat: no-repeat;
  color: var(--ink);
  font: 400 17px/1.6 var(--font);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: clip;
}

@media (min-width: 700px) { body { font-size: 18px; } }

img, svg, audio { display: block; max-width: 100%; }
img { height: auto; }

a { color: var(--orange); text-decoration: none; }
a:hover { color: var(--orange-hi); }

::selection { background: var(--select); }

:focus-visible { outline: 2px solid var(--orange); outline-offset: 3px; }

.wrap { max-width: var(--page); margin: 0 auto; padding: 0 var(--gutter); }

.visually-hidden {
  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;
}

.skip {
  position: absolute; left: var(--gutter); top: -100px;
  z-index: 30; padding: 10px 14px;
  background: var(--bar); border: 1px solid var(--rule-up);
  font: 500 12px/1 var(--mono); letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ink);
}
.skip:focus { top: 14px; }

/* ---- top bar ------------------------------------------------------------ */
/* Anchored to the top of the window and above everything, including the pinned
   frame on the front page, which scrolls under it. */
/* THE ONLY LIGHT SURFACE ON THE SITE. Everything below it is the instrument's
   own darkness, so the bar has to carry its own ink colour rather than inherit. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--bar);
  color: var(--bar-ink);
}

.masthead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s5);
  padding: var(--s4) 0;
  position: relative;
}

/* The nav is centred on the BAR, not on the gap between the wordmark and the
   buttons — the buttons are much wider than JAS-I, so a flex centre would still
   sit left of true centre. Below the breakpoint it drops back into the flow. */
.masthead > .nav {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
}
@media (max-width: 880px) {
  .masthead > .nav { position: static; transform: none; }
}

.mark {
  font: 400 30px/1 Goldman, var(--font);
  letter-spacing: 0.08em;
  color: var(--orange);
}
.mark:hover { color: var(--orange-hi); }
/* Preserve the supplied wordmark's orange gradients. */
.mark { display: block; flex-shrink: 0; width: 118px; height: 26px; }
.mark img { display: block; width: 100%; height: 100%; object-fit: contain; }

.nav {
  display: flex;
  gap: var(--s6);
  font: 400 17px/1 var(--font);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.nav a { color: var(--bar-ink); padding-bottom: 4px; }
.nav a:hover { color: var(--orange); }
.nav a[aria-current="page"] {
  color: var(--orange);
  border-bottom: 2px solid var(--orange);
}

/* The two things a visitor might actually want to DO, kept reachable from every
   scroll position rather than only at the foot of the page. */
.bar-actions { display: flex; align-items: center; gap: var(--s5); }

.btn-sm {
  padding: 9px 20px;
  border: 0;
  border-radius: 999px;
  background: var(--orange);
  color: var(--on-orange);
  font: 600 15px/1 var(--font);
  white-space: nowrap;
}
.btn-sm:hover { background: var(--orange-hi); color: var(--on-orange); }

.btn-sm-quiet {
  padding: 9px 18px;
  border: 1px solid var(--bar-edge);
  border-radius: 999px;
  background: none;
  color: var(--bar-ink);
  font: 500 15px/1 var(--font);
  white-space: nowrap;
}
.btn-sm-quiet:hover { border-color: var(--orange); color: var(--orange); }

/* Below this the bar cannot hold the wordmark, the links and the pill on one
   line. Hiding the quiet button is not enough on its own: the primary pill
   carries a different label on each page — "Tell me when it drops" is some 35px
   wider than "Keep me posted" — so the bar overflowed by a different amount per
   page rather than failing in one obvious place. Letting the links drop to a
   second row fixes every page at once and nothing has to shrink. */
@media (max-width: 760px) {
  .bar-actions .btn-sm-quiet { display: none; }
  .masthead { flex-wrap: wrap; row-gap: var(--s3); }
  .masthead > .nav {
    order: 3;
    width: 100%;
    justify-content: flex-start;
    gap: var(--s5);
  }
  .bar-actions { gap: var(--s3); }
}

/* ---- headings and prose ------------------------------------------------- */
h1, h2, h3 { margin: 0; font-weight: 500; text-wrap: balance; }

h1 { font-size: clamp(29px, 3.6vw, 38px); line-height: 1.16; }
h2 { font-size: clamp(22px, 2.6vw, 27px); line-height: 1.26; }
h3 { font-size: 17px; font-weight: 600; line-height: 1.35; }

p { margin: 0 0 var(--s4); max-width: 62ch; text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

.lede {
  font-size: clamp(19px, 1.55vw, 25px);
  line-height: 1.55;
  color: var(--ink);
  max-width: 21em;
  font-weight: 400;
}

.eyebrow {
  margin: 0 0 var(--s4);
  font: 400 10px/1 var(--mono);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--faint);
}

code { font-family: var(--mono); font-size: 0.86em; color: var(--ink-2); }

/* ---- the wordmark and its plate ---------------------------------------- */
/* The only black on the page besides the plugin. KHORLO is set in the
   instrument's purple with a white O, exactly as the plugin sets it. */
p.khorlo-mark { margin: 0 0 clamp(28px, 3vw, 44px); }

.khorlo-mark {
  display: block;
  margin-bottom: clamp(28px, 3vw, 44px);
  text-align: center;
  font: 400 clamp(40px, 4.6vw, 60px)/1 Goldman, var(--font);
  letter-spacing: 0.06em;
  color: var(--purple);
}
.khorlo-mark .o { color: var(--mark-o); }

.khorlo-stage {
  margin: 0 0 var(--s4);
  font: 400 11px/1 var(--mono);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--faint);
}

/* ---- the one button ----------------------------------------------------- */
/* A pill, and a big one. There is one action on this page and it should look
   like the only one. */
.btn {
  display: inline-block;
  padding: 22px 64px;
  border: 0;
  border-radius: 999px;
  background: var(--orange);
  color: var(--on-orange);
  font: 600 26px/1 var(--font);
  text-align: center;
  cursor: pointer;
}
.btn-centre { display: block; width: max-content; margin: var(--s7) auto; }

/* Not a call to action. It is a hint that the page continues — mono, faint, and
   the same size as every other label on the page, so the one pill on the page
   stays the one pill on the page. */
.scroll-cue {
  display: flex; align-items: center; gap: var(--s3);
  width: max-content; margin: var(--s8) auto var(--s7);
  font: 400 24px/1 var(--mono);
  letter-spacing: 0.1em;
  color: var(--faint); text-decoration: none;
  transition: color 160ms ease;
}
.scroll-cue svg { width: 18px; height: 28px; transition: transform 400ms cubic-bezier(.2,.7,.2,1); }
.scroll-cue:hover { color: var(--ink-2); }
.scroll-cue:hover svg { transform: translateY(4px); }
@media (prefers-reduced-motion: reduce) {
  .scroll-cue:hover svg { transform: none; }
}
.btn:hover { background: var(--orange-hi); color: var(--on-orange); }
.btn-wide { display: block; width: 100%; }

/* The form buttons are pills like every other action on the site, and they stay
   disabled until there is an address to submit — a submit that can only fail is
   not an invitation. */
.btn-pill {
  padding: 13px 28px;
  border: 0;
  border-radius: 999px;
  background: var(--orange);
  color: var(--on-orange);
  font: 600 16px/1 var(--font);
  white-space: nowrap;
  cursor: pointer;
  transition: background 180ms ease, opacity 180ms ease;
}
.btn-pill:hover:not([disabled]) { background: var(--orange-hi); color: var(--on-orange); }
.btn-pill-quiet:not([disabled]) {
  background: none;
  border: 1px solid var(--rule-up);
  color: var(--ink-2);
}
.btn-pill-quiet:hover:not([disabled]) { background: none; border-color: var(--orange); color: var(--ink); }
.btn-pill[disabled] {
  background: none;
  border: 1px solid var(--rule-up);
  color: var(--faint);
  cursor: not-allowed;
}

.btn-quiet {
  padding: 0 0 4px;
  border: 0;
  border-bottom: 2px solid var(--orange);
  border-radius: 0;
  background: none;
  color: var(--orange);
  font: 500 16px/1.3 var(--font);
  cursor: pointer;
}
.btn-quiet:hover { background: none; color: var(--orange-hi); }
.btn[disabled] { background: var(--rule-up); cursor: default; }

.btn-row { display: flex; flex-wrap: wrap; gap: var(--s5); align-items: center; }

/* ---- the rows: words left, instrument right ---------------------------- */
/* The screenshot is the widest thing in the row and the only thing with any
   depth to it. The words sit in a panel of their own so the two never touch. */
.row-pair {
  display: grid;
  grid-template-columns: minmax(0, 0.62fr) minmax(0, 1fr);
  gap: 0;
  /* Stretch, not start: the panel takes the height of the screenshot beside it,
     so the two columns terminate together however long the copy runs. */
  align-items: stretch;
  margin-bottom: var(--s8);
}
.row-pair > .panel { display: flex; flex-direction: column; }
.row-pair > .panel > .panel-body { flex: 1; }
.js .carousel[data-ready] .panel-body { padding-bottom: clamp(96px, 9vw, 130px); }

/* ---- progressive reveal (assets/reveal.js) ------------------------------ */
/* Same opacity + blur as the pinned frame on the front page, so a page that
   scrolls normally still arrives the same way. Only active under .js: without it
   nothing is ever hidden. */
.js [data-reveal] {
  opacity: 0;
  filter: blur(12px);
  transition: opacity 700ms ease, filter 700ms ease;
  will-change: opacity, filter;
}
.js [data-reveal="in"] { opacity: 1; filter: blur(0); }

/* The words land before the instrument they describe. */
.js [data-reveal-delay="1"] { transition-delay: 180ms; }

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] { opacity: 1; filter: none; transition: none; }
}

.panel { background: var(--panel); }
.panel-body { padding: clamp(30px, 3.4vw, 56px); }
.panel-body > .lede { margin-bottom: var(--s6); }

/* No frame, no shadow. Flush against the panel, the screenshot is the lit half of
   one object rather than a picture pinned next to some text. */
.shot { margin: 0; display: block; }
.shot img { width: 100%; height: auto; }

/* THE SCREENSHOT AND THE PANEL BESIDE IT ARE ALWAYS THE SAME HEIGHT. The row
   takes the height of whichever is taller — usually the copy — and the image
   sits in the rest.

   `cover` was CROPPING: it filled the cell by cutting the screenshot down from
   the top-left, so the string cards and the preset name on the right edge were
   simply gone, on every slide. A plugin screenshot with its right third missing
   reads as a broken layout, not as a crop. `contain` shows the whole capture at
   the column's full width; the band it leaves above and below is painted in the
   plugin's own edge colour (--shot-ground, sampled from the captures), so it reads as
   the instrument's ground continuing rather than as letterboxing. */
.row-pair > .shot { display: flex; flex-direction: column; overflow: hidden; background: var(--shot-ground); }
.row-pair > .shot > img {
  flex: 1;
  min-height: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
}
/* A screenshot shown inside a panel rather than as the panel's counterpart. */
.shot.inset { box-shadow: none; border: 1px solid var(--rule); max-width: 420px; }

.shot figcaption {
  margin-top: var(--s3);
  font: 400 10px/1.5 var(--mono);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--faint);
}

@media (max-width: 900px) {
  .row-pair { grid-template-columns: 1fr; margin-bottom: var(--s7); }
}

/* ---- the appearance and modulation showcase ---------------------------- */
/* Words on the left, four figures on the right: the dialog and two instruments
   across the top, the modulation strip spanning underneath them. */
.showcase {
  /* Follows the full-bleed dark section, which has no bottom margin of its own. */
  margin-top: var(--s8);
  display: grid;
  grid-template-columns: minmax(0, 0.42fr) minmax(0, 1fr);
  gap: clamp(28px, 3.4vw, 56px);
  align-items: start;
  margin-bottom: var(--s8);
}
.showcase-say { padding-top: var(--s4); }
.showcase-say .lede { max-width: 20em; }

.showcase-figs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(10px, 1.2vw, 18px);
  align-items: start;
}
.showcase-figs .shot { margin: 0; }
.showcase-wide { grid-column: 1 / -1; }

@media (max-width: 900px) {
  .showcase { grid-template-columns: 1fr; }
  .showcase-figs { grid-template-columns: 1fr; }
}

/* ---- a band for wide, short figures ------------------------------------ */
/* The modulation strip is nearly 8:1. Beside a column of text it is a sliver in a
   tall empty box, so it gets the full width and the words go above it. */
.row-band { margin-bottom: var(--s8); }
.row-band-say { background: var(--panel); padding: clamp(30px, 3.4vw, 56px); }
.row-band-say .lede { max-width: 46em; }
.row-band .shot { margin-top: var(--s5); }

/* ---- where the nav jumps land ------------------------------------------
   The bar is sticky, so a jump target needs a margin or it lands under it.
   The mailing-list band lands in the MIDDLE of the window: at the top it read
   as a header for whatever came next (Aaron, 2026-09-14). */
#demos, #about { scroll-margin-top: 96px; }
#keep, #beta { scroll-margin-top: max(96px, calc(50vh - 150px)); }

/* ---- the release band ----------------------------------------------------
   A short what's-new at the top of the page for the weeks after a release.
   Same ground and orange rule as the mailing-list band, so it reads as part of
   the site rather than a banner stuck on it. Remove or update per release. */
.release { margin-bottom: var(--s7); padding: var(--s6); background: var(--band); border-top: 2px solid var(--orange); }
.release-h { margin: 0 0 var(--s4); font-size: clamp(22px, 2vw, 30px); line-height: 1.15; color: var(--ink); }
.release-list { margin: 0 0 var(--s5); padding: 0; list-style: none; display: grid; gap: var(--s4); grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.release-list li { font-size: 15px; line-height: 1.5; color: var(--muted); }
.release-list b { color: var(--ink); font-weight: 600; }
.release .btn-row { margin: 0; }

/* ---- the screenshot carousel ------------------------------------------- */
/* Slides stack in ONE grid cell, so the frame takes the height of the tallest
   and never resizes underneath the arrows as you step through. Only .js gets the
   stacking — without it every slide is simply visible, one after another. */
.carousel { margin-bottom: var(--s8); }

/* EVEN LINE LENGTHS, not greedy ones. The slide copy is large type in a narrow
   column, so the default wrap leaves a jagged right edge: two long lines, then
   a short one. `balance` equalises them.
   It only applies to SHORT blocks — browsers stop balancing at around six lines
   and silently do nothing above that, which is why the copy has to be short
   enough as well. Measured: at four lines it changes the wrap, at seven it
   changed nothing at all. */
.carousel .lede { text-wrap: balance; }
.carousel-frame { display: block; }
.carousel-controls { display: none; }

.js .carousel[data-ready] .carousel-frame { display: grid; }
.js .carousel[data-ready] .carousel-frame > * { grid-area: 1 / 1; }
.carousel { position: relative; }

/* One wordmark for the whole carousel, sitting where the slides' own marks used
   to — same padding as .panel-body, so nothing appears to move. Without JS the
   slides simply stack, and it reads as the heading of the first one. */
.carousel-mark { margin: 0; padding: clamp(30px, 3.4vw, 56px) clamp(30px, 3.4vw, 56px) 0; background: var(--panel); }
.js .carousel[data-ready] .carousel-mark {
  position: absolute; top: 0; left: 0; z-index: 2;
  width: calc(0.62 / 1.62 * 100%);  /* the panel column; .row-pair has gap: 0 */
  box-sizing: border-box;
  background: none; pointer-events: none;
}
.js .carousel[data-ready] .panel-body {
  padding-top: calc(clamp(30px, 3.4vw, 56px) + clamp(40px, 4.6vw, 60px) + clamp(28px, 3vw, 44px));
}
.carousel-frame { overflow-x: clip; }

.js .carousel[data-ready] .carousel-controls {
  display: grid;
  grid-template-columns: minmax(0, 0.62fr) minmax(0, 1fr);
  position: absolute;
  left: 0; right: 0;
  bottom: clamp(20px, 2.6vw, 34px);
  /* The bar spans the frame so its first column lines up with the panel, but only
     the controls themselves take the pointer. */
  pointer-events: none;
}
.carousel-controls > .cc {
  grid-column: 1;
  display: flex;
  align-items: center;
  padding: 0 clamp(20px, 2.4vw, 34px);
  pointer-events: auto;
}
.carousel-controls .dots { flex: 1; justify-content: center; }

/* Stacked, there is no panel foot to sit on: the controls drop into normal flow
   under the frame instead of floating over the screenshot. */
@media (max-width: 900px) {
  .js .carousel[data-ready] .carousel-controls {
    position: static;
    grid-template-columns: 1fr;
    margin-top: var(--s5);
  }
  .js .carousel[data-ready] .panel-body { padding-bottom: clamp(30px, 3.4vw, 56px); }
  .row-pair > .shot { display: block; }
  .row-pair > .shot > img { height: auto; object-fit: fill; }
  /* One column means the measure is the panel, not a 21em slot inside it. */
  .row-pair > .panel .lede { max-width: 34em; }
}

/* A slide leaves the way you sent it and the next one comes from the other side,
   so the direction of travel matches the arrow you pressed. --dir is set by
   carousel.js: -1 going back, +1 going forward. */
.js .carousel[data-ready] [data-slide] {
  opacity: 0;
  visibility: hidden;
  transform: translateX(calc(var(--dir, 1) * 44px));
  transition: opacity 420ms ease, transform 460ms cubic-bezier(0.22, 0.61, 0.36, 1),
              visibility 0s linear 460ms;
  margin-bottom: 0;
}
/* The one on its way out travels the opposite way to the one arriving. */
.js .carousel[data-ready] [data-slide][data-leaving] {
  transform: translateX(calc(var(--dir, 1) * -44px));
}
.js .carousel[data-ready] [data-slide][data-on="true"] {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity 420ms ease, transform 460ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

@media (prefers-reduced-motion: reduce) {
  .js .carousel[data-ready] [data-slide] { transform: none; transition: opacity 200ms ease, visibility 0s linear 200ms; }
  .js .carousel[data-ready] [data-slide][data-leaving] { transform: none; }
}

.arrow {
  width: 62px;
  height: 62px;
  flex: none;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: var(--control);
  color: var(--control-ink);
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
}
.arrow:hover { background: var(--control-hi); }

.dots { display: flex; gap: 10px; }
.dot {
  width: 15px;
  height: 15px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--dot);
  cursor: pointer;
}
.dot:hover { background: var(--control-hi); }
.dot[aria-current="true"] { background: var(--orange); }

/* The dots already say where you are; the count is for screen readers and for
   anyone who wants the number. */
.carousel-count { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }

/* ---- full-size screenshots --------------------------------------------- */
[data-zoom] { cursor: zoom-in; }

.zoom {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: none;
  place-items: center;
  padding: clamp(16px, 4vw, 56px);
  background: var(--overlay);
  cursor: zoom-out;
}
.zoom[data-open] { display: grid; }
.zoom img { max-width: 100%; max-height: 100%; width: auto; }

.zoom-close {
  position: absolute;
  top: clamp(12px, 2vw, 24px);
  right: clamp(12px, 2vw, 24px);
  width: 44px;
  height: 44px;
  border: 1px solid var(--plate-edge);
  border-radius: 50%;
  background: none;
  color: var(--plate-ink);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}
.zoom-close:hover { border-color: var(--plate-ink); }

/* ---- the front-page stage ----------------------------------------------- */
/* ONE PINNED FRAME for the whole opening. The screenshot, the illustration and
   the six sentences live in it and cross-fade; nothing inside it scrolls,
   nothing inside it resizes, so no line of type ever re-wraps mid-motion.
   See assets/stage.js.

   Everything pinned is scoped to .js. Without it the same markup is a plain
   column: screenshot, then the sentences in order. */
/* DARK GROUND. The explanation is the one place the instrument's own light is the
   subject, so it runs on the same near-black the plugin does — the linework goes
   pale and the impulses read as glow rather than ink. Full-bleed out of the
   column it sits in. */
.stage-scroll,
.stage-scroll:first-child {
  position: relative;
  padding: var(--s6) 0 var(--s7);
  background: var(--stage);
  overflow: clip;               /* nothing of the frame escapes the ground */
  --ring-line: var(--stage-line);
  --ring-mark: var(--stage-mark);
  color: var(--stage-ink);
}
.stage-scroll > .eyebrow {
  max-width: var(--page);
  margin: 0 auto;
  padding: 0 var(--gutter);
  color: var(--stage-ink-2);
}

/* The dark section is full-bleed, so the frame is what re-establishes the page
   column — same max-width and same gutter as every .wrap on the page, so the
   captions line up with the panels above and below. */
.stage {
  max-width: var(--page);
  margin: 0 auto;
  padding: clamp(var(--s6), 5vw, var(--s7)) var(--gutter) 0;
  display: grid;
  grid-template-columns: minmax(0, 0.62fr) minmax(0, 1fr);
  gap: clamp(24px, 3vw, 40px);
  align-items: center;
}

/* Both halves stack their states in ONE grid cell, so every state is centred on
   the same spot and none of them can push another around. */
.stage-copy, .stage-fig { display: grid; position: relative; }
.stage-copy > *, .stage-fig > * { grid-area: 1 / 1; }

.stage-fig .shot { align-self: center; }

.stage-ring {
  align-self: center;
  justify-self: center;
  aspect-ratio: 1;
  height: min(70vh, 580px);
  width: auto;
  max-width: 100%;
}

/* FIXED SIZE, FIXED LINE BREAKS. The active sentence is not larger than the
   others — only present. */
.caption {
  align-self: center;
  margin: 0;
  padding: 0;
  font-size: clamp(20px, 1.9vw, 23px);
  line-height: 1.5;
  /* --stage-ink, not --plate-ink. The captions sit ON the band, so they have to
     turn over with it; --plate-ink is the light type that belongs on a surface
     that stays dark, like the lightbox and the terminal blocks. Painted with it,
     the sentences went near-white on a light grey band. */
  color: var(--stage-ink);
  max-width: none;
  text-wrap: pretty;
}

/* ---- without JS: a plain column ---------------------------------------- */
.stage-ring, .drivers { display: none; }
.captions { display: block; }
.caption + .caption { margin-top: var(--s4); }

/* ---- with JS: pinned, and every change is a cross-fade ----------------- */
.js .stage {
  position: sticky;
  top: 0;
  height: 100vh;
  /* The bar is sticky above it, so the frame gives that much back as padding to
     keep its content clear of it.

     THE OVERLAP LIVES ON THE DRIVERS, NOT HERE. A negative margin-bottom on a
     sticky element extends its margin box past its containing block, which lets
     it keep travelling after the section has ended — the illustration then paints
     over the rows below. The drivers pull themselves up instead, which gives the
     same geometry while the frame stays inside its own section. */
  padding-top: var(--barh);
  align-content: center;
}
.js .stage-ring { display: block; }
.js .drivers { display: block; position: relative; margin-top: -100vh; }
.js .captions { display: grid; }
.js .captions > * { grid-area: 1 / 1; }
.js .caption + .caption { margin-top: 0; }

/* The illustration is always present — it is the drawing inside it that builds.
   The sentences travel: up from below the frame, out through the top, fading and
   blurring at both ends so the movement is legible without any of them ever
   being readable in two places at once. */
.js .caption {
  transition: opacity 620ms ease, filter 620ms ease, transform 620ms ease;
  will-change: opacity, filter, transform;
  opacity: 0;
  filter: blur(12px);
}
.js .caption[data-pos="next"] { transform: translateY(26vh); }
.js .caption[data-pos="past"] { transform: translateY(-26vh); }
/* The active sentence follows the scroll directly, so it must not be smoothed —
   a transition here would lag behind the wheel. The ones entering and leaving
   still ease. */
.js .caption[data-pos="now"] {
  opacity: 1;
  filter: blur(0);
  transform: translateY(var(--drift, 0vh));
  transition: opacity 620ms ease, filter 620ms ease;
}

/* One driver per sentence. Height is dwell time. */
.driver { height: 88vh; }

@media (max-width: 900px) {
  .stage { grid-template-columns: 1fr; gap: var(--s5); }
  .stage-ring { height: min(44vh, 400px); }
  .caption { font-size: clamp(19px, 4.4vw, 22px); }
}

@media (prefers-reduced-motion: reduce) {
  .js .stage {
    position: static; height: auto; margin: 0;
    padding-top: clamp(var(--s6), 5vw, var(--s7));
  }
  .js .drivers { margin-top: 0; }
  .js .drivers { display: none; }
  .js .captions { display: block; }
  .js .caption + .caption { margin-top: var(--s4); }

  /* THE [data-pos] SELECTORS HAVE TO BE NAMED HERE, not just `.js .caption`.
     stage.js keeps setting data-pos in this mode — it has no idea the CSS has
     changed underneath it — and `.js .caption[data-pos="past"]` outranks
     `.js .caption` on specificity, so a bare `transform: none` above lost to it
     and every past sentence was still being thrown 26vh upward. Out of a column
     that no longer scrolls, and into .stage-scroll's clip: the first sentences
     were simply sliced off the top.

     Reported from Firefox on Windows 11, where the OS "show animations" setting
     puts a lot of people in this branch without ever choosing it. */
  .js .caption,
  .js .caption[data-pos="next"],
  .js .caption[data-pos="past"],
  .js .caption[data-pos="now"] {
    opacity: 1;
    filter: none;
    transform: none;
    transition: none;
  }
}

/* ---- a ring standing on its own (404) ---------------------------------- */
.ring-inline { width: min(100%, 420px); aspect-ratio: 1; margin: 0 auto; }

.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.62fr) minmax(0, 1fr);
  gap: clamp(24px, 3vw, 40px);
  align-items: center;
}
.hero-stage { display: flex; align-items: center; justify-content: center; }

@media (max-width: 900px) {
  .hero-grid { grid-template-columns: 1fr; }
  .hero-stage { margin-top: var(--s6); }
}

/* ---- ruled lists: demos, spec, notes ----------------------------------- */
.rows { border-top: 1px solid var(--rule); }
.row {
  display: grid;
  grid-template-columns: 130px minmax(0, 1fr);
  gap: var(--s5);
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--rule);
  align-items: baseline;
}
.row > .row-key {
  font: 400 11px/1.5 var(--mono);
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted);
}
.row > .row-val { font-size: 15px; line-height: 1.55; color: var(--ink-2); max-width: none; }

/* ---- the beta page: two downloads and nothing in their way -------------- */
/* Everything here is sized against the one question the page answers: which
   file do I click. The buttons are the largest thing on it by a wide margin,
   and the caveats below are deliberately the smallest. */
.beta-hero { padding: var(--s7) 0 var(--s8); text-align: center; }

/* The wordmark, then the build it is. The stage line used to sit above the
   wordmark and read as a label for the page rather than for the build.

   It needs its margins reset explicitly: the global `p` rule caps paragraphs at
   62ch with no auto margins, so inside a centred block the TEXT centres within a
   left-aligned box and the line lands left of everything above it. */
.beta-mark { margin-bottom: var(--s3); }
.beta-stage {
  max-width: none;
  margin: 0 auto var(--s6);
}

.dl-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s4);
  max-width: 760px;
  margin: 0 auto;
}
.dl-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); max-width: 980px; }
@media (max-width: 860px) { .dl-grid-3 { grid-template-columns: 1fr; } }
@media (max-width: 620px) { .dl-grid { grid-template-columns: 1fr; } }

/* One line under the buttons, in his voice. This slot carried the expiry
   warning while the builds had one. */
.beta-note {
  max-width: 46em;
  margin: var(--s7) auto 0;
  font-size: 17px; line-height: 1.6; color: var(--ink);
}

/* ---- the email step before a download ----------------------------------- */
/* A native <dialog>: the browser owns focus, Escape and the backdrop, so the
   script only has to open it, read the address, and start the file. */
.gate {
  width: min(520px, calc(100vw - 2 * var(--s5)));
  padding: 0;
  border: 1px solid var(--rule-up);
  /* Opaque. --panel is a 7% white wash meant to sit on the ground; over a
     dimmed page it lets the download buttons show through the form. */
  background: var(--ground-mid);
  color: var(--ink);
}
.gate::backdrop { background: var(--scrim); }
.gate-form { position: relative; padding: var(--s6) var(--s6) var(--s5); }
.gate-form .eyebrow { margin-bottom: var(--s3); }
.gate-h { margin: 0 0 var(--s3); font: 600 clamp(20px, 2.4vw, 24px)/1.25 var(--font); color: var(--ink); }
.gate-say { max-width: none; margin: 0 0 var(--s5); font-size: 15px; line-height: 1.6; color: var(--muted); }
.gate-form .field { min-width: 0; }
.gate-actions { display: flex; flex-wrap: wrap; gap: var(--s3); margin-top: var(--s5); }
.gate-form [data-msg] { margin-top: var(--s4); }
.gate-close {
  position: absolute; top: var(--s3); right: var(--s3);
  width: 36px; height: 36px;
  border: 0; border-radius: 50%;
  background: none; color: var(--muted);
  font: 400 24px/1 var(--font);
  cursor: pointer;
}
.gate-close:hover { color: var(--ink); background: var(--sheen-soft); }

.dl {
  display: block;
  padding: var(--s6) var(--s5);
  border: 1px solid var(--rule-up);
  background: var(--panel);
  color: var(--ink);
  text-align: center;
  transition: border-color 140ms ease, background 140ms ease;
}
.dl:hover { border-color: var(--orange); background: var(--sheen); color: var(--ink); }

.dl-os {
  display: block;
  font: 400 clamp(24px, 3vw, 32px)/1.1 var(--font);
  color: var(--orange);
  margin-bottom: var(--s2);
}
.dl:hover .dl-os { color: var(--orange-hi); }
.dl-what { display: block; font-size: 17px; color: var(--ink); }
.dl-size {
  display: block; margin-top: var(--s2);
  font: 400 11px/1 var(--mono); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--faint);
}

.beta-actions {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: var(--s3); margin-top: var(--s7);
}

/* The two things a tester still has to be told, at the smallest size that is
   still honest. Below the fold on purpose. */
.beta-fine {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: var(--s8);
  padding-top: var(--s7);
  padding-bottom: var(--s7);
  border-top: 1px solid var(--rule);
}
@media (max-width: 800px) { .beta-fine { grid-template-columns: 1fr; gap: var(--s6); } }
.beta-fine p { font-size: 15px; line-height: 1.7; color: var(--muted); }

/* The command needs air on BOTH sides. Its bottom margin is zero by default, so
   the line explaining Windows and Linux was touching the black block above it
   and reading as part of the terminal output rather than as separate advice. */
.beta-fine .cmd {
  font-size: 12px;
  margin: var(--s5) 0 0;
  padding: var(--s5);
  line-height: 1.75;
}
/* Numbered because they are done in order, and a reader who has done step
   two is looking for step three, not for the paragraph after the one they
   just read. */
.beta-fine .steps { margin: var(--s4) 0 0; padding-left: 1.4em; }
.beta-fine .steps li { margin-top: var(--s4); font-size: 15px; line-height: 1.7; color: var(--muted); }
.beta-fine .steps li::marker { color: var(--orange); font-family: var(--mono); font-size: 12px; }
.beta-fine .steps .cmd { margin: var(--s3) 0; white-space: pre-wrap; }
.beta-fine kbd { font-family: var(--mono); font-size: 0.86em; color: var(--ink-2); }

.beta-fine-note {
  margin-top: var(--s5) !important;
  font-size: 13px !important;
  line-height: 1.75 !important;
  color: var(--faint) !important;
}

.spec { width: 100%; border-collapse: collapse; text-align: left; }
.spec th, .spec td { padding: var(--s3) 0; border-bottom: 1px solid var(--rule); vertical-align: baseline; }
.spec tr:first-child th, .spec tr:first-child td { border-top: 1px solid var(--rule); }
.spec th {
  width: 130px; padding-right: var(--s5); font-weight: 400;
  font: 400 11px/1.5 var(--mono);
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted);
}
.spec td { font-size: 15px; line-height: 1.55; color: var(--ink-2); }

@media (max-width: 560px) {
  .row { grid-template-columns: 1fr; gap: var(--s2); }
  .spec th { width: auto; display: block; padding-bottom: 0; border: 0; }
  .spec td { display: block; padding-top: var(--s2); }
}

/* ---- two-column ruled body --------------------------------------------- */
.cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 1px minmax(0, 1fr);
  gap: clamp(28px, 3.4vw, 48px);
}
.cols > .rule-v { background: var(--rule); }

@media (max-width: 860px) {
  .cols { grid-template-columns: 1fr; gap: var(--s7); }
  .cols > .rule-v { display: none; }
}

/* ---- demos -------------------------------------------------------------- */
/* The sound-example cards, from the plugin repo's docs/examples page, made
   compact: heading, one line, the transport, then notes, passes and macro
   meters on one row. Three across on a desktop. */
.demos-band { margin-bottom: var(--s8); }
.demos-band > .lede { margin-bottom: var(--s6); max-width: 60ch; }
.example-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr)); gap: var(--s4); }
.example { min-width: 0; padding: 16px 18px 14px; border: 1px solid var(--rule); border-radius: 8px; background: var(--panel); transition: border-color .15s; }
.example[data-state="playing"] { border-color: var(--orange-hi); }
.example h3 { margin: 0; font-size: 17px; line-height: 1.2; font-weight: 600; color: var(--ink); }
.example .description { margin: 6px 0 10px; font-size: 13px; line-height: 1.45; color: var(--muted); }
.example .transport { display: none; grid-template-columns: 36px minmax(0, 1fr) 34px; align-items: center; gap: 10px; }
.example.enhanced .transport { display: grid; }
.example audio[controls] { display: block; width: 100%; margin: 8px 0; }
.example.enhanced audio { display: none; }
.example .player-play { width: 36px; height: 36px; }
.example .icon-pause, .example[data-state="playing"] .icon-play { display: none; }
.example[data-state="playing"] .icon-pause { display: block; }
.wave-seek { height: 36px; position: relative; }
.wave-base, .wave-progress { position: absolute; inset: 0; pointer-events: none; }
.wave-base { color: var(--muted); opacity: .5; }
.wave-progress { color: var(--orange-hi); clip-path: inset(0 100% 0 0); }
.wave-seek svg { width: 100%; height: 100%; fill: currentColor; }
.wave-seek input { position: absolute; inset: 0; width: 100%; height: 100%; cursor: pointer; opacity: 0; margin: 0; }
.wave-seek:focus-within { outline: 2px solid var(--orange-hi); outline-offset: 4px; border-radius: 1px; }
.example .player-time { text-align: right; letter-spacing: 0; }
.example-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--rule); font: 400 10px/1 var(--mono); color: var(--muted); }
.notes { display: flex; gap: 4px; }
.notes span { padding: 4px 6px; background: var(--sheen-soft); border-radius: 2px; color: var(--ink-2); }
.notes span.active { background: var(--orange); color: #fff; }
.passes { display: flex; gap: 6px; }
.passes button { padding: 4px 8px; border: 1px solid var(--rule-up); border-radius: 3px; background: transparent; color: var(--muted); font: 400 10px/1 var(--mono); cursor: pointer; }
.passes button[aria-current="step"] { color: var(--ink); border-color: var(--orange-hi); }
.passes button:hover:not(:disabled) { color: var(--ink); }
.passes button:disabled { cursor: default; }
.macros { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin-left: auto; }
.macro-label { font: 400 9px/1 var(--mono); letter-spacing: 0.12em; color: var(--faint); }
.macro { display: inline-flex; align-items: center; gap: 6px; color: var(--ink-2); font-size: 10px; }
.macro-track { width: 26px; height: 3px; background: var(--rule-up); }
.macro-track i { display: block; width: 100%; height: 100%; background: var(--orange-hi); transform: scaleX(0); transform-origin: left; }
.player-error { margin: 8px 0 0; font-size: 13px; color: var(--ink); }
.deets-band { margin-bottom: var(--s8); max-width: 62ch; }
@media (prefers-reduced-motion: reduce) { .example { transition: none; } }

/* ---- demo players ------------------------------------------------------- */
/* The native <audio> pill is somebody else's design system, and these three
   files are the reason anyone is on the page. */

/* ---- about, at the foot of the page ------------------------------------ */
.about {
  margin-top: var(--s8);
  padding-top: var(--s7);
  border-top: 1px solid var(--rule);   /* the rule runs the full width; only the text is narrow */
}
.about .lede, .about .about-contact { max-width: 46em; }
.about-contact { margin-top: var(--s5); color: var(--muted); font-size: 16px; }

.demos { border-top: 1px solid var(--rule); }

/* The provenance line under the players. Quiet, but not hidden: it is the thing
   that makes "straight out of the plugin" a claim rather than a slogan. */
.demos-note {
  margin: var(--s4) 0 0;
  font-size: 14px; line-height: 1.6; color: var(--faint);
  max-width: 46ch;
}

.player {
  display: grid;
  grid-template-columns: 44px 108px minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s4);
  padding: var(--s4) 0;
  border-bottom: 1px solid var(--rule);
}
.player audio { display: none; }

.player-play {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--orange);
  color: var(--on-orange);
  cursor: pointer;
}
.player-play:hover { background: var(--orange-hi); }
.player-play svg { width: 15px; height: 15px; fill: currentColor; }
/* One button, two glyphs: whichever the state does not call for is not drawn. */
.player-play .icon-pause,
.player[data-state="playing"] .player-play .icon-play { display: none; }
.player[data-state="playing"] .player-play .icon-pause { display: block; }

.player-name { font-size: 17px; color: var(--ink); }

.player-rail {
  position: relative;
  height: 22px;
  display: flex;
  align-items: center;
  cursor: pointer;
}
.player-rail::before {
  content: '';
  position: absolute;
  left: 0; right: 0;
  height: 2px;
  background: var(--rule);
}
.player-fill {
  position: absolute;
  left: 0; right: 0;
  height: 2px;
  background: var(--orange);
  transform: scaleX(0);
  transform-origin: left;
}
.player-rail:focus-visible { outline-offset: 6px; }

.player-time {
  font: 400 12px/1 var(--mono);
  letter-spacing: 0.06em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

@media (max-width: 620px) {
  .player { grid-template-columns: 44px minmax(0, 1fr) auto; }
  .player-name { grid-column: 2; }
  .player-rail { grid-column: 1 / -1; }
}

/* ---- notes -------------------------------------------------------------- */
.notes { border-top: 1px solid var(--rule); }
.note { padding: var(--s4) 0; border-bottom: 1px solid var(--rule); }
.note h3 { margin: 0 0 var(--s2); }
.note p { margin: 0; font-size: 15px; line-height: 1.55; color: var(--muted); }

/* ---- the terminal line -------------------------------------------------- */
.cmd {
  margin: var(--s4) 0 0;
  padding: var(--s4);
  background: var(--plate);
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.6;
  color: var(--cmd-ink);
  word-break: break-all;
  max-width: none;
}

figure { margin: var(--s5) 0 0; }
figcaption {
  margin-top: var(--s3);
  font-size: 14px; line-height: 1.6; color: var(--muted); max-width: 52ch;
}

/* ---- forms: signup and the beta door ----------------------------------- */
.form-band {
  margin-top: var(--s8);
  border-top: 1px solid var(--rule);
  padding: var(--s6) 0;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s6);
}
/* The one public action on the page. It gets the plate, the rule in the orange,
   and type a step up — the beta door below stays a quiet outline. */
.form-band-lead {
  border-top: 2px solid var(--orange);
  padding: var(--s7) var(--s6) var(--s7);
  background: var(--band);
}
.form-band-lead .form-say { max-width: 40ch; }
.form-band.form-band-lead .form-say p:first-child { font-size: 30px; line-height: 1.15; color: var(--ink); }
.form-band.form-band-lead .form-say p { font-size: 15px; }
.form-band-lead .btn-pill { padding: 16px 34px; font-size: 15px; }
/* the beta band stacks: heading and line, then the button under them, left */
#beta.form-band-lead { flex-direction: column; align-items: flex-start; gap: var(--s5); }
#beta.form-band-lead .form-say { max-width: 52ch; }
#beta.form-band-lead .btn-row { margin: 0; }

.form-band .form-say { max-width: 44ch; }
.form-band .form-say p:first-child { margin-bottom: var(--s2); font-size: 19px; color: var(--ink); }
.form-band .form-say p { font-size: 14px; color: var(--muted); }

.inline-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s5); }

.field { display: flex; flex-direction: column; gap: var(--s2); min-width: 260px; }
.field-label {
  font: 400 10px/1 var(--mono);
  letter-spacing: 0.2em; text-transform: uppercase; color: var(--faint);
}

input[type="email"] {
  width: 100%;
  padding: 0 0 8px;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--rule-up);
  border-radius: 0;
  font: 400 16px/1.4 var(--font);
  color: var(--ink);
  appearance: none;
}
input[type="email"]::placeholder { color: var(--faint); }
input[type="email"]:focus { outline: 0; border-bottom-color: var(--orange); }

[data-msg] {
  flex-basis: 100%;
  margin: 0;
  font: 400 12px/1.5 var(--mono);
  letter-spacing: 0.06em;
  color: var(--muted);
}
[data-msg][data-state="ok"]  { color: var(--orange); }
[data-msg][data-state="err"] { color: var(--err); }

/* ---- footer ------------------------------------------------------------- */
.connections { padding: var(--s7) 0; border-top: 1px solid var(--rule); }
.connections-intro { color: var(--muted); margin: var(--s4) 0 var(--s5); }
.social-rail {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  list-style: none; padding: 0; margin: 0; border-top: 1px solid var(--rule);
}
.social-rail li { min-width: 0; border-bottom: 1px solid var(--rule); }
.social-link { display: flex; flex-direction: column; gap: var(--s3); padding: var(--s5); }
.social-name { display: flex; justify-content: space-between; gap: var(--s4); color: var(--ink); }
.social-detail { font: 400 12px/1.5 var(--mono); color: var(--muted); }
a.social-link:hover .social-name, a.social-link:hover .social-detail { color: var(--orange); }
a.social-link:focus-visible { outline: 2px solid var(--orange); outline-offset: -2px; }
.social-upcoming .social-name { color: var(--muted); }
@media (max-width: 600px) { .social-rail { grid-template-columns: 1fr; } }

.foot {
  margin-top: var(--s8);
  border-top: 1px solid var(--rule);
  padding: var(--s5) 0 var(--s7);
}
.foot .wrap {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: var(--s4) var(--s6);
}
.foot p {
  margin: 0;
  font: 400 10px/1.7 var(--mono);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--faint);
  max-width: 46ch;
}
.foot-links {
  display: flex; flex-wrap: wrap; gap: var(--s5);
  font: 400 10px/1 var(--mono);
  letter-spacing: 0.16em; text-transform: uppercase;
}
.foot-links a { color: var(--muted); }
.foot-links a:hover { color: var(--orange); }
.foot-fine { flex-basis: 100%; }

@media print {
  /* Paper is white whatever the screen is doing, so this one stays a literal. */
  body { background: #fff; }
  .stage-ring, .skip, .nav, .drivers { display: none; }
}

/* ---- the press kit ------------------------------------------------------ */
.press-hero { padding: var(--s7) 0 var(--s7); text-align: center; }
.press-lede { max-width: 44em; margin: 0 auto var(--s6); font-size: 16px; line-height: 1.6; color: var(--muted); }
.press-sec { max-width: 880px; margin: 0 auto; padding: var(--s6) 0; border-top: 1px solid var(--rule); }
.press-sec p { color: var(--muted); font-size: 16px; line-height: 1.7; }
.press-one { font-size: 20px; color: var(--ink); }
.press-note { font: 400 13px/1.7 var(--font); color: var(--faint); }
.press-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s4); margin-top: var(--s5); }
.press-grid .shot img { display: block; border: 1px solid var(--rule); }
.press-marks { display: grid; gap: var(--s4); margin-top: var(--s5); max-width: 640px; }
.press-marks .shot img { display: block; background: var(--ground-mid); padding: 24px 32px; box-sizing: border-box; border: 1px solid var(--rule); }
/* the icon is already a dark square: no plate behind it, and shown at a sane size */
.press-marks .press-icon { width: 160px; }
.press-marks .press-icon img { padding: 0; background: none; width: 160px; height: 160px; }
@media (max-width: 700px) { .press-grid { grid-template-columns: 1fr; } }

/* ---- the checksum disclosure on the beta page --------------------------- */
.beta-verify { max-width: 46em; margin: var(--s5) auto 0; text-align: left; }
.beta-verify summary {
  text-align: center; cursor: pointer;
  font: 400 11px/1 var(--mono); letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--faint);
}
.beta-verify summary:hover { color: var(--muted); }
.beta-verify .cmd { font-size: 11px; line-height: 1.9; white-space: pre-wrap; word-break: break-all; }

/* ---- the changelog on the beta page ------------------------------------- */
/* One column, read top to bottom, left-aligned. It shipped inside .beta-fine,
   whose two-column grid dealt the heading and the three entries into separate
   cells and let the hero's centring reach the text. */
.beta-log {
  max-width: 880px;
  margin: 0 auto;
  padding: var(--s7) 0;
  text-align: left;
}
.beta-log .beta-fine-note { max-width: none; }
.beta-log .beta-fine-note + .beta-fine-note { margin-top: var(--s5) !important; }
.beta-log strong { color: var(--ink-2); }

/* Date / Version / Detail, one release per row. */
.log-table { width: 100%; border-collapse: collapse; margin-top: var(--s5); }
.log-table th {
  text-align: left; padding: 0 var(--s5) var(--s2) 0;
  font: 400 10px/1 var(--mono); letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--faint); font-weight: 400;
}
.log-table td {
  padding: var(--s4) var(--s5) var(--s4) 0;
  border-top: 1px solid var(--rule);
  font-size: 14px; line-height: 1.7; color: var(--muted);
  vertical-align: top;
}
.log-table td:nth-child(1), .log-table td:nth-child(2) { white-space: nowrap; font-family: var(--mono); font-size: 12px; padding-top: calc(var(--s4) + 3px); }
.log-table td:nth-child(2) { color: var(--ink-2); }
.log-table td:last-child { padding-right: 0; }
@media (max-width: 700px) { .beta-log { overflow-x: auto; } .log-table { min-width: 560px; } }

/* ---- the theme toggle ---------------------------------------------------- */
/* Two glyphs, one shown at a time by which theme is stamped on <html>. */
