/* Opus DSP.
 *
 * The same near-black, the same one accent, the same letter-spaced small caps
 * as the instrument this sells. Somebody who has seen the plug-in should feel
 * they are in the same room; somebody who has not should not be able to tell
 * which came first.
 *
 * No framework. There is not enough here to need one, and a page that loads
 * nothing loads instantly.
 *
 * The order below is the order a page is built: the tokens, the reset, the
 * type, the shell, then the pieces, then the pages that need something of their
 * own. Anything used twice lives above the pages; anything used once lives with
 * the page that uses it.
 */

@font-face {
  font-family: 'Space Grotesk';
  src: url('/SpaceGrotesk.woff2') format('woff2');
  font-weight: 300 700;
  font-display: swap;
}

/* --- what everything is made of ------------------------------------------ */

/* The page is paper, and the instrument is the dark thing on it.
 *
 * It used to be near black, and the instrument is near black: the one object
 * this site exists to show was a dark rectangle in the middle of a dark
 * rectangle, and you had to look for where it began. Paper frames it for
 * nothing, and it is what every shop that sells an instrument does.
 *
 * Two sets of the same names. These are the paper ones; .stage further down
 * turns them back over for the band the panel stands on, so every component
 * written for a dark page still works inside it without knowing anything about
 * either. */
:root {
  --paper:     #f4f4f6;   /* the page */
  --raised:    #ffffff;   /* a card lifted off it */
  --raised-2:  #ececed;   /* a band recessed into it */
  --stage:     #0a0a0d;   /* the dark island the instrument sits on */

  --text:      #0e0f13;
  --text-2:    rgba(14, 15, 19, 0.74);
  --dim:       rgba(14, 15, 19, 0.58);
  --faint:     rgba(14, 15, 19, 0.40);
  --ghost:     rgba(14, 15, 19, 0.14);
  --ghost-2:   rgba(14, 15, 19, 0.26);
  --hairline:  rgba(14, 15, 19, 0.09);
  --wash:      rgba(14, 15, 19, 0.035);
  --wash-2:    rgba(14, 15, 19, 0.06);

  /* The instrument's own accent, as three numbers.

     The panel in the page changes them: it warms from cold blue to amber as
     BLOOM opens and says so from inside its frame, and the site follows. Kept
     for the dark band, where a pale colour belongs. */
  --glow-rgb:  140, 232, 255;
  --glow:      rgb(var(--glow-rgb));

  /* And the same accent taken down until it can be read on paper. Nothing pale
     survives on white: the old cyan on this page was a highlighter. Mixed rather
     than fixed, so a warming panel still warms the words beside it. */
  /* Mixed towards black, in oklab, and not towards a navy in sRGB.

     The accent had a dark blue mixed into it, which is fine while the accent is
     blue and turns it to mud the moment the panel warms: amber and navy make
     brown-grey, and the one word on this page that wears the accent went grey.
     Black takes the lightness down and leaves the hue alone, and oklab keeps the
     chroma that sRGB washes out on the way. */
  --brand:     color-mix(in oklab, var(--glow) 58%, #0b0b0e);
  --on-brand:  #ffffff;

  --warm:      #b4581a;
  --bad:       #c0342a;
  --good:      #1c7a4a;
  --ease:      cubic-bezier(0.22, 0.61, 0.36, 1);

  /* One rhythm for the whole site. Sections are tall, and the space between a
     heading and its paragraph is always the same space. */
  --gap:       clamp(54px, 6vw, 96px);

  /* The bar across the top, named because the first screen has to subtract it:
     the bar is in the flow, so a hero of one whole screen makes a first screen of
     one screen plus a bar, and the arrow at the foot of it is below the fold. */
  --bar:       68px;

  /* And the band it really occupies. The bar reserves the hairline it only
     draws once the page has moved, so it stands one pixel taller than --bar.
     Anything meant to reach the very top of the page has to clear both. */
  --bar-box:   calc(var(--bar) + 1px);
  --radius:    14px;

  /* The rule between one part of the page and the next, out of nothing at both ends.
 
     The plug-in's own idiom: it draws the line between its sound and its controls this
     way, because a border cannot carry a gradient and a hard stop at the edge of a
     window looks like a cut. The page and the window are meant to be the same room.
 
     A share of the line's own length rather than a fixed distance, so the same shape
     comes out at eleven hundred and sixty pixels and at three hundred and forty six. */
  --rule-fade: linear-gradient(90deg,
               transparent 0,
               var(--hairline) 12%,
               var(--hairline) 88%,
               transparent 100%);
}

/* The band the instrument stands on, and anything else that wants to be dark.

   The same token names with dark values, so the demo strip, the picker, the
   guide tab and the notice above the panel are all still written once. */
.stage {
  --paper:     var(--stage);
  --raised:    #14141a;
  --raised-2:  #1b1b22;

  --text:      rgba(255, 255, 255, 0.94);
  --text-2:    rgba(255, 255, 255, 0.78);
  --dim:       rgba(255, 255, 255, 0.54);
  --faint:     rgba(255, 255, 255, 0.32);
  --ghost:     rgba(255, 255, 255, 0.15);
  --ghost-2:   rgba(255, 255, 255, 0.3);
  --hairline:  rgba(255, 255, 255, 0.09);
  --wash:      rgba(255, 255, 255, 0.05);
  --wash-2:    rgba(255, 255, 255, 0.08);

  --brand:     var(--glow);
  --on-brand:  #05070a;

  background: var(--stage);
  color: var(--text);
}

* { box-sizing: border-box; }

/* THE PAGE DOES NOT MOVE SIDEWAYS WHEN IT GETS LONGER.
 *
 * Unfolding a licence made the whole site jump. The page became taller than the
 * window, a scrollbar appeared, and where scrollbars take room — everywhere but
 * macOS with its overlay ones — that is fifteen pixels off the width. Measured:
 * 1000 becomes 985, so everything centred slides seven pixels left at the exact
 * moment somebody clicks something. It reads as the site flinching.
 *
 * It was always possible. The account page made it visible, because with its
 * sections folded it is now short enough not to need a bar in the first place.
 *
 * overflow-y: scroll, and not scrollbar-gutter: stable, which is the modern
 * spelling of this and is what I reached for first. Measured in the browser,
 * on this page: the gutter computed as "stable" on the root and the width still
 * went 1000 to 985. Only reserving the track for real held it still. Where
 * scrollbars float over the content this shows nothing and costs nothing. */
html { scroll-behavior: smooth; overflow-y: scroll; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

html, body {
  margin: 0;
  padding: 0;
  background: var(--paper);
  color: var(--text);
  font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 16px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Nothing leans out of the side of the page. CLIPPED, and not hidden.
 *
 * It was hidden, and hidden is a scrollbox: a box with one axis hidden and the
 * other left alone does not keep the other visible, it turns it into auto, so
 * the body became a thing that scrolls. It never actually scrolls - it is
 * exactly as tall as its contents - but position: sticky is measured against
 * the nearest ancestor that scrolls, and the bar was pinned to a box that
 * stands still. The bar therefore left the top of the screen with the page,
 * on every page, while the whole site was written as though it stayed:
 * .moved gives it a background once the page has moved, and half a dozen
 * sections carry a scroll-margin the height of a bar that was sitting over
 * nothing. Measured before the change: at 220 pixels down, the bar was at -220.
 *
 * clip cuts the same overhang and creates no scrollbox, so the bar is measured
 * against the window again. Said twice on purpose: a browser that does not know
 * clip drops the second line and keeps exactly the behaviour it has today. */
body {
  overflow-x: hidden;
  overflow-x: clip;
}

a { color: inherit; text-decoration: none; }

::selection { background: color-mix(in srgb, var(--brand) 22%, transparent); }

:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 3px;
  border-radius: 4px;
}

.wrap { width: min(1160px, calc(100% - 44px)); margin: 0 auto; }

/* --- the glyph -------------------------------------------------------------
 *
 * Two circles crossing, before the name, in the bar and in the footer.
 *
 * A mask and not an image: the file is a black shape on nothing, and painting
 * currentColor through it means the glyph is whatever colour the words next to it
 * are - the bar's ink, the footer's quieter grey, a hover - out of one file. An
 * <img> would need one file per colour and would still be wrong the day a colour
 * changes.
 * ---------------------------------------------------------------------- */

.glyph {
  --glyph: url("/mark.png");

  display: inline-block;
  width: 1.16em;                 /* the file is 498 by 428 */
  height: 1em;
  margin-right: 0.62em;
  vertical-align: -0.08em;

  background-color: currentColor;
  -webkit-mask-image: var(--glyph);
  mask-image: var(--glyph);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

/* In the wordmark it is set against letters that are spaced wide apart, so it
   needs a little more room than the letters do and none of their spacing. */
.mark .glyph { width: 1.5em; height: 1.29em; margin-right: 0.5em; vertical-align: -0.22em; }
.narrow { width: min(760px, calc(100% - 44px)); margin: 0 auto; }

/* --- the words ------------------------------------------------------------ */

h1, h2, h3, h4 { font-weight: 500; letter-spacing: -0.018em; margin: 0; }

h1 { font-size: clamp(38px, 5.4vw, 68px); line-height: 1.04; }
h2 { font-size: clamp(27px, 3.2vw, 40px); line-height: 1.12; }
h3 { font-size: 19px; line-height: 1.3; letter-spacing: -0.01em; }
h4 { font-size: 15px; }

p { margin: 0 0 16px; color: var(--dim); }
p:last-child { margin-bottom: 0; }

.lead {
  font-size: clamp(17px, 1.5vw, 20px);
  line-height: 1.55;
  color: var(--text-2);
  max-width: 54ch;
}

.tiny { font-size: 13px; color: var(--faint); }

.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12.5px;
  letter-spacing: 0.03em;
}

/* Small caps, letter-spaced. The one typographic mannerism the instrument has,
   and the one thing that makes both of these look like the same product. */
.eyebrow {
  display: inline-block;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--faint);
}

.accent { color: var(--brand); }

/* --- the bar across the top ---------------------------------------------- */

header.top {
  position: sticky;
  top: 0;
  z-index: 40;
  border-bottom: 1px solid transparent;
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease),
              backdrop-filter 0.3s var(--ease);
}

/* Only once the page has moved. A bar that is already a bar at the top of a
   page draws a line under a heading for no reason. */
/* Solid, not frosted. Frosted glass over the dark band came out as a slab of
   mud: it takes the colour of whatever is under it, and what is under it is the
   one black thing on the page. */
header.top.moved {
  background: var(--paper);
  border-bottom-color: var(--hairline);
}

header.top .wrap { height: var(--bar); display: flex; align-items: center; gap: 30px; }

/* The maker's name, and nothing done to it.

   It was a word with a gradient bled through it, which is a trick from a
   different decade and the first thing he pointed at. Letter-spaced small caps in
   the page's own ink says the same thing and does not ask to be admired. */
.mark {
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--text);
  white-space: nowrap;
}

nav.top-links { position: relative; margin-left: auto; display: flex; align-items: center; --nav-gap: 26px; gap: var(--nav-gap); }

nav.top-links a {
  position: relative;
  font-size: 13px;
  color: var(--dim);
  transition: color 0.2s var(--ease);
}

nav.top-links a:hover, nav.top-links a.here { color: var(--text); }

/* The line, once, for the row.

   Ink and not the accent: the accent is the instrument's colour and it belongs to the
   light in the hero, not to the furniture. A page you are on and a word under your
   hand are both just "this one", and this page says that in black.

   Where it is and how long are set by main.js. Width and position rather than a
   transform of a fixed box, so the line is the width of the word rather than a
   stretched copy of some other word's line. */
.top-mark {
  position: absolute;
  bottom: -7px;
  left: 0;
  width: 0;
  height: 1px;
  background: var(--text);
  opacity: 0;
  pointer-events: none;
  transition: transform 0.3s var(--ease), width 0.3s var(--ease),
              opacity 0.22s var(--ease);
}

.top-mark.on { opacity: 1; }

/* Nothing to slide from, so it does not slide: the first appearance is a fade, and
   only after that does moving mean anything. */
.top-mark.cold { transition: opacity 0.22s var(--ease); }

/* --- the rest of the pages, on a narrow screen ------------------------------
 *
 * Three lines and a sheet that drops out of the bar.
 *
 * The base rules first and the media queries after, which is not a style point: a
 * display written above a display of equal weight loses to it, and hiding something
 * "for phones" in a block that sits above its own definition is a rule that never
 * fires. That has cost three separate things on this page today.
 * ---------------------------------------------------------------------- */

.menu-key {
  display: none;
  margin-left: 2px;
  padding: 12px 6px;             /* a thumb's worth of target round three thin lines */
  background: none;
  border: 0;
  cursor: pointer;
  flex-direction: column;
  gap: 4px;
  align-items: stretch;
}

.menu-key span {
  display: block;
  width: 17px;
  height: 1.5px;
  border-radius: 1px;
  background: var(--dim);
  transition: transform 0.26s var(--ease), opacity 0.2s var(--ease),
              background 0.2s var(--ease);
}

.menu-key[aria-expanded="true"] span { background: var(--text); }

/* Open: the top and bottom lines cross where the middle one was. */
.menu-key[aria-expanded="true"] span:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
.menu-key[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.menu-key[aria-expanded="true"] span:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }

.menu-sheet {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 39;

  display: flex;
  flex-direction: column;
  padding: 6px 22px 14px;

  background: var(--paper);
  border-bottom: 1px solid var(--hairline);

  /* From under the bar, not from nowhere. */
  transform: translateY(-8px);
  opacity: 0;
  transition: opacity 0.24s var(--ease), transform 0.24s var(--ease);
}

/* An id or a class with a display of its own beats [hidden], every time, so it is
   said here rather than hoped for. */
.menu-sheet[hidden] { display: none; }

.menu-sheet.open { transform: none; opacity: 1; }

.menu-sheet a {
  padding: 13px 0;
  font-size: 15px;
  color: var(--text-2);
  border-top: 1px solid var(--wash-2);
}

.menu-sheet a:first-child { border-top: 0; }
.menu-sheet a.here { color: var(--text); }

/* What the bar is showing at this width, the sheet is not. */
.menu-sheet a.roomy { display: none; }

@media (max-width: 860px) {
  nav.top-links { --nav-gap: 18px; }
  nav.top-links a.wide { display: none; }
  .menu-key { display: flex; }
}

/* The mark is already a way home, so the link that says so is the first thing
   to go when there is no room for it. */
@media (max-width: 600px) {
  header.top .wrap { gap: 16px; }
  nav.top-links { --nav-gap: 16px; }
  nav.top-links a { font-size: 12.5px; }
  nav.top-links a.roomy { display: none; }

  /* The divider went with them. It divided the instruments from what you do
     once you own one, and with the instruments in the sheet it was a line at
     the start of the row with nothing on its left. */
  nav.top-links .top-sep { display: none; }
  .mark { font-size: 13px; letter-spacing: 0.28em; }

  /* The bar has stopped showing it, so the sheet starts. */
  .menu-sheet a.roomy { display: block; }
}

/* --- buttons -------------------------------------------------------------- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  white-space: nowrap;
  /* Ink, not accent. A coloured button on every page was the loudest thing on
     the site and the accent stopped meaning anything; filled ink is what a shop
     that wants to look expensive does, and it inverts by itself inside .stage. */
  color: var(--paper);
  background: var(--text);
  border: 1px solid var(--text);
  border-radius: 9px;
  padding: 0 26px;
  height: 46px;
  cursor: pointer;
  transition: transform 0.14s var(--ease), box-shadow 0.24s var(--ease),
              background 0.2s var(--ease), border-color 0.2s var(--ease), color 0.2s var(--ease);
}

