/* ---------------------------------------------------------------------------
   The site, on one position
   ---------------------------------------------------------------------------
   Three pages with one job between them. The home page plants a word — *why* —
   and gives up everything that does not reinforce it. The two inner pages take
   the detail the home page sacrificed, which is also where the search traffic
   lands: a page that tries to own a word *and* answer forty questions does
   neither.

   So: home is one idea at poster size. `how-it-works` is the mechanism.
   `product` is the surface area — what it connects to, what it knows, who it
   is for. Every page carries the same furniture, which lives here.
   --------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
   Dark, and why
   ---------------------------------------------------------------------------
   One reason decides it: the ads are dark and the product is dark. A click
   from a dark ad onto a white page reads as a different company, and the three
   screenshots this site is built around are captures of a dark application —
   on a light page they are three black rectangles floating in paper.

   The cost is real and is paid on the two inner pages, which are long and are
   read rather than scanned. It is paid in contrast: body text goes up a step
   in both size and lightness from what a light page would need, because the
   same grey that reads as "quiet" on white reads as "unfinished" on black.

   The whole palette is flipped here rather than per page, so nothing has a
   light-mode corner left in it. `flow.css` and `shared.css` both take their
   materials from these names, so the shared pieces come with it.

   Light is still offered, for the visitor who asked for it: their system says
   so, or they pressed the switch in the bar. Dark stays the default the page
   is designed on — it is what shows before the head script has run, and what
   a visitor with no preference and no script gets. The light palette is below
   the dark one; see `theme.js` for how the choice is made.
   --------------------------------------------------------------------------- */

:root {
  --bg: #101312;
  --bg-panel: #191d1c;
  --bg-raised: #161a19;
  --line: #2a302e;
  --line-soft: #232826;

  --text: #f4f3f0;
  /* A step lighter than the light page's equivalent: see the note above. */
  --text-soft: #b3b8b4;
  --text-dim: #868b87;

  /* `--accent` is the *text* orange and has to be read on near-black, where
     the light page's deepened #c7491c disappears. The fill stays the brand
     value, which is what `--accent-brand` is for. */
  --accent: #ff8a5c;
  --accent-wash: rgba(255, 108, 55, 0.13);

  --ok: #4ec27a;
  --err: #ff6b63;
  /* The product's own violet — the colour a variable is drawn in inside the
     application. Two jobs here, and only two: pen marks over the page's own
     content, where orange would read as a thing to click and grey would
     disappear into the rules; and the bar's call, which is deliberately the
     quieter of the two buttons that say the same thing. Nothing is *stated*
     in it. */
  --violet: #9187ff;

  --measure: 1060px;

  --font-display: 'Bricolage Grotesque';
  /* The field the page is drawn on — the same dotted ground the application's
     own canvases use, at a fraction of their contrast. */
  --dot: rgba(244, 243, 240, 0.055);

  /* How heavy a drop shadow is. A shadow that gives a card depth on black is
     a smear on paper, so light scales every one of them down. */
  --shadow-k: 1;
  /* The three dots on a window frame around a capture. */
  --chrome-dot: #34403c;

  color-scheme: dark;
}

/*
 * Light: the live site's original paper palette, from `shared.css`, with the
 * few names this file adds. Text keeps the steps a light page needs rather
 * than the lifted ones dark does.
 */
:root[data-theme='light'] {
  --bg: #faf9f7;
  --bg-panel: #ffffff;
  --bg-raised: #f3f1ed;
  --line: #e2ded6;
  --line-soft: #eeebe5;

  --text: #17171a;
  --text-soft: #4d4d55;
  --text-dim: #72727c;

  --accent: #c7491c;
  --accent-wash: rgba(199, 73, 28, 0.09);

  --ok: #2c864d;
  --err: #cf3b32;
  --violet: #695be8;

  --dot: rgba(23, 23, 26, 0.07);
  --shadow-k: 0.28;
  --chrome-dot: #d9d4ca;

  color-scheme: light;
}

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

/* --- the bar ------------------------------------------------------------- */

.bar {
  position: sticky;
  top: 0;
  z-index: 40;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid transparent;
}

.bar.is-ruled {
  border-bottom-color: var(--line);
}

.bar-in {
  display: flex;
  align-items: center;
  gap: 24px;
  height: 66px;
}

.logo {
  display: flex;
  align-items: center;
  gap: 9px;
  font-family: var(--font-display), var(--font-ui, Geist), sans-serif;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.02em;
  text-decoration: none;
}

/* The application's own mark, exported from `.brand-mark` in `App.css` — an
   orange square with a white M, not a gradient chip drawn to resemble one.
   Rendered once at 512 and scaled by the browser; it is a flat shape and
   loses nothing on the way down. */
.logo img {
  display: block;
  width: 22px;
  height: 22px;
}

.bar nav {
  display: flex;
  gap: 22px;
  margin-left: auto;
  font-size: 14.5px;
}

/* Below the width where the bar's button is dropped, the way in goes with it —
   but the way *back* in stays, because that is the one a phone is most likely
   to want. */
@media (max-width: 720px) {
  .log-in {
    font-size: 14px;
  }
}

.bar nav a {
  color: var(--text-soft);
  text-decoration: none;
}

.bar nav a:hover,
.bar nav a[aria-current] {
  color: var(--text);
}

