/* ============================================================================
   JASI — 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: Instrument Sans for everything, Fira Mono for labels — the plugin's own
   faces. Goldman is display only and used for two words: JASI and KHORLO.

   ALL THREE ARE SELF-HOSTED and these files are byte-for-byte the ones compiled
   into the plugin, copied from Khorlo/public/fonts. That is the point: the site
   and the instrument render the wordmark identically, and neither makes a
   third-party request. The design arrived asking Google Fonts for Hind, which
   was the plugin's body face at the time; the plugin has since moved to
   Instrument Sans, so following the original reasoning means following it here.

   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');
}

/* Variable, 400–700 from one file per subset, so the 400/500/600 the page asks
   for all come out of the same download. */
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/instrumentsans-var-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+2074, U+20AC,
                 U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/instrumentsans-var-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;
  --bar:    #e8e6e2;   /* the top bar, and the only light surface on the site */
  --panel:  rgba(255,255,255,0.07);   /* content panels, over the ground */
  --plate:  #0d0d0c;

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

  --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: #eef1f6;

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

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

  --font: 'Instrument 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;
}

*, *::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%, rgba(255,255,255,0.10), 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: rgba(196,73,26,0.4); }

: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 JASI, 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); }

.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: #fff;
  font: 600 15px/1 var(--font);
  white-space: nowrap;
}
.btn-sm:hover { background: var(--orange-hi); color: #fff; }

.btn-sm-quiet {
  padding: 9px 18px;
  border: 1px solid rgba(25,25,23,0.28);
  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: #fff; }

.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: #fff;
  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: #fff; }
.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: #fff;
  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: #fff; }
.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; }

.row-pair > .shot { display: flex; flex-direction: column; }
.row-pair > .shot > img { flex: 1; min-height: 0; object-fit: contain; object-position: center top; }
/* 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); }

/* ---- 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); }
.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: #d9d7d3;
  color: #2a2926;
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
}
.arrow:hover { background: #fff; }

.dots { display: flex; gap: 10px; }
.dot {
  width: 15px;
  height: 15px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: #f2f0ec;
  cursor: pointer;
}
.dot:hover { background: #fff; }
.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: rgba(13,13,13,0.94);
  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 rgba(238,241,246,0.3);
  border-radius: 50%;
  background: none;
  color: #eef1f6;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}
.zoom-close:hover { border-color: #eef1f6; }

/* ---- 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(--plate);
  overflow: clip;               /* nothing of the frame escapes the dark ground */
  --ring-line: rgba(215,225,240,0.30);
  --ring-mark: #eef1f6;
  color: #eef1f6;
}
.stage-scroll > .eyebrow {
  max-width: var(--page);
  margin: 0 auto;
  padding: 0 var(--gutter);
  color: rgba(238,241,246,0.5);
}

/* 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;
  color: #eef1f6;
  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 { opacity: 1; filter: none; transform: none; transition: none; }
  .js .caption + .caption { margin-top: var(--s4); }
}

/* ---- 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; }

/* Size and checksum under the download rows. Deliberately the quietest type on
   the page: it is reference material a tester consults once, not part of the
   offer. Beta page only. */
.dl-meta {
  margin: var(--s4) 0 0;
  max-width: none;
  font-size: 13px; line-height: 1.7; color: var(--faint);
}
.dl-meta code {
  font-family: var(--mono); font-size: 12px; color: var(--muted);
}
.dl-meta-note { display: block; margin-top: var(--s1); }

.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 -------------------------------------------------------------- */
/* ---- demo players ------------------------------------------------------- */
/* The native <audio> pill is somebody else's design system, and these three
   files are the reason anyone is on the page. */
/* ---- the pitch, under the carousel ------------------------------------- */
/* Centred and narrow, so it reads as a caption to the instrument above rather
   than the start of the body copy. */
.intro {
  margin: var(--s7) auto 0;
  max-width: 34em;
  text-align: center;
}
.intro .lede { max-width: none; }
.intro .khorlo-stage { margin-bottom: var(--s4); }
.intro-tail { margin-top: var(--s4); color: var(--muted); }

/* ---- about, at the foot of the page ------------------------------------ */
.about {
  margin-top: var(--s8);
  padding-top: var(--s7);
  border-top: 1px solid var(--rule);
  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: #fff;
  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: #e8b4a0;
  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(--plate);
}
.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; }

.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: #9a2b12; }

/* ---- footer ------------------------------------------------------------- */
.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 {
  body { background: #fff; }
  .stage-ring, .skip, .nav, .drivers { display: none; }
}