/* Lifted, not lit. A glow around a button is a thing that only reads on black. */
.button:hover { transform: translateY(-1px); box-shadow: 0 6px 18px rgba(14, 15, 19, 0.14); }
.button:active { transform: translateY(0); box-shadow: none; }

.button.quiet {
  color: var(--text);
  background: transparent;
  border-color: var(--ghost);
}

.button.quiet:hover { border-color: var(--ghost-2); box-shadow: none; background: var(--wash); }

.button.danger { color: var(--bad); background: none; border-color: rgba(255, 107, 94, 0.3); }
.button.danger:hover { border-color: var(--bad); box-shadow: none; }

.button.small { height: 34px; padding: 0 16px; font-size: 10px; letter-spacing: 0.16em; }
.button.wide { width: 100%; }
.button[disabled] { opacity: 0.4; pointer-events: none; }

.doing { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }

/* --- fields --------------------------------------------------------------- */

input[type=text], input[type=email], input[type=password], input[type=number],
input[type=url], input[type=file], textarea, select {
  width: 100%;
  font-family: inherit;
  font-size: 15px;
  color: var(--text);
  background: var(--wash);
  border: 1px solid var(--hairline);
  border-radius: 10px;
  padding: 12px 15px;
  outline: none;
  transition: border-color 0.2s var(--ease), background 0.2s var(--ease), box-shadow 0.2s var(--ease);
}

textarea { min-height: 150px; resize: vertical; line-height: 1.6; }

input:focus, textarea:focus, select:focus {
  border-color: color-mix(in srgb, var(--brand) 50%, transparent);
  background: var(--wash-2);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand) 14%, transparent);
}

label.field { display: block; margin-bottom: 16px; }

/* --- reading what you are typing --------------------------------------------
 *
 * The eye sits inside the field, at the right, over the last of the room the input has.
 * Made in main.js, so a page with no script has a plain field and no dead button.
 * ---------------------------------------------------------------------- */

label.field.with-peek { position: relative; }

/* Room for it, so a long password does not run under the eye. */
label.field.with-peek input { padding-right: 46px; }

.peek {
  position: absolute;
  right: 6px;
  bottom: 5px;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--faint);
  cursor: pointer;
  transition: color 0.18s var(--ease), background 0.18s var(--ease);
}

.peek:hover { color: var(--text); background: var(--wash); }
.peek svg { width: 18px; height: 13px; display: block; }

/* Shut, the eye is crossed out; open, the line goes and the eye is an eye. Which way
   round matters: the icon shows what pressing it will do, not what it did. */
.peek[aria-pressed="true"] { color: var(--text); }
.peek[aria-pressed="true"] .cut { opacity: 0; }
.peek .cut { transition: opacity 0.18s var(--ease); }

/* The same little uppercase caption over a group of controls that is not one
   control. Three radio buttons cannot live inside a <label>, and the caption
   above them still has to look like every other caption on the form. */
label.field > span, .field > .field-say {
  display: block;
  margin-bottom: 7px;
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--faint);
}

div.field { margin-bottom: 16px; }

/* --- the box on the feedback page ----------------------------------------- */

.say { max-width: 52ch; }
.say textarea { min-height: 190px; }

/* The field no hand fills in. Off the page rather than display:none, which some
   of what fills these in knows to skip. */
.nobody {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* --- notices -------------------------------------------------------------- */

.trouble, .note {
  border-radius: 10px;
  padding: 13px 16px;
  font-size: 14px;
  margin-bottom: 20px;
}

.trouble { border: 1px solid rgba(255, 107, 94, 0.34); background: rgba(255, 107, 94, 0.07); color: var(--bad); }
.note { border: 1px solid var(--hairline); background: var(--wash); color: var(--dim); }

/* --- sections ------------------------------------------------------------- */

/* Half a gap each side, so two sections meeting come to one gap between them
   rather than two. A full gap top and bottom is what made the page read as a
   sequence of things that had each been abandoned. */
section { padding: calc(var(--gap) * 0.55) 0; position: relative; }

.section-head { max-width: 60ch; margin-bottom: 46px; }
.section-head h2 { margin-bottom: 16px; }

/* A hairline that fades out at both ends, so a section boundary reads as a
   seam rather than as a table border. */
.seam {
  height: 1px;
  border: 0;
  margin: 0;
  background: linear-gradient(90deg, transparent, var(--hairline) 22%, var(--hairline) 78%, transparent);
}

/* --- things that arrive as you reach them ---------------------------------

   Held down and released by the observer in main.js. Everything is visible
   without JavaScript: the class is added, never removed, and the starting state
   only exists once the script has said it is in charge.
   ------------------------------------------------------------------------- */

html.js .rise { opacity: 0; transform: translateY(18px); }

html.js .rise.here {
  opacity: 1;
  transform: none;
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
}

/* The rescue, when the transition above turns out not to run at all. Put on by
   main.js, and stronger than everything else here on purpose: it is the last
   thing standing between a decoration and a blank page. */
html.plain .rise {
  opacity: 1 !important;
  transform: none !important;
  transition: none !important;
}

html.js .rise.d1.here { transition-delay: 0.08s; }
html.js .rise.d2.here { transition-delay: 0.16s; }
html.js .rise.d3.here { transition-delay: 0.24s; }

/* --- the hero ------------------------------------------------------------- */

.hero { position: relative; padding: clamp(64px, 11vh, 118px) 0 0; overflow: hidden; }

.hero h1 { max-width: 15ch; margin-bottom: 22px; }
/* The word stays in the page's own ink.

   It wore the accent, which meant it changed colour whenever the panel below it
   warmed, and a headline that changes colour on its own is a headline arguing with
   the picture behind it. The aurora is where the colour on this screen belongs. */
.hero h1 em { font-style: normal; color: inherit; }

.hero .lead { margin-bottom: 30px; }

/* The instrument itself, running, above the fold.

   No frame, no border, no rounded corner of ours around the panel: it is a
   window that already has all three, on a background the same near-black as
   this page. Anything drawn around it would be a second window around the
   first. */
.hero-stage {
  position: relative;
  margin: 44px 0 0;
  padding: 0;
}

.hero-stage img { display: block; width: 100%; height: auto; }

/* The strip above it: what you can do with the panel, and which sound is in it.

   Two things and no more. It used to carry four, in four different voices, and
   read as a toolbar somebody had run out of room for. */
/* What the panel is, before anybody plays it.

   Not a yellow box with a triangle in it. This page is dark and quiet, so an
   alarm would be the loudest thing on it, and what is being said is not an alarm:
   it is a fact about the thing directly underneath. The first clause is in the
   page's own ink so the eye lands on it, and the rest is the same grey as the line
   under the panel, because it is an aside and not a warning label. */
/* AND SET APART, because saying it in the same voice was saying nothing.
 
   It was a grey line at twelve and a half sitting directly over a grey line at
   thirteen and a half, both starting on the same pixel. Two sentences of the same
   weight in the same colour, one under the other: nothing said which was the caution
   and which was the invitation, so the eye took them as one paragraph and skipped
   both.
 
   Nothing here is louder. It is a shape instead - a shade of wash under it, a hairline
   round it, its own indent - so it reads as a note ABOUT the panel rather than as a
   line beside the one that says how to play it. On a stage this dark, five per cent of
   white is barely a tint, and barely a tint is all a shape needs. */
.demo-warn {
  /* The same box as the strip under it, so the two start on the same pixel. A
     max-width here instead would have centred a narrower block inside a wider one and
     indented this line by a hundred and twenty pixels against everything else on the
     stage. */
  width: min(100%, 1100px);
  margin: 0 auto 20px;
  padding: 12px 16px;

  background: var(--wash);
  border: 1px solid var(--hairline);
  border-radius: 10px;

  font-size: 12.5px;
  line-height: 1.6;
  color: var(--dim);
}

/* And the measure on the words rather than on the box: eleven hundred pixels of
   twelve-point type is a line the eye loses its place in. */
.demo-warn span { display: block; max-width: 860px; }

.demo-warn b { font-weight: 400; color: var(--text); }

.demo-bar {
  width: min(100%, 1100px);
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 24px;
  flex-wrap: wrap;
  padding: 0 2px 14px;
}

.demo-say {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: 13.5px;
  color: var(--dim);
}

.demo-say kbd {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 8px;
  border: 1px solid var(--ghost);
  border-radius: 6px;
  background: var(--wash);
  font-family: inherit;
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text);
}

.demo-say kbd svg { width: 11px; height: 11px; display: block; }

/* Wraps, because on a phone it does not fit: the tempo, the sound and the button
   that plays the sound raw come to more than a phone is wide, and the last of them
   was hanging off the right edge with its play button cut in half. */
.demo-pick {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 18px;
  margin-left: auto;
}

/* The tempo. There is no host here to be asked for one, and it is a setting
   nobody came to use, so it is small and it says what it is. */


/* The tempo, in ink.

   It was the instrument's accent on a grey rule, which made a cyan dot the brightest
   thing in a section that has an instrument in it. Ink and paper instead: behind the
   handle the page's own black, in front of it the same hairline grey as every other
   rule here, and the handle a black dot with a ring of paper round it so it reads as
   sitting ON the track rather than threaded through it.

   The two halves are a gradient with a stop written from main.js, because there is no
   property for the part of a range before its thumb that every engine has. */


/* Under the hand it grows a little, which is all the movement it has. */


/* Which sound is in it. A name and a chevron, so it reads as a thing that
   opens rather than as a word beside a button that might do anything. */
.demo-sound { position: relative; }

/* --- the sound before any of it -------------------------------------------

   Beside the picker, because it is about the thing the picker chose: the same
   file, before the instrument has had it. A word and a single round button —
   there is nothing to configure here, and a transport with a scrub bar and a
   time would be a second player on the page competing with the one below it.
   ------------------------------------------------------------------------ */

.demo-original {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
}

.demo-original > button {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--hairline);
  background: var(--wash);
  color: var(--text);
  cursor: pointer;
  transition: border-color 0.2s var(--ease), background 0.2s var(--ease),
              color 0.2s var(--ease);
}

.demo-original > button:hover {
  border-color: var(--ghost-2);
  background: var(--wash-2);
}

.demo-original > button svg { width: 12px; height: 12px; }

/* One button, two marks, and only ever one of them showing: a play that turns
   into a stop is a control somebody can use without reading it. */
.demo-original > button .stop { display: none; }
.demo-original > button.playing .play { display: none; }
.demo-original > button.playing .stop { display: block; }

.demo-original > button.playing {
  color: var(--brand);
  border-color: color-mix(in srgb, var(--brand) 55%, transparent);
  background: color-mix(in srgb, var(--brand) 12%, transparent);
}

.demo-sound > button {
  display: flex;
  align-items: center;
  gap: 9px;
  font-family: inherit;
  font-size: 13px;
  color: var(--text);
  background: var(--wash);
  border: 1px solid var(--hairline);
  border-radius: 8px;
  padding: 7px 12px;
  cursor: pointer;
  transition: border-color 0.2s var(--ease), background 0.2s var(--ease);
}

.demo-sound > button:hover { border-color: var(--ghost-2); }

.demo-sound > button svg { width: 12px; height: 12px; color: var(--faint); }

.demo-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 30;
  min-width: 180px;
  padding: 6px;
  border: 1px solid var(--hairline);
  border-radius: 10px;

  /* Paper, like the strip it drops out of. It was a near-black sheet with a heavy
     shadow, from when this whole band was dark: on the light band it read as a hole
     with the names of the presets nearly invisible inside it. */
  background: var(--raised);
  box-shadow: 0 18px 40px rgba(14, 15, 19, 0.13);
}

.demo-menu[hidden] { display: none; }

.demo-menu button {
  display: block;
  width: 100%;
  font-family: inherit;
  font-size: 13.5px;
  text-align: left;
  color: var(--text);
  background: none;
  border: 0;
  border-radius: 7px;
  padding: 8px 11px;
  cursor: pointer;
  transition: background 0.15s var(--ease);
}

.demo-menu button:hover { background: var(--wash-2); }

/* The panel is eleven hundred pixels wide and is not going to be anything else,
   so it is scaled as a whole rather than reflowed. The box keeps its shape and
   the frame inside it is scaled from the top left corner to fill it. */
/* Never wider than the panel really is. Scaling a canvas interface up makes
   every hairline in it soft, and the whole look of this instrument is hairlines
   drawn on whole pixels. Down is fine; up is not. */
.demo-hold {
  position: relative;
  width: min(100%, 1100px);
  margin: 0 auto;
  aspect-ratio: 1100 / 732;
  overflow: hidden;
  border-radius: 12px;
  background: #050508;
}

/* The way out of the frame, on its own line above the panel.

   Not over the instrument: a shop control sitting inside something meant to
   read as hardware is a shop control in the wrong place. Out here it is a line
   about this page.

   Written in tokens, not in colours. It used to say white, which was true when
   it sat inside .stage and invisible the moment it moved out onto paper — the
   whole point of the two token sets further up is that a component like this
   never needs to know which of the two it landed on.

   Flush with the panel's left edge, which is not the column's: the panel is
   min(100%, 1100px) centred, so past 1100 it steps inwards and leaves a margin
   this used to sit out in. The box round it repeats that sizing rather than
   guessing the margin, so the two edges are the same edge at every width. */
.demo-above {
  width: min(100%, 1100px);
  margin: 0 auto 9px;
}

.demo-full {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 11px;

  border: 1px solid var(--ghost);
  border-radius: 8px;
  background: var(--wash);

  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--dim);

  transition: color 0.18s var(--ease, ease),
              border-color 0.18s var(--ease, ease),
              background-color 0.18s var(--ease, ease);
}

.demo-full svg { width: 12px; height: 12px; flex: 0 0 12px; }

.demo-full:hover,
.demo-full:focus-visible {
  color: var(--text);
  border-color: var(--ghost-2);
  background: var(--wash-2);
}

/* On a phone the panel is a covered postage stamp and there is nothing to open
   it for — see the note on the small-screen still in home.ejs. */
@media (max-width: 860px) {
  .demo-above { display: none; }
}

/* A line travelling round the edge of it.

   The panel is a black rectangle on a black page and its border disappears; you
   cannot see where the instrument stops. This draws the edge, and moves, so it
   reads as something running rather than a frame around a picture.

   Made of one conic gradient turned by an angle, masked down to the border
   itself: the fill is punched out, leaving a ring one pixel wide. Both ends of
   the bright part fade to nothing, so what goes round is a stroke with soft
   ends rather than a dot chasing its tail. */
@property --turn {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

.demo-hold::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;

  background: conic-gradient(from var(--turn) at 50% 50%,
              transparent 0deg,
              color-mix(in srgb, var(--brand) 6%, transparent) 18deg,
              color-mix(in srgb, var(--brand) 55%, transparent) 44deg,
              color-mix(in srgb, var(--brand) 6%, transparent) 70deg,
              transparent 96deg,
              transparent 360deg),
              linear-gradient(var(--wash-2), var(--wash-2));

  transition: none;

  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;

  animation: demo-turn 7s linear infinite;
}

@keyframes demo-turn { to { --turn: 360deg; } }

/* --- listening, or not ----------------------------------------------------

   A panel inside a frame that does not have the keys looks exactly like a panel
   that is broken: you press a letter, nothing happens, and there is nothing on
   screen to say the letter went to the page around it instead. Two marks for the
   two states, and between them there is nothing left to guess at.
   ------------------------------------------------------------------------ */

/* It has them: a ring, held, over the one that travels. Enough to notice on the
   way past and not enough to be a border. */