/*
 * The bar's call, in the violet rather than the brand orange.
 *
 * It is the same action as the one in the hero and it should not be the same
 * object. This one is persistent — it rides along the top of every page and is
 * scrolled past far more often than it is pressed — while the hero's and the
 * close's arrive at the end of an argument. Different size was already saying
 * some of that; colour says the rest, and on a dark page a deep violet sits
 * back where a full-strength orange leans forward.
 *
 * Deeper than `--violet` itself, which is a pen colour meant to read on
 * near-black at low weight. A button is a filled object and takes the same hue
 * several steps down, or it glows like something is wrong.
 */
/* The other door. A plain link rather than a second button: a returning
   customer is looking for it and will find a word, while a new one should meet
   exactly one thing that looks pressable. */
.log-in {
  font-size: 14.5px;
  color: var(--text-soft);
  text-decoration: none;
  white-space: nowrap;
}

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

/* The switch between light and dark. A glyph rather than a word, beside the
   way back in: it is furniture, and pressed about as often. It shows where it
   would take you — the sun on the dark page, the moon on the light one. */
.theme-toggle {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--text-soft);
  cursor: pointer;
}

.theme-toggle:hover {
  color: var(--text);
  background: color-mix(in srgb, var(--text) 7%, transparent);
}

.theme-toggle:focus-visible {
  outline: 2px solid var(--accent-brand);
  outline-offset: 2px;
}

.theme-toggle svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.theme-toggle .to-dark,
:root[data-theme='light'] .theme-toggle .to-light {
  display: none;
}

:root[data-theme='light'] .theme-toggle .to-dark {
  display: block;
}

/* Each capture of the application comes in both themes, and the page shows
   the one it is wearing: a dark screenshot on paper reads as a black box, and
   the product should look on the page the way it will look when opened. Only
   the visible one is fetched, since both are lazy and a hidden image is never
   in view. */
/* Written as `img.x.x` to outrank the frames' own `.frag img` and `.plate img`,
   which set display on every picture inside them. */
img.shot-light.shot-light,
:root[data-theme='light'] img.shot-dark.shot-dark {
  display: none;
}

:root[data-theme='light'] img.shot-light.shot-light {
  display: block;
}

/* Smaller than the calls further down the page, and smaller than it was. It
   is the third thing in a row of four and should not outweigh the nav it sits
   beside. */
.bar .btn {
  height: 34px;
  padding: 0 16px;
  font-size: 13.5px;
}

