/* Voronda — page and section styles. */

.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

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

/* ---------- hero ----------
   The mark, rebuilt from its four real shapes: the arms carry the photograph, the petals
   stay crimson. Faint construction lines run along the arms' own angles. This is the whole
   hero without motion; the pinned version with the dive follows below. */

/* Pulled up under the sticky header, which stays transparent until the page moves. */
.hero { position: relative; isolation: isolate; overflow: hidden;
  margin-top: calc(var(--header-h) * -1);
  padding-block: calc(var(--header-h) + var(--s-6)) clamp(3rem, 1.5rem + 5vw, 6rem); }

.hero__in { display: grid; align-items: center; gap: var(--s-7) var(--s-8);
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  min-height: min(84vh, 50rem); }
.hero__copy { position: relative; z-index: 1; max-width: 52rem; }
.hero__rule { width: 132px; height: 3px; background: var(--c-red); margin-block: var(--s-6) var(--s-5); }
.hero .lead { color: var(--ink-muted); max-width: 42rem; }
.hero .btn { margin-top: var(--s-6); }

.hero__mark { position: relative; }
.vmark { width: 100%; height: auto; overflow: visible; }
.vm__petal { fill: var(--c-crimson); }

.vguides { fill: none; stroke: var(--rule); stroke-width: 1.6; }
.vguides line { stroke-dasharray: 1; }
.vguides .vguides__axis { stroke-dasharray: 6 12; }
.vguides circle { stroke: var(--rule-firm); }

.vm--pa, .vm--pa .vm__drift { transform-box: fill-box; transform-origin: 28% 100%; }
.vm--pb, .vm--pb .vm__drift { transform-box: fill-box; transform-origin: 100% 100%; }
.vm__drift, .vm__img { will-change: transform; }


@media (max-width: 900px) {
  .hero__in { grid-template-columns: 1fr; min-height: 0; gap: var(--s-6); }
  .hero__mark { order: -1; width: min(62%, 20rem); margin-inline: auto; }
  /* The mark stands centred over the words, so the words stand centred under it; the three-word
     headline can be larger than the long ones the display size is floored for. */
  .hero__copy { margin-inline: auto; text-align: center; }
  .hero .display { font-size: clamp(2.5rem, 1.2rem + 8vw, 3.5rem); }
  .hero__rule, .hero .lead { margin-inline: auto; }
}


/* The same mark assembled by scroll rather than on load, for the work and contact pages.
   motion.js drives --ap from 0 to 1 as it arrives; without it --ap stays 1, assembled. */
.vmark--scrub .vm { opacity: calc(var(--ap, 1) * 2); }
.vmark--scrub .vm--pa, .vmark--scrub .vm--pb { opacity: calc((var(--ap, 1) - 0.45) * 2.2); }
.vmark--scrub .vm--l .vm__drift { transform: translate(calc((1 - var(--ap, 1)) * -130px), calc((1 - var(--ap, 1)) * -225px)); }
.vmark--scrub .vm--r .vm__drift { transform: translate(calc((1 - var(--ap, 1)) * 137px), calc((1 - var(--ap, 1)) * -221px)); }
.vmark--scrub .vm--pa .vm__drift { transform: rotate(calc((1 - var(--ap, 1)) * -48deg)) scale(calc(0.3 + var(--ap, 1) * 0.7)); }
.vmark--scrub .vm--pb .vm__drift { transform: rotate(calc((1 - var(--ap, 1)) * 42deg)) scale(calc(0.3 + var(--ap, 1) * 0.7)); }
.vmark--scrub .vm__img { transform: translateY(calc((1 - var(--ap, 1)) * 70px)); }

/* ---------- page heads ----------
   Every inner page opens on the mark drawn as an outline over its construction lines,
   the petals solid. It draws itself on load and drifts on --hp as the head scrolls away. */

.pagehead--v { position: relative; isolation: isolate; overflow: hidden; }
/* Doubled class: the older .pagehead padding sits later in this file. */
.pagehead.pagehead--v { padding-bottom: clamp(3rem, 2rem + 4vw, 6rem); }
.pagehead--v .shell { position: relative; z-index: 1; }
.pagehead--v .display { animation: rise 820ms var(--ease-out-expo) both 120ms; }
.pagehead--v .lead { animation: rise 820ms var(--ease-out-expo) both 320ms; }
.phv { position: absolute; z-index: 0; right: 2vw; top: 50%;
  height: 92%; width: auto; aspect-ratio: 800 / 757; translate: 0 -50%; overflow: visible;
  transform: translateY(calc(var(--hp, 0) * 70px)) rotate(calc(var(--hp, 0) * 5deg));
  pointer-events: none; }
.phv__arms path { fill: none; stroke: var(--rule-firm); stroke-width: 2.2; stroke-dasharray: 1;
  animation: draw 1700ms var(--ease-out-quart) both 200ms; }
.phv__petals path { fill: var(--accent); transform-box: fill-box;
  animation: vm-petal 1200ms var(--ease-out-expo) both 900ms; }
.phv__petals path:first-child { --r0: -48deg; transform-origin: 28% 100%; }
.phv__petals path:last-child { --r0: 42deg; transform-origin: 100% 100%; animation-delay: 1020ms; }
/* On narrow screens the mark sits above the heading, as the home hero's does, rather
   than behind text that now spans the full width. */
@media (max-width: 900px) {
  .pagehead.pagehead--v { padding-top: 12rem; }
  .phv { top: 1.25rem; right: 50%; height: 9.5rem; translate: 50% 0; }
}

/* A hairline under the header that fills with the page. */
.header__progress { position: absolute; left: 0; right: 0; bottom: -1px; height: 2px;
  background: var(--accent); transform: scaleX(var(--sp, 0)); transform-origin: left;
  pointer-events: none; }

/* ---------- rail lists: a red line that fills as the list is read ---------- */

.rail-list { position: relative; }
.rail-list::before, .rail-list::after { content: ""; position: absolute; width: 1px;
  left: calc(0.31em - 0.5px); top: 1.2em; bottom: 1.2em; background: var(--rule-firm); }
.rail-list::after { background: var(--accent); transform-origin: top;
  transform: scaleY(var(--wp, 1)); }
.rail-list > li { position: relative; z-index: 1; }
.rail-list .petal { background: var(--bg); box-shadow: 0 0 0 5px var(--bg); }
.motion .rail-list [data-reveal] .petal { transform: rotate(-120deg) scale(0.4);
  transition: transform 1000ms var(--ease-out-expo) 150ms; }
.motion .rail-list [data-reveal].is-in .petal { transform: none; }

/* ---------- the bright stretch ----------
   After the dark opening the home and school pages turn bright. The stretch rises over the opening
   with a V cut into its top edge that closes as it arrives (--vn, from motion.js, as on the V
   panels); without scripts there is no cut. The close sits on a bed that is bright where its notch
   opens and dark where its point lands on the footer. */
.bright { position: relative; clip-path: polygon(0 0, 50% calc(var(--vn, 0) * 12vw), 100% 0, 100% calc(100% + 2px), 0 calc(100% + 2px)); }
.bright > section:first-child { padding-top: calc(var(--s-section) + 4vw); }
.closer-bed { margin-top: -1px; background: linear-gradient(to bottom, var(--c-paper) 50%, var(--c-carbon) 50%); }
.ground-bright .ui { box-shadow: 0 1.25rem 2.5rem -1.25rem oklch(31.8% 0.005 67.7 / 0.32), 0 0 0 1px oklch(31.8% 0.005 67.7 / 0.05); }
.ground-bright .frame, .ground-bright .mini { box-shadow: 0 1.5rem 3rem -1.5rem oklch(31.8% 0.005 67.7 / 0.4); }

/* Who it's for: the four kinds as words big enough to fill the page, outlined until chosen; the
   one chosen fills red, and its example stands beside them on a white card. */
.who--big .who__grid { grid-template-columns: minmax(0, 0.58fr) minmax(0, 0.42fr); align-items: center; }
.who__h { max-width: 24ch; font-size: var(--t-h3); font-weight: var(--w-display); letter-spacing: var(--track-h); line-height: 1.2; color: var(--ink-muted); }
.who--big .who__side { gap: var(--s-5); }
.who--big .who__pick { max-width: none; gap: 0; }
.who--big .who__pick label { display: block; min-height: 0; padding: 0.06em 0; border-radius: 0; background: none;
  font-size: clamp(2.3rem, 0.9rem + 4.3vw, 5.25rem); font-weight: var(--w-display); line-height: 1; letter-spacing: var(--track-display);
  color: transparent; -webkit-text-stroke: 1.5px var(--ink-muted); cursor: pointer;
  transition: color 260ms var(--ease-out-quart), -webkit-text-stroke-color 260ms var(--ease-out-quart); }
.who--big .who__pick label:hover { background: none; -webkit-text-stroke-color: var(--ink); }
.who--big .who__pick input:checked + label { color: var(--accent); background: none; -webkit-text-stroke-color: transparent; }
.who--big .who__pick input:focus-visible + label { outline-offset: 0.1em; }
@media (forced-colors: active) { .who--big .who__pick label { color: CanvasText; -webkit-text-stroke: 0; } }
.who--big .who__say { background: #fff; box-shadow: 0 1.5rem 3rem -1.5rem oklch(31.8% 0.005 67.7 / 0.35), 0 0 0 1px oklch(31.8% 0.005 67.7 / 0.06); }
@media (max-width: 960px) {
  .who--big .who__grid { grid-template-columns: minmax(0, 1fr); }
  .who--big .who__pick { flex-direction: column; }
}

/* Sections a link can jump to land below the sticky header, not under it. */
section[id] { scroll-margin-top: var(--header-h); }

/* ---------- section heads ----------
   A section opens on its heading and at most one line under it. No labels above headings: where
   a section sits on the page already says what it is. */
.sec-h { font-size: clamp(2.1rem, 1.15rem + 3vw, 3.75rem); font-weight: var(--w-display); line-height: 1.02;
  letter-spacing: var(--track-display); max-width: 19ch; text-wrap: balance; }
.sec-lead { margin-top: var(--s-5); max-width: 46ch; font-size: var(--t-lead); line-height: 1.5; color: var(--ink-muted); }
.sec-foot { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2) var(--s-6); margin-top: var(--s-6); }
.sec-foot p { color: var(--ink-muted); max-width: 46ch; }
/* Over content that runs the full width, the head stands centred, so the space beside it is even. */
.sec-h--center, .sec-lead--center { margin-inline: auto; text-align: center; }
/* What we do carries the details, so its section lines run longer and get a wider measure. */
.sec-lead--wide { max-width: 60ch; }
/* A heading meant to carry its section alone, with no line under it: a step larger. */
.sec-h--big { max-width: 20ch; font-size: clamp(2.5rem, 1.25rem + 3.9vw, 4.5rem); }
.sec-foot--center { flex-direction: column; justify-content: center; text-align: center; }
.sec-foot--center p { max-width: none; }
/* Two links under one section (home, Where it runs): side by side in equal columns, their lines on one
   baseline, each centred in its half; stacked only on a narrow screen. */
.sec-foot__pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: end; gap: var(--s-2) var(--s-6);
  width: min(100%, 40rem); }
.sec-foot__pair .cta { justify-content: center; }
@media (max-width: 560px) { .sec-foot__pair { grid-template-columns: minmax(0, 1fr); width: auto; justify-items: center; } }

/* ---------- the opening ----------
   The claim and the line under it on the left, the app at work on the right, all in the first
   screen. The two are centred against each other, so the space above and below is even. The film
   plays by itself (demos.js). */
/* The opening fills the first screen, so it is dark from edge to edge and the bright stretch only
   comes in as the page moves. */
.intro { display: grid; align-content: center; min-height: calc(100svh - var(--header-h));
  padding-block: clamp(1.75rem, 0.8rem + 3vw, 3.25rem) var(--s-section); }
.intro__grid { display: grid; gap: var(--s-5) var(--s-8); align-items: start;
  grid-template-columns: minmax(0, 0.46fr) minmax(0, 0.54fr); grid-template-rows: 1fr auto auto 1fr;
  grid-template-areas: ". screen" "h screen" "copy screen" ". screen"; }
.intro__h { grid-area: h; font-size: clamp(2.4rem, 1rem + 3.2vw, 4.4rem); }
.intro__h > span { display: block; }
/* Scarlet on carbon is 3.2:1: enough for type this large, and the one place the claim turns red. */
.intro__em { color: var(--accent); }
.intro__copy { grid-area: copy; display: grid; gap: var(--s-6); padding-top: var(--s-2); }
.intro__copy .lead { max-width: 30ch; }
.intro__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3) var(--s-6); }
/* Above the petals that fall when the page is still, below the header. */
.intro__screen { grid-area: screen; align-self: center; position: relative; z-index: calc(var(--z-header) - 1); container-type: inline-size; }
.intro__h > span { animation: rise 820ms var(--ease-out-expo) both 80ms; }
.intro__h > span + span { animation-delay: 200ms; }
.intro__copy { animation: rise 820ms var(--ease-out-expo) both 320ms; }
.intro__screen { animation: rise 900ms var(--ease-out-expo) both 420ms; }
@media (max-width: 960px) {
  .intro__grid { grid-template-columns: minmax(0, 1fr); grid-template-rows: none; grid-template-areas: "h" "copy" "screen"; }
  .intro__copy .lead { max-width: 40ch; }
}
/* The word that turns over in the claim, like a slot machine's reel (demos.js). The script draws the
   line a second time beside the real one (which only screen readers and crawlers keep), with the
   word in a window exactly one line box high, so nothing of a word that is leaving or coming shows
   above or below the line. Only the descender of a g reaches below the line box in Chivo (0.037em,
   measured), so the window reaches 0.05em under it (--rp) without making the line taller; nothing
   reaches above it (a word with an accent on a capital would need room there). The words on the
   reel sit 1.4 lines apart, so the one leaving is gone before the next arrives. The script finds the
   lines as they fall without it (with the widest word instead, should that one not fit) and fixes
   them (.is-set), so a change of word only moves what follows it on its own line, never a word to
   or from the next one. With no script or reduced motion
   none of this exists and the sentence is plain text. */
.reel-text.is-set { white-space: nowrap; }
.reel-glue { white-space: nowrap; }
.reel { --rp: 0.05em; display: inline-block; box-sizing: border-box; vertical-align: top; overflow: hidden;
  height: calc(var(--lh-display) * 1em + var(--rp)); padding-bottom: var(--rp); margin-bottom: calc(-1 * var(--rp)); }
.reel__strip { display: flex; flex-direction: column; align-items: flex-start; }
.reel__w { display: block; white-space: nowrap; }
.reel__w + .reel__w { margin-top: calc(var(--lh-display) * 1.4em); }
/* Pause and play, for a film that would otherwise never stop. Only there while it plays. */
.intro__pause { display: none; }
.motion .intro__pause { position: absolute; right: 1.6cqw; bottom: 1.6cqw; z-index: 2; display: grid; place-items: center;
  width: 2.75rem; height: 2.75rem; padding: 0; border: 1px solid oklch(92.7% 0.007 5.6 / 0.4); border-radius: 50%;
  color: var(--c-white); background: oklch(14.5% 0 0 / 0.66); cursor: pointer; transition: background-color 160ms var(--ease-out-quart); }
.motion .intro__pause:hover { background: var(--c-carbon); }
.intro__pause::before { content: ""; box-sizing: border-box; width: 0.75rem; height: 0.875rem; border: solid currentColor; border-width: 0 0.25rem; }
.intro__pause[aria-pressed="true"]::before { width: 0; height: 0; margin-left: 0.2rem; border-width: 0.45rem 0 0.45rem 0.75rem;
  border-color: transparent transparent transparent currentColor; }

/* ---------- what it does ----------
   The jobs down the side as tabs; the one chosen plays once in the frame beside it (demos.js). It
   never moves on by itself: only the visitor changes the tab. Its line opens under the tab in use.
   The tabs are rows of the hairline grid: a hairline over each and one under the last. The tab in
   use is marked the way every chosen cell is: its rule turns red and the V hangs from its start. */
.tour__grid { display: grid; gap: var(--s-6) var(--s-8); align-items: center; grid-template-columns: minmax(0, 0.42fr) minmax(0, 0.58fr); }
.tour__side { display: grid; align-content: center; }
.tour__tabs { display: grid; gap: 0; margin-top: var(--s-6); border-bottom: var(--hairline); }
.tour__tab { position: relative; display: grid; width: 100%; margin: 0; padding: var(--s-5) var(--s-4) var(--s-5) 0; border: 0;
  border-top: var(--hairline); background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; }