.demo-hold.listening {
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--brand) 55%, transparent),
              0 0 26px -6px color-mix(in srgb, var(--brand) 35%, transparent);
}

/* It does not: a word over the top of it. Not a wall — the panel is still there,
   still moving, and one click puts the keys where they belong. */
.demo-hold::after {
  content: "click the simulation, then play the keys";
  position: absolute;
  inset: 0;
  z-index: 3;
  display: grid;
  place-items: center;
  border-radius: inherit;
  pointer-events: none;

  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-2);
  text-shadow: 0 1px 14px rgba(0, 0, 0, 0.9);

  background: radial-gradient(120% 90% at 50% 50%,
              rgba(5, 5, 8, 0.34) 0%, rgba(5, 5, 8, 0.72) 100%);

  opacity: 0;
  transition: opacity 0.32s var(--ease);
}

/* Only once it is running. Before anybody has pressed start, the frame has a sheet
   of its own saying so, and this would be a second instruction over the top of it. */
.demo-hold.started:not(.listening)::after { opacity: 1; }

/* The global reduced-motion rule shortens every animation to a hundredth of a
   millisecond, which would spin this faster than anything on screen rather than
   stopping it. Said again, plainly. */
@media (prefers-reduced-motion: reduce) {
  .demo-hold::before { animation: none !important; }
}

.demo-hold iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 1100px;
  height: 732px;
  border: 0;
  transform-origin: top left;
  transform: scale(var(--demo-scale, 1));
}

/* The way into the guide, above the panel because that is where the hand is. */
.demo-tip {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 12.5px;
  letter-spacing: 0.01em;
  color: var(--faint);
}

.demo-tip kbd {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 8px;
  border: 1px solid var(--ghost);
  border-radius: 6px;
  background: var(--wash);
  font-family: inherit;
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim);
}

.demo-tip kbd svg { width: 11px; height: 11px; display: block; }

/* The guide, under the instrument. Same width as the panel and the same near
   black, so the two read as one thing with a seam rather than as a panel and a
   box under it. Shut to begin with, and it scrolls inside itself when it is
   open: a reference that pushes the rest of the page down by four screens is
   one nobody scrolls past. */
/* Paper, like the band it stands on.

   It was a near-black box because the band was near-black. The band is the page's own
   colour now and this is a list of words about the panel rather than a second window:
   a card, with a hairline round it. The document inside it is served the instrument's
   stylesheet in its light theme - see guide.html in make-demo - or every label in it
   would be white on white. */
.demo-guide {
  width: min(100%, 1100px);
  margin: 14px auto 0;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  overflow: hidden;
  background: var(--raised);
}

.demo-guide-tab {
  display: flex;
  align-items: baseline;
  gap: 14px;
  width: 100%;
  padding: 14px 18px;
  font-family: inherit;
  text-align: left;
  color: var(--text);
  background: none;
  border: 0;
  cursor: pointer;
}

.demo-guide-tab span {
  font-size: 13px;
  letter-spacing: 0.02em;
}

.demo-guide-tab em {
  font-style: normal;
  font-size: 11.5px;
  color: var(--faint);
}

.demo-guide-tab svg {
  width: 14px;
  height: 14px;
  margin-left: auto;
  align-self: center;
  color: var(--dim);
  transition: transform 0.34s var(--ease);
}

.demo-guide-tab:hover svg { color: var(--text); }
.demo-guide.open .demo-guide-tab svg { transform: rotate(180deg); }
/* No line under the tab when it is open. The list below it starts with its own field
   and its own row of chips, which is edge enough; a rule as well was two edges saying
   the same thing half a pixel apart. */

/* Height rather than display, so it opens and shuts rather than appearing. */
.demo-guide-body {
  height: 0;
  overflow: hidden;
  transition: height 0.4s var(--ease);
}

.demo-guide.open .demo-guide-body { height: 620px; }

.demo-guide-body iframe {
  display: block;
  width: 100%;
  height: 620px;
  border: 0;
}

/* Over the frame, so the frame never sees a finger.

   Not a wall of paint: enough to say the panel is not for touching and little
   enough to leave it visible, which is the only reason it is there. */
.demo-desktop {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 4;
  place-items: end center;
  padding: 0 18px 16px;

  background: linear-gradient(to bottom,
              color-mix(in srgb, #05050a 12%, transparent) 0%,
              color-mix(in srgb, #05050a 34%, transparent) 46%,
              color-mix(in srgb, #05050a 88%, transparent) 100%);
}

.demo-desktop p {
  margin: 0;
  max-width: 34ch;
  text-align: center;
  font-size: 12.5px;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.62);
}

.demo-desktop b {
  display: block;
  font-weight: 400;
  font-size: 14px;
  color: rgba(255, 255, 255, 0.96);
}

/* --- the panel on a phone -------------------------------------------------
 *
 * Shown, and not for playing.
 *
 * There was a photograph of it here, on the grounds that eleven hundred pixels
 * shrunk to a phone is an illegible postage stamp. It is illegible - but a
 * photograph goes out of date silently, and this cannot: it is the same panel
 * every other screen gets, one fixed-size document scaled to whatever room the
 * page has, so what a phone shows is always what the instrument looks like now.
 *
 * Everything that is for playing goes: the strip that chooses a sound, the notice
 * about it being a simulation, the guide it is a guide to. What is left is the
 * shape of the instrument and one line saying where to go to touch it.
 * ---------------------------------------------------------------------- */
@media (max-width: 900px) {
  .demo-warn, .demo-bar, .demo-guide { display: none; }

  /* Inset with everything else, and not bled to the edges.

     It was full width for the eleven per cent of extra instrument that buys, and
     eleven per cent is not worth a panel pressed against both sides of the screen:
     it now begins where the headings begin. */

  .demo-desktop { display: grid; }

  /* The word about holding a key belongs to somebody who can. */
  .demo-hold::after { content: none; }
}


@media (max-width: 700px) {
  .hero-stage { margin-top: 34px; }
}

/* No glow behind the hero. It was there to stop a black page reading as flat,
   and on paper the same thing is a grey smudge under the navigation. */

/* The one tick on the site, and it decides whether a payment can start.

   Set out as a line of prose with a box at the head of it rather than as a control with a
   label: what matters is that it is read, and a checkbox with four words beside it is not
   read. Full size box, because it is the thing being pressed. */
.agree {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 12px;
  align-items: start;
  max-width: 52ch;
  margin: 0 0 20px;
  cursor: pointer;
}

.agree input {
  width: 18px;
  height: 18px;
  margin: 2px 0 0;
  accent-color: var(--text);
  cursor: pointer;
}

.agree span {
  font-size: 13px;
  line-height: 1.6;
  color: var(--dim);
}

.agree:hover span { color: var(--text-2); }

/* .agree.under lived here: the small version of the tick, for the one under the Buy button.
   That tick is gone - the agreement to immediate delivery and the waiver of the fourteen days
   are in the terms now, the way Arturia and everyone else selling direct in Europe does it -
   and its only user went with it. The plain .agree above stays: signup uses it for accepting
   the terms, where a deliberate act is the whole point of the page. */

/* The two words that have to be readable as links, because accepting something you cannot
   open is not accepting it. Underlined rather than coloured: the accent belongs to the
   instrument, and a link in a sentence is a link. */
.agree a {
  color: var(--text);
  text-decoration: underline;
  text-decoration-color: var(--ghost-2);
  text-underline-offset: 3px;
}

.agree a:hover { text-decoration-color: var(--text); }

/* --- the pages nobody reads until they need them --------------------------
 *
 * Terms, privacy, the legal notice. Long prose, and the only long prose on this site.
 *
 * Set narrower than everything else and a little larger than the small print, because
 * these are the pages somebody reads when they are worried: sixty-six characters to a
 * line, headings that can be scanned for the paragraph that matters, and no clever
 * layout of any kind. Nothing here is designed to be skipped.
 * ---------------------------------------------------------------------- */

.legal-page { padding: clamp(48px, 7vh, 84px) 0 var(--gap); }

.legal-page h1 { margin-bottom: 18px; }
.legal-page .lead { margin-bottom: 8px; }

.legal { margin-top: 44px; }

.legal h2 {
  font-size: 19px;
  letter-spacing: -0.01em;
  margin: 44px 0 14px;
  padding-top: 22px;
  border-top: 1px solid var(--hairline);
}

.legal h2:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

.legal h3 {
  font-size: 14px;
  margin: 26px 0 10px;
  color: var(--text);
}

.legal p {
  font-size: 15px;
  line-height: 1.72;
  color: var(--text-2);
  max-width: 66ch;
  margin: 0 0 15px;
}

.legal b { font-weight: 400; color: var(--text); }

.legal a { color: var(--text); text-decoration: underline; text-decoration-color: var(--ghost-2); text-underline-offset: 3px; }
.legal a:hover { text-decoration-color: var(--text); }

.legal-list { max-width: 66ch; margin: 0 0 16px; padding-left: 20px; }
.legal-list li { font-size: 15px; line-height: 1.7; color: var(--text-2); margin-bottom: 9px; }

/* The publisher's own facts, as a list of pairs rather than a paragraph: somebody
   looking for an address is looking for a line, not for a sentence. */
.legal-facts { margin: 0 0 8px; }

.legal-facts > div {
  display: grid;
  grid-template-columns: 210px 1fr;
  gap: 14px;
  padding: 11px 0;
  border-bottom: 1px solid var(--hairline);
}

.legal-facts dt {
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--faint);
  padding-top: 3px;
}

.legal-facts dd { margin: 0; font-size: 15px; color: var(--text-2); }

.legal-facts .said-no { color: var(--bad); }

@media (max-width: 620px) {
  .legal-facts > div { grid-template-columns: 1fr; gap: 4px; }
}

/* --- panes and grids ------------------------------------------------------ */

.pane {
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 28px 30px;
}

.grid { display: grid; gap: 18px; }
.g2 { grid-template-columns: repeat(2, 1fr); }
.g3 { grid-template-columns: repeat(3, 1fr); }
.g4 { grid-template-columns: repeat(4, 1fr); }

@media (max-width: 900px) { .g3, .g4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .g2, .g3, .g4 { grid-template-columns: 1fr; } }

/* --- the parts, each one standing beside its words -------------------------

   No frame, no card, no screenshot. What is on the right of each row is the
   instrument's own control, in an iframe of its own so that it can be served
   the instrument's own stylesheet: see demo/guide/parts.js. It is the same
   knob, at the same size, and it cannot go out of date.
   ------------------------------------------------------------------------- */

.parts .part-row {
  display: grid;
  gap: clamp(24px, 4vw, 56px);
  align-items: center;
  padding: clamp(30px, 4vw, 46px) 0;
  border-top: 1px solid var(--hairline);
}

.parts .part-row:first-child { border-top: 0; padding-top: 0; }

@media (min-width: 900px) {
  .parts .part-row { grid-template-columns: minmax(0, 34ch) minmax(0, 1fr); }
}

.part-words h2 { font-size: clamp(21px, 2vw, 27px); margin-bottom: 12px; }
.part-words p { margin: 0; font-size: 14.5px; max-width: 44ch; }

.part-frame {
  display: block;
  width: 100%;
  border: 0;
  background: none;
}

/* --- a list of what is included ------------------------------------------- */

.ticks { list-style: none; margin: 0; padding: 0; display: grid; gap: 13px; }

.ticks li {
  position: relative;
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: 12px;
  align-items: start;
  font-size: 14.5px;
  color: var(--text-2);
}

/* Drawn rather than typed. A check in a font is whatever the font feels like
   that day, and on Windows it is often a box.

   The tick is a mask rather than a picture of a tick, so its colour is the
   accent rather than a blue baked into a data URI — which is the one place a
   hard-coded colour could not have followed the instrument. */
.ticks li::before {
  content: "";
  width: 18px; height: 18px;
  margin-top: 3px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--brand) 10%, transparent);
}

.ticks li::after {
  content: "";
  position: absolute;
  width: 18px; height: 18px;
  margin-top: 3px;
  background: var(--brand);
  -webkit-mask: var(--tick) center / 18px 18px no-repeat;
  mask: var(--tick) center / 18px 18px no-repeat;
}

.ticks li {
  --tick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18'%3E%3Cpath d='M5 9.4l2.6 2.6L13 6.4' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

.ticks li.no { color: var(--faint); }

.ticks li.no::after { display: none; }

.ticks li.no::before {
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18'%3E%3Cpath d='M6 6l6 6M12 6l-6 6' fill='none' stroke='rgba(255,255,255,0.3)' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E") center / 18px 18px no-repeat,
    var(--wash);
}

/* --- the price -------------------------------------------------------------

   A number and a sentence. It used to be a bordered card with a lit edge, a
   pill in the corner and seven ticks, which is a lot of furniture around one
   figure that never changes.
   ------------------------------------------------------------------------- */

/* The price and the closing call are one thought, so they are not two screens
   apart. */
.price { padding-bottom: 0; }

.price-line { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; margin: 22px 0 30px; }

.price-line b {
  font-size: clamp(38px, 5vw, 54px);
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 1;
}

.price-line span { font-size: 14px; color: var(--faint); }

/* The figure the price is set against.

   After the real one and not before it: the row is baseline-aligned and the big
   number is what the layout hangs off, so a small number to its left is the
   first thing the eye lands on and the last thing it should be.

   The rule through it is drawn thin and in the text's own colour rather than
   left to the browser, which draws it at the font's own thickness — at this size
   that is heavy enough to read as a strike through a mistake rather than as a
   price that no longer applies. */
.price-was {
  font-size: clamp(19px, 2.2vw, 24px);
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--faint);

  text-decoration: line-through;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--ghost-2);
}

.price-figure { display: flex; align-items: baseline; gap: 10px; margin: 14px 0 4px; }
.price-figure b { font-size: clamp(40px, 5vw, 54px); font-weight: 500; letter-spacing: -0.03em; line-height: 1; }
.price-figure span { font-size: 13px; color: var(--faint); }

.price-note { font-size: 13px; color: var(--faint); margin-bottom: 26px; }

.badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 11px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--brand) 30%, transparent);
  background: color-mix(in srgb, var(--brand) 7%, transparent);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--brand);
  white-space: nowrap;
}

.badge.plain { border-color: var(--ghost); background: none; color: var(--faint); }

/* --- questions ------------------------------------------------------------ */

.faq { border-top: 1px solid var(--hairline); }

.faq details {
  border-bottom: 1px solid var(--hairline);
  padding: 4px 0;
}

.faq summary {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 22px 2px;
  cursor: pointer;
  list-style: none;
  font-size: 16.5px;
  color: var(--text);
  transition: color 0.2s var(--ease);
}

.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--brand); }

/* The sign at the end of the row, which turns rather than swaps. */
.faq summary::after {
  content: "";
  margin-left: auto;
  flex: 0 0 auto;
  width: 13px; height: 13px;
  background:
    linear-gradient(var(--faint), var(--faint)) center / 13px 1px no-repeat,
    linear-gradient(var(--faint), var(--faint)) center / 1px 13px no-repeat;
  transition: transform 0.3s var(--ease), background 0.3s var(--ease);
}

.faq details[open] summary::after {
  transform: rotate(135deg);
  background:
    linear-gradient(var(--brand), var(--brand)) center / 13px 1px no-repeat,
    linear-gradient(var(--brand), var(--brand)) center / 1px 13px no-repeat;
}

.faq .answer { padding: 0 60px 24px 2px; }
.faq .answer p { font-size: 15px; }

@media (max-width: 620px) { .faq .answer { padding-right: 10px; } }

/* --- specifications ------------------------------------------------------- */