.bar .btn-primary,
.btn-primary.btn-violet {
  background-image: linear-gradient(103deg, #7a68f0 0%, #5a46d2 48%, #3a2a94 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    inset 0 -1px 0 rgba(28, 16, 80, 0.3),
    0 8px 20px -8px rgba(74, 56, 190, 0.75);
}

.bar .btn-primary:hover,
.btn-primary.btn-violet:hover {
  background-image: linear-gradient(103deg, #8b7bf5 0%, #6a57e0 48%, #4735ab 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.26),
    inset 0 -1px 0 rgba(28, 16, 80, 0.26),
    0 12px 26px -9px rgba(74, 56, 190, 0.82);
}

.bar .btn-primary:active,
.btn-primary.btn-violet:active {
  box-shadow:
    inset 0 1px 2px rgba(20, 10, 60, 0.45),
    0 2px 8px -4px rgba(74, 56, 190, 0.6);
}

@media (max-width: 720px) {
  /*
   * The bar's navigation scrolls rather than wraps or disappears.
   *
   * Four destinations and a way back in do not fit beside the mark on a phone,
   * and the three usual answers are all worse than this one: wrapping makes
   * the bar two rows tall on every page, hiding links loses Pricing and
   * Download on the device most likely to be someone's first visit, and a
   * burger is a menu to build and maintain for five links.
   *
   * So it is a strip that slides, clipped to its own box — which is also what
   * stops the document scrolling sideways, since the overflow now belongs to
   * the nav instead of the page.
   */
  .bar nav {
    gap: 15px;
    min-width: 0;
    overflow-x: auto;
    scrollbar-width: none;
    font-size: 14px;
    -webkit-overflow-scrolling: touch;
  }

  .bar nav::-webkit-scrollbar {
    display: none;
  }

  .bar nav a {
    white-space: nowrap;
  }

  .bar .btn {
    display: none;
  }

  /* The switch takes room the nav strip would otherwise scroll through, so
     the bar's spacing tightens to give it back. */
  .bar-in {
    gap: 14px;
  }

  /* The mark keeps its width; the nav strip is the part that scrolls. */
  .logo {
    flex: none;
  }

  .theme-toggle {
    width: 30px;
    height: 30px;
  }
}

/* ---------------------------------------------------------------------------
   The ground
   ---------------------------------------------------------------------------
   Flat near-black under a statement and two links is not restraint, it is an
   absence — there is nothing for the type to sit on and nothing to say where
   the page begins.

   Two things, both faint enough to be felt rather than seen. A dot grid, which
   is the same field the sources canvas and the investigation canvas are drawn
   on inside the product, at about a fifth of their contrast — so the site is
   standing on the application's own paper. And a warm bloom in the brand
   orange behind the opening, which lifts the corner the eye lands on first.

   Both are masked out downwards. A texture that runs the whole page competes
   with the two canvases rebuilt further down, which have dotted fields of
   their own at full strength; this one has to have finished by the time they
   arrive. The closing section gets the same treatment upside down, so the page
   opens and closes on the same ground.
   --------------------------------------------------------------------------- */

.open,
.close {
  position: relative;
  isolation: isolate;
}

.open::before,
.close::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: radial-gradient(circle, var(--dot) 1px, transparent 1px);
  background-size: 26px 26px;
}

.open::before {
  /* Up under the bar, which is translucent and blurred over it. */
  top: -66px;
  background-image:
    radial-gradient(56% 64% at 50% 10%, rgba(255, 108, 55, 0.13), transparent 70%),
    radial-gradient(circle, var(--dot) 1px, transparent 1px);
  background-size:
    auto,
    26px 26px;
  -webkit-mask-image: linear-gradient(#000 0%, #000 46%, transparent 92%);
  mask-image: linear-gradient(#000 0%, #000 46%, transparent 92%);
}

.close::before {
  background-image:
    radial-gradient(48% 60% at 50% 96%, rgba(255, 108, 55, 0.1), transparent 70%),
    radial-gradient(circle, var(--dot) 1px, transparent 1px);
  background-size:
    auto,
    26px 26px;
  -webkit-mask-image: linear-gradient(transparent 0%, #000 34%, #000 100%);
  mask-image: linear-gradient(transparent 0%, #000 34%, #000 100%);
}

/* ---------------------------------------------------------------------------
   Arriving
   ---------------------------------------------------------------------------
   Each block comes up as it is reached, and only the first time. It is a small
   rise and a fade — 16px and 560ms — rather than a slide from the side: the
   page is read downwards and the motion should agree with the reading, not cut
   across it.

   Staggered inside a section, not across the page. Blocks are given their
   index within their own section, so a heading lands, then its sentence, then
   its figure — the order they are meant to be read in. A page-wide counter
   would have the last section starting two seconds after it was reached.

   The class goes on from script, so the page is fully visible to anything that
   does not run one — a crawler, a reader, a browser with it turned off. This
   is decoration and it never gets to decide whether the content exists.
   --------------------------------------------------------------------------- */

.js-reveal {
  opacity: 0;
  transform: translateY(16px);
  transition:
    opacity 560ms cubic-bezier(0.22, 0.61, 0.36, 1),
    transform 560ms cubic-bezier(0.22, 0.61, 0.36, 1);
  transition-delay: calc(var(--r, 0) * 90ms);
}

.js-reveal.is-in {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .js-reveal,
  .js-reveal.is-in {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* --- type ---------------------------------------------------------------- */

section {
  padding: clamp(52px, 8vw, 104px) 0;
}

h2 {
  max-width: 18ch;
  font-size: clamp(26px, 3.8vw, 40px);
}

h2 .dim {
  color: var(--text-dim);
}

h3 {
  font-size: 18px;
}

.sub {
  max-width: 56ch;
  margin-top: 15px;
  font-size: 17px;
  color: var(--text-soft);
}

.lede {
  max-width: 48ch;
  margin-top: 22px;
  font-size: clamp(17px, 1.9vw, 20px);
  color: var(--text-soft);
}

/* The one action. A second button beside it splits the decision, so the
   alternative is a plain link that does not compete for the eye. */
.act {
  display: flex;
  align-items: center;
  gap: 20px;
  margin-top: 30px;
  flex-wrap: wrap;
}

/*
 * The call in a hero or a close is a bigger object than the one in the bar.
 *
 * They were the same 46px button, which made the one at the top of the page —
 * a persistent, secondary offer you scroll past — exactly as loud as the one
 * the whole section has been arguing towards. Size is the cheapest way to say
 * which of the two is the point.
 */
.act .btn {
  height: 52px;
  padding: 0 28px;
  font-size: 16px;
}

.act .also {
  font-size: 15px;
  color: var(--text-soft);
  text-underline-offset: 3px;
}

/* ---------------------------------------------------------------------------
   The legal pages
   ---------------------------------------------------------------------------
   Long, plain, and read rather than scanned — so they get a narrower measure
   than the rest of the site and nothing else. No cards, no accents, no
   illustration. A legal page that has been designed is a legal page somebody
   suspects of hiding something.
   --------------------------------------------------------------------------- */

.legal {
  max-width: 68ch;
}

.legal h2 {
  max-width: none;
  margin-top: 46px;
  font-size: clamp(20px, 2.4vw, 25px);
  /* The bar is sticky and 66px tall: a heading arrived at by its id — the cookie
     banner links to `#cookies` — landed underneath it. */
  scroll-margin-top: 90px;
}

.legal h3 {
  margin-top: 28px;
  font-size: 17px;
  color: var(--text);
}

.legal p,
.legal li {
  margin-top: 14px;
  font-size: 16.5px;
  line-height: 1.65;
  color: var(--text-soft);
}

.legal ul {
  margin: 0;
  padding-left: 22px;
}

.legal li::marker {
  color: var(--text-dim);
}

.legal strong {
  color: var(--text);
  font-weight: 500;
}

.legal a {
  color: var(--accent);
}

/* The blanks somebody has to fill in before this is worth anything. Marked
   rather than left as ordinary prose, so they cannot be read past. */
.legal .fill {
  padding: 1px 6px;
  border: 1px dashed color-mix(in srgb, var(--violet) 55%, transparent);
  border-radius: 4px;
  background: color-mix(in srgb, var(--violet) 10%, transparent);
  color: var(--violet);
  font-family: 'Geist Mono', ui-monospace, monospace;
  font-size: 0.86em;
}

/* Unmissable, and meant to be deleted. */
.draft {
  max-width: 68ch;
  margin-top: 26px;
  padding: 16px 18px;
  border: 1px solid color-mix(in srgb, var(--violet) 45%, transparent);
  border-left: 3px solid var(--violet);
  border-radius: 0 var(--r) var(--r) 0;
  background: color-mix(in srgb, var(--violet) 8%, transparent);
  font-size: 15px;
  line-height: 1.6;
  color: var(--text-soft);
}

.draft b {
  color: var(--violet);
  font-weight: 600;
}

.legal-updated {
  margin-top: 8px;
  font-family: 'Geist Mono', ui-monospace, monospace;
  font-size: 13px;
  color: var(--text-dim);
}

/* The footer grows a second row once there are legal links in it. */
.foot-legal {
  font-size: 13.5px;
}

/* ---------------------------------------------------------------------------
   The support form
   ---------------------------------------------------------------------------
   Plain fields on the page's own dark ground, at the legal pages' measure.
   Nothing here is a card: a support form that has been designed looks like a
   funnel, and somebody filling one in already has a problem.
   --------------------------------------------------------------------------- */

.ticket {
  max-width: 62ch;
  margin-top: 30px;
  display: grid;
  gap: 18px;
}

.ticket-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px;
}

.ticket label {
  display: grid;
  gap: 7px;
  font-size: 14.5px;
  color: var(--text-soft);
}

/* The caption is one grid row, asterisk included: loose in the label it became
   a row of its own and hung under the word it qualifies. */
.ticket label > span {
  display: block;
}

.ticket .req {
  color: var(--accent);
  font-weight: 500;
}

.ticket input,
.ticket textarea {
  width: 100%;
  box-sizing: border-box;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--bg-panel);
  color: var(--text);
  font: inherit;
  font-size: 15.5px;
}

.ticket textarea {
  min-height: 170px;
  line-height: 1.55;
  resize: vertical;
}

.ticket input:focus-visible,
.ticket textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: transparent;
}

.ticket input::placeholder,
.ticket textarea::placeholder {
  color: var(--text-dim);
}

/* The honeypot. Off-screen rather than `display: none`, which some bots read
   as a field to skip. */
.ticket-trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.ticket-send {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

.ticket-note {
  margin: 0;
  font-size: 14.5px;
  line-height: 1.5;
}

.ticket-note.is-bad {
  color: var(--err);
}

.ticket-note.is-good {
  color: var(--ok);
}

.ticket[data-sending] button {
  opacity: 0.6;
  pointer-events: none;
}

@media (max-width: 560px) {
  .ticket-pair {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---------------------------------------------------------------------------
   The price ladder
   ---------------------------------------------------------------------------
   Four columns, read across. One is marked — not the dearest, the one most
   people should be on — and it is marked by a border and a filled button
   rather than by a ribbon saying so.
   --------------------------------------------------------------------------- */

.tiers {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 16px;
  margin-top: 34px;
  align-items: stretch;
}

.tier {
  display: flex;
  flex-direction: column;
  padding: 24px 22px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--bg-panel);
}

/*
 * A tier that is not for sale yet.
 *
 * Muted rather than hidden: the ladder is the argument for what Free is part
 * of, and three blank spaces make the free tier look like the whole ambition.
 * What it loses is the button — a column nobody can buy should not carry a
 * control that implies they can.
 */
.tier.is-soon {
  background: transparent;
  border-style: dashed;
}

.tier.is-soon h2,
.tier.is-soon .tier-has b {
  color: var(--text-soft);
}

.tier.is-soon .tier-has,
.tier.is-soon .tier-from {
  color: var(--text-dim);
}

.tier-cost .is-soon {
  font-family: var(--font-ui, inherit);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--accent);
}

/* The list is the last thing in a column that has no button, so it should not
   stop short of the ones that do. */
.tier.is-soon .tier-has {
  margin-bottom: 0;
}

.tiers-note {
  margin: 26px 0 0;
  font-size: 15px;
  line-height: 1.6;
  color: var(--text-dim);
  max-width: 62ch;
}

.tier.is-picked {
  border-color: color-mix(in srgb, var(--accent-brand) 55%, transparent);
  background: var(--bg-raised);
}

.tier h2 {
  max-width: none;
  font-size: 19px;
}

.tier-who {
  margin-top: 8px;
  min-height: 3.2em;
  font-size: 14.5px;
  line-height: 1.45;
  color: var(--text-dim);
}

.tier-cost {
  margin-top: 16px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}

.tier-cost b {
  font-family: var(--font-display), sans-serif;
  font-size: 27px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
}

.tier-cost span {
  font-size: 13.5px;
  color: var(--text-dim);
}

/* "Everything in Pro, and:" — the ladder said once, so each column lists only
   what it adds rather than repeating four lines that are true everywhere. */
.tier-from {
  margin-top: 16px;
  font-size: 13.5px;
  color: var(--text-dim);
}

.tier-from + .tier-has {
  margin-top: 12px;
}

.tier-has {
  flex: 1;
  margin: 18px 0 22px;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 10px;
  align-content: start;
  font-size: 14.5px;
  color: var(--text-soft);
}

.tier-has b {
  color: var(--text);
  font-weight: 600;
}

.tier .btn {
  justify-content: center;
}

.tier .btn-secondary {
  border: 1px solid var(--line);
  background: transparent;
  color: var(--text);
}

.tier .btn-secondary:hover {
  border-color: var(--text-dim);
}

@media (max-width: 1000px) {
  .tiers {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* The blurb only needs a fixed height to keep four columns aligned. */
  .tier-who {
    min-height: 0;
  }
}

@media (max-width: 560px) {
  .tiers {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---------------------------------------------------------------------------
   The download page
   ---------------------------------------------------------------------------
   It was the last page on the old light stylesheet and looked like a different
   company. Rebuilt on this one, but every id and every class `downloads.js`
   writes is kept exactly as it was — the release card, the asset list and the
   waiting list are all drawn by that script, and renaming any of them would
   have been a silent break with nothing to see until a release exists.
   --------------------------------------------------------------------------- */

.dl-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 232px;
  gap: clamp(24px, 4vw, 52px);
  align-items: start;
}

.dl-side {
  position: sticky;
  top: 90px;
  padding: 20px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--bg-panel);
}

.dl-meta {
  margin: 0;
  display: grid;
  gap: 12px;
}

.dl-meta div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.dl-meta dt {
  font-size: 13.5px;
  color: var(--text-dim);
}

.dl-meta dd {
  margin: 0;
  font-family: 'Geist Mono', ui-monospace, monospace;
  font-size: 13px;
  color: var(--text);
}

.dl-links {
  display: grid;
  gap: 9px;
  margin-top: 20px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
  font-size: 14px;
}

.dl-links a {
  color: var(--text-soft);
  text-decoration: none;
}

.dl-links a:hover {
  color: var(--text);
}

/* Where the release button lands, and what stands in for it until one does. */
.dl-action {
  min-height: 52px;
  display: flex;
  align-items: center;
}

.dl-waiting,
.dl-empty,
.notice {
  font-size: 15px;
  color: var(--text-dim);
}

/* Paragraphs carry no margin on this site, so the statement, the explanation
   and the button under them were three lines of one block — the button sat on
   the last line of the sentence it answers. A stack with air in it, and more
   above the button than between the paragraphs: it is the way out of the
   passage, not its next line. */
.dl-empty {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}

.dl-empty .btn {
  margin-top: 14px;
}

.dl-primary {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

/* `btn-lg` is the download button's own size, written by the script. */
.dl-primary .btn-lg {
  height: 52px;
  padding: 0 28px;
  font-size: 16px;
}

.dl-file {
  display: grid;
  gap: 2px;
}

.dl-file-name {
  font-size: 14.5px;
  color: var(--text);
}

.dl-file-meta {
  font-family: 'Geist Mono', ui-monospace, monospace;
  font-size: 12.5px;
  color: var(--text-dim);
}

.dl-extras {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}

.dl-extra {
  padding: 5px 11px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--bg-panel);
  font-size: 13px;
  color: var(--text-soft);
  text-decoration: none;
}

.dl-extra:hover {
  border-color: var(--text-dim);
  color: var(--text);
}

.dl-facts {
  margin: 32px 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 16px;
  max-width: 62ch;
}

.dl-facts li {
  font-size: 15.5px;
  line-height: 1.6;
  color: var(--text-soft);
}

.dl-facts strong {
  color: var(--text);
  font-weight: 600;
}

/* --- the waiting list --- */

.waitlist {
  margin-top: 34px;
  padding: 24px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--bg-panel);
  max-width: 62ch;
}

.waitlist.is-done .waitlist-row {
  display: none;
}

.waitlist-title {
  max-width: none;
  font-size: 19px;
}

.waitlist-lede {
  margin-top: 8px;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--text-dim);
}

.waitlist-row {
  display: flex;
  gap: 10px;
  margin-top: 18px;
  flex-wrap: wrap;
}

.waitlist-row input {
  flex: 1;
  min-width: 220px;
  box-sizing: border-box;
  padding: 0 13px;
  height: 46px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 15px;
}

.waitlist-row input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: transparent;
}

.waitlist-row input::placeholder {
  color: var(--text-dim);
}

.waitlist-row .btn {
  height: 46px;
}

.waitlist-note {
  margin: 14px 0 0;
  font-size: 14.5px;
}

.waitlist-note:empty {
  display: none;
}

/* The script says `ok` and `err`; these said `good` and `bad`, so neither
   colour was ever applied and a success read like any other sentence. */
.waitlist-note.is-err {
  color: var(--err);
}

/*
 * Done: a green tick that draws itself, then the words.
 *
 * The ring closes, the tick is drawn through it, and the sentence rises in
 * beside it — about a second in all, once, and then still. The form's row is
 * gone by now, so this is the whole answer and is given the room of one.
 */
.waitlist-note.is-ok {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 18px;
  padding: 14px 16px;
  border: 1px solid color-mix(in srgb, var(--ok) 35%, transparent);
  border-radius: var(--r);
  background: color-mix(in srgb, var(--ok) 9%, transparent);
  color: var(--ok);
  font-size: 15px;
  font-weight: 500;
  line-height: 1.45;
  animation: waitlist-rise 420ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
}

.waitlist-tick {
  flex: none;
  width: 26px;
  height: 26px;
}

.waitlist-tick circle {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-dasharray: 70;
  stroke-dashoffset: 70;
  transform: rotate(-90deg);
  transform-origin: center;
  animation: waitlist-draw 520ms cubic-bezier(0.65, 0, 0.35, 1) 80ms forwards;
}

.waitlist-tick path {
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 18;
  stroke-dashoffset: 18;
  animation: waitlist-draw 300ms cubic-bezier(0.65, 0, 0.35, 1) 520ms forwards;
}

.waitlist-note.is-ok .waitlist-words {
  animation: waitlist-rise 420ms cubic-bezier(0.2, 0.7, 0.2, 1) 260ms both;
}

@keyframes waitlist-draw {
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes waitlist-rise {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .waitlist-note.is-ok,
  .waitlist-note.is-ok .waitlist-words {
    animation: none;
  }

  .waitlist-tick circle,
  .waitlist-tick path {
    animation: none;
    stroke-dashoffset: 0;
  }
}

/* --- what it needs, and how to start it --- */

.specs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 18px;
  margin: 28px 0 0;
}

.spec {
  padding: 18px 20px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--bg-panel);
}

.spec dt {
  font-size: 13px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.spec dd {
  margin: 8px 0 0;
  font-size: 15px;
  line-height: 1.55;
  color: var(--text-soft);
}

.steps-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 18px;
  margin-top: 28px;
}

.step-card {
  padding: 22px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--bg-panel);
}

.step-n {
  font-family: 'Geist Mono', ui-monospace, monospace;
  font-size: 12px;
  color: var(--accent);
}

.step-cmd {
  display: block;
  margin: 12px 0 14px;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--bg);
  font-family: 'Geist Mono', ui-monospace, monospace;
  font-size: 13.5px;
  color: var(--text);
  overflow-x: auto;
}