.tour__t { font-size: var(--t-h3); font-weight: var(--w-display); letter-spacing: var(--track-h); line-height: 1.15;
  color: var(--ink-faint-text); transition: color var(--dur) var(--ease-out-quart); }
.tour__tab:hover .tour__t, .tour__tab[aria-selected="true"] .tour__t { color: var(--ink); }
.tour__p { display: grid; grid-template-rows: 0fr; opacity: 0; color: var(--ink-muted);
  transition: grid-template-rows 380ms var(--ease-out-quart), opacity 260ms var(--ease-out-quart); }
.tour__p > span { min-height: 0; overflow: hidden; }
.tour__p > span > span { display: block; max-width: 40ch; padding-top: var(--s-3); }
.tour__tab[aria-selected="true"] .tour__p, html:not(.js) .tour__p { grid-template-rows: 1fr; opacity: 1; }
/* The red rule over the tab in use, drawn from the left when it is chosen, with the V at its start. */
.tour__bar { position: absolute; left: 0; right: 0; top: calc(var(--hair) * -1); height: 2px; background: var(--accent);
  transform: scaleX(0); transform-origin: left; transition: transform 520ms var(--ease-out-expo); }
.tour__tab[aria-selected="true"] .tour__bar { transform: none; }
/* Where the tabs move on by themselves (the school page's How it teaches, [data-tour="auto"]), the
   rule fills as the scene runs, so the next move can be seen coming. demos.js sets --tour-t. */
.tour.is-auto .tour__tab[aria-selected="true"] .tour__bar { transition: none; animation: tour-bar var(--tour-t, 6s) linear both; }
@keyframes tour-bar { from { transform: scaleX(0); } to { transform: none; } }
.tour__bar::after { content: ""; position: absolute; left: 0; top: 0; width: var(--v-tick); height: calc(var(--v-tick) * var(--v-depth));
  background: inherit; clip-path: var(--v-cut); }
.tour__tab:focus-visible { outline: var(--focus); outline-offset: 2px; }
html:not(.js) .tour__tab { cursor: default; }
/* On a phone: the head, then the frame, then the tabs under it. */
@media (max-width: 960px) {
  .tour__grid { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .tour__side { display: contents; }
  .tour__side > .sec-h { order: 1; }
  .tour__side > .sec-lead { order: 2; }
  .tour__stage { order: 3; margin-top: var(--s-6); }
  .tour__tabs { order: 4; }
}

/* ---------- who it's for ----------
   Pick a kind of organisation and it is asked something of theirs, in the type of the headings,
   and answers in the app's own bubble. Radio buttons, so it works without scripts; demos.js types
   the question out and moves on by itself while nobody has picked one. */
.who__grid { display: grid; grid-template-columns: minmax(0, 0.44fr) minmax(0, 0.56fr); gap: var(--s-6) var(--s-8); align-items: stretch; }
.who__side { display: grid; align-content: center; gap: var(--s-6); }
.who__pick { display: flex; flex-direction: column; align-items: stretch; gap: var(--s-2); max-width: 22rem; margin: 0; padding: 0; border: 0; }
.who__pick input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; pointer-events: none; }
.who__pick label { display: inline-flex; align-items: center; min-height: 2.75rem; padding: 0.5rem 1.25rem;
  background: var(--bg-raised); font-size: var(--t-small); font-weight: var(--w-strong); color: var(--ink-muted); cursor: pointer;
  transition: color var(--dur) var(--ease-out-quart), background-color var(--dur) var(--ease-out-quart); }
.who__pick label:hover { color: var(--ink); background: var(--panel); }
.who__pick input:checked + label { color: #fff; background: var(--c-red); }
.who__pick input:focus-visible + label { outline: var(--focus); outline-offset: var(--focus-offset); }
.who__say { display: grid; align-content: center; min-height: 24rem; padding: clamp(1.75rem, 1rem + 2.4vw, 3.5rem); background: var(--bg-raised); }
@media (max-width: 960px) {
  .who__grid { grid-template-columns: minmax(0, 1fr); }
  .who__pick { flex-direction: row; flex-wrap: wrap; max-width: none; }
  .who__say { min-height: 20rem; }
}
.who__ex { grid-area: 1 / 1; display: none; align-content: center; gap: var(--s-5); }
.who:has(#who-1:checked) [data-ex="1"], .who:has(#who-2:checked) [data-ex="2"],
.who:has(#who-3:checked) [data-ex="3"], .who:has(#who-4:checked) [data-ex="4"] { display: grid; }
@supports not selector(:has(*)) { .who__ex[data-ex="1"] { display: grid; } }
.who__q { max-width: 24ch; font-size: clamp(1.5rem, 1rem + 1.4vw, 2.35rem); font-weight: var(--w-display); line-height: 1.05;
  letter-spacing: var(--track-display); text-wrap: balance; }
.who__q::after { content: ""; display: inline-block; width: 0.07em; height: 0.8em; margin-left: 0.1em; vertical-align: -0.05em; background: var(--accent); }
.motion .who__q::after { animation: caret 1.05s steps(1) infinite; }
.who__w { white-space: nowrap; }
.who__ex.is-typing .ch { opacity: 0; animation: ch-on 1ms linear forwards; animation-delay: calc(var(--i, 0) * 30ms); }
@keyframes ch-on { to { opacity: 1; } }
.who__a { display: flex; align-items: flex-start; gap: var(--s-3); justify-self: start; max-width: 36rem; margin: 0; padding: var(--s-4) var(--s-5);
  border-radius: 1.25rem 1.25rem 1.25rem 0.35rem; background: #fff; color: var(--c-charcoal); font-size: var(--t-lead); line-height: 1.45;
  font-weight: var(--w-ui); box-shadow: inset 4px 0 0 var(--c-red), 0 1.25rem 2.5rem -1.25rem oklch(31.8% 0.005 67.7 / 0.3);
  transition: opacity 400ms var(--ease-out-quart), transform 600ms var(--ease-out-expo); }
.who__a .petal { margin-top: 0.2em; color: var(--c-red); }
.who__ex.is-typing:not(.is-answered) .who__a { opacity: 0; transform: translateY(10px); }
.who__ex .cta { justify-self: start; }

/* ---------- control ----------
   Four cells, each a small working piece of the app: permissions large, roles and the log beside
   it, and the visitor's own name across the bottom. The cells are the site, square; what is in
   them is the product, rounded. */
.ctl__grid { display: grid; gap: var(--s-4); margin-top: var(--s-7); grid-template-columns: repeat(12, minmax(0, 1fr)); }
.cell { display: grid; align-content: start; gap: var(--s-3); min-width: 0; padding: clamp(1.4rem, 0.9rem + 1.6vw, 2.5rem);
  background: var(--bg-raised); }
/* One cell carries colour, the stronger blush. (The red name cell became a plain one on 2026-10-16:
   "Fully custom, in your design", the same as the others.) */
.cell--blush { background: var(--panel); }
/* Whatever sits in a cell may shrink with it: a grid item otherwise refuses to go below its
   content, and the picker would push a phone screen sideways. */
.cell > * { min-width: 0; }
.cell h3 { font-size: var(--t-h3); font-weight: var(--w-display); letter-spacing: var(--track-h); line-height: 1.15; }
.cell > p, .cell__copy > p { color: var(--ink-muted); max-width: 42ch; }
.cell > .ui, .cell > .log { margin-top: var(--s-4); }
/* Two rows of two, wide beside narrow and then the other way round. */
.cell--a { grid-column: 1 / 9; }
.cell--c { grid-column: 9 / 13; }
.cell--b { grid-column: 1 / 6; }
.cell--wide { grid-column: 6 / 13; grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); align-items: center; gap: var(--s-5) var(--s-6); }
.cell__copy { display: grid; gap: var(--s-3); align-content: start; }
.cell__copy .btn { justify-self: start; margin-top: var(--s-4); }
.cell--wide.cell--full { grid-column: 1 / -1; grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); }
@media (max-width: 1100px) {
  .cell--a, .cell--b, .cell--c, .cell--wide { grid-column: 1 / -1; }
}
@media (max-width: 700px) { .cell--wide, .cell--wide.cell--full { grid-template-columns: minmax(0, 1fr); } }

/* A text field in the page's own type: the name being typed shows up on the app beside it. */
.field { display: grid; gap: var(--s-2); margin-top: var(--s-3); }
.field label { font-size: var(--t-small); font-weight: var(--w-strong); color: var(--ink-muted); }
.field input { width: 100%; margin: 0; padding: 0.3em 0 0.25em; border: 0; border-bottom: 2px solid var(--rule-firm); border-radius: 0;
  background: transparent; color: var(--ink); font: var(--w-display) clamp(1.4rem, 1rem + 1.4vw, 2rem)/1.15 var(--font);
  letter-spacing: var(--track-h); caret-color: var(--accent); transition: border-color var(--dur) var(--ease-out-quart); }
.field input::placeholder { color: var(--ink-faint-text); opacity: 1; }
.field input:hover { border-bottom-color: var(--ink-muted); }
.field input:focus { outline: none; border-bottom-color: var(--accent); }
/* Beside the little app the field has half a cell: smaller type, so the example fits. */
.cell--wide .field input { font-size: clamp(1.2rem, 0.95rem + 0.7vw, 1.55rem); }

/* ---------- where it runs ----------
   The team's screens, each a window of the app with one question and its answer, wired to one server
   in the middle, inside the building's red walls (no label: the walls say it); outside, the AI services, the line to them cut at
   the wall. On a wide screen it is a drawing: the cards sit on a 1000 by 560 field (the same as the
   SVG's viewBox, so the cables meet their edges), sized in cqw so it scales as one. Narrower than
   760px it stacks: the server, the four windows, then the services outside. demos.js draws it in
   when it arrives and moves data along the cables; without it, it stands finished. */
.srv__fig { margin: var(--s-7) 0 0; container: net / inline-size; }
.net { --c: 1cqw; position: relative; aspect-ratio: 1000 / 560; font-size: calc(var(--c) * 1.05); line-height: 1.3; }
.net__lines { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.net p { margin: 0; }
.bld__wall { fill: none; stroke: var(--c-red); stroke-width: 4; stroke-dasharray: 1; transition: stroke-dashoffset 1600ms var(--ease-out-quart); }
.cable { fill: none; stroke: var(--rule-firm); stroke-width: 2.5; stroke-linecap: round; stroke-dasharray: 1;
  transition: stroke-dashoffset 1100ms var(--ease-out-quart) 500ms; }
.out__line { fill: none; stroke: var(--rule-firm); stroke-width: 2.5; stroke-dasharray: 8 10; }
.out__x { transform-box: fill-box; transform-origin: center; transition: opacity 400ms var(--ease-out-quart) 1500ms, transform 600ms var(--ease-out-expo) 1500ms; }
.out__x path { fill: none; stroke: var(--c-scarlet); stroke-width: 7; stroke-linecap: round; }
.out__x.is-hit { animation: out-hit 520ms var(--ease-out-quart); }
@keyframes out-hit { 40% { transform: scale(1.35); } }
.dots circle { fill: var(--c-red); }
.dots .is-out { fill: var(--c-scarlet); }

.net__dev, .net__srv, .net__out { position: absolute; transition: opacity 600ms var(--ease-out-quart), translate 800ms var(--ease-out-expo); }
.net__dev { width: 19%; display: grid; align-content: start; gap: calc(var(--c) * 0.6); padding: calc(var(--c) * 1) calc(var(--c) * 1.1);
  border-radius: calc(var(--c) * 1); background: #fff; color: var(--c-charcoal);
  box-shadow: 0 calc(var(--c) * 1.2) calc(var(--c) * 2.6) calc(var(--c) * -1.2) oklch(31.8% 0.005 67.7 / 0.38), 0 0 0 1px oklch(31.8% 0.005 67.7 / 0.07); }
/* The top row hangs from 80, the bottom row stands on 504, so every language keeps 40 inside the wall above and below. */
.net__dev--1 { left: 4%; top: 14.3%; } .net__dev--2 { left: 4%; bottom: 10%; }
.net__dev--3 { left: 55%; top: 14.3%; } .net__dev--4 { left: 55%; bottom: 10%; }
.net__who { display: flex; align-items: center; gap: calc(var(--c) * 0.6); font-weight: var(--w-display); }
.net__who i { flex: none; display: grid; place-items: center; width: calc(var(--c) * 1.9); height: calc(var(--c) * 1.9); border-radius: 50%;
  background: var(--c-red); color: #fff; font-style: normal; font-size: calc(var(--c) * 0.9); }
.net__who b { margin-left: auto; font-weight: var(--w-strong); font-size: calc(var(--c) * 0.88); color: var(--c-warmgray); white-space: nowrap; }
.net__me { justify-self: end; max-width: 92%; padding: calc(var(--c) * 0.45) calc(var(--c) * 0.8); font-weight: var(--w-strong);
  border-radius: calc(var(--c) * 0.9) calc(var(--c) * 0.9) calc(var(--c) * 0.25) calc(var(--c) * 0.9); background: var(--c-red); color: #fff; }
.net__ai { justify-self: start; max-width: 96%; padding: calc(var(--c) * 0.45) calc(var(--c) * 0.8);
  border-radius: calc(var(--c) * 0.9) calc(var(--c) * 0.9) calc(var(--c) * 0.9) calc(var(--c) * 0.25);
  background: oklch(97% 0.004 5.6); box-shadow: inset calc(var(--c) * 0.22) 0 0 var(--c-red), inset 0 0 0 1px oklch(31.8% 0.005 67.7 / 0.1); }
.net__srv { left: 31.2%; top: 35.7%; width: 15.6%; height: 32.1%; display: grid; align-content: center; gap: calc(var(--c) * 0.7);
  padding: calc(var(--c) * 1.2); border-radius: calc(var(--c) * 1); background: #262423; color: #fff;
  box-shadow: 0 calc(var(--c) * 1.5) calc(var(--c) * 3) calc(var(--c) * -1.2) oklch(0% 0 0 / 0.5), 0 0 0 1px oklch(100% 0 0 / 0.08); }
.net__srv-h { font-weight: var(--w-display); font-size: calc(var(--c) * 1.3); letter-spacing: -0.01em; }
.net__units { list-style: none; margin: 0; padding: 0; display: grid; gap: calc(var(--c) * 0.45); }
.net__units li { display: flex; align-items: center; gap: calc(var(--c) * 0.6); padding: calc(var(--c) * 0.5) calc(var(--c) * 0.7);
  border-radius: calc(var(--c) * 0.45); background: oklch(100% 0 0 / 0.07); box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.1);
  font-size: calc(var(--c) * 0.95); font-weight: var(--w-ui); }
.net__units li::before { content: ""; flex: none; width: calc(var(--c) * 0.55); height: calc(var(--c) * 0.55); border-radius: 50%; background: var(--c-scarlet); }
.net__out { left: 82.6%; top: 41%; width: 16.4%; display: grid; justify-items: center; gap: calc(var(--c) * 0.3); padding: calc(var(--c) * 1.2) calc(var(--c) * 0.8);
  border: 2px dashed var(--rule-firm); border-radius: calc(var(--c) * 1); text-align: center; color: var(--ink-muted); }
/* The cloud (redrawn 2026-10-16): three round lobes on one flat base, a large one in the middle, drawn
   as one line in the site's line-icon weight with round ends and joins. */
.net__cloud { width: calc(var(--c) * 3.6); height: auto; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.net__out-h { font-weight: var(--w-strong); color: var(--ink); }
.net__out-p { font-size: calc(var(--c) * 0.92); }
/* Arriving: the walls draw round, the server comes in, then the screens one by one, the cables reach
   them, and last the services outside with the cut. */
.net.is-armed:not(.is-on) .bld__wall, .net.is-armed:not(.is-on) .cable { stroke-dashoffset: 1; }
.net.is-armed:not(.is-on) .out__x { opacity: 0; transform: scale(0.4); }
.net.is-armed:not(.is-on) :is(.net__dev, .net__srv, .net__out) { opacity: 0; translate: 0 calc(var(--c) * 1.4); }
.net__srv { transition-delay: 300ms; } .net__dev--1 { transition-delay: 500ms; } .net__dev--3 { transition-delay: 620ms; }
.net__dev--2 { transition-delay: 740ms; } .net__dev--4 { transition-delay: 860ms; } .net__out { transition-delay: 1200ms; }
@keyframes srv-led { 50% { opacity: 0.3; } }
.motion .net.is-on .net__units li::before { animation: srv-led 1.8s steps(1) infinite; }
.motion .net.is-on .net__units li:nth-child(2)::before { animation-delay: -0.6s; }
.motion .net.is-on .net__units li:nth-child(3)::before { animation-delay: -1.2s; }

/* Narrow: no drawing, the same pieces stacked inside the red walls, the services after them. */
@container net (max-width: 759px) {
  .net { --c: 0.55rem; aspect-ratio: auto; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.75rem;
    padding: 0.9rem; border: 3px solid var(--c-red); border-radius: 14px; font-size: 0.8125rem; }
  .net__lines { display: none; }
  .net__dev, .net__srv, .net__out { position: static; width: auto; height: auto; }
  .net__dev { padding: 0.65rem; gap: 0.45rem; border-radius: 0.6rem; }
  .net__srv { grid-column: 1 / -1; order: -1; padding: 0.9rem; gap: 0.5rem; border-radius: 0.6rem; }
  .net__srv-h { font-size: 1rem; }
  .net__units { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .net__units li { font-size: 0.75rem; padding: 0.35rem 0.45rem; }
  .net__who i { width: 1.4rem; height: 1.4rem; font-size: 0.7rem; }
  .net__who b { font-size: 0.6875rem; }
  .net__out { grid-column: 1 / -1; margin-top: 0.4rem; padding: 0.8rem 2.6rem 0.8rem 0.8rem; border-radius: 0.6rem; grid-template-columns: auto 1fr;
    justify-items: start; text-align: left; column-gap: 0.75rem; position: relative; left: auto; top: auto; }
  .net__out-p { font-size: 0.75rem; }
  .net__me, .net__ai { font-size: 0.8125rem; }
  .net__cloud { grid-row: span 2; width: 2.4rem; }
  .net__out::before { content: "\2715"; position: absolute; right: 0.8rem; top: 50%; translate: 0 -50%; color: var(--c-scarlet); font-weight: var(--w-display); font-size: 1.25rem; }
}
@container net (max-width: 420px) { .net { grid-template-columns: minmax(0, 1fr); } }

/* ---------- the four steps, the first one free ----------
   Numbered because the order is the information. A line through four diamonds, the first filled. */
.track { position: relative; list-style: none; margin: var(--s-7) 0 0; padding: 0; display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s-6); }
.track::before { content: ""; position: absolute; left: 1.25rem; right: 0; top: 1.25rem; height: 2px; background: var(--rule-firm); transform-origin: left; }
.track__i { position: relative; display: grid; gap: var(--s-2); align-content: start; padding-top: 3.75rem; }
.track__n { position: absolute; left: 0; top: 0; z-index: 0; display: grid; place-items: center; width: 2.5rem; height: 2.5rem;
  font-size: var(--t-small); font-weight: var(--w-display); color: var(--ink); }
.track__n::before { content: ""; position: absolute; inset: 0; z-index: -1; border-radius: 50%; background: var(--bg); border: 2px solid var(--rule-firm); }
.track__i--free .track__n { color: var(--c-white); }
.track__i--free .track__n::before { background: var(--c-red); border-color: var(--c-red); }
.track h3 { font-size: var(--t-h3); font-weight: var(--w-display); letter-spacing: var(--track-h); line-height: 1.15; }
.track p { color: var(--ink-muted); max-width: 30ch; }
.js .track[data-reveal] { opacity: 1; transform: none; }
.js .track[data-reveal]::before { transition: transform 1100ms var(--ease-out-expo) 120ms; }
.js .track[data-reveal]:not(.is-in)::before { transform: scaleX(0); }
.js .track[data-reveal] .track__i { transition: opacity 600ms var(--ease-out-quart), transform 700ms var(--ease-out-expo); }
.js .track[data-reveal]:not(.is-in) .track__i { opacity: 0; transform: translateY(14px); }
.track__i:nth-child(2) { transition-delay: 120ms; } .track__i:nth-child(3) { transition-delay: 240ms; } .track__i:nth-child(4) { transition-delay: 360ms; }
@media (max-width: 860px) {
  .track { grid-template-columns: minmax(0, 1fr); gap: var(--s-6); }
  .track::before { right: auto; bottom: 1.25rem; width: 2px; height: auto; transform-origin: top; }
  .js .track[data-reveal]:not(.is-in)::before { transform: scaleY(0); }
  .track__i { padding: 0.4rem 0 0 4rem; }
}

/* Steps you can pick (home and school): each has a radio button, hidden, and a label stretched over
   the whole step, so a click or tap anywhere chooses it and the arrow keys move between them. The
   one chosen sits on a soft panel with its title red and its circle filled. Without :has nothing
   changes, and nothing breaks. */
/* In the hairline grid (2026-10-13): the line the steps run along is the grid's top rule, drawn in as
   the steps arrive; hairlines stand between the steps and one closes the row. The step chosen is
   marked as every chosen cell is: its stretch of the top rule turns red and the V hangs from it. */
.track--pick { gap: 0; border-bottom: var(--hairline); }
.track--pick::before { left: 0; right: 0; top: 0; height: var(--hair); background: var(--rule-firm); }
.track--pick .track__i { padding: calc(var(--s-5) + 3.25rem) var(--s-5) var(--s-6); transition: background-color var(--dur) var(--ease-out-quart), opacity 600ms var(--ease-out-quart), transform 700ms var(--ease-out-expo); }
.track--pick .track__i + .track__i { border-left: var(--hairline); }
.track--pick .track__n { left: var(--s-5); top: var(--s-5); }
.track--pick .track__i::before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 2px; background: var(--accent);
  transform: scaleX(0); transform-origin: left; transition: transform 520ms var(--ease-out-expo); }
.track--pick .track__i::after { content: ""; position: absolute; left: var(--s-5); top: 0; width: var(--v-tick); height: calc(var(--v-tick) * var(--v-depth));
  background: var(--accent); clip-path: var(--v-cut); opacity: 0; transition: opacity var(--dur) var(--ease-out-quart); }
.track--pick .track__i:has(.track__pick:checked)::before { transform: none; }
.track--pick .track__i:has(.track__pick:checked)::after { opacity: 1; }
/* The arrival is staggered; a pick is not: the delays are per property. */
.track--pick .track__i:nth-child(2) { transition-delay: 0ms, 120ms, 120ms; }
.track--pick .track__i:nth-child(3) { transition-delay: 0ms, 240ms, 240ms; }
.track--pick .track__i:nth-child(4) { transition-delay: 0ms, 360ms, 360ms; }
.track--pick .track__n::before, .track--pick h3 { transition: background-color var(--dur) var(--ease-out-quart), border-color var(--dur) var(--ease-out-quart), color var(--dur) var(--ease-out-quart); }
.track__pick { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; pointer-events: none; }
.track__hit { position: absolute; inset: 0; z-index: 1; cursor: pointer; }
.track--pick .track__i:hover::before { transform: none; opacity: 0.3; }
.track--pick .track__i:has(.track__pick:checked)::before { opacity: 1; }
.track--pick .track__i:has(.track__pick:checked) h3 { color: var(--accent-text); }
.track--pick .track__i:has(.track__pick:checked) .track__n { color: #fff; }
.track--pick .track__i:has(.track__pick:checked) .track__n::before { background: var(--c-red); border-color: var(--c-red); }
.track--pick .track__i:has(.track__pick:focus-visible) { outline: var(--focus); outline-offset: 2px; }
@media (max-width: 860px) {
  /* Stacked, the steps are rows: a hairline over each, the circle at its start. */
  .track--pick::before { display: none; }
  .track--pick .track__i, .track--pick .track__i + .track__i { border-left: 0; border-top: var(--hairline); }
  .track--pick .track__i { padding: var(--s-5) 0 var(--s-5) 4rem; }
  .track--pick .track__n { left: 0; }
  .track--pick .track__i::after { left: 0; }
}
/* Side by side, the four titles share one row, so every step's text starts on the same line
   however its title wraps, in any language. */
@media (min-width: 861px) {
  .track { grid-template-rows: auto auto; row-gap: var(--s-2); }
  .track__i { grid-row: span 2; grid-template-rows: subgrid; }
}

/* ---------- the founders ----------
   Two cards side by side under a centred heading, everything in them centred: the portrait cut to
   the mark's point, the name, the role in red, a short bio, the links. Stacked on a phone. */
.people { list-style: none; margin: var(--s-7) auto 0; padding: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-4); max-width: 62rem; }
.person { display: grid; justify-items: center; align-content: start; gap: var(--s-3); padding: clamp(1.75rem, 1rem + 2vw, 2.75rem);
  background: var(--bg-raised); text-align: center; }
.photo--portrait { width: min(11rem, 60%); aspect-ratio: 1; margin-bottom: var(--s-3); }
.photo__initial { font-size: clamp(2.5rem, 2rem + 2vw, 3.5rem); font-weight: var(--w-display); line-height: 1; color: var(--accent); }
.photo--portrait .photo__empty { font-size: 0.75rem; }
.person__name { font-size: var(--t-h2); font-weight: var(--w-display); letter-spacing: var(--track-h); line-height: 1.05; }
.person__role { font-weight: var(--w-strong); color: var(--accent-text); }
.person__bio { max-width: 34ch; color: var(--ink-muted); }
.person__links { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--s-2) var(--s-6); margin-top: var(--s-2); }
.person__links .cta { padding-block: var(--s-2); font-size: var(--t-small); }
@media (max-width: 760px) { .people { grid-template-columns: minmax(0, 1fr); } }

/* ---------- the close ----------
   The oxblood V panel: the question, the line to answer it in, and the free demo. The line is the
   email's own sentence with the same blank; site.js carries what is written to the contact page.
   Scripts only. */
.closer .display { max-width: 15ch; font-size: clamp(2.25rem, 1.2rem + 4.2vw, 4.75rem); }
.closer__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-4) var(--s-6); margin-top: var(--s-7); }
.closer__note { max-width: 30ch; font-size: var(--t-small); color: var(--ink-muted); }
.pitch { display: none; }
.js .pitch { display: block; max-width: 38em; margin-top: var(--s-6); font-size: var(--t-h3); line-height: 1.6; cursor: text; }
.js .pitch + .closer__actions { margin-top: var(--s-6); }