/* Lines, not a table of boxes. Six facts nobody reads twice do not need
   nineteen borders around them. */
.specs { margin: 30px 0 0; }

.specs > div {
  display: grid;
  grid-template-columns: 130px minmax(0, 1fr);
  gap: 20px;
  align-items: baseline;
  padding: 13px 0;
  border-top: 1px solid var(--hairline);
}

.specs > div:first-child { border-top: 0; }

.specs dt {
  font-size: 9.5px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--faint);
}

.specs dd { margin: 0; font-size: 14.5px; color: var(--dim); }

@media (max-width: 560px) {
  .specs > div { grid-template-columns: minmax(0, 1fr); gap: 4px; }
}

/* --- the closing call ----------------------------------------------------- */

.closer {
  position: relative;
  text-align: center;
  padding: clamp(60px, 7vw, 104px) 0;
  overflow: hidden;
}

.closer h2 { margin-bottom: 18px; }
.closer .lead { margin: 0 auto 30px; }
.closer .doing { justify-content: center; }

/* A shelf of installers: rows with a hairline between them, and nothing
   around them. */
.shelf + .shelf { margin-top: 10px; }

/* --- rows ----------------------------------------------------------------- */

.row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 15px 0;
  border-top: 1px solid var(--hairline);
}

.row:first-of-type { border-top: 0; }

/* A row whose text runs to several lines, where centring the button beside it
   leaves it floating in the middle of them. */
.row--top { align-items: flex-start; }
.row .grow { flex: 1 1 auto; min-width: 0; }
.row .said { font-size: 12.5px; color: var(--faint); }

/* The mark of the machine a row is for.

   Twenty-six pixels: as tall as the two lines of words beside it, so the row reads
   as one thing with a mark on it rather than as words with a speck in front. At the
   size of the type it was a speck. Held back from the text colour rather than as
   faint as the small print, because at this size faint is grey mush. */
.mark-plat {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  color: var(--dim);
}

body.light .mark-plat { color: rgba(0, 0, 0, 0.55); }

/* --- the account ---------------------------------------------------------- */

.account-head {
  display: flex;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: 34px;
}

/* ONE SUBJECT AT A TIME, AND A LIST DOWN THE LEFT.
 *
 * The page was one scroll holding four unrelated things: the keys, three cards
 * about where to download them, the name, the password. Each read as the end of
 * the page, and somebody who came to free a machine went past a password form
 * to get there.
 *
 * Two hundred pixels for the list. Wide enough for the longest word in it with
 * room to spare, narrow enough that the panel beside it keeps the measure a
 * paragraph wants. The list holds still while the panel scrolls, because it is
 * a place and not a part of the content. */
.account-layout {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr);
  gap: 30px;
  align-items: start;
}

.account-nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
  position: sticky;
  top: 96px;
}

.account-nav-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 14px;
  border: 1px solid transparent;
  border-radius: 10px;
  font-size: 14.5px;
  color: var(--dim);
  transition: color 0.18s var(--ease), background 0.18s var(--ease),
              border-color 0.18s var(--ease);
}

.account-nav-item:hover { color: var(--text); background: var(--wash); }

/* The one you are on, in the site's own way of saying so: the brand, barely,
   behind it and around it. The same two values the chips use on the download
   page and the admin one, so a third idea of "this one" does not appear here. */
.account-nav-item.on {
  color: var(--text);
  border-color: color-mix(in srgb, var(--brand) 46%, transparent);
  background: color-mix(in srgb, var(--brand) 7%, transparent);
}

/* How many licences, beside the word. A number somebody can read from the list
   without opening it, and nothing else in here carries one. */
.account-nav-count {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--faint);
}

.account-nav-item.on .account-nav-count { color: var(--dim); }

/* Under a certain width the list goes on top, laid out as a row that scrolls
   sideways if it has to. Two columns of two hundred pixels on a phone would
   leave the panel too narrow to hold a licence key. */
@media (max-width: 760px) {
  .account-layout { grid-template-columns: 1fr; gap: 18px; }

  .account-nav {
    position: static;
    flex-direction: row;
    gap: 6px;
    overflow-x: auto;
    scrollbar-width: none;
    padding-bottom: 2px;
  }

  .account-nav::-webkit-scrollbar { display: none; }
  .account-nav-item { flex: 0 0 auto; padding: 8px 14px; }
}

.avatar {
  width: 54px; height: 54px;
  flex: 0 0 auto;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 19px;
  letter-spacing: 0.04em;
  color: var(--brand);
  background: color-mix(in srgb, var(--brand) 9%, transparent);
  border: 1px solid color-mix(in srgb, var(--brand) 24%, transparent);
}

.card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 22px;
  flex-wrap: wrap;
}

.key {
  display: flex;
  align-items: center;
  gap: 14px;
  background: color-mix(in srgb, var(--brand) 5%, transparent);
  border: 1px solid color-mix(in srgb, var(--brand) 22%, transparent);
  border-radius: 11px;
  padding: 14px 18px;
}

.key code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 17px;
  letter-spacing: 0.16em;
  color: var(--brand);
  user-select: all;
  overflow-wrap: anywhere;
}

.key button { margin-left: auto; }

.slots { display: flex; gap: 6px; margin-left: auto; }
.slot { width: 26px; height: 5px; border-radius: 3px; background: var(--wash-2); }
.slot.used { background: var(--brand); }

/* The machines a key is on, under the key, in the admin's own file.
 *
 * Indented off a rule rather than boxed: it belongs to the line above it, and
 * a card inside a row inside a pane is three frames deep for four words and a
 * date. The rule is the same hairline the rows are divided by, so the nesting
 * is read as nesting rather than as a new section.
 *
 * Wrapping on purpose. A fingerprint is sixteen characters and a label is
 * whatever somebody called their computer, and on a narrow window the date is
 * the part worth keeping on screen. */
.machines {
  margin: 8px 0 0;
  padding: 2px 0 0 12px;
  border-left: 1px solid var(--hairline);
  list-style: none;
}

.machines li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 12px;
  padding: 5px 0;
}

/* min-width: 0 so a long hostname wraps instead of pushing the button off the
   edge. .row .grow says the same thing, and does not reach in here. */
.machines .grow { flex: 1 1 auto; min-width: 0; }

.machines-name { font-size: 13px; }

/* The steps somebody follows the first time, numbered, because the first time
   is the only time anybody reads this. */
.steps { counter-reset: step; display: grid; gap: 18px; }

.steps li { list-style: none; display: grid; grid-template-columns: 34px 1fr; gap: 16px; align-items: start; }

.steps li::before {
  counter-increment: step;
  content: counter(step);
  width: 30px; height: 30px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--ghost);
  font-size: 12px;
  color: var(--dim);
}

.steps b { display: block; font-weight: 500; margin-bottom: 3px; }
.steps p { margin: 0; font-size: 14px; }

/* --- the changelog -------------------------------------------------------- */

.release { display: grid; grid-template-columns: 148px 1fr; gap: 30px; padding: 30px 0; border-top: 1px solid var(--hairline); }
.release:first-child { border-top: 0; }
.release .when { font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--faint); }
.release .version { font-size: 20px; margin-bottom: 4px; }
.release .what { white-space: pre-wrap; color: var(--dim); }

@media (max-width: 640px) { .release { grid-template-columns: 1fr; gap: 10px; } }

/* --- the foot ------------------------------------------------------------- */

/* No gap above it. The rule it draws is where the page ends, and a gap between the
   last section and that rule reads as a section missing rather than as air. What
   space there is belongs to the section above, which has its own. */
/* And the top of the footer, the same way. A border cannot carry a gradient, so the
   border goes and the line is drawn. */
footer.foot {
  position: relative;
  padding: 56px 0 64px;
}

footer.foot::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  translate: -50% 0;
  width: min(1160px, calc(100% - 44px));
  height: 1px;
  pointer-events: none;
  background: var(--rule-fade);
}

/* Five columns beside the mark, not four: the products moved into a shop
   column of their own and the shared pages — download, changelog — got theirs,
   which is one more than this grid was declaring. A sixth child against five
   tracks wraps to a second row on its own, which reads as a mistake. */
.foot-grid { display: grid; grid-template-columns: 1.5fr repeat(5, 1fr); gap: 34px 28px; margin-bottom: 46px; }

@media (max-width: 820px) { .foot-grid { grid-template-columns: 1fr 1fr; gap: 30px; } }

.foot-grid h4 { font-size: 10px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--faint); margin-bottom: 16px; }
.foot-grid a { display: block; font-size: 14px; color: var(--dim); padding: 4px 0; transition: color 0.2s var(--ease); }
.foot-grid a:hover { color: var(--text); }
.foot-grid p { font-size: 13.5px; max-width: 34ch; }

.foot-end {
  display: flex;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
  padding-top: 26px;
  position: relative;
  font-size: 12.5px;
  color: var(--faint);
}

/* The same fading rule over the copyright line as at the head of every section.

   Absolutely positioned, which matters here: this row is a flex container, and a
   pseudo-element in one becomes a flex item - a third child between the two spans,
   pushing them apart. Out of the flow it is a line and nothing else. */
.foot-end::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  pointer-events: none;
  background: var(--rule-fade);
}

/* --- pretend money ---------------------------------------------------------

   A shop that looks the same whether or not the money is real is a shop where
   somebody eventually tests with their own card, or ships a licence for a
   payment that never happened. Loud on purpose, and only ever seen while the
   keys are the test ones.
   ------------------------------------------------------------------------- */

.pretend {
  position: relative;
  z-index: 50;
  background: var(--warm);
  color: #0e0f14;
  text-align: center;
  padding: 8px 16px;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

/* --- the admin page -------------------------------------------------------
 *
 * One address sees this, and it is a workbench rather than a shopfront. It
 * keeps the older, plainer pieces on purpose: making it pretty would be time
 * spent on the one page that never has to persuade anybody of anything.
 * ------------------------------------------------------------------------- */

.label {
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 12px;
}

.panes { display: grid; gap: 18px; }
.panes.two { grid-template-columns: 1fr 1fr; }
.panes.three { grid-template-columns: repeat(3, 1fr); }

@media (max-width: 820px) { .panes.two, .panes.three { grid-template-columns: 1fr; } }

/* --- a page that is only a form ------------------------------------------- */

/* One page in three, and the browser picks which.

   /reset cannot know what it is until its own script has looked in the URL
   fragment, which is the one part of an address that never reaches a server.
   All three are in the markup and this shows one, so the page has a shape
   before any script runs rather than being assembled by it. */
.reset-part { display: none; }

#reset[data-state="waiting"] .reset-part[data-when="waiting"],
#reset[data-state="ready"]   .reset-part[data-when="ready"],
#reset[data-state="stale"]   .reset-part[data-when="stale"],
#reset[data-state="bare"]    .reset-part[data-when="bare"] { display: block; }

.gate { width: min(430px, calc(100% - 44px)); margin: 0 auto; padding: clamp(56px, 10vh, 104px) 0 var(--gap); }
.gate h1 { font-size: 32px; margin-bottom: 12px; }
.gate .lead { font-size: 15.5px; margin-bottom: 30px; }
.gate .after { margin-top: 24px; font-size: 13.5px; color: var(--faint); }
.gate .after a { color: var(--dim); }
.gate .after a:hover { color: var(--brand); }

/* --- the new direction ----------------------------------------------------
 *
 * Paper, one dark band, and the accent kept for the few places it means
 * something. What is below is the part of the redesign that is new rather than
 * retuned: the band, the three steps, and the rhythm the sections stand in.
 * ------------------------------------------------------------------------- */

/* The band the instrument stands on.

   Full width, so the dark is a deliberate object on the page rather than a box
   that failed to reach the edge. The wrap inside it keeps the panel on the same
   column as every heading above and below. */
/* No margin above it. There used to be one, and it was neither the hero nor the
   band: a strip of bare paper between the light and the dark, with a hard edge at
   each end. The two meet. */
/* The band the instrument stands on, and it is paper.
 *
 * It was the one dark section on the page, on the reasoning that a near-black panel
 * needs a near-black island under it or it reads as a hole. What it actually did was
 * put a black slab across the middle of a light page and make the panel the darker of
 * two dark things - and the two lines above it, the strip, the notice and the guide
 * tab all had to be re-inverted to survive in there.
 *
 * The page is one colour from the top now and the panel is the only dark object on it,
 * which is what it is: a window, on a page. The two things that ARE the instrument -
 * the panel's own box and the guide's - carry .stage themselves, so everything written
 * for a dark surface still works inside them and nothing else has to know.
 * ---------------------------------------------------------------------- */
.band {
  position: relative;

  /* Arriving from /#demo it should not land under the bar that is sitting over it -
     nor flush against it, which is what the bar's own height alone gives: the bar is a
     pixel taller than --bar because of its rule. A little air as well. */
  scroll-margin-top: calc(var(--bar) + 14px);

  margin: 0;
  padding: clamp(26px, 4vw, 44px) 0 clamp(20px, 3vw, 34px);
}

/* The line between the first screen and the plug-in, faded out at both ends.
 
   The same rule the plug-in draws between its sound and its controls, down to the
   numbers: a border cannot carry a gradient, so it is drawn, and it comes out of
   nothing at the edges rather than stopping dead on them. See #panel::after in the
   instrument's own stylesheet - copied deliberately, because this page and that window
   are meant to be the same room.
 
   On the band rather than under the hero: it belongs to the thing it introduces. */
.band::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  translate: -50% 0;
  height: 1px;
  pointer-events: none;

  /* The page's own measure: the same width as every heading and paragraph on it, and
     the same as the rules further down. Edge to edge it read as a border between two
     documents; at a third of that it was a dash. */
  width: min(1160px, calc(100% - 44px));
  background: var(--rule-fade);
}

/* Three steps, numbered by the list itself.

   A row on a wide window and a column on a narrow one, with the number in the
   accent because it is the one place on this page where an accent is doing a job:
   it is what makes three paragraphs read as an order rather than as three
   paragraphs. */
/* No rule at the head of this section.

   There was one, and where it landed was directly under the guide's card - so it read
   as a second edge belonging to that card rather than as the start of this section. The
   space between the two is wide enough to do the separating on its own, and the heading
   is the announcement. */
.how { padding-top: var(--gap); padding-bottom: 6px; }

/* --- the three ways to play it --------------------------------------------

   THE SHAPE IS THE POINT. This sits where a numbered list of three steps used
   to, and above a run of words-beside-a-live-control. It must not be mistaken
   for either: a third thing in one of those two shapes is a third thing nobody
   stops at. So the picture goes OVER the words rather than beside or before
   them, and it is the only place on the page where that happens.

   No card, no band, no surface of its own. The page separates by hairline and
   air, and a block that suddenly grew a background would be shouting to be
   noticed instead of being worth noticing.
   ------------------------------------------------------------------------- */

.how-lead {
  margin: 10px 0 0;
  font-size: 14.5px;
  color: var(--dim);
  max-width: 46ch;
}

.modes {
  list-style: none;
  margin: 30px 0 0;
  padding: 0;
  display: grid;
  gap: 30px;
  grid-template-columns: repeat(3, 1fr);
}

.modes li {
  padding-top: 22px;
  border-top: 1px solid var(--hairline);
}

/* Given a height, so the three names sit on one line whatever each drawing
   contains — three marks of three heights would put three headings at three
   places and undo the row. */
.modes svg {
  display: block;
  width: 100%;
  max-width: 104px;
  height: 30px;
  margin-bottom: 14px;
  overflow: visible;
}

