/* ---------------------------------------------------------------------------
   The inner pages
   ---------------------------------------------------------------------------
   Loaded after `shared.css`, which owns the palette, the type, the buttons,
   the bar and the footer. What is here is the inner pages' own furniture: the
   page head, the sections and cards they are built from, the legal pages, the
   support form, the price ladder, the download page, and the framed captures
   of the product.

   Every colour is a name from `shared.css`, so each piece comes out in both
   themes without a rule of its own.
   --------------------------------------------------------------------------- */

/* 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. 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-dark.shot-dark,
:root[data-theme='dark'] img.shot-light.shot-light {
  display: none;
}

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

/* ---------------------------------------------------------------------------
   The ground
   ---------------------------------------------------------------------------
   Flat. The page is ruled, not textured: the opening and the closing stand on
   the plain sheet, like every section between them, and a page head sits
   between two rules of its own. The dot field and the orange bloom that used
   to lift the opening went with mockup D's "no gradients".
   --------------------------------------------------------------------------- */

.open,
.close {
  position: relative;
}

/* The two rules the sheet is ruled with, down the edges of the 1180px column,
   the same as the home page's. Drawn once behind the whole of `main`, so they
   run unbroken through every section's padding, and anything with a ground of
   its own — the page head, a band, a dark panel — covers them, as a full-bleed
   piece should. The docs have their own columns and keep them. */
main:not(.docs-main) {
  position: relative;
  isolation: isolate;
}

main:not(.docs-main)::before {
  content: '';
  position: absolute;
  inset: 0 max(0px, calc((100% - var(--measure)) / 2));
  z-index: -1;
  border-inline: 1px solid var(--line-soft);
  pointer-events: none;
}

/* And a rule across between one section and the next. */
main:not(.docs-main) > section + section {
  border-top: 1px solid var(--line-soft);
}

/* ---------------------------------------------------------------------------
   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: 20ch;
  font-size: clamp(32px, 4.6vw, 48px);
}

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 {
  padding: 14px 26px;
  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 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 -------------------------------- */

/* On white, like the home page's hero, between the bar and its own rule. */
.page-head {
  position: relative;
  padding: clamp(56px, 8vw, 104px) 0 clamp(40px, 5vw, 64px);
  border-bottom: 1px solid var(--line-soft);
  background: var(--panel);
}

.page-head h1 {
  max-width: 15ch;
  font-size: clamp(42px, 5.6vw, 72px);
}

.page-head .lede {
  max-width: 54ch;
  color: var(--text-body);
}

.crumb {
  margin-bottom: 18px;
  font: 400 15px var(--sans);
  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: 24px 24px 26px;
  border-radius: 20px;
  background: var(--card);
}

.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 {
  padding-top: clamp(80px, 12vw, 150px);
  text-align: center;
}

.close h2 {
  max-width: 18ch;
  margin-inline: auto;
  font-size: clamp(40px, 6vw, 66px);
}

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

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

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