.step-card p {
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--text-soft);
}

.panel {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--bg-panel);
  overflow: hidden;
}

.panel-head {
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
  background: var(--bg-raised);
  font-size: 13.5px;
  color: var(--text-dim);
}

.panel-body {
  padding: 16px;
  font-family: 'Geist Mono', ui-monospace, monospace;
  font-size: 13px;
  line-height: 1.7;
  color: var(--text);
}

.panel-body .is-said {
  color: var(--text-dim);
}

.panel-body .is-next {
  margin-top: 14px;
}

/* --- every version, as the script draws them --- */

.releases {
  display: grid;
  gap: 14px;
  margin-top: 28px;
}

.release {
  padding: 20px 22px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--bg-panel);
}

.release-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
}

.tag {
  font-family: 'Geist Mono', ui-monospace, monospace;
  font-size: 14px;
  font-weight: 500;
  color: var(--accent);
}

.release-date {
  font-size: 13.5px;
  color: var(--text-dim);
}

.release-notes {
  margin-top: 12px;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--text-soft);
  white-space: pre-wrap;
}

.assets {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}

.asset {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--bg-raised);
  text-decoration: none;
}

.asset:hover {
  border-color: var(--text-dim);
}

.asset-name {
  font-size: 13.5px;
  color: var(--text);
}