/* THE ACCENT IS --glow, NOT --brand.

   These are the only marks on the page that are pictures of the instrument's own
   controls, so they take the instrument's own colour — --glow, the cyan the
   panel is actually drawn in, which warms to amber as BLOOM opens and which
   main.js writes on the root sixteen times a second.

   --brand is the wrong token here and was the "weird blue". It is that same
   accent deliberately mixed 58% towards black so that WORDS carrying it can be
   read on white — a dark slate teal, #52818f. Correct for a link or a button
   label; wrong for a drawing of a step in the sequencer, because up there a step
   is a WHITE cell with a cyan edge, not a solid dark teal block. Two things
   claiming to be the same control in two different colours is the incoherence.

   So the cells are built the way the panel builds them: paper-white pane,
   hairline edge, and the one that is sounding tinted and outlined in the live
   accent. Nothing is filled with solid accent — the panel never does that
   either. */
.modes .dim {
  fill: #fff;
  stroke: var(--hairline);
  stroke-width: 1;
}

.modes .lit {
  fill: color-mix(in srgb, var(--glow) 30%, #fff);
  stroke: var(--glow);
  stroke-width: 1;
}

/* The rings come off the point in the live accent too, thinning into the paper.
   A grey ring round a coloured dot reads as a target drawn over it. */
.modes .ring {
  fill: none;
  stroke: color-mix(in srgb, var(--glow) 62%, transparent);
  stroke-width: 1;
}

.modes .head-dot { fill: var(--glow); stroke: none; }

/* The note names. Small, and inheriting the page's face, which is already the
   instrument's own Space Grotesk — nothing to restate here. */
.modes .name {
  font-size: 7.5px;
  font-weight: 500;
  text-anchor: middle;
  fill: var(--dim);
}

/* The key under a finger keeps DARK writing, as the panel's lit step keeps its
   dark number: the cell is tinted, not flooded, so ink still reads on it. */
.modes .name.on { fill: var(--text); }

.modes h3 { margin: 0 0 6px; font-size: 15px; }
.modes p { margin: 0; font-size: 14.5px; color: var(--text-2); }

@media (max-width: 760px) {
  .modes { grid-template-columns: 1fr; gap: 24px; }
}

/* --- Sonus: three ways to find it -----------------------------------------

   Three columns of words, and deliberately nothing else. Two illustrated
   versions of this were built and both were worse than none; the reasoning is
   in views/sonus.ejs where the section is. Its own class rather than .modes
   above, because .modes carries MOTION's sequencer drawings and this carries no
   drawing at all.
   ------------------------------------------------------------------------- */

.ways {
  list-style: none;
  margin: 30px 0 0;
  padding: 0;
  display: grid;
  gap: 30px;
  grid-template-columns: repeat(3, 1fr);
}

.ways li {
  padding-top: 22px;
  border-top: 1px solid var(--hairline);
}

.ways h3 { margin: 0 0 6px; font-size: 15px; }
.ways p { margin: 0; font-size: 14.5px; color: var(--text-2); }

/* A file name quoted inside a sentence, and nothing more than that: the same
   monospace the licence key uses, a shade darker than the sentence around it,
   at the size of the sentence. No tint, no border, no pill — those belong to
   the key box, which is a thing you copy, not a thing you read. */
.ways code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  color: var(--text);
  overflow-wrap: anywhere;
}

@media (max-width: 760px) {
  .ways { grid-template-columns: 1fr; gap: 24px; }
}

/* The specifications, on the recessed band: a list of facts is not a feature and
   should not be sitting on the same white as one. */
.spec-band {
  margin-top: var(--gap);
  padding: var(--gap) 0;
  background: var(--raised-2);
}

.spec-band + .closer { padding-top: var(--gap); }

/* --- the first screen -----------------------------------------------------
 *
 * The whole screen, and nothing else on it. Arriving on a page that already
 * shows two thirds of a dark band underneath is arriving in the middle of
 * something; one screenful of paper, and the way down said out loud.
 * ------------------------------------------------------------------------- */

.hero {
  position: relative;
  overflow: hidden;

  /* svh rather than vh: on a phone vh is the window with the browser's own bars
     pretended away, so a hero of 100vh is a hero with its last line under the
     address bar. */
  min-height: calc(100svh - var(--bar));
  display: flex;
  align-items: center;
  padding: 78px 0 104px;
}

/* Short windows: a laptop in a call, a browser with three toolbars. The screen
   stops being a screenful and the arrow stops being useful. */
@media (max-height: 620px) {
  .hero { min-height: 0; padding: 72px 0 40px; }
}

/* --- and Sonus's, which carries on behind the bar --------------------------
 *
 * MOTION's pixel grid is a patch that travels the page under the pointer,
 * placed by main.js in the page's own terms and owned by no section, so it
 * crosses the bar as though the bar were not there. Which it is not, until the
 * page moves and it takes a background.
 *
 * Sonus's lattice is a canvas inset: 0 of this section, and the section begins
 * where the bar ends. So it drew a hard horizontal edge a pixel under the
 * navigation, and the first screen read as two sheets of paper rather than one.
 * The section cannot simply stop clipping instead: a cover hangs ninety pixels
 * past its right edge and the scene leans another twenty out of the top and the
 * bottom as the pointer moves, so the page would grow a sideways scrollbar and
 * the artwork would lean into the bar.
 *
 * So the section itself is pulled up over the bar and given the same distance
 * back as padding. The canvas fills the padding box, and therefore now fills
 * the band behind the bar too, while every word inside stays where it was. The
 * covers are held back by hand: they are inset: 0 of the same box and would
 * otherwise have been drawn into a picture a bar taller than the one they were
 * placed in.
 * ------------------------------------------------------------------------- */
.hero.under-bar {
  margin-top: calc(-1 * var(--bar-box));
  padding-top: calc(78px + var(--bar-box));
  min-height: calc(100svh - var(--bar) + var(--bar-box));
}

.hero.under-bar .covers { top: var(--bar-box); }

@media (max-height: 620px) {
  /* Both values said again, and not only the padding: the two classes beat the
     plain .hero above whatever order they are read in. */
  .hero.under-bar { min-height: 0; padding-top: calc(72px + var(--bar-box)); }
}

/* --- the light the instrument makes ---------------------------------------
 *
 * The instrument's own aurora, running the instrument's own shader. Four blurred
 * ellipses drifting about in CSS was a gradient mesh from a template and read as
 * one; this is the same flow the panel draws, driven by numbers this page makes up
 * rather than by an engine.
 *
 * Turned over, because the shader paints light on black and this page is paper.
 * Inverting gives ink on white, and turning the hue half a circle afterwards puts
 * the colours back where they started: the amber stays amber, the teal stays teal.
 * The alternative was a hero with a black rectangle in it.
 * ------------------------------------------------------------------------- */

.aura {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  /* Turned over, and turned back.
     
     The shader paints light on black and this page is paper, so it is inverted:
     the black ground becomes the page and the light becomes ink. Inverting takes
     every hue to its opposite, and half a turn of the colour wheel afterwards puts
     them back where they were.
     
     Then --aura-hue on top of that, which is the panel's own accent: the demo
     frame says what colour it is wearing, the page works out how far that is from
     the blue it opens on, and the field turns with it. A cold instrument makes a
     cold hero and a warm one makes a warm hero. */
  /* Turned over, and turned back.

     The shader paints light on black and this page is paper, so the buffer is
     inverted: the black ground becomes the page and the light becomes ink.
     Inverting takes every hue to its opposite, and half a turn of the wheel
     afterwards puts them back.

     Tried and taken out: making the shader hand over its brightness as coverage
     instead, which is the technically right way to put light on paper and turned
     the smoke into a flat mass of colour. The field is dim on purpose and reads as
     smoke because it is dim; normalising it takes the smoke out.

     --aura-hue on top is the panel's own accent: the demo frame says what colour
     it is wearing and the field turns with it. */
  filter: invert(1)
          hue-rotate(calc(180deg + var(--aura-hue, 0deg)))
          saturate(var(--aura-sat, 1.05))
          brightness(var(--aura-lift, 1));

  /* And it leans, on both axes. uScan is the only thing the shader will move for
     you and it moves sideways, so up and down is the canvas itself: a hand at the
     top of the screen and a hand at the foot of it do not get the same picture. */
  transform: translate3d(calc(var(--ax, 0) * 22px), calc(var(--ay, 0) * 26px), 0) scale(1.06);
  transition: transform 1.5s var(--ease);

  /* It arrives when WebGL has drawn its first frame rather than as a black
     rectangle waiting for one. */
  opacity: 0;
  transition: opacity 1.2s var(--ease);
}

.aura.lit { opacity: 1; }

/* Handed over as ink rather than inverted: the shader has done the work, and
   anything on top of it here would take the panel's colours off again. */
.aura.ink { filter: none; }

/* How dark the field is allowed to get. Screen never touches white, so the paper
   is untouched and only the core comes up. One number. */
.aura-floor {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: #fff;
  mix-blend-mode: screen;
  opacity: var(--aura-floor, 0);
}

/* --- the covers on Sonus's first screen ------------------------------------
 *
 * MOTION's page lights its hero with a shader; this one lights it with the
 * artwork, because that is what the product is about. Five pack covers at five
 * distances inside one perspective, turning towards the pointer.
 *
 * REAL DEPTH, NOT LAYERED PARALLAX. The scene has a perspective and the covers
 * sit at different z inside it, so turning it turns them by different amounts
 * for free and the near ones pass in front of the far ones without a single
 * z-index. Sliding five flat images by five different multipliers looks like
 * five images being slid.
 *
 * TWO NUMBERS FROM JAVASCRIPT AND NOTHING ELSE. main.js writes --px and --py on
 * the container — the pointer, as -1 to 1 — and every transform below is
 * written against them. So the work per frame is two custom properties, not
 * five style objects, and the whole arrangement can be re-composed here without
 * touching a line of script.
 *
 * z-index 1: over the paper, under .hero .wrap which is 3. The words always win.
 * ------------------------------------------------------------------------- */

/* --- the lattice under Sonus's first screen --------------------------------
 *
 * A dot at each corner of a large cell, and the cell itself never drawn: the
 * eye joins them up on its own, and what it joins is bigger and quieter than
 * anything a line would give. MOTION's page rules a grid every three pixels
 * because that page is about a screen; this one is about a shelf of records, so
 * the ground under it is a lattice rather than a mesh.
 *
 * Sized and drawn by main.js. Nothing here but where it sits: behind the covers
 * (1) and behind the words (3), and never in the way of a pointer.
 * ------------------------------------------------------------------------- */

.lattice {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.covers {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;          /* nothing here is clickable, and the buttons are */
  perspective: 1100px;
  perspective-origin: 66% 46%;   /* roughly the middle of where they sit */
}

.covers-scene {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  transform: rotateY(calc(var(--px, 0) * 5deg))
             rotateX(calc(var(--py, 0) * -4deg));
  transition: transform 420ms cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: transform;
}

.cover {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--size);
  aspect-ratio: 1;
  margin: 0;
  transform-style: preserve-3d;
  /* Centred on its point, then drifted by the pointer in proportion to how near
     it is, then set at its own distance, then tipped a little off square. */
  transform:
    translate(-50%, -50%)
    translate3d(calc(var(--px, 0) * var(--depth) * 22px),
                calc(var(--py, 0) * var(--depth) * 15px),
                calc((var(--depth) - 0.8) * 70px))
    rotate(var(--rz, 0deg));
  transition:
    transform 520ms cubic-bezier(0.22, 0.61, 0.36, 1),
    opacity 640ms var(--ease) var(--wait, 0s);
  will-change: transform;
}

/* The light each one makes is each one, blurred.
 *
 * A single accent glow behind all five would be the house colour behind five
 * pictures; this is the picture's own colour bleeding onto the paper, so the
 * yellow cover warms the page and the teal one cools it. Set from --art on the
 * element, which is the same file the img is showing — one address, no chance
 * of the glow being a different cover than the art. */
.cover::before {
  content: "";
  position: absolute;
  inset: -16%;
  background: var(--art) center / cover no-repeat;
  filter: blur(34px) saturate(1.6);
  opacity: 0.34;
  border-radius: 26px;
  transform: translateZ(-30px);
}

/* They arrive like the words do, one after another.
 *
 * .rise cannot be used on a cover: its released state is transform: none, which
 * would wipe the placement, the distance and the tilt in one go. So the fade is
 * on the figure, where there is an opacity to spare, and the lift is on the img,
 * which has no transform of its own to fight over. The 3-D transform is left
 * entirely alone.
 *
 * AN ANIMATION AND NOT A CLASS. The first version held them at nothing under
 * html.js and released them from JavaScript, like .rise does. That put five
 * invisible covers behind every fault anywhere in main.js — and one of those
 * was found on this very page today, where a missing element threw and took the
 * whole file with it. An animation with both fill modes needs nothing to run
 * and cannot be left half done.
 *
 * --wait is set beside each cover's placement, so the order they come in is
 * written where the order they are stacked in is written. */
.cover { animation: cover-in 640ms var(--ease) var(--wait, 0s) both; }
.cover img { animation: cover-lift 660ms var(--ease) var(--wait, 0s) both; }

@keyframes cover-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes cover-lift {
  from { transform: translateY(18px) scale(0.975); }
  to   { transform: none; }
}

/* The search that would find it.
 *
 * Inside the figure, so the cover's rotation and its share of the pointer are
 * already applied and there is no second number to keep in step.
 *
 * nowrap and centred on the cover: a phrase that wrapped would be a paragraph,
 * and these are meant to read as one line typed into a box. Wider than the
 * cover on purpose — a search is longer than a sleeve is wide. */
.cover-say {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.005em;
  color: var(--say);
  opacity: 0.92;
}

.cover-say.below { top: calc(100% + 13px); }
.cover-say.above { bottom: calc(100% + 13px); }

.cover img {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 10px;
  /* Lifted off the paper rather than outlined. A border on a photograph is a
     frame around it, and these are meant to be floating, not hung. */
  box-shadow:
    0 26px 54px -18px rgba(14, 15, 19, 0.34),
    0 3px 10px rgba(14, 15, 19, 0.10);
}

/* Where the five of them are.
 *
 * All right of the headline's column, which runs to about half the width: a
 * cover under the words is a cover that makes the words hard to read, and two
 * of these are nearly black. Back to front, so the paint order is the depth
 * order. */
.cover.c1 { --size: 112px; --x: 57%; --y: 14%; --rz: -8deg; --wait: 0.10s; }
.cover.c2 { --size: 146px; --x: 95%; --y: 20%; --rz:  6deg; --wait: 0.18s; }
.cover.c3 { --size: 170px; --x: 67%; --y: 87%; --rz:  7deg; --wait: 0.26s; }
.cover.c4 { --size: 210px; --x: 76%; --y: 44%; --rz: -4deg; --wait: 0.34s; }
.cover.c5 { --size: 238px; --x: 92%; --y: 82%; --rz:  9deg; --wait: 0.42s; }

/* As the column narrows the covers go, furthest first — a shrunken cover is a
   thumbnail, and the headline needs the room more than the decoration does. */
@media (max-width: 1180px) {
  .cover.c1, .cover.c2 { display: none; }
  .cover.c3 { --size: 150px; --x: 66%; --y: 74%; }
  .cover.c4 { --size: 186px; --x: 82%; --y: 40%; }
  .cover.c5 { --size: 206px; --x: 96%; --y: 80%; }
}

@media (max-width: 900px) {
  .cover.c3 { display: none; }
  .cover.c4 { --size: 150px; --x: 88%; --y: 34%; }
  .cover.c5 { --size: 168px; --x: 98%; --y: 74%; }
}