/* ---------- what we do: everything handed over ----------
   What you keep, each rising into place as the list arrives. */
.hand { position: relative; list-style: none; margin: 0; padding: 0 var(--s-5); display: grid; gap: var(--s-3); max-width: 30rem; }
.hand__i { position: relative; z-index: 1; display: flex; align-items: center; gap: var(--s-4); padding: var(--s-4) var(--s-5); border-radius: 0.75rem;
  background: #fff; color: var(--c-charcoal); font-weight: var(--w-strong); box-shadow: 0 0.75rem 2rem oklch(31.8% 0.005 67.7 / 0.14); }
.hand__i:nth-child(1) { rotate: -1.5deg; } .hand__i:nth-child(2) { rotate: 1deg; margin-inline: var(--s-4) calc(var(--s-4) * -1); } .hand__i:nth-child(3) { rotate: -0.5deg; }
.hand__i:nth-child(4) { rotate: 0.8deg; margin-inline: calc(var(--s-3) * -1) var(--s-3); }
.hand__ico { flex: none; display: grid; place-items: center; width: 2.25rem; height: 2.25rem; border-radius: 0.55rem; background: var(--c-red); color: var(--c-white);
  font-size: 0.8125rem; font-weight: var(--w-display); letter-spacing: -0.04em; }
.hand__ico--doc { background: var(--c-white); box-shadow: inset 0 0 0 2px var(--c-red);
  background-image: linear-gradient(var(--c-red), var(--c-red)), linear-gradient(var(--c-red), var(--c-red)), linear-gradient(var(--c-red), var(--c-red));
  background-size: 1.1rem 2px, 1.1rem 2px, 0.7rem 2px; background-position: 50% 32%, 50% 50%, 32% 68%; background-repeat: no-repeat; }
/* Support: a ring, the red of a lifebuoy. */
.hand__ico--care { border-radius: 50%; background: radial-gradient(circle, var(--c-white) 0 36%, var(--c-red) 37%); }
.hand__ico--data { border-radius: 50% / 22%; box-shadow: inset 0 -0.5rem 0 -0.3rem oklch(100% 0 0 / 0.35), inset 0 0.5rem 0 -0.3rem oklch(100% 0 0 / 0.35); }
.js .hand[data-reveal] { opacity: 1; transform: none; }
.js .hand[data-reveal] .hand__i { transition: opacity 600ms var(--ease-out-quart), translate 900ms var(--ease-out-expo); }
.js .hand[data-reveal]:not(.is-in) .hand__i { opacity: 0; translate: 0 4rem; }
.hand__i:nth-child(2) { transition-delay: 140ms; } .hand__i:nth-child(3) { transition-delay: 280ms; } .hand__i:nth-child(4) { transition-delay: 420ms; }

/* Five things it will never do: each one struck through as it arrives. */
.never span { text-decoration: line-through; text-decoration-thickness: 2px; text-decoration-color: var(--accent);
  transition: text-decoration-color 700ms var(--ease-out-quart) 400ms; }
.js .never li[data-reveal]:not(.is-in) span { text-decoration-color: transparent; }
.badge { display: inline-flex; align-items: center; gap: var(--s-3); margin-top: var(--s-7); padding: var(--s-3) var(--s-5);
  border: 2px solid var(--accent); font-weight: var(--w-strong); }
.badge .petal { color: var(--accent); }

/* ---------- the header's free demo button, on every page ---------- */

.header__cta { display: inline-flex; align-items: center; white-space: nowrap;
  padding: 0.55rem 0.95rem; background: var(--c-red); color: var(--c-white);
  font-size: var(--t-small); font-weight: var(--w-strong); text-decoration: none;
  transition: background var(--dur) var(--ease-out-quart), transform 160ms var(--ease-out-quart); }
.header__cta:hover, .header__cta:focus-visible { background: var(--c-scarlet); }
.header__cta:active { transform: scale(0.97); }
@media (max-width: 860px) { .header__cta { padding: 0.45rem 0.75rem; } }
/* On the narrowest phones the logo, the demo button and Menu only fit with tighter gaps,
   and below 360px, in the longer languages, only with the mark alone. */
@media (max-width: 400px) { .header__in { gap: var(--s-4); } }
@media (max-width: 359px) { .header .brand__name { display: none; } }
/* German's "Kostenlose Demo" is the longest button label, and needs the name's room a little
   sooner: measured touching at 375px, clear at 390px. */
@media (max-width: 384px) { html:lang(de) .header .brand__name { display: none; } }

/* ---------- rules that draw themselves in ----------
   The site's full-width hairlines, drawn left to right as their row arrives. */

.contact__card, .never li { position: relative; }
.motion .never li[data-reveal]::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
  background: var(--rule); transform: scaleX(0); transform-origin: left;
  transition: transform 1200ms var(--ease-out-expo) 150ms; }
.motion .never li[data-reveal] { border-bottom-color: transparent; }
.motion .never li[data-reveal].is-in::after { transform: none; }
.motion .contact__card[data-reveal] { border-top-color: transparent; }
.motion .contact__card[data-reveal]::before {
  content: ""; position: absolute; left: 0; right: 0; top: -2px; height: 2px;
  background: var(--rule-firm); transform: scaleX(0); transform-origin: left;
  transition: transform 1200ms var(--ease-out-expo) 150ms, background var(--dur) var(--ease-out-quart); }
.motion .contact__card[data-reveal].is-in::before { transform: none; }
.motion .contact__card:not(.contact__card--static):hover::before,
.motion .contact__card:not(.contact__card--static):focus-visible::before { background: var(--accent); }

/* ---------- what we do: the opening ----------
   The claim alone, centred in the dark first screen, so the space around it is even; an outlined
   petal on either side, mirrored, turning a little as the page moves (motion.js sets --vp). */

.intro--center { position: relative; overflow: hidden; padding-block: var(--s-section); text-align: center; }
.intro__stack { position: relative; z-index: 1; display: grid; justify-items: center; gap: var(--s-6); }
.intro--center .intro__h { grid-area: auto; font-size: clamp(2.8rem, 1.2rem + 4.6vw, 5.75rem); max-width: 13ch; }
.intro--center .lead { max-width: 44ch; animation: rise 820ms var(--ease-out-expo) both 320ms; }
.intro--center .intro__actions { justify-content: center; animation: rise 820ms var(--ease-out-expo) both 440ms; }
.intro__petal { position: absolute; top: 50%; right: -5vw; width: min(28vw, 24rem); height: auto; aspect-ratio: 189 / 396;
  translate: 0 -50%; rotate: calc(var(--vp, 0.5) * 40deg - 20deg); color: transparent; stroke: var(--rule-firm); stroke-width: 0.6;
  pointer-events: none; }