.asset-meta {
  font-family: 'Geist Mono', ui-monospace, monospace;
  font-size: 12px;
  color: var(--text-dim);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

@media (max-width: 880px) {
  .dl-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Sticky beside the content, ordinary above it. */
  .dl-side {
    position: static;
    order: -1;
  }
}

/* --- a page head, for the two inner pages -------------------------------- */

.page-head {
  padding: clamp(40px, 6vw, 76px) 0 clamp(24px, 3vw, 40px);
  border-bottom: 1px solid var(--line);
}

.page-head h1 {
  max-width: 17ch;
  font-size: clamp(32px, 5vw, 54px);
}

.page-head .lede {
  max-width: 54ch;
}

.crumb {
  margin-bottom: 14px;
  font-size: 13px;
  color: var(--text-dim);
}

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

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

/* --- shared blocks ------------------------------------------------------- */

.band {
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: var(--bg-raised);
}

.rules,
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 26px 0 0;
  padding: 0;
  list-style: none;
}

.rules li,
.chips li {
  padding: 7px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--bg-panel);
  font-size: 14.5px;
  color: var(--text-soft);
}

.row-marks {
  display: flex;
  justify-content: center;
  gap: clamp(12px, 3vw, 32px);
  flex-wrap: wrap;
}

.mark-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Cards, in the one shape every page uses. */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
  margin-top: 28px;
}

