/*
 * site.css: the page below the hero (this site's file; the build subagent writes it).
 * The body tokens first, then the motion patterns, the shared frame, the nav, the
 * sections in page order, the footer, and the page transitions
 * between pages. Every rule is scoped under a
 * section's class, a body class or a data attribute, never a bare section, h1, h2, img,
 * video, button, p, a, div or span rule that sets size, spacing, colour or motion: it
 * would reach the hero. The hero's own buttons are styled in hero.css; the button here
 * is scoped to the body (.section, .site-nav, .not-found, .page-read).
 *
 * The look is the brand's web kit in its "ink frame" direction (design/redesign-
 * brief.md, Stanley's request of 2026-10-18): a light page held by ink, with the nav,
 * one ink band (the self-check, decision D-6) and the footer in ink. Three light tones
 * one step apart (page, paper, white), a fixed page margin and 12 columns, three
 * section spacers. White browser windows (decision D-3; paper on the ink band), one dot
 * and one tab line, each on a solid magenta plate it lifts off, never a blurred shadow.
 *
 * Motion, one language everywhere: long expo tails, short travel, small staggers.
 *   P1  section headings rise word by word from a slot (site.js splits them; D-2)
 *   P2  lists arrive as lists: at most five steps of 60 to 70ms
 *   P3  windows rise into place, then lift off their magenta plate
 *   P4  a clip reveal on one picture, inside the services standout only
 *   P5  the Send button: magnetic, a circle fill and a letter roll, fine pointers only
 *   P6  the letter roll on text links (the tagline band, the nav, the footer), the same
 *       roll as P5's label, fine pointers only
 * Only transform and opacity move (plus stroke-dashoffset, and clip-path inside the
 * standout); every property is named; every start state is under .js (T-052); under
 * reduced motion every final state shows at once.
 *
 * Only what the front page can use is here. Every page waits for this file before its
 * first paint, and the inner pages' blocks were two fifths of it (110 of 272 KB): What we
 * do, contact, page.php and 404.php, About, How it works, the icon films, the page-top
 * pictures, Our work and the closing pair. They are in pages.css, which every other page
 * loads after this one (inc/site.php; 2026-10-25), and older comments that say "site.css"
 * of them mean pages.css now. A rule goes there only if it can never match on the front
 * page.
 */

/* ---- Body tokens. MOTION 6 (decision D-1), density 3. */
:root{
  /* The starter's token interface (check-site's tokens check reads these names). The
     spacing scale and container now come from the redesign's frame below. */
  --radius-body: 12px;
  --ease-arrive: cubic-bezier(0.16, 1, 0.3, 1);
  --space-1: 0.5rem;
  --space-2: 1rem;
  --space-3: 1.75rem;
  --space-4: 3rem;
  --space-5: 5rem;
  --section-pad: var(--pad-m);
  --container: min(100% - 2 * var(--margin), 82rem);
  --type-ratio: 1.25;

  /* Surfaces: three light tones one step apart, plus the kit's ink. Ink on --page
     17.0:1, on white 17.7:1, on paper 15.98:1; --ink-soft 7.98:1 on --page, 7.5:1 on
     paper. Magenta (4.23:1 on --page) is for plates, the drawn line, icons and type of
     24px and up: never body text. */
  --page: #FBFAF7;
  --paper: #F4F3EF;
  --white: #FFFFFF;
  --ink: #111827;
  --ink-soft: #474E5E;
  --rule: #DDDAD2;
  --rule-ink: #3A4252;
  --slot: #EDEBE5;
  --lit: #F9CFE5;

  /* The page frame: a fixed margin, 12 columns, one gutter (16px at 375, 40 at 1280). */
  --margin: clamp(1rem, 0.4rem + 2.6vw, 2.5rem);
  --gutter: clamp(1rem, 0.6rem + 1.6vw, 2rem);
  --frame-inline: max(var(--margin), (100% - 82rem) / 2);

  /* Exactly three section spacers, and two gaps inside a section. */
  --pad-s: clamp(4rem, 2.6rem + 5.6vw, 7.25rem);
  --pad-m: clamp(5rem, 3.2rem + 7.2vw, 9.25rem);
  --pad-l: clamp(6rem, 3.6rem + 9.6vw, 12.25rem);
  --gap-head: clamp(2.5rem, 1.5rem + 4vw, 5rem);
  --gap-text: clamp(1.25rem, 1rem + 1vw, 2rem);

  /* Type */
  --fs-poster: clamp(3rem, 1rem + 7.8vw, 7rem);
  --fs-h2: clamp(2.5rem, 1.4rem + 4.4vw, 5.5rem);
  --fs-h3: clamp(1.75rem, 1.2rem + 2.2vw, 3.25rem);
  --fs-step: clamp(1.25rem, 1.05rem + 0.8vw, 1.625rem);
  --fs-quote: clamp(1.5rem, 1.1rem + 1.7vw, 2.5rem);
  --fs-lead: clamp(1.1875rem, 1rem + 0.75vw, 1.625rem);
  --fs-faq: clamp(1.125rem, 1rem + 0.55vw, 1.5rem);
  --fs-body: clamp(1.0625rem, 1rem + 0.25vw, 1.1875rem);
  --fs-small: 0.875rem;

  /* Shape: the window, the small corner, and the plates (window, button). */
  --radius-win: 18px;
  --radius-sm: 10px;
  --plate: 8px;
  --plate-sm: 5px;
  --line: 3px;
  --line-heavy: 4px;
  --tab: 2.25rem;
  --win-pad: clamp(1.25rem, 0.8rem + 2vw, 3rem);
  --nav-h: 4.25rem;
  /* The floating pill (the nav block says why): a fixed face on an even ring, so its
     height is one figure everything clear of it reads (62px). Each grows at half the
     rate of the reader's text, 44 and 6px at 100% (as 2.75rem and .375rem were), 66 and
     9px at 200%: at the full rate the pill was 118px tall at 200% (device pass,
     2026-10-27), the top fifth of a 568px screen, round a Menu word of 34px. */
  --pill-face: calc(1.375rem + 22px);
  --pill-ring: calc(.1875rem + 3px);
  --pill-h: calc(var(--pill-face) + 2 * var(--pill-ring) + 2 * var(--line));

  /* Motion. State changes reuse the hero's --ease-out (tokens.css, never edited here). */
  --ease-roll: cubic-bezier(0.625, 0.05, 0, 1);
  --t-feedback: 0.12s;
  --t-state: 0.24s;
  --t-hover: 0.3s;
  --t-enter: 0.8s;
  --rise-y: 40px;
  --list-y: 20px;
}
@media (max-width: 47.99em){
  :root{--rise-y: 28px; --list-y: 16px}
}

/* ---- Page-wide: the canvas stays ink, so overscroll at either end is ink and nothing
   below reaches the hero; every body section sets its own opaque ground. The hero sets
   its own type in hero.css. */
body{margin:0;background:var(--canvas);color:var(--text-primary);font-family:var(--font-body);font-size:1.0625rem;line-height:1.6;-webkit-text-size-adjust:100%;text-rendering:optimizeLegibility}
::selection{background:var(--accent);color:var(--on-accent)}
input,textarea{caret-color:var(--accent)}

.skip-link:focus{position:fixed;z-index:60;top:var(--space-1);left:var(--space-1);padding:.75em 1.25em;background:var(--paper);color:var(--ink);border-radius:999px;font-weight:700}

/* ==== The motion patterns ========================================================= */

/* P1. The masked word rise (Kota's "rise from a slot"). site.js splits each section h2
   into span.word > span.word__in and marks it .rise and data-reveal. animation, not
   transition, so no stagger delay ever reaches a hover. Behind the one class, .rise, so
   it can be switched off in one rule. */
.section .word{display:inline-block;overflow:hidden;overflow:clip;padding-block:.1em .16em;margin-block:-.1em -.16em;vertical-align:top}
.section .word__in{display:inline-block}
.js .rise:not(.in) .word__in{transform:translateY(calc(100% + .3em))}
.js .rise.in .word__in{animation:rise-word var(--t-enter) var(--ease-arrive) calc(min(var(--i, 0), 8) * 45ms) both}
@keyframes rise-word{from{transform:translateY(calc(100% + .3em))}to{transform:none}}

/* P2. A list that arrives as a list: at most five steps (the sixth and later share the
   fifth), each 70ms by default. */
.js [data-list]:not(.in) > :not(ul),.js [data-list]:not(.in) > ul > li{opacity:0}
.js [data-list].in > :not(ul),.js [data-list].in > ul > li{animation:list-in .7s var(--ease-arrive) calc(min(var(--i, 0), 4) * var(--list-step, 70ms)) both}
@keyframes list-in{from{opacity:0;transform:translateY(var(--list-y))}to{opacity:1;transform:none}}

/* P3. The window on its plate (the kit's hard shadow made a real plate, so a lift is a
   pure transform), and the frame reveal (Clay's "window" reveal): the frame rises 40px
   and settles from .96, lying flat over its plate; then the window lifts off it to
   (-8px, -8px) and the magenta slides out from under it. The brand mark in motion. The
   lift sits a plate in from the column's start, so the window's own edge is on it. */
.lift{position:relative;border-radius:var(--radius-win);margin-block-start:var(--plate);margin-inline-start:var(--plate)}
.lift::before{content:"";position:absolute;inset:1px;border-radius:inherit;background:var(--accent)}
.lift > .window{transform:translate(calc(var(--plate) * -1),calc(var(--plate) * -1))}
.js [data-reveal]:not(.in) > .lift{opacity:0;transform:translateY(var(--rise-y)) scale(.96)}
.js [data-reveal]:not(.in) > .lift > .window{transform:none}
.js [data-reveal] > .lift{transition:transform var(--t-enter) var(--ease-arrive),opacity .5s var(--ease-arrive)}
.js [data-reveal] > .lift > .window{transition:transform .6s var(--ease-arrive) .35s}

/* The kit's container: a browser window with one dot and one tab line and a thick ink
   outline. White on the light page (decision D-3); paper on the ink band. */
.window{position:relative;box-sizing:border-box;background:var(--white);color:var(--ink);border:var(--line-heavy) solid var(--ink);border-radius:var(--radius-win);padding:calc(var(--tab) + var(--win-pad)) var(--win-pad) var(--win-pad)}
.window::before{content:"";position:absolute;inset:0 0 auto 0;block-size:var(--tab);border-block-end:var(--line) solid var(--ink)}
.window::after{content:"";position:absolute;top:calc(var(--tab) / 2 - 6px);left:1.125rem;inline-size:12px;block-size:12px;border-radius:50%;background:var(--ink)}

/* The one button, P3 at button size: a magenta pill plate with the paper face lifted
   5px off it. Hover lifts the face further, a press pushes it almost home. Scoped to the
   body, so the hero's buttons (hero.css) never change. */
:is(.section,.site-nav,.not-found,.page-read) .btn{position:relative;display:inline-flex;box-sizing:border-box;margin:var(--plate-sm) 0 0 var(--plate-sm);padding:0;font-family:var(--font-body);font-weight:700;font-size:var(--fs-body);line-height:1.2;color:var(--ink);background:var(--accent);border:0;border-radius:999px;text-decoration:none;cursor:pointer;-webkit-tap-highlight-color:transparent}
:is(.section,.site-nav,.not-found,.page-read) .btn__face{position:relative;display:inline-flex;align-items:center;justify-content:center;gap:.6em;box-sizing:border-box;inline-size:100%;min-block-size:3.25rem;padding:.8em 1.6em;background:var(--paper);border:var(--line) solid var(--ink);border-radius:inherit;transform:translate(calc(var(--plate-sm) * -1),calc(var(--plate-sm) * -1));transition:transform var(--t-state) var(--ease-out)}
@media (hover:hover){
  :is(.section,.site-nav,.not-found,.page-read) .btn:hover .btn__face{transform:translate(calc(var(--plate-sm) * -1 - 2px),calc(var(--plate-sm) * -1 - 2px))}
}
:is(.section,.site-nav,.not-found,.page-read) .btn:active .btn__face{transform:translate(-1px,-1px);transition-duration:.1s}
:is(.section,.site-nav,.not-found,.page-read) .btn:focus-visible{outline:none}
/* The ring clears the pill's magenta plate, so it never fuses with it (audit, 2026-10-25). */
:is(.section,.site-nav,.not-found,.page-read) .btn:focus-visible .btn__face{outline:3px solid var(--focus);outline-offset:calc(var(--plate-sm) + 3px)}

/* Text links (nav, "Back to the film", the contact facts, the footer): a 2px magenta
   underline draws in from the left on hover and leaves to the right, faster. Outside
   the nav a faint line sits under them at rest, so they read as links. Both lines sit
   just under the text whatever the link's box (1lh is the text's own line). */
.text-link{position:relative;display:inline-block;color:inherit;text-decoration:none}
.text-link::before,.text-link::after{content:"";position:absolute;left:0;right:0;bottom:calc((100% - 1lh) / 2 + .02em);pointer-events:none}
.text-link::after{block-size:2px;background:var(--accent);transform:scaleX(0);transform-origin:100% 50%;transition:transform .2s var(--ease-arrive)}
:is(.section,.site-foot,.not-found) .text-link::before{block-size:1px;background:currentColor;opacity:.4}
@media (hover:hover){
  .text-link:hover::after{transform:scaleX(1);transform-origin:0 50%;transition-duration:var(--t-hover)}
}

/* P6. The letter roll (Huge's links; Stanley, 2026-10-19), on the tagline band's links
   and the nav's and footer's text links, on fine pointers only. site.js splits the words
   into span.roll > span.roll__ch, aria-hidden, and keeps them whole in a visually-hidden
   span, so a link's name is still exactly its words. On hover or keyboard focus each
   letter slides up out of its line, 14ms after the one before, and its text-shadow,
   one line below it, rises into its place: the same letter, no second copy in the
   page. Back down at once, faster. The letters are clipped at the foot of their line,
   where the underline is, so the new ones come up out of the line as it draws (every
   rolled link has the leading for its descenders inside that); .1em of room above.
   Kerning and ligatures are off on these links from the first paint, as they are
   across the letters' own boxes, so the split moves nothing. Touch screens are never
   split, and under reduced motion nothing rolls: the underline is the feedback there. */
@media (hover:hover) and (pointer:fine){
  :is(.site-nav,.site-foot,.tagline) .text-link{font-kerning:none;font-variant-ligatures:none}
}
.roll{display:inline-flex;padding-block-start:.1em;margin-block-start:-.1em;overflow:hidden;overflow:clip;vertical-align:top}
.roll__ch{--roll-d:calc(1lh + .3em);display:inline-block;white-space:pre;text-shadow:0 var(--roll-d) currentColor;transition:transform .2s var(--ease-roll)}
@media (hover:hover) and (pointer:fine) and (prefers-reduced-motion: no-preference){
  :is(:hover,:focus-visible) > .roll > .roll__ch{transform:translateY(calc(var(--roll-d) * -1));transition:transform .4s var(--ease-roll) calc(min(var(--i, 0), 24) * 14ms)}
}

/* ==== The shared frame ============================================================ */

/* The floating pill's top inset, its height (--pill-h) and 1rem of air, kept clear of
   everything the browser scrolls to: without it a control reached by Shift+Tab scrolled
   in under the pill (review finding a11y-2, WCAG 2.4.11). It serves the anchor jumps
   too, so a section adds no margin of its own: 88px down since the pill's even ring
   (82 before, 84 before that). */
html{scroll-padding-block-start:calc(env(safe-area-inset-top, 0px) + .625rem + var(--pill-h) + 1rem)}

/* Every body section owns an opaque ground (which also hides the hero's environment
   glow below the fold) and one of the three spacers. */
.section{padding-block:var(--pad-m);scroll-margin-block-start:0;background:var(--page);color:var(--ink);font-size:var(--fs-body)}
.section > *{box-sizing:border-box;inline-size:var(--container);margin-inline:auto}
.section h2,.section h3{margin:0;font-family:var(--font-display);font-weight:400;text-wrap:balance}
.section h2{font-size:var(--fs-h2);line-height:.95;letter-spacing:-.03em}
.section h3{line-height:1.05;letter-spacing:-.02em}
/* One type ladder on the front page (audit, 2026-10-25): the opening h1 (75px at 1440)
   was out-ranked by every section heading (86px), with card and step titles one small
   step below those, so every stack read at section volume. Here the section heading
   stops at 68px, under the h1, with a 2.25rem floor so the phone's h1 (41px) leads too;
   the tagline (96px) and "Hello." stay the page's two statements. Scoped to the front
   page, as --fs-h2 also sizes headings on the inner pages. One exception, by design: at
   1024x768 the clay symbol holds the h1 at 42px, under a 56px heading. */
.home{--fs-h2:clamp(2.25rem, 1.6rem + 3vw, 4.25rem)}
/* text-wrap-style, not the text-wrap shorthand: the shorthand also set the wrap mode,
   so at this weight (a class and an element) it turned wrapping back on in any
   paragraph whose own class keeps it to one line (white-space:nowrap), as the growth
   report's tab title found at large text (device pass, 2026-10-27). */
.section p{margin:0;text-wrap-style:pretty}
.section figure{margin:0}
/* contain:layout (page-design.md section 4): layout work inside a standout stays inside
   it (review finding motion-6). It also makes the standout the containing block for
   anything fixed inside it, and nothing there is: the trail's tiles are absolute in
   their own layer and the wheel's view sticky, and both measured the same with it as
   without, at 1440 (How you get a price's route, D-60, holds nothing fixed). */
[data-standout]{position:relative;contain:layout}

/* The 12-column grid (Huge): one column below 48em. */
.grid{display:grid;grid-template-columns:minmax(0,1fr);column-gap:var(--gutter)}
@media (min-width: 48em){
  .grid{grid-template-columns:repeat(12,minmax(0,1fr))}
}

/* A section head: the heading on column 1, the intro under it in the offset reading
   column (5 to 11 at 64em, 3 to 12 from 48em). Never a split header. */
.s-head{row-gap:var(--gap-text)}
.s-intro{display:grid;gap:var(--space-2);max-inline-size:46ch;font-size:var(--fs-lead);font-weight:450;line-height:1.45;color:var(--ink-soft)}
@media (min-width: 48em){
  .s-head > h2{grid-column:1 / 11}
  .s-head > .s-intro{grid-column:3 / -1}
}
/* From 64em the heading takes ten columns, not eight: at 88px "How a quote works" broke
   onto two lines from about 1490px wide, moving that one head's intro and content 85px
   down (audit, 2026-10-25). */
@media (min-width: 64em){
  .s-head > h2{grid-column:1 / 11}
  .s-head > .s-intro{grid-column:5 / 12}
}

/* A gap marker where a photograph goes: a frame the photograph will fill, with the
   marker in words, so Sam sees which one to chase. */
.section .slot{box-sizing:border-box;display:grid;place-items:center;padding:var(--space-2);text-align:center;font-size:var(--fs-small);line-height:1.4;color:var(--ink-soft);background:var(--slot);border:2px dashed var(--border-input);border-radius:var(--radius-sm)}

/* ==== The ground: the whole page changes colour at its turns ======================= */
/* Stanley, 2026-10-22, from Huge's "Working with us.": as a dark section comes up the
   screen the whole page's ground fades to dark, its words turning light with it, and
   back as a light one comes: the page changes mood, never a band with a hard edge. Only
   at a page's real turns, where its ground goes between light and dark today:
     What we do   into the ink at Looking after it, out of it at Yours to keep
     The front    into the ink at the scorecard, out of it at How you get a price
     A case study into our ink at Their pages, side by side, out of it at And on a
                  phone (or Your site next?). Only while those pages print (three
                  captures or more): with none the page has no dark part and no turn.
   No other page has a turn (How it works, About, Contact and Our work are light from top
   to foot), so none changes. The footer is the frame's ink, as the bar is, and keeps its
   edge.
   --turn names the sections that take part and the ground each one is (paper or ink);
   site.js (bootGround) reads it. A turn is where it changes: when that section's heading
   comes a third of the way up the screen, one IntersectionObserver sets data-ground on
   <html> (no scroll listener), and back as it goes down again. Then:
    - <main>'s ::before and ::after are the ground: fixed behind everything, the dark one
      under the light one, and only the light one's opacity moves (0.8 s, the recording's
      pace, on the site's arrival curve).
    - the sections taking part are see-through over it: a dark one always, a light one
      while the ground is dark. A light one's own tone (paper or page) is back once the
      fade to light has finished, so at rest every section is the tone it always was.
    - their words, the ones that stand on the ground, take the ground's ink, both pairs
      measured at rest on each ground, at 1440 and 375 (ink on the page 17.0:1, on paper
      15.98:1, --ink-soft 7.99:1 and 7.51:1; paper on ink 15.98:1, --text-secondary
      10.39:1; the scorecard's magenta word 3.98:1 on paper and 4.02:1 on ink, display
      size only). They change at once, never through a colour animation (transform and
      opacity only move): at the moment in the fade where both inks read alike on it
      (4:1), not before the ground has begun to change.
    - windows, cards, pills, panels and the nav keep their own fills: the floating pill
      is paper with its ink outline on either ground, and the ink bar is only ever at
      the top, above every turn.
   Under reduced motion the change is at once (engine.css zeroes every duration and
   delay). Without the script nothing here applies: every section keeps its own ground,
   as before (T-052). Nothing loops (D-31). */
.svc-group--build,.svc-close,.recent-work,.how-it-works,.case-page > :is(.case-rows,.case-phones,.work-close){--turn:paper}
.svc-group--care,.self-check,.case-page > .case-pages{--turn:ink}
.has-turns{--ground-paper:var(--page);--ground-ink:var(--ink);--on-paper:var(--ink);--on-paper-soft:var(--ink-soft);--on-ink:var(--text-primary);--on-ink-soft:var(--text-secondary)}
/* --flip: when the words change in each fade (above). */
html[data-ground="paper"] .has-turns{--on:var(--on-paper);--on-soft:var(--on-paper-soft);--flip:.07s}
html[data-ground="ink"] .has-turns{--on:var(--on-ink);--on-soft:var(--on-ink-soft);--flip:.1s}
.has-turns::before,.has-turns::after{content:"";position:fixed;inset:0 0 auto;z-index:-1;block-size:100vh;block-size:100lvh;pointer-events:none}
.has-turns::before{background:var(--ground-ink)}
.has-turns::after{background:var(--ground-paper);transition:opacity var(--t-enter) var(--ease-arrive)}
html[data-ground="ink"] .has-turns::after{opacity:0}
html[data-ground] .on-ground--ink,html[data-ground="ink"] .on-ground{background:none}
html[data-ground] .on-ground{color:var(--on);transition:color 0s linear var(--flip)}
html[data-ground="paper"] .on-ground--paper{transition:color 0s linear var(--flip),background-color 0s linear var(--t-enter)}
/* The words that set their own colour and stand on the ground (the rest inherit). */
html[data-ground] .on-ground :is(.s-intro,.check__intro,.review cite){color:var(--on-soft);transition:color 0s linear var(--flip)}
html[data-ground] .on-ground :is(.work__page-go,.steps__go,.review blockquote p,.case-row__words p,.case-phones__words p){color:var(--on);transition:color 0s linear var(--flip)}
/* The growth check's scorecard, beside the deck from 64em, is still on screen as How you
   get a price's heading brings the paper back (1024x768 to 2000x1200): its group names,
   counts, caption and empty small cards' numbers take the ground's ink too (they stayed
   paper on the paper, 1.06:1 and 1.64:1), and its empty places' dashes a grey as faint on
   the paper as theirs on the ink (about 2.6:1 on each). A filled place is a card and
   keeps its own fill. Each changes at the flip, as the words above; a place that fills
   changes at once. */
html[data-ground] .on-ground .check__row-name,html[data-ground] .on-ground .check__slot--note:not(.is-filled) .check__chip-n{color:var(--on)}
html[data-ground] .on-ground .check__row-of,html[data-ground] .on-ground .check__pile[data-empty]::before{color:var(--on-soft)}
html[data-ground="paper"] .on-ground .check__slot:not(.is-filled) .check__slot-face{border-color:var(--ink-soft)}
html[data-ground] .on-ground :is(.check__row-name,.check__row-of,.check__slot--note:not(.is-filled) .check__chip-n){transition:color 0s linear var(--flip)}
html[data-ground] .on-ground .check__slot:not(.is-filled) .check__slot-face{transition:border-color 0s linear var(--flip)}
html[data-ground] .on-ground .check__pile[data-empty]::before{transition:opacity .3s var(--ease-out),color 0s linear var(--flip)}
html.is-ground-still[data-ground] .on-ground :is(.check__row-name,.check__row-of,.check__slot--note:not(.is-filled) .check__chip-n,.check__slot:not(.is-filled) .check__slot-face),html.is-ground-still[data-ground] .on-ground .check__pile[data-empty]::before{transition:none}
/* The first setting, as the page opens, is not a change: nothing fades. */
html.is-ground-still .has-turns::after,html.is-ground-still .on-ground,html.is-ground-still .on-ground :is(.s-intro,.check__intro,.review cite,.work__page-go,.steps__go,.review blockquote p,.case-row__words p,.case-phones__words p){transition:none}

/* ==== The nav: nav/floating-pill. The ink bar, then the paper pill ================ */
/* At the top, the ink bar in the page's flow above the first screen; once that screen
   has gone, the paper pill (site.js adds .is-fixed; header.php says why). No blur in
   either state: the bar can sit over the scrubbing stage, and the pill is solid paper. */
.site-head{position:relative;z-index:40;min-block-size:var(--nav-h);background:var(--canvas)}
.site-nav{box-sizing:border-box;display:flex;align-items:center;gap:var(--space-2);min-block-size:var(--nav-h);padding-inline:var(--frame-inline);background:var(--canvas);color:var(--paper)}
.site-nav__home{display:inline-flex;align-items:center;min-block-size:44px;min-inline-size:44px;margin-inline-end:auto;border-radius:6px}
.site-nav__logo{display:block;block-size:2.125rem;inline-size:auto}
/* Large text on a phone: the bar is its own size container, and when it is too narrow
   for the lockup, Get in touch and Menu at the reader's text size, Get in touch goes (the
   intro's pill and the menu still lead to contact), so Menu is never pushed off the
   screen (audit, 2026-10-25: at 150% on a 360 screen Menu ran to x 383). The container is
   the nav, never the header, whose containment would hold the fixed pill. The three
   need about 15em in the nav's own text (measured: they fit 320 to 1440 at 100%). */
.site-nav{container:sitenav / inline-size}
@container sitenav (max-width: 14.99em){
  .site-nav .site-nav__quick{display:none}
}
/* Forced colours cannot recolour a logo drawn as a picture: each lockup keeps its own
   ground, so the name never vanishes into a system colour (audit, 2026-10-25). */