.intro__petal--l { right: auto; left: -5vw; scale: -1 1; }
@media (max-width: 860px) {
  .intro__petal { top: auto; bottom: -6%; width: 44vw; translate: none; opacity: 0.55; }
}
/* No script, no demo window: the buttons that would open it are not shown. */
html:not(.js) [data-tryit-open] { display: none; }

/* ---------- what we do: where it lives, drawn ----------
   The same boundary twice: your own building with the rack inside, or one sealed room in
   ours. The lines draw in when the option arrives. */

.odiag { display: block; width: 100%; max-width: 22rem; height: auto; margin-bottom: var(--s-5); overflow: visible; }
.odiag rect { fill: none; stroke: var(--ink-muted); stroke-width: 1.6; stroke-dasharray: 1;
  transition: stroke-dashoffset 1400ms var(--ease-out-expo); }
.odiag .od-wall { stroke: var(--ink); stroke-width: 2.4; }
.odiag .od-wall--ours { stroke: var(--ink-faint); }
.odiag .od-room { transition-delay: 150ms; }
.odiag .od-room--yours { stroke: var(--accent); stroke-width: 2.4; }
.odiag .od-rack rect { transition-delay: 320ms; }
.odiag .od-led { fill: var(--accent); stroke: none; stroke-dasharray: none;
  transition: opacity 400ms ease 1000ms; }
.motion .option[data-reveal]:not(.is-in) .odiag rect:not(.od-led) { stroke-dashoffset: 1; }
.motion .option[data-reveal]:not(.is-in) .odiag .od-led { opacity: 0; }

/* ---------- legal pages: an index of the sections, built from the headings ---------- */

.legal-layout.has-index { display: grid; gap: var(--s-8); align-items: start;
  grid-template-columns: minmax(10rem, 14rem) minmax(0, 46rem); }
.legal-index { position: sticky; top: calc(var(--header-h) + var(--s-6)); font-size: var(--t-small); }
.legal-index:empty { display: none; }
.legal-index ol { list-style: none; padding: 0; display: grid; gap: var(--s-1);
  border-left: 1px solid var(--rule); }
.legal-index a { display: block; margin-left: -1px; padding: var(--s-2) var(--s-4);
  color: var(--ink-muted); text-decoration: none; border-left: 2px solid transparent;
  transition: color var(--dur) var(--ease-out-quart), border-color var(--dur) var(--ease-out-quart); }
.legal-index a:hover { color: var(--ink); }
.legal-index a.is-current { color: var(--ink); border-left-color: var(--accent); }
.legal h2 { scroll-margin-top: calc(var(--header-h) + var(--s-5)); }
@media (max-width: 900px) {
  .legal-layout.has-index { grid-template-columns: 1fr; }
  .legal-index { display: none; }
}

/* ---------- the V panel ----------
   The drenched demo panel, cut to the mark: a notch at the top that closes as the panel
   arrives (motion.js drives --vn from 1 to 0), and a point at the bottom that stays. */

.vpanel { position: relative; overflow: hidden;
  padding-bottom: calc(var(--s-section-lg) + 5vw);
  clip-path: polygon(0 0, 50% calc(var(--vn, 0) * 16vw), 100% 0, 100% calc(100% - 5vw),
    50% 100%, 0 calc(100% - 5vw)); }
.vpanel .shell { position: relative; z-index: 1; }
.vpanel__petal { position: absolute; top: 50%; right: -3vw; z-index: 0;
  width: min(34vw, 30rem); height: auto; aspect-ratio: 189 / 396;
  translate: 0 -50%; rotate: calc(var(--vp, 0.5) * 50deg - 25deg);
  color: transparent; stroke: var(--rule-firm); stroke-width: 0.6; pointer-events: none; }
@media (max-width: 860px) {
  .vpanel__petal { top: 8%; right: -14vw; width: 52vw; translate: none; opacity: 0.6; }
}

/* Headings that wipe up out of their own baseline instead of fading. The clip sits on the
   heading, not on the observed wrapper: an element clipped to nothing never counts as
   intersecting, so it would never be told to reveal. */
.js [data-reveal].wipe { opacity: 1; transform: none; }
.js [data-reveal].wipe > * { transform: translateY(0.35em); clip-path: inset(0 -100vw 100% -100vw);
  transition: clip-path 1100ms var(--ease-out-expo), transform 1100ms var(--ease-out-expo); }
/* Only the bottom edge moves; the sides stay far out so a long word is never cut off. */
.js [data-reveal].wipe.is-in > * { transform: none; clip-path: inset(-0.2em -100vw -0.3em -100vw); }


/* ---------- section scaffolding ----------
   A two-column split: heading holds the left rail, body runs right. It gives every
   section a consistent spine without needing a kicker label above each heading. */

.split { display: grid; gap: var(--s-6) var(--s-8); align-items: start;
  grid-template-columns: minmax(12rem, 20rem) minmax(0, 1fr); }
.split__h { position: sticky; top: calc(72px + var(--s-6)); }
@media (max-width: 860px) {
  .split { grid-template-columns: 1fr; gap: var(--s-5); }
  .split__h { position: static; }
}
.split__body > * + * { margin-top: var(--s-5); }
.split__body .cta { margin-top: var(--s-6); }

/* ---------- the 404 page (written by tools/dist.mjs) ----------
   The mark with one petal fallen off: it drops from its place to the ground under the V's point
   once, as the page opens, and lies there. Everything centred, in one column. */
.nf { min-height: 100svh; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--s-5); padding-block: var(--s-7); text-align: center; }
.nf__brand { position: absolute; top: var(--s-5); left: 50%; translate: -50% 0; }
.nf__v { width: min(56vw, 17rem); height: auto; overflow: visible; }
.nf__ground { stroke: var(--rule-firm); stroke-width: 3; }
.nf__petal { fill: var(--c-crimson); }
.nf__fallen { transform-box: fill-box; transform-origin: 50% 50%; transform: translate(-300px, 572px) rotate(-84deg); }
@media (prefers-reduced-motion: no-preference) {
  .nf__fallen { animation: nf-fall 1700ms cubic-bezier(0.55, 0, 0.75, 0.4) 700ms both; }
}
@keyframes nf-fall {
  0% { transform: none; }
  30% { transform: translate(-24px, 120px) rotate(-16deg); }
  62% { transform: translate(-190px, 370px) rotate(-56deg); }
  100% { transform: translate(-300px, 572px) rotate(-84deg); }
}
.nf__h { max-width: 14ch; }
.nf__p { max-width: 34rem; }
.nf__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--s-4); margin-top: var(--s-2); }
.nf__other { font-size: var(--t-small); color: var(--ink-muted); }
.nf__other a { color: var(--ink); }

/* ---------- photographs ----------
   Real ones only: for now the two founders, on the home page. The photo ends in the mark's point,
   as the oxblood panel does, and holds its 3:2 shape before it is in, so nothing moves when it
   arrives. tools/photo.py makes the files (sized, and stripped of location, camera and date) and
   puts them in the slot. Nothing is laid over a photograph. */
.photo { position: relative; margin: 0; aspect-ratio: 3 / 2; overflow: hidden; background: var(--placeholder-bg);
  clip-path: polygon(0 0, 100% 0, 100% 91%, 50% 100%, 0 91%); }
.photo__img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* Until then: what goes here, quietly, in the middle of the space it will take. */
.photo__empty { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--s-2); padding: var(--s-4) var(--s-4) var(--s-6); text-align: center; font-size: var(--t-small); line-height: 1.35; }
.photo__empty .petal { font-size: 1.5rem; color: var(--ink-faint); }
/* As it arrives, its lower corners fold up into the point: the mark, finishing itself. Keyed to
   .js, which the head sets before the first frame, not to .motion, which comes a moment later and
   would make the point unfold on every load before it ever folded. With reduced motion site.js
   marks it arrived at once and base.css makes the change instant. */
.js .photo[data-reveal] { transition: opacity var(--dur-slow) var(--ease-out-expo), transform var(--dur-slow) var(--ease-out-expo),
  clip-path 1100ms var(--ease-out-expo) 120ms; }
.js .photo[data-reveal]:not(.is-in) { clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 100%, 0 100%); }

/* ---------- the free-demo panel ---------- */

.demo { display: grid; gap: var(--s-6) var(--s-8); align-items: start;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.demo .display { font-size: clamp(2.25rem, 1.2rem + 4.2vw, 4.25rem); }
.demo__body > * + * { margin-top: var(--s-5); }
.demo__body .btn { margin-top: var(--s-6); }
@media (max-width: 860px) { .demo { grid-template-columns: 1fr; } }
/* On the oxblood the demo button turns over: soft white, the mark's other colour, so the
   strongest offer on the page has the strongest button. */
.drench .btn { background: var(--c-white); color: var(--c-oxblood); }
.drench .btn:hover, .drench .btn:focus-visible { background: oklch(97.5% 0.004 5.6); }
.drench .btn .petal { color: var(--c-crimson); }
.drench :focus-visible { outline-color: var(--c-white); }

/* ---------- page heads ---------- */

.pagehead { padding-top: clamp(3rem, 2rem + 5vw, 6.5rem); padding-bottom: var(--s-7); }
.pagehead .display { max-width: 16ch; }
.pagehead .lead { margin-top: var(--s-6); }
/* A page head already carries its own air; the section under it doesn't need to add more. */
.pagehead + .section, .pagehead + .section--lg { padding-top: var(--s-7); }

.footer__tag { margin-top: var(--s-4); color: var(--ink-muted); font-size: var(--t-small);
  max-width: 24ch; }

/* ---------- what we do: the four steps ----------
   Each step beside the email you get at its end, half and half, so the space either side is
   even. The only numbered run on the site: the order is the information. The email slides in
   like new mail once its step is on screen. */

.flow { list-style: none; margin: var(--s-8) 0 0; padding: 0; display: grid; }
.flow__i { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--s-6) var(--s-8); align-items: center;
  padding-block: var(--s-7); border-top: 1px solid var(--rule); }
.flow__i:last-child { border-bottom: 1px solid var(--rule); }
.flow__copy { display: grid; grid-template-columns: 4.5rem minmax(0, 1fr); gap: var(--s-3) var(--s-5); align-content: start; }
.flow__copy > :not(.flow__n) { grid-column: 2; }
.flow__n { grid-row: 1 / span 3; font-size: clamp(2.75rem, 2rem + 2.4vw, 4.25rem); font-weight: var(--w-display); line-height: 0.85;
  letter-spacing: var(--track-display); color: var(--accent); font-variant-numeric: lining-nums tabular-nums; }
.flow__copy h3 { font-size: var(--t-h3); font-weight: var(--w-display); letter-spacing: var(--track-h); line-height: 1.15; }
.flow__copy p { color: var(--ink-muted); max-width: 46ch; }
.flow__copy .flow__you { display: flex; flex-wrap: wrap; gap: 0.15rem 0.6rem; margin-top: var(--s-2); padding-top: var(--s-3);
  border-top: 1px dashed var(--rule-firm); color: var(--ink); font-weight: var(--w-ui); }
.flow__you b { color: var(--accent-text); font-weight: var(--w-strong); }
/* A small sign in the first step, and only there: the staff interviews, with your OK. */
.flow__copy .flow__tag { justify-self: start; display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.3rem 0.75rem;
  border: 1px solid var(--rule-firm); font-size: var(--t-small); font-weight: var(--w-ui); color: var(--ink); }
.flow__tag svg { flex: none; width: 1.05rem; height: 1.05rem; fill: none; stroke: var(--accent); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.js .flow__i[data-reveal] { opacity: 1; transform: none; }
.js .flow__i[data-reveal] .flow__copy { transition: opacity 600ms var(--ease-out-quart), transform 700ms var(--ease-out-expo); }
.js .flow__i[data-reveal] .mail { transition: opacity 500ms var(--ease-out-quart) 180ms, translate 800ms var(--ease-out-expo) 180ms; }
.js .flow__i[data-reveal] .peek { transition: opacity 500ms var(--ease-out-quart) 360ms, translate 800ms var(--ease-out-expo) 360ms; }
.js .flow__i[data-reveal]:not(.is-in) .peek { opacity: 0; translate: 0 1rem; }
.js .flow__i[data-reveal]:not(.is-in) .flow__copy { opacity: 0; transform: translateY(14px); }
.js .flow__i[data-reveal]:not(.is-in) .mail { opacity: 0; translate: 1.5rem 0; }

/* Steps 1 and 3 show what you get and, under it, a small demo: a click opens the demo to try. */
.flow__show { justify-self: center; width: min(100%, 27rem); display: grid; gap: var(--s-4); }
.flow__show > .mail { width: 100%; }
.peek { position: relative; display: block; width: 100%; padding: 0; border: 0; border-radius: 1rem; background: none; cursor: pointer;
  text-align: left; font: inherit; color: inherit; }
.peek .mini { display: block; transition: filter 200ms var(--ease-out-quart); box-shadow: 0 1.25rem 2.5rem -1.25rem oklch(31.8% 0.005 67.7 / 0.45); }
.peek__go { position: absolute; left: 50%; bottom: 1.1rem; z-index: 1; translate: -50% 0; display: inline-flex; align-items: center; gap: 0.6rem;
  padding: 0.6rem 1.1rem; white-space: nowrap; font-size: 0.9375rem; font-weight: var(--w-strong); color: var(--c-white); background: var(--c-red);
  box-shadow: 0 0.75rem 1.5rem -0.5rem oklch(0% 0 0 / 0.5); transition: background-color 160ms var(--ease-out-quart); }
.peek__go .petal { color: var(--c-white); }
.peek:hover .mini { filter: brightness(0.94); }
.peek:hover .peek__go { background: var(--c-crimson); }
.peek:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }

/* The email itself: the product's rounded white, from a person, unread. */
.mail { justify-self: center; width: min(100%, 27rem); display: grid; gap: 0.45rem; padding: 1.1rem 1.25rem 1.25rem; border-radius: 1rem;
  background: #fff; color: var(--c-charcoal); font-size: 0.9375rem; line-height: 1.45;
  box-shadow: 0 1.25rem 2.5rem -1.25rem oklch(31.8% 0.005 67.7 / 0.32), 0 0 0 1px oklch(31.8% 0.005 67.7 / 0.05); }