.card {
  padding: 22px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--bg-panel);
}

.card b {
  display: block;
  margin-bottom: 8px;
  font-size: 16.5px;
}

.card span {
  font-size: 14.5px;
  color: var(--text-soft);
}

/* --- the close every page ends on ---------------------------------------- */

.close {
  text-align: center;
  border-top: 1px solid var(--line);
}

.close h2 {
  max-width: 22ch;
  margin-inline: auto;
}

.close .sub {
  margin-inline: auto;
}

.close .act {
  justify-content: center;
}

footer {
  padding: 34px 0 52px;
  border-top: 1px solid var(--line);
  font-size: 14px;
  color: var(--text-dim);
}

.foot-in {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  align-items: center;
}

.foot-in nav {
  display: flex;
  /* Seven links now, and on a phone they came to 493px in a 358px column and
     pushed the whole document sideways. A footer list is exactly the thing
     that should wrap. */
  flex-wrap: wrap;
  gap: 10px 18px;
  margin-left: auto;
}

footer a {
  text-decoration: none;
}

footer a:hover {
  color: var(--text);
}

/* The footer's way back into the cookie banner. A button, because it does
   something rather than going somewhere — drawn as the links beside it, because
   that is what it is to somebody reading the row. */
.consent-open {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

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

.consent-open:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ---------------------------------------------------------------------------
   The cookie banner
   ---------------------------------------------------------------------------
   A bar across the foot of the window, on the page's own measure: the words
   range left on the column everything else stands on, and the two answers sit
   at its right edge, where the bar at the top keeps its button. It was a card
   in the corner first, which read as a chat widget that had wandered in.

   Nothing is withheld until it is answered — the site works the same either
   way — so it covers the last strip of the page and not the page.

   Built by `consent.js`, which is also why it is styled here and not in a
   page: every page loads this file and none of them contains the markup.
   --------------------------------------------------------------------------- */

.consent {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 60;
  padding: 18px 0;
  border-top: 1px solid var(--line);
  background: color-mix(in srgb, var(--bg-panel) 94%, transparent);
  backdrop-filter: blur(14px);
  box-shadow: 0 -18px 50px -24px rgb(0 0 0 / calc(0.75 * var(--shadow-k)));
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--text-soft);
  opacity: 0;
  transform: translateY(100%);
  visibility: hidden;
  transition:
    opacity 260ms ease,
    transform 320ms cubic-bezier(0.2, 0.7, 0.2, 1),
    visibility 0s linear 320ms;
}