@media (forced-colors: active){
  .site-nav__logo{forced-color-adjust:none;box-sizing:content-box;padding:2px 6px;border-radius:6px}
  .site-nav__logo--paper{background:#F4F3EF}
  .site-nav__logo--ink{background:#111827}
}
/* One lockup at a time: the reversed one on ink, the dark one on the pill's paper. */
.site-nav__logo--paper{display:none}
.site-nav__menu{order:2;display:inline-flex;align-items:center;min-block-size:44px;padding:0 1.1em;font-weight:700;font-size:1.0625rem;color:var(--paper);background:transparent;border:2px solid var(--paper);border-radius:999px;cursor:pointer}
.site-nav__menu[aria-expanded="true"]{background:var(--paper);color:var(--ink)}
/* Get in touch beside Menu on phones (D-51, header.php): the page's one action in reach
   on every page, not behind Menu. The kit's button at the bar's smaller size, before
   Menu so the thumb meets the action first; gone from 64em, where the action sits among
   the links, and without the script, where the links wrap into the bar with it. */
.site-nav__quick{order:1;flex:none}
/* (.site-nav .site-nav__quick below: the kit's button rule, :is(.section,.site-nav) .btn,
   sets display and outranks a single class.) */
.site-nav .site-nav__quick .btn__face{min-block-size:2.75rem;padding:.5em .85em;font-size:.875rem;white-space:nowrap}
@media (min-width: 64em){
  .site-nav .site-nav__quick{display:none}
}
/* From 48em (a tablet, a phone held sideways) the bar has room to spare, so Get in touch
   takes Menu's 17px rather than the phones' 14px, which read as a size smaller than the
   button beside it on a 768 to 1023 screen. */
@media (min-width: 48em) and (max-width: 63.99em){
  .site-nav .site-nav__quick .btn__face{padding-inline:1.05em;font-size:1.0625rem}
}
html:not(.js) .site-nav .site-nav__quick{display:none}
/* The phones' bar with Get in touch in it (D-51, measured 2026-10-25): the lockup, the
   button and Menu need 333px at the old gaps, so below 64em the gaps close to 8px and
   Menu's sides to .9em, and the pill's left padding to .875rem (with the pill's own
   rules, below). With the lockup that fits from 366px (measured in both states, review
   of 2026-10-25: at 360 Menu ran 6px into the pill), so below 23em the lockup is the RS
   symbol alone (header.php), which fits a 320. The home link keeps 44px across whatever
   it holds. The number, once it is real, gives way below 32em, where the bar no longer
   has room for it as well (it stays last in the menu). */
@media (max-width: 63.99em){
  .site-nav{gap:var(--space-1)}
  .site-nav__menu{padding:0 .9em}
}
@media (max-width: 31.99em){
  .site-nav__tel,.site-head.is-fixed .site-nav__tel{display:none}
}
.site-nav__tel{order:3;font-weight:700;font-variant-numeric:tabular-nums;white-space:nowrap}
.site-nav__tel a{color:var(--paper)}
/* The slot's link is a 44px target with its words in the middle, as the desktop links
   are (below). .text-link is inline-block, which outranks engine.css's inline-flex for
   [data-tap], so on a touch screen the 44px box held its words at the top: 9px above
   Menu's and the links' words, in the bar and the pill alike (P1-05, P1-17). */
.site-nav__tel .text-link{display:inline-flex;align-items:center;min-block-size:44px}
.site-nav__links{list-style:none;margin:0;padding:0}
.site-nav__links a{font-weight:500;font-size:1.0625rem}
.site-nav__links .text-link{color:var(--paper)}
.site-nav .btn{--plate-sm:4px}
.site-nav .btn__face{gap:.5em;min-block-size:2.75rem;padding:.55em 1.05em .55em 1.25em}
/* The arrow on "Get in touch" is decoration (aria-hidden); it leans on toward the
   contact page under a mouse. */
.site-nav__arrow{flex:none;inline-size:1em;block-size:1em;transition:transform var(--t-hover) var(--ease-out)}
@media (hover:hover){
  .site-nav .btn:hover .site-nav__arrow{transform:translateX(3px)}
}

/* Phones: the menu drops below the bar as an overlay panel; its links arrive as a list
   (P2 at 50ms steps, 12px). */
@media (max-width: 63.99em){
  .site-nav__links{position:absolute;top:100%;left:0;right:0;padding:var(--space-1) var(--margin) var(--space-4);background:var(--canvas);border-block-end:1px solid var(--rule-ink)}
  /* The panel ends at the screen's foot and scrolls inside itself, never the page under
     it, as the pill's window does (below): with the number, the email and the brand's
     line (P1-52) it ran 67px past the foot of a 320 by 568 screen, and over 280px past a
     phone held sideways (review finding bar-menu-320x568). It hangs from the bar, so
     the bar's height is all it leaves above. With JavaScript only: without it the list
     sits in the bar's own flow (below) and must never be cut short. */
  .js .site-nav__links{box-sizing:border-box;max-block-size:calc(100vh - var(--nav-h));max-block-size:calc(100dvh - var(--nav-h));overflow-y:auto;overscroll-behavior:contain}
  /* The panel (and the pill's window) is the size container for the brand's line at its
     foot (below), so that line is sized to the panel it sits in, never past it. */
  .js .site-nav__links{container-type:inline-size}
  .site-nav__links .text-link{display:flex;align-items:center;min-block-size:3.5rem;font-size:var(--fs-lead);border-block-end:1px solid var(--rule-ink)}
  /* One menu, one type, wherever it is opened (looks-2): the links in the display face,
     as in the pill's window (below), so the brand's line under them no longer outweighs
     them. The way to reach us stays in the body face at its boldest, as there, so the
     email still fits at 320px. With JavaScript only: without it the links sit in the
     bar's own row (below), at the bar's size. */
  .js .site-nav__links .text-link{font-family:var(--font-display);font-size:clamp(1.5rem, 1.2rem + 1.4vw, 2rem);font-weight:400;letter-spacing:-.02em}
  .js .site-nav__call .text-link{font-family:var(--font-body);font-size:var(--fs-lead);font-weight:700;letter-spacing:0}
  /* The email breaks after its @ (<wbr>), and anywhere only as the last resort, as the
     contact page's does: at 200% text "remasterstudio.co.uk" alone is wider than either
     panel and ran off it. */
  .site-nav__call .text-link{overflow-wrap:anywhere}
  .site-nav__links .text-link::after{display:none}
  /* Never a display value on the list itself: core.js closes the menu with hidden. */
  /* Air under the action as well as over it: in the pill's window the face has no
     plate under it, and at 320 the email under it wraps to two lines that filled their
     row, so its first line sat 2px under the button (18px in the bar's panel). */
  .site-nav__action{padding-block:var(--space-3) var(--space-1)}
  .site-nav__action .btn{font-size:var(--fs-lead)}
  /* Never wider than the panel either: its narrowest, "touch", the arrow and the face's
     sides need 6.3em, which the pill's window at 320 with 200% text did not have (it ran
     60px into the window's edge). Binds only there; at 100% text it is the lead size. */
  .js .site-nav__action .btn{font-size:min(var(--fs-lead), 100cqi / 7.5)}
  .site-nav__links > li:nth-child(2){--i:1}
  .site-nav__links > li:nth-child(3){--i:2}
  .site-nav__links > li:nth-child(4){--i:3}
  .site-nav__links > li:nth-child(n+5){--i:4}
  .js[data-menu-open] .site-nav__links > li{animation:menu-in .36s var(--ease-arrive) calc(var(--i, 0) * 50ms) both}
}
@keyframes menu-in{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
/* The phone in the menu (layout-1). With Get in touch beside Menu (D-51) the bar and the
   pill show the number only from 32em (the phones' bar, above), so below that the
   number is the menu's last item; the menu carries it at every phone width. [data-tap] (engine.css)
   would centre it; it sits on the left with the other items. */
.site-nav__call{display:none}
@media (max-width: 63.99em){
  .site-nav__call{display:block}
  .site-nav__call .text-link{justify-content:flex-start;box-sizing:border-box;padding-block:.5rem;font-weight:700;font-variant-numeric:tabular-nums}
}
/* The menu's foot (P1-52, both panels): the brand's line, after the ways to reach us,
   large in the display face so its last word can take the magenta (4.02:1 on the ink,
   large text only); in ink on the pill's paper window, below. Phones only, and never in
   the bar's own flow without JavaScript, where it would sit among the links. */
.site-nav__brand{display:none}
@media (max-width: 63.99em){
  .site-nav__brand{display:block;margin-block-start:var(--space-3);max-inline-size:14ch;font-family:var(--font-display);font-size:clamp(1.75rem, 1.4rem + 1.6vw, 2.25rem);line-height:1.05;letter-spacing:-.02em;color:var(--paper);text-wrap:balance}
  /* Never wider than the panel: "remastered." is 6.42em, so the line keeps to 1/6.6 of
     the panel's width, which only binds at large text (at 200% on a 390 phone both its
     words ran 60 to 120px past the window; at 100% it is under the clamp from 320). */
  .site-nav__brand{font-size:min(clamp(1.75rem, 1.4rem + 1.6vw, 2.25rem), 100cqi / 6.6)}
  .site-nav__brand em{font-style:normal;color:var(--accent)}
  html:not(.js) .site-nav__brand{display:none}
}
/* "Menu" reads "Close" while the menu is open (P1-52). Both words share one grid cell,
   the other hidden, so the button keeps the wider word's width and nothing beside it
   moves; visibility, not display, for that, and its name is still the word shown. */
.site-nav__word{display:inline-grid;justify-items:center}
.site-nav__word > span{grid-area:1 / 1}
.site-nav__menu[aria-expanded="true"] .site-nav__word--open,.site-nav__menu:not([aria-expanded="true"]) .site-nav__word--shut{visibility:hidden}
/* Phones without JavaScript: the Menu button cannot work and core.js never hides the
   list, so the links wrap in the bar's own flow below the lockup instead. The list's own
   last row is then the one way to reach us, after "Get in touch" as the Sitewide board
   draws it: the bar's slot, once there is a number, would print it a second time from
   32em, and the email under a real number (P1-52) is for the open menu, not this row of
   links. */
@media (max-width: 63.99em){
  /* 12px over the lockup's 44px row puts it 17px from the top, as in the 68px bar; the
     wrapped bar had it 5px from the screen's top edge. */
  html:not(.js) .site-nav{flex-wrap:wrap;row-gap:0;padding-block-start:.75rem}
  html:not(.js) .site-nav__menu{display:none}
  html:not(.js) .site-nav__tel,html:not(.js) .site-nav__call + .site-nav__call{display:none}
  html:not(.js) .site-nav__links{position:static;order:4;flex-basis:100%;display:flex;flex-wrap:wrap;align-items:center;column-gap:var(--space-3);padding:0 0 var(--space-2);border:0}
  html:not(.js) .site-nav__links .text-link{min-block-size:44px;font-size:1.0625rem;border:0}
  html:not(.js) .site-nav__action{padding:0}
  html:not(.js) .site-nav__action .btn{font-size:1.0625rem}
}
/* The page you are on (P1-31, after Huge, Clay and Frost): its link holds the underline
   the hover draws, 2px in the magenta on the ink, in ink on the paper pill and its
   window (below), read from aria-current, which stays for screen readers. "Get in
   touch" is a button, with no line to hold: on the contact page its words take the
   same 2px magenta line, on the paper face and on the pill's ink one alike. In the phone
   menus a row is the link, edge to edge, so the line was the row's width: the current
   row is a grid whose one track is its words' width, and the line is that track's
   second row, pulled up to where the hover's line sits on the bar (the links' hover
   never draws in the menus). */
.site-nav__links .text-link[aria-current="page"]::after{transform:scaleX(1)}
.site-nav__action .btn[aria-current="page"] .btn__face{text-decoration-line:underline;text-decoration-color:var(--accent);text-decoration-thickness:2px;text-underline-offset:.25em}
@media (max-width: 63.99em){
  .site-nav__links .text-link[aria-current="page"]{display:grid;grid-template-columns:max-content;grid-template-columns:fit-content(100%);align-content:center}
  .site-nav__links .text-link[aria-current="page"]::after{display:block;position:static;margin-block-start:calc(-2px - .02em)}
}
/* Desktop: one line, the links between the lockup and the phone. The bar is 80px and
   the lockup 48px (Sam, 2026-10-25: the logo a fifth bigger, and room round "Get in
   touch", which sat 7px from the bar's edges at 68px; now 13px). --nav-h feeds every
   calc() that keeps the first screen clear of the bar (hero.css, pages.css, case.css,
   the wheel and the sticky heads), so they all move with it. Phones keep 68px and their
   lockup: at 375 it already stands 8px from Get in touch. */
@media (min-width: 64em){
  :root{--nav-h:5rem}
  .site-nav{gap:clamp(1.25rem, .5rem + 1.6vw, 2.25rem)}
  .site-nav__menu{display:none}
  .site-nav__logo{block-size:3rem}
  .site-nav__links:not([hidden]){display:flex;align-items:center;gap:clamp(1.25rem, .5rem + 1.6vw, 2.25rem);order:1}
  .site-nav__links .text-link{display:inline-flex;align-items:center;min-block-size:44px}
  .site-nav__action{margin-inline-start:var(--space-1)}
}

/* The pill, once the first screen has gone (Made by Shape's floating bar, in the kit):
   fixed, inset from the screen's edges by the page margin and below the notch, centred
   and at most 72rem; paper, the kit's 3px ink outline, fully round, on a hard magenta
   plate that never moves or transitions (the pill carries it). It drops in from above on
   the body's arrival curve, transform only, 600ms (an overlay's entrance). The header
   keeps the bar's height in the flow, so the page never jumps.
   The ring (Sam, 2026-10-25: "when I scroll it touched"): the pill set its sides only,
   and "Get in touch" kept the bar's fluid face, 50px at 19px type, so from 1200px wide
   it filled the 50px inside the outline, 0.2px clear above and below and 24px at the
   end, and its ink border ran into the pill's. Now the face and Menu are a fixed
   --pill-face (44px) on an even --pill-ring (6px) above, below and at the end, and the
   pill's height is the sum, --pill-h (62px), which scroll-padding, the self-check's
   clearance and the phone window below read. */
.site-head.is-fixed .site-nav{position:fixed;top:calc(env(safe-area-inset-top, 0px) + .625rem);left:0;right:0;inline-size:min(100% - 2 * var(--margin), 72rem);min-block-size:var(--pill-h);margin-inline:auto;padding:var(--pill-ring) var(--pill-ring) var(--pill-ring) 1.125rem;color:var(--ink);background:var(--paper);border:var(--line) solid var(--ink);border-radius:999px;box-shadow:var(--plate-sm) var(--plate-sm) 0 var(--accent);animation:nav-drop .6s var(--ease-arrive) both}
@keyframes nav-drop{from{transform:translateY(-140%)}to{transform:none}}
.site-head.is-fixed .site-nav__logo--ink{display:none}
/* Half the text's growth, as the pill's face (above): 28px at 100% text, 42 at 200%,
   where at 1.75rem the symbol (64px wide) ran 4px into Menu on a 320 phone. */
.site-head.is-fixed .site-nav__logo--paper{display:block;block-size:calc(.875rem + 14px)}
.site-head.is-fixed .site-nav__links .text-link,.site-head.is-fixed .site-nav__tel a{color:var(--ink)}
.site-head.is-fixed .site-nav__links .text-link[aria-current="page"]::after{background:var(--ink)}
.site-head.is-fixed .site-nav__menu{min-block-size:var(--pill-face);color:var(--ink);border-color:var(--ink)}
.site-head.is-fixed .site-nav__menu[aria-expanded="true"]{color:var(--paper);background:var(--ink)}
/* "Get in touch" on the pill: an ink pill with paper words, flat, since the pill under
   it already stands on the plate; the arrow still leans on. Its face is the fixed
   --pill-face whatever the type's size, so it can never grow into the outline (above). */
.site-head.is-fixed .site-nav .btn{margin:0;background:none}
.site-head.is-fixed .site-nav .btn__face{block-size:var(--pill-face);min-block-size:0;padding-block:0;white-space:nowrap;color:var(--paper);background:var(--ink);border-color:var(--ink);transform:none}
@media (hover:hover){
  .site-head.is-fixed .site-nav .btn:hover .btn__face{transform:none}
}
.site-head.is-fixed .site-nav .btn:active .btn__face{transform:translateY(1px)}
/* The number in the pill (P1-17, Stanley 2026-10-21: "Yes, add it"), only once it is
   real: the tap-to-ring link beside Menu on a phone, last after "Get in touch" on a
   desktop. Never the email in its place (P1-05; header.php prints the slot only for a
   real number): until there is a number the pill stays the lockup, the links and the
   action. Below 32em the pill keeps the lockup, Get in touch and Menu, and the number
   stays last in the menu (the phones' bar, above). */
/* Phones: the pill holds the lockup, Get in touch (D-51) and the Menu button (Stanley's
   brief), and the number from 32em once it is real (above), before Menu: last, it sat 6px from the
   outline, where the pill's end is cut for the round button. The menu opens as a paper
   window under the pill, its links large in the display face. */
@media (max-width: 63.99em){
  /* 14px at 100% text, 21 at 200% (half the text's growth, as the ring's): the symbol
     and Menu need all but 2px of the rest on a 320 phone at 200%. */
  .site-head.is-fixed .site-nav{padding-inline:calc(.4375rem + 7px) var(--pill-ring)}
  .site-head.is-fixed .site-nav__tel{order:1}
  .site-head.is-fixed .site-nav__links{top:calc(100% + .75rem);left:calc(var(--line) * -1);right:calc(var(--line) * -1);padding:var(--space-1) 1.25rem 1.5rem;background:var(--paper);border:var(--line) solid var(--ink);border-radius:var(--radius-win);box-shadow:var(--plate-sm) var(--plate-sm) 0 var(--accent)}
  .site-head.is-fixed .site-nav__links .text-link{font-family:var(--font-display);font-size:clamp(1.5rem, 1.2rem + 1.4vw, 2rem);font-weight:400;letter-spacing:-.02em;border-block-end-color:var(--rule)}
  /* The way to reach us is a fact, not a page: the body face at its boldest, as in the
     bar's panel. In the display face the email (P1-05) ran 10px past the window at 320px
     even broken after its @. */
  .site-head.is-fixed .site-nav__call .text-link{font-family:var(--font-body);font-size:var(--fs-lead);font-weight:700;letter-spacing:0}
  /* The window's own Get in touch keeps the pill's ink face but not its fixed height
     and single line, which are for the pill's row (above): here it wraps and grows with
     the reader's text, as the bar panel's does. At 200% text on a 390 phone the one line
     ran 78px past the window's edge, cut off by its scroll box. */
  .site-head.is-fixed .site-nav__links .btn__face{block-size:auto;min-block-size:var(--pill-face);padding-block:.55em;white-space:normal}
  .site-head.is-fixed .site-nav__brand{color:var(--ink)}
  /* The window is fixed with the pill, so the page cannot scroll it into view: with the
     email and the brand's line (P1-52) it ran past the foot of a 320 by 568 screen, and
     of any phone held sideways. It stops a margin short of the screen's foot (the pill's
     inset, its --pill-h, the window's .75rem gap under it and the plate kept clear) and
     scrolls inside itself, never the page under it. */
  .site-head.is-fixed .site-nav__links{box-sizing:border-box;max-block-size:calc(100vh - env(safe-area-inset-top, 0px) - (.625rem + var(--pill-h) + .75rem + 1rem) - var(--plate-sm));max-block-size:calc(100dvh - env(safe-area-inset-top, 0px) - (.625rem + var(--pill-h) + .75rem + 1rem) - var(--plate-sm));overflow-y:auto;overscroll-behavior:contain}
}
/* An upright tablet: the pill's window drops from Menu as a card at the pill's right
   end, not the pill's whole width: at 768 to 1023 the full-width window was 716 to
   958px of paper with its words in the left 300px. The bar's ink panel stays a sheet
   across the screen. */
@media (min-width: 48em) and (max-width: 63.99em) and (min-height: 32.01em){
  .site-head.is-fixed .site-nav__links{left:auto;inline-size:min(24rem, 100%)}
}
/* A phone held sideways (844 by 390 and the like): the menu in two columns, the pages
   on the left and the action, the way to reach us and the brand's line on the right,
   so the whole menu shows at once. In one column, a full-width list on a 390 screen,
   the email and the line were below the panel's foot in the bar and the window alike
   (213 of 534px out of view at 844 by 390), and the right half of the panel was empty.
   The right column's rows are no taller than the pages' 56px rows beside them, so no
   page's row is stretched: the action without its padding, the email a size down where
   its column is too narrow for it on one line (568 wide), and the brand's line across
   two rows. Its size and the action's keep to the column, not the panel, at large
   text. Both panels, with the script only; never a display value on the list while
   core.js has it hidden. */
@media (max-width: 63.99em) and (orientation: landscape) and (max-height: 32em){
  .js .site-nav__links:not([hidden]){--menu-gap:clamp(1.5rem, 4vw, 4rem);display:grid;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:var(--menu-gap);grid-auto-flow:row dense;align-items:center}
  .js .site-nav__links > li{grid-column:1}
  .js .site-nav__links > :is(.site-nav__action,.site-nav__call,.site-nav__brand){grid-column:2}
  .js .site-nav__links > .site-nav__action{padding-block:0}
  .js .site-nav__action .btn{font-size:min(var(--fs-lead), (100cqi - var(--menu-gap)) / 15)}
  .js .site-nav__call .text-link,.js .site-head.is-fixed .site-nav__call .text-link{font-size:min(var(--fs-lead), (100cqi - var(--menu-gap)) / 28.5)}
  .js .site-nav__links > .site-nav__brand{grid-row:span 2;align-self:end;margin-block-start:0;font-size:min(clamp(1.75rem, 1.4rem + 1.6vw, 2.25rem), (100cqi - var(--menu-gap)) / 13.2)}
  .js .site-nav__links,.site-head.is-fixed .site-nav__links{padding-block-end:var(--space-1)}
}

/* The desktop pill's lockup at 40px, a fifth bigger with the bar's (Sam, 2026-10-25),
   8px inside the ring. */
@media (min-width: 64em){
  .site-head.is-fixed .site-nav{gap:clamp(1rem, .25rem + 1.4vw, 2rem);padding-inline:1.5rem var(--pill-ring)}
  /* The ring is for the action's face; once the number is real and ends the pill, it keeps
     the 24px from the rounded end it had (review reg-pill-number-at-rim, 2026-10-26). */
  .site-head.is-fixed .site-nav__tel{margin-inline-end:calc(1.5rem - var(--pill-ring))}
  .site-head.is-fixed .site-nav__logo--paper{block-size:2.5rem}
}

/* ==== The tagline band: tagline/changing-word, a standout ("clip-reveal trail") ==== */
/* Straight after the film (decision D-16; tagline.php says why a standout sits there).
   Huge's home screen in our kit, on the light page: the brand's line as a poster at the
   old hero headline's size (the intro's rule, 13.5cqi of its column up to 6rem), a word
   to a line, the last word in the magenta and changing; the rolling links (P6) at the
   lower right; and, under a mouse, the picture trail across the whole band, behind the
   words. From 64em on a screen tall enough the band is one screen tall, the headline at
   its top and the links at its foot, which leaves the trail its room. */
.tagline{--tint-ink:#E3E5EA;padding-block:var(--pad-m)}
.tagline__inner{row-gap:var(--gap-head)}
/* Above the trail, as the links are: a tile passes behind the words, so the line stays
   readable while it plays (Sam's item 7; his image 4 showed tiles covering it). */
.tagline__head{position:relative;z-index:2;min-inline-size:0;container-type:inline-size}
/* The 2.25rem floor gives way to 15% of the column at large text (device pass,
   2026-10-27): at 200% its 72px ran "businesses," (6.1em) and "remastered." (6.3em) 110
   to 200px off a 320 or 390 screen. At 100% text the column's 13.5cqi is over the floor
   from 320, so it never binds there. */
.tagline h2{font-size:clamp(min(2.25rem, 15cqi), 13.5cqi, 6rem)}
/* Where the trail can play (a mouse, motion allowed), a keyline of the page's paper round
   every letter: unseen on the band's own paper, it keeps the words whole where a tile
   passes behind them, the magenta word over the clay's magenta above all (Sam's item 7).
   Offset shadows with no blur, in eight directions, which every browser paints behind
   the letters; painted once, never animated. */
@media (hover:hover) and (pointer:fine) and (prefers-reduced-motion: no-preference){
  .js .tagline[data-standout] h2{text-shadow:.035em 0 var(--paper),-.035em 0 var(--paper),0 .035em var(--paper),0 -.035em var(--paper),.025em .025em var(--paper),-.025em .025em var(--paper),.025em -.025em var(--paper),-.025em -.025em var(--paper)}
}
/* A word to a line: each P1 line box a block. The last one holds every changing word in
   one grid cell, so the line is as tall as one word whichever shows, and each word on
   one line (tagline.php leaves out a phrase too wide for it; site.js skips any copy
   that would not fit the screen in hand). The copies are not for copying. The changing
   line clips at its own top, not .1em above it: at this leading a word leaving upward
   would otherwise pass over the foot of the line above (no glyph here reaches above
   the line box at rest). */
.tagline .word{display:block}
/* As blocks, the words' negative margins collapse rather than cancel their clip padding,
   so the first line's letters stood 10px further from the second's than the second's
   from the third's (101 and 91px at 1440, measured on the letters). The middle line moves
   up by that, and the changing line with it, so both gaps are even and its clip keeps
   the same clearance from the line above (audit, 2026-10-25). */
.tagline .word + .word:not(.tagline__slot){margin-block-start:-.26em}
.tagline .tagline__slot{padding-block-start:0;margin-block-start:0;color:var(--accent)}
.tagline .tagline__words{display:inline-grid}
.tagline__w{grid-area:1 / 1;justify-self:start;white-space:nowrap}
.tagline__w[aria-hidden]{-webkit-user-select:none;user-select:none}

/* The change, once site.js has built the copies (.is-built): every word waits below its
   line; the one showing (.is-on) rises into it, 550ms on the arrival curve, a beat after
   the old one (.is-off) starts up and out, 400ms. A word that has left goes back below
   unseen, with no transition. .is-snap puts "remastered." back at once whenever a run is
   cut short (off screen, a hidden tab, reduced motion). One run, then "remastered." for
   good (a11y-7). Transform and opacity only. */
.js [data-standout] .tagline__words.is-built > .tagline__w{transform:translateY(calc(100% + .3em));opacity:0}
.js [data-standout] .tagline__words.is-built > .tagline__w.is-on{transform:none;opacity:1}
.js [data-standout] .tagline__words.is-built > .tagline__w.is-off{transform:translateY(calc(-100% - .3em));opacity:0}
.js [data-standout] .tagline__words.is-built:not(.is-snap) > .tagline__w.is-on{transition:transform .55s var(--ease-arrive) 60ms,opacity .3s var(--ease-out) 60ms}
.js [data-standout] .tagline__words.is-built:not(.is-snap) > .tagline__w.is-off{transition:transform .4s var(--ease-roll),opacity .3s var(--ease-in-out) 80ms}

/* The links: a short list of sentences, each with its arrow, which leans on a few px as
   its letters roll. Above the trail, so a tile never covers them, and never hit by it. */
.tagline__links{position:relative;z-index:2;min-inline-size:0}
.tagline__links ul{list-style:none;margin:0;padding:0;display:grid;justify-items:start}
.tagline .tagline__link{display:inline-flex;align-items:center;gap:.4em;min-block-size:44px;font-weight:700;font-size:var(--fs-body);line-height:1.3;color:var(--ink)}
.tagline__arrow{flex:none;inline-size:.85em;block-size:.85em;transition:transform var(--t-hover) var(--ease-out)}
@media (hover:hover) and (pointer:fine){
  .tagline__link:is(:hover,:focus-visible) .tagline__arrow{transform:translateX(.3em)}
}
@media (min-width: 48em){
  .tagline__head{grid-column:1 / -1}
  .tagline__links{grid-column:7 / -1}
}
@media (min-width: 64em){
  .tagline__head{grid-column:1 / 9}
  .tagline__links{grid-column:9 / -1}
}
/* One screen: the links span both rows at the foot, in their own columns, so the band
   is never taller than the headline and the list need it to be. */
@media (min-width: 64em) and (min-height: 40em){
  .tagline{display:grid;box-sizing:border-box;min-block-size:100vh;min-block-size:100dvh;padding-block:var(--pad-s)}
  .tagline__inner{grid-template-rows:auto 1fr}
  .tagline__links{grid-row:1 / -1;align-self:end}
}
/* The one screen was for the links at its foot. The So What test cut them (D-47), and the
   band was left a headline over 485px of empty paper at 1440 (681px before About's words,
   against the page's 233 to 344px between sections; audit, 2026-10-25). Without links it
   is a band like the others; with links it is one screen again by itself. */
@media (min-width: 64em) and (min-height: 40em){
  .tagline:not(:has(.tagline__links)){min-block-size:0;padding-block:var(--pad-m) var(--pad-s)}
}

/* The trail (site.js): a layer over the band, clipped to it, so a tile never crosses
   into the film above or the section below; under the headline and the links. Each
   tile a kit square: the small corner, the ink outline, a flat ground of paper, white,
   the pale magenta or a pale ink, and a clay object on it, old then new: the two
   pictures share one cell, and site.js fades one into the other (Sam's item 7). Its
   size follows the screen (about 10rem at 1440); it is placed by its centre, so
   site.js only sets a transform. Hidden at rest: only its animation (pop in, turn new,
   fade out) shows it. The pictures take 84% of it: the pairs are cut with room for the
   old ones' loose parts (an antenna, a fallen arrowhead), so the objects sit as large
   as the single icons did at 76%. */
.tagline > .tagline__trail{position:absolute;inset:0;z-index:1;inline-size:auto;margin:0;overflow:hidden;overflow:clip;pointer-events:none}
.tagline__tile{--tile:clamp(7rem, 11vw, 11rem);position:absolute;top:calc(var(--tile) / -2);left:calc(var(--tile) / -2);display:grid;place-items:center;box-sizing:border-box;inline-size:var(--tile);block-size:var(--tile);background:var(--tile-bg, var(--paper));border:var(--line) solid var(--ink);border-radius:var(--radius-sm);opacity:0}
.tagline__tile img{grid-area:1 / 1;display:block;inline-size:84%;block-size:auto}

@media (prefers-reduced-motion: reduce){
  .js [data-standout] .tagline__words > .tagline__w,.tagline__arrow{transition:none}
}

/* ==== About: about/thread, a short message thread (Sam, 2026-10-25). Light page ==== */
/* The visitor asks, Sam and Stanley answer (about.php). From 64em, the heading and the
   approved line frame the left five columns, top and foot, and the thread runs down the
   right seven, its question level with the heading: the eye goes heading, thread, line,
   link, the source order. Phones: one column, the way a phone shows a chat. The kit, not
   an app: no times, ticks or "online" dots (page-design 11), just the brand's white
   windows made bubbles, each on its magenta plate, the visitor's in ink as the nav's one
   ink button is. --pad-m above, as every neighbour has: --pad-l left 344px of bare paper
   over the old heading at 2000. */
.about{--face:clamp(2.75rem, 2.6rem + 2.2vw, 5.5rem);--bubble-fs:clamp(1.1875rem, .95rem + .8vw, 1.75rem);--bubble-r:clamp(1.25rem, 1rem + .8vw, 1.75rem);--bubble-tip:.375rem}
.about__inner{row-gap:var(--gap-head)}
/* The section heading's own size, as every other section's (audit, 2026-10-25): at the
   poster size it was 112px, out-ranking the brand's own line just above it (96px), and
   past the 6rem display cap. Its old -.04em indent went with it: the words are
   inline-block spans, so every word inherited it, and the heading hung 4px past the
   frame with word gaps a seventh tighter than every other heading's. */
.about h2{font-size:var(--fs-h2)}

/* The thread: a list of groups, one per speaker in turn. */
.about__thread{list-style:none;margin:0;padding:0;row-gap:clamp(1.75rem, 1.3rem + 1.6vw, 2.75rem)}
.thread__group{min-inline-size:0}
.thread__group.is-you{display:grid;justify-items:end;justify-self:end;row-gap:calc(var(--plate) + .5rem);max-inline-size:min(88%, 30rem)}
/* Face, name and messages share one grid, the face beside the first message and the
   name over it, as a chat sets a group. The dots take the first message's own cell, so
   they come and go without moving anything. */
.thread__group.is-them{display:grid;grid-template-columns:var(--face) minmax(0,1fr);column-gap:clamp(.75rem, .45rem + 1.1vw, 1.375rem);row-gap:calc(var(--plate) + .5rem);justify-items:start;align-items:start}
.thread__group.is-them > *{grid-column:2}
.thread__group.is-them > .thread__face{grid-area:2 / 1}
.thread__name{grid-area:1 / 2;font-family:var(--font-display);font-size:clamp(.9375rem, .85rem + .35vw, 1.1875rem);line-height:1.15;letter-spacing:-.01em;color:var(--ink)}
.thread__name + .thread__bubble,.thread__typing{grid-area:2 / 2}
/* Initials until that person's photograph comes (inc/about.php): Sam on white, Stanley
   on ink, so the two read apart at a glance as well as by their letters. */
.thread__face{display:grid;place-items:center;box-sizing:border-box;inline-size:var(--face);block-size:var(--face);font-family:var(--font-display);font-size:calc(var(--face) * .34);line-height:1;letter-spacing:-.02em;color:var(--ink);background:var(--white);border:var(--line) solid var(--ink);border-radius:50%;box-shadow:var(--plate-sm) var(--plate-sm) 0 var(--accent);overflow:hidden;overflow:clip}
.thread__group.is-alt .thread__face{color:var(--paper);background:var(--ink)}
/* A short name in the circle ("Sam", "Stan": Stanley, 2026-10-26, so a client sees at
   once who is who) in place of initials: smaller, so four letters clear the ink ring at
   the smallest face, 44px. */
.thread__face.is-word{font-size:calc(var(--face) * .29);letter-spacing:-.03em}
/* A real photograph, person by person (Sam, 2026-10-26): it fills the same circle, in
   the same outline and on the same plate as the initials beside it, so one photograph
   and one set of initials still read as a pair. The square cut needs no crop; a 4:5
   portrait alone keeps the face high, as the About page's cards keep it. */
.thread__face.has-photo{background:var(--slot)}
.thread__img{display:block;inline-size:100%;block-size:100%;object-fit:cover;object-position:50% 28%}
/* A face that links to that person's own page (D-72): the plate grows under a mouse, as
   the kit's buttons lift, and the focus ring is the kit's. No transform, which the
   thread's play-in owns. */
a.thread__face{text-decoration:none;-webkit-tap-highlight-color:transparent}
a.thread__face:focus-visible{outline:3px solid var(--focus);outline-offset:3px}
@media (hover:hover){
  a.thread__face{transition:box-shadow var(--t-state) var(--ease-out)}
  a.thread__face:hover{box-shadow:calc(var(--plate-sm) + 3px) calc(var(--plate-sm) + 3px) 0 var(--accent)}
}
/* A bubble is as wide as its longest line (site.js measures it, .is-fit), so a short
   message makes a short bubble, as in any chat; balanced lines keep the shape even.
   Without the script each simply takes the measure. */
.thread__bubble{position:relative;box-sizing:border-box;max-inline-size:min(100%, 28ch);padding:.62em .95em .68em;font-size:var(--bubble-fs);font-weight:450;line-height:1.36;text-wrap:balance;color:var(--ink);background:var(--white);border:var(--line) solid var(--ink);border-radius:var(--bubble-r);border-start-start-radius:var(--bubble-tip);box-shadow:var(--plate) var(--plate) 0 var(--accent)}
.thread__bubble.is-fit{inline-size:var(--fit)}
/* The balance above lost to ".section p" (text-wrap: pretty, one class and a type), so a
   two-line message broke long then short ("...we notice and / fix it." at 1440, found
   2026-10-26); the thread's own class wins it back. */
.about__thread .thread__bubble{text-wrap:balance}
.thread__group.is-you .thread__bubble{font-weight:500;color:var(--paper);background:var(--ink);border-radius:var(--bubble-r);border-start-end-radius:var(--bubble-tip);box-shadow:none}

/* "Typing": three dots in a small bubble, only while site.js has armed the thread. */
.thread__typing{display:none}
.about__thread.is-armed .thread__typing{display:inline-flex;align-items:center;gap:.32em;box-sizing:border-box;padding:.9em 1em;font-size:var(--bubble-fs);background:var(--white);border:var(--line) solid var(--ink);border-radius:var(--bubble-r);border-start-start-radius:var(--bubble-tip);box-shadow:var(--plate-sm) var(--plate-sm) 0 var(--accent);opacity:0;transform:scale(.8);transform-origin:0 0;transition:opacity .18s var(--ease-out),transform .35s var(--ease-arrive)}
.thread__typing i{display:block;inline-size:.36em;block-size:.36em;border-radius:50%;background:var(--ink)}
.thread__typing i:nth-child(2){--d:1}
.thread__typing i:nth-child(3){--d:2}
.about__thread.is-armed .is-typing .thread__typing{opacity:1;transform:none}
/* A few beats while it shows, never a loop (D-31): a finite count, run only while the
   group is typing. */
.about__thread.is-armed .is-typing .thread__typing i{animation:thread-dot .84s var(--ease-in-out) calc(var(--d, 0) * 140ms) 4 both}
@keyframes thread-dot{0%,64%,100%{transform:none;opacity:.3}32%{transform:translateY(-.22em);opacity:1}}

/* The play-in (site.js, bootThread), once: nothing starts hidden until the script arms
   the thread, so without it, before it runs or under reduced motion the whole thread is
   there (T-052). Each message grows from the corner nearest its speaker. */
.about__thread.is-armed .thread__bubble{transition:opacity .3s var(--ease-out),transform .55s var(--ease-arrive)}
.about__thread.is-armed .thread__bubble:not(.is-on){opacity:0;transform:translateY(.5rem) scale(.9)}
.about__thread.is-armed .is-them .thread__bubble{transform-origin:0 0}
.about__thread.is-armed .is-you .thread__bubble{transform-origin:100% 0}
.about__thread.is-armed :is(.thread__face,.thread__name){transition:opacity .3s var(--ease-out),transform .5s var(--ease-arrive)}
.about__thread.is-armed .thread__group:not(.is-on) .thread__face{opacity:0;transform:scale(.6)}
.about__thread.is-armed .thread__group:not(.is-on) .thread__name{opacity:0;transform:translateY(.35rem)}
@media print{
  .about__thread .thread__typing{display:none}
  .about__thread.is-armed :is(.thread__bubble,.thread__face,.thread__name){opacity:1;transform:none}
}

/* Under the thread: the approved line, set as the section's closing statement (the
   display face, a step under the heading), then the link on to the About page (D-17),
   its arrow leaning on toward the page under a mouse, as the nav's does; under reduced
   motion it moves at once. */
.about__foot{display:grid;gap:clamp(1rem, .7rem + 1vw, 1.75rem);align-content:start;justify-items:start}
.about__line{max-inline-size:17ch;font-family:var(--font-display);font-size:clamp(1.375rem, 1.05rem + 1.1vw, 2.125rem);line-height:1.12;letter-spacing:-.02em;text-wrap:balance;color:var(--ink)}
.about__more{font-size:var(--fs-lead)}
.about__go{display:inline-flex;align-items:center;gap:.4em;min-block-size:44px;font-weight:700;color:var(--ink)}
.about__arrow{flex:none;inline-size:1em;block-size:1em;transition:transform var(--t-hover) var(--ease-out)}
@media (hover:hover){
  .about__go:hover .about__arrow{transform:translateX(3px)}
}
@media (prefers-reduced-motion: reduce){
  .about__arrow{transition:none}
}
@media (min-width: 48em){
  .about__head,.about__thread,.about__foot{grid-column:1 / -1}
  .thread__group.is-you{grid-column:5 / -1;max-inline-size:none}
  .thread__group.is-them{grid-column:1 / 11}
}
/* The frame: the heading at the top of the left five columns, the line and link at their
   foot, level with the thread's last message; the thread on the seven to the right,
   from the top. Its groups sit on the thread's own seven columns. */
@media (min-width: 64em){
  .about__inner.has-thread{grid-template-rows:auto 1fr;row-gap:var(--gap-text)}
  .about__inner.has-thread .about__head{grid-column:1 / 6;grid-row:1}
  .about__inner.has-thread .about__thread{grid-column:6 / -1;grid-row:1 / span 2;grid-template-columns:repeat(7,minmax(0,1fr))}
  .thread__group.is-you{grid-column:3 / -1}
  .thread__group.is-them{grid-column:1 / -1}
  .about__inner.has-thread .about__foot{grid-column:1 / 6;grid-row:2;align-self:end}
}
/* At the reader's own large text on a phone (narrower than 20em of it: never at 16px,
   where 320px is 20em), the face beside its name and the messages under both, the
   screen's width: beside a 92px face the messages had 186px at 390 and 200% text and set
   a word or two a line ("If it ever / goes / down, / we / notice / and fix / it.";
   device pass, 2026-10-27). */
@media (max-width: 19.99em){
  .thread__group.is-them > *{grid-column:1 / -1}
  .thread__group.is-them > .thread__face{grid-area:1 / 1}
  .thread__group.is-them > .thread__name{grid-area:1 / 2;align-self:center}
  .thread__group.is-them > :is(.thread__name + .thread__bubble,.thread__typing){grid-area:2 / 1 / auto / -1}
}

/* ==== Then and now (proof/before-after, calm; D-51) ================================
   The front page's proof of the opening line: the head on the section grid, then the
   frame wide in the kit's window on its plate, then a link to the contact page. The
   window's tab and dot are the kit's; its padding is trimmed so the two pages read
   large. The slider itself (.tn) is shared with the case study's own before and after. */
/* On paper, not the page: the intro ends on paper too (hero.css; its films graded so the
   studio's floor is paper, and their foot faded into it at every size, item 5, Sam,
   2026-10-25), so no line crosses the screen at the join; the white window stands out on
   it (audit, 2026-10-25). */
.then-now{padding-block:var(--pad-m);background:var(--paper)}
/* On the front page the whole light run before the ink band is one ground, paper, so it
   reads as one surface with the white windows on it, and no faint 1.06:1 line crosses
   the screen at each section's edge (audit, 2026-10-25). After the ink band the page's
   own ground returns, where that one edge is hidden behind the band. */
.home :is(.tagline,.about,.services){background:var(--paper)}
.then-now-band__inner{display:grid;row-gap:var(--gap-head)}
.then-now-band__window{padding:calc(var(--tab) + clamp(.5rem, .3rem + .8vw, 1rem)) clamp(.5rem, .3rem + .8vw, 1rem) clamp(.5rem, .3rem + .8vw, 1rem)}
/* From 64em the window is no taller than the screen under the pill (capped on its lift,
   so its plate stays an even 8px rather than a slab on the right), so the whole comparison,
   its handle and its hint are in view at once (audit, 2026-10-25: 896px tall at 1440x900);
   it still starts on the frame line. On any screen held sideways too, a phone's above
   all: the window as wide as the column was 465 to 520px tall on a 375 to 430px screen,
   never in view whole, and too tall for the hero's flight to land on (hero-site.js). In
   portrait the cap never binds. */
@media (min-width: 64em), (orientation: landscape){
  .then-now-band__pic > .lift{max-inline-size:calc(min(100%, (100svh - 12.5rem) * 1.6 + 2rem) - var(--plate))}
}
/* Never under 10rem (or the column, if narrower): at the reader's large text on a phone
   held sideways the 12.5rem kept for the pill, the tab and the hint is taller than the
   screen, and the cap went to nothing: at 200% on 568 by 320 the window was its 40px of
   padding with the comparison gone and its hint a word a line (device pass,
   2026-10-27). At 100% text the cap is 216px or more on every screen on the list, so
   the floor never binds there. */
@media (min-width: 64em), (orientation: landscape){
  .then-now-band__pic > .lift{max-inline-size:calc(max(min(100%, 10rem), min(100%, (100svh - 12.5rem) * 1.6 + 2rem)) - var(--plate))}
}
/* While the hero's flight drives the window (hero-site.js, item 6: it arrives oversized
   and pulls back into its frame as the symbol comes down onto it), the window is at its
   rest from the start, so the rise every window has (P3) does not run inside the flight's
   own move. The flight's transform is measured from the window's top left. */
.then-now-band__pic.is-flight{transform-origin:0 0}
.then-now-band__pic.is-flight > .lift,.then-now-band__pic.is-flight > .lift > .window{transition:none}
/* Side by side (hero.css's 48em at 4:3 or wider), the head's line sits under the heading
   on the left, in the hero's own words' column (the same sum), not offset to the right
   as other sections' are: the hero's flight (hero-site.js, item 6) carries the symbol
   down the right of the screen beside it, where it covered the line for half a screen of
   scroll (D-55). Without the flight it reads as the hero's words do, one screen down. */
@media (min-width: 48em) and (min-aspect-ratio: 4/3){
  .then-now-band__head > .s-intro{grid-column:1 / -1;max-inline-size:min(46ch, 100vw - 965 * max(100vw, (100svh - var(--nav-h)) * 16 / 9) / 1920 - max(var(--margin), (100vw - 82rem) / 2) - 3vw)}
}
.then-now-band__more{margin:0}
.then-now-band__go{display:inline-flex;align-items:center;gap:.4em;min-block-size:44px;font-size:var(--fs-lead);font-weight:700;color:var(--ink)}
/* One line on the smallest phones: wrapped at 320 to 328px, the link's drawn line ran
   through its second line and the arrow sat apart at the edge (review, 2026-10-25). */
@media (max-width: 22.99em){
  .then-now-band__go{font-size:min(var(--fs-lead), 1.0625rem)}
}
:is(.then-now-band__go,.about__go,.steps__go,.work__page-go,.tagline__link)::before,:is(.then-now-band__go,.about__go,.steps__go,.work__page-go,.tagline__link)::after{bottom:calc((44px - 1lh) / 2 + .02em)}
/* A wrapped onward link keeps its rule under its last line, not through it (audit,
   2026-10-25: "it, free." read as struck out at 390). On one line, the same place as
   any .text-link's: these all keep a 44px minimum. */
.then-now-band__arrow{flex:none;inline-size:1em;block-size:1em;transition:transform var(--t-hover) var(--ease-out)}
@media (hover:hover){
  .then-now-band__go:hover .then-now-band__arrow{transform:translateX(3px)}
}

/* The slider (.tn, template-parts/then-now.php). Until site.js makes it live (.is-live,
   never .js alone, so a script that fails to run never leaves a control that does
   nothing): the old page above the new, each with its chip. Live: the old page in the
   flow, the new laid over it from the left and clipped at --split (then-now.php says why
   that way round), a hairline at the cut and a round knob on it. The frame takes the
   pointer (site.js: a sideways drag or a tap moves it, an up-and-down swipe scrolls the
   page and moves nothing, pan-y); the range, invisible over the frame and deaf to the
   pointer, is for the keyboard and screen readers, and the knob takes its focus ring. The
   knob stays inside the frame at 0 and 100, so it and its ring are never cut in half.
   The chips sit at the foot: the top of both pages is their own nav, and a chip there
   covered our logo. */
.tn{position:relative}
.tn__frame{position:relative;display:grid;gap:var(--space-2);overflow:hidden;border-radius:var(--radius-sm)}
.tn__then,.tn__now{display:block}
.tn img{display:block;inline-size:100%;block-size:auto;background:var(--slot)}
.tn__tag{position:absolute;bottom:var(--space-1);z-index:2;padding:.3em .75em;font-size:var(--fs-small);font-weight:700;line-height:1.2;color:var(--ink);background:var(--paper);border:2px solid var(--ink);border-radius:999px;pointer-events:none}
.tn__tag--now{left:var(--space-1)}
/* On a phone the chips sit at the top, over each page's own bar, not over our words at
   the foot of the tall crop (audit, 2026-10-25). */
@media (max-width: 47.99em){
  .tn.is-live .tn__tag{top:var(--space-1);bottom:auto}
}
.tn__tag--then{right:var(--space-1)}
/* A chip goes while its page is all but hidden, so "Now" never sits over the old page
   (site.js sets the classes from the cut; review, 2026-10-25). */
.tn.is-at-then .tn__tag--now,.tn.is-at-now .tn__tag--then{opacity:0}
@media (prefers-reduced-motion: no-preference){
  .tn.is-live .tn__tag{transition:opacity .2s var(--ease-out)}
}
/* While the hero's flight sets the chips' opacity on the scroll (hero-site.js, item 6),
   a transition would only trail it. */
.tn[data-flight="on"] .tn__tag{transition:none}
.tn:not(.is-live) .tn__tag{position:static;justify-self:start}
.tn:not(.is-live) .tn__frame{grid-template-areas:"tthen" "then" "tnow" "now"}
.tn:not(.is-live) .tn__tag--then{grid-area:tthen}
.tn:not(.is-live) .tn__then{grid-area:then}
.tn:not(.is-live) .tn__tag--now{grid-area:tnow}
.tn:not(.is-live) .tn__now{grid-area:now}
.tn__handle,.tn__knob,.tn__range,.tn__hint{display:none}
.tn.is-live .tn__frame{display:block;gap:0;--split:50%;cursor:ew-resize;touch-action:pan-y;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent}
.tn.is-live img{-webkit-user-drag:none}
.tn.is-live .tn__now{position:absolute;inset:0;z-index:1;clip-path:inset(0 calc(100% - var(--split)) 0 0)}
.tn.is-live .tn__now img{block-size:100%;object-fit:cover;object-position:50% 0}
.tn.is-live .tn__handle{display:block;position:absolute;top:0;bottom:0;left:var(--split);z-index:2;inline-size:var(--line);margin-inline-start:calc(var(--line) / -2);background:var(--ink);pointer-events:none}
.tn.is-live .tn__knob{position:absolute;top:50%;left:clamp(1.5rem + 6px, var(--split), 100% - 1.5rem - 6px);z-index:2;display:grid;place-items:center;inline-size:3rem;block-size:3rem;box-sizing:border-box;color:var(--ink);background:var(--paper);border:var(--line) solid var(--ink);border-radius:50%;box-shadow:var(--plate-sm) var(--plate-sm) 0 var(--accent);transform:translate(-50%,-50%);pointer-events:none}
.tn__knob svg{inline-size:1.375rem;block-size:1.375rem}
.tn.is-live .tn__range{display:block;position:absolute;inset:0;z-index:3;inline-size:100%;block-size:100%;margin:0;opacity:0;pointer-events:none}
.tn__range:focus-visible ~ .tn__knob{outline:3px solid var(--focus);outline-offset:3px}
.tn.is-live .tn__hint{display:block;margin-block-start:var(--space-1);font-size:var(--fs-small);font-weight:600;color:var(--ink-soft);text-align:center}

/* ==== Services: the window stack and wheel, a standout ("pan clip-reveal line") === */
.services{padding-block:var(--pad-m) var(--pad-l)}
.services__reduced{display:none;max-inline-size:40ch;color:var(--ink);font-size:var(--fs-body)}
@media (prefers-reduced-motion: reduce){ .services__reduced{display:block} }
.stack{list-style:none;margin-block:var(--gap-head) 0;padding:0;display:grid;row-gap:clamp(1.75rem, 1rem + 3vw, 3rem)}
.stack__card{position:relative}
/* Dealt (Stanley, 2026-10-22): wherever the wheel does not run (below), each window
   arrives tilted a few degrees, alternately leaning right and left as the wheel's lean
   either side of its top, and swings upright as it lands: P3's arrival with a turn in
   it, once, on core.js's .in. 3 degrees keeps a turned corner inside the page's margin
   at 375px. Only with motion allowed, and only under .js (T-052): without the script,
   and under reduced motion, the windows are simply there, upright. */
.stack__card{--lean:3deg}
.stack__card:nth-child(even){--lean:-3deg}
@media (prefers-reduced-motion: no-preference){
  .js [data-standout] .stack__card:not(.in) > .lift{transform:translateY(var(--rise-y)) rotate(var(--lean)) scale(.96)}
}
.stack__window{display:grid;row-gap:var(--gap-text)}
.stack__body{display:grid;align-content:space-between;row-gap:var(--gap-text);min-inline-size:0}
/* Larger than the other h3s: each card is a screen of its own, and a small title left it
   mostly empty space at 1280. */
.stack__name{position:relative;font-size:clamp(1.75rem, .9rem + 3.6vw, 4.5rem)}
/* The card and step titles one clear step under the section heading (the ladder above). */
/* The floor gives way to 11vw at large text (device pass, 2026-10-27): at 200% its 56px
   held "hackers" at 237px, which widened the stack's column and ran every window 77px
   off a 320 screen (45 off a 360). Never at 100% text: 11vw is over 1.75rem from 255px. */
.home .stack__name{font-size:clamp(min(1.75rem, 11vw), 1rem + 2vw, 3rem)}
.stack__window .stack__name{line-height:1}
.stack__foot{display:grid;gap:var(--space-2);justify-items:start}
.stack__text{max-inline-size:34ch;font-size:var(--fs-lead);font-weight:450;line-height:1.45;color:var(--ink-soft)}
.stack__link{display:flex;flex-wrap:wrap;column-gap:var(--space-3);font-weight:700;font-size:var(--fs-body)}
/* 46px, not 44: a window scales from .96 as it arrives, and its targets must still be
   44px while it does. */
.stack__link .text-link{display:inline-flex;align-items:center;min-block-size:46px}
.stack__media{min-inline-size:0;align-self:center}
/* The clay pictures take the kit's ink line, as How it works' tiles do: on their own cream
   in a white window they read as the empty slot's tone (audit, 2026-10-25). */
.stack__media .clip{display:block;box-sizing:border-box;overflow:hidden;overflow:clip;border:var(--line) solid var(--ink);border-radius:var(--radius-sm);background:var(--slot)}
.stack__img{display:block;inline-size:100%;block-size:auto;object-fit:cover}
/* The film panel's frame is cut shorter than 16:9 on the wheel: 2:1 at 1440 by 900, 2.4:1
   at 1366 by 657, 2.8:1 at 1024 by 768. Centred, it keeps the whole symbol (19% to 81% of
   the still's height) at all three; at 2.8:1 its hop (D-18), which rises 3% of the
   frame's height, loses a sliver at the top, where 35% from the top cut the resting
   base instead. */
.stack__card[data-effect="film"] .stack__img{aspect-ratio:16/9;object-position:60% 50%}
/* The live demos (Sam, 2026-10-25: "the cards are boring"; template-parts/services-
   demo.php): each card's picture row a small drawn scene of its promise, 2:1, 400 by
   200 units (--u), every piece placed by its own inline --x, --y, --w and --h. Lines
   are the kit's ink at one weight whatever the scene's size (--dl, 2 to 3.5px). Every
   custom property a piece reads is set here first, so none is inherited from round the
   card: on the wheel, --r is the circle's radius and --run the turn's length, and a
   demo that read them as its own turn and play state played nothing there. On a
   phone the scene is a full-width row between the name and the words, as the film is
   (beside the name, as the icons stood, it was too small to read); from 48em it takes
   half the window beside the words. */
.demo{--u:.25cqi;--dl:clamp(2px, .75cqi, 3.5px);--dx:0;--dy:0;--sx:1;--sy:1;--r:0deg;--t:0s;--play:running;position:relative;display:block;inline-size:100%;aspect-ratio:2 / 1;container:demo / inline-size;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none}
.demo .p{position:absolute;left:calc(var(--x) * var(--u));top:calc(var(--y) * var(--u));inline-size:calc(var(--w) * var(--u));block-size:calc(var(--h) * var(--u));box-sizing:border-box}
@media (max-width: 47.99em){
  .stack__window.has-media .stack__body{display:contents}
  .stack__window.has-media .stack__name{grid-row:1}
  .stack__window.has-media .stack__media{grid-row:2}
  .stack__window.has-media .stack__foot{grid-row:3}
}
@media (min-width: 48em){
  .stack__window{grid-template-columns:repeat(12,minmax(0,1fr));column-gap:var(--gutter)}
  .stack__body{grid-column:1 / -1;grid-template-columns:subgrid}
  .stack__window.has-media .stack__body{grid-column:1 / 7}
  .stack__name{grid-column:1 / 8}
  .stack__foot{grid-column:1 / 7}
  .stack__window.has-media :is(.stack__name,.stack__foot){grid-column:1 / -1}
  .stack__media{grid-column:7 / -1}
  /* A window with no picture (none since every panel has a demo or a film, 2026-10-25;
     kept for a panel added without one) gives its words the room, as the wheel's
     cards do (none may look like an empty window, Stanley 2026-10-19; layout-3): the
     name on columns 1 to 5, the words beside it on 7 to 12, set large as a statement,
     their foot level with the name's when the name is the taller. :where() keeps these
     under the wheel's own rules, which win on the wheel. */
  :where(.stack__window:not(.has-media)) .stack__name{grid-column:1 / 6}
  :where(.stack__window:not(.has-media)) .stack__foot{grid-column:7 / -1;align-self:end}
  :where(.stack__window:not(.has-media)) .stack__text{max-inline-size:none;font-size:clamp(1.375rem, .9rem + 1.3vw, 2.25rem);font-weight:500;line-height:1.25;letter-spacing:-.01em}
}
/* The wheel (Huge's card wheel; Stanley, 2026-10-22: "I quite like this fan idea", in
   place of D-7's sideways pan), at MOTION 8 (D-7), the section's one scroll-driven
   effect. Gated here and only here, exactly as the pan was: a desktop screen tall
   enough, a mouse or trackpad, motion allowed, a browser with scroll-driven animations,
   and the script (.js), whose guard (bootWheel) turns a scroll of the band by
   find-in-page back into the page's own: without it a pinned wheel could be left
   showing the wrong window, or none (motion-2). Everywhere else the cards stack, dealt
   (above): touch screens, phones and tablets keep the vertical stack rather than a
   sideways row (D-9). The section grows by the turn's length, half a screen a card, up
   to one and a half screens: longer than the one-screen rule, at Stanley's request for
   Huge's pace (D-8). Six cards reach that cap at every size, as the pan's row did, so
   the pin is the pan's exactly and nothing below moves; where the turn is longer than
   the cap the wheel just turns faster. No progress line (page-design.md, section 11
   bans filled-track progress bars).
   No scroll listener: a view timeline on .wheel turns the ring, one transform on one
   element; core.js's IntersectionObserver sees the turn, so each card arrives as it
   swings in. overflow: hidden, never clip, keeps the view a scroll container that
   focus, find-in-page and scrollIntoView can still move; site.js turns that scroll into
   the page's own. Delete this one block and the cards simply stack. */
@supports (animation-timeline: view()){
  @media (min-width: 64em) and (min-height: 40em) and (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference){
    .js .wheel{--run:calc((var(--n) - 1) * 50svh);block-size:calc(100svh + min(var(--run), 150svh));margin-block-start:var(--gap-head);view-timeline:--wheel block;view-timeline-inset:0}
    .js .section > .wheel{inline-size:auto}
    /* The circle, from the view's own width (a classic scrollbar makes 100vw wider than
       the page). --card: each window a square about a third of the screen's width, as
       the reference's are, and bigger than the pan's cards were (38vw, and 42rem at
       most); 28rem at least, so a 1024px screen's windows still give their picture a
       frame, but never taller than the screen leaves free under the nav, so the window
       at the top is always whole. --r: the circle's radius, its centre far below the
       band. At 4.6 cards and 15 degrees apart, neighbours clear each other by 7% of a
       card at their feet and a third of one at their heads, a wedge of page between
       them, and the next window round shows at the screen's edge. Halfway between two
       stops, the two windows either side of the top rise 2% of a card above where the
       top one's head stands, less than --air, so no corner reaches the nav. --air: the
       least space above and below the window at the top, less on a short screen; on a
       taller screen that window stands in the middle of the space under the nav, as the
       reference's do, and its neighbours lean down towards the screen's foot. The
       timeline measures against the whole screen: its inset would otherwise follow
       html's scroll-padding (a11y-2) and start the turn 82px before the pin, out of
       step with bootWheel's own reading of it. */
    .js .wheel__view{--air:clamp(1rem, 4svh, 1.75rem);--card:min(clamp(28rem, 40cqw, 56rem), 100svh - var(--nav-h) - 2 * var(--air) - var(--plate));--r:calc(var(--card) * 4.6);--step:15deg;position:sticky;top:0;box-sizing:border-box;block-size:100svh;overflow:hidden;container-type:inline-size}
    /* The ring is a point, the circle's centre; it turns, and every window turns with
       it: from the first at the top to the last, (n - 1) steps. Each window stands on it
       at its own angle (--i, its place in the list), turned with the circle, so the one
       at the top is upright and the ones either side lean away. */
    .js .wheel__ring{position:absolute;top:calc(var(--nav-h) + max(var(--air), (100svh - var(--nav-h) - var(--card) - var(--plate)) / 2) + var(--card) / 2 + var(--r));left:50%;display:block;inline-size:0;block-size:0;margin:0;padding:0;animation:wheel-turn linear both;animation-timeline:--wheel;animation-range:contain 0% contain 100%}
    /* Once bootWheel runs (.has-room), the circle stands a radius and a card to the right
       of the view's start, and the view rests scrolled there, drawn exactly as before: a
       card turned past the top to the left is then inside the view's scroll range too,
       so find-in-page, focus and a screen reader can scroll to it, and bootWheel reads
       where they aimed. Until bootWheel runs the circle stands at 0 and the wheel still
       draws; only the cards past the top are out of their reach. The view's scroll
       reaches a screen past the centre whatever the turn (::after), so it can always
       come to rest. */
    .js .wheel__view.has-room .wheel__ring{left:calc(50% + var(--r) + var(--card))}
    .js .wheel__view.has-room::after{content:"";position:absolute;top:0;left:0;inline-size:calc(100% + var(--r) + var(--card));block-size:1px;pointer-events:none}
    .js .wheel__ring > .stack__card{--lean:0deg;position:absolute;top:calc(var(--card) / -2);left:calc(var(--card) / -2);inline-size:var(--card);transform-origin:50% calc(var(--card) / 2);transform:rotate(calc(var(--i, 0) * var(--step))) translateY(calc(var(--r) * -1));container-type:inline-size}
    /* Its centre, for bootWheel's "at the top": the card's own box is the box round a
       tilted square, wider than the square. */
    .js .wheel__mark{position:absolute;top:50cqi;left:50%;display:block;inline-size:1px;block-size:1px;pointer-events:none}

    /* Each window square (or taller, never cut: with a reader's own text spacing it
       grows with its words, review finding a11y-5, WCAG 1.4.12): the name at the top,
       the picture filling the middle (never under --pic-min), the words and the link at
       the foot, all sized from the card's own width, so a big screen's big window is
       filled, not emptied. The 12-column subgrid is for the stacked cards. */
    .js .wheel__ring .stack__window{--win-pad:clamp(1.25rem, 6cqi, 2.75rem);--pic-min:clamp(5rem, 25cqi, 14rem);display:grid;grid-template-rows:auto minmax(var(--pic-min),1fr) auto;grid-template-columns:minmax(0,1fr);row-gap:clamp(.75rem, 3.6cqi, 1.5rem);aspect-ratio:1}
    .js .wheel__ring .stack__body{display:contents}
    /* One name size on all six cards (parallel items, equal treatment), from the card's
       width, as large as it goes while "Motion and film" still leaves its picture room. */
    .js .wheel__ring .stack__name{grid-row:1;grid-column:1;font-size:clamp(1.75rem, 9cqi, 5.25rem)}
    .js .home .wheel__ring .stack__name{font-size:clamp(1.75rem, 6cqi, 3.5rem)}
    .js .wheel__ring .stack__media{grid-row:2;grid-column:1;position:relative;align-self:stretch;min-block-size:0}
    .js .wheel__ring .stack__media .clip{position:absolute;inset:0}
    .js .wheel__ring .stack__img{block-size:100%;aspect-ratio:auto}
    /* A demo is the largest 2:1 scene its row holds, centred in it. */
    .js .wheel__ring .stack__media--demo{container-type:size}
    .js .wheel__ring .demo{position:absolute;inset:0;margin:auto;inline-size:min(100cqi, 200cqh);block-size:min(50cqi, 100cqh);aspect-ratio:auto}
    .js .wheel__ring .stack__foot{grid-row:3;grid-column:1;gap:var(--space-1)}
    .js .wheel__ring .stack__text{max-inline-size:40ch;font-size:clamp(var(--fs-body), 3cqi, 1.5rem)}
    .js .wheel__ring .stack__link{font-size:clamp(var(--fs-body), 2.6cqi, 1.375rem)}
    /* A card with no picture (none since 2026-10-25's demos; kept for a panel added
       without one) gives its room to its words (Stanley, 2026-10-19: none may look like
       an empty window), set large, one size for all; still --ink-soft, so the name
       leads. */
    .js .wheel__ring .stack__window:not(.has-media){grid-template-rows:auto minmax(0,1fr) auto}
    .js .wheel__ring .stack__window:not(.has-media) .stack__text{max-inline-size:none;font-size:clamp(1.375rem, 6.5cqi, 2.5rem);font-weight:500;line-height:1.25;letter-spacing:-.01em}

    /* Each card's demo plays once it reaches the top of the wheel (.is-here, from
       site.js; bootDemos holds it until then), upright and centred, not at its first
       pixel at the screen's edge (.in). Never a card's name: held back to the top, Found
       on Google and Safe from hackers swung in as blank windows, readable 720px and
       450px of scroll after every other card at 1440 by 900 (Sam, 2026-10-25: "too
       late"). The lift arrives on .in. Not the film card's picture: held back to the
       top, it was an empty white box while the card swung in, as the card was read
       (audit, 2026-10-25). It reveals on .in as in the stack, and its film still plays at
       the top. */
  }
}
.wheel__mark{display:none}
@keyframes wheel-turn{to{transform:rotate(calc((1 - var(--n)) * var(--step)))}}

/* Each card performs its own effect once its window has landed. Finished states first,
   so the section is complete with no script; start states only under .js (T-052).
   services.php pairs them with the panels by number: a demo on 1 to 5, the film on 6
   (Sam, 2026-10-25; D-24's slide, words, glow and draw are gone with it). */
/* film: the clip reveal (P4): the frame opens upward while the picture settles inside it. */
.js [data-standout] .stack__card[data-effect="film"] .clip{clip-path:inset(0 0 0 0 round var(--radius-sm));transition:clip-path var(--t-enter) var(--ease-arrive) .3s}
.js [data-standout] .stack__card[data-effect="film"]:not(.in) .clip{clip-path:inset(100% 0 0 0 round var(--radius-sm))}
.js [data-standout] .stack__card[data-effect="film"] .stack__img{transition:transform var(--t-enter) var(--ease-arrive) .3s}
.js [data-standout] .stack__card[data-effect="film"]:not(.in) .stack__img{transform:scale(1.08)}
/* film, then: the panel's own film (D-18) over its still, once the reveal has finished.
   The <video> carries the still's class, so the reveal's settle above and every wheel
   rule size and move it exactly as they do the still; it lies over it in the same frame
   (absolute on the wheel already). Hidden until it has a frame (site.js marks the frame
   .is-ready), so a film that is loading, or never loads, leaves the still; gone under
   reduced motion. Its first and last frames are the still. */
.clip[data-film]{position:relative}
.stack__film{position:absolute;inset:0;block-size:100%;visibility:hidden}
.clip.is-ready > .stack__film{visibility:visible}
@media (prefers-reduced-motion: reduce){
  .stack__film{display:none}
}
/* demo: each scene drawn finished (no script, reduced motion), and played only once
   site.js arms it (.is-armed), held at its start until its card has arrived (.is-on).
   A moving piece's start is its offset from its finished place: --dx and --dy in units,
   --sx and --sy as scales, played to nothing (d-from). */
@keyframes d-from{from{transform:translate(calc(var(--dx) * var(--u)),calc(var(--dy) * var(--u))) rotate(var(--r)) scale(var(--sx),var(--sy))}}
@keyframes d-fade{from{opacity:0}}
@keyframes d-draw{from{stroke-dashoffset:1}}
/* The kit's window in small: white, the ink line, the tab line and its dot, on its
   magenta plate. --sw: a drawn line's weight in its own picture's units, a step heavier
   in a small scene, so it reads about as --dl does at every size. */
.demo .d-win{background:var(--white);border:var(--dl) solid var(--ink);border-radius:calc(7 * var(--u));box-shadow:calc(4 * var(--u)) calc(4 * var(--u)) 0 var(--accent)}
.demo .d-win::before{content:"";position:absolute;inset:0 0 auto;block-size:calc(13 * var(--u));border-block-end:var(--dl) solid var(--ink)}
.demo .d-win::after{content:"";position:absolute;top:calc(4 * var(--u));left:calc(5 * var(--u));inline-size:calc(5 * var(--u));aspect-ratio:1;border-radius:50%;background:var(--ink)}
.demo svg{display:block;inline-size:100%;block-size:100%;overflow:visible;fill:none;stroke-linecap:round;stroke-linejoin:round;--sw:2.6}
@container demo (min-width: 25rem){ .demo svg{--sw:2.1} }
/* The build: the wide window, pressed onto its plate, narrows into a phone as its
   blocks restack in one column, then lifts off its plate (P3). The frame is two layers,
   ink and white, each clipped to the shape (clip-path, standout only), so its corners
   stay round and its line one weight as it narrows; the blocks are inside the white,
   so they reflow inside the window, as a page does in a narrowing browser. */
.b-plate,.b-dev,.b-frame,.b-screen{position:absolute;inset:0}
.b-plate{background:var(--accent);transform:translate(calc(4 * var(--u)),calc(4 * var(--u)))}
.b-plate,.b-frame{clip-path:inset(4% 38% round calc(9 * var(--u)))}
.b-frame{background:var(--ink)}
.b-screen{background:var(--white);clip-path:inset(calc(4% + var(--dl)) calc(38% + var(--dl)) round calc(9 * var(--u) - var(--dl)))}
.demo .b-tab{block-size:var(--dl);margin-block-start:calc(var(--dl) / -2);background:var(--ink)}
.b-dot,.b-ln,.demo .b-home{border-radius:999px;background:var(--ink)}
.b-blk{transform-origin:0 0;border-radius:calc(1.5 * var(--u))}
.b-head{background:var(--accent)}
.b-pic{background:var(--rule)}
.b-ln--soft{background:var(--rule)}
.b-card{background:var(--slot)}
.demo .b-home{block-size:var(--dl)}
@keyframes b-morph{from{clip-path:inset(12.5% 14% round calc(8 * var(--u)))}}
@keyframes b-morph-in{from{clip-path:inset(calc(12.5% + var(--dl)) calc(14% + var(--dl)) round calc(8 * var(--u) - var(--dl)))}}
@keyframes b-wrap{0%{transform:translate(calc(var(--dx) * var(--u)),calc(var(--dy) * var(--u))) scale(var(--sx),var(--sy))}45%{transform:translate(calc(var(--mx) * var(--u)),calc(var(--my) * var(--u))) scale(var(--msx),var(--msy))}100%{transform:none}}
@keyframes b-press{0%{transform:none;animation-timing-function:var(--ease-in-out)}14%,71%{transform:translate(calc(4 * var(--u)),calc(4 * var(--u)));animation-timing-function:var(--ease-arrive)}92%,100%{transform:none}}
/* Found on Google: a magnifier passes down the page; each line it reads turns to ink and
   gets its tick (drawn, the section's line); then a broken link's two halves meet. No
   results page and no position: Google reads the site, nothing is promised about where
   it shows (services-page.parts.3.note). */
.g-ln,.g-sub{border-radius:999px;background:var(--rule)}
.g-sub{background:var(--slot)}
.demo .g-read{background:var(--ink);transform-origin:0 50%}
.g-tick{border-radius:50%;background:var(--accent)}
.g-tick path{stroke:var(--white);stroke-width:2.6;stroke-dasharray:1}
.g-lens{position:absolute;inset:0;box-sizing:border-box;border-radius:50%;border:calc(var(--dl) * 1.4) solid var(--ink);background:rgb(255 255 255 / .55)}
.g-handle{position:absolute;left:82%;top:82%;inline-size:calc(7 * var(--u));block-size:calc(19 * var(--u));margin-inline-start:calc(-3.5 * var(--u));border-radius:999px;background:var(--ink);transform:rotate(-45deg);transform-origin:50% 0}
.g-chain rect{stroke:var(--ink);stroke-width:calc(var(--sw) * 1.25)}
.g-b rect{stroke:var(--accent)}
.g-halo rect{stroke:var(--white);stroke-width:calc(var(--sw) * 1.25 + 3.5)}
.g-spark{stroke:var(--accent);stroke-width:var(--sw);transform-box:fill-box;transform-origin:100% 100%}
@keyframes g-pass{0%{transform:translate(calc(-150 * var(--u)),calc(-113 * var(--u)))}10%{transform:translate(calc(-112 * var(--u)),calc(-113 * var(--u)))}28%{transform:translate(calc(-112 * var(--u)),calc(-79 * var(--u)))}46%{transform:translate(calc(-112 * var(--u)),calc(-45 * var(--u)))}64%{transform:translate(calc(-112 * var(--u)),calc(-11 * var(--u)))}100%{transform:none}}
/* In the link's own picture, so its units: translate's px are the picture's units. */
@keyframes g-a{from{transform:translate(-9px,9px)}}
@keyframes g-b{from{transform:translate(9px,-9px)}}
@keyframes g-spark{from{opacity:0;transform:scale(.3)}}
/* Safe from hackers: three attempts, from the left, the right and above, each hit the
   padlock, which gives a little and stays shut, and fall to the ground, where they lie.
   No counter and no number of attacks. A hit's spark is gone in a third of a second, so
   the finished picture has none. */
.demo .k-ground{block-size:var(--dl);border-radius:999px;background:var(--rule)}
.demo .k-body{position:absolute;left:0;right:0;bottom:0;block-size:calc(80 * var(--u));border-width:calc(var(--dl) * 1.2);border-radius:calc(13 * var(--u))}
.demo .k-body::before,.demo .k-body::after{content:none}
.k-hole{position:absolute;left:50%;top:calc(82 * var(--u));inline-size:calc(13 * var(--u));aspect-ratio:1;margin-inline-start:calc(-6.5 * var(--u));border-radius:50%;background:var(--ink)}
.k-hole::after{content:"";position:absolute;left:50%;top:60%;inline-size:calc(5 * var(--u));block-size:calc(14 * var(--u));margin-inline-start:calc(-2.5 * var(--u));border-radius:calc(2.5 * var(--u));background:var(--ink)}
.k-shackle path{stroke:var(--ink);stroke-width:calc(var(--sw) * 3.2);stroke-linecap:butt}
.demo .k-dart{transform:rotate(var(--r))}
.k-shaft{stroke:var(--ink);stroke-width:calc(var(--sw) * 1.3)}
.k-tip{fill:var(--ink);stroke:var(--ink);stroke-width:calc(var(--sw) * .6)}
.k-spark{opacity:0}
.k-spark path{stroke:var(--accent);stroke-width:calc(var(--sw) * 1.1)}
@keyframes k-shot{0%{transform:translate(calc(var(--x0) * var(--u)),calc(var(--y0) * var(--u))) rotate(var(--r0));animation-timing-function:cubic-bezier(.55,0,.9,.5)}35%{transform:translate(calc(var(--x1) * var(--u)),calc(var(--y1) * var(--u))) rotate(var(--r0));animation-timing-function:var(--ease-out)}60%{transform:translate(calc(var(--x2) * var(--u)),calc(var(--y2) * var(--u))) rotate(var(--r2));animation-timing-function:cubic-bezier(.5,0,.85,.45)}100%{transform:translate(0px,0px) rotate(var(--r))}}
@keyframes k-spark{0%{opacity:0;transform:scale(.4)}25%{opacity:1;transform:none}100%{opacity:0;transform:scale(1.15)}}
@keyframes k-give{0%,24%,31%,51%,58%,79%,86%,100%{transform:none}27%{transform:translateX(calc(2.5 * var(--u)))}54%{transform:translateX(calc(-2.5 * var(--u)))}82%{transform:translateY(calc(2 * var(--u)))}}
/* We run your site: behind the site's window the sky turns once, day to night to day
   (one transform on one disc), and the window stays lit all through it. At night it
   blips off; a spanner comes and taps it, and it is back (Sam, 2026-10-25: the sites
   are monitored, and "if your site breaks, one of us fixes it" is true). No uptime
   figure, no status dot. */
/* Ink behind the disc, so the clipped edge blends into the frame's ink line at night. */
.demo .r-sky{overflow:hidden;overflow:clip;border:var(--dl) solid var(--ink);border-radius:calc(12 * var(--u));background:var(--ink)}
.demo .r-disc{border-radius:50%;background:linear-gradient(var(--paper) 50%, var(--ink) 50%)}
.r-sun{border-radius:50%;background:var(--accent)}
.r-moon{border-radius:50%;background:var(--paper)}
.r-moon::after{content:"";position:absolute;inset:-6% -6% 18% 22%;border-radius:50%;background:var(--ink)}
.r-star{border-radius:50%;background:var(--paper)}
.demo .r-ground{background:var(--white);border-block-start:var(--dl) solid var(--ink)}
.demo .r-win{border-radius:calc(6 * var(--u))}
.r-bar{border-radius:calc(1.5 * var(--u));background:var(--accent)}
.r-ln{border-radius:999px;background:var(--ink)}
.r-ln--soft{background:var(--rule)}
.r-blk{border-radius:calc(1.5 * var(--u));background:var(--slot)}
.r-off{position:absolute;inset:calc(13 * var(--u)) 0 0;border-radius:0 0 calc(5 * var(--u)) calc(5 * var(--u));background:var(--rule);opacity:0}
.demo .r-fix{transform:rotate(var(--r))}
.r-edge{stroke:var(--ink);stroke-width:calc(var(--sw) * 4.6)}
.r-tool{stroke:var(--accent);stroke-width:calc(var(--sw) * 2.5)}
@keyframes r-turn{0%,4.5%{transform:none;animation-timing-function:var(--ease-in-out)}36%,72%{transform:rotate(180deg);animation-timing-function:var(--ease-in-out)}100%{transform:rotate(360deg)}}
@keyframes r-off{0%,42%{opacity:0}43.5%,47%{opacity:1}45%{opacity:.3}65%{opacity:1}69%,100%{opacity:0}}
@keyframes r-fix{0%,44%{opacity:0;transform:translate(calc(70 * var(--u)),calc(-70 * var(--u))) rotate(30deg)}49%{opacity:1}56%{transform:translate(calc(-12 * var(--u)),calc(-36 * var(--u))) rotate(0deg)}59.5%,66.5%{transform:translate(calc(-12 * var(--u)),calc(-36 * var(--u))) rotate(-20deg)}63%,70%{transform:translate(calc(-12 * var(--u)),calc(-36 * var(--u))) rotate(0deg);animation-timing-function:var(--ease-in-out)}84%,100%{opacity:1;transform:translate(0px,0px) rotate(var(--r))}}
/* Kept up to date: each add-on's tag, a circling arrow (an update waiting), spins once
   and becomes a magenta tick as the add-on turns from grey to ink; then a copy of the
   site lifts out of the row and drops into a box, whose lid shuts: the backup. */
.demo .u-shelf{block-size:var(--dl);border-radius:999px;background:var(--rule)}
.demo .u-blk{background:var(--white);border:var(--dl) solid var(--ink);border-radius:calc(8 * var(--u));box-shadow:calc(4 * var(--u)) calc(4 * var(--u)) 0 var(--accent)}
.demo .u-blk svg{position:absolute;inset:calc(var(--dl) * -1)}
.u-blk path{stroke-width:calc(var(--sw) * 1.2)}
.u-old{stroke:var(--rule)}
.u-new{stroke:var(--ink)}
.u-tag > span{position:absolute;inset:0;border-radius:50%}
.u-wait{background:var(--white);box-shadow:inset 0 0 0 var(--dl) var(--ink);opacity:0}
.u-wait path{stroke:var(--ink);stroke-width:calc(var(--sw) * .75)}
.u-done{background:var(--accent)}
.u-done path{stroke:var(--white);stroke-width:var(--sw)}
.demo .u-copy{border:var(--dl) solid var(--ink);border-radius:calc(4 * var(--u));background:linear-gradient(var(--accent) 0 0) calc(5 * var(--u)) calc(6 * var(--u)) / calc(16 * var(--u)) calc(4 * var(--u)) no-repeat,linear-gradient(var(--ink) 0 0) calc(5 * var(--u)) calc(14 * var(--u)) / calc(14 * var(--u)) calc(3 * var(--u)) no-repeat,linear-gradient(var(--rule) 0 0) calc(5 * var(--u)) calc(21 * var(--u)) / calc(17 * var(--u)) calc(3 * var(--u)) no-repeat,var(--white)}
.demo .u-box{border:var(--dl) solid var(--ink);border-radius:0 0 calc(5 * var(--u)) calc(5 * var(--u));background:var(--white);box-shadow:calc(4 * var(--u)) calc(4 * var(--u)) 0 var(--accent)}
.demo .u-box::after{content:"";position:absolute;left:25%;right:25%;top:30%;block-size:calc(5 * var(--u));border-radius:999px;background:var(--accent)}
.demo .u-lid{border:var(--dl) solid var(--ink);border-radius:calc(3 * var(--u));background:var(--white);transform-origin:0 100%}
@keyframes u-spin{0%{transform:none;opacity:1}80%{transform:rotate(360deg);opacity:1}100%{transform:rotate(360deg);opacity:0}}
@keyframes u-copy{0%{opacity:0;transform:translate(calc(-185 * var(--u)),calc(-81 * var(--u))) scale(.5)}18%{opacity:1;transform:translate(calc(-185 * var(--u)),calc(-96 * var(--u)));animation-timing-function:var(--ease-in-out)}58%{transform:translate(0px,calc(-102 * var(--u)));animation-timing-function:cubic-bezier(.5,0,.85,.45)}100%{opacity:1;transform:none}}
@keyframes u-lid{0%,86%{transform:rotate(-32deg)}100%{transform:none}}
@media (prefers-reduced-motion: no-preference){
  /* Held at its start until its card arrives: the play state rides on each shorthand
     (var(--play)), inherited, because a shorthand resets animation-play-state and a
     separate paused rule loses to any later or more specific one (it did: every demo
     played out at load, off screen). */
  .demo.is-armed:not(.is-on){--play:paused}
  .demo.is-armed :is(.b-plate,.b-frame){animation:b-morph .95s var(--ease-in-out) .3s both var(--play)}
  .demo.is-armed .b-screen{animation:b-morph-in .95s var(--ease-in-out) .3s both var(--play)}
  .demo.is-armed :is(.b-tab,.b-dot){animation:d-from .95s var(--ease-in-out) .3s both var(--play)}
  .demo.is-armed .b-blk{animation:d-from .9s var(--ease-in-out) calc(.32s + var(--t, 0s)) both var(--play)}
  .demo.is-armed .b-wrap{animation-name:b-wrap}
  .demo.is-armed .b-dev{animation:b-press 1.75s linear both var(--play)}
  .demo.is-armed .b-home{animation:d-fade .4s var(--ease-out) 1.35s both var(--play)}
  .demo.is-armed .g-mag{animation:g-pass 2s var(--ease-in-out) .15s both var(--play)}
  .demo.is-armed .g-read{animation:d-from .3s var(--ease-out) var(--t) both var(--play)}
  .demo.is-armed .g-tick{animation:d-from .35s var(--ease-arrive) calc(var(--t) + .12s) both var(--play)}
  .demo.is-armed .g-tick path{animation:d-draw .3s var(--ease-out) calc(var(--t) + .3s) both var(--play)}
  .demo.is-armed .g-a{animation:g-a .5s var(--ease-in-out) 1.65s both var(--play)}
  .demo.is-armed .g-b{animation:g-b .5s var(--ease-in-out) 1.65s both var(--play)}
  .demo.is-armed .g-spark{animation:g-spark .35s var(--ease-arrive) 2.1s both var(--play)}
  .demo.is-armed .k-dart{animation:k-shot .95s linear var(--t) both var(--play)}
  .demo.is-armed .k-spark{animation:k-spark .5s var(--ease-out) calc(var(--t) - .02s) both var(--play)}
  .demo.is-armed .k-lock{animation:k-give 1.8s linear both var(--play)}
  .demo.is-armed .r-disc{animation:r-turn 3.2s linear both var(--play)}
  .demo.is-armed .r-off{animation:r-off 3.2s linear both var(--play)}
  .demo.is-armed .r-fix{animation:r-fix 3.2s var(--ease-out) both var(--play)}
  .demo.is-armed .u-wait{animation:u-spin .6s var(--ease-in-out) var(--t) both var(--play)}
  .demo.is-armed .u-done{animation:d-from .3s var(--ease-arrive) calc(var(--t) + .48s) both var(--play);--sx:0;--sy:0}
  .demo.is-armed .u-new{animation:d-fade .3s var(--ease-out) calc(var(--t) + .48s) both var(--play)}
  .demo.is-armed .u-copy{animation:u-copy 1.05s linear 1.5s both var(--play)}
  .demo.is-armed .u-lid{animation:u-lid 2.85s var(--ease-in-out) both var(--play)}
}
@media (prefers-reduced-motion: reduce){
  .js [data-standout] .stack__card .stack__img{transform:none;transition:none}
  .js [data-standout] .stack__card .clip{clip-path:none;transition:none}
}

/* ==== Our work: a fan of cards. The paper band, calm =============================== */
/* A card per client, held like a hand of playing cards (Stanley, 2026-10-20: "a fan of
   cards ... make this more fun"; recent-work.php): each the kit's window on its magenta
   plate, the client's live site under the tab line and the words beneath, or, until the
   client has agreed, the card's back. From 64em the cards stand in one row across the
   width, turned by their place in the hand (--fan, from the template: -1, 0 and 1 for
   three), the outer ones dropped a little, so the row reads as an arc; each overlaps the
   next, the first on top. No row scrolls and no card reaches past the screen's edge,
   so there is no sideways scroll bar anywhere (Stanley's screenshot showed the filmstrip's).
   Pointing at a card, keyboard focus in it, or a tap on a touch screen (.is-front,
   site.js) lifts it out of the hand, straight and on top, and lifts its window further
   off the plate as the buttons do; the others ease aside. Transform only, named, on the
   state ease with no overshoot: in over 450ms, back over 300ms (exits faster). The
   cards arrive as a list (P2), on the li, so the arrival never fights the card's own
   transform. Calm (page-design.md section 4): nothing moves until it is asked to.
   Under reduced motion the hand is laid flat, cards side by side with a gutter, and
   nothing moves; with no script it is complete as it stands. */
.recent-work{background:var(--paper)}
.recent-work > .work__body{inline-size:auto}
.work__body{display:grid;grid-template-columns:minmax(0,1fr);row-gap:var(--gap-head);margin-block-start:var(--gap-head)}
.work__list{
  --fan-tilt: 1.25deg;
  --fan-drop: 0rem;
  --fan-aside: 0rem;
  --fan-rise: .5rem;
  --fan-overlap: 0rem;
  --back-h: 30rem;
  list-style:none;margin:0;padding:0 var(--frame-inline);display:grid;grid-template-columns:minmax(0,1fr);gap:var(--space-4)
}
/* --f is the card's place held to -1..1, so a fourth or fifth card is turned no
   further than the third. Measured with three cards only until a fourth client is
   real; with scratch configs (review finding work-sound-3), four cards keep 15px from
   the screen's edge at 1024, lifted or not, and 16px or more above it; five never
   scroll the page sideways, but come to 9px at 1024, at about 190px a card, where the
   back's line breaks. A fifth client needs a layout of its own before it ships. */
.work__item{--f: clamp(-1, var(--fan, 0), 1); position:relative;z-index:calc(var(--n, 1) - var(--i, 0));display:grid;min-inline-size:0;pointer-events:none}
/* Where the pointer finds a card: its place in the hand at rest, which never moves
   (review finding work-look-1). The card itself moves when it lifts, so as its own hit
   area a pointer on the dropped, turned foot of an outer card lifted it out from under
   the pointer, dropped it back under it and lifted it again, over and over. This box
   is the card's at rest, turned and dropped the same way about the same point (the
   card is the item less the plate at its top and left), and the card's moving box
   adds to it, so once lifted a card stays lifted until the pointer leaves both, and
   back at rest it lies inside this box, away from the pointer. Invisible; it only
   takes the pointer. The item itself does not, as its plain box would claim a corner
   of the next card. */
.work__item::after{
  content:"";position:absolute;inset:0;z-index:-1;pointer-events:auto;
  transform:translateY(calc(var(--f) * var(--f) * var(--fan-drop))) rotate(calc(var(--f) * var(--fan-tilt)));
  transform-origin:calc(50% + var(--plate) / 2) 100%
}
.work__card{
  display:grid;pointer-events:auto;
  transform:
    translate(calc(var(--aside, 0) * var(--fan-aside)), calc(var(--f) * var(--f) * var(--fan-drop) * (1 - var(--up, 0)) - var(--up, 0) * var(--fan-rise)))
    rotate(calc(var(--f) * var(--fan-tilt) * (1 - var(--up, 0))))
    scale(calc(1 + var(--up, 0) * .025));
  transform-origin:50% 100%;
  transition:transform .3s var(--ease-out)
}
.work__card > .window{transition:transform .3s var(--ease-out)}
.recent-work .work__window{display:grid;grid-template-rows:auto 1fr;padding:calc(var(--tab) + var(--line)) 0 0;overflow:hidden;overflow:clip}
.recent-work .is-down .work__window{grid-template-rows:1fr}

/* The card lifted: pointed at, focused from the keyboard, or dealt forward by a tap.
   The selectors are written twice, as hover may only answer a mouse or trackpad.
   Keyboard focus only (:focus-visible), not any focus: a mouse click on "Visit their
   site" leaves focus on the link while their site opens in a new tab, and back on
   the page that card stayed lifted and showing its section wherever the pointer was,
   so pointing at another card stood two up at once (review finding work-look-4). */
.work__item:is(:has(:focus-visible),.is-front){z-index:calc(var(--n, 1) + 1)}
.work__item:is(:has(:focus-visible),.is-front) > .work__card{--up:1;transition-duration:.45s}
.work__item:is(:has(:focus-visible),.is-front) > .work__card > .window{transform:translate(calc(var(--plate) * -1.5),calc(var(--plate) * -1.5));transition-duration:.45s}
.work__item:has(~ .work__item.is-front, ~ .work__item :focus-visible) > .work__card{--aside:-1}
.work__item:is(:has(:focus-visible),.is-front) ~ .work__item > .work__card{--aside:1}
@media (hover:hover) and (pointer:fine){
  .work__item:hover{z-index:calc(var(--n, 1) + 1)}
  .work__item:hover > .work__card{--up:1;transition-duration:.45s}
  .work__item:hover > .work__card > .window{transform:translate(calc(var(--plate) * -1.5),calc(var(--plate) * -1.5));transition-duration:.45s}
  .work__item:has(~ .work__item:hover) > .work__card{--aside:-1}
  .work__item:hover ~ .work__item > .work__card{--aside:1}
}

/* Phones and tablets, below 64em: one column, at most 34rem wide and centred, each card
   turned a touch by its place. Not the hand: three across a tablet left the client's
   site a thumbnail, 253px wide at 768 where the filmstrip gave it about 600 (review
   finding work-look-6), and the client's site is what the section is for. Cards still
   face down, one after another, gather into a pile (with the script, so without it
   every card shows whole): each tucked under the one before, its foot showing, and a
   tap or focus deals it forward (site.js); under reduced motion they stay a column. A
   back's height is fixed here so the pile can tuck. The pointer cursor on touch
   screens is for iOS, which sends a tap's click to a plain box only when it looks
   clickable. */
@media (hover:none){
  .work__card{cursor:pointer}
}
@media (max-width: 63.99em){
  .work__list{--fan-tilt: 1deg; padding-inline:var(--frame-inline);grid-template-columns:minmax(0,34rem);justify-content:center}
  .work__card{transform-origin:50% 50%}
  .work__item::after{transform-origin:calc(50% + var(--plate) / 2) calc(50% + var(--plate) / 2)}
  .work__item.is-down .work__window{block-size:var(--back-h)}
}
@media (max-width: 63.99em) and (prefers-reduced-motion: no-preference){
  .js .work__item.is-down + .work__item.is-down{margin-block-start:calc(3.25rem - var(--back-h) - var(--space-4))}
  /* A card under the pile shows only its pattern until it is dealt forward: its words
     would sit loose in the strip that shows. Still there for a screen reader, and its
     link still takes focus, which deals it forward. */
  .js .work__item.is-down + .work__item.is-down:not(:has(:focus-visible),.is-front) .work__back > *{opacity:0}
  .js .work__item.is-down + .work__item.is-down .work__back > *{transition:opacity .15s var(--ease-out)}
  .js .work__item.is-down + .work__item.is-down:is(:has(:focus-visible),.is-front) .work__back > *{transition-duration:.3s}
}

/* The hand, from 64em: equal tracks for the cards, centred, at most 26rem each, the
   first and last held in from the frame (--fan-room) so their turned corners, eased
   aside, stay at least 16px inside the screen, the phone's gutter, with the three
   cards there are (measured from 1024 to 1920; four and five: --f, above). Each card
   reaches half the overlap into its neighbours. */
@media (min-width: 64em){
  .work__list{
    --fan-tilt: 5deg;
    --fan-drop: 1.25rem;
    --fan-aside: .75rem;
    --fan-rise: 1rem;
    --fan-overlap: 2rem;
    --fan-room: 3.5rem;
    grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:minmax(0,26rem);justify-content:center;gap:0;
    padding-inline:calc(var(--frame-inline) + var(--fan-room));padding-block-end:var(--fan-drop)
  }
  .work__item{margin-inline:calc(var(--fan-overlap) / -2)}
}
/* On a wide, tall enough screen the two cards take more room, both alike so they still
   read as a hand (audit, 2026-10-25: the client's screen was 368px wide at 1440). */
@media (min-width: 80em) and (min-height: 45.0625em){
  .work__list{grid-auto-columns:minmax(0,30rem)}
}
/* On a phone a lone face-down card is shorter: at 30rem the back of "Your site next?"
   stood as tall as the client's own card (audit, 2026-10-25). Only alone, so the pile's
   tuck, worked from --back-h, is untouched. */
@media (max-width: 63.99em){
  .work__list:not(:has(.is-down + .is-down)){--back-h:20rem}
}
/* Short screens, a laptop's 1366 by 657 and the like: the hand at rest and a lifted
   card both fit in the space under the fixed nav (review finding work-look-5; at
   1366 by 657 the hand needed 630px of the 586 there, so a lifted card slid under
   the nav). Narrower cards alone barely help, as the words wrap and the card grows
   back, so the cards also drop and rise less and set their words a little closer
   (with the words, below). */
@media (min-width: 64em) and (max-height: 45em){
  .work__list{--fan-drop: .5rem; --fan-rise: .5rem; grid-auto-columns:minmax(0,22.5rem)}
}

/* Reduced motion: the hand laid flat. No turn, no drop, no overlap, a gutter between
   the cards, nothing that moves on a hover, a focus or a tap, and no pile on phones.
   Nothing is turned, so the hand needs no room for turned corners and spans the
   frame: the room kept the flat cards needlessly narrow (review findings work-look-2
   and work-sound-2). */
@media (prefers-reduced-motion: reduce){
  .work__list{--fan-tilt: 0deg; --fan-drop: 0rem; --fan-aside: 0rem; --fan-rise: 0rem; --fan-overlap: 0rem; --fan-room: 0rem}
  .work__card,.work__card > .window{transition:none}
  .work__item:is(:has(:focus-visible),.is-front,:hover) > .work__card{--up:0}
  .work__item:is(:has(:focus-visible),.is-front,:hover) > .work__card > .window{transform:translate(calc(var(--plate) * -1),calc(var(--plate) * -1))}
}
@media (min-width: 64em) and (prefers-reduced-motion: reduce){
  .work__list{column-gap:var(--gutter)}
}

/* The face-up card's screen: straight under the tab line and edge to edge, as a browser
   shows a page. It keeps its shape whether it holds a capture or the gap marker: the
   phone's capture at 4:5 below 48em, the desktop's 16:10 from 48em, both from the top
   of the page. Every capture in it fills the same frame from the top, so the hero and
   the section over it line up exactly. */
.work__screen{position:relative;aspect-ratio:4/5;overflow:hidden;overflow:clip;background:var(--slot);border-block-end:var(--line) solid var(--ink)}
.work__img{position:absolute;inset:0;display:block;inline-size:100%;block-size:100%;object-fit:cover;object-position:50% 0}
.recent-work .work__screen .slot{position:absolute;inset:var(--space-2)}
@media (min-width: 48em){
  .work__screen{aspect-ratio:16/10}
}
/* A phone held sideways (landscape, 40em tall or less) takes the desktop's 16:10 and its
   capture (recent-work.php's <source>): the phone's 4:5 capture was 640 to 663px tall
   there, its card 961 to 987px on a 320 to 375px screen (device pass, 2026-10-27). */
@media (max-width: 47.99em) and (orientation: landscape) and (max-height: 40em){
  .work__screen{aspect-ratio:16/10}
}
/* A capture still arriving (site.js marks it .is-loading, so only with the script) is
   hidden on the window's pale ground until it is whole, never shown painted in strips
   (Sam's item 1, 2026-10-26); the hero still then comes in over 300ms where motion is
   allowed. The hover's section keeps its own fades (below). Four classes, past the
   hover's three (.work__item:is(:hover) .work__more), so a section still arriving
   stays hidden under the pointer. */
.recent-work .work__screen .work__img.is-loading{opacity:0}
@media (prefers-reduced-motion: no-preference){
  .recent-work .work__screen > picture > .work__img{transition:opacity .3s var(--ease-out)}
}

/* Further down (Stanley, 2026-10-19): the window shows the client's hero, and pointing
   at the card, or keyboard focus in it (its link), cross-fades to a section further
   down their page (recent-work.php). The section lies over the hero and only its
   opacity changes, so nothing moves or reflows: about 400ms in and 250ms back, exits
   faster than entrances (page-design.md, section 4), on the state ease. Calm (D-14):
   it answers a hover and never moves on its own. Only where a mouse or trackpad can
   hover: elsewhere (touch screens, and phones below 48em, which show their own
   capture) it stays display:none, so the lazy image is never fetched. Not a start
   state: the hero is what shows at rest, script or none (T-052). Under reduced motion
   it swaps at once. */
.work__more{display:none;opacity:0}
@media (min-width: 48em) and (hover:hover) and (pointer:fine){
  .work__more{display:block}
  .work__item:is(:hover,:has(:focus-visible)) .work__more{opacity:1}
}
@media (min-width: 48em) and (hover:hover) and (pointer:fine) and (prefers-reduced-motion: no-preference){
  .work__more{transition:opacity .25s var(--ease-out)}
  .work__item:is(:hover,:has(:focus-visible)) .work__more{transition-duration:.4s}
}

/* The clip (decision D-19, recent-work.php): the client's hero arriving, played once by
   site.js over the hero still, in the same frame and from the top (.work__img), under
   the section, so the hover still swaps over it. Shown only where site.js plays it:
   from 48em, under a mouse or trackpad, with motion allowed, and with the script
   (T-052: no script, no clip, the still as it always was). Elsewhere display:none, so
   nothing of it is fetched. Transparent until it is playing (.is-on), so the still
   shows while the first frames arrive and whenever site.js sets it back: in over
   300ms, out over 150ms, exits faster than entrances. Opacity only. */
.work__clip{display:none}
@media (min-width: 48em) and (hover:hover) and (pointer:fine) and (prefers-reduced-motion: no-preference){
  .js .work__clip{display:block;opacity:0;transition:opacity .15s var(--ease-out)}
  .js .work__clip.is-on{opacity:1;transition-duration:.3s}
}

/* The face-up card's words: the client's name in the display face, its line, what we
   did for them once that is written (P1-28), when it went live, and the way to their
   site, which opens in a new tab (target, recent-work.php). The arrow is decoration
   (aria-hidden) and leans out toward the site under a mouse. */
.work__about{display:grid;gap:var(--space-1);align-content:start;justify-items:start;padding:var(--space-3) clamp(1rem, .6rem + 1vw, 1.75rem) var(--space-2)}
.recent-work .work__name{font-size:clamp(1.5rem, 1rem + 1.1vw, 2.25rem)}
/* A client with a case study (recent-work.php): the name is a text link there, its
   underline drawing in under a mouse as every text link's does, and the screen above it
   is the same link for the pointer, as a block so the window's rows stay as they were.
   Its focus ring is the name's: the screen's copy never takes focus. The name's box is
   at least 44px tall for a thumb (review finding front-name-link-tap-target), padded
   out round its one line, the matching negative margin keeping that line, and so the
   card, where they were (the hand fits under the nav, below). The underline still
   sits at the line's foot: .text-link draws it from the middle of the box. */
.work__shot{display:block;color:inherit;text-decoration:none;-webkit-tap-highlight-color:transparent}
.recent-work .work__case{color:inherit;padding-block:max(0px, (44px - 1lh) / 2);margin-block:min(0px, (1lh - 44px) / 2)}
/* pretty: no word left alone on the line's last line in a narrow card ("platters.",
   review finding work-look-3). */
.work__text{max-inline-size:38ch;font-size:var(--fs-body);font-weight:450;line-height:1.45;color:var(--ink-soft);text-wrap:pretty}
/* Our part in ink, after the client's line in the softer ink: theirs, then ours. */
.work__did{max-inline-size:38ch;font-size:var(--fs-body);font-weight:600;line-height:1.45;color:var(--ink);text-wrap:pretty}
.work__live{font-weight:700;color:var(--ink)}
.work__visit{font-weight:700}
/* A link's words stay on one line: broken, the underline, drawn at the middle line's
   foot, ran between the lines like a strike-through (review findings work-look-2 and
   work-sound-2). */
.work__visit .text-link{display:inline-flex;align-items:center;gap:.4em;min-block-size:44px;white-space:nowrap}
/* Below 64em, one card to a column, the link fits one line at 100% text, so it may
   wrap there: kept to one line at 200% it was wider than the card's words (261px for
   254 at 390), widened the card's column and ran the card 53px off a 320 screen
   (device pass, 2026-10-27). Wrapped, its resting line sits under the last line, not
   between the two; for one line in its 44px box the offset is the same as before. */
@media (max-width: 63.99em){
  .work__visit .text-link{white-space:normal;text-wrap:balance}
  .work__visit .text-link::before,.work__visit .text-link::after{bottom:calc(max(0px, (44px - 1lh) / 2) + .02em)}
}
.work__arrow{flex:none;inline-size:.9em;block-size:.9em;transition:transform var(--t-hover) var(--ease-out)}
@media (hover:hover){
  .work__visit .text-link:hover .work__arrow{transform:translate(2px,-2px)}
}
/* Closer on short screens, so the hand fits under the nav (the hand, above). */
@media (min-width: 64em) and (max-height: 45em){
  .work__about{padding-block-start:var(--space-2);gap:.25rem}
  .recent-work .work__name{font-size:1.75rem}
}

/* The card's back, while the client has not agreed (recent-work.php): the kit's ink,
   the tab line and dot in paper as the reversed symbol draws them, a pattern of the
   symbol's window in the muted magenta (symbol-tile.svg) inside a ruled inner border,
   as a playing card's back is, and the symbol itself at its middle on its own ink, with
   the line and the way to the contact page, and nothing else: no gap marker (P1-16).
   Paper on ink 15.98:1, and on the pattern's strokes over 6:1; the focus ring, magenta
   on ink, 4.02:1. */
.work__item.is-down .work__window{background:var(--ink);color:var(--text-primary)}
.work__item.is-down .work__window::before{border-block-end-color:var(--text-primary)}
.work__item.is-down .work__window::after{background:var(--text-primary)}
/* The back is a size container so its line is sized by the card, not the screen: by
   the screen it broke as "Your site / next?" in the flat hand's 294px cards at 1024
   (review finding work-look-3). 9.5% of the back's width keeps it on one line in any
   card, plaque and all, from 18px to the 34px it was already capped at.
   The pattern lies only inside the ruled border, in whole tiles (round fits a whole
   number of them to the box), and the symbol and the words sit together on one ink
   plaque: the pattern ran on under the border and was cut into half tiles at the
   card's edges, and each line's own ink patch cut ragged holes in it (review finding
   work-look-7). The plaque is a band across the card from side to side of the ruled
   border, ruled top and bottom the same way, so the pattern shows whole above and
   below it; in the column a back is 30rem tall (--back-h), near a playing card's
   shape, so whole rows of it show there too. The border and pattern are one box
   behind the plaque, kept inside the back by isolation. */
.work__back{container-type:inline-size;position:relative;isolation:isolate;display:grid;align-content:center;padding:var(--space-3) 1.25rem;text-align:center}
.work__back::before{content:"";position:absolute;inset:.75rem;z-index:-1;padding:.375rem;border:var(--line) solid var(--accent-muted);border-radius:var(--radius-sm);background:url("../logo/symbol-tile.svg") 0 0 / 3.5rem auto round content-box;pointer-events:none}
.work__plaque{display:grid;justify-items:center;gap:var(--space-1);margin-inline:calc(.75rem + var(--line) - 1.25rem);padding:var(--space-2) .75rem;background:var(--ink);border-block:var(--line) solid var(--accent-muted)}
.work__seal{display:block;inline-size:clamp(4rem, 30cqi, 7.5rem);block-size:auto;margin-block-end:var(--space-1)}
.recent-work .work__next{font-family:var(--font-display);font-size:clamp(1.125rem, 9.5cqi, 2.125rem);line-height:1.05;letter-spacing:-.02em;color:var(--text-primary);text-wrap:balance}
.work__go{font-weight:700}
.work__go .text-link{display:inline-flex;align-items:center;min-block-size:44px;white-space:nowrap}
/* Below 64em it may wrap, as "Visit their site" may (above, which says how its line
   keeps under its last line): on one line, at 200% text on a 320 phone, "Get in touch"
   widened the back's plaque, so the back's words sat off centre and ran to the
   window's edge (device pass, 2026-10-27). */
@media (max-width: 63.99em){
  .work__go .text-link{white-space:normal;text-wrap:balance}
  .work__go .text-link::before,.work__go .text-link::after{bottom:calc(max(0px, (44px - 1lh) / 2) + .02em)}
}

/* The reviews merged in (reviews.php): under the panels, in the reading width. */
.work__body > .reviews{--i:1;box-sizing:border-box;inline-size:min(var(--container), 46rem);margin-inline:var(--frame-inline) auto;display:grid;gap:var(--space-3);align-content:start;padding-block-start:var(--space-3);border-block-start:var(--line) solid var(--ink)}
.recent-work .reviews h3{font-size:var(--fs-step)}
.recent-work .review{display:grid;gap:var(--space-2)}
.recent-work .review blockquote{margin:0}
.recent-work .review blockquote p{max-inline-size:28ch;font-size:var(--fs-quote);font-weight:500;line-height:1.3;letter-spacing:-.01em;color:var(--ink);text-wrap:balance}
.recent-work .review cite{font-style:normal;font-weight:700;color:var(--ink-soft)}

/* ==== The growth check: check/five-questions, a deck of cards. THE ink band, the moment */
/* Decision D-21 (Stanley, 2026-10-20: "more purple accents", "more of a full screen",
   "a more fun way", "a lot of dead space"), a scorecard since D-59 (Sam, 2026-10-25,
   item 11), six taps, a preview and an email card since D-71 (Sam, 2026-10-27). Phone:
   the heading, the intro, the strip of six small boxes, then the deck. From 64em the
   band fills at least the screen below
   the nav's pill (the pill's own clearance, as html's scroll-padding: so an anchor jump
   to #check shows exactly the band), wider than the page's 82rem frame so nothing is
   dead space at 1440 or 1920, and the deck is sized by its own width (cqi), so it grows
   with the screen up to 2560. The heading, the intro and the pile are on the left, the
   deck on the right; the scorecard sits at the foot of its column, level with the deck's
   foot. Magenta: the heading's key word (4.02:1 on ink, display size only), the cards'
   huge numbers and the score (3.98:1 on paper, 40px and up), the small card's number
   (24px and up; the phone's is ink), the preview's step numbers (22px Archivo Black,
   large text), the plates under the cards and tiles, and a chosen tile's or a ticked
   box's fill (near-black on it, 4.53:1). Never body text on ink. */
.self-check{--check-clear:calc(env(safe-area-inset-top, 0px) + .625rem + var(--pill-h) + 1rem);padding-block:var(--pad-m);background:var(--ink);color:var(--text-primary)}
.check__layout{display:grid;grid-template-columns:minmax(0,1fr);grid-template-areas:"title" "intro" "pile" "deck";row-gap:var(--gap-text)}
.self-check .check__title{grid-area:title;font-size:clamp(2.75rem, 1rem + 5vw, 9rem)}
/* Never past 16vw (device pass, 2026-10-27): at 200% text the heading's 72px floor ran
   "business" (4.6em) 5px past the frame at 390 and 43px off a 320 screen. At 100%
   text --fs-h2 is under 16vw at every width from 320. */
.home .self-check .check__title{font-size:min(var(--fs-h2), 16vw)}
.check__mark{color:var(--accent)}
.check__intro{grid-area:intro;max-inline-size:40ch;font-size:clamp(1.1875rem, .95rem + .75vw, 2rem);font-weight:450;line-height:1.45;color:var(--text-secondary)}
/* The deck's intro promises a score and recommendations, which only the deck gives: the
   static list without JavaScript has its own (T-052; self-check.php). */
html:not(.js) .check__intro--deck,.js .check__intro--static{display:none}
.check__pile{grid-area:pile}
/* --deal: how tall a playing-card tile may be (below). */
.check__deck{--deal:min(36cqi, 34svh);grid-area:deck;container-type:inline-size;min-inline-size:0;margin-block-start:var(--space-1)}
/* --vu, from 64em: the band's left column (heading, intro, pile) and its padding grow with
   the screen's width as if it were no wider than 16:9, so a wide window that is short
   for its width (1920x880, 1920x960: the browser's bars) still fits the band under the
   pill with chip 05 on the screen (review finding deck-3; at 1920x880 the pile ran
   105px below the fold). */
@media (min-width: 64em){
  .self-check{--vu:min(1vw, 1.78svh);--check-pad:clamp(2.5rem, 1rem + 3 * var(--vu), 5rem);box-sizing:border-box;min-block-size:calc(100svh - var(--check-clear));display:grid;align-content:center;padding-block:var(--check-pad)}
  /* The band's words start on the page's frame, as every heading does; only the deck runs
     on to the bare margin on the right (audit, 2026-10-25: the heading sat 24px left of
     the frame at 1440 and 264px at 1920). */
  .self-check > *{inline-size:auto;margin-inline:var(--frame-inline) var(--margin)}
  .self-check .check__title{font-size:clamp(2.75rem, 1rem + 5 * var(--vu), 6rem)}
  .home .self-check .check__title{font-size:max(2.75rem, min(var(--fs-h2), 1rem + 5 * var(--vu)))}
  .check__intro{font-size:clamp(1.1875rem, .95rem + .75 * var(--vu), 2rem)}
  .check__layout{grid-template-columns:minmax(0,5fr) minmax(0,7fr);grid-template-rows:auto auto 1fr;grid-template-areas:"title deck" "intro deck" "pile deck";column-gap:clamp(2.5rem, .5rem + 4vw, 7rem);align-items:start}
  .check__deck{align-self:center;margin:0}
  .check__pile{align-self:end}
}
/* Upright from 64em (a 12.9in iPad, 1024x1366), the screen is far taller than the band's
   two columns, sized by their width: a full screen there was 1278px of ink round 560px of
   heading and deck, 359px bare above them and below. Upright the band is as tall as its
   words and deck with the page's own section padding, as its neighbours are (device
   pass, 2026-10-27). */
@media (min-width: 64em) and (orientation: portrait){
  .self-check{min-block-size:0;padding-block:var(--pad-m)}
}
/* An upright phone (D-84, Sam on his iPhone, 2026-10-28: "should be able to see
   everything in one view"): the heading, the intro, the strip and the whole card showing
   share one screen of Safari's with its bars (390x664, 360x640, 430x739; the deck's own
   rules are with the deck's, below). The band's top is the pill's own clearance, so with
   the band's top at the screen's top the heading starts 16px under the pill, where it ran
   3px under the pill's plate; an anchor jump to #check (the contact page's link) lands the
   band's top there too, not 88px lower with the card's foot off the screen. The rows
   closer. */
@media (max-width: 32.49em){
  .self-check{padding-block-start:var(--check-clear);scroll-margin-block-start:calc(var(--check-clear) * -1)}
  .check__layout{row-gap:.625rem}
  .check__deck{margin-block-start:.25rem}
}
/* At the reader's usual text size (large text keeps its own sizes): the heading two lines
   from 320, where at --fs-h2 it took three at 360 and 375, a line of the card's room
   ("business to grow?" is 9.45em wide); the intro two lines, the phone's width, not 40ch. */
@media (min-width: 19.6em) and (max-width: 32.49em){
  .home .self-check .check__title{font-size:max(1.875rem, min(var(--fs-h2), (100vw - 2 * var(--margin)) / 9.5))}
  .check__intro{max-inline-size:none;font-size:clamp(1rem, 4.6vw, 1.0625rem);line-height:1.4}
}
/* The deck's entrance (calm: once, as the windows' P3 rise; core.js adds .in). */
.js .check__deck[data-reveal]:not(.in){opacity:0;transform:translateY(var(--rise-y))}
.js .check__deck[data-reveal]{transition:transform var(--t-enter) var(--ease-arrive),opacity .5s var(--ease-arrive)}

/* Without JavaScript: the questions to read, each with what a new site would do with a
   yes, the last one with its answers in words, and the link to the contact page, in one
   paper window. */
.self-check .window{background:var(--paper)}
.check__fallback{box-shadow:var(--plate) var(--plate) 0 var(--accent)}
.check__fallback h3{font-size:var(--fs-step);margin-block-end:var(--space-3)}
.check__list{display:grid;gap:var(--space-3);grid-template-columns:repeat(auto-fit, minmax(min(100%, 18rem), 1fr))}
.check__group{display:grid;align-content:start;gap:var(--space-2);margin:0;padding:var(--space-2) 0 0;list-style:none;border-block-start:2px solid var(--ink)}
.check__fallback .check__gname{margin:0;font-size:var(--fs-step)}
.check__qs{display:grid;gap:var(--space-1);margin:0;padding-inline-start:1.25em}
.check__qs .check__q{margin:0}
/* Under each question, what a new site would do with a yes (item 5): the result card's
   recommendation, read here without the script. */
.check__then{margin:.15rem 0 0;font-size:var(--fs-body);line-height:1.45;color:var(--ink-soft)}
.check__qs > li::marker{font-weight:700;color:var(--ink-soft)}
.check__group--more > li{display:grid;gap:var(--space-1)}
.check__q{font-weight:700;font-size:var(--fs-lead);line-height:1.35}
.check__options{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:.25rem 1.25rem}
/* Plain words with a small square, never outlined like a button: nothing here is pressed. */
.check__options li{position:relative;padding-inline-start:1rem;line-height:1.5}
.check__options li::before{content:"";position:absolute;left:0;top:.6em;inline-size:.4rem;block-size:.4rem;background:var(--accent)}
.check__fallback .check__go{margin-block-start:var(--space-3);font-weight:700}
.check__fallback .check__go--ring{margin-block-start:0}
.check__go .text-link{display:inline-flex;align-items:center;min-block-size:44px}

/* ---- The deck (site.js builds it under .js). Every card sits in one grid cell. From
   64em the cell is the tallest question card's height and every card is that size, like
   a real deck, so nothing below jumps as the cards change (under 64em, below). Only the
   current card (and one leaving) is visible; the rest are hidden from sight, the
   keyboard and screen readers. Behind the current card, the cards still to come peek
   out of the deck, one to three of them. */
.check__stack{--deck-step:clamp(.5rem, 1.6cqi, 1.125rem);--card-plate:clamp(8px, 1.1cqi, 14px);position:relative;display:grid;padding:0 calc(var(--deck-step) * 3 + var(--card-plate)) calc(var(--deck-step) * 3 + var(--card-plate)) 0}
.check__card,.check__under{grid-area:1 / 1;min-inline-size:0}
.check__card{position:relative;z-index:1;display:flex;visibility:hidden}
.check__card.is-current{z-index:2;visibility:visible}
.check__card.is-leaving{z-index:3;visibility:visible;pointer-events:none}
/* From 64em the result card is in the deck's cell from the start, so its arrival changes
   nothing's size: while it was left out until it showed, a taller result grew the band
   past the screen at 1366x657 and 1024x768, the pile jumped down under the flying card
   and chip 05 landed below the fold (review finding deck-3). It takes no part in the
   cell's height (size containment: the question cards set it), so however long the
   answers, it is always the question cards' size; in the rare case its report does not
   fit even a size or two down (site.js fitResult), it grows to its whole height
   (.is-whole) rather than scroll inside the card with nothing to show there is more,
   only while it shows, so the question cards keep their size after it. The send card
   and the thanks (D-59) the same way.
   Under 64em, where the deck is one column with the page, each card fills the room kept
   for the tallest question card, its tiles at its foot rather than stretched, and the
   card leaving is lifted out of the flow so the next one takes its place at once. */
@media (min-width: 64em){
  .check__card:is(.is-result,.is-send,.is-sent){contain:size}
  .check__card.is-result.is-whole:is(.is-current,.is-leaving){contain:none}
  :is(.is-result,.is-send,.is-sent) .check__ask,.is-result .check__report{min-block-size:0}
  .is-result .check__report{overflow-y:auto;overscroll-behavior:contain}
  /* The send card's boxes scroll inside it when a failure or an error makes them longer
     than the card, with room kept round them for their focus plates. */
  .check__formbody{flex:1 1 auto;overflow-y:auto;overscroll-behavior:contain;margin:-.375rem -.5rem 0 -.375rem;padding:.375rem .5rem .5rem .375rem}
}
@media (max-width: 63.99em){
  /* A card leaving (the result on Start again, taller than the room kept for the
     questions: site.js keepRoom) never paints over the section after the band. */
  .self-check{overflow:clip}
  .check__card:not(.is-current):not(.is-leaving){display:none}
  .check__card.is-leaving{position:absolute;inset:0 calc(var(--deck-step) * 3 + var(--card-plate)) auto 0}
}
/* Every card the size of the room kept for the tallest (site.js keepRoom), as on a
   desktop, its answers straight under its question and the spare paper at its foot
   (audit, 2026-10-25: the first card was 377px tall in 679px of room at 390, a third of a
   screen of bare ink under it, which read as the band's end). Only on a screen tall
   enough for the full card (320x640 and 360x740 keep their own heights); the tiles keep
   their own height (deck-2). */
@media (max-width: 63.99em) and (min-height: 48em){
  .check__deck{display:grid}
  .check__deck .check__tiles{align-content:start}
}
.check__under{position:relative;z-index:0}
.check__under > span{position:absolute;inset:0;display:none;box-sizing:border-box;background:var(--paper);border:var(--line-heavy) solid var(--ink);border-radius:var(--radius-win);box-shadow:var(--card-plate) var(--card-plate) 0 var(--accent);transform:translate(calc(var(--n) * var(--deck-step)),calc(var(--n) * var(--deck-step))) rotate(calc(var(--n) * 1.1deg))}
.check__under > span:nth-child(1){--n:1}
.check__under > span:nth-child(2){--n:2}
.check__under > span:nth-child(3){--n:3}
.check__under[data-left="1"] > span:nth-child(-n+1),.check__under[data-left="2"] > span:nth-child(-n+2),.check__under[data-left="3"] > span:nth-child(-n+3){display:block}
.check__under > span:nth-child(2){z-index:-1}
.check__under > span:nth-child(3){z-index:-2}

/* A card: the kit window (paper on the ink, the 4px outline, the dot and the tab line)
   on its solid magenta offset shadow, which travels with it when it flies. */
.check__face{flex:1;display:flex;flex-direction:column;gap:clamp(1rem, 2.6cqi, 2rem);min-inline-size:0;box-shadow:var(--card-plate) var(--card-plate) 0 var(--accent)}
.check__num{display:flex;align-items:baseline;gap:.3em;margin:0;font-family:var(--font-display);line-height:.8;letter-spacing:-.03em;font-variant-numeric:tabular-nums}
.check__face{--n-size:clamp(2.75rem, 8cqi, 6.5rem)}
.check__n{font-size:var(--n-size);color:var(--accent)}
.check__of{font-size:clamp(1.25rem, 3.4cqi, 2.75rem);color:var(--ink-soft)}
/* A check mark (the preview at no yes) or the email card's envelope is exactly the number
   row's height (.8 of the number), so the heading starts where every question's does. */
.check__done{display:block;inline-size:auto;block-size:calc(var(--n-size) * .8);color:var(--accent)}
.check__ask{flex:1 1 auto;display:flex;flex-direction:column;gap:clamp(.75rem, 1.8cqi, 1.375rem)}
.self-check .check__question{font-size:clamp(1.625rem, 4.4cqi, 4.25rem);line-height:1.06;outline:none}
.check__hint{margin-block-start:-.25rem;font-size:var(--fs-body);font-weight:500;color:var(--ink-soft)}

/* The answers: chunky kit tiles, real buttons. Each face stands on its magenta plate
   (the hard shadow), lifts a little more under a mouse, and presses down when tapped.
   Chosen (aria-pressed): pressed in, filled magenta and ticked, never colour alone. A
   tile that moves on at the tap carries an arrow; a tile among many to pick carries a
   box that takes the tick. */
/* From 64em every card is the tallest card's height. A tile is its own height, never
   stretched to fill a card (1fr rows made the one-answer cards' tiles tall white slabs
   with a small word in a corner, and every card's tiles a different size: review finding
   deck-2). Where a card has room, its tiles grow to --tile-max at most and stand in the
   middle of it; never less than their words (min-content: with a fixed least, or auto,
   which a fixed most limits, "More enquiries" ran out of its tile at 1920x880). The
   face keeps --tile-min itself. */
.check__tiles{--tile-min:clamp(3.5rem, 8.5cqi, 6.25rem);--tile-max:auto;flex:1 1 auto;list-style:none;margin:clamp(.25rem, 1cqi, .75rem) 0 0;padding:0;display:grid;grid-template-columns:minmax(0,1fr);grid-auto-rows:minmax(min-content, var(--tile-max));align-content:center;gap:clamp(.875rem, 2.2cqi, 1.625rem)}
.check__tiles > li{display:flex;min-inline-size:0}
.check__tiles > li[hidden]{display:none}
.check__tile{--lift:6px;position:relative;flex:1;display:flex;min-inline-size:0;margin:var(--lift) 0 0 var(--lift);padding:0;font:inherit;font-weight:700;color:var(--ink);text-align:start;background:var(--accent);border:0;border-radius:var(--radius-sm);cursor:pointer;-webkit-tap-highlight-color:transparent}
/* The words from the face's top, the mark in its top corner. The words make room for the
   mark on their first line only (a float the mark's size), so the rest of the line is
   theirs: a mark kept above them made each tile 2em taller, and one kept beside them
   left the words too narrow. */
.check__tile-face{position:relative;flex:1;display:flex;align-items:flex-start;box-sizing:border-box;min-inline-size:0;min-block-size:calc(var(--tile-min) - var(--lift));padding:.7em .9em .7em 1.05em;font-size:clamp(1.125rem, 2.6cqi, 2.125rem);line-height:1.15;background:var(--white);border:var(--line) solid var(--ink);border-radius:inherit;transform:translate(calc(var(--lift) * -1),calc(var(--lift) * -1));transition:transform var(--t-state) var(--ease-out)}
/* No word left alone on a tile's last line (pretty: balancing the lines round the mark's
   room on the first left "A" alone above "new website"). A word is broken only if it is
   wider than the whole tile, which no answer is at any width (review finding deck-1:
   "Somethin / g else" came from the padding below losing to the rule above, not from
   the width). */
.check__tile-word{min-inline-size:0;overflow-wrap:break-word;text-wrap:pretty}
.check__tile-word::before{content:"";float:right;inline-size:1.35em;block-size:1em}
.check__tile-mark{position:absolute;top:.75em;right:.75em;inline-size:1.15em;block-size:1.15em;fill:none;stroke:currentColor;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round}
.check__tile-mark .check__tick{display:none}
.check__tile[aria-pressed="true"] .check__tile-face{color:var(--on-accent);background:var(--accent);transform:translate(-2px,-2px)}
.check__tile[aria-pressed="true"] .check__tick{display:inline}
.check__tile[aria-pressed="true"] .check__arrow{display:none}
@media (hover:hover){
  .check__tile:hover .check__tile-face{transform:translate(calc(var(--lift) * -1 - 2px),calc(var(--lift) * -1 - 2px))}
  .check__tile[aria-pressed="true"]:hover .check__tile-face{transform:translate(-3px,-3px)}
}
.check__tile:active .check__tile-face,.check__tile[aria-pressed="true"]:active .check__tile-face{transform:translate(-1px,-1px);transition-duration:var(--t-feedback)}
.check__tile:focus-visible{outline:none}
/* The ring clears the tile's magenta plate, and is ink on the card's paper, where a
   magenta ring met the plate and, on a chosen tile, its magenta face (audit, 2026-10-25). */
.check__tile:focus-visible .check__tile-face{outline:3px solid var(--ink);outline-offset:calc(var(--lift) + 2px)}
/* Columns by the deck's own width: one on a phone, two on a narrow deck, three from 34rem
   (three answers in a row, five as three and two). Five in a row was tried at 2560: the
   words broke inside themselves. These come after the face's own rule, which has the
   same weight: written above it, a rule here never applies (review finding deck-1). */
@container (min-width: 26rem){
  .check__tiles{grid-template-columns:repeat(2, minmax(0,1fr))}
}
@container (min-width: 34rem){
  .check__tiles{grid-template-columns:repeat(3, minmax(0,1fr))}
}
/* A card that moves on at the tap (one answer: 1, 4 and 5) deals its answers as playing
   cards, three across from 30rem: the answer big in the display face at the card's foot,
   the arrow in its top corner, and about as tall as a playing card for its width
   (--tile-max) where the card has the room, never taller (review finding deck-2). As
   the go cards are the tallest, their tiles set the deck's height (--deal), so it is
   bounded by the screen's height too: on a short screen (from 1366x657) 34svh, where
   the rest of the card is sized by the height (below); on a taller one, the screen less
   the pill, the band's padding and the rest of the card, which grows with the deck's
   width (about 33cqi + 9rem, measured at 1440, 1600, 1680 and 1920), so a wide screen
   in a browser's window (1920x970, 1680x950, 1600x900) fits under the pill too. */
@media (min-width: 64em) and (min-height: 52em){
  .check__deck{--deal:min(36cqi, 100svh - var(--check-clear) - 2 * var(--check-pad) - 33cqi - 9rem);--win-pad:min(clamp(1.25rem, .8rem + 2vw, 3rem), 4svh)}
  /* The rest of the card is bounded by the height too, for the same wide, short windows
     (1920x880: the cards to pick were 743px for 672 of room, at their least). */
  .check__face{--n-size:clamp(2.75rem, min(8cqi, 10svh), 6.5rem)}
  .self-check .check__question{font-size:clamp(1.625rem, min(4.4cqi, 4.6svh), 4.25rem)}
  .check__tiles{--tile-min:clamp(3.5rem, min(8.5cqi, 8svh), 6.25rem)}
}
@container (min-width: 30rem){
  .check__tiles--go{--tile-max:clamp(var(--tile-min), var(--deal), 26rem);grid-template-columns:repeat(3, minmax(0,1fr))}
  /* The size follows the tile's width (about 25cqi less its 1.7rem of padding), so the
     longest word ("months", 4em) and a pair ("next few", 4.7em) always fit a line: a
     floor in rem broke "lookin / g" at 1024 (review finding deck-1). */
  .check__tiles--go .check__tile-face{align-items:flex-end;padding:calc(.9rem + clamp(1.25rem, 3cqi, 2rem)) .8rem .65em .9rem;font-family:var(--font-display);font-size:clamp(1.125rem, 5.2cqi - .4rem, 3.25rem);font-weight:400;line-height:1.02;letter-spacing:-.01em}
  .check__tiles--go .check__tile-word{text-wrap:balance}
  .check__tiles--go .check__tile-word::before{display:none}
  .check__tiles--go .check__tile-mark{top:.9rem;right:.9rem;inline-size:clamp(1.25rem, 3cqi, 2rem);block-size:clamp(1.25rem, 3cqi, 2rem)}
}
/* Yes and Not yet (the scored questions, D-59): two playing cards side by side at every
   width, a big target for a thumb, the word bigger as each card is wider than one of
   three. After the rules above, which they override. */
.check__tiles--two{--tile-max:clamp(var(--tile-min), var(--deal), 26rem);grid-template-columns:repeat(2, minmax(0,1fr))}
.check__tiles--two .check__tile-face{align-items:flex-end;padding:calc(.9rem + clamp(1.25rem, 3cqi, 2rem)) .8rem .65em .9rem;font-family:var(--font-display);font-size:clamp(1.5rem, 7cqi - .4rem, 4rem);font-weight:400;line-height:1.02;letter-spacing:-.01em}
.check__tiles--two .check__tile-word{text-wrap:balance}
.check__tiles--two .check__tile-word::before{display:none}
.check__tiles--two .check__tile-mark{top:.9rem;right:.9rem;inline-size:clamp(1.25rem, 3cqi, 2rem);block-size:clamp(1.25rem, 3cqi, 2rem)}

/* The last card's five answers (D-71): three across from 30rem in two rows, which share
   the height one row of Yes and Not yet takes, so that card is no taller than the others
   and the deck, the strip beside it and the preview's button all fit the screen from
   1366x657 (dealt at full height, the two rows made the card 1,060px at 1440x900 and put
   the preview's button below the fold). The words a size down, so "Looking after the
   site" fits three lines in a tile; never less than their words (min-content, deck-1). */
.check__tiles--many{--tile-max:clamp(var(--tile-min), (var(--deal) - clamp(.875rem, 2.2cqi, 1.625rem)) / 2, 13rem)}
@container (min-width: 30rem){
  .check__tiles--many .check__tile-face{padding-block-start:calc(1.25rem + clamp(1.25rem, 3cqi, 2rem));font-size:clamp(1rem, 3.2cqi - .15rem, 2rem);line-height:1.05}
}

/* The card's foot: Back on the left (a quiet text button); on the email card, the button
   on the right, the kit button with its face in ink. */
.check__foot{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--space-1) var(--space-2);margin-block-start:auto;padding-block-start:var(--space-1)}
.check__foot:empty{display:none}
.check__foot .btn,.check__actions .btn{font-size:clamp(1.0625rem, 2.1cqi, 1.5rem)}
.check__next{margin-inline-start:auto}
.self-check :is(.check__next,.check__send) .btn__face{color:var(--paper);background:var(--ink);border-color:var(--ink)}
/* At least 44px each way: "Back" alone is 41px wide on a phone (review findings deck-6,
   deck-sound-1). The words stay at the left, in line with the card's edge. */
.check__quiet{position:relative;display:inline-flex;align-items:center;justify-content:flex-start;min-block-size:46px;min-inline-size:44px;padding:0;font-weight:700;font-size:var(--fs-body);color:var(--ink);background:none;border:0;cursor:pointer}
.check__quiet > span{position:relative;background:linear-gradient(currentColor,currentColor) 0 100% / 100% 1px no-repeat}
.check__quiet > span::after{content:"";position:absolute;left:0;right:0;bottom:-1px;block-size:2px;background:var(--accent);transform:scaleX(0);transform-origin:100% 50%;transition:transform .2s var(--ease-arrive)}
@media (hover:hover){
  .check__quiet:hover > span::after{transform:scaleX(1);transform-origin:0 50%;transition-duration:var(--t-hover)}
}
.check__deck .check__quiet:focus-visible{outline:3px solid var(--focus);outline-offset:3px}
.check__deck .btn:focus-visible{outline:none}
/* "Or ring us" (P1-17): a tel: link beside "Send this to us", in the quiet buttons' look
   so it reads as the second way, never a second pill. */
.check__ring{text-decoration:none}

/* The preview (D-71; the result card of D-59): the score where a question's number is
   (a tick in its place at no yes: Sam, no "0 / 10"), their line as the heading, then
   their strongest point and where we'd start: the first place in words and the rest as
   locked lines, with how many more are in the full report; then the note and "Get my
   full report", Start again and Back. From 64em, where every card is the question cards'
   size (review finding deck-3), the strongest point sits beside where we'd start; below
   it they stack, so on a tablet the preview fills the room kept for the tallest question
   rather than leaving a third of the card bare. Ink for every word; magenta only for the
   score, the tick, the step numbers. */
.check__top{display:flex;align-items:center;gap:clamp(.875rem, 2.8cqi, 2rem)}
.check__top > .check__num{flex:none}
.check__top > .check__question{flex:1 1 auto;min-inline-size:0;text-wrap:balance}
/* The score a size above the question cards' numbers: the card is about it. */
.check__score .check__n{font-size:calc(var(--n-size) * 1.3)}
.check__score .check__of{white-space:nowrap}
.check__report{flex:1 1 auto;display:grid;grid-template-columns:minmax(0,1fr);align-content:start;gap:clamp(1rem, 2.6cqi, 1.75rem) clamp(1.25rem, 4cqi, 3rem);margin-block-start:clamp(.125rem, .6cqi, .5rem)}
@media (min-width: 64em){
  @container (min-width: 34rem){
    .check__report{grid-template-columns:minmax(0,1fr) minmax(0,1.6fr);grid-template-areas:"best start"}
    .check__best{grid-area:best}
    .check__start{grid-area:start}
    /* No yes, so no strongest point (D-65 names no "Not yet"): where we'd start takes the
       whole row rather than sit right of an empty column. */
    .check__report:has(> .check__best:empty){grid-template-areas:"start start"}
  }
}
.check__best:empty{display:none}
.check__box-tick{inline-size:72%;block-size:72%;fill:none;stroke:currentColor;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round}
/* Balanced, so a heading never leaves a word alone on a line in the narrow column. */
.check__sub{margin:0 0 clamp(.375rem, 1.2cqi, .75rem);font-family:var(--font-body);font-size:clamp(.9375rem, 1.8cqi, 1.25rem);font-weight:700;line-height:1.3;letter-spacing:0;color:var(--ink-soft);text-wrap:balance}
.check__well{display:flex;align-items:baseline;gap:.5em;margin:0;font-size:clamp(1.0625rem, 2.6cqi, 1.625rem);font-weight:700;line-height:1.25}
.check__well .check__box-tick{flex:none;inline-size:1em;block-size:1em;color:var(--accent);stroke-width:4;transform:translateY(.14em)}
.check__recs{display:grid;gap:clamp(.625rem, 2.2cqi, 1.375rem);margin:0;padding:0;list-style:none;counter-reset:rec}
.check__recs li{position:relative;padding-inline-start:clamp(2.125rem, 5cqi, 3rem);font-size:clamp(1rem, 2.4cqi, 1.5rem);font-weight:500;line-height:1.4;text-wrap:pretty;counter-increment:rec}
.check__recs li::before{content:counter(rec, decimal-leading-zero);position:absolute;top:-.1em;left:0;font-family:var(--font-display);font-size:clamp(1.375rem, 2.6cqi, 1.75rem);line-height:1.1;letter-spacing:-.02em;color:var(--accent)}
/* A locked place to start: two soft bars where its words would be, and a padlock, all
   decoration (aria-hidden); the line under them says what they stand for. Never real
   words blurred: nothing is hidden that a visitor could not be told. */
.check__locked{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;column-gap:.75rem;row-gap:.4rem;min-block-size:1.4em}
.check__locked::before{opacity:.45}
.check__bar{grid-column:1;display:block;block-size:.62em;border-radius:999px;background:linear-gradient(90deg, var(--rule) 0%, var(--slot) 100%)}
.check__bar--short{inline-size:62%}
.check__lock{grid-column:2;grid-row:1 / span 2;inline-size:1.1em;block-size:1.1em;fill:none;stroke:var(--ink-soft);stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
/* Two classes deep, so .section p's margin of nothing does not win. */
.check__start > .check__more{margin:clamp(.75rem, 2cqi, 1.25rem) 0 0;font-size:clamp(.9375rem, 1.8cqi, 1.125rem);font-weight:700;line-height:1.35;color:var(--ink)}
/* A report that does not fit the card steps down until it does (site.js fitResult). */
.check__report.is-tight{row-gap:var(--space-2)}
.check__report.is-tight :is(.check__recs li,.check__well){font-size:clamp(.9375rem, 1.5cqi, 1.125rem)}
.check__report.is-tighter{row-gap:.75rem}
.check__report.is-tighter .check__recs{gap:.375rem}
.check__report.is-tighter :is(.check__recs li,.check__well){font-size:.9375rem;line-height:1.35}
.check__report.is-tighter .check__sub{margin-block-end:.25rem}
.check__note{font-size:var(--fs-body);color:var(--ink-soft)}
.check__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2) var(--space-3);margin-block-start:auto;padding-block-start:var(--space-1)}
/* The result's note and its buttons, one block at the card's foot, spaced as the card. */
.check__rfoot{display:flex;flex-direction:column;gap:inherit;margin-block-start:auto}
/* Start again and Back as one pair (site.js), no box of its own but on an upright phone. */
.check__rnav{display:contents}
.check__actions .btn__face{min-block-size:3.5rem;padding-inline:1.5em}

/* The email card (D-71; Send A of D-59): the contact form's boxes (pages.css has them,
   which the front page does not load, so they are said again here), the email and the
   first name two across from 34rem, the note on what happens above them, the failure
   under them, Back and "Get my report" at the foot. */
.check__form{flex:1 1 auto;display:flex;flex-direction:column;gap:clamp(.75rem, 2cqi, 1.25rem);min-block-size:0;margin:0}
.check__formbody{display:flex;flex-direction:column;gap:inherit;min-block-size:0}
.check__fields{display:grid;grid-template-columns:minmax(0,1fr);gap:clamp(.75rem, 1.8cqi, 1.125rem) var(--space-2)}
@container (min-width: 34rem){
  .check__fields{grid-template-columns:repeat(2, minmax(0,1fr))}
}
/* From 64em on a deck under 34rem (1024 to about 1080 wide) the boxes stand one under
   the other in the question cards' size, and the first name's box ran 16 to 24px past
   the card's foot, half a box in a body that scrolled (1024x768, 1024x1366). There the
   note, the gaps and the boxes are a size closer, as on a phone held sideways, and both
   boxes are whole (two across, "Your first name (optional)" took two lines; device
   pass, 2026-10-27). */
@media (min-width: 64em){
  @container (max-width: 33.99rem){
    .is-send .check__hint{line-height:1.4}
    .is-send .check__form{gap:.625rem}
    .is-send .check__fields{row-gap:.625rem}
    .is-send .check__field input{min-block-size:2.75rem;padding-block:.5em}
  }
}
.check__field{display:grid;align-content:start;gap:.35rem;min-inline-size:0}
.check__field label{font-weight:700;color:var(--ink)}
.check__field input{box-sizing:border-box;inline-size:100%;min-block-size:3.25rem;padding:.7em .9em;font-family:var(--font-body);font-size:var(--fs-body);line-height:1.4;color:var(--ink);background:var(--white);border:var(--line) solid var(--ink);border-radius:12px}
.check__field input:focus-visible{outline:none;box-shadow:var(--plate-sm) var(--plate-sm) 0 var(--accent)}
.check__field [aria-invalid="true"]{box-shadow:var(--plate-sm) var(--plate-sm) 0 var(--ink)}
.check__error:not([hidden]){justify-self:start;margin:0;padding:.3em .75em;font-size:var(--fs-small);font-weight:700;color:var(--error);background:var(--ink);border-radius:8px}
.check__failed:not([hidden]){margin:0;padding:.75em 1em;font-weight:600;line-height:1.45;color:var(--ink);background:var(--white);border:var(--line) solid var(--ink);border-radius:12px;box-shadow:var(--plate-sm) var(--plate-sm) 0 var(--ink)}
.check__failed:focus{outline:none}
/* Brought into view whole, its plate too, when it takes focus inside the card's body. */
.check__failed{scroll-margin:.75rem}
.check__failed a{font-weight:700;color:var(--ink);overflow-wrap:anywhere;text-decoration-color:var(--accent);text-decoration-thickness:2px;text-underline-offset:.2em}
.check__aside{display:flex;flex-wrap:wrap;gap:0 var(--space-3);margin:0 0 calc(var(--space-1) * -1)}
/* While it sends, the contact form's busy look (pages.css, which the front page does not
   load, so the ring's keyframes are here too): a faded Send with a turning ring. Transform
   only, so engine.css's reduced-motion rule leaves a still ring (T-052). */
.check__form[aria-busy="true"] .check__submit{cursor:progress;opacity:.8}
.check__form[aria-busy="true"] .check__submit .btn__face::after{content:"";inline-size:1em;block-size:1em;box-sizing:border-box;border:2px solid currentColor;border-inline-end-color:transparent;border-radius:50%;animation:sending .8s linear infinite}
@keyframes sending{to{transform:rotate(1turn)}}

/* ---- The strip beside the deck (the pile until D-59, the scorecard until D-71): a box
   for each yes-or-no question and a small card for the last question. site.js flies
   each answered card into its own place, then this lands: a tick on magenta for a yes,
   plain paper for a not yet, the answer written on the small card. Every place is there
   from the start, dashed until it fills, so nothing moves as it fills. From 64em at the
   foot of the left column: a row of five big boxes, then the small card; under 64em one
   strip of six small boxes above the deck, the small card showing its number only (the
   words are on the preview). The strip is decoration (aria-hidden): the cards and the
   live region say it all. */
.check__layout:not(.is-built) .check__pile{display:none}
/* --box: six places and the gaps fit the phone's width, from 320 (29px) up. */
.check__pile{--box:clamp(1.75rem, (100vw - 8rem) / 7, 2.5rem);position:relative;display:flex;align-items:center;gap:.625rem;max-inline-size:100%}
/* At 200% text the strip may no longer fit a phone's width: it wraps rather than running
   off the band (review a11y, 2026-10-26). At 100% it fits and never wraps. */
@media (max-width: 63.99em){
  .check__pile{flex-wrap:wrap;row-gap:.5rem}
  /* The five boxes too, not only the strip: at 200% on a 320 phone they were 318px in a
     288px band, the fifth cut at the screen's edge (device pass, 2026-10-27). */
  .check__pile .check__boxes{flex-wrap:wrap;row-gap:.5rem}
}
.check__pile .check__boxes,.check__notes{display:flex;gap:.3rem}
.check__slot{--r:0deg;position:relative;flex:none;inline-size:var(--box);block-size:var(--box)}
.check__slot-face{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:.6rem;box-sizing:border-box;color:var(--ink);border:2px dashed var(--border-input);border-radius:var(--radius-sm);opacity:.55;transform:rotate(var(--r))}
.check__slot.is-filled .check__slot-face{opacity:1;background:var(--paper);border:var(--line) solid var(--ink);box-shadow:3px 3px 0 var(--accent)}
.check__slot.is-yes .check__slot-face{color:var(--on-accent);background:var(--accent);box-shadow:3px 3px 0 var(--paper)}
.check__slot .check__box-tick{inline-size:74%;block-size:74%}
.check__slot--note .check__chip-n{font-family:var(--font-display);font-size:max(.75rem, var(--box) * .46);line-height:1;letter-spacing:-.02em;font-variant-numeric:tabular-nums}
/* Ink, not the secondary grey, under the empty face's .55: about 5.4:1 (review a11y, 2026-10-26). */
.check__slot--note:not(.is-filled) .check__chip-n{color:var(--text-primary)}
.check__slot--note .check__chip-a{display:none}
@media (min-width: 64em){
  .check__pile{--box:clamp(2.75rem, 1.5rem + 1.6 * var(--vu), 3.75rem);display:grid;grid-template-columns:minmax(0,1fr);justify-items:start;gap:clamp(.75rem, .4rem + .5 * var(--vu), 1.125rem);inline-size:min(100%, 30rem)}
  .check__pile .check__boxes{gap:clamp(.625rem, .35rem + .4 * var(--vu), 1rem)}
  .check__slot.is-filled .check__slot-face{box-shadow:4px 4px 0 var(--accent)}
  .check__slot.is-yes .check__slot-face{box-shadow:4px 4px 0 var(--paper)}
  .check__notes{justify-self:stretch;display:grid}
  .check__slot--note{--r:-1deg;inline-size:calc(100% - 1rem);block-size:calc(var(--box) * .85)}
  .check__slot--note .check__slot-face{justify-content:flex-start;gap:clamp(.75rem, .4rem + .6 * var(--vu), 1.125rem);padding-inline:1rem}
  .check__slot--note .check__chip-n{flex:none;font-size:clamp(1.25rem, .9rem + .6 * var(--vu), 1.75rem);color:var(--accent)}
  /* The small card is unseen until its answer lands (the flight, D-55, brings it in), its
     room kept so nothing moves as it arrives: empty, a long dashed bar marked only with
     its number read as a misaligned placeholder with nothing to say what it was for
     (review, 2026-10-26). On a phone it is the strip's sixth box, numbered, and stays. */
  .check__slot--note:not(.is-filled) .check__slot-face{opacity:0}
  .check__slot--note .check__chip-a{display:block;min-inline-size:0;overflow:hidden;font-size:clamp(1rem, .8rem + .35 * var(--vu), 1.25rem);font-weight:700;line-height:1.2;white-space:nowrap;text-overflow:ellipsis}
  /* The empty strip says what it is for until the first box fills (audit, 2026-10-25: a
     bare dashed tray under the intro, with nothing to say why). Its words are the
     strip's, which is aria-hidden: the cards and the live region say it all. */
  .check__pile[data-empty]::before{content:attr(data-empty);font-size:var(--fs-small);font-weight:600;color:var(--text-secondary);transition:opacity .3s var(--ease-out)}
  .check__pile[data-empty]:has(.is-filled)::before{opacity:0}
}
/* A short desktop screen (1366x657, 1024x768, and up to 52em: 1440x821, a 1440x900 screen
   less a browser's bars): less air round the band and inside the card, so the whole card
   and the strip fit under the pill. */
@media (min-width: 64em) and (max-height: 51.99em){
  .self-check{--check-pad:clamp(1.25rem, 3svh, 2rem)}
  .self-check .check__title{font-size:min(1rem + 5vw, 11svh)}
  .check__intro{font-size:clamp(1.125rem, 2.8svh, 1.375rem)}
  .check__deck{--win-pad:clamp(1rem, 3.5svh, 1.75rem)}
  .check__face{gap:clamp(.625rem, 2svh, 1rem)}
  .check__face{--n-size:clamp(2.5rem, 7svh, 4.5rem)}
  /* The note is one line, so the preview fits in the question cards' height (review
     finding deck-3); the score already shares its row with the heading. Its line a step
     smaller than a question too, so the strongest point and where we'd start keep their
     room at 1024x768. */
  .is-result .check__note{font-size:.9375rem}
  .self-check .is-result .check__question{font-size:clamp(1.375rem, 3.3svh, 1.875rem)}
  .self-check .check__question{font-size:clamp(1.5rem, 4.6svh, 2.25rem)}
  .check__tiles{--tile-min:3.5rem}
  .check__deck :is(.check__foot,.check__actions) .btn__face{min-block-size:2.875rem}
}
/* A phone's deck (under 30rem): a card was taller than the screen, so its buttons were
   below the fold (review finding deck-4). A smaller number, closer tiles and less air, so
   every card fits a 375x812 or 390x844 screen under the pill and the strip. On a shorter
   screen still, a question card's foot (Back) and the email card's (Back, Get my report)
   stay at the screen's foot while the card runs past it, on the card's own paper. The
   preview's never does: it is the one card read to its end, and held there its note and
   buttons hid where we'd start, the heart of it (D-59). It comes straight after, as the
   page scrolls. At the usual text size an upright phone's deck is closer still, with
   Back beside the card's number (D-84, after this block). */
@container (max-width: 29.99rem){
  .check__face{--n-size:2.5rem;--win-pad:1rem;gap:.75rem}
  /* Yes and Not yet as two upright playing cards, so their card is nearer the room kept
     for the tallest (the last card's five tiles) and the thumb has a big target. The
     word as big as "Not yet" (3.91em) can be and stay one line: it was 7cqi, which set
     it as "Not / yet" from 320 to 375 and left it 0px to spare at 390 (device pass,
     2026-10-27). The tile is about half the deck less 72px inside, so at most 12.6cqi less
     1.15rem, a little under that so 4px or more are spare: 17px at 320, 22 to
     26px from 360 to 390, 28px from 414. */
  .check__tiles--two{--tile-max:auto}
  .check__tiles--two .check__tile-face{min-block-size:min(56cqi, 12rem);padding-inline:.6rem;font-size:clamp(1.0625rem, 12.4cqi - 1.15rem, 1.75rem)}
  .check__ask{gap:.625rem}
  .check__hint{margin-block-start:-.125rem;font-size:1rem}
  .check__tiles{--tile-min:3.25rem;margin-block-start:0;gap:.625rem}
  .check__note{font-size:.9375rem}
  .check__actions{gap:.25rem 1.5rem}
  /* Back and "Get my report" on one row from 320: by 12px they took two, a foot 129px
     tall held over the email box at the screen's foot (device pass, 2026-10-27). With
     room to spare they still stand apart (space-between). */
  .check__foot{column-gap:.5rem}
  .check__foot .btn{font-size:1rem}
  .check__foot .btn__face{padding-inline:1.1em}
  /* The preview's score and the email card's envelope above their heading, where a
     question's number is, not beside it: beside them the heading had 170 to 200px and
     set as six lines at 320 ("Good / things / going for / you, with / more / to come.")
     and four at 390 (device pass, 2026-10-27). The envelope goes back beside its
     heading at the usual text size, where the heading is 20px (D-84, below). */
  .check__top{flex-direction:column;align-items:flex-start;gap:.5rem}
  /* With the reader's own large text (a 32px default, 200%), the question no more than
     9.1% of the deck's width: at its 1.625rem least it was 52px in a card about 190px
     wide, and "recommend" ran 129px out of the card at 390 and 199px at 320, cut by the
     cards under it (device pass, 2026-10-27). 9.1% keeps "recommend" whole at 390 and
     200% (29.7px). At the usual 16px the phone's 26px is less than that on any phone's
     deck (26.2 to 36px), so nothing changes there. A word still too wide for the card
     may break rather than be cut (at 320 and 200%, where the card is 124px inside). */
  .self-check .check__question{font-size:min(1.625rem, 9.1cqi);overflow-wrap:anywhere}
}
/* An upright phone's deck (D-84): every card, its heading and its strip in one screen of
   Safari's with its bars (390x664, 360x640, 430x739; the band's own rules are above).
   At the reader's usual text size only (from 19.6em wide: a phone's deck of 17.5rem and
   more, as the container rules above count it); with large text the deck is as it was.
   - Closer: a smaller number, less air in the card, the cards behind it a step closer.
   - Yes and Not yet no taller than 16svh (106px at 664 high, 118 at 739): at
     min(56cqi, 12rem) they were 192px each, and their foot ran 66px past the screen at
     390x664 with the heading in view.
   - Back beside the card's number, not in a foot under the answers: the foot was 64px
     of every card after the first. Its 46px target stands across the number's row. The
     preview's Start again and Back the same, beside the score (site.js .check__rnav), so
     "Get my full report" has its row alone. A tile's focus no longer keeps the foot's
     room at the screen's foot (below 64em, further down): no foot is there, and the room
     scrolled the heading under the pill at a Tab to a low tile.
   - The question a step smaller (24px; 22px under 600px high, with the tab and the number
     closer still); the preview's line and the email card's heading 20px, the envelope
     beside its heading, the preview's report, the note and the email card's boxes a size
     closer.
   The preview still does not fit with the heading on any phone (515px at 390): show() in
   site.js brings it up with the strip, or alone under the pill where only that shows it
   whole (360x640). */
@media (min-width: 19.6em) and (max-width: 32.49em){
  .check__stack{--deck-step:.375rem}
  .check__face{--n-size:2.25rem;--win-pad:.875rem;gap:.625rem}
  .check__tiles--two .check__tile-face{min-block-size:min(56cqi, 12rem, 16svh)}
  .self-check .check__question{font-size:min(1.5rem, 9.1cqi)}
  .check__face > .check__foot{position:absolute;inset:calc(var(--tab) + var(--win-pad)) var(--win-pad) auto auto;block-size:calc(var(--n-size) * .8);align-content:center;margin:0;padding:0;background:none}
  .is-result .check__rnav{position:absolute;inset:calc(var(--tab) + var(--win-pad) + var(--n-size) * .52 - 23px) var(--win-pad) auto auto;display:flex;column-gap:1rem}
  html:has(.check__tile:focus){scroll-padding-block-end:0}
  .self-check .is-result .check__question{font-size:1.25rem}
  .check__report{row-gap:.75rem;margin-block-start:0}
  .check__sub{margin-block-end:.25rem;font-size:.875rem}
  .check__well{font-size:1rem}
  .check__recs{gap:.375rem}
  .check__recs li{padding-inline-start:2rem;font-size:.9375rem;line-height:1.35}
  .check__recs li::before{font-size:1.25rem}
  .check__locked{row-gap:.3rem}
  .check__bar{block-size:.45em}
  .check__start > .check__more{margin-block-start:.5rem;font-size:.875rem}
  .check__actions{gap:.25rem 1rem}
  .check__actions .btn{font-size:1rem}
  .check__actions .btn__face{min-block-size:3rem;padding-inline:1.1em}
  .is-send .check__top{flex-direction:row;align-items:center;gap:.75rem}
  .self-check .is-send .check__question{font-size:1.25rem}
  .is-send .check__hint{font-size:.9375rem;line-height:1.4}
  .is-send .check__form{gap:.625rem}
  .is-send .check__fields{row-gap:.625rem}
  .is-send .check__field input{min-block-size:2.75rem;padding-block:.5em}
}
/* The last card's five answers two across (D-84), the fifth the row's width: one under
   another they were 300px of a 461px card at 390. The words a size down and the mark
   closer to the corner, so each answer is two lines from 360 ("Looking after the site"
   took three at 360 with the usual padding); at 320 some take three. Not with large text,
   where a word would break inside itself. */
@container (min-width: 17.51rem) and (max-width: 29.99rem){
  .check__tiles--many{grid-template-columns:repeat(2, minmax(0,1fr));gap:.5rem}
  .check__tiles--many > li:last-child:nth-child(odd){grid-column:1 / -1}
  .check__tiles--many .check__tile-face{padding:.5rem .5rem .5rem .75rem;font-size:.9375rem;line-height:1.1}
  .check__tiles--many .check__tile-word::before{inline-size:1.3em}
  .check__tiles--many .check__tile-mark{top:.55rem;right:.5rem}
}
/* Under 600px high (an iPhone SE's 375x548 in Safari, 320x500): the rows, the tab and the
   number closer again, so the first card fits under the heading at 375x548 (D-84). */
@media (min-width: 19.6em) and (max-width: 32.49em) and (max-height: 37.49em){
  .check__layout{row-gap:.5rem}
  .check__face{--tab:1.875rem;--n-size:2rem}
  .self-check .check__question{font-size:min(1.375rem, 9.1cqi)}
}
/* Yes and Not yet one under the other once the reader's text is large for the deck (the
   deck narrower than 17.5rem of it: from about 150% on a phone; never at the usual 16px,
   where the narrowest phone's deck is 18rem): side by side at 200% each tile left its
   words 0 to 23px, and they stood a letter to a line, "Y / e / s" (device pass,
   2026-10-27). One under the other, no taller than their words need. */
@container (max-width: 17.5rem){
  .check__tiles--two{grid-template-columns:minmax(0,1fr)}
  .check__tiles--two .check__tile-face{min-block-size:auto;font-size:min(1.25rem, 9cqi)}
}
@media (max-width: 63.99em){
  .check__foot{position:sticky;inset-block-end:0;z-index:1;margin-inline:calc(var(--win-pad) * -1);padding-inline:var(--win-pad);padding-block-end:.5rem;margin-block-end:-.5rem;background:var(--paper)}
  /* A tile brought into view (a Tab to it) stops above that foot, never under it, where
     it would be focused and hidden: the screen's foot keeps the foot's room as html's
     scroll-padding. Only while a tile has focus: held always, it made every focus on the
     page count that room, and a Tab from the last tile to Back, already in view, jumped
     the page 353px at 375x812 (review finding vdeck2-2). The tile's own scroll-margin
     does not do it: Chrome leaves a tile half under the foot as already in view. The email
     card's boxes, its failure and its links the same (D-59). */
  html:has(:is(.check__tile,.check__field input,.check__failed,.check__aside a):focus){scroll-padding-block-end:5.5rem}
}
/* A phone held sideways (844x390, 932x430; device pass, 2026-10-27): the question cards
   were the upright phone's, the number, the question and the answers one under another,
   about 480px tall in the 234px left under the pill and the strip. After each tap the
   next card came up with its question cut by the sticky foot and its answers under it,
   off the screen. Here each question card is three rows, the width the screen has: the
   number and the question side by side (the preview's top row), the answers in one row
   under them as wide, low tiles (five across on the last card), and Back. The strip sits
   closer above the deck. Every question card's question and answers are then on the
   screen under the pill, Back held at its foot (cards 181 to 268px from 136px down). */
@media (max-width: 63.99em) and (max-height: 30em) and (orientation: landscape){
  .check__layout{row-gap:.75rem}
  .check__deck{margin-block-start:0}
  .check__face{--n-size:2.25rem;--win-pad:1rem;gap:.625rem}
  .check__face:has(> .check__ask > .check__tiles){display:grid;grid-template-columns:auto minmax(0,1fr);align-content:start;column-gap:1rem}
  .check__face:has(> .check__ask > .check__tiles) > .check__ask{display:contents}
  .check__face:has(> .check__ask > .check__tiles) > .check__num{grid-column:1;align-self:center}
  .self-check .check__face:has(> .check__ask > .check__tiles) .check__question{grid-column:2;align-self:center;font-size:1.375rem;text-wrap:balance}
  .check__face:has(> .check__ask > .check__tiles) .check__tiles{--tile-max:auto;grid-column:1 / -1;margin:0;gap:.75rem}
  .check__face:has(> .check__ask > .check__tiles) > .check__foot{grid-column:1 / -1}
  .check__face .check__tiles--many{grid-template-columns:repeat(5, minmax(0,1fr))}
  .check__face .check__tiles .check__tile-face{min-block-size:3.75rem;padding:.6rem 2.25rem .55rem .85rem}
  .check__face .check__tiles--two .check__tile-face{font-size:1.5rem}
  .check__face .check__tiles--many .check__tile-face{align-items:flex-start;padding:.6rem .75rem;font-family:var(--font-body);font-size:1rem;font-weight:700;line-height:1.2;letter-spacing:0}
  .check__face .check__tiles--many .check__tile-word::before{display:block}
  .check__face .check__tiles .check__tile-mark{top:.6rem;right:.6rem;inline-size:1.25rem;block-size:1.25rem}
  /* The email card a size closer, so once it comes up to the pill (site.js, show()) its
     heading, its note and both boxes are above its sticky foot. */
  .self-check .is-send .check__question{font-size:1.375rem}
  .is-send .check__ask{gap:.5rem}
  .is-send .check__hint{font-size:.9375rem;line-height:1.4}
  .is-send .check__form{gap:.625rem}
  .is-send .check__field input{min-block-size:2.75rem;padding-block:.5em}
  .check__deck :is(.check__foot,.check__actions) .btn__face{min-block-size:2.875rem}
  /* The preview's strongest point beside where we'd start, as from 64em: the card is the
     screen's width, and one long column of short lines only made it longer to read. */
  .check__report{grid-template-columns:minmax(0,1fr) minmax(0,1.6fr);grid-template-areas:"best start"}
  .check__best{grid-area:best}
  .check__start{grid-area:start}
  .check__report:has(> .check__best:empty){grid-template-areas:"start start"}
}
@media (prefers-reduced-motion: reduce){
  .js .check__deck[data-reveal]{opacity:1;transform:none;transition:none}
  .check__tile-face,.check__quiet > span::after{transition:none}
}

/* ==== Questions people ask: faq/details-list. The paper band, calm ================ */
/* More our colours and more playful (Stanley, 2026-10-19), and still calm: state
   feedback only. Each question a white kit pill on its own magenta plate (P3 at pill
   size): at rest the pill sits --plate up and left of its plate; hover lifts a closed
   one 3px further; an open one presses down to 3px, like a real button, and a press
   pushes it to 1px. The plus is a round magenta button (paper on magenta, 3.98:1) whose
   plus turns 135 degrees into a cross. One open at a time through name="faq". The answer
   slides and fades in, opacity and transform only, never a height. */
.faq{--radius-pill:2rem;padding-block:var(--pad-m) var(--pad-s);background:var(--paper)}
.faq__inner{row-gap:var(--gap-head)}
/* Full width until the hang at 64em: .grid has 12 columns from 48em. */
.faq__inner > *{grid-column:1 / -1;min-inline-size:0}
.faq__head{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:end;column-gap:var(--space-2)}
.faq__icon{display:block;inline-size:clamp(5rem, 3.4rem + 6.5vw, 8.5rem)}
/* "Questions" (5.23em in its face) never wider than the head: at 200% text on a 390
   phone the heading was 80px and the word ran 60px off the screen. The head is the size
   container; the cap binds only at large text (40 to 52px at 100%, 320 to 1023). */
.faq__head{container-type:inline-size}
.faq h2{font-size:min(var(--fs-h2), 100cqi / 5.5)}
.faq__img{display:block;inline-size:100%;block-size:auto}
/* Below 22.5em "Questions" at its 40px is wider than the column the icon leaves it (209
   against 192px at 320), so the icon takes its own row above the heading, smaller: it is
   decoration (aria-hidden), so moving it changes no reading order (review finding
   layout-6). */
@media (max-width: 22.49em){
  .faq__head{grid-template-columns:minmax(0,1fr);row-gap:var(--space-2)}
  .faq__icon{order:-1;justify-self:start;inline-size:4.5rem}
}
.faq__list{--list-step:60ms;display:grid;row-gap:clamp(1rem, .75rem + .9vw, 1.5rem)}
.faq .lift{border-radius:var(--radius-pill)}
.faq__pill{--off:var(--plate);position:relative;display:block;box-sizing:border-box;background:var(--white);color:var(--ink);border:var(--line) solid var(--ink);border-radius:var(--radius-pill);transform:translate(calc(var(--off) * -1),calc(var(--off) * -1));transition:transform var(--t-state) var(--ease-out)}
.faq__pill[open]{--off:3px}
@media (hover:hover){
  .faq__pill:not([open]):hover{--off:calc(var(--plate) + 3px)}
}
.faq .faq__pill:has(> summary:active){--off:1px;transition-duration:.1s}
.faq summary{position:relative;display:flex;align-items:center;box-sizing:border-box;min-block-size:3.5rem;padding:.875rem 4.5rem .875rem clamp(1.125rem, .9rem + 1vw, 1.75rem);border-radius:calc(var(--radius-pill) - var(--line));font-weight:700;font-size:var(--fs-faq);line-height:1.3;list-style:none;cursor:pointer}
.faq summary::-webkit-details-marker{display:none}
/* No word left alone on a question's last line (review finding layout-5). */
.faq__q{text-wrap:pretty}
/* Focus: a magenta ring inside the white pill (4.4:1), where the magenta plate cannot
   swallow it. */
.faq summary:focus-visible{outline:3px solid var(--focus);outline-offset:-7px}
.faq__plus{position:absolute;top:50%;right:clamp(.625rem, .4rem + .6vw, 1rem);box-sizing:border-box;inline-size:2.75rem;block-size:2.75rem;margin-block-start:-1.375rem;background:var(--accent);border:var(--line) solid var(--ink);border-radius:50%;transition:transform var(--t-hover) var(--ease-out)}
.faq__plus::before,.faq__plus::after{content:"";position:absolute;left:50%;top:50%;inline-size:1.125rem;block-size:4px;margin:-2px 0 0 -.5625rem;border-radius:2px;background:var(--paper)}
.faq__plus::after{transform:rotate(90deg)}
.faq__pill[open] .faq__plus{transform:rotate(135deg)}
/* 47ch keeps a line to 80 characters or fewer (browser-check 18): at 52ch the price
   answer, longer since the slim, ran to 86 at 1440. */
.faq__a{max-inline-size:47ch;padding:0 clamp(1.125rem, .9rem + 1vw, 1.75rem) 1.5rem;font-size:var(--fs-body);color:var(--ink-soft)}
/* Links inside the answers (P1-26): inline, so a long one wraps with its sentence (the
   site's .text-link is an inline-block and would not); in ink a weight up, on the faint
   resting line other text links have (currentColor at 40%), which turns magenta and
   2px under a mouse, at once: only colour and thickness change, nothing animates. */
.faq__a a{color:var(--ink);font-weight:600;text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:.2em;text-decoration-color:color-mix(in srgb, currentColor 40%, transparent)}
@media (hover:hover){
  .faq__a a:hover{text-decoration-color:var(--accent);text-decoration-thickness:2px}
}
.faq__a a:focus-visible{outline:3px solid var(--focus);outline-offset:2px;border-radius:2px}
.faq__pill[open] > .faq__a{animation:faq-in .3s var(--ease-arrive) both}
@keyframes faq-in{from{opacity:0;transform:translateY(-.5rem)}to{opacity:1;transform:none}}
/* At the reader's own large text on a phone (narrower than 20em of it: never at 100%,
   where 320px is 20em), the plus sits over the question at the pill's top right and the
   question takes the pill's width: beside the 4.5rem kept for the plus a question's
   longest word widened every pill, to x 364 on a 320 or 360 phone at 200% (device pass,
   2026-10-27). */
@media (max-width: 19.99em){
  .faq summary{padding-block-start:calc(.875rem + 2.75rem + .5rem);padding-inline-end:clamp(1.125rem, .9rem + 1vw, 1.75rem)}
  .faq__plus{top:.875rem;margin-block-start:0}
}
@media (min-width: 64em){
  .faq__inner{align-items:start}
  .faq__head{grid-column:1 / 5;grid-template-columns:minmax(0,1fr);row-gap:var(--gap-text);align-items:start}
  .faq__icon{inline-size:min(100%, 17rem, 32svh)}
  .faq__list{grid-column:5 / -1}
  .faq h2{font-size:clamp(2.5rem, 1rem + 3.2vw, 4.25rem)}
}
/* The heading and icon held in view under the nav while the questions scroll past, on
   a screen tall enough to hold them. */
@media (min-width: 64em) and (min-height: 40em){
  .faq__head{position:sticky;top:calc(var(--nav-h) + 2.5rem)}
}

/* ==== How you get a price: steps/track, a standout ("line") ====================== */
/* Sam's call of 2026-10-25, in place of the sticky panels (D-10): no card, no plate
   rising, no pile. One thick magenta line through five round portholes, each holding
   its step's clay film and standing on the logo's window dot, which fills magenta as
   the line reaches it (how-it-works.php says why each part is as it is). Finished
   states first; start states only under .js (T-052).
   Below 64em the route runs down a lane on the left: the porthole in the lane, the
   number, name and line beside it. --port is the porthole; --row the space between
   stops, which the line crosses. .route's grid has a row per stop (--n, from the
   template) and the list takes them as a subgrid, so the line, a grid item in the lane
   from row 1 to the last but one, runs from the first stop's dot to the last stop's by
   the rows themselves, however the words wrap. The name's and text's rules start with
   .how-it-works to outrank .section h3 and .section p, which zero their margins.
   The porthole is never more than about a quarter of the screen's width (26vw): with
   the reader's own large text (200%) its 5rem least was 160px of a 390 phone, its
   words had 130px, and "change," and "touch" ran past the screen's edge (60px at 320;
   device pass, 2026-10-27); the step's name, below, no more than 10.5vw, for 320. At
   the usual 16px both caps are wider than what they cap on every screen, so nothing
   changes there. */
.route{--port:min(clamp(5rem, 3.2rem + 7.5vw, 7.5rem), 26vw);--row:clamp(2.25rem, 1.75rem + 2vw, 3.25rem);--dot:1.125rem;--ring:var(--line);--steps-paper:#F4F1EA;position:relative;display:grid;grid-template-columns:var(--port) minmax(0,1fr);grid-template-rows:repeat(var(--n, 5), auto);column-gap:clamp(1.125rem, .75rem + 1.6vw, 2rem);row-gap:var(--row);margin-block-start:var(--gap-head)}
.route__stops{position:relative;z-index:1;grid-column:1 / -1;grid-row:1 / -1;display:grid;grid-template-columns:subgrid;grid-template-rows:subgrid;margin:0;padding:0;list-style:none;counter-reset:step}
.route__stop{counter-increment:step;grid-column:1 / -1;display:grid;grid-template-columns:subgrid;grid-template-rows:auto auto 1fr;align-content:start}
.route__num{grid-column:2;display:block;padding-block-start:clamp(.5rem, .2rem + 1.4vw, 1.25rem);font-family:var(--font-display);font-size:1rem;line-height:1;letter-spacing:-.01em;font-variant-numeric:tabular-nums}
.route__num::before{content:counter(step, decimal-leading-zero)}
.how-it-works .route__name{grid-column:2;margin-block-start:.5rem;font-size:min(clamp(1.375rem, 1.05rem + 1.3vw, 2rem), 10vw);line-height:1.05}
.how-it-works .route__text{grid-column:2;max-inline-size:34ch;margin-block-start:.625rem;font-size:var(--fs-body);font-weight:450;line-height:1.5;color:var(--ink-soft);text-wrap:pretty}
/* The step's words at most 10vw, and its name's cap 10vw, not 10.5 (device pass,
   2026-10-27): at 200% text on a 320 phone the column beside the stations is 137px, and
   "change," and "anything" ran 3 and 4px past the frame. Neither binds at 100% text. */
.how-it-works .route__text{font-size:min(var(--fs-body), 10vw)}
/* Short leads, two to four lines at 19 to 26px, set balanced rather than pretty: pretty
   stops only a one-word last line, and these were ending "prices here.", "with you.",
   "you say so." or on "and" (audit, 2026-10-25). Then and now's, the services' and the
   letter's stay pretty: balanced, they came out worse. On the smallest phones the cards'
   words drop to the body size, as 19px gave lines of 23 characters. */
.section :is(.steps__intro,.work__intro,.check__intro){text-wrap:balance}
@media (max-width: 23.99em){
  .stack__text{font-size:var(--fs-body)}
}
/* The words that stand on the ground take its ink as the page turns from the self-
   check's ink back to paper (bootGround), as the section's intro does. */
html[data-ground] .on-ground .route__text{color:var(--on-soft);transition:color 0s linear var(--flip)}
html.is-ground-still .on-ground .route__text{transition:none}

/* The porthole: a paper circle in the kit's ink outline, with no plate: a magenta plate
   offset down and right put a magenta crescent where the line leaves each dot, and the
   two ran together (2026-10-25, at 1440). The line and the dots are this section's
   magenta. The film fills 84% of the porthole (the frame), its square's edges faded into
   the porthole's paper (the films carry that paper, #F3EFE8 at their edges, within two
   levels of it on screen), so no object's corner reaches the ring; the still sits in the
   frame at the film's scale (its object is about 80% of the film). */
.route__port{position:relative;grid-column:1;grid-row:1 / -1;align-self:start;display:block;inline-size:var(--port);aspect-ratio:1}
.route__face{position:absolute;inset:0;display:grid;place-items:center;box-sizing:border-box;border-radius:50%;background:var(--steps-paper);border:var(--ring) solid var(--ink);overflow:hidden;overflow:clip}
.route__frame{position:relative;display:block;inline-size:84%;aspect-ratio:1}
.route__img{position:absolute;inset:10%;display:block;inline-size:80%;block-size:80%;object-fit:contain}
/* The film: hidden until it has a frame (site.js marks the porthole .is-ready), so a
   film still loading, or one that never loads, leaves the still. Under reduced motion it
   goes, and the still is all there is. */
.route__film{position:absolute;inset:0;display:block;inline-size:100%;block-size:100%;object-fit:cover;visibility:hidden;-webkit-mask-image:radial-gradient(closest-side, #000 86%, transparent 100%);mask-image:radial-gradient(closest-side, #000 86%, transparent 100%)}
.route__port.is-ready .route__film{visibility:visible}
@media (prefers-reduced-motion: reduce){
  .route__film{display:none}
}
/* The dot: the logo's window dot, paper in its ink ring, at the porthole's foot, where
   the line meets it. It fills magenta from its centre as its stop lights. */
.route__dot{position:absolute;z-index:1;top:100%;left:50%;box-sizing:border-box;inline-size:var(--dot);aspect-ratio:1;margin:calc(var(--dot) / -2) 0 0 calc(var(--dot) / -2);border-radius:50%;background:var(--paper);border:var(--ring) solid var(--ink)}
.route__dot::after{content:"";position:absolute;inset:0;border-radius:50%;background:var(--accent)}

/* The line: magenta, one stroke, drawn by stroke-dashoffset (pathLength 1). Down, a
   straight stroke in the lane, stretched to the rows it spans (preserveAspectRatio
   none: the stroke runs along the stretch, so its width never changes); across, the
   garland, scaled as one with the row. */
.route__line{pointer-events:none}
.route__line svg{position:absolute;inset:0;display:block;inline-size:100%;block-size:100%;overflow:visible}
.route__line path{fill:none;stroke:var(--accent);stroke-dasharray:1 2;stroke-dashoffset:0}
.route__line--down{position:relative;grid-column:1;grid-row:1 / -2;justify-self:center;inline-size:8px;margin-block:var(--port) calc(-1 * (var(--row) + var(--port)))}
.route__line--down path{stroke-width:8;stroke-linecap:butt}
.route__line--across{display:none}
.route__line--across path{stroke-width:9;stroke-linecap:round;stroke-linejoin:round}

/* Desktop, from 64em: the stops side by side, a column each, and the garland under the
   portholes. The porthole is .565 of a column's pitch (its width and one gutter), and
   the drawing's viewBox spans the five pitches (how-it-works.php works it out from the
   same numbers), so the drawing scales with the row and its dots stay on the portholes'
   dots. The number starts .22 of a pitch under the porthole (margins in % are of the
   column's width), clear of the swag where it rises to the column's dot, and the swags'
   lowest points, over the gutters, sit above the names: the whole section, from heading
   to the last line, fits 1440x900 as an anchor jump lands it. */
@media (min-width: 64em){
  .route{--dot:1.375rem;--ring:var(--line-heavy);display:block}
  .route__stops{grid-template-columns:repeat(var(--n, 5), minmax(0,1fr));grid-template-rows:none;column-gap:var(--gutter)}
  .route__stop{grid-column:auto;display:block}
  .route__port{inline-size:calc((100% + var(--gutter)) * .565)}
  .route__num{padding-block-start:0;margin-block-start:calc((100% + var(--gutter)) * .22);font-size:1.125rem}
  .how-it-works .route__name{margin-block-start:.5rem;font-size:clamp(1.25rem, .55rem + 1.1vw, 1.625rem)}
  .how-it-works .route__text{margin-block-start:.625rem}
  .route__line--down{display:none}
  .route__line--across{display:block;position:absolute;inset-block-start:0;inset-inline-start:0;inline-size:calc(100% + var(--gutter))}
  .route__line--across svg{position:static;block-size:auto}
}

/* The arrival. Each stop lights as the line reaches it (site.js marks it .is-lit): its
   dot fills from its centre and its clay object comes up from faint to full, and then
   its film plays (site.js waits for these). The line draws with the scroll where the
   browser has a view timeline: across, while the row's top rises from halfway down the
   screen to a fifth of the way down, so it starts with the first porthole whole on
   screen and ends with the row still clear of the nav's pill. Sam's item 6
   (2026-10-26): from 90% to 55% it began with only the portholes' tops over the fold,
   every dot below it, and was over before the row reached the middle (measured at
   1024x768, 1440x900 and 2000x1200). Down, its head held 60% up the screen (the
   timeline's view ends 60% short of the screen's foot, and the stroke crosses that
   edge), so the first porthole is whole on screen before the line leaves its dot, and
   the last is still on screen when it ends (390x844, 768x1024). site.js lights the
   stops at the same places. Without a view timeline, the line follows the last lit stop
   (--reach, from site.js). */
.js .route__stop:not(.is-lit) .route__frame{opacity:.28;transform:scale(.84)}
.js .route__stop:not(.is-lit) .route__dot::after{transform:scale(0)}
.js .route__frame{transition:opacity .5s var(--ease-arrive),transform .7s var(--ease-arrive)}
.js .route__dot::after{transition:transform .4s var(--ease-arrive)}
.js .route__line path{stroke-dashoffset:calc(1 - var(--reach, 0));transition:stroke-dashoffset .9s var(--ease-arrive)}
@keyframes route-draw{from{stroke-dashoffset:1}to{stroke-dashoffset:0}}
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion: no-preference){
    .js .route__line{view-timeline:--route block}
    .js .route__line--down{view-timeline-inset:0% 60%}
    .js .route__line path{transition:none;animation:route-draw linear both;animation-timeline:--route}
    .js .route__line--across path{animation-range:cover 50vh cover 80vh}
    .js .route__line--down path{animation-range:entry-crossing 0% entry-crossing 100%}
  }
}
@media (prefers-reduced-motion: reduce){
  .js .route__line path{stroke-dashoffset:0;transition:none;animation:none}
  .js .route__stop:not(.is-lit) .route__frame{opacity:1;transform:none}
  .js .route__stop:not(.is-lit) .route__dot::after{transform:none}
  .js .route__frame,.js .route__dot::after{transition:none}
}

/* The link on to the How it works page (D-23) under the route: the lead's size, in ink,
   its arrow leaning on toward the page under a mouse, as the About link's does. Two
   classes, to outrank .section p, which zeroes every margin, the frame's auto ones
   included. */
.how-it-works .steps__more{margin:var(--gap-head) auto 0;font-size:var(--fs-lead)}
.steps__go{display:inline-flex;align-items:center;gap:.4em;min-block-size:44px;font-weight:700;color:var(--ink)}
.steps__arrow{flex:none;inline-size:1em;block-size:1em;transition:transform var(--t-hover) var(--ease-out)}
@media (hover:hover){
  .steps__go:hover .steps__arrow{transform:translateX(3px)}
}
@media (prefers-reduced-motion: reduce){
  .steps__arrow{transition:none}
}

/* ==== The call to action. Light page ============================================= */
/* Two places since the form got its own page (Stanley, 2026-10-19): a short closing
   band on the front page, calm, and the contact page, where the form's window carries
   the standout's glow.
   The email is big and bold on the contact page's hero, and the band's "Ring us" and
   "Text us" (once the number is real, cta.php) take the same look: the display face in
   ink over a thick magenta underline, the colours swapping at once on hover (nothing
   animates but transform and opacity). The email breaks after the @ (<wbr>, from
   inc/site.php), and anywhere as the last resort, so it never runs off a 320px screen.
   The underline is a background stripe on the inline link, cloned onto every line it
   wraps to: a pseudo-element line sits under the last line only, and text-decoration
   showed a hairline seam at the <wbr>. Inline, the padding gives each line a 44px hit
   area without moving the lines apart (and display: inline undoes the engine's
   inline-flex for data-tap): .38em, since .32em left the lines 41 to 43px at 375
   (review finding a11y-9). */
.cta{padding-block:var(--pad-l)}
.cta__mail{line-height:1.3}
.cta__email,.cta__way{display:inline;padding-block:.38em;font-family:var(--font-display);font-weight:400;line-height:1.1;letter-spacing:-.02em;color:var(--ink);text-decoration:none;background:linear-gradient(var(--accent),var(--accent)) 0 calc(100% - .3em) / 100% max(3px, .075em) no-repeat;-webkit-box-decoration-break:clone;box-decoration-break:clone;overflow-wrap:anywhere}
@media (hover:hover){
  .cta__email:hover,.cta__way:hover{color:var(--accent);background-image:linear-gradient(var(--ink),var(--ink))}
}

/* The band (front page; decision D-20): "Hello." enormous across the frame, the line
   large under it with "Let's do it." the action. The email it set huge has gone to the
   footer alone (D-20 amended, 2026-10-21); in its place, only once each fact is real,
   "Ring us" and "Text us" at the email's old size (P1-17); the towns are the footer's
   alone (cta.php says why). Without them the band is "Hello." and the line, closed by
   the section's own
   padding. The band is the size container for "Hello.", so its sum reads the band's
   own width (cqi), never the window's with its scrollbar.
   "Hello." is never cut. Measured in Archivo Black (2026-10-20), its ink is 2.87em wide
   as set and 3.62em with WCAG 1.4.12's text spacing (letter-spacing .12em on each of
   its six letters), the spacing at which the intro's enormous "Hello." ran off a phone
   and was clipped (the review's finding); its box is then 3.78em, and 3.9em with the
   clip's room below. So it is sized at 4em to the band's 100cqi, whatever the window:
   72px at 320, 86px at 375, up to 20rem on a desktop, and under 46svh so a short window
   still shows the line with it. The words' clip boxes (P1, the rise) get .06em of room
   at each side, so the H's stem and the full stop's ink are never shaved. The H's stem
   stands .075em inside its glyph, so the line is indented back by that much and the
   stem stands on the frame line; the words' own boxes take no indent, since text-indent
   is inherited into inline-blocks and would land once per box. */
.cta__band{container-type:inline-size}
.cta__band > *{grid-column:1 / -1}
.cta__band h2{font-size:min(100cqi / 4,20rem,46svh);text-indent:-.075em}
.cta__band h2 .word{padding-inline:.06em;margin-inline:-.06em;text-indent:0}
/* The line: the body face at its boldest, large, in two balanced lines at most on a
   desktop. "Let's do it." never breaks inside, and stays an inline box: padding on it
   widens the tap target to 44px and more without moving a line. Its thick ink underline
   is Stanley's screenshot of the intro's; under a mouse a magenta one draws in over it
   from the left, as the site's text links do. Both lines are drawn the same way, so the
   magenta covers the ink to the pixel at every size (a text-decoration underline snaps
   to the device pixel on its own and showed a pixel of ink under the magenta at 768 and
   2560), and both are never under 3px, the email's stripe below it: .07em alone was
   1.96px on a phone and read as an ordinary link (review finding
   lets-do-it-underline-thin-on-phones). The line's top stays .13em under the baseline
   as it grows. In forced colours, where a drawn line vanishes, the text's own underline
   comes back. While the contact page is not published
   the words print plain (cta.php), so nothing here styles them. Scoped to the band, so
   it outranks .section p's margin:0 and text-wrap:pretty, which had set the line flush
   under "Hello." with no gap (review finding band-line-gap-lost). */
.cta__band .cta__stop{color:var(--accent)}
.cta__band .cta__more{margin:var(--gap-text) 0 0;max-inline-size:34em;font-size:var(--fs-lead);font-weight:450;line-height:1.45;color:var(--ink-soft);text-wrap:pretty}
.cta__band .cta__act{margin:clamp(1.5rem, 1rem + 1.5vw, 2.5rem) 0 0}
.cta__band .cta__btn .btn__face{gap:.6em;min-block-size:3.75rem;padding:.85em 1.9em;font-size:var(--fs-lead);background:var(--white)}
/* At the reader's own large text on a phone (narrower than 20em of it: never at 100%),
   the button's sides close to 1em: at 1.9em of 38px they took 144px, and the button ran
   97px off a 320 screen at 200% even with its words a word a line (device pass,
   2026-10-27). */
@media (max-width: 19.99em){
  .cta__band .cta__btn .btn__face{padding-inline:1em}
}
.cta__arrow{flex:none;inline-size:1em;block-size:1em}
.cta__band .cta__line{margin:var(--gap-text) 0 0;max-inline-size:22em;font-family:var(--font-body);font-weight:700;font-size:clamp(1.75rem, 1rem + 2.6vw, 3.25rem);line-height:1.08;letter-spacing:-.02em;color:var(--ink);text-wrap:balance}
.cta__go{--go-line:max(3px,.07em);position:relative;padding-block:.15em;white-space:nowrap;color:inherit;text-decoration:none;-webkit-tap-highlight-color:transparent}
.cta__go::before,.cta__go::after{content:"";position:absolute;left:0;right:0;bottom:calc(.33em - var(--go-line));block-size:var(--go-line);pointer-events:none}
.cta__go::before{background:currentColor}
.cta__go::after{background:var(--accent);transform:scaleX(0);transform-origin:100% 50%;transition:transform .2s var(--ease-arrive)}
@media (hover:hover){
  .cta__go:hover::after{transform:scaleX(1);transform-origin:0 50%;transition-duration:var(--t-hover)}
}
.cta__go:focus-visible{outline:3px solid var(--focus);outline-offset:4px;border-radius:4px}
@media (forced-colors: active){
  .cta__go{text-decoration:underline;text-decoration-thickness:var(--go-line);text-underline-offset:.13em}
  .cta__go::before,.cta__go::after{display:none}
}
.cta__band .cta__ways{margin-block-start:clamp(2rem, 1.25rem + 3vw, 4rem);font-size:clamp(1.5rem, .55rem + 3.7vw, 4.25rem);line-height:1.3}
.cta__ways .cta__way:not(:last-child){margin-inline-end:.45em}

/* P5 (Huge's call to action), Send only, on fine pointers with motion: the button leans
   toward the pointer (site.js sets --mx and --my in a frame), a magenta circle fills the
   face from where the pointer came in, and the label rolls letter by letter. Exits are
   faster than entrances. The face keeps its P3 lift underneath. Only the face's two rules
   are here: they match the front page's closing band button too (.cta .btn__face), so
   they stay with the front page; the rest of P5 is the contact page's, in pages.css. */
.cta .btn__face{overflow:hidden;overflow:clip;isolation:isolate}
@media (hover:hover) and (pointer:fine){
  .cta .btn:hover .btn__face{color:var(--on-accent)}
}

/* ==== The footer: footer/statement, on ink ======================================= */
/* Sam's style B (2026-10-25; footer.php says why): short columns in the body face, the
   reversed wordmark across the page's frame as the one bold move, the facts small under
   a single hairline. Paper on ink, with the magenta only in the brand line's last word
   (large type), the links' hover line and the arrow. */
.site-foot{padding-block:var(--pad-s) var(--space-4);background:var(--canvas);color:var(--text-primary)}
.site-foot__inner{box-sizing:border-box;inline-size:var(--container);margin-inline:auto;row-gap:var(--gap-head)}
.site-foot p,.site-foot address{margin:0;font-style:normal}
.site-foot a{color:var(--text-primary)}

/* The close, on every page but the front page: this page's own sign-off (P1-51) above
   the brand's line, both in the display face, the line's last word in the magenta,
   never under 24px (4.02:1 on ink: large text only). The line leads them, a step under
   the section headings, so the wordmark below stays the one bold move. */
.site-foot__close{display:grid;gap:var(--space-2);align-content:start;min-inline-size:0}
.site-foot__signoff{max-inline-size:24ch;font-family:var(--font-display);font-size:clamp(1.5rem, 1.1rem + 1.2vw, 2.25rem);line-height:1.05;letter-spacing:-.02em;text-wrap:balance}
.site-foot__sign{max-inline-size:14ch;font-family:var(--font-display);font-size:clamp(2rem, 1.3rem + 2.4vw, 3rem);line-height:1;letter-spacing:-.02em;text-wrap:balance}
/* Never wider than its column: "remastered." is 6.42em, so the line keeps to 1/6.6 of
   the close's width (its size container). At 200% text on a 390 phone it was 64px and
   ran 53px off the screen; at 100% text it binds nowhere from 320 to 2560. */
.site-foot__close{container-type:inline-size}
.site-foot__sign{font-size:min(clamp(2rem, 1.3rem + 2.4vw, 3rem), 100cqi / 6.6)}
.site-foot__sign em{font-style:normal;color:var(--accent)}

/* The columns: a heading each, small in the display face and the softer paper, over
   short lists of links in the body face, each a 44px target. Under a heading in a list
   of its own a link needs no resting line; the magenta line draws in on hover, and the
   letters roll (P6); "Get in touch", the page's one action, in bold. Below 64em the pages run in two columns of their own where both
   fit at the reader's text size (7.5em each: two at 320px, one at 200% text on a phone,
   where two broke "How it works" over two lines), so the footer stays short; the email
   breaks after its @ only where a column is too narrow. */
.site-foot__cols{display:grid;gap:var(--space-4) var(--gutter);align-content:start;min-inline-size:0}
.site-foot__head{margin:0 0 .5rem;font-family:var(--font-display);font-size:1rem;font-weight:400;line-height:1.2;letter-spacing:-.01em;color:var(--text-secondary)}
.site-foot__list{list-style:none;margin:0;padding:0}
.site-foot__list li{break-inside:avoid}
.site-foot__list a{display:inline-flex;align-items:center;gap:.4em;min-block-size:44px;font-size:clamp(1.0625rem, .98rem + .3vw, 1.25rem);font-weight:500;line-height:1.3}
/* The email breaks after its @, and anywhere only as the last resort, as the menus' does:
   at 200% text on a 320 phone "remasterstudio.co.uk" alone ran 30px off the screen. */
.site-foot__list a{overflow-wrap:anywhere}
.site-foot__list .text-link::before{display:none}
/* The page you are on holds the hover's line, as the nav's link does (P1-31). */
.site-foot__list .text-link[aria-current="page"]::after{transform:scaleX(1)}
.site-foot__list .site-foot__go{font-weight:700}
.site-foot__tel a{font-variant-numeric:tabular-nums}
.site-foot__arrow{flex:none;inline-size:.9em;block-size:.9em;color:var(--accent);transition:transform var(--t-hover) var(--ease-out)}
@media (hover:hover){
  .site-foot__go:hover .site-foot__arrow{transform:translateX(3px)}
}
@media (max-width: 63.99em){
  .site-foot__col:first-child:not(:only-child) .site-foot__list{columns:2 7.5em;column-gap:var(--gutter)}
}

/* The wordmark (footer/statement, the one exception to the 6rem display cap,
   page-design.md section 5): the picture across the frame, its glyphs on the frame's
   edges. Its file has 16 units of ink round 618.3 by 150 of letters (measured), so the
   picture is drawn that much larger and pulled out by it on every side, and its box is
   the letters' own. It rises once from that box as it comes into view (P1's slot rise,
   at the picture's size: 1.1s on the arrival curve, transform only), from core.js's .in;
   the start state is only under .js and with motion allowed (T-052), so reduced motion
   and no script see it in place. */
.site-foot__mark{grid-column:1 / -1;min-inline-size:0;overflow:hidden;overflow:clip}
.site-foot__mark img{display:block;inline-size:calc(100% * 650.3 / 618.3);max-inline-size:none;block-size:auto;margin:calc(-100% * 16 / 618.3)}
@media (prefers-reduced-motion: no-preference){
  .js .site-foot__mark:not(.in) img{transform:translateY(100%)}
  .js .site-foot__mark.in img{animation:foot-rise 1.1s var(--ease-arrive) both}
}
@keyframes foot-rise{from{transform:translateY(100%)}to{transform:none}}

/* The bottom row: the facts (phase 07), small, and the symbol home, under the hairline
   the wordmark stands on. Each fact prints only once it is real (footer.php). One under
   another on a phone; from 48em along one line, wrapping, with the symbol at its end. */
.site-foot__base{grid-column:1 / -1;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--space-3);margin-block-start:calc(var(--space-3) - var(--gap-head));padding-block-start:var(--space-3);border-block-start:1px solid var(--rule-ink);font-size:var(--fs-small);line-height:1.6;color:var(--text-secondary)}
.site-foot__legal{display:grid;gap:.25rem;text-wrap:pretty}
.site-foot__legal a{display:inline-flex;align-items:center;min-block-size:44px}
.site-foot__home{display:inline-flex;border-radius:6px}
.site-foot__home img{display:block;block-size:2.75rem;inline-size:auto}
.site-foot .figure{font-variant-numeric:tabular-nums}
@media (min-width: 48em){
  .site-foot__close,.site-foot__cols{grid-column:1 / -1}
  .site-foot__cols{grid-template-columns:repeat(2,minmax(0,1fr))}
  .site-foot__base{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center}
  .site-foot__legal{display:flex;flex-wrap:wrap;align-items:center;gap:.25rem var(--space-3)}
}
/* From 64em "Talk to us" starts on column 9 on every page, four columns wide, so the
   email stays on one line from 1024px. The columns sit on the page's own grid
   (subgrid; a browser without it keeps the two equal halves above). Beside the close
   (1 to 5) "Pages" takes 6 to 8; on the front page, which has no close, it takes the
   start of the row instead, its links in two columns, so the row is held at both ends
   of the frame and not left empty for half its width. */
@media (min-width: 64em){
  .site-foot__close{grid-column:1 / 6}
  .site-foot__cols{grid-column:6 / -1}
  .site-foot__cols:first-child{grid-column:1 / -1}
  .site-foot__cols:first-child > .site-foot__col:first-child:not(:only-child) .site-foot__list{columns:2 7.5em;column-gap:var(--gutter)}
}
@supports (grid-template-columns: subgrid){
  @media (min-width: 64em){
    .site-foot__cols{grid-template-columns:subgrid}
    .site-foot__cols > .site-foot__col:first-child:not(:only-child){grid-column:1 / 4}
    .site-foot__cols > .site-foot__col:last-child:not(:only-child){grid-column:4 / -1}
    .site-foot__cols:first-child > .site-foot__col:first-child:not(:only-child){grid-column:1 / 7}
    .site-foot__cols:first-child > .site-foot__col:last-child:not(:only-child){grid-column:9 / -1}
  }
}
@media (prefers-reduced-motion: reduce){
  .js .site-foot__mark img{animation:none;transform:none}
}

/* The nav: the About page's link makes a sixth item there, one more than page-design.md
   allows (Stanley's call, D-17). With a real-length phone number in place of [PHONE]
   ("01843 000000") six no longer fit below 75em: the labels broke onto two lines at
   1120 to 1160px, and the number ran past the pill's edge and off the screen at 1024 to
   1064px. There the bar and the pill draw their items closer, and the links and the
   number step down to 16px, each on one line (measured every 8px from 1024 to 1440, bar
   and pill: nothing wraps, overlaps or leaves the nav). */
@media (min-width: 64em) and (max-width: 74.99em){
  .site-nav,.site-nav__links:not([hidden]),.site-head.is-fixed .site-nav{gap:.75rem}
  .site-nav__links a,.site-nav__tel{font-size:1rem;white-space:nowrap}
  /* The tight gaps are for a real number in the bar; with no number the links keep their
     room (audit, 2026-10-25: 12px apart at 1024). */
  .site-nav:not(:has(.site-nav__tel)) .site-nav__links:not([hidden]){gap:clamp(1.25rem, .5rem + 1.6vw, 2.25rem)}
}

/* ==== Page transitions: the curtain between pages (D-46) ========================== */
/* Stanley asked for the page change to "do the cool animation but then also say, in our
   style, what page it is changing to" (2026-10-22, after Huge's site; it replaces the
   window of 2026-10-19). Following a link to another page of the site: an ink curtain
   sweeps up over the page being left, its top edge a wide arc (.45s); it holds for a
   beat (.35s) with the new page's name in the middle, in the display face, paper, its
   full stop in the magenta (the brand's one accent, at 24px and up only: 4.02:1 on
   ink), the reversed RS symbol over it; then it lifts off the top, its bottom edge the
   arc the other way (.5s), and the new page is there. Huge's pace. The browser starts
   it only once the new page is ready to draw, so nothing waits on it.
   How (cross-document view transitions; no script needed for the page to work):
   - The opt-in, @view-transition, is the one line not here: header.php prints it inline
     in every page's head, inside (prefers-reduced-motion: no-preference), so under
     reduced motion there is no transition and no curtain. In this file it was not
     reliable: the front page is long enough to be painted before it has finished
     parsing, and Chromium then revealed it with no transition (8 runs in 8 on the local
     site, 2026-10-19, read from pagereveal); with the opt-in inline in the head it ran 5
     in 5, and holding render until #main instead ran 1 in 5.
   - Every page but the not-found one prints a hidden .curtain holding its own name
     (header.php). The cue in its head hears pagereveal and, only when a link of this
     site brought the visitor here, gives the transition the type "curtain"; anything
     else it skips. While a "curtain" transition runs, the curtain shows, named
     "curtain", in the new page's snapshot only, so the browser runs it as an arriving
     element over the old page's snapshot: the browser runs the whole pass (up, hold,
     off) on that snapshot with no script and no class to change, and the curtain is
     hidden again in the very frame the transition ends
     (:active-view-transition-type stops matching), so it can never stay down over a
     page or block a click. The old page's snapshot never moves; the new one (the live
     page) waits hidden until the ink covers the screen, and the lift uncovers it.
   - The lift is an animation of its own (curtain-lift, which waits out the sweep and
     the hold), so its start is an event the cue hears (animationstart, on <html>): the
     front page's film waits for it (hero-site.js), so the visitor sees the symbol's
     whole drop rather than its end, half of it played under the ink (review finding
     film-under-ink, 2026-10-22). The lift fills forwards only, so until it starts the
     sweep's end holds the curtain down.
   - Everything else (a reload, back and forward, a new tab, a download, a same-page
     anchor, the first visit, JavaScript off, a browser without cross-document
     transitions or their types) has no transition or no curtain, and the page simply
     shows: with no curtain, neither snapshot animates, so the change is instant.
   - The arcs are the curtain's own shape, not a clip that changes: a box 20% wider than
     the screen with elliptical ends (border-radius 50% across), so across the screen
     each end is a shallow arc --sag deep, never a semicircle (11% of the screen's
     height, at most 7% of its width, so a phone's arc is as shallow as a desktop's: at
     14% a phone's was nearly a dome). The curtain is the screen's height plus a --sag
     above and below, its sides clipped at the screen's edges, so at rest it covers the
     screen whatever the arcs, and only transform moves it: from translateY(100%) to
     none, and on to translateY(-100%), each leg on --ease-in-out's curve. The name
     rises into its place as the ink lands (the windows' short rise, --rise-y on a long
     tail) and rides off with it.
   - The live curtain is display: none (and [hidden]) except during that transition,
     takes no pointer event and sits above everything (the cursor's bubble is 100): no
     layout, so nothing on the page shifts.
   The symbol is the file the footer shows, loaded eagerly there, so it is in hand when
   a link is followed; its own ink ground matches --canvas, so it sits on the ink
   without an edge. */
.curtain{--sag:min(11vh, 7vw);position:fixed;top:calc(var(--sag) * -1);left:0;z-index:200;display:none;place-items:center;inline-size:100%;block-size:calc(100vh + 2 * var(--sag));block-size:calc(100lvh + 2 * var(--sag));overflow-x:clip;pointer-events:none}
.curtain::before{content:"";position:absolute;inset:0 -10%;background:var(--canvas);border-radius:50% / calc(var(--sag) * 2.236)}
.curtain__name{position:relative;display:grid;justify-items:center;gap:var(--space-2);margin:0;padding-inline:var(--margin);font-family:var(--font-display);font-size:clamp(1.5rem, 1.1rem + 1.6vw, 2.5rem);line-height:1.1;letter-spacing:-.02em;color:var(--paper);text-align:center;text-wrap:balance}
.curtain__name::before{content:"";inline-size:1.6em;aspect-ratio:328 / 288;background:url("../logo/symbol-reversed.svg") 50% 50% / contain no-repeat}
.curtain__word{display:block}
.curtain__word::after{content:".";color:var(--accent)}
:root:active-view-transition-type(curtain) .curtain{display:grid;view-transition-name:curtain}
:root:active-view-transition-type(curtain) .curtain__name{animation:curtain-name .45s var(--ease-arrive) .2s both}
/* The browser's own cross-fade blends the two snapshots with plus-lighter and fades
   them; here neither moves, and under the curtain the new one waits until the ink is
   up (step-end: hidden for the sweep, then there). */
::view-transition-old(root),::view-transition-new(root){animation:none;mix-blend-mode:normal}
:root:active-view-transition-type(curtain)::view-transition-new(root){animation:curtain-under .45s step-end both}
::view-transition-new(curtain){animation:curtain-up .45s var(--ease-in-out) both,curtain-lift .5s var(--ease-in-out) .8s forwards}
@keyframes curtain-up{from{transform:translateY(100%)}to{transform:none}}
@keyframes curtain-lift{from{transform:none}to{transform:translateY(-100%)}}
@keyframes curtain-under{from{opacity:0}to{opacity:1}}
@keyframes curtain-name{from{opacity:0;transform:translateY(var(--rise-y))}to{opacity:1;transform:none}}

/* ==== Reduced motion: every final state at once (engine.css already zeroes every
   duration; these make the finished states explicit). The services wheel lies flat as
   a stack above, upright and undealt: its query and the deal's both ask for motion. */
@media (prefers-reduced-motion: reduce){
  .js .rise .word__in,.js .rise.in .word__in{animation:none;transform:none}
  .js [data-list] > :not(ul),.js [data-list] > ul > li,.js [data-list].in > :not(ul),.js [data-list].in > ul > li{animation:none;opacity:1;transform:none}
  .js [data-reveal] > .lift{opacity:1;transform:none;transition:none}
  .js [data-reveal] > .lift > .window{transform:translate(calc(var(--plate) * -1),calc(var(--plate) * -1));transition:none}
  .js[data-menu-open] .site-nav__links > li{animation:none}
  .site-head.is-fixed .site-nav{animation:none}
  .site-nav__arrow,.site-foot__arrow{transition:none}
  .faq__pill[open] > .faq__a{animation:none}
  .faq__pill,.faq__plus,.text-link::after{transition:none}
}

/* ==== The pointer's bubble over pictures (Stanley, 2026-10-20; inc/cursor.php) ======= */
/* One for the page, made by site.js (bootCursor) for a mouse or trackpad only. The kit
   in small: a paper circle in the ink outline on its solid magenta plate, one ink word in
   the display face (ink on paper, 15.9:1; never ink on the magenta, 4.02:1). A longer
   word Sam writes stretches it into a pill rather than spilling out. It never takes a
   pointer event, so everything under it works as before, and it sits above everything,
   the nav's pill included. Only transform and opacity move: the place is a transform
   site.js sets each frame, and it grows in from the corner nearest the pointer's tip
   (.is-left, .is-up), or from the middle of its near side when it sits level beside the
   tip to keep off a link (.is-mid, Sam's item 8), in .22s and goes in .12s, on the out
   curve with no overshoot.
   Under reduced motion it simply appears and goes. */
.cursor{position:fixed;top:0;left:0;z-index:100;pointer-events:none;transform:translate3d(var(--cx, -200px),var(--cy, -200px),0)}
.cursor__ball{display:grid;place-items:center;box-sizing:border-box;min-inline-size:4.75rem;block-size:4.75rem;padding:0 .5rem;font-family:var(--font-display);font-size:.9375rem;line-height:1;letter-spacing:-.01em;white-space:nowrap;color:var(--ink);background:var(--paper);border:var(--line) solid var(--ink);border-radius:999px;box-shadow:var(--plate-sm) var(--plate-sm) 0 var(--accent);opacity:0;transform:scale(.4);transform-origin:0 0;transition:opacity .12s var(--ease-out),transform .12s var(--ease-out)}
.cursor.is-left .cursor__ball{transform-origin:100% 0}
.cursor.is-up .cursor__ball{transform-origin:0 100%}
.cursor.is-left.is-up .cursor__ball{transform-origin:100% 100%}
.cursor.is-mid .cursor__ball{transform-origin:0 50%}
.cursor.is-left.is-mid .cursor__ball{transform-origin:100% 50%}
.cursor.is-on .cursor__ball{opacity:1;transform:none;transition-duration:.22s}
/* The system pointer hides only while the bubble shows over this picture. */
.is-cursor,.is-cursor *{cursor:none}
@media (prefers-reduced-motion: reduce){
  .cursor__ball{transform:none;transition:none}
}

/* The front page's way on to the Our work page, under the hand of cards (recent-work.php):
   the lead's size, in ink, centred under the hand, its arrow leaning on toward the page
   under a mouse as the How it works link's does. In .work__body, so it arrives with the
   cards (P2) and needs no start state of its own. */
.work__body > .work__page{justify-self:start;margin:0 var(--frame-inline);font-size:var(--fs-lead)}
.work__page-go{display:inline-flex;align-items:center;gap:.4em;min-block-size:44px;font-weight:700;color:var(--ink)}
.work__page-arrow{flex:none;inline-size:1em;block-size:1em;transition:transform var(--t-hover) var(--ease-out)}
@media (hover:hover){
  .work__page-go:hover .work__page-arrow{transform:translateX(3px)}
}
@media (prefers-reduced-motion: reduce){
  .work__page-arrow{transition:none}
}

/* The cookie choice (D-86, inc/consent.php): a small card on the ink at the screen's foot,
   right on a wide screen, across it on a phone, never over the whole page. Accept and
   Reject the same size side by side: refusing as easy as accepting (phase 07). */
.consent{position:fixed;z-index:70;right:1rem;bottom:1rem;box-sizing:border-box;inline-size:min(25rem, calc(100vw - 2rem));padding:1rem 1.125rem 1.125rem;color:var(--paper);background:var(--ink);border:var(--line) solid var(--ink);border-radius:var(--radius-win);box-shadow:var(--plate-sm) var(--plate-sm) 0 var(--accent);font-size:.9375rem;line-height:1.45}
.consent[hidden]{display:none}
.consent__text{margin:0 0 .875rem}
.consent__text a{color:inherit;font-weight:700;text-decoration:underline;text-underline-offset:.15em}
.consent__do{display:grid;grid-template-columns:1fr 1fr;gap:.625rem}
.consent__btn{min-block-size:44px;padding:.5rem 1rem;font:inherit;font-weight:700;color:var(--ink);background:var(--paper);border:2px solid var(--paper);border-radius:999px;cursor:pointer}
.consent__btn:hover{background:#FFFFFF}
/* Accept in the magenta (Sam, 2026-10-29, D-88: "make it clear that accept is default
   option"), still the same size and place as Reject. Sam's call against the ICO's
   "equally prominent" guidance and phase 07, made knowing the risk. */
.consent__btn[data-consent-yes]{color:var(--on-accent);background:var(--accent);border-color:var(--accent)}
.consent__btn[data-consent-yes]:hover{background:var(--accent-hover);border-color:var(--accent-hover)}
.consent__btn:focus-visible,.consent__text a:focus-visible{outline:3px solid var(--accent);outline-offset:2px}
@media (max-width: 30rem){
  .consent{right:.625rem;bottom:.625rem;inline-size:calc(100vw - 1.25rem)}
}
.site-foot__consent{min-block-size:44px;padding:0;font:inherit;color:inherit;background:none;border:0;text-decoration:underline;text-underline-offset:.15em;cursor:pointer}
.site-foot__consent[hidden]{display:none}