/* On a phone the covers stay, and stop pretending to float.
 *
 * Hiding them was the easy answer and the wrong one: they are what the page is
 * about. But a scene with a perspective needs a pointer to answer to and a
 * phone has none, and five covers scattered behind a column 375 pixels wide
 * land on the words.
 *
 * So on a phone it becomes a shelf: two of them side by side in a band under
 * the text, which the hero makes room for. Still tipped off square, still each
 * with its search. The two brightest, because they are the two that survive
 * being that small.
 *
 * The numbers are measured rather than guessed. At 375 the text ends at 625 and
 * the hero at 835 with this padding, so a 124-tall cover held 46 off the bottom
 * starts at 665: forty clear of the last line, and its caption lands at 800
 * with room under it. An earlier pass had them starting at 611 and cutting
 * through the line above. */
@media (max-width: 700px) {
  .hero { padding-bottom: 210px; }

  .covers { perspective: none; }
  .covers-scene { transform: none; }

  .cover.c1, .cover.c2, .cover.c3 { display: none; }

  .cover.c4,
  .cover.c5 {
    --size: 124px;
    top: auto;
    bottom: 46px;
    /* No drift and no distance: the pointer that drove them is not there. */
    transform: translate(-50%, 0) rotate(var(--rz));
  }

  .cover.c4 { --x: 29%; --rz: -5deg; }
  .cover.c5 { --x: 71%; --rz:  6deg; }

  /* Both under their cover down here. One above and one below, in a single
     row, would read as two unrelated things. */
  .cover-say {
    font-size: 11px;
    top: calc(100% + 10px);
    bottom: auto;
  }
}

/* Below the narrowest phone still sold, one of them goes rather than both
   becoming thumbnails. 359 and not 430: at 375 two of them fit with their
   searches clear of each other, measured, and dropping one there would have
   been thrown away room. */
@media (max-width: 359px) {
  .cover.c4 { display: none; }
  .cover.c5 { --x: 50%; --rz: 4deg; }
}

/* Still, if that is what the system asks for. The covers stay — they are the
   picture, not the motion — and only the answering stops. */
@media (prefers-reduced-motion: reduce) {
  .covers-scene,
  .cover { transition: none; }
}

/* --- the screen the light is on ---------------------------------------------
 *
 * A patch of display travelling under the pointer, and paper everywhere else.
 *
 * A vertical rule and a horizontal rule every three pixels: close up a pixel grid,
 * at arm's length a surface. Multiply, so it darkens what is under it rather than
 * sitting on top - a grey film over amber smoke would read as dirt.
 *
 * Over the whole section it made the section a screen, which is a claim about the
 * page. A disc of it that moves makes the LIGHT be on a screen, which is the thing
 * the page is about. There were four dots at the corners of it as well; they went,
 * and the patch took their room.
 *
 * Where it is and how wide come from main.js, which has the chase the aurora uses.
 * They are set on the root and not on the hero, because the BAR needs them as well:
 * the hero begins under it, so a patch that belonged to the hero alone stopped dead
 * in a straight line across the top of the page. The bar wears the same texture, one
 * bar-height further down the same circle, and stops wearing it the moment it goes
 * solid - which is the moment the hero is behind it and there is nothing to continue.
 * ---------------------------------------------------------------------- */

.screen {
  position: absolute;
  z-index: 5;                    /* over the field, under the bar's own words */
  pointer-events: none;
  mix-blend-mode: multiply;

  /* Where and how big are main.js's: the box is the patch and it travels. */
  top: 0;
  left: 0;

  /* Brought up with the field rather than before it: the first thing the page draws
     should not be a grey disc on white. */
  opacity: 0;
  transition: opacity 1.2s var(--ease);
}

/* Once the field has drawn a frame, and not before. */
.screened .screen { opacity: 1; }


/* Everything above the wash, the grid and the screen texture. The hero's own
   children have no z-index of their own, and one of them being positioned would
   otherwise put it underneath. */
.hero .wrap, .cue { position: relative; z-index: 3; }

/* --- the way down --------------------------------------------------------- */

.cue {
  position: absolute;
  left: 50%;
  bottom: 46px;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  font-family: inherit;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--faint);
  background: none;
  border: 0;
  cursor: pointer;
  transition: color 0.24s var(--ease);
}

.cue:hover { color: var(--text); }

.cue svg {
  width: 16px;
  height: 16px;
  display: block;
  animation: nudge 2.4s var(--ease) infinite;
}

@keyframes nudge {
  0%, 100% { transform: translateY(0); opacity: 0.55; }
  50%      { transform: translateY(6px); opacity: 1; }
}

/* And the two screens where the arrow is a lie.
 *
 * On a phone there is no demo down there to arrive at - the panel is a picture,
 * and the line over the picture says where the real one is. In a short window the
 * first screen is not a screenful and the arrow points at something already
 * visible.
 *
 * Below the block above and not beside the other mobile rules, which is the whole
 * point: display: none written earlier lost to the display: flex above, at equal
 * specificity, so both hides were dead and the arrow showed on every phone. */
@media (max-width: 900px), (max-height: 620px) {
  .cue { display: none; }
}


/* --- the tuner ------------------------------------------------------------
 *
 * Only with ?tune in the address, and never on anybody else's screen.
 *
 * Written after an hour of me changing one number, him looking at it, and saying
 * something a number cannot be derived from. Four sliders and he has the picture
 * he wants in half a minute, which is not a shortcut: nobody can describe a colour
 * over a wire, and the person who should be turning these is the one who can see
 * it.
 * ------------------------------------------------------------------------- */

.tuner {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 90;
  display: grid;
  gap: 10px;
  padding: 16px 18px;
  border: 1px solid var(--ghost);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(10px);
  box-shadow: 0 12px 40px rgba(14, 15, 19, 0.14);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--text);
  width: 250px;
}

.tuner label { display: grid; gap: 4px; }
.tuner span { display: flex; justify-content: space-between; color: var(--dim); }
.tuner input { width: 100%; }
.tuner code { font-size: 10px; color: var(--faint); word-break: break-all; }

/* --- the partner pages ----------------------------------------------------
 *
 * Three pages made of the same eight pieces: /partner, which is the programme
 * and then the partner's own desk; /admin/partners, which is every application
 * and every video; and one partner's file under it.
 *
 * The pieces are shared on purpose. A video on the partner's page and the same
 * video on the admin's wall are the same card, so what one of them learns to do
 * the other one already does, and the two can never come to disagree about
 * what a "picked out" video looks like, or where the platform's mark sits.
 * ------------------------------------------------------------------------- */

/* The mark of the platform. Sized in em so it takes the size of whatever it
   sits in, and painted in currentColor so it takes the colour too. */
.plat { width: 1em; height: 1em; display: block; flex: 0 0 auto; }

.avatar .plat { width: 25px; height: 25px; }

/* --- a row of figures ----------------------------------------------------- */

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
  gap: 14px;
}

.tile {
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 20px 22px;
}

.tile b {
  display: block;
  font-size: clamp(24px, 3vw, 32px);
  font-weight: 500;
  letter-spacing: -0.025em;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
}

.tile span {
  display: block;
  margin-top: 7px;
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--faint);
}

/* --- three buttons where a menu would have hidden two --------------------- */

.picks { display: flex; gap: 10px; flex-wrap: wrap; }

.pick { position: relative; display: inline-flex; }

.pick input {
  position: absolute;
  inset: 0;
  opacity: 0;
  margin: 0;
  cursor: pointer;
}

.pick > span {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  height: 44px;
  padding: 0 17px;
  border: 1px solid var(--hairline);
  border-radius: 10px;
  background: var(--wash);
  font-size: 14px;
  color: var(--dim);
  transition: color 0.18s var(--ease), border-color 0.18s var(--ease),
              background 0.18s var(--ease);
}

.pick > span .plat { width: 17px; height: 17px; }
.pick:hover > span { color: var(--text); border-color: var(--ghost); }

.pick input:checked + span {
  color: var(--text);
  border-color: color-mix(in srgb, var(--brand) 46%, transparent);
  background: color-mix(in srgb, var(--brand) 7%, transparent);
}

.pick input:focus-visible + span {
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand) 14%, transparent);
}

/* --- the one field on the whole programme ---------------------------------
 *
 * A partner pastes a link and presses add. There is no second field and there
 * is not going to be one. The domain says which platform it is, and the mark
 * at the left of the box changes to it as the link lands, so somebody who has
 * pasted the wrong thing can see that before they press anything.
 *
 * Four marks are printed into the box and CSS shows the one the page picked.
 * Swapping markup would mean the script owns what the icon looks like; this way
 * the stylesheet does, and a page with no script still shows a field that works.
 * ------------------------------------------------------------------------- */

.paste {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 7px 7px 7px 17px;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  background: var(--wash);
  transition: border-color 0.2s var(--ease), background 0.2s var(--ease),
              box-shadow 0.2s var(--ease);
}

.paste:focus-within {
  border-color: color-mix(in srgb, var(--brand) 50%, transparent);
  background: var(--wash-2);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand) 14%, transparent);
}

.paste input[type=url] {
  flex: 1 1 auto;
  min-width: 0;
  padding: 12px 0;
  border: 0;
  border-radius: 0;
  background: none;
}

.paste input[type=url]:focus { background: none; box-shadow: none; }

.paste-mark {
  position: relative;
  width: 22px;
  height: 22px;
  flex: 0 0 auto;
  color: var(--faint);
  transition: color 0.2s var(--ease);
}

.paste-mark .plat {
  position: absolute;
  inset: 0;
  width: 22px;
  height: 22px;
  opacity: 0;
  transition: opacity 0.18s var(--ease);
}

.paste[data-plat="other"]     .plat[data-plat="other"],
.paste[data-plat="instagram"] .plat[data-plat="instagram"],
.paste[data-plat="tiktok"]    .plat[data-plat="tiktok"],
.paste[data-plat="youtube"]   .plat[data-plat="youtube"] { opacity: 1; }

/* Known, and it says so in the one way a field can: the mark goes from grey to
   the accent the moment the address is one of the three. */
.paste:not([data-plat="other"]) .paste-mark { color: var(--brand); }

/* --- filtering a list that is already on the page ------------------------- */

.filters { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 20px; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 34px;
  padding: 0 15px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: none;
  font-family: inherit;
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--dim);
  cursor: pointer;
  transition: color 0.18s var(--ease), border-color 0.18s var(--ease),
              background 0.18s var(--ease);
}

.chip .plat { width: 15px; height: 15px; }
.chip:hover { color: var(--text); border-color: var(--ghost); }

.chip.on {
  color: var(--text);
  border-color: color-mix(in srgb, var(--brand) 46%, transparent);
  background: color-mix(in srgb, var(--brand) 7%, transparent);
}

/* --- a video ---------------------------------------------------------------
 *
 * The same card on both dashboards. Square rather than nine by sixteen, and
 * that is the one compromise in it: a wall holding portrait reels beside
 * landscape YouTube has to crop one of the two, and a square crops both a
 * little instead of one of them badly, and every row comes out the same
 * height, which is what makes a list of forty readable at all.
 * ------------------------------------------------------------------------- */

.reels { display: grid; gap: 14px; }

.reel {
  display: grid;
  grid-template-columns: 112px 1fr auto;
  gap: 18px;
  align-items: center;
  padding: 14px;
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}

/* Filtering, as one attribute on the container. Three rules rather than one,
   because CSS cannot compare a parent's attribute with a child's, and three
   rules that say what they do beat a clever one that does not. */
.reels[data-only="instagram"] .reel:not([data-plat="instagram"]),
.reels[data-only="tiktok"]    .reel:not([data-plat="tiktok"]),
.reels[data-only="youtube"]   .reel:not([data-plat="youtube"]) { display: none; }

.reel-shot {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 1;
  border-radius: 10px;
  overflow: hidden;
  background: var(--wash-2);
}

/* Over the mark, not instead of it. A thumbnail that will not load, because the
   platform expired its address or the bucket cannot be reached from where
   somebody is sitting, leaves the platform's mark showing rather than an empty
   grey square, and no script is involved in that happening. */
.reel-shot img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.reel-none {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--faint);
}

.reel-none .plat { width: 26px; height: 26px; }

/* The mark again, over the corner of the picture, because at a glance down a
   list the picture is what the eye lands on and not the line of text beside it. */
.reel-plat {
  position: absolute;
  left: 7px;
  bottom: 7px;
  display: grid;
  place-items: center;
  width: 25px;
  height: 25px;
  border-radius: 8px;
  background: rgba(10, 10, 13, 0.6);
  color: #fff;
  backdrop-filter: blur(5px);
}

.reel-plat .plat { width: 14px; height: 14px; }

.reel-words { min-width: 0; }

.reel-title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 15px;
  line-height: 1.4;
  color: var(--text);
  margin-bottom: 5px;
}

.reel-title:hover { color: var(--brand); }
.reel-words .said { font-size: 12.5px; color: var(--faint); }

.figures {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  align-items: center;
  margin-top: 9px;
  font-size: 13px;
  color: var(--dim);
}

.figures b { font-weight: 500; color: var(--text); font-variant-numeric: tabular-nums; }

/* Where the number came from, and it is not decoration: an exact count from an
   API and a figure read off a page are not the same claim, and only one of them
   is worth acting on. */
.figures .from {
  font-size: 9.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
  border: 1px solid var(--hairline);
  border-radius: 999px;
  padding: 3px 10px;
  cursor: help;
}

.reel-doing { display: flex; flex-direction: column; gap: 8px; }
.reel-doing form { display: contents; }

.reel.away { opacity: 0.55; }
.reel.away .reel-title { text-decoration: line-through; }

/* --- somebody's pages, as things to click --------------------------------- */

.handles { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 18px; }

.handle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 34px;
  padding: 0 14px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  font-size: 13px;
  color: var(--dim);
  transition: color 0.18s var(--ease), border-color 0.18s var(--ease);
}

.handle .plat { width: 15px; height: 15px; }
.handle:hover { color: var(--text); border-color: var(--ghost); }
.handle.main { color: var(--text); border-color: color-mix(in srgb, var(--brand) 40%, transparent); }

/* --- the roster ----------------------------------------------------------- */

.roster-row { display: flex; align-items: center; gap: 16px; }
.roster-row a:hover { color: var(--brand); }

.roster-mark {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
  border-radius: 9px;
  background: var(--wash);
  color: var(--faint);
}

.roster-mark .plat { width: 17px; height: 17px; }

.roster-sums { display: flex; gap: 18px; flex-wrap: wrap; font-size: 13px; color: var(--dim); }
.roster-sums b { font-weight: 500; color: var(--text); font-variant-numeric: tabular-nums; }

/* --- one video, on the file ----------------------------------------------- */

.filed-top { display: grid; grid-template-columns: 132px 1fr; gap: 20px; margin-bottom: 18px; }
.filed-words { min-width: 0; }
.filed.away { opacity: 0.6; }

/* The three boxes for typing in what a platform would not say. Small, and at
   the foot of the card rather than in the middle of it: this is the exception,
   not the way the numbers normally arrive. */
.byhand { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; margin-bottom: 10px; }
.byhand label.field { margin-bottom: 0; width: 118px; }
.byhand input { padding: 9px 12px; font-size: 14px; }

.history { display: grid; margin-bottom: 16px; }

.history-row {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  padding: 7px 0;
  border-top: 1px solid var(--hairline);
  font-size: 12.5px;
  color: var(--dim);
}

.history-row b { font-weight: 500; color: var(--text); font-variant-numeric: tabular-nums; }

/* --- narrow ---------------------------------------------------------------
 *
 * The buttons go under the card rather than beside it, and the picture shrinks.
 * Nothing is dropped: a partner adding a video from their phone half an hour
 * after posting it is the ordinary case, not the awkward one.
 * ------------------------------------------------------------------------- */