.mail p { margin: 0; }
.mail__top { display: flex; align-items: center; gap: 0.7rem; margin-bottom: 0.3rem; }
.mail__av { flex: none; display: grid; place-items: center; width: 2.25rem; height: 2.25rem; border-radius: 50%; font-style: normal;
  font-weight: var(--w-display); font-size: 0.9rem; color: #fff; background: var(--c-charcoal); }
.mail__av--r { background: var(--c-red); }
.mail__from { display: grid; line-height: 1.25; }
.mail__from b { font-weight: var(--w-strong); }
.mail__from span { font-size: 0.8125rem; color: var(--c-warmgray); }
.mail__time { margin-left: auto; align-self: start; font-size: 0.8125rem; color: var(--c-warmgray); white-space: nowrap; }
.mail__subj { position: relative; padding-left: 0.95rem; font-weight: var(--w-display); }
.mail__subj::before { content: ""; position: absolute; left: 0; top: 0.5em; width: 0.45rem; height: 0.45rem; border-radius: 50%; background: var(--c-red); }
.mail__body { color: oklch(40% 0.008 50); }
.mail__chip { justify-self: start; display: inline-flex; align-items: center; gap: 0.5rem; margin-top: 0.4rem; padding: 0.4rem 0.8rem;
  border-radius: 0.5rem; font-size: 0.8125rem; font-weight: var(--w-strong); color: var(--c-crimson); box-shadow: inset 0 0 0 1px var(--c-red); }
.mail__chip--link { color: #fff; background: var(--c-red); box-shadow: none; }
.mail__chip--file::before { content: ""; width: 0.8rem; height: 0.95rem; border-radius: 1px 3px 1px 1px; background: var(--c-red);
  box-shadow: inset -0.3rem 0.3rem 0 -0.15rem oklch(100% 0 0 / 0.55); }
.mail__chip--date::before { content: ""; width: 0.85rem; height: 0.8rem; border-radius: 2px; border: 2px solid var(--c-red); border-top-width: 0.3rem; }
/* A meeting, as a calendar shows it: the day and time on a little page, where and who, accepted. */
.meet { gap: 0.6rem; }
.meet__top { display: flex; align-items: center; gap: 0.85rem; }
.meet__cal { flex: none; display: grid; width: 3.4rem; overflow: hidden; border-radius: 0.6rem; text-align: center; line-height: 1.2;
  box-shadow: 0 0 0 1px oklch(31.8% 0.005 67.7 / 0.14); }
.meet__cal b { padding: 0.15rem 0; font-size: 0.75rem; font-weight: var(--w-strong); text-transform: uppercase; letter-spacing: 0.04em;
  color: #fff; background: var(--c-red); }
.meet__cal span { padding: 0.3rem 0 0.35rem; font-size: 0.9375rem; font-weight: var(--w-display); }
.meet__what { display: grid; line-height: 1.3; }
.meet__what b { font-weight: var(--w-display); }
.meet__len { font-size: 0.8125rem; color: var(--c-warmgray); }
.meet__row { display: flex; align-items: center; gap: 0.6rem; color: oklch(40% 0.008 50); }
.meet__row--where::before { content: ""; flex: none; width: 0.7rem; height: 0.7rem; margin-inline: 0.15rem 0.3rem; border-radius: 50% 50% 50% 0;
  rotate: -45deg; box-shadow: inset 0 0 0 0.2rem var(--c-red); }
.meet__row .mail__av { width: 1.6rem; height: 1.6rem; font-size: 0.7rem; }
.meet__ok::before { content: ""; width: 0.3rem; height: 0.6rem; margin: 0 0.2rem 0.15rem 0.1rem; border: solid currentColor;
  border-width: 0 2px 2px 0; rotate: 45deg; }
@media (max-width: 860px) {
  .flow__i { grid-template-columns: minmax(0, 1fr); }
  /* Under its text the email rises into place: from the side it would push past a phone's edge. */
  .js .flow__i[data-reveal]:not(.is-in) .mail { translate: 0 1rem; }
  .mail, .flow__show { justify-self: start; }
}
@media (max-width: 520px) { .flow__copy { grid-template-columns: minmax(0, 1fr); } .flow__copy > :not(.flow__n) { grid-column: 1; } .flow__n { grid-row: auto; } }

/* ---------- the demo to try ----------
   The app in a window over the page, the whole of it in view: as wide as the screen allows, and
   never taller than it. The card that asks whether you want it stands over the app's foot, on
   the oxblood of the demo panel. */

.tryit { width: min(94vw, 76rem, calc((100svh - 11rem) * 16 / 11)); height: fit-content; max-width: none; max-height: none; margin: auto;
  padding: 0; border: 0; overflow: visible;
  background: transparent; color: var(--c-white); }
.tryit::backdrop { background: oklch(10% 0 0 / 0.8); }
.motion .tryit[open] { animation: tryit-in 260ms var(--ease-out-quart); }
.motion .tryit[open]::backdrop { animation: tryit-fade 260ms var(--ease-out-quart); }
@keyframes tryit-in { from { opacity: 0; scale: 0.97; } }
@keyframes tryit-fade { from { opacity: 0; } }
.tryit__bar { display: flex; align-items: center; justify-content: space-between; gap: var(--s-4); margin-bottom: var(--s-3); }
.tryit__h { margin: 0; font-size: var(--t-small); }
.tryit__h b { font-weight: var(--w-strong); }
.tryit__hint { color: oklch(100% 0 0 / 0.72); }
.tryit__x { position: relative; flex: none; width: 2.75rem; height: 2.75rem; padding: 0; border: 1px solid oklch(100% 0 0 / 0.4); background: transparent;
  color: var(--c-white); cursor: pointer; transition: background-color 160ms var(--ease-out-quart); }
.tryit__x::before, .tryit__x::after { content: ""; position: absolute; left: 50%; top: 50%; width: 1.1rem; height: 2px; background: currentColor;
  translate: -50% -50%; rotate: 45deg; }
.tryit__x::after { rotate: -45deg; }
.tryit__x:hover { background: oklch(100% 0 0 / 0.12); }
.tryit__x:focus-visible { outline: 2px solid var(--c-white); outline-offset: 2px; }
.tryit__screen { position: relative; container-type: inline-size; }
.tryit__pop { position: absolute; left: 50%; bottom: 7%; z-index: 7; width: min(92%, 35rem); translate: -50% 0; display: grid; gap: var(--s-3);
  padding: var(--s-5) var(--s-6); box-shadow: 0 1.5rem 3rem oklch(0% 0 0 / 0.45); }
.tryit__pop[hidden] { display: none; }
.motion .tryit__pop.is-in { animation: tryit-pop 280ms var(--ease-out-quart); }
@keyframes tryit-pop { from { opacity: 0; transform: translateY(0.75rem); } }
.tryit__pop p { margin: 0; }
.tryit__pop-h { font-size: var(--t-h3); font-weight: var(--w-display); letter-spacing: var(--track-h); line-height: 1.15; }
.tryit__pop .tryit__pop-go { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3) var(--s-5); margin-top: var(--s-2); }
/* The strip along the foot, there the whole time: this is a preview, and where the real one starts. */
.tryit__mark { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2) var(--s-4); margin: 0; padding: var(--s-3) var(--s-3) var(--s-3) var(--s-5);
  font-size: var(--t-small); font-weight: var(--w-ui); }
.tryit__mark > .petal { flex: none; }
.tryit__mark > span { flex: 1 1 16rem; }
.tryit__mark .btn { min-height: 2.75rem; padding: 0.5rem 1.1rem; font-size: var(--t-small); }
.tryit__keep { padding: var(--s-2) 0; border: 0; border-bottom: 1px solid oklch(100% 0 0 / 0.5); background: none; color: inherit; font: inherit;
  font-weight: var(--w-strong); cursor: pointer; }
.tryit__keep:hover { border-bottom-color: currentColor; }

/* ---------- what we do: how we work ----------
   Four cells, two by two, the stronger blush on the diagonal. Each ends in a small working piece
   of the product, standing on the cell's foot so the pieces line up across a row. */

.how { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-4); margin-top: var(--s-7); }
.how__c { grid-template-rows: auto auto 1fr; }
.cell.how__c > .ui { align-self: end; width: min(100%, 26rem); margin-top: var(--s-5); }
@media (max-width: 760px) { .how { grid-template-columns: minmax(0, 1fr); } }

/* ---------- what we do: the two places it can run ----------
   Each on its own line under the heading, the drawing above its name. */

.where .options { margin-top: var(--s-7); }
.options { display: grid; gap: var(--s-7); margin-top: var(--s-8);
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr)); }
.option { padding-top: var(--s-5); border-top: 2px solid var(--rule-firm); }
.option h3 { font-size: var(--t-h3); font-weight: var(--w-display); letter-spacing: var(--track-h); margin-bottom: var(--s-4); }
.option p { color: var(--ink-muted); }
.options__note { margin-top: var(--s-8); font-size: var(--t-lead); max-width: 44ch; line-height: 1.45; }

/* ---------- what we do: yours, and never ----------
   What you keep and what it never does, side by side at the same height. */

.own { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-7) var(--s-8); align-items: start; }
.own__col { display: grid; gap: var(--s-5); align-content: start; min-width: 0; }
.own__h { font-size: clamp(1.9rem, 1.2rem + 2vw, 2.9rem); max-width: 14ch; }
.own__p { color: var(--ink-muted); max-width: 42ch; }
.own .hand { margin-top: var(--s-3); }
.own .badge { justify-self: start; margin-top: var(--s-3); }
@media (max-width: 860px) { .own { grid-template-columns: minmax(0, 1fr); gap: var(--s-section); } }

/* ---------- the never list ----------
   Full-width rules, not the coloured side-stripe that usually turns up here. */

.never { list-style: none; padding: 0; margin: 0; display: grid; }
.never li { padding-block: var(--s-4); border-bottom: 1px solid var(--rule);
  font-weight: var(--w-ui); }
.never li:first-child { border-top: 1px solid var(--rule); }

/* ---------- contact ---------- */

.contact { display: grid; gap: var(--s-5);
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
.contact__card {
  display: grid; gap: var(--s-2); align-content: start;
  padding: var(--s-6) var(--s-5) var(--s-6) 0;
  border-top: 2px solid var(--rule-firm);
  text-decoration: none;
  transition: border-color var(--dur) var(--ease-out-quart);
}
.contact__card:not(.contact__card--static):hover,
.contact__card:not(.contact__card--static):focus-visible { border-top-color: var(--accent); }
.contact__v { font-size: var(--t-h3); font-weight: var(--w-display);
  letter-spacing: var(--track-h); word-break: break-word; }
.contact__card:not(.contact__card--static):hover .contact__v { color: var(--accent-text); }
.contact__n { color: var(--ink-muted); font-size: var(--t-small); max-width: 30ch; }

/* ---------- contact: the page is the email ----------
   The headline is the subject line and the body is the email itself, set large on the dark
   ground. What the visitor types keeps the letter's ink, underlined in red; the one place their
   words turn red is the headline, where their organisation's name lands. */

.write { position: relative; overflow-x: clip; padding-block: clamp(2.5rem, 1.4rem + 4.4vw, 6rem) var(--s-section);
  scroll-margin-top: var(--header-h); }
.write > .shell { position: relative; z-index: 1; }
/* The petal's outline, brought over from the free-demo panel: most of it on screen, the rest
   past the right edge, turning a little as the email scrolls by. */
.write__petal { position: absolute; top: 68%; right: -3vw; z-index: 0; width: min(34vw, 30rem); height: auto;
  aspect-ratio: 189 / 396; translate: 0 -50%; rotate: calc(var(--vp, 0.5) * 50deg - 25deg);
  color: transparent; stroke: var(--rule-firm); stroke-width: 0.6; pointer-events: none; }
@media (max-width: 860px) {
  .write__petal { top: auto; bottom: 4%; right: -18vw; width: 58vw; translate: none; opacity: 0.45; }
}
.write__grid { display: grid; gap: var(--s-7) var(--s-9); align-items: start;
  grid-template-columns: minmax(0, 1fr) minmax(13rem, 18rem); }
.write__how { position: sticky; top: calc(var(--header-h) + var(--s-6)); padding-top: var(--s-3); }
.write__steps { list-style: none; padding: 0; margin: 0; counter-reset: step; display: grid; gap: var(--s-4); }
.write__steps li { counter-increment: step; display: grid; grid-template-columns: 1.5em minmax(0, 1fr); gap: var(--s-3);
  padding-top: var(--s-3); border-top: 1px solid var(--rule); color: var(--ink-muted); font-size: var(--t-small); line-height: 1.5; }
.write__steps li::before { content: counter(step); color: var(--accent); font-weight: var(--w-display); font-size: var(--t-body); line-height: 1.3; }
@media (max-width: 960px) {
  .write__grid { grid-template-columns: 1fr; gap: var(--s-7); }
  .write__how { position: static; max-width: 34rem; }
}

.letter { position: relative; max-width: 56rem; }
.letter__head { margin: 0; }
.letter__head > div { border-bottom: 1px solid var(--rule); }
.letter__head dd { margin: 0; }
.letter__to { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-2) var(--s-4); padding-block: var(--s-3); }
.letter__to dt { min-width: 5.5rem; }
.letter__k { color: var(--ink-muted); }
.letter__subject { display: grid; gap: var(--s-3); padding-block: var(--s-4) var(--s-6); }
.letter__h { font-size: var(--t-display); font-weight: var(--w-display); line-height: var(--lh-display);
  letter-spacing: var(--track-display); text-wrap: balance; overflow-wrap: break-word; }
.letter__h .mirror { color: var(--accent); }
.letter__h .mirror.is-empty { color: var(--ink-faint-text); }
.letter__body { margin-top: var(--s-6); font-size: var(--t-h3); line-height: 1.6; max-width: 38em; }
.letter__body p + p { margin-top: 0.75em; }

.blank { color: var(--ink); padding: 0 0.1em; outline: none; cursor: text; overflow-wrap: anywhere;
  border-bottom: 2px solid var(--rule-firm); caret-color: var(--accent);
  box-decoration-break: clone; -webkit-box-decoration-break: clone;
  transition: border-color var(--dur) var(--ease-out-quart), background-color var(--dur) var(--ease-out-quart); }
.blank:empty::before { content: attr(data-ph); color: var(--ink-faint-text); pointer-events: none; }
.blank:not(:empty) { border-bottom-color: var(--accent); }
.blank:hover { border-bottom-color: var(--ink-muted); }
.blank:not(:empty):hover { border-bottom-color: var(--accent); }
/* The blank being written in lights up, framed in red, with a flash as it is entered; letter.js
   draws its thick caret, so the hairline one is hidden while that is showing. */
.blank:focus { border-bottom-color: var(--accent); background-color: oklch(59.2% 0.241 29.2 / 0.2);
  box-shadow: 0 0 0 2px oklch(59.2% 0.241 29.2 / 0.65); border-radius: 2px; }
.motion .blank:focus { animation: blank-on 560ms var(--ease-out-expo); }
@keyframes blank-on { from { background-color: oklch(59.2% 0.241 29.2 / 0.5); } }
.blank.has-caret { caret-color: transparent; }
.blank[aria-invalid="true"] { border-bottom-color: var(--accent); background-color: oklch(59.2% 0.241 29.2 / 0.18); }
.blank[aria-invalid="true"]:empty::before { color: var(--c-rose); }
/* The closing line's hint being typed (site.js) has a caret of its own: steady while the keys go
   down, blinking while an example holds. Stepping in hands over to the real one (site.js). */
.motion .blank.is-eg:empty::after { content: ""; display: inline-block; width: 0.08em; height: 1.05em;
  margin-left: 0.05em; vertical-align: -0.18em; background: var(--accent); animation: eg-blink 1.1s steps(1) infinite; }
.motion .blank.is-eg.is-eg-keys:empty::after { animation: none; }
@keyframes eg-blink { 50% { opacity: 0; } }
.mirror { cursor: text; }
.mirror.is-empty { color: var(--ink-faint-text); }

.letter__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-4); margin-top: var(--s-7); }
.letter__status { flex-basis: 100%; font-size: var(--t-small); color: var(--c-rose); }
.letter__status:empty { display: none; }
html:not(.js) .letter__copy { display: none; }

/* Sent: the email steps back, and a note takes the buttons' place. */
.letter__head, .letter__body { transition: opacity var(--dur-slow) var(--ease-out-expo); }
.write.is-sent .letter__head, .write.is-sent .letter__body { opacity: 0.36; }
.write.is-sent .letter__actions { display: none; }
.letter__sent { margin-top: var(--s-7); max-width: 36rem; }
.letter__sent[hidden] { display: none; }
.letter__sent:focus { outline: none; }
.letter__sent-h { display: flex; gap: var(--s-3); align-items: baseline; font-size: var(--t-h3);
  font-weight: var(--w-display); letter-spacing: var(--track-h); line-height: var(--lh-h); }
.letter__sent-h .petal { flex: none; font-size: 1.1em; color: var(--accent); }
.letter__sent p + p { margin-top: var(--s-3); color: var(--ink-muted); }
.letter__sent-alt { font-size: var(--t-small); }
.letter__sent-actions { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-top: var(--s-5); }
.motion .letter__sent:not([hidden]) { animation: rise 700ms var(--ease-out-expo) both; }

/* Arrival: letter.js types the email out once. Each word waits hidden until it is written;
   the red caret sits after the last one, then waits in the first blank. */
.tw { opacity: 0; }
.tw.is-on { opacity: 1; }
/* Out of sight until letter.js has decided whether to type the email out (and, in another
   language, until the words have arrived), so the finished email never flashes up first. If the
   script never arrives, it shows anyway after a moment. Without scripts none of this applies. */
.js .write:not(.is-ready) .letter__head, .js .write:not(.is-ready) .letter__body,
.js .write:not(.is-ready) .letter__actions, .js .write:not(.is-ready) .write__how {
  opacity: 0; transition: none; animation: write-show 0s linear 2.5s forwards; }
@keyframes write-show { to { opacity: 1; } }
.write.is-typing .letter__actions, .write.is-typing .write__how { opacity: 0; transition: none; }
.letter__actions, .write__how { transition: opacity 900ms var(--ease-out-expo); }
.letter__caret { position: absolute; left: 0; top: 0; z-index: 2; width: 3px; background: var(--accent);
  pointer-events: none; opacity: 0; }
.letter__caret.is-on { opacity: 1; }
.letter__caret.is-waiting { animation: caret 1.05s steps(1) infinite; }
@keyframes caret { 50% { opacity: 0; } }