.consent.is-open {
  opacity: 1;
  transform: none;
  visibility: visible;
  transition-delay: 0s;
}

.consent-in {
  display: flex;
  align-items: center;
  gap: 18px 40px;
}

.consent-words {
  flex: 1;
  min-width: 0;
  display: grid;
  gap: 4px;
  max-width: 78ch;
}

.consent-title {
  font-family: var(--font-display), var(--font-ui, Geist), sans-serif;
  font-size: 16.5px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
}

.consent a {
  color: var(--text);
  text-underline-offset: 3px;
  white-space: nowrap;
}

/* One size for both, side by side — see `build` in `consent.js`. */
.consent-actions {
  flex: none;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.consent-actions .btn {
  justify-content: center;
  height: 40px;
  min-width: 116px;
  padding: 0 18px;
  font-size: 14.5px;
}

.consent-actions .btn-ghost:hover {
  border-color: var(--text-dim);
}

/* Under the words once they no longer fit beside them, and then the width of
   the column: two thumb-sized targets rather than two small ones at one end. */
@media (max-width: 760px) {
  .consent {
    padding: 16px 0 18px;
  }

  .consent-in {
    flex-direction: column;
    align-items: stretch;
  }
}

@media (prefers-reduced-motion: reduce) {
  .consent {
    transform: none;
    transition: none;
  }
}

/* ---------------------------------------------------------------------------
   Proof: what the reference sites put above the argument
   ---------------------------------------------------------------------------
   n8n runs enterprise logos under its fold and GitHub stars in its nav; Zapier
   runs three numbers and then SOC 2 · GDPR. Both answer the same question
   before making a claim: *is this real, and does it touch my stack?*

   Mosaic has no user count worth printing and will not invent one, so the
   proof here is the two things this buyer actually checks — what it connects
   to, and where the data goes.
   --------------------------------------------------------------------------- */

/*
 * The row of facts — parked, not deleted.
 *
 * Taken off the page on 19/09: the slot directly under the hero is where the
 * demo recording goes, and two things competing for it is one too many. The
 * rule stays so putting the row back is a paste of the markup rather than a
 * rebuild — it is still the right answer to "is this safe?", just not the
 * right answer to "what is this?", which is what the space under a hero is for.
 *
 * Not badges, when it returns: an IT manager reads these as conditions of
 * purchase, and a badge invites the question of who awarded it.
 */
.trust {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 26px;
  margin: 30px 0 0;
  padding: 0;
  list-style: none;
}

.trust li {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14.5px;
  color: var(--text-soft);
}

.trust li::before {
  content: '';
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ok);
}

/* The strip of marks. Monochrome and one optical size, so it reads as a list
   of names rather than a wall of borrowed brands — and so the two that are set
   as text sit in it without looking like a mistake. */
.strip {
  padding: 26px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.strip-label {
  margin-bottom: 20px;
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-align: center;
}

.strip-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(20px, 4vw, 52px);
}

.strip-row li {
  display: flex;
  align-items: center;
  height: 26px;
  color: var(--text-dim);
  opacity: 0.85;
  transition: opacity 140ms ease, color 140ms ease;
}

.strip-row li:hover {
  opacity: 1;
  color: var(--text-soft);
}

.strip-row img {
  height: 28px;
  width: auto;
  /* The files are black-on-transparent. Inverting is the one honest way to put
     them on a dark ground without keeping a recoloured copy of somebody else's
     mark on disk — but see `logos/README.md`: several of these brands publish a
     light variant and say to use it, so this is a placeholder for that. */
  filter: grayscale(1) invert(1);
  /* The files are black; this takes them to the strip's grey without needing a
     recoloured copy of somebody else's mark on disk. At 0.55 they sat behind
     the wordmarks beside them, which made the row look half-finished. */
  opacity: 0.66;
}

.strip-row li:hover img {
  opacity: 1;
}

/* On paper the files are already the right colour: black, greyed by the
   opacity above. Only the dark page needs them inverted. */
:root[data-theme='light'] .strip-row img {
  filter: grayscale(1);
}

/* The two whose owners had their glyphs withdrawn from the open set, set in
   the page's own type at the marks' optical size. See `logos/README.md`. */
/* Matched to the glyphs rather than to a heading: at 600 the words shouted
   over the marks and the row stopped reading as one list. */
.strip-row .word {
  font-size: 16.5px;
  font-weight: 500;
  letter-spacing: -0.015em;
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   The product, on a dark ground
   ---------------------------------------------------------------------------
   Every reference site shows its own interface. The page around it stays light
   — that register suits somebody buying on risk — and the product gets the one
   dark band, which is also how the application looks in its own dark mode.
   --------------------------------------------------------------------------- */

/*
 * Was the one dark band in a light page. With the page dark it earns its keep
 * differently: a shade lighter than the ground, so the captures sit on
 * something rather than floating.
 */
.showcase {
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: var(--bg-raised);

  /*
   * The shared flow pieces take their materials from these, and their
   * defaults are the light page's. Set here rather than on `:root` so one
   * dark band can sit in a light page without the row of nodes further up
   * turning dark with it — which is what a root override would do.
   *
   * Without this the tally rendered white on white and the figures the
   * section exists to show were invisible.
   */
  --flow-line: var(--line);
  --flow-fill: var(--bg-panel);
  --flow-tile: color-mix(in srgb, var(--text) 5%, transparent);
  --flow-soft: var(--text-soft);
  --flow-dim: var(--text-dim);
  --flow-cause: var(--accent-wash);
}

.showcase .flow-node,
.showcase .tally div {
  color: var(--text);
}

.showcase .mark {
  border-color: color-mix(in srgb, var(--text) 14%, transparent);
  background: color-mix(in srgb, var(--text) 5%, transparent);
  color: var(--text);
  box-shadow: none;
}

.showcase code {
  color: var(--text-soft);
}

.showcase h2,
.showcase .lede {
  color: var(--text);
}

.showcase .sub {
  color: var(--text-soft);
}

.showcase h2 .dim {
  color: var(--text-soft);
}

.showcase .also {
  color: var(--text-soft);
}

.shot {
  margin-top: 32px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--bg-panel);
  box-shadow: 0 30px 70px rgb(0 0 0 / calc(0.4 * var(--shadow-k)));
}