@media (max-width: 760px) {
  .reel { grid-template-columns: 88px 1fr; gap: 14px; }
  .reel-doing { grid-column: 1 / -1; flex-direction: row; flex-wrap: wrap; }
  .filed-top { grid-template-columns: 88px 1fr; gap: 14px; }
  .roster-row { flex-wrap: wrap; }
  .roster-sums { gap: 14px; }
  .byhand label.field { width: calc(50% - 6px); }
}

/* --- the board ------------------------------------------------------------
 *
 * Every partner on one line, and the two columns that are the whole reason it
 * exists: how many videos, and how long since the last one. Status says the
 * arrangement stands; those two say whether anything came of it, and finding
 * the rows where those disagree is what this page is for.
 *
 * A grid rather than a table, so the same eight columns collapse to two on a
 * phone without a second set of markup.
 * ------------------------------------------------------------------------- */

.board {
  border-top: 1px solid var(--hairline);
  margin-top: 4px;
}

.board-row, .board-head {
  display: grid;
  grid-template-columns: 36px minmax(0, 1fr) 62px 90px 74px 74px 90px auto;
  gap: 14px;
  align-items: center;
  padding: 13px 0;
  border-bottom: 1px solid var(--hairline);
}

.board-head {
  padding: 9px 0;
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--faint);
  border-bottom-color: var(--ghost);
}

.board-row[hidden] { display: none; }
.board-row a:hover { color: var(--brand); }

/* Five cells of the row on a wide screen, one wrapping line on a narrow one,
   out of the same markup. */
.board-figures { display: contents; }

.board .figure {
  font-size: 14px;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* Never posted, and said quietly rather than in red. It is a thing to notice
   on a Sunday, not an alarm: somebody accepted on Friday has never posted
   either. */
.board .never { font-style: normal; color: var(--faint); }
.board .cold  { color: var(--warm); }

.board-none {
  padding: 26px 0;
  font-size: 14px;
  color: var(--faint);
}

/* ── the workbench ─────────────────────────────────────────────────────────
 *
 * Four pages behind one address, and until now there was no way from any of
 * them to any other: /admin held releases, product settings and everybody's
 * feedback in one column, and /admin/partners was reachable only by typing it.
 * The releases page also switched between instruments with nav.top-links —
 * which is the SITE header's own class, so a second header appeared in the
 * middle of the page and read as one, in a slightly different place each time.
 *
 * One bar, always in the same spot, saying where you are. The chips below it
 * are the same chips the partner board sifts with, so a switcher looks like a
 * switcher wherever it turns up.
 */
.admin-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 8px;
}

.admin-tabs .chip { text-decoration: none; }

/* The instrument this page is about. Same chips, its own line, and never mixed
   in with the four above: which page you are on and which instrument you are
   looking at are two questions. */
.admin-seg { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 14px 0 26px; }
.admin-seg .chip { text-decoration: none; }

/* Named, because two rows of identical pills is one question asked twice. */
.admin-seg-of {
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--faint);
  margin-right: 4px;
}

/* ── a release, and everything it takes to cut one ─────────────────────────
 *
 * One row when it is shut and the whole workbench when it is open. Every
 * release used to be a pane of its own carrying four upload rows and a
 * textarea whether anybody wanted them or not, so five releases was a page
 * nobody could see the end of and the answer to "what is live" was somewhere
 * in the middle of it.
 *
 * A <details> because the shut state has to be the useful one: version, whether
 * it is out, what can be downloaded, when. Everything that writes is one click
 * in, which is also where destructive buttons belong.
 */
.rel { border-bottom: 1px solid var(--hairline); }

.rel > summary {
  display: grid;
  grid-template-columns: 14px 92px 88px minmax(0, 1fr) 100px;
  gap: 14px;
  align-items: center;
  padding: 14px 0;
  cursor: pointer;
  list-style: none;
}

.rel > summary::-webkit-details-marker { display: none; }

/* The arrow, turned by hand rather than left to the browser's own triangle,
   which is a different shape and colour on every platform. */
.rel-turn {
  color: var(--faint);
  font-size: 10px;
  transition: transform 0.18s var(--ease);
}

.rel[open] > summary .rel-turn { transform: rotate(90deg); }

.rel-v {
  font-size: 15px;
  font-variant-numeric: tabular-nums;
}

.rel > summary:hover .rel-v { color: var(--brand); }

.rel-files { display: flex; flex-wrap: wrap; gap: 6px 12px; }

.rel-body {
  padding: 4px 0 24px 28px;
  border-left: 1px solid var(--hairline);
  margin-left: 6px;
}

/* An installer arriving. The file input is the ugliest control on the web and
   there is no hiding it here, so it is simply given room and a small face. */
.rel-take {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}

.rel-take input[type="file"] {
  width: 230px;
  font-size: 11px;
  padding: 6px 8px;
}

@media (max-width: 760px) {
  .rel > summary { grid-template-columns: 14px 1fr auto; }
  .rel > summary .rel-files, .rel > summary .rel-when { grid-column: 2 / -1; }
  .rel-body { padding-left: 16px; }
}

/* ── one customer, and the machines their key is on ────────────────────────
 * The same shut-then-open shape as a release. */
.who { border-bottom: 1px solid var(--hairline); }

.who > summary {
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr) 150px 108px;
  gap: 14px;
  align-items: center;
  padding: 14px 0;
  cursor: pointer;
  list-style: none;
}

.who > summary::-webkit-details-marker { display: none; }
.who[open] > summary .rel-turn { transform: rotate(90deg); }
.who > summary:hover .who-mail { color: var(--brand); }

.who-mail {
  font-size: 14px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.who-count { font-variant-numeric: tabular-nums; }

.who-body {
  padding: 2px 0 22px 28px;
  border-left: 1px solid var(--hairline);
  margin-left: 6px;
}

@media (max-width: 760px) {
  .who > summary { grid-template-columns: 14px 1fr auto; }
  .who > summary .who-keys { grid-column: 2 / -1; }
  .who-body { padding-left: 16px; }
}

/* --- the three rows of chips above it -------------------------------------- */

.sift { display: grid; gap: 10px; margin-bottom: 22px; }
.sift .filters { margin-bottom: 0; }

.sift-sort {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-left: auto;
}

.sift-sort > span {
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--faint);
  white-space: nowrap;
}

.sift-sort select {
  width: auto;
  height: 34px;
  padding: 0 30px 0 13px;
  font-size: 12.5px;
  border-radius: 999px;
}

@media (max-width: 860px) {
  /* The mark and the name keep their row; every figure goes underneath it as a
     labelled pair, because eight columns on a phone is eight columns of nothing. */
  .board-head { display: none; }

  .board-row {
    grid-template-columns: 36px minmax(0, 1fr) auto;
    row-gap: 8px;
  }

  .board-figures {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 16px;
    grid-column: 2 / -1;
  }

  /* Placed rather than left to fall where it lands. The figures take the whole
     width of the second row, so auto-placement puts this on a third row of its
     own, at the left, under nothing, which reads as a button belonging to the
     row below. Named to the first row, it stays beside the name it opens. */
  .board-row > .button {
    grid-column: 3;
    grid-row: 1;
    align-self: center;
  }

  .board .figure {
    display: inline-flex;
    gap: 6px;
    font-size: 13px;
    color: var(--dim);
  }

  .board .figure::before {
    content: attr(data-say);
    font-size: 9.5px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--faint);
    align-self: center;
  }

  .sift-sort { margin-left: 0; }
}

/* --- the pitch and the form, side by side ---------------------------------
 *
 * The form used to sit at the foot of three sections of prose, which is the
 * shape of a page that would rather be read than answered. Beside the pitch,
 * the thing to do is on screen with the reason for doing it, and the page is
 * one screen instead of four.
 * ------------------------------------------------------------------------- */

.apply-split {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: clamp(30px, 5vw, 66px);
  align-items: start;
}

.apply-say h1 {
  font-size: clamp(29px, 3.6vw, 44px);
  max-width: 16ch;
  margin-bottom: 20px;
}

.apply-say .lead { max-width: 40ch; margin-bottom: 28px; }
.apply-say .ticks { max-width: 34ch; }

.apply-box { padding: 30px 32px; }
.apply-box .field:last-of-type { margin-bottom: 22px; }

@media (max-width: 900px) {
  .apply-split { grid-template-columns: 1fr; gap: 34px; }
  .apply-say h1 { max-width: none; }
}

/* --- the word against somebody's name -------------------------------------
 *
 * One word, in the page's own ink, with nothing drawn round it.
 *
 * It was a pill: accent text, accent border, a tinted fill. Which is the
 * treatment this site gives a thing you can act on, and this is not one — it is
 * a statement of fact about who somebody is. Dressed as a badge it read as a
 * rosette, and it competed with the name it was standing next to.
 *
 * Kept as a modifier of .badge rather than a class of its own so it inherits
 * the size, the tracking and the small caps every other label on the site has.
 * All this does is take the jewellery off.
 * ------------------------------------------------------------------------- */

.badge.soft {
  padding: 0;
  border: 0;
  background: none;
  color: var(--text);
}

/* Only where it is a link — on /account the word goes to the partner page. */
a.badge.soft { transition: color 0.18s var(--ease); }
a.badge.soft:hover { color: var(--brand); }


/* --- the one thing on the page you can act on ----------------------------

   The buy block sits in the site's own card, with a little more of everything
   than a .pane usually gets: it is the end of the argument the page has been
   making, and it was drawn exactly like the sections making it.

   A shadow rather than a heavier edge. A thicker border reads as a box round
   some text; a shadow reads as the thing being nearer than the page, which is
   what is meant. */
/* No shadow. It lifted the card off the page, and the page does not lift
   anything else: it separates by hairline and air throughout, and the download
   page's own card — the same .pane, same border, same radius — has never had
   one. Two cards claiming to be the house card in two different treatments is
   the incoherence, and the border alone is enough to say "this is the box with
   the price in it". */
.buycard {
  padding: clamp(30px, 4vw, 46px) clamp(26px, 4vw, 46px);
}

/* The price is the number somebody came for, so it is allowed to be a size the
   page does not otherwise use. */
.buycard .price-line b { font-size: clamp(38px, 5vw, 54px); }

/* --- one card per product on the download page ---------------------------

   The page listed every product's installers in one run, which was fine while
   there was one product and became a wall the moment there were two: four rows
   of "macOS / Windows" with two small headings somewhere in the middle of them.

   A card each makes the page a choice between two things rather than a list of
   four, which is what somebody arriving here is actually doing. Same .pane as
   the buy card on the product pages, so it is the house card again and not a
   third idea. */
.dlcard { padding: clamp(20px, 2.4vw, 28px) clamp(18px, 2.4vw, 30px); }

/* The rows inside it are already separated by their own rules, so the card does
   not need to hold them apart as well. */
.dlcard .shelf { margin-top: 4px; }
.dlcard .row:last-child { border-bottom: 0; padding-bottom: 0; }

/* --- the account page, folded and untinted -------------------------------

   Two changes, and the second is the one that mattered.

   FOLDED. Every licence used to be open at once, so two products meant two
   keys, two machine lists and two sets of notes on screen together — a page you
   read in order to find the one line you came for. One <details> each now,
   native, so there is no script and the browser's own find-in-page opens the
   right one.

   AND UNTINTED. The avatar, the key box, the badge and the machine slots were
   all washed with the brand colour — a colour-mix of it at five, seven, nine
   and twenty-four per cent. Brand tint on every element is not branding, it is
   the absence of a decision about what deserves colour, and it reads as the
   house style of something generated rather than designed. The key is the one
   thing on this page anybody came for, so it is the one thing that keeps a
   colour, and it keeps it in its text rather than in a box around it. */

/* A closed row is a row, not a card with one line in it.

   .pane gives twenty-eight pixels of padding, which is right for a panel full
   of things and absurd around a name and a word: two shut licences filled a
   screen. Tighter when shut, and the room comes back when there is something in
   it to need room. */
.licence {
  padding: 16px 22px;
  transition: padding 0.18s var(--ease);
}

.licence[open] { padding: 20px 22px 26px; }

.licence > summary {
  display: flex;
  align-items: center;
  gap: 14px;
  cursor: pointer;
  list-style: none;
}

/* The name takes the slack, so the badge and the mark end up beside each other
   on the right. They were three items spread apart by space-between, which left
   "active" stranded in the middle of the row belonging to nothing. */
.licence > summary > div:first-child { flex: 1 1 auto; min-width: 0; }

.licence > summary::-webkit-details-marker { display: none; }

/* A mark that turns, on the side the text runs towards. */
.licence > summary::after {
  content: "";
  flex: 0 0 auto;
  width: 6px;
  height: 6px;
  margin-left: 4px;
  border-right: 1.5px solid var(--faint);
  border-bottom: 1.5px solid var(--faint);
  transform: rotate(45deg) translateY(-2px);
  transition: transform 0.2s var(--ease), border-color 0.2s var(--ease);
}

.licence[open] > summary::after { transform: rotate(-135deg) translateY(-2px); }
.licence > summary:hover::after { border-color: var(--dim); }

/* The key: mono, roomy, and coloured in the text. No tinted panel round it. */
.key {
  background: var(--wash);
  border: 1px solid var(--hairline);
  border-radius: 9px;
}

.badge {
  border: 1px solid var(--hairline);
  background: none;
  color: var(--dim);
}

/* Used slots in the ink, not the brand: how many machines are in use is a
   count, not a status worth colouring. */
.slot.used { background: var(--ghost-2); }

/* The divider in the bar, which is a dot.

   Left of it the instruments, right of it what you do once you own one.

   It was a one-pixel vertical rule, and a rule was the wrong object. A vertical
   line beside a word is what a border looks like, so however it was spaced it
   read as an edge belonging to one of the two words rather than as something
   between them — tight, it looked stuck to "download"; loose, it looked like a
   hole with a line in it. A dot has no sides to belong to. It is punctuation,
   and punctuation is what this actually is.

   Three pixels, in --faint. A dot has almost no area, so it needs more ink per
   pixel than a rule to carry at all: --hairline at this size measured 1.03
   against the bar, which is nothing. --faint is forty per cent, which sits it
   below the words it divides and above the background it sits on, and it has a
   light and a dark declaration so the bar keeps working in both.

   aria-hidden in the markup, so it is never read out. A screen reader gets the
   run of links, which is what the row is; the grouping is a thing for eyes. */
nav.top-links .top-sep {
  align-self: center;

  /* A short rule, not a dot, and faded at both ends like every other line in the
     two products. A dot has almost no area and had to be carried at --faint to
     register at all; a rule fourteen pixels long can be much fainter and still
     read, so it divides without competing with the words on either side of it. */
  width: 14px;
  height: 1px;
  border-radius: 0;
  background: linear-gradient(90deg,
    transparent 0,
    var(--ghost) 25%,
    var(--ghost) 75%,
    transparent 100%);
  flex: 0 0 auto;

  /* It sits inside the row's gap rather than adding to it.

     A flex item is given the gap on both sides, so a divider carrying margins
     of its own was spaced twice over: twenty-six pixels of gap plus six of
     margin on each side put sixty-five pixels between Sonus and download.

     The negative margin eats both gaps back and pays out a deliberate figure —
     the gap plus ten, so the pause here is longer than between two ordinary
     words without becoming a gulf. Written against --nav-gap rather than a
     number, so it holds at each of the three widths the bar uses. */
  margin: 0 calc(9px - var(--nav-gap) / 2);
}