/* Sending: a selection sweeps down the email, line by line, as it goes to the email app. */
.letter__sel { position: absolute; inset: 0; pointer-events: none; z-index: 1; }
.letter__sel i { position: absolute; background: oklch(59.2% 0.241 29.2 / 0.34); transform: scaleX(0);
  transform-origin: left; transition: transform 260ms var(--ease-out-quart); }
.letter__sel.is-on i { transform: none; }
.letter__sel.is-out { opacity: 0; transition: opacity 700ms var(--ease-out-expo); }


/* ---------- sign-off ---------- */

.signoff { display: grid; gap: var(--s-6) var(--s-8); align-items: center;
  grid-template-columns: minmax(0, 0.45fr) minmax(0, 1fr); }
.signoff__media { max-width: 16rem; }
.signoff__copy .lead { margin-top: var(--s-5); }
@media (max-width: 760px) {
  .signoff { grid-template-columns: 1fr; }
  .signoff__media { max-width: 11rem; }
}

/* ---------- legal pages ----------
   Plain and quick to read. A lawyer or a data protection officer is the reader here,
   and they are not looking for art direction. */

.legal { max-width: 46rem; }
.legal h2 { font-size: var(--t-h3); font-weight: var(--w-display);
  letter-spacing: var(--track-h); margin-top: var(--s-8); margin-bottom: var(--s-4); }
.legal h3 { font-size: var(--t-body); font-weight: var(--w-strong);
  margin-top: var(--s-6); margin-bottom: var(--s-3); }
.legal p, .legal li { color: var(--ink-muted); }
.legal p + p { margin-top: var(--s-4); }
.legal ul { margin-top: var(--s-4); padding-left: 1.1em; display: grid; gap: var(--s-3); }
.legal dl { margin-top: var(--s-4); display: grid; gap: var(--s-3); }
.legal dt { font-weight: var(--w-strong); color: var(--ink); }
.legal dd { margin: 0; color: var(--ink-muted); }
.legal .note {
  margin-top: var(--s-6); padding: var(--s-5);
  border: 1px dashed var(--rule-firm); color: var(--ink-muted); font-size: var(--t-small);
}

/* ---------- faq: ask it here ----------
   A search field over the questions, answered in the browser. js/faq.js scores every
   question against what is typed, slides the list into the new order, underlines the words
   that matched and opens the best answer. Nothing typed is sent anywhere, and the counter
   under the field reads the browser's own record of requests to show it. Without scripts
   the field stays hidden and each question is a plain disclosure that opens on its own. */

.ask { display: none; padding-block: 0 var(--s-6); scroll-margin-top: calc(var(--header-h) + var(--s-4)); }
.js .ask { display: block; }
.ask__field { position: relative; margin-top: var(--s-3); border-bottom: 2px solid var(--rule-firm); }
.ask__input { display: block; width: 100%; margin: 0; padding: 0.3em 0 0.25em; border: 0; border-radius: 0;
  background: transparent; color: var(--ink); caret-color: var(--accent);
  font: var(--w-display) clamp(1.75rem, 1rem + 3.4vw, 3.75rem)/1.1 var(--font); letter-spacing: var(--track-display);
  -webkit-appearance: none; appearance: none; }
.ask__input::placeholder { color: var(--ink-faint-text); opacity: 1; }
.ask__input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.ask__input:focus { outline: none; }
/* The underline fills red while the field has focus: the one place this page listens. */
.ask__caret { position: absolute; left: 0; right: 0; bottom: -2px; height: 2px; background: var(--accent);
  transform: scaleX(0); transform-origin: left; transition: transform var(--dur-slow) var(--ease-out-expo); }
.ask__field:focus-within .ask__caret { transform: none; }
.ask__meta { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-6); margin-top: var(--s-5);
  font-size: var(--t-small); color: var(--ink-muted); }
.ask__local { display: inline-flex; align-items: center; gap: var(--s-2); }
.ask__local .petal { width: 0.7rem; height: 1.4rem; color: var(--accent); }
.ask__sent b { display: inline-block; margin-left: var(--s-2); color: var(--accent); font-size: 1.3em;
  font-variant-numeric: tabular-nums; }
.ask__sent b.is-tick { animation: ask-tick 420ms var(--ease-out-expo); }
@keyframes ask-tick { 30% { transform: scale(1.5); } to { transform: none; } }
.ask__none { margin-top: var(--s-5); color: var(--ink-muted); }
.ask__none a { color: var(--accent-text); }

.faq { padding-block: var(--s-4) var(--s-section); }
.faq__list { position: relative; border-top: 1px solid var(--rule-firm); }
/* A red line sweeps down the list each time the question changes: the search pass, drawn. */
.faq__list::after { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 2px; pointer-events: none;
  background: linear-gradient(90deg, transparent, var(--accent) 30%, var(--accent) 70%, transparent); opacity: 0; }
.faq__list.is-scan::after { animation: faq-scan 700ms var(--ease-out-quart); }
@keyframes faq-scan { from { opacity: 1; transform: translateY(0); } 85% { opacity: 1; } to { opacity: 0; transform: translateY(var(--faq-h, 600px)); } }

.qa { position: relative; border-bottom: 1px solid var(--rule-firm); }
.js .qa[data-reveal] { transition: opacity var(--dur-slow) var(--ease-out-expo), transform var(--dur-slow) var(--ease-out-expo); }
.js .qa[data-reveal].is-dim { opacity: 0.3; }
.qa::before { content: ""; position: absolute; left: calc(var(--s-5) * -1); top: -1px; bottom: -1px; width: 3px;
  background: var(--accent); transform: scaleY(0); transform-origin: top;
  transition: transform var(--dur-slow) var(--ease-out-expo); }
.qa[open]::before, .qa.is-best::before { transform: none; }
.qa__s { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline;
  gap: var(--s-3) var(--s-6); padding-block: var(--s-5); cursor: pointer; list-style: none; }
.qa__s::-webkit-details-marker { display: none; }
.qa__s:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.qa__q { font-size: var(--t-h3); font-weight: var(--w-display); line-height: 1.2; letter-spacing: var(--track-h);
  transition: transform var(--dur) var(--ease-out-expo), color var(--dur);
  text-wrap: wrap; }  /* a heading now, but wrapped like the plain text it was */
.qa__s:hover .qa__q { transform: translateX(6px); }
.qa[open] .qa__q { color: var(--ink); }
/* A small V that turns over when the answer is open. */
.qa__mark { width: 0.7rem; height: 0.7rem; margin-right: 0.3rem; border-right: 2px solid var(--ink-muted);
  border-bottom: 2px solid var(--ink-muted); transform: translateY(-0.2rem) rotate(45deg);
  transition: transform var(--dur-slow) var(--ease-out-expo), border-color var(--dur); }
.qa[open] .qa__mark { transform: translateY(0.15rem) rotate(-135deg); border-color: var(--accent); }
.qa__a { overflow: hidden; }
.qa__a p { padding-bottom: var(--s-6); color: var(--ink-muted); }
.hit { color: inherit; background: linear-gradient(var(--accent), var(--accent)) 0 100% / 0 0.12em no-repeat;
  transition: background-size var(--dur-slow) var(--ease-out-expo); }
.hit.is-on { background-size: 100% 0.12em; }
.qa__q .hit { color: var(--ink); }

@media (max-width: 760px) {
  .qa__s { gap: var(--s-2) var(--s-4); }
  .qa::before { left: calc(var(--s-3) * -1); }
}
@media (prefers-reduced-motion: reduce) {
  .faq__list.is-scan::after { animation: none; }
  .ask__sent b.is-tick { animation: none; }
}

/* ---------- faq: the cherry branch ----------
   Grown once by a script and written into the page head as vector shapes, so it is sharp at
   any size; reds only. The limb draws in from the right edge a segment at a time, blossoms
   open along it, and it sways a little. A few petals fall across the head. */

.cb { position: absolute; z-index: 0; top: 0; right: 0; width: min(58vw, 840px); height: auto;
  overflow: visible; pointer-events: none; }
.cb__sway { transform-box: view-box; transform-origin: 100% 0%; animation: cb-sway 9s ease-in-out 2.8s infinite alternate; }
@keyframes cb-sway { from { transform: none; } to { transform: rotate(1.3deg); } }
.cb__b { fill: none; stroke: oklch(38% 0.018 40); stroke-linecap: round; stroke-dasharray: 1;
  animation: draw var(--t, 400ms) linear both var(--d, 0ms); }
.cb__o, .cb__bud { transform-box: fill-box; transform-origin: 50% 50%;
  animation: cb-bloom 900ms var(--ease-out-expo) both var(--d, 0ms); }
.cb__o circle { fill: var(--c-oxblood); }
.cb__bud { fill: var(--c-crimson); animation-duration: 700ms; }
@keyframes cb-bloom { from { opacity: 0; transform: scale(0) rotate(-50deg); } to { opacity: 1; transform: none; } }

.cb-fall { position: absolute; z-index: 0; inset: 0; overflow: hidden; pointer-events: none; }
.cb-fall i { position: absolute; top: 3%; left: var(--x); width: var(--s); height: calc(var(--s) * 1.2);
  border-radius: 75% 15% 75% 15%; background: var(--c-red); opacity: 0;
  animation: cb-fall var(--t) linear var(--d) infinite; }
.cb-fall i:nth-child(1) { --x: 62%; --s: 11px; --t: 12s; --d: 3.4s; background: var(--c-scarlet); }
.cb-fall i:nth-child(2) { --x: 74%; --s: 9px;  --t: 14s; --d: 5.8s; }
.cb-fall i:nth-child(3) { --x: 84%; --s: 12px; --t: 11s; --d: 8.1s; background: var(--c-crimson); }
.cb-fall i:nth-child(4) { --x: 55%; --s: 8px;  --t: 15s; --d: 10.5s; background: var(--c-scarlet); }
.cb-fall i:nth-child(5) { --x: 91%; --s: 10px; --t: 13s; --d: 4.6s; }
.cb-fall i:nth-child(6) { --x: 68%; --s: 9px;  --t: 16s; --d: 13s; background: var(--c-crimson); }
.cb-fall i:nth-child(7) { --x: 79%; --s: 11px; --t: 12.5s; --d: 16s; background: var(--c-scarlet); }
@keyframes cb-fall {
  0% { opacity: 0; transform: translate(0, 0) rotate(0deg); }
  8% { opacity: 0.95; }
  30% { transform: translate(-46px, 150px) rotate(150deg); }
  55% { transform: translate(-14px, 290px) rotate(290deg); }
  82% { opacity: 0.9; transform: translate(-78px, 450px) rotate(450deg); }
  100% { opacity: 0; transform: translate(-60px, 560px) rotate(540deg); }
}

@media (max-width: 900px) {
  .cb { width: 112vw; right: -14vw; top: -0.5rem; }
}
@media (prefers-reduced-motion: reduce) {
  .cb__sway, .cb__b, .cb__o, .cb__bud { animation: none; }
  .cb-fall { display: none; }
}

/* The inline styles, moved here so the pages need no inline styles at all (the security
   policy allows none). Each rule is one element, found by its place in the markup; they
   were written by a script from the markup as it was, and change only with it. */