.shot-bar {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--line);
}

.shot-bar i {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--chrome-dot);
}

.shot-bar b {
  margin-left: 8px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-soft);
}

.shot img {
  width: 100%;
  height: auto;
}

/* Until a real capture is in place, the frame carries the drawn panel and says
   so — a mock dressed as a screenshot is the one thing a sceptical buyer never
   forgives. */
.shot-note {
  margin-top: 14px;
  font-size: 13px;
  color: var(--text-soft);
}

/* ---------------------------------------------------------------------------
   The demo, under the hero
   --------------------------------------------------------------------------- */

.demo {
  padding-top: 0;
  padding-bottom: clamp(36px, 5vw, 64px);
}

/*
 * Never shown larger than it was recorded.
 *
 * The capture is 1920 wide. On a 2x screen a 1060px column is 2120 device
 * pixels, so filling the measure upscales it and it looks exactly as soft as
 * that sounds. Capped at 960 CSS px it lands 1:1 on a 2x display and a little
 * oversampled on a 1x one, which is the right way round.
 */
.demo video {
  display: block;
  width: 100%;
  max-width: 960px;
  height: auto;
  margin-inline: auto;
  background: var(--bg-panel);
}

.demo .shot {
  max-width: 960px;
  margin-inline: auto;
}

/* "Is this safe?" — the same row as before, in the place the question is
   actually asked rather than under the hero. */
.assure {
  padding: clamp(28px, 4vw, 44px) 0;
  border-top: 1px solid var(--line);
}

.assure .trust {
  margin: 0;
  justify-content: center;
}

/* ---------------------------------------------------------------------------
   Plates: a real screen, framed
   ---------------------------------------------------------------------------
   n8n and Zapier both carry a picture of the product in nearly every section,
   and the reason is not decoration: a claim about software is cheap, and the
   software is the evidence. These are captures of the running application with
   the demo estate in it — no mock-ups, no rendered art.

   The frame is the window chrome the `.shot` figure already uses, pulled out so
   a section can drop one in without repeating the markup.
   --------------------------------------------------------------------------- */

.plate {
  margin: 28px 0 0;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--bg-panel);
  box-shadow: 0 24px 60px rgb(0 0 0 / calc(0.35 * var(--shadow-k)));
}

.plate-bar {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
  background: var(--bg-raised);
}

.plate-bar i {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--chrome-dot);
}

.plate-bar b {
  margin-left: 8px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text-dim);
}

.plate img {
  display: block;
  width: 100%;
  height: auto;
}

.plate-note {
  margin-top: 12px;
  font-size: 13.5px;
  color: var(--text-dim);
}

/* Side by side where a section has the room: the words keep their measure and
   the screen takes the rest, rather than a full-width plate pushing the reading
   column down a screenful. */
.beside {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: clamp(24px, 4vw, 52px);
  align-items: center;
}

.beside .plate {
  margin-top: 0;
}

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

/* ---------------------------------------------------------------------------
   Fragments
   ---------------------------------------------------------------------------
   A full application window spends two thirds of the frame on a sidebar nobody
   was asked to look at. Both reference sites crop to the thing itself — the
   nodes, the card, the list — so each picture carries one idea.

   A fragment gets no window chrome either: a title bar is a promise that you
   are looking at a whole screen, and this is deliberately a piece of one. It
   gets a hairline, a soft lift off the page, and a caption in words.
   --------------------------------------------------------------------------- */

.frag {
  margin: 26px 0 0;
}

.frag img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--bg-panel);
  box-shadow: 0 18px 44px rgb(0 0 0 / calc(0.34 * var(--shadow-k)));
}

/* The chain is a wide strip and reads badly squeezed into a text measure, so
   it is allowed past the column on a roomy page. */
.frag.is-wide img {
  border-radius: var(--r-lg);
}

.frag figcaption {
  margin-top: 12px;
  font-size: 13.5px;
  color: var(--text-dim);
}

/*
   A mark on a fragment, for the one part of it the caption is talking about.

   Drawn in CSS over the picture rather than painted into the PNG. A ring baked
   into the file is soft on a retina screen, goes stale the first time the table
   moves a row, and cannot be recoloured with the palette — this one is a real
   border at whatever the device's pixels are, and `fragments.js` prints the
   four percentages on every re-shoot so it lands back on the same rows.

   Red because it is naming the rows that are wrong, which is the one thing on
   this page red is for.
*/
.frag-plate {
  position: relative;
}

.frag-ring {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--w);
  height: var(--h);
  box-sizing: border-box;
  border: 2px solid var(--err);
  border-radius: 9px;
  /* A breath of the same red outside the line, so the ring holds its own
     against a busy table instead of being read as one more cell border. */
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--err) 16%, transparent);
  pointer-events: none;
}

.beside .frag {
  margin-top: 0;
}