/* ── The quick search panel, under the application ─────────────────────────
   It reuses the demo's frame furniture; only three things differ. It sits under
   the application rather than beside it, its frame is shorter because the panel
   is shorter than a window, and it has a caption, which the application does not
   need because it explains itself as you move over it. */
.spot-stage {
  margin-top: 56px;
}

/* Its own container, not .demo-hold.
 
   .demo-hold is built for the application's window: a fixed 1100 by 732 frame,
   scaled by a variable the page computes, with a dark plate and a lit edge. The
   panel is none of those things. It has no edges, it floats, and it is a third
   the height. Reusing that frame put it in a box and shrank it inside one. */
.spot-hold {
  position: relative;
  width: min(100%, 1100px);
  margin: 0 auto;
  /* Taller than the panel by the reach of its own shadow. The frame clips, so a
     shadow with nowhere to fall was being cut off square along the bottom edge. */
  aspect-ratio: 1100 / 440;
}

.spot-hold iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  background: transparent;
}

/* On a phone it is scaled, not hidden.
 
   It was hidden below 760, on the grounds that the panel is seven hundred pixels
   wide and the shortcut is one nobody can press on a phone. Both are true and
   neither is a reason to remove the feature from the page: somebody reading on a
   phone is deciding whether to buy it for the machine they work on. So the frame
   keeps the panel's real proportions and shrinks, the way the application above
   it already does — a picture that happens to still work rather than a picture.
 
   The scale is set from the page, because a transform cannot read its own
   container's width in CSS alone. */
@media (max-width: 760px) {
  /* Not scaled down. Shrinking the whole frame to fit a phone put the panel at
     just over a quarter size, which is a thumbnail of a search box rather than a
     search box. The frame keeps the phone's own width and the panel inside it
     folds its columns away, the way the application's list does as its window
     narrows. Taller, because the rows are the same height and there are five. */
  .spot-hold { aspect-ratio: 3 / 4; }

  /* Room is short and the keys are explaining a desktop gesture, so they take
     less of it. */
  .spot-key { margin-bottom: 10px; gap: 7px; }
  .spot-key kbd { font-size: 16px; padding: 8px 11px; min-width: 36px; }
  .spot-key kbd.wide { min-width: 68px; font-size: 13px; }
  .spot-say { font-size: 13px; margin-top: 16px; }
}

/* On paper. It was white, from the dark band the application sits on, which put
   white text on a white page and made the whole caption disappear. */
.spot-say {
  max-width: 680px;
  margin: 22px auto 0;
  text-align: center;
  font-size: 14px;
  line-height: 1.62;
  color: var(--text-2);
}


/* The combination, said once and said large. It is the whole feature: everything
   under it only happens because this was pressed. */
.spot-key {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin: 0 0 14px;
  font-size: 15px;
  color: var(--faint);
}

/* Keys on paper: a raised white cap with an edge and a shadow under it, the way
   a key looks. They were white on white, which is to say invisible. */
.spot-key kbd {
  font-family: inherit;
  font-size: 21px;
  font-weight: 500;
  line-height: 1;
  color: var(--text);
  padding: 11px 15px;
  min-width: 46px;
  text-align: center;
  border-radius: 10px;
  background: var(--raised);
  border: 1px solid var(--ghost);
  box-shadow: 0 1px 0 var(--ghost-2);
}

/* Words where the Mac has symbols. "Ctrl" is four characters against one glyph,
   and "space" is five, so both get their own size rather than stretching a cap
   built for a single mark. */
.spot-key.is-windows kbd[data-mod],
.spot-key kbd.wide {
  font-size: 16px;
  letter-spacing: 0.02em;
}

.spot-key kbd.wide { min-width: 92px; }

.spot-key span {
  font-size: 15px;
  opacity: 0.5;
}


/* ── The front page ─────────────────────────────────────────────────────────
   Split down the middle, one product each side, and nothing else on it.

   On paper, like the rest of the site. It was black for a while, on the argument
   that two dark windows want a dark page behind them; what that actually bought
   was one page whose bar, footer and rules all had to be redefined to match it,
   and a MOTION capture that all but vanished into the background it was supposed
   to sit on. Both captures carry their own window edge and their own shadow on a
   transparent surround, so on paper they read as two photographs on a table. */
.front {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  min-height: calc(100vh - var(--bar, 64px));
}

.front-half {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(20px, 2.4vw, 32px);
  padding: clamp(36px, 4vw, 68px) clamp(24px, 3.4vw, 56px);
  text-align: center;
}

/* The rule between them, faded out at both ends.
 
   The same treatment every horizontal line in the application gets, turned on
   its side: a line that runs edge to edge reads as a hard division, and one that
   fades reads as two things side by side. A pseudo-element and not a border,
   because a border cannot hold a gradient. */
.front-half + .front-half { position: relative; }

.front-half + .front-half::before {
  content: "";
  position: absolute;
  left: 0;
  top: 6%;
  bottom: 6%;
  width: 1px;
  background: linear-gradient(180deg,
    transparent 0,
    var(--hairline) 18%,
    var(--hairline) 82%,
    transparent 100%);
  pointer-events: none;
}

/* The line over each window.

   There was nothing above the pictures and the half started on a photograph with
   no idea what it was of. This says the one thing a screenshot cannot: what the
   thing is and what it runs as. Held right back, in the same small caps every
   label on this site is set in, so it reads as a caption over a photograph and
   not as a second heading competing with the name underneath. */
.front-kind {
  margin: 0;
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--faint);
}

.front-shot {
  margin: 0;
  width: 100%;
  max-width: 620px;
}

/* Nothing around them.

   Both captures already contain a window: its own rounded corners, its own edge,
   its own shadow, on a transparent surround. A ring and a radius added here drew
   a second box around the first, which is what it looked like — a frame around a
   frame. The picture is left alone.

   Which makes the transparent surround a requirement of the file, not a detail
   of how it was saved. A capture flattened onto black looks identical in a dark
   editor and identical next to the window it came from; here it becomes a black
   rectangle with a window inside it, and one of the two products on the front
   page has a frame the other doesn't. It happened once, to Sonus. Any shot that
   replaces one of these keeps its alpha: rounded corners cut out, shadow left as
   a gradient in the alpha channel, nothing composited onto a colour. */
.front-shot img {
  display: block;
  width: 100%;
  height: auto;
}

.front-said { max-width: 42ch; }

.front-said h2 {
  margin: 0 0 10px;
  color: var(--text);
}

.front-line {
  margin: 0 0 12px;
  font-size: 15px;
  color: var(--text-2);
}

.front-more {
  margin: 0 0 24px;
  font-size: 13.5px;
  line-height: 1.65;
  color: var(--dim);
}

/* One under the other on anything narrow, with the rule turned to match. */
@media (max-width: 900px) {
  .front {
    grid-template-columns: 1fr;
    min-height: 0;
  }

  /* Stacked, so the rule lies down and fades left and right instead. */
  .front-half + .front-half::before {
    left: 6%;
    right: 6%;
    top: 0;
    bottom: auto;
    width: auto;
    height: 1px;
    background: linear-gradient(90deg,
      transparent 0,
      var(--hairline) 18%,
      var(--hairline) 82%,
      transparent 100%);
  }
}

/* --- what people who bought it say ------------------------------------------

   Two things that look like one: a band that drifts, and a form under it.

   THE BAND IS NEVER DRAWN UNDER FOUR REVIEWS. That is decided in
   lib/reviews.js and enforced in the template; there is no empty state here
   because there is no empty state on the page.

   The rows drift opposite ways. Two going the same way read as one block
   sliding and the eye stops seeing cards; opposite, each row is read against
   the other. The track is written twice and moved by half its width, which is
   a loop with no seam. It stops under the pointer, because a moving card
   cannot be read, and it does not move at all for somebody who asked for less
   motion - see the bottom of this block, where it becomes a row you push.
   ------------------------------------------------------------------------- */

.reviews-band { padding-top: clamp(56px, 8vh, 96px); padding-bottom: clamp(48px, 7vh, 84px); }

.reviews-head {
  display: flex;
  gap: clamp(24px, 5vw, 64px);
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  margin-bottom: clamp(28px, 4vw, 44px);
}

.reviews-head-said { max-width: 46ch; }
.reviews-head-said h2 { margin-bottom: 12px; }

.reviews-verified { color: var(--dim); font-size: 15px; margin: 0; }

/* The average, the stars and the five counts, as one quiet block. */
.reviews-score {
  display: grid;
  grid-template-columns: auto auto;
  align-items: center;
  gap: 4px 16px;
}

.reviews-score-number {
  font-size: clamp(40px, 5vw, 56px);
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.reviews-score-side { display: flex; flex-direction: column; gap: 6px; }

/* The five counts. Small and quiet on purpose: the point is that they are
   there, not that they are read. An average alone is the number a shop would
   invent; these are the ones it could not. */
.reviews-spread {
  grid-column: 1 / -1;
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: grid;
  gap: 4px;
  min-width: 190px;
}

.reviews-spread li {
  display: grid;
  grid-template-columns: 10px 1fr 18px;
  align-items: center;
  gap: 8px;
}

.reviews-spread-n,
.reviews-spread-c {
  font-size: 11px;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}

.reviews-spread-c { text-align: right; }

.reviews-spread-bar {
  height: 3px;
  border-radius: 2px;
  background: var(--wash-2);
  overflow: hidden;
}

.reviews-spread-bar > span {
  display: block;
  height: 100%;
  border-radius: 2px;
  background: var(--ghost-2);
}

/* --- the rows themselves --------------------------------------------------- */

/* Full bleed, because a row that drifts has to run off the page. Inside .wrap
   it would stop at a margin and the loop would be visible at both ends. */
.reviews-rows {
  display: grid;
  gap: 18px;
  overflow: hidden;

  /* THE FADE AT BOTH ENDS, and it is a mask rather than two gradients laid on
     top. A gradient over the cards has to be the colour of the page, so it
     breaks the day the page changes colour and it sits over anything that
     scrolls under it. A mask takes the pixels away instead, and knows nothing
     about what is behind. */
  -webkit-mask-image: linear-gradient(90deg,
    transparent 0, #000 9%, #000 91%, transparent 100%);
  mask-image: linear-gradient(90deg,
    transparent 0, #000 9%, #000 91%, transparent 100%);
}

.reviews-row { overflow: hidden; }

.reviews-track { display: flex; width: max-content; }

.reviews-run { display: flex; gap: 18px; padding-right: 18px; }

/* One second per card, so a wall of thirty moves at the same speed as a row of
   six rather than tearing past. */
.reviews-row.rightwards .reviews-track {
  animation: reviews-drift-right calc(var(--cards, 6) * 7s) linear infinite;
}

.reviews-row.leftwards .reviews-track {
  animation: reviews-drift-left calc(var(--cards, 6) * 7s) linear infinite;
}

.reviews-rows:hover .reviews-track,
.reviews-rows:focus-within .reviews-track { animation-play-state: paused; }

@keyframes reviews-drift-left {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

@keyframes reviews-drift-right {
  from { transform: translate3d(-50%, 0, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

/* --- one card -------------------------------------------------------------- */

.review-card {
  flex: 0 0 auto;
  width: clamp(268px, 26vw, 340px);
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 22px 24px 20px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.review-body {
  margin: 0;
  font-size: 15px;
  line-height: 1.62;
  color: var(--text-2);

  /* Four lines and then an ellipsis. A card that grows to fit the longest
     review makes every other card in the row the same height as it, and one
     person writing an essay would set the height of the whole band. */
  display: -webkit-box;
  -webkit-line-clamp: 5;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.review-who { display: flex; align-items: center; gap: 11px; margin-top: auto; }

/* The circle with a letter in it.
 *
 * The account page has none, and that was right there: a circle with your own
 * initial in it, on a page about your own licence key, is a picture of nothing.
 * Here it does work - it tells one card from the next down a moving row, which
 * is the whole reason a review carries a face anywhere. */
.review-avatar {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--text-2);
  background: var(--wash-2);
  border: 1px solid var(--hairline);
}

/* Nobody's initial for somebody who asked not to be named: the verified mark
   instead, which says the true thing about them. */
.review-avatar.unnamed svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: var(--faint);
  stroke-width: 2.1;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.review-who-said { display: flex; flex-direction: column; min-width: 0; }
.review-who-said b { font-size: 14px; font-weight: 500; }
.review-who-said .tiny { font-size: 11px; letter-spacing: 0.04em; }

/* --- stars, drawn once and read everywhere --------------------------------- */

.review-stars { display: inline-flex; gap: 3px; }

.review-stars svg {
  width: 15px;
  height: 15px;
  fill: var(--ghost);
}

.review-stars svg.on { fill: var(--text); }

/* --- leaving one ------------------------------------------------------------

   Only ever drawn for somebody who owns the product - see
   partials/reviews-block.ejs - so there is no signed-out state and no "buy it
   first" message. Somebody who has not bought it sees no form and is told
   nothing, because there is nothing to tell them.
   ------------------------------------------------------------------------- */

.reviews-invite { padding-top: clamp(40px, 6vh, 72px); padding-bottom: clamp(48px, 7vh, 84px); }

.reviews-invite-inner {
  max-width: 620px;
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 30px clamp(22px, 4vw, 34px) 28px;
}

.reviews-said {
  margin: 0 0 18px;
  padding: 11px 15px;
  border-radius: 10px;
  background: var(--wash);
  border: 1px solid var(--hairline);
  font-size: 14px;
  color: var(--text-2);
}

.review-form-legend {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--faint);
  padding: 0;
  margin-bottom: 12px;
}

.review-stars-field { border: 0; padding: 0; margin: 0 0 20px; }

.review-label {
  display: block;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 8px;
}

.review-optional { letter-spacing: 0.08em; text-transform: none; color: var(--ghost-2); }

/* FIVE RADIO BUTTONS, WRITTEN BACKWARDS.
 *
 * Five to one in the markup and turned back round here, so that "every star up
 * to the one I am on" is a plain sibling selector and not a script. With no CSS
 * at all it degrades to five labelled radio buttons, which is still the
 * question being asked. */
.review-stars-input {
  display: inline-flex;
  flex-direction: row-reverse;
  justify-content: flex-end;
  gap: 4px;
}

.review-stars-input input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.review-stars-input label {
  cursor: pointer;
  line-height: 0;
  padding: 3px;
  border-radius: 6px;
}

.review-stars-input label svg { width: 26px; height: 26px; fill: var(--ghost); transition: fill 0.12s var(--ease); }

/* The one under the pointer and every one after it in the markup, which is
   every one before it on the screen. */
.review-stars-input label:hover svg,
.review-stars-input label:hover ~ label svg,
.review-stars-input input:checked ~ label svg { fill: var(--text); }

/* Keyboard: the ring goes on the label, since the input itself is not drawn. */
.review-stars-input input:focus-visible + label {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 26%, transparent);
}

.review-form .field { margin-bottom: 16px; }
.review-form textarea { min-height: 112px; }
.review-note { flex: 1 1 200px; }

/* --- narrow ---------------------------------------------------------------- */

@media (max-width: 720px) {
  .reviews-head { align-items: flex-start; }
  .reviews-score { grid-template-columns: auto auto; }
  .reviews-spread { display: none; }
  .review-card { width: min(78vw, 300px); }
}

/* --- and for somebody who asked for less motion ----------------------------
   It stops, and becomes a row pushed by a finger or a wheel. Not hidden and
   not frozen with half a card cut off at the edge: the same reviews, still
   reachable, without anything moving on its own. */
@media (prefers-reduced-motion: reduce) {
  .reviews-row { overflow-x: auto; }
  .reviews-track { animation: none !important; width: auto; }
  .reviews-run:nth-child(2) { display: none; }
}