.petal-list--loose { gap: var(--s-4); }
.cb__sway > :nth-child(1) { --d: 0ms; --t: 120ms; stroke-width: 15; }
.cb__sway > :nth-child(2) { --d: 85ms; --t: 120ms; stroke-width: 14.1; }
.cb__sway > :nth-child(3) { --d: 168ms; --t: 120ms; stroke-width: 13.3; }
.cb__sway > :nth-child(4) { --d: 252ms; --t: 120ms; stroke-width: 12.4; }
.cb__sway > :nth-child(5) { --d: 336ms; --t: 120ms; stroke-width: 11.6; }
.cb__sway > :nth-child(6) { --d: 421ms; --t: 120ms; stroke-width: 10.7; }
.cb__sway > :nth-child(7) { --d: 507ms; --t: 120ms; stroke-width: 9.9; }
.cb__sway > :nth-child(8) { --d: 593ms; --t: 120ms; stroke-width: 9; }
.cb__sway > :nth-child(9) { --d: 679ms; --t: 120ms; stroke-width: 8.1; }
.cb__sway > :nth-child(10) { --d: 763ms; --t: 120ms; stroke-width: 7.3; }
.cb__sway > :nth-child(11) { --d: 847ms; --t: 120ms; stroke-width: 6.4; }
.cb__sway > :nth-child(12) { --d: 928ms; --t: 120ms; stroke-width: 5.6; }
.cb__sway > :nth-child(13) { --d: 1007ms; --t: 120ms; stroke-width: 4.7; }
.cb__sway > :nth-child(14) { --d: 1085ms; --t: 120ms; stroke-width: 3.9; }
.cb__sway > :nth-child(15) { --d: 194ms; --t: 380ms; stroke-width: 6; }
.cb__sway > :nth-child(16) { --d: 293ms; --t: 380ms; stroke-width: 5.6; }
.cb__sway > :nth-child(17) { --d: 373ms; --t: 380ms; stroke-width: 5.2; }
.cb__sway > :nth-child(18) { --d: 460ms; --t: 380ms; stroke-width: 4.8; }
.cb__sway > :nth-child(19) { --d: 553ms; --t: 380ms; stroke-width: 4.4; }
.cb__sway > :nth-child(20) { --d: 627ms; --t: 380ms; stroke-width: 4.1; }
.cb__sway > :nth-child(21) { --d: 707ms; --t: 380ms; stroke-width: 3.8; }
.cb__sway > :nth-child(22) { --d: 803ms; --t: 380ms; stroke-width: 3.3; }
.cb__sway > :nth-child(23) { --d: 891ms; --t: 380ms; stroke-width: 3; }
.cb__sway > :nth-child(24) { --d: 975ms; --t: 380ms; stroke-width: 2.6; }
.cb__sway > :nth-child(25) { --d: 1067ms; --t: 380ms; stroke-width: 2.2; }
.cb__sway > :nth-child(26) { --d: 1153ms; --t: 380ms; stroke-width: 1.8; }
.cb__sway > :nth-child(27) { --d: 1253ms; --t: 380ms; stroke-width: 1.6; }
.cb__sway > :nth-child(28) { --d: 465ms; --t: 120ms; stroke-width: 7; }
.cb__sway > :nth-child(29) { --d: 502ms; --t: 120ms; stroke-width: 6.6; }
.cb__sway > :nth-child(30) { --d: 540ms; --t: 120ms; stroke-width: 6.3; }
.cb__sway > :nth-child(31) { --d: 581ms; --t: 120ms; stroke-width: 5.9; }
.cb__sway > :nth-child(32) { --d: 623ms; --t: 120ms; stroke-width: 5.6; }
.cb__sway > :nth-child(33) { --d: 665ms; --t: 120ms; stroke-width: 5.2; }
.cb__sway > :nth-child(34) { --d: 709ms; --t: 120ms; stroke-width: 4.9; }
.cb__sway > :nth-child(35) { --d: 753ms; --t: 120ms; stroke-width: 4.5; }
.cb__sway > :nth-child(36) { --d: 797ms; --t: 120ms; stroke-width: 4.1; }
.cb__sway > :nth-child(37) { --d: 841ms; --t: 120ms; stroke-width: 3.8; }
.cb__sway > :nth-child(38) { --d: 885ms; --t: 120ms; stroke-width: 3.4; }
.cb__sway > :nth-child(39) { --d: 927ms; --t: 120ms; stroke-width: 3.1; }
.cb__sway > :nth-child(40) { --d: 969ms; --t: 120ms; stroke-width: 2.7; }
.cb__sway > :nth-child(41) { --d: 1009ms; --t: 120ms; stroke-width: 2.4; }
.cb__sway > :nth-child(42) { --d: 657ms; --t: 380ms; stroke-width: 2.7; }
.cb__sway > :nth-child(43) { --d: 728ms; --t: 380ms; stroke-width: 2.4; }
.cb__sway > :nth-child(44) { --d: 821ms; --t: 380ms; stroke-width: 2.1; }
.cb__sway > :nth-child(45) { --d: 903ms; --t: 380ms; stroke-width: 1.8; }
.cb__sway > :nth-child(46) { --d: 972ms; --t: 380ms; stroke-width: 1.6; }
.cb__sway > :nth-child(47) { --d: 1048ms; --t: 380ms; stroke-width: 1.6; }
.cb__sway > :nth-child(48) { --d: 848ms; --t: 120ms; stroke-width: 5; }
.cb__sway > :nth-child(49) { --d: 870ms; --t: 120ms; stroke-width: 4.8; }
.cb__sway > :nth-child(50) { --d: 892ms; --t: 120ms; stroke-width: 4.5; }
.cb__sway > :nth-child(51) { --d: 916ms; --t: 120ms; stroke-width: 4.3; }
.cb__sway > :nth-child(52) { --d: 941ms; --t: 120ms; stroke-width: 4; }
.cb__sway > :nth-child(53) { --d: 967ms; --t: 120ms; stroke-width: 3.8; }
.cb__sway > :nth-child(54) { --d: 994ms; --t: 120ms; stroke-width: 3.5; }
.cb__sway > :nth-child(55) { --d: 1022ms; --t: 120ms; stroke-width: 3.3; }
.cb__sway > :nth-child(56) { --d: 1050ms; --t: 120ms; stroke-width: 3.1; }
.cb__sway > :nth-child(57) { --d: 1079ms; --t: 120ms; stroke-width: 2.8; }
.cb__sway > :nth-child(58) { --d: 1108ms; --t: 120ms; stroke-width: 2.6; }
.cb__sway > :nth-child(59) { --d: 1137ms; --t: 120ms; stroke-width: 2.3; }
.cb__sway > :nth-child(60) { --d: 1167ms; --t: 120ms; stroke-width: 2.1; }
.cb__sway > :nth-child(61) { --d: 1196ms; --t: 120ms; stroke-width: 1.8; }
.cb__sway > :nth-child(62) { --d: 1034ms; --t: 380ms; stroke-width: 1.8; }
.cb__sway > :nth-child(63) { --d: 1116ms; --t: 380ms; stroke-width: 1.6; }
.cb__sway > :nth-child(64) { --d: 1198ms; --t: 380ms; stroke-width: 1.6; }
.cb__sway > :nth-child(65) { --d: 1261ms; --t: 380ms; stroke-width: 1.6; }
.cb__sway > :nth-child(66) > :nth-child(1) { --d: 713ms; }
.cb__sway > :nth-child(67) > :nth-child(1) { --d: 880ms; }
.cb__sway > :nth-child(68) > :nth-child(1) { --d: 973ms; }
.cb__sway > :nth-child(69) > :nth-child(1) { --d: 1395ms; }
.cb__sway > :nth-child(70) > :nth-child(1) { --d: 1487ms; }
.cb__sway > :nth-child(71) > :nth-child(1) { --d: 1673ms; }
.cb__sway > :nth-child(72) > :nth-child(1) { --d: 1148ms; }
.cb__sway > :nth-child(73) > :nth-child(1) { --d: 1618ms; }
.cb__sway > :nth-child(74) > :nth-child(1) { --d: 1681ms; }
.cb__sway > :nth-child(75) > :nth-child(1) { --d: 535ms; fill: #C60605; }
.cb__sway > :nth-child(76) > :nth-child(1) { --d: 553ms; fill: #A00707; }
.cb__sway > :nth-child(77) > :nth-child(1) { --d: 701ms; fill: #C60605; }
.cb__sway > :nth-child(78) > :nth-child(1) { --d: 720ms; fill: #EB0805; }
.cb__sway > :nth-child(79) > :nth-child(1) { --d: 741ms; fill: #A00707; }
.cb__sway > :nth-child(80) > :nth-child(1) { --d: 750ms; fill: #EB0805; }
.cb__sway > :nth-child(81) > :nth-child(1) { --d: 798ms; fill: #C60605; }
.cb__sway > :nth-child(82) > :nth-child(1) { --d: 833ms; fill: #EB0805; }
.cb__sway > :nth-child(83) > :nth-child(1) { --d: 887ms; fill: #C60605; }
.cb__sway > :nth-child(84) > :nth-child(1) { --d: 928ms; fill: #A00707; }
.cb__sway > :nth-child(85) > :nth-child(1) { --d: 973ms; fill: #EB0805; }
.cb__sway > :nth-child(86) > :nth-child(1) { --d: 1002ms; fill: #A00707; }
.cb__sway > :nth-child(87) > :nth-child(1) { --d: 1072ms; fill: #A00707; }
.cb__sway > :nth-child(88) > :nth-child(1) { --d: 1104ms; fill: #EB0805; }
.cb__sway > :nth-child(89) > :nth-child(1) { --d: 1119ms; fill: #C60605; }
.cb__sway > :nth-child(90) > :nth-child(1) { --d: 1160ms; fill: #C60605; }
.cb__sway > :nth-child(91) > :nth-child(1) { --d: 1172ms; fill: #C60605; }
.cb__sway > :nth-child(92) > :nth-child(1) { --d: 1173ms; fill: #EB0805; }
.cb__sway > :nth-child(93) > :nth-child(1) { --d: 1181ms; fill: #C60605; }
.cb__sway > :nth-child(94) > :nth-child(1) { --d: 1187ms; fill: #A00707; }
.cb__sway > :nth-child(95) > :nth-child(1) { --d: 1247ms; fill: #EB0805; }
.cb__sway > :nth-child(96) > :nth-child(1) { --d: 1257ms; fill: #A00707; }
.cb__sway > :nth-child(97) > :nth-child(1) { --d: 1269ms; fill: #EB0805; }
.cb__sway > :nth-child(98) > :nth-child(1) { --d: 1288ms; fill: #A00707; }
.cb__sway > :nth-child(99) > :nth-child(1) { --d: 1313ms; fill: #C60605; }
.cb__sway > :nth-child(100) > :nth-child(1) { --d: 1327ms; fill: #C60605; }
.cb__sway > :nth-child(101) > :nth-child(1) { --d: 1362ms; fill: #C60605; }
.cb__sway > :nth-child(102) > :nth-child(1) { --d: 1364ms; fill: #A00707; }
.cb__sway > :nth-child(103) > :nth-child(1) { --d: 1404ms; fill: #A00707; }
.cb__sway > :nth-child(104) > :nth-child(1) { --d: 1416ms; fill: #EB0805; }
.cb__sway > :nth-child(105) > :nth-child(1) { --d: 1426ms; fill: #EB0805; }
.cb__sway > :nth-child(106) > :nth-child(1) { --d: 1427ms; fill: #C60605; }
.cb__sway > :nth-child(107) > :nth-child(1) { --d: 1446ms; fill: #A00707; }
.cb__sway > :nth-child(108) > :nth-child(1) { --d: 1447ms; fill: #C60605; }
.cb__sway > :nth-child(109) > :nth-child(1) { --d: 1451ms; fill: #C60605; }
.cb__sway > :nth-child(110) > :nth-child(1) { --d: 1508ms; fill: #C60605; }
.cb__sway > :nth-child(111) > :nth-child(1) { --d: 1513ms; fill: #EB0805; }
.cb__sway > :nth-child(112) > :nth-child(1) { --d: 1513ms; fill: #EB0805; }
.cb__sway > :nth-child(113) > :nth-child(1) { --d: 1521ms; fill: #EB0805; }
.cb__sway > :nth-child(114) > :nth-child(1) { --d: 1522ms; fill: #A00707; }
.cb__sway > :nth-child(115) > :nth-child(1) { --d: 1548ms; fill: #C60605; }
.cb__sway > :nth-child(116) > :nth-child(1) { --d: 1558ms; fill: #EB0805; }
.cb__sway > :nth-child(117) > :nth-child(1) { --d: 1580ms; fill: #EB0805; }
.cb__sway > :nth-child(118) > :nth-child(1) { --d: 1587ms; fill: #A00707; }
.cb__sway > :nth-child(119) > :nth-child(1) { --d: 1621ms; fill: #A00707; }
.cb__sway > :nth-child(120) > :nth-child(1) { --d: 1699ms; fill: #A00707; }
.cb__sway > :nth-child(121) > :nth-child(1) { --d: 1761ms; fill: #EB0805; }
.cb__sway > :nth-child(122) > :nth-child(1) { --d: 1778ms; fill: #EB0805; }


/* ---------- stillness: a V pointing on ----------
   site.js shows it after fifteen seconds without scrolling, tapping or typing. The petals that used
   to drift down the screen after eight seconds are gone (2026-10-13): this audience reads slowly,
   and nothing should fall across the text while they do. */

.scroll-hint { position: fixed; left: 50%; bottom: calc(1.4rem + env(safe-area-inset-bottom, 0px)); z-index: calc(var(--z-header) - 1);
  translate: -50% 0; padding: 0.6rem 0.8rem; cursor: pointer; opacity: 0; pointer-events: none;
  transition: opacity 600ms var(--ease-out-expo); }
.scroll-hint svg { display: block; width: 28px; height: 18px; fill: none; stroke: var(--c-red);
  stroke-width: 2.5; stroke-linecap: square; stroke-linejoin: miter; }
.scroll-hint.is-on { opacity: 1; pointer-events: auto; }
.scroll-hint.is-on svg { animation: hint-bob 1.8s var(--ease-out-quart) infinite; }
@keyframes hint-bob { 0%, 100% { transform: none; } 45% { transform: translateY(7px); } }
@media (prefers-reduced-motion: reduce) { .scroll-hint.is-on svg { animation: none; } }

/* ---------- stillness, page by page ----------
   Each page moves something of its own once html has .is-still (site.js,
   after eight seconds without input). Most of it is set up paused and only runs while still, so a
   scroll freezes it where it is instead of snapping it back. It uses translate, rotate and scale,
   which stack on top of the transforms the scroll already drives. None of it runs without
   .motion, so reduced motion and no scripts leave it all at rest. */

@keyframes still-drift { 0%, 100% { translate: 0 0; } 50% { translate: 0 -16px; } }
@keyframes still-sway { 0%, 100% { rotate: 0deg; } 50% { rotate: calc(var(--sw, 1) * 5deg); } }
@keyframes still-turn { 0%, 100% { transform: none; } 50% { transform: rotate(8deg); } }
@keyframes still-flick { 0%, 100% { rotate: 0deg; } 50% { rotate: 14deg; } }
@keyframes still-bob { 0%, 100% { translate: 0 0; } 50% { translate: 0 -4px; } }
@keyframes still-cheap { 0%, 100% { scale: 1; filter: opacity(1); } 50% { scale: 0.94; filter: opacity(0.55); } }
@keyframes still-gust { 0%, 100% { rotate: 0deg; } 30% { rotate: -2.2deg; } 62% { rotate: 0.8deg; } }

/* Everywhere: the photograph drifts inside every V, the V's petals sway, outlined petals turn,
   and the petals marking list items flick. */
.motion .vm__img { animation: still-drift 7s ease-in-out infinite paused; }
.motion .vm--pa .vm__drift, .motion .vm--pb .vm__drift { animation: still-sway 5.5s ease-in-out infinite paused; }
.motion .vm--pb .vm__drift { --sw: -1; animation-delay: -1.4s; }
.motion .phv__petals path { animation: vm-petal 1200ms var(--ease-out-expo) both 900ms, still-sway 5.5s ease-in-out 0s infinite paused; }
.motion .phv__petals path:last-child { --sw: -1; animation-delay: 1020ms, -1.4s; }
.motion .vpanel__petal, .motion .write__petal { animation: still-turn 9s ease-in-out infinite paused; }
.motion .petal-list .petal { animation: still-flick 4.5s ease-in-out infinite paused; }
.motion .petal-list li:nth-child(2n) .petal { animation-delay: -1.5s; }
.motion .petal-list li:nth-child(3n) .petal { animation-delay: -3s; }

/* What we do: a dot runs the route along the V. */
/* FAQ: a gust through the cherry branch. */
.motion .cb { transform-origin: 100% 0; animation: still-gust 5s ease-in-out infinite paused; }
/* Legal pages: the section being read is marked, and stays still: these pages are read slowly,
   and a breathing mark beside the text distracted from it (audit 2026-10-11, WCAG 2.2.2). */

.motion.is-still .vm__img, .motion.is-still .vm--pa .vm__drift, .motion.is-still .vm--pb .vm__drift,
.motion.is-still .vpanel__petal, .motion.is-still .write__petal, .motion.is-still .petal-list .petal,
.motion.is-still .cb {
  animation-play-state: running; }
.motion.is-still .phv__petals path { animation-play-state: running, running; }

/* ---------- placeholders ----------
   Visibly unfinished on purpose. robots.txt blocks the site until these are filled,
   and PLACEHOLDERS.md lists every one. */

[data-placeholder] {
  color: var(--ink-faint);
  background: var(--placeholder-bg);
  box-shadow: 0 0 0 0.18em var(--placeholder-bg);
  border-radius: 1px;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

/* ---------- hero load sequence ----------
   A plain CSS animation, not a JS-gated transition: it completes on its own, so the
   page is never left blank for a crawler, a hidden tab, or a headless screenshot. */

@keyframes rise { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: none; } }
@keyframes grow { from { transform: scaleX(0); } to { transform: none; } }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
/* Each arm arrives along its own axis, so the two halves of the photograph only line up
   at the moment the V closes. */
@keyframes vm-l { from { opacity: 0; transform: translate(-130px, -225px); } to { opacity: 1; transform: none; } }
@keyframes vm-r { from { opacity: 0; transform: translate(137px, -221px); } to { opacity: 1; transform: none; } }
@keyframes vm-petal { from { opacity: 0; transform: rotate(var(--r0)) scale(0.3); } to { opacity: 1; transform: none; } }

.hero h1    { animation: rise 820ms var(--ease-out-expo) both 140ms; }
.hero__rule { transform-origin: left; animation: grow 700ms var(--ease-out-expo) both 380ms; }
.hero .lead { animation: rise 820ms var(--ease-out-expo) both 460ms; }
.hero .cta  { animation: rise 820ms var(--ease-out-expo) both 580ms; }
/* A glide rather than a snap: the arms start gently, travel, and settle over two seconds, and
   they are fully visible before they have gone a third of the way. */
@keyframes vm-l-soft { from { opacity: 0; transform: translate(-130px, -225px); } 30% { opacity: 1; } to { opacity: 1; transform: none; } }
@keyframes vm-r-soft { from { opacity: 0; transform: translate(137px, -221px); } 30% { opacity: 1; } to { opacity: 1; transform: none; } }
@keyframes vm-petal-soft { from { opacity: 0; transform: rotate(var(--r0)) scale(0.3); } 35% { opacity: 1; } to { opacity: 1; transform: none; } }
.hero .vm--l { animation: vm-l-soft 2100ms cubic-bezier(0.45, 0, 0.15, 1) both 150ms; }
.hero .vm--r { animation: vm-r-soft 2100ms cubic-bezier(0.45, 0, 0.15, 1) both 300ms; }
.hero .vm--pa { --r0: -48deg; animation: vm-petal-soft 1700ms cubic-bezier(0.45, 0, 0.15, 1) both 1150ms; }
.hero .vm--pb { --r0: 42deg; animation: vm-petal-soft 1700ms cubic-bezier(0.45, 0, 0.15, 1) both 1300ms; }
.vguides line:not(.vguides__axis) { animation: draw 1600ms var(--ease-out-quart) both 300ms; }
.vguides__axis, .vguides circle { animation: fade 900ms ease both 1100ms; }

@media (prefers-reduced-motion: reduce) {
  .hero h1, .hero__rule, .hero .lead, .hero .cta,
  .vm, .vguides line, .vguides circle,
  .pagehead--v .display, .pagehead--v .lead, .phv__arms path, .phv__petals path {
    animation: none;
  }
  /* As specific as the hero's own rules, which would otherwise still play. */
  .hero .vm { animation: none; }
}

/* ---------- v2 (audit 2026-10-11) ----------
   The pieces added after the audit: the films' pause button, the price range, and on the school
   page the data protection officer's list, the honest comparison with the free state chatbot and
   who decides what. All of it is built from the tokens above; nothing here moves. */

/* Pause and play, under each film (WCAG 2.2.2). Hidden until demos.js starts the film. Its words say
   what a press does; the icon follows. 44px tall for a finger. */
.film-pause { display: inline-flex; align-items: center; gap: var(--s-2); min-height: 2.75rem; margin-top: var(--s-2);
  padding: 0 var(--s-3) 0 0; border: 0; background: none; color: var(--ink-muted); font: inherit;
  font-size: var(--t-small); font-weight: var(--w-ui); cursor: pointer; transition: color var(--dur) var(--ease-out-quart); }
.film-pause[hidden] { display: none; }
.film-pause:hover, .film-pause:focus-visible { color: var(--ink); }
.film-pause__i { width: 1rem; height: 1rem; flex: none; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; }
.film-pause__tri { fill: currentColor; stroke: none; }
.film-pause__tri, .film-pause__b, .film-pause.is-held .film-pause__bars, .film-pause.is-held .film-pause__a { display: none; }
.film-pause.is-held .film-pause__tri, .film-pause.is-held .film-pause__b { display: inline; }

/* What it costs, school page: the heading across the top, then the quote (the three amounts,
   set like an offer on paper) beside what moves the price and when you pay. */
/* In the hairline grid (2026-10-13), like the home page's price: one ruled band, the quote and what
   moves the price as its two cells, the rows of the quote divided by the quieter hairline. */
.price__grid { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: var(--s-7) 0; align-items: stretch; }
.price__head { grid-column: 1 / -1; }
.quote, .price__why { position: relative; padding-block: var(--s-6) var(--s-7); border-top: var(--hairline); border-bottom: var(--hairline); }
.quote { margin: 0; padding-right: var(--s-8); }
.price__why { padding-left: var(--s-7); border-left: var(--hairline); }
.quote__row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--s-2) var(--s-5); padding-block: var(--s-5);
  border-top: var(--hairline-soft); }
.quote__row:first-child { border-top: 0; padding-top: 0; }
.quote__row:last-child { padding-bottom: 0; }
.quote dt { font-size: var(--t-h3); font-weight: var(--w-display); letter-spacing: var(--track-h); line-height: 1.15; text-wrap: balance; }
.quote dd { margin: 0; }
.quote__amt { grid-column: 2; grid-row: 1 / span 2; justify-self: end; text-align: right; white-space: nowrap;
  font-size: clamp(1.2rem, 0.95rem + 0.9vw, 1.7rem); font-weight: var(--w-display); letter-spacing: var(--track-h); line-height: 1.2; }
.quote__unit { display: block; margin-top: var(--s-1); font-size: var(--t-small); font-weight: var(--w-ui); letter-spacing: 0; color: var(--ink-muted); }
.quote__p { grid-column: 1; color: var(--ink-muted); max-width: 40ch; }
.price__why { display: grid; gap: var(--s-4); align-content: start; }
.price__why h3 { font-size: var(--t-h3); font-weight: var(--w-display); letter-spacing: var(--track-h); line-height: 1.15; }
.price__why h3:not(:first-child) { margin-top: var(--s-5); }
.price__why p, .price__why li { color: var(--ink-muted); }
.price__why .petal-list { gap: var(--s-3); }
@media (max-width: 900px) {
  .price__grid { grid-template-columns: minmax(0, 1fr); }
  .quote { padding-right: 0; border-bottom: 0; }
}
@media (max-width: 520px) {
  .quote__row { grid-template-columns: minmax(0, 1fr); }
  .quote__amt { grid-column: 1; grid-row: auto; justify-self: start; text-align: left; white-space: normal; }
  .quote__unit { display: inline; margin: 0 0 0 var(--s-1); }
}

/* What it costs, home and What we do: the heading and lead across the top, then one starting
   price set at display size under a hairline, like the total at the foot of an offer, beside
   what is included, when you pay and the demo. */
/* In the hairline grid (2026-10-13): one ruled band, the figure and what's included as its two
   cells with a hairline between them, and the V hanging over each cell's first line. */
.cost { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: var(--s-7) 0; }
.cost__head { grid-column: 1 / -1; }
.cost__fig, .cost__side { position: relative; display: grid; align-content: start; padding-block: var(--s-6) var(--s-7);
  border-top: var(--hairline); border-bottom: var(--hairline); }
.cost__fig { padding-right: var(--s-8); }
.cost__side { padding-left: var(--s-7); border-left: var(--hairline); }
.cost__fig::before, .cost__side::before, .quote::before, .price__why::before {
  content: ""; position: absolute; top: 0; left: var(--tick-x, 0); width: var(--v-tick); height: calc(var(--v-tick) * var(--v-depth));
  background: var(--accent); clip-path: var(--v-cut); }
.cost__side, .price__why { --tick-x: var(--s-7); }
.cost__fig { gap: var(--s-4); }
.cost__label, .cost__side h3 { font-size: var(--t-h3); font-weight: var(--w-display); letter-spacing: var(--track-h); line-height: 1.15; text-wrap: balance; }
.cost__amt { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 0.35em; margin-block: var(--s-3) var(--s-2);
  font-size: clamp(3.25rem, 1.4rem + 6.2vw, 7.5rem); font-weight: var(--w-display); line-height: 0.9; letter-spacing: var(--track-display); }
.cost__from { font-size: var(--t-h3); letter-spacing: var(--track-h); color: var(--accent); }
.cost__num { white-space: nowrap; }
.cost__note { max-width: 40ch; color: var(--ink-muted); }
.cost__side { gap: var(--s-4); }
.cost__side .petal-list { gap: var(--s-3); }
.cost__side li, .cost__terms { color: var(--ink-muted); }
.cost__side .btn { justify-self: start; margin-top: var(--s-3); }
@media (max-width: 900px) {
  .cost { grid-template-columns: minmax(0, 1fr); }
  .cost__fig { padding-right: 0; border-bottom: 0; }
  .cost__side, .price__why { --tick-x: 0; padding-left: 0; border-left: 0; }
}

/* School: for the data protection officer. Two lists side by side (the layout of "yours at
   handover" on What we do), plain rules between the items, the review date under them. */
.dpo__cols { margin-top: var(--s-7); }
.dpo__h { font-size: var(--t-h3); font-weight: var(--w-display); letter-spacing: var(--track-h); line-height: 1.15; }
.dpo__list { list-style: none; padding: 0; margin: 0; display: grid; }
.dpo__list li { padding-block: var(--s-4); border-bottom: 1px solid var(--rule); font-weight: var(--w-ui); }
.dpo__list li:first-child { border-top: 1px solid var(--rule); }
.dpo__rev { font-size: var(--t-small); color: var(--ink-muted); }
.dpo .cta { justify-self: start; }

/* School: the free state chatbot, fairly. Its facts in an outlined box, what Voronda School adds
   in the blush panel beside it; the source and date under the facts. */
.vs__then { margin-top: var(--s-3); }
.vs__grid { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: var(--s-4); margin-top: var(--s-7); }
.vs__them, .vs__us { display: grid; gap: var(--s-5); align-content: start; padding: clamp(1.4rem, 0.9rem + 1.6vw, 2.5rem); }
.vs__them { border: 1px solid var(--rule-firm); }
.vs__us { background: var(--panel, var(--bg-raised)); }
.vs h3 { font-size: var(--t-h3); font-weight: var(--w-display); letter-spacing: var(--track-h); line-height: 1.15; }
.vs__list { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--s-3); color: var(--ink-muted); }
.vs__src { font-size: var(--t-small); color: var(--ink-faint-text); }
.vs .petal-list { gap: var(--s-4); }
@media (max-width: 860px) { .vs__grid { grid-template-columns: minmax(0, 1fr); } }

/* School: who decides what. The three buyers on the options' ruled columns, each ending in its
   next step. */
.buy__paths { margin-top: var(--s-7); }
.buy__paths .option { display: grid; gap: var(--s-3); align-content: start; }
.buy__paths .option h3 { margin-bottom: 0; }
.buy__paths .cta { justify-self: start; }

/* German compounds ("Datenschutzbeauftragten") are wider than a 375px phone at heading size: they
   hyphenate there, and no word may ever push the page sideways. */
.sec-h { overflow-wrap: break-word; }
:lang(de) .sec-h { hyphens: auto; }

/* ---------- use cases (2026-10-16) ----------
   The grounds of What we do: the dark opening with the four kinds as jumps, the bright stretch, the
   oxblood close. Each kind is a band of the hairline grid: its name on the left, held in view while
   its jobs pass on a wide screen, and the jobs in ruled cells on the right, a hairline between them
   and a small V hanging from the rule over each title. No cards, no shadows. */
.uc-jump { display: flex; flex-wrap: wrap; justify-content: center; gap: 0 var(--s-6); animation: rise 820ms var(--ease-out-expo) both 560ms; }
.uc + .uc { padding-top: 0; }
.uc__in { display: grid; grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); gap: var(--s-6) var(--s-7); align-items: start; }
.uc__head { display: grid; gap: var(--s-4); justify-items: start; padding-top: var(--s-6); border-top: var(--hairline); }
.uc__head .sec-h { font-size: clamp(2rem, 1.2rem + 2.4vw, 3.25rem); }
@media (min-width: 901px) { .uc__head { position: sticky; top: calc(var(--header-h) + var(--s-5)); } }
.uc__grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  border-bottom: var(--hairline); }
.uc__job { position: relative; display: grid; align-content: start; gap: var(--s-3); min-width: 0;
  padding: var(--s-6) var(--s-6) var(--s-7) 0; border-top: var(--hairline); }
.uc__job:nth-child(2n) { padding-left: var(--s-6); padding-right: 0; border-left: var(--hairline); --tick-x: var(--s-6); }
.uc__job::before { content: ""; position: absolute; top: 0; left: var(--tick-x, 0); width: var(--v-tick); height: calc(var(--v-tick) * var(--v-depth));
  background: var(--accent); clip-path: var(--v-cut); }
.uc__job h3 { font-size: var(--t-h3); font-weight: var(--w-display); letter-spacing: var(--track-h); line-height: 1.15; text-wrap: balance; }
.uc__job > p { color: var(--ink-muted); max-width: 42ch; }
/* What a job is made of: the feature picker's own names, as small ruled tags. */
.uc__uses { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); margin-top: var(--s-2); }
.uc__uses p { margin-right: var(--s-1); font-size: var(--t-small); font-weight: var(--w-strong); color: var(--accent-text); }
.uc__uses ul { display: contents; list-style: none; }
.uc__uses li { padding: 0.2rem 0.6rem; border: var(--hairline); font-size: var(--t-small); font-weight: var(--w-ui); color: var(--ink); }
/* Built around you: the four choices in one ruled row. */
.uc__grid--four { margin-top: var(--s-7); grid-template-columns: repeat(4, minmax(0, 1fr)); }
.uc__grid--four .uc__job { padding: var(--s-6) var(--s-5) var(--s-7); --tick-x: var(--s-5); }
.uc__grid--four .uc__job + .uc__job { border-left: var(--hairline); }
@media (max-width: 900px) {
  .uc__in { grid-template-columns: minmax(0, 1fr); }
  .uc__grid--four { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .uc__grid--four .uc__job:nth-child(3) { border-left: 0; }
}
@media (max-width: 640px) {
  .uc__grid, .uc__grid--four { grid-template-columns: minmax(0, 1fr); }
  .uc__job, .uc__job:nth-child(2n), .uc__grid--four .uc__job { padding: var(--s-5) 0 var(--s-6); border-left: 0; --tick-x: 0; }
  .uc__grid--four .uc__job + .uc__job { border-left: 0; }
}

/* ---------- loading (2026-10-15) ----------
   Only what is really on its way gets a shape: the photograph behind a film, a frame or a small
   window; a portrait; the photograph in the contact page's V; and a film's main column until
   demos.js has set it up and the fonts are in. The shape sits exactly over the box it stands for,
   so nothing moves, in --bg-raised with the ground's own soft glow (--sk-glow) breathing slowly in
   and out: no sweep, no bounce. Nothing else is held back, and text is never covered.
   The head adds .sk to <html> (a one-line script, so without JavaScript none of this exists) and
   site.js takes each shape away the moment its picture is decoded or its film is ready: .sk-out
   fades it, .sk-ok removes it. A shape only appears after 220ms, so a page that is ready by then
   (cached) never shows one, and 1.2 seconds after the page is read every shape goes regardless.
   With reduced motion the shape stands still and the swap is instant. */
@keyframes sk-in { from { opacity: 0; } }
@keyframes sk-pulse { 50% { box-shadow: var(--sk-hi); } }
@keyframes sk-fill { 50% { fill-opacity: 0.62; } }
.app__sk, .vm__sk { display: none; }

/* The photographs: a layer over the picture, under whatever the page draws on it. */
.sk .stage--film > .app:not(.sk-ok)::after, .sk .frame:not(.sk-ok)::before, .sk .mini:not(.sk-ok)::before,
.sk .photo:not(.sk-ok):has(.photo__img)::after {
  --sk-lo: inset 0 0 0 1px var(--rule), inset 0 0 2.5rem -0.75rem var(--sk-glow);
  --sk-hi: inset 0 0 0 1px var(--rule-firm), inset 0 0 5.5rem 0.75rem var(--sk-glow);
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; background: var(--bg-raised); box-shadow: var(--sk-lo);
  animation: sk-in 360ms var(--ease-out-quart) 220ms backwards, sk-pulse 1.8s ease-in-out 580ms infinite;
  transition: opacity 360ms var(--ease-out-quart); }
.sk .stage--film > .app:not(.sk-ok)::after, .sk .photo:not(.sk-ok):has(.photo__img)::after { z-index: 1; }

/* A film's main column: the switch at the top, the greeting and the prompt, as they stand in its
   first frame, in the window's own blush. Only where demos.js keeps the column hidden until it is
   set up, so not with reduced motion, where the finished frame simply stands. */
.app__sk i { position: absolute; display: block; background: var(--c-blush-2); border-radius: calc(var(--u) * 1.2);
  --sk-lo: 0 0 0 0 oklch(76.7% 0.088 19.3 / 0);
  --sk-hi: 0 0 calc(var(--u) * 1.8) calc(var(--u) * 0.15) oklch(76.7% 0.088 19.3 / 0.6);
  box-shadow: var(--sk-lo); animation: sk-pulse 1.8s ease-in-out 580ms infinite; }
.app__sk i:nth-child(1) { top: calc(var(--u) * 1.6); left: 50%; width: calc(var(--u) * 12); height: calc(var(--u) * 3.3); translate: -50% 0; }
.app__sk i:nth-child(2) { top: calc(var(--u) * (var(--ct) - 4.2)); left: 50%; width: min(calc(var(--u) * 34), 70%); height: calc(var(--u) * 2.2);
  translate: -50% 0; border-radius: calc(var(--u) * 0.7); }
.app__sk i:nth-child(3) { top: calc(var(--u) * (var(--ct) - 0.15)); left: calc(var(--u) * var(--px)); right: calc(var(--u) * var(--px));
  height: calc(var(--u) * 8.45); border-radius: calc(var(--u) * 1.6); background: var(--c-paper);
  --sk-lo: inset 0 0 0 1px var(--c-blush-2), 0 0 0 0 oklch(76.7% 0.088 19.3 / 0);
  --sk-hi: inset 0 0 0 1px var(--c-petal), 0 0 calc(var(--u) * 1.8) calc(var(--u) * 0.15) oklch(76.7% 0.088 19.3 / 0.6); }
@media (prefers-reduced-motion: no-preference) {
  .sk .stage--film:not(.sk-ok) .app__sk { display: block; position: absolute; z-index: 1; top: 0; right: 0; bottom: 0;
    left: calc(var(--u) * var(--side)); pointer-events: none;
    animation: sk-in 360ms var(--ease-out-quart) 220ms backwards; transition: opacity 360ms var(--ease-out-quart); }
  .sk .stage--film:not(.sk-ok):not(.sk-out) :is(.app__main, .app__cursor) { opacity: 0; }
}

/* The photograph in the contact page's V: each arm filled until it arrives. */
.sk .vmark:not(.sk-ok) .vm__sk { display: inline; fill: var(--bg-raised);
  animation: sk-in 360ms var(--ease-out-quart) 220ms backwards, sk-fill 1.8s ease-in-out 580ms infinite;
  transition: opacity 360ms var(--ease-out-quart); }

/* Ready: the shape fades from wherever it had got to (from nothing, if it never showed). */
.sk .stage--film > .app.sk-out::after, .sk .frame.sk-out::before, .sk .mini.sk-out::before, .sk .photo.sk-out:has(.photo__img)::after,
.sk .stage--film.sk-out .app__sk { opacity: 0; animation: none; }
.sk .vmark.sk-out .vm__sk { opacity: 0; animation: none; }

/* Reduced motion: the shape stands still (it still waits its 220ms, so a quick page shows none),
   and base.css makes the swap instant. */
@media (prefers-reduced-motion: reduce) {
  .sk .stage--film > .app:not(.sk-ok)::after, .sk .frame:not(.sk-ok)::before, .sk .mini:not(.sk-ok)::before,
  .sk .photo:not(.sk-ok):has(.photo__img)::after, .sk .vmark:not(.sk-ok) .vm__sk { animation: sk-in 1ms linear 220ms backwards; }
}
