/* ===========================================================================
   MAIN. Consumes tokens.css and nothing else. No colour literal appears
   below this line except transparent and currentColor.

   Structure: a shared role layer (wrap, eyebrow, h2, lead, btn, reveal),
   then one block per section, scoped under that section's id.
   ======================================================================== */

*, *::before, *::after { box-sizing: border-box; }

/* The horizontal guard lives here and ONLY here. The root's overflow
   propagates to the viewport, so this alone stops sideways scrolling. The
   same declaration on `body` does something different and much worse: it
   makes body itself a scroll container whose scrollHeight equals its
   clientHeight, which silently kills `position: sticky` on the masthead,
   because sticky is resolved against the nearest scrollport and that box
   never scrolls. It shipped that way once. Do not put it back. */
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  overflow-x: hidden;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
/* The masthead is sticky, so an anchor jump or a focused control must stop
   below it rather than under it; on a phone the builder's pinned bar covers
   the foot of the screen in the same way. */
html { scroll-padding-top: 5.5rem; }
@media (max-width: 61.99em) { html { scroll-padding-bottom: 6.5rem; } }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-text);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  font-synthesis-weight: none;
}

img { max-width: 100%; display: block; }

::selection { background: var(--accent-deep); color: var(--paper); }

/* ------------------------------------------------------------ focus ---- */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
  border-radius: var(--r-1);
}
:focus:not(:focus-visible) { outline: none; }

.skip {
  position: fixed;
  top: var(--s-3);
  left: var(--s-3);
  z-index: var(--z-skip);
  padding: var(--s-3) var(--s-5);
  background: var(--accent-deep);
  color: var(--paper);
  font-family: var(--font-text);
  font-size: var(--t-small);
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-160%);
}
.skip:focus { transform: none; }

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

/* ----------------------------------------------------------- roles ----- */
.wrap {
  width: 100%;
  max-width: var(--spread);
  margin-inline: auto;
  padding-inline: var(--edge);
}

.eyebrow {
  margin: 0 0 var(--s-5);
  font-size: var(--t-eyebrow);
  font-weight: 600;
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--quiet);
}

.h1, .h2, .h3 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: var(--track-display);
  color: var(--fg);
  margin: 0;
  text-wrap: balance;
}
.h1 { font-size: var(--t-display); line-height: var(--lh-display); }
.h2 { font-size: var(--t-h2); line-height: var(--lh-heading); }
.h3 { font-size: var(--t-h3); line-height: var(--lh-heading); font-weight: 700; }

/* The one accent word per page. Noto Sans JP has no true italic, and a browser
   synthesised oblique on a geometric sans looks like a rendering fault, so the
   emphasis is carried by weight and colour. <i> is kept as the element because
   it is still the correct semantics for a word set apart from its neighbours. */
.h1 i, .h2 i {
  font-style: normal;
  font-weight: 800;
  color: var(--accent);
}

.lead {
  font-size: var(--t-lead);
  line-height: 1.55;
  color: var(--fg);
  max-width: var(--measure);
  margin: var(--s-5) 0 0;
}

/* The two-tone lead: first sentence in ink, the rest in quiet, hierarchy
   inside a paragraph without a second face. ONE rule and one span per lead;
   no section gets its own version. The quiet inks in tokens.css hold 7:1 or
   better on every ground a lead is rendered on, which is what lets the quiet
   half sit on the purchase path at all. */
:is(.lead, .buy__lead) > span { color: var(--quiet); }

/* One icon style, drawn by hand: 24x24, 2px stroke, squared caps, 45/90
   angles and quarter arcs, currentColor throughout. Source of record with
   the grid rules is assets/icons/, one file per glyph; each use is inlined.
   Icons appear only where they carry information: the product cards, the
   steps, and the dashboard section. Accent-deep reads 12.76:1 on paper and
   11.70:1 on paper-2, far past the 3:1 of SC 1.4.11. */
.ic {
  display: inline-block;
  width: 24px;
  height: 24px;
  color: var(--accent);
}
.h3 .ic {
  vertical-align: -0.22em;
  margin-right: var(--s-3);
}

.copy { max-width: var(--measure); color: var(--quiet); margin: 0; }

/* A visible operator slot. It should look deliberate, not like a mistake, and
   it may never look like the one thing on the line that CAN be pressed.

   IT IS NO LONGER THE LINK COLOUR. This set --accent, and the rule seven
   lines below sets that same token on every <a> on the site, so a bracket was
   painted in the link colour and underlined while the live link beside it was
   plain ink: measured in the masthead, "[set your phone]" came out
   rgb(63,28,94) underlined and "Buy" came out rgb(27,26,23) with no rule. The
   dead thing outranked the live thing, in a header that holds two items on a
   phone. The hero said it twice more, in the sentence whose whole job is to
   make a stranger trust a person, as two purple underlined phrases that read
   as two broken links.

   Quiet ink and the dotted rule say deliberate without borrowing the
   affordance, at 8.16:1 on paper. tools/contrast.mjs measures it on both
   grounds a slot renders on. */
/* A phone number inside a pill that wraps keeps its digits together. At 375
   the two call pills broke "(720) 916-" from "1357": .slot carried nowrap
   while the number was a bracket, and filling it in dropped the span. The
   pill's label is wrapped in ONE span, because .btn is inline-flex and a bare
   .nowrap child would become a second flex item beside the words. */
.nowrap { white-space: nowrap; }

.slot {
  font-style: normal;
  color: var(--quiet);
  border-bottom: 1px dotted currentColor;
  white-space: nowrap;
}

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

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  padding: 0.9rem 1.6rem;
  border-radius: var(--r-pill);
  font-family: var(--font-text);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: 0.01em;
  text-decoration: none;
  border: var(--hair) solid transparent;
  cursor: pointer;
  transition: background-color var(--dur-fast) ease,
              color var(--dur-fast) ease,
              border-color var(--dur-fast) ease,
              transform var(--dur-fast) ease;
}
.btn--fill {
  background: var(--accent-fill);
  color: var(--accent-fill-fg);
}
.btn--fill:hover { transform: translateY(-1px); background: var(--fg); }
.btn--line {
  background: transparent;
  color: var(--fg);
  border-color: var(--rule-strong);
}
.btn--line:hover { border-color: var(--fg); transform: translateY(-1px); }
.btn--big { padding: 1.05rem 1.9rem; font-size: 1rem; }

/* An action whose destination has not been set yet. It keeps the weight of
   the real button, because the buying door is the loudest thing on the page
   and removing it would redraw the layout twice, but it is NOT a control:
   no href, no cursor, no hover lift, nothing in the tab order that could be
   activated to no effect. The bracket inside says why, in the same voice as
   every other unset slot on the site, and it takes the button's own colour
   so the contrast is the button's own rather than the accent on a fill.

   The bracket is not optional decoration. This rule shipped once against
   markup that had no .slot inside it, and the result was three full weight
   accent pills, indistinguishable from the live button above them, that did
   nothing at all when pressed. A dead control that looks alive is worse than
   no control. index.html carries the bracket and aria-disabled now, and
   tools/stripe-links.mjs writes both back on --unset. */
.btn--unset {
  cursor: default;
  /* AND IT MUST NOT LOOK LIKE THE LIVE ONE.
     The rule above already said, in prose, that a dead control looking alive
     is worse than no control - and then set only the cursor, so the four buy
     rows kept btn--fill and rendered as full weight accent pills. At 375px
     they were indistinguishable from a live purchase button, and a tap did
     nothing at all: no message, no alternative, on the four highest intent
     controls on the site. The bracket was doing the whole job on its own, in
     small type, at the end of the label.
     So the fill comes off. It reads as what it is: the shape of a control
     that is not wired yet, with the reason written inside it. Putting the
     link back through tools/stripe-links.mjs removes this class and the pill
     returns to full weight, which is the point of doing it here rather than
     in the markup. */
  background: transparent;
  color: var(--quiet);
  border-color: var(--rule-strong);
  box-shadow: none;
  font-weight: 400;
}
.btn--unset:hover { transform: none; background: transparent; }
.btn--unset .slot { color: inherit; border-bottom-color: currentColor; }

/* the same idea in running text: a label with no destination yet */
.unset { color: var(--quiet); }

/* a plain text link that underlines from the baseline. Same grown target
   as the mast links: the text is the design, the pseudo is the touch area.

   nowrap because the pseudo takes its box from ONE fragment of a wrapped
   inline: two lines and the grown target silently vanishes. Costs nothing in
   layout, unlike inline-block. Buys a constraint: a tlink phrase must fit one
   line of the narrowest column (280px at 320) or it overflows. */
.tlink {
  color: var(--fg);
  text-decoration: none;
  border-bottom: var(--hair) solid var(--rule-strong);
  padding-bottom: 2px;
  transition: border-color var(--dur-fast) ease, color var(--dur-fast) ease;
  position: relative;
  white-space: nowrap;
}
.tlink::after {
  content: "";
  position: absolute;
  inset: -0.5rem 0;
}
.tlink:hover { color: var(--accent); border-color: var(--accent); }

/* ------------------------------------------------------------ texture ----
   The site's ONE depth treatment: a hairline grid drawn with two repeating
   gradients, so it costs no request and no bytes worth measuring. It is used
   in exactly two places, the ground of the system section and the mat the
   console screenshot is framed on, and it is masked to a soft field so it
   never reads as a rectangle laid over the page.

   isolation + z-index -1 keeps it above the element's own background and
   below every child, so no text is ever composited underneath it. The alpha
   is held at 0.055 in tokens.css because that is the darkest it can be while
   ink-quiet, the lightest text that ever sits on it, still measures 6.67:1
   against a grid line. `node tools/contrast.mjs` reproves that on demand. */
.tx { position: relative; isolation: isolate; }
.tx::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(to right, var(--tx) 0 var(--hair), transparent var(--hair) var(--tx-pitch)),
    repeating-linear-gradient(to bottom, var(--tx) 0 var(--hair), transparent var(--hair) var(--tx-pitch));
  -webkit-mask-image: radial-gradient(120% 96% at 50% 4%, black 30%, transparent 84%);
  mask-image: radial-gradient(120% 96% at 50% 4%, black 30%, transparent 84%);
}

/* ---------------------------------------------------------- reveals ----
   Rest state is the finished state. The initial offset is only ever applied
   when the script has confirmed it is running, so with JavaScript off, or
   under reduced motion, every word below is already on the page. */
.js .reveal { opacity: 0; transform: translateY(30px); }
.js .reveal.is-in {
  opacity: 1;
  transform: none;
  transition: opacity var(--dur-enter) ease, transform var(--dur-enter) var(--ease-out);
  transition-delay: calc(var(--d, 0) * var(--stagger));
}
@media (prefers-reduced-motion: reduce) {
  .js .reveal { opacity: 1; transform: none; transition: none; }
}

section { padding-block: var(--section-pad); }

/* ------------------------------------------------------------- atmos ----
   The optional warmth layer: six generated still-life bands, section
   dividers, OFF by default. The whole switch is one attribute on <body>:
   without data-atmos="on" the bands have no box, are never painted, and
   their lazy images are never fetched, so the page is the evidence recipe
   untouched. With it, they render full bleed, still, with honest alt text.
   They are photographs of nothing this business did, and the alt text on
   each one says so before it says anything else. */
.atmos { display: none; }
body[data-atmos="on"] .atmos {
  display: block;
  height: clamp(10rem, 26vh, 15rem);
}
body[data-atmos="on"] .atmos img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ==================== mast: the header ================================== */
.mast {
  position: sticky;
  top: 0;
  z-index: var(--z-head);
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: var(--hair) solid var(--rule);
}
.mast__in {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3) var(--s-4);
  min-height: 4.25rem;
}
/* ---- the signature ------------------------------------------------------
   Page 8 of the standards master: two SEPARATE ADJACENT assets. The mark is
   the link and carries the accessible name; the wordmark beside it is a
   decorative image with an empty alt. Nothing nests and nothing overlaps.

   One number sizes all of it. --lockup-u is what a single unit of the
   master's own artwork is worth in CSS px, and every length below is that
   unit times a dimension measured off the master by tools/logo_geometry.py.
   Nothing here is a round number somebody liked: at --lockup-u 0.2678 the
   mark renders 28.00 x 32.00, the gap 11.81, the wordmark 114.75 x 18.48 and
   the whole lockup 154.56, which clears page 7's two floors (16px on the
   full-colour mark, 140px on the horizontal lockup) at every width.

   The COLOUR is not here and must not be. Page 9 forbids recolouring the
   logotype or rebuilding its accent in CSS, so both files carry their own
   fills and arrive through <img>, where a custom property cannot reach them.
   That is the structural version of the rule, and it is why the two coloured
   spans that used to sit in the masthead are gone.                          */
.mast__brand {
  display: flex;
  align-items: center;
  gap: calc(var(--lockup-gap) * var(--lockup-u));
}
.mast__mark {
  display: block;
  position: relative;
  line-height: 0;
}
.mast__mark img {
  display: block;
  width: calc(var(--mark-w) * var(--lockup-u));
  height: calc(var(--mark-h) * var(--lockup-u));
}
.mast__wm {
  display: block;
  width: calc(var(--wm-w) * var(--lockup-u));
  height: calc(var(--wm-h) * var(--lockup-u));
}
/* The 44px target and the visible focus ring are THE SAME BOX, which is the
   only way to be sure the thing a keyboard user sees is the thing a thumb
   hits. The growth is derived rather than typed: whatever the mark is drawn
   at, the pseudo-element takes it out to exactly 44 x 44. Measured with
   elementFromPoint at the four corners, not with getBoundingClientRect.

   It grows 8px to the right and the wordmark begins 11.81px away, so the
   target stops 3.81px short of the adjacent asset. Page 8's "no overlapping
   links" is held by geometry, not by hoping.                                */
.mast__mark::after {
  content: "";
  position: absolute;
  inset: calc((44px - var(--mark-h) * var(--lockup-u)) / -2)
         calc((44px - var(--mark-w) * var(--lockup-u)) / -2);
}
/* SIGNAL BLUE, and this is the one thing on the marketing site wearing it.
   The master labels that colour UI ONLY and page 8 spends it on exactly this:
   the keyboard focus indicator on the linked mark. 5.84:1 on paper, against
   the 3:1 of SC 1.4.11. */
.mast__mark:focus-visible { outline: none; }
.mast__mark:focus-visible::after { outline: 2px solid var(--brand-signal); }
.mast__nav {
  display: flex;
  align-items: center;
  gap: clamp(var(--s-4), 3vw, var(--s-6));
  font-size: var(--t-small);
}
.mast__nav a {
  color: var(--ink);
  text-decoration: none;
  border-bottom: var(--hair) solid transparent;
  padding-bottom: 2px;
  transition: border-color var(--dur-fast) ease;
  /* The visible link stays a text line; the TARGET is grown to the 44px
     floor by a pseudo element, so the design does not change and an older
     thumb still cannot miss. The gap tokens keep neighbours separated. */
  position: relative;
}
/* A padded inset alone is not a floor, it is a bet on the label being long
   enough, and the shortest label on this page loses the bet. Measured with
   elementFromPoint on a served page: "Work" and "Prices" cleared 44 on their
   own text width, "Buy" was 33px at 390 and 36px at 1280, and it is the ONE
   nav item that survives the 32em drop, so on a phone it was the only nav
   target there was. A min-width on the pseudo, centred on the text, makes
   the floor a floor rather than a coincidence of copy. It costs nothing
   visually and it survives the next rename. Checked for overlap against the
   grown boxes, not the visible ones: the nav gap clears it at every width. */
.mast__nav a::after {
  content: "";
  position: absolute;
  top: -0.8rem;
  bottom: -0.8rem;
  left: 50%;
  transform: translateX(-50%);
  width: calc(100% + 0.6rem);
  min-width: 44px;
}
.mast__nav a:hover { border-color: var(--accent); }

/* Click to call is in the header at EVERY width, phones included. It shipped
   hidden below 46em, which left a phone visitor with no tel: control between
   the hero and the contact section: 4,726px, about six screens, on a page
   whose promise is "I answer the phone". A sticky number is the conversion
   floor for a local service site and it is not the thing that gets dropped
   when the line is short. */
.mast__tel { display: inline; }

/* What gets dropped instead, measured rather than guessed. At 375px the line
   is 337px wide and holds a 155px lockup; the buying door plus the number is
   138px, so the two of them fit with 44px to spare and the header stays one
   row. Everything else goes.

   THE LOCKUP ITSELF NEVER SHRINKS, at any width. It used to: the wordmark was
   type and the type dropped to 0.95rem below 32em. Page 7 of the master puts
   a floor of 140 CSS px under the horizontal lockup and 16 px under the
   full-colour mark, and the lockup is 154.56 px at every viewport this page
   has, so both floors hold on a phone as well as on a desk. If a narrower
   line ever forces the issue, the answer page 2 gives is the standalone mark,
   which is an approved configuration, and not a smaller lockup, which is not.

   Written as "keep the number and the buying door" rather than as a list of
   what to hide, because the three pages carry three different navs (Work,
   Prices, Home) and an enumerated list would have quietly stopped covering
   work.html the day its first item was renamed. Nothing dropped here is the
   only route to itself: the mark is the way home, Prices is an anchor on
   the page you are already on, and Work is linked from the hero
   action and from the footer. The phone was the one that had no second route. */
@media (max-width: 32em) {
  .mast__nav { gap: var(--s-4); font-size: var(--t-micro); }
  .mast__nav a:not(.mast__tel):not([href$="buy-06"]) { display: none; }
}
/* At 320 the number and the buying door no longer both fit beside the
   signature. Buy is a section of a page that scrolls to it; the number is the
   business. */
@media (max-width: 21em) {
  .mast__nav a:not([href$="buy-06"]) { display: none; }
}

/* AND WHILE THE NUMBER IS A BRACKET IT IS NOT THE BUSINESS, IT IS A BRACKET.
   The two rules above trade the links away in favour of the phone, which is
   the right trade the day there is a phone. There is not one yet, and
   .mast__tel is a <span>, so `a:not(.mast__tel)` spared the placeholder and
   hid the three real links: measured at 320x700 on the served page, Work,
   Prices and Buy were all display:none and the entire primary navigation of
   the site was the string "[set your phone]". 320 CSS px is an iPhone SE, and
   any 375pt iPhone with Display Zoom on, which is what a 52 year old turns on.

   So the slot is dropped FIRST at both widths and the buying door holds the
   line at 21em. Filling the number removes the `unset` class, both rules stop
   matching, and the trade above resumes on its own. */
@media (max-width: 32em) {
  .mast__nav .mast__tel.unset { display: none; }
}
@media (max-width: 21em) {
  .mast__nav:has(.mast__tel.unset) a[href$="buy-06"] { display: block; }
}

/* ==================== hero-01: promise, action, one real site =========== */
#hero-01 {
  padding-top: clamp(2.5rem, 7vh, 5rem);
  padding-bottom: var(--section-pad);

  & .hero__in {
    display: grid;
    row-gap: clamp(var(--s-6), 4vw, var(--s-7));
    column-gap: clamp(var(--s-7), 7vw, var(--s-9));
    align-items: center;
  }
  @media (min-width: 62em) {
    & .hero__in { grid-template-columns: minmax(0, 1fr) auto; }
  }

  & .hero__eyebrow { margin-bottom: var(--s-4); }

  /* the operator line: a person, a place, and the promise the buyer is
     actually testing. Body size, full ink, right under the lead. */
  & .hero__op {
    max-width: var(--measure);
    margin: var(--s-4) 0 0;
  }

  & .hero__acts {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-4);
    margin-top: clamp(var(--s-6), 4vw, var(--s-7));
  }

  /* the price chip: quiet, one line, body sized. Quiet ink is 8.16:1 on
     paper, past the 7:1 purchase-path floor. */
  & .hero__price {
    margin: var(--s-4) 0 0;
    font-size: var(--t-body);
    color: var(--quiet);
  }

  /* the platform, cropped, in the first screen. A dark rectangle set straight
     onto warm paper reads as a hole, so it takes the same paper mat and
     hairline the whole screenshot takes further down, at a smaller gauge.

     30rem is a ceiling, not a width: the crop is 687 real pixels and this
     site does not upscale a screenshot. At 30rem it renders at 0.70 of its
     own size, and on a 375px phone at 0.49, so it is sharp at 1x and close
     to sharp at 2x. */
  & .chip {
    margin: clamp(var(--s-6), 4vw, var(--s-7)) 0 0;
    max-width: 30rem;
  }
  /* Square corners, deliberately: radius lives on pill controls and the
     device frame only. The soft shadow stays because this is a photograph
     frame, the one place the visual language grants a shadow. */
  & .chip__mat {
    display: block;
    padding: var(--s-2);
    background: var(--paper);
    border: var(--hair) solid var(--rule-strong);
    box-shadow: var(--shadow-mat);
  }
  & .chip img {
    width: 100%;
    height: auto;
  }
  & .chip figcaption {
    margin-top: var(--s-3);
    font-size: var(--t-micro);
    color: var(--quiet);
  }

  /* the device. This is the page's one signature moment: a real site,
     at a size where its own type and price are legible, scrolling itself
     on hover or keyboard focus. Without JavaScript it is a finished,
     static frame, because the movement is a CSS transition on :hover
     and :focus-visible and nothing else. */
  & .rig {
    --rig-w: clamp(16rem, 76vw, 21rem);
    --rig-h: clamp(25rem, 60vh, 34rem);
    display: block;
    width: var(--rig-w);
    margin-inline: auto;
    text-decoration: none;
    color: inherit;
  }
  & .rig__glass {
    display: block;
    position: relative;
    height: var(--rig-h);
    overflow: hidden;
    border-radius: var(--r-device);
    border: var(--hair) solid var(--rule-strong);
    background: var(--paper-2);
    box-shadow: var(--shadow-rig);
  }
  & .rig__shot {
    width: 100%;
    height: auto;
    transform: translateY(0);
    transition: transform 900ms var(--ease-out);
    will-change: transform;
  }
  & .rig:hover .rig__shot,
  & .rig:focus-visible .rig__shot {
    transform: translateY(calc(-100% + var(--rig-h)));
    transition: transform var(--dur-scroll) linear;
  }
  & .rig:focus-visible .rig__glass { outline: 2px solid var(--focus); outline-offset: 4px; }
  & .rig:focus-visible { outline: none; }

  & .rig__cap {
    display: block;
    max-width: var(--rig-w);
    margin-top: var(--s-4);
    font-size: var(--t-micro);
    color: var(--quiet);
  }
  & .rig__cap b { font-weight: 600; color: var(--ink); }
  & .rig__cap .go { white-space: nowrap; color: var(--accent); }
  & .rig:hover .rig__cap .go { text-decoration: underline; }

  /* TOUCH DEVICES SCROLL IT THEMSELVES. There is no hover on a phone, so the
     frame used to sit frozen while the caption told the visitor to hover: the
     one signature element on the page, dead, on the device most prospects
     arrive with. The glass becomes a real scroller instead, so a thumb flicks
     through the actual build. Dragging scrolls and tapping still opens the
     site, because the browser separates those two gestures for us.

     Deliberately NOT an auto-scrolling loop. WCAG 2.2.2 requires content that
     moves for more than five seconds to be pausable, and a perpetual loop on
     the hero would fail it at Level A on a page whose whole argument is that
     it is legible to a sixty year old. It would also compete with the headline
     during the fifty milliseconds where the appeal verdict is formed. Motion
     the visitor starts is worth more than motion aimed at them. */
  @media (hover: none) {
    & .rig__glass {
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
      overscroll-behavior-y: contain;
      scrollbar-width: none;
    }
    & .rig__glass::-webkit-scrollbar { display: none; }
    /* The transform is the hover mechanism; native scrolling replaces it here
       and the two must not fight over the same pixels. */
    & .rig__shot { transform: none; transition: none; }
  }

  /* The caption names the gesture the device actually has. */
  & .rig__cap .cap-touch { display: inline; }
  & .rig__cap .cap-hover { display: none; }
  @media (hover: hover) {
    & .rig__cap .cap-touch { display: none; }
    & .rig__cap .cap-hover { display: inline; }
  }

  @media (prefers-reduced-motion: reduce) {
    & .rig__shot, & .rig:hover .rig__shot, & .rig:focus-visible .rig__shot {
      transition: none;
      transform: translateY(0);
    }
  }
}

/* ==================== section heads, shared ============================
   Every section opens the same way: eyebrow, heading, one short lead, then
   the same gap to its content. The rhythm walking down the page comes from
   this one rule rather than from each section's own margins. */
.sec__head { max-width: 44rem; }
.sec__head .eyebrow { margin-bottom: var(--s-3); }
.sec__head .lead { margin-top: var(--s-4); }
.sec__head .lead[hidden] { display: none; }

/* ==================== offer-02: build your system =======================
   The parts on the left, the running total on the right, and the right side
   stays on screen while the left is scrolled, so a tick and its effect are
   always in the same view. On a phone the panel follows the rows and a
   sticky bar at the foot of the screen carries the meter and the count while
   the rows are being read. The count and the lit segments are CSS alone
   (a counter and :has()), so they cost the script nothing. */
#offer-02 {
  border-top: var(--hair) solid var(--rule);
  counter-reset: parts;

  & .offer__build {
    display: grid;
    gap: var(--s-6);
    margin-top: clamp(var(--s-6), 4vw, var(--s-7));
  }
  @media (min-width: 62em) {
    & .offer__build {
      grid-template-columns: minmax(0, 1fr) minmax(0, 23rem);
      gap: clamp(var(--s-6), 4vw, var(--s-8));
      align-items: start;
    }
  }

  /* ---- the four rows ---- */
  & .offer__grid { display: grid; border-top: var(--hair) solid var(--rule); }
  & .card {
    position: relative;
    display: grid;
    grid-template-columns: 24px minmax(0, 1fr);
    gap: var(--s-3) var(--s-4);
    padding: var(--s-5) var(--s-3);
    border-bottom: var(--hair) solid var(--rule);
  }
  & .card > .ic { align-self: start; margin-top: 1.55rem; }
  & .card__body > * { margin: 0; }
  & .card__tag { font-size: var(--t-micro); font-weight: 600; color: var(--quiet); }
  & .card .h3 { margin-top: var(--s-1); font-size: clamp(1.25rem, 2vw, 1.5rem); }
  & .card .copy { margin-top: var(--s-2); }
  & .card__end {
    grid-column: 2;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-3) var(--s-4);
  }
  @media (min-width: 40em) {
    & .card { grid-template-columns: 24px minmax(0, 1fr) auto; align-items: center; }
    & .card__end { grid-column: 3; flex-direction: column; align-items: flex-end; }
  }
  & .card__price {
    margin: 0;
    font-family: var(--font-display);
    font-size: 1.25rem;
    font-weight: 700;
    line-height: 1.2;
    color: var(--ink);
    white-space: nowrap;
  }

  /* The pill is the control, and its hit area stretches over the whole row,
     so a tap anywhere on a part adds it. The copy is two lines now, which is
     what makes an unselectable row acceptable. The ticked state is an inset
     shadow, so nothing reflows under the thumb. */
  & .card__add {
    display: inline-flex;
    align-items: center;
    gap: var(--s-2);
    min-height: 44px;
    padding: 0.5rem var(--s-4);
    background: var(--paper);
    border: var(--hair) solid var(--rule-strong);
    border-radius: var(--r-pill);
    font-size: var(--t-small);
    font-weight: 600;
    color: var(--ink);
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
  }
  & .card__add::after { content: ""; position: absolute; inset: 0; }
  & .card__add[hidden] { display: none; }
  & .card__add input:checked { counter-increment: parts; }
  & .add__on { display: none; }
  .js & .card:hover { background: var(--paper-2); }
  @supports selector(:has(*)) {
    & .card:has(.card__add input:checked) { background: var(--paper-2); box-shadow: inset 3px 0 0 var(--accent); }
    & .card__add:has(input:checked) { background: var(--paper-3); box-shadow: inset 0 0 0 2px var(--accent); }
    & .card__add:has(input:checked) .add__off { display: none; }
    & .card__add:has(input:checked) .add__on { display: inline; }
    &:has(#add-site:checked) .pick__meter [data-k="site"],
    &:has(#add-reviews:checked) .pick__meter [data-k="reviews"],
    &:has(#add-social:checked) .pick__meter [data-k="social"],
    &:has(#add-care:checked) .pick__meter [data-k="care"] { background: var(--accent); }
  }

  /* ---- meter and count, used by the bar and the panel ---- */
  & .pick__meter {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--s-1);
  }
  & .pick__meter i { display: block; height: 0.5rem; background: var(--paper-3); }
  @media (prefers-reduced-motion: no-preference) {
    & .pick__meter i { transition: background-color var(--dur-fast) ease; }
  }
  & .pick__count { margin: var(--s-2) 0 0; font-size: var(--t-micro); font-weight: 600; color: var(--quiet); }
  & .pick__n::before { content: counter(parts); }

  /* ---- the phone's sticky bar ---- */
  & .pick__bar {
    position: sticky;
    bottom: 0;
    z-index: 5;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--s-2) var(--s-4);
    margin: calc(var(--s-6) * -1) calc(var(--edge) * -1) 0;
    padding: var(--s-3) var(--edge);
    background: var(--paper);
    border-top: var(--hair) solid var(--rule-strong);
    box-shadow: 0 -0.5rem 1.5rem rgba(27, 26, 23, 0.07);
  }
  & .pick__bar .pick__meter { grid-column: 1 / -1; }
  & .pick__bar .pick__count { margin: 0; }
  & .pick__bar[hidden] { display: none; }
  & .pick__go { display: inline-flex; align-items: center; min-height: 44px; font-size: var(--t-small); font-weight: 700; color: var(--accent); }
  @media (min-width: 62em) {
    & .pick__bar { display: none; }
  }

  /* ---- the panel ---- */
  & .pick__panel {
    padding: var(--s-5);
    background: var(--paper-2);
    border-top: 3px solid var(--accent);
  }
  @media (min-width: 62em) {
    & .pick__panel {
      position: sticky;
      top: 5.5rem;
      max-height: calc(100vh - 6.5rem);
      overflow-y: auto;
    }
  }
  & .pick__q {
    margin: 0 0 var(--s-4);
    font-family: var(--font-display);
    font-size: var(--t-h3);
    font-weight: 700;
    line-height: var(--lh-heading);
    color: var(--ink);
  }
  & .pick { margin-bottom: var(--s-5); }
  & .pick__out {
    display: flex;
    flex-direction: column;
    gap: var(--s-2);
    margin-top: var(--s-4);
  }
  & .pick__sum {
    display: block;
    font-family: var(--font-display);
    font-size: clamp(1.6rem, 3vw, 2.1rem);
    font-weight: 700;
    line-height: 1.15;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
  }
  & .pick__why, & .pick__note { display: block; margin: 0; font-size: var(--t-small); color: var(--quiet); }
  /* A display rule beats the user agent's [hidden], and the script hides these
     by attribute, so each one carries its guard. */
  & .pick__sum[hidden], & .pick__why[hidden], & .pick__note[hidden] { display: none; }
  & .pick .v { font-variant-numeric: tabular-nums; }

  /* A segmented control: three equal segments, the radio itself visually
     hidden and the whole segment its label, so Quarterly fits a 23rem panel
     on one row. Focus still draws on the segment (the :has() ring below). */
  & .pick__term { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-2); margin-top: var(--s-4); }
  & .term input { position: absolute; opacity: 0; width: 1px; height: 1px; }
  & .term {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: 0.5rem var(--s-2);
    background: var(--paper);
    border: var(--hair) solid var(--rule-strong);
    border-radius: var(--r-pill);
    font-size: var(--t-small);
    color: var(--ink);
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
  }
  @supports selector(:has(*)) {
    & .term:has(input:checked) { background: var(--paper-3); box-shadow: inset 0 0 0 2px var(--accent); }
  }

  /* ---- bundles: a price list with no script, shortcuts with one ---- */
  & .plans__q { margin: 0 0 var(--s-2); font-size: var(--t-small); font-weight: 700; color: var(--ink); }
  & .plans { list-style: none; margin: 0; padding: 0; border-top: var(--hair) solid var(--rule); }
  & .plan {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-3);
    min-height: 44px;
    margin-inline: calc(var(--s-3) * -1);
    padding: var(--s-2) var(--s-3);
    border-bottom: var(--hair) solid var(--rule);
  }
  & .plan__name, & .plan__price { margin: 0; font-size: var(--t-body); }
  & .plan__name { font-weight: 600; color: var(--ink); }
  & .plan__price { color: var(--quiet); font-variant-numeric: tabular-nums; white-space: nowrap; }
  & .plan[role="button"] { cursor: pointer; }
  & .plan[role="button"]:hover { background: var(--paper); }
  & .plan:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
  & .plan.is-answer { background: var(--paper); box-shadow: inset 3px 0 0 var(--accent); }
  & .plan.is-answer .plan__price { color: var(--ink); font-weight: 700; }
  & .plans__total { margin: var(--s-4) 0 0; font-size: var(--t-small); color: var(--quiet); }
  & .pick__cta { width: 100%; justify-content: center; margin-top: var(--s-5); }
}

/* ---------------------------------------------------- ticks and radios ----
   HOISTED OUT OF #plans-03, because the checkbox now renders in offer-02 and
   the radio still renders in plans-03, and the alternative was thirty lines
   of drawing code copied into a second block. One rule, two homes.

   The box and the dot are DRAWN rather than typed. A tick character would be
   U+2713, which is outside the unicode-range this page's font subset declares,
   so it would arrive in whatever the system fell back to. Two borders rotated
   45 degrees always look like the same tick.                                */
.pick input,
.card__add input {
  appearance: none;
  -webkit-appearance: none;
  width: 1.35rem;
  height: 1.35rem;
  margin: 0;
  display: grid;
  place-items: center;
  background: var(--paper);
  border: var(--hair) solid var(--rule-strong);
  cursor: pointer;
}
.pick input[type="radio"] { border-radius: var(--r-pill); }
.pick input::after,
.card__add input::after {
  content: "";
  width: 0.62rem;
  height: 0.34rem;
  border-left: 2px solid var(--accent-fill-fg);
  border-bottom: 2px solid var(--accent-fill-fg);
  transform: translateY(-0.1rem) rotate(-45deg);
  opacity: 0;
}
.pick input[type="radio"]::after {
  width: 0.52rem;
  height: 0.52rem;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--accent-fill-fg);
  transform: none;
}
.pick input:checked,
.card__add input:checked { background: var(--accent); border-color: var(--accent); }
.pick input:checked::after,
.card__add input:checked::after { opacity: 1; }

/* THE FOCUS RING GOES ON THE PILL, NOT ON THE 21px BOX INSIDE IT, because the
   pill is what a keyboard is actually moving between.

   AND BOTH HALVES ARE INSIDE @supports selector(:has(*)), which is the fix for
   a live Level AA failure rather than a nicety. The suppression used to be
   unguarded while its replacement sat on a :has() selector, so on Firefox ESR
   115, which ships :has() disabled, the native ring was removed and nothing
   drew one: SC 2.4.7 failing outright on every control in the picker. A
   browser without :has() now keeps its own ring, which is worse looking and
   compliant, and that is the correct direction for the trade. This pass adds a
   second :has() state on .card__add, which is what turns the guard from
   optional into mandatory. */
@supports selector(:has(*)) {
  .pick input:focus-visible,
  .card__add input:focus-visible { outline: none; }
  .term:has(input:focus-visible),
  .card__add:has(input:focus-visible) {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
  }
}

/* ==================== platform-04: the software, photographed ===========
   A landscape screenshot of a dark console framed on a light mat. The mat
   is the only reason this needs any styling at all: a dark rectangle set
   straight onto warm paper reads as a hole, and a hairline plus a little
   air turns it into a photograph of something.                            */
#platform-04 {
  background: var(--paper-2);
  border-top: var(--hair) solid var(--rule);

  /* what the login shows, in the buyer's nouns, glyph beside each. Quiet
     ink holds 7.48:1 on paper-2; the glyphs read 11.70:1. */
  /* One line at every width: the four keys shrink their gap before they wrap. */
  & .plat__keys {
    list-style: none;
    display: flex;
    flex-wrap: nowrap;
    justify-content: space-between;
    gap: var(--s-2);
    max-width: 30rem;
    margin: var(--s-5) 0 0;
    padding: 0;
  }
  & .plat__keys li {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    white-space: nowrap;
    font-size: clamp(0.72rem, 3.4vw, var(--t-small));
    font-weight: 600;
    color: var(--quiet);
  }
  & .plat__keys .ic { width: clamp(16px, 5vw, 24px); height: clamp(16px, 5vw, 24px); }

  & .shot {
    margin: clamp(var(--s-6), 5vw, var(--s-8)) 0 0;
    max-width: 58rem;
  }
  /* Square corners, like the chip in the hero: radius belongs to pills and
     the device frame. The mat's soft shadow is the photograph-frame
     exception and stays. */
  & .shot__mat {
    padding: clamp(var(--s-3), 1.4vw, var(--s-5));
    background: var(--paper);
    border: var(--hair) solid var(--rule-strong);
    box-shadow: var(--shadow-mat);
  }
  /* width and height are on the element, so the box is reserved before the
     bytes arrive and nothing below it moves when they do. */
  & .shot img {
    width: 100%;
    height: auto;
    border: var(--hair) solid var(--rule);
  }
  /* Two phone screens of the client dashboard, side by side in the mat.
     Portrait screenshots at natural phone proportions; capped so a pair of
     390-point screens never balloons past believable size on a wide desk. */
  & .shot__pair {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: clamp(var(--s-3), 1.4vw, var(--s-5));
    max-width: 46rem;
    margin-inline: auto;
  }
  & figcaption {
    margin-top: var(--s-4);
    max-width: var(--measure);
    font-size: var(--t-micro);
    color: var(--quiet);
  }

  /* On a phone the screenshots take the full viewport width and stack: two
     phone screens side by side at 170px each would be unreadable, and a
     phone screenshot read ON a phone at its own size is the most honest
     rendering this figure can have. */
  @media (max-width: 47.99em) {
    & .shot {
      max-width: none;
      margin-inline: calc(var(--edge) * -1);
    }
    & .shot__mat {
      padding: var(--s-2);
      border-inline: 0;
    }
    & .shot__pair {
      grid-template-columns: 1fr;
      max-width: 24rem;
    }
    & figcaption { padding-inline: var(--edge); }
  }
}

@media (min-width: 62em) {
  #platform-04 .plat__in {
    display: grid;
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
    gap: clamp(var(--s-6), 5vw, var(--s-8));
    align-items: center;
  }
  #platform-04 .shot { margin-top: 0; }
}

/* ==================== buy-06: one ask, two channels =====================
   reach-05's whole stylesheet block is in here, and its section is gone. The
   form is a second channel for the same ask, not a section of its own, so it
   composes inside the dark band with the call control above it.

   THE FORM'S RULES FOLLOW THE BAND, WITH ONE DELIBERATE EXCEPTION. This
   section carries data-theme="board", which remaps the whole semantic set, so
   labels, notes and the summary box are written against --fg, --quiet and
   --accent and simply arrive correct on the dark ground. The INPUTS are the
   exception: they keep `background: var(--paper)` and `color: var(--ink)`,
   which are primitives rather than remapped tokens, because light fields on a
   dark band read unambiguously as fields. Their edge takes --rule-field for
   the reason tokens.css gives beside it: a border paints over its own
   element's background, so the board's light --rule-strong would have
   composited over that porcelain fill and vanished.

   FOUR ROWS WERE ADDED TO tools/contrast.mjs FOR THIS and the gate is the only
   acceptable proof: the field label on board, .note on board, the error text
   on board, and the invalid field's edge against the porcelain fill.

   AND THE FOUR STRIPE ROWS ARE GONE, with .buy__list, .buy__row, .buy__name,
   .buy__price and .buy__unset. Preview first: the doors are not demoted, they
   are deleted, and the argument is in index.html above the section.        */
#buy-06 {
  background: var(--bg);
  color: var(--fg);

  & .buy__head { max-width: 40rem; }
  & .buy__head .h2 { color: var(--fg); }
  /* The door has to say the same thing the argument said two screens up, or
     the section that takes the money contradicts the section that made the
     case. Both of this section's leads use it: the one that frames the call
     and the enhanced active choice above the terms. */
  & .buy__lead {
    margin: var(--s-5) 0 0;
    font-size: var(--t-lead);
    line-height: 1.55;
    color: var(--fg);
  }
  & .buy__lead[hidden] { display: none; }
  & .buy__choice { margin-top: clamp(var(--s-7), 6vw, var(--s-8)); max-width: 40rem; }

  /* The ask: one control, one sentence, four fields. Held to a single column
     at every width. A four-field form running the full 1440 spread reads as an
     application rather than a callback, and the reading order this section is
     built on (loss, call, second channel, choice, terms, proof) only survives
     if nothing sits beside anything. */
  & .buy__ask {
    max-width: 30rem;
    margin-top: clamp(var(--s-6), 5vw, var(--s-7));
  }

  & form { display: grid; gap: var(--s-5); margin-top: var(--s-5); }
  & form > .btn { justify-self: start; }
  & .field { display: grid; gap: var(--s-2); }
  & label { font-size: var(--t-small); font-weight: 600; color: var(--fg); }
  & input, & textarea, & select {
    width: 100%;
    padding: 0.8rem 0.9rem;
    font: inherit;
    font-size: var(--t-small);
    color: var(--ink);
    background: var(--paper);
    border: var(--hair) solid var(--rule-field);
    transition: border-color var(--dur-fast) ease;
  }
  & textarea { min-height: 6.5rem; resize: vertical; }
  & input:hover, & textarea:hover { border-color: var(--ink); }
  & .err {
    font-size: var(--t-micro);
    color: var(--accent);
    font-weight: 600;
  }
  /* The invalid edge is the PORCELAIN accent, not the band's. --accent under
     this theme is accent-bright, which is the pale link colour that reads on a
     dark ground and measures about 1.4:1 against the paper fill it would be
     drawn on. The field is a porcelain object, so its error edge is the
     porcelain one. */
  & [aria-invalid="true"] { border-color: var(--accent-deep); border-width: 2px; }

  & .summary {
    padding: var(--s-4) var(--s-5);
    border: 2px solid var(--accent);
    font-size: var(--t-small);
  }
  & .summary ul { margin: var(--s-2) 0 0; padding-left: 1.1rem; }
  & .summary[hidden] { display: none; }

  & .pot { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

  /* The scriptless confirmation. Absent until the URL names it, which the
     portal's 303 does after it has written the lead to a database. Same edge
     as .summary, because it is the same kind of object: the page telling you
     what just happened to what you typed. */
  & .sent { display: none; }
  & .sent:target {
    display: block;
    margin: var(--s-6) 0 0;
    padding: var(--s-4) var(--s-5);
    border: 2px solid var(--accent);
    font-size: var(--t-small);
  }
  & .sent:focus-visible { outline: 3px solid var(--accent-deep); outline-offset: 3px; }

  & .note { font-size: var(--t-micro); color: var(--quiet); margin: 0; }
  /* The draft only exists when the script does, and only when the form has
     somewhere to send. The markup keeps it hidden and main.js shows it when
     both are true; these two rules are the scriptless half of the same
     statement. Saying otherwise would be a claim about storage that is not
     true. */
  html:not(.js) & .draft { display: none; }

  /* WITH NO SCRIPT AND NO DESTINATION THERE IS NO SEND, SO THERE IS NO
     BUTTON. While the target is a slot the button is type="button", so on the
     scriptless road it was a full weight accent pill that did nothing at all
     when pressed: no navigation, no error, no message, on the control that
     captures the lead. That is the rule the four buy rows were fixed for, "a
     dead control that looks alive is worse than no control", left standing in
     the one place it mattered most. tools/reach-target.mjs writes
     type="submit" back and this rule stops matching. */
  html:not(.js) & form[data-reach="unset"] [data-send] { display: none; }
  & .nojs { display: none; }
  html:not(.js) & .nojs {
    display: block;
    padding: var(--s-4) var(--s-5);
    border: var(--hair) solid var(--rule-strong);
    font-size: var(--t-small);
  }

  /* the demonstration shelf and the admission beside it, closing the page on
     the one control that has never been dead. */
  & .buy__again {
    margin: clamp(var(--s-6), 4vw, var(--s-7)) 0 0;
    padding-top: var(--s-6);
    border-top: var(--hair) solid var(--rule);
  }
  & .buy__proof { margin: 0; color: var(--fg); max-width: 40rem; }
  & .buy__acts {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-4);
    margin-top: var(--s-5);
  }
}

/* The ways to reach me on the left, the form on the right, so the section is
   one screen on a desk rather than a column of seven blocks. */
#buy-06 {
  & .buy__grid { display: grid; gap: clamp(var(--s-6), 5vw, var(--s-8)); }
  @media (min-width: 62em) {
    & .buy__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 28rem); align-items: start; }
  }
  & .buy__ask { max-width: none; margin-top: 0; }
  & .buy__ask form { margin-top: 0; }
  & .buy__again {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-3) var(--s-5);
    margin-top: var(--s-7);
    padding-top: 0;
    border-top: 0;
  }
  /* Cancelling is one quiet link that opens an email: there when it is
     needed, never the loudest thing in the section. 44px tall (decision 10). */
  & .buy__cancel {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    margin-top: var(--s-5);
    font-size: var(--t-small);
    color: var(--quiet);
  }
  & .buy__cancel:hover { color: var(--fg); }
}

/* ==================== colophon: short, operational ====================== */
.colophon {
  border-top: var(--hair) solid var(--rule);
  padding-block: clamp(var(--s-7), 7vh, var(--s-8));
  font-size: var(--t-small);

  & .colophon__in {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s-5) var(--s-6);
  }
  & nav { display: flex; flex-wrap: wrap; gap: var(--s-5); }
  & a {
    color: var(--ink);
    text-decoration: none;
    border-bottom: var(--hair) solid var(--rule-strong);
    /* grown target, same device as the mast links: 44px reachable, text
       unchanged */
    position: relative;
  }
  & a::after { content: ""; position: absolute; inset: -0.8rem -0.3rem; }
  & a:hover { color: var(--accent); border-color: var(--accent); }
  & .quiet { color: var(--quiet); font-size: var(--t-micro); }
  & .quiet a { color: var(--quiet); border-bottom-color: var(--rule); }
}

/* ==================== banner: interior pages ============================ */
.banner {
  padding-top: clamp(3rem, 8vh, 5.5rem);
  padding-bottom: clamp(2.5rem, 6vh, 4rem);
  border-bottom: var(--hair) solid var(--rule);
}
.crumbs { font-size: var(--t-micro); color: var(--quiet); margin: 0 0 var(--s-5); }
/* 19px, and the only route up a level: grown like the rest. */
.crumbs a { color: var(--quiet); position: relative; }
.crumbs a::after {
  content: "";
  position: absolute;
  inset: -0.8rem -0.2rem;
}

/* ==================== demo sites: a showroom, not a list =================
   One site on the stage, big enough to judge, with next, back and "show me
   another"; a finder that narrows the tiles by trade or kind; and the tiles
   themselves, each the site's own share card. The stage and the finder are
   the script's (hidden without it); the tiles are links either way. */
.banner .lead { margin-top: var(--s-4); }
.showroom { padding-top: clamp(var(--s-6), 5vw, var(--s-7)); padding-bottom: 0; }
/* The closing button belongs to the tiles above it, not to a section of its
   own: without this the two sections' padding stacked into a 200px gap. */
.showroom + section { padding-block: clamp(var(--s-6), 5vw, var(--s-7)) clamp(var(--s-7), 7vw, var(--s-8)); }
.showroom + section p { margin: 0; }

.stage {
  display: grid;
  gap: var(--s-5);
  align-items: center;
  scroll-margin-top: 5.5rem;
}
@media (min-width: 58em) {
  .stage { grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); gap: clamp(var(--s-6), 4vw, var(--s-8)); }
}
.stage__frame {
  display: block;
  border: var(--hair) solid var(--rule-strong);
  box-shadow: var(--shadow-mat);
  background: var(--paper-2);
}
.stage__img { display: block; width: 100%; height: auto; aspect-ratio: 1200 / 630; }
.stage__info > * { margin: 0; }
.stage__what { font-size: var(--t-small); font-weight: 600; color: var(--quiet); }
.stage__name {
  margin-top: var(--s-2);
  font-family: var(--font-display);
  font-size: var(--t-h2);
  font-weight: 700;
  line-height: var(--lh-heading);
  color: var(--ink);
  text-wrap: balance;
}
.stage__count { margin-top: var(--s-3); font-size: var(--t-micro); color: var(--quiet); font-variant-numeric: tabular-nums; }
.stage__acts { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: var(--s-3); margin-top: var(--s-5); }
.stage__acts .btn--fill { justify-content: center; padding-inline: var(--s-4); }
.stage__step { min-width: 3.25rem; justify-content: center; font-size: 1.15rem; }
.stage__shuffle {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-top: var(--s-3);
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  font-size: var(--t-small);
  font-weight: 600;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  cursor: pointer;
}

.finder {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3) var(--s-4);
  margin-top: clamp(var(--s-6), 5vw, var(--s-8));
  padding-top: var(--s-5);
  border-top: var(--hair) solid var(--rule);
}
.finder input {
  flex: 1 1 16rem;
  max-width: 24rem;
  min-height: 3rem;
  padding: 0.7rem 1.1rem;
  font: inherit;
  font-size: var(--t-body);
  color: var(--ink);
  background: var(--paper);
  border: var(--hair) solid var(--rule-strong);
  border-radius: var(--r-pill);
}
.chips { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 44px;
  padding: 0.4rem 0.95rem;
  background: var(--paper);
  border: var(--hair) solid var(--rule-strong);
  border-radius: var(--r-pill);
  font: inherit;
  font-size: var(--t-small);
  color: var(--ink);
  cursor: pointer;
}
.chip em { font-style: normal; font-size: var(--t-micro); color: var(--quiet); font-variant-numeric: tabular-nums; }
.chip:hover { background: var(--paper-2); }
.chip[aria-pressed="true"] { background: var(--paper-3); box-shadow: inset 0 0 0 2px var(--accent); }

.tiles {
  list-style: none;
  display: grid;
  /* Two across on a phone, so 36 sites are 18 rows rather than 36. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-4) var(--s-3);
  margin: var(--s-5) 0 0;
  padding: 0;
}
.tile[hidden] { display: none; }
.tile a { display: block; text-decoration: none; color: var(--ink); }
.tile img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 640 / 336;
  border: var(--hair) solid var(--rule);
  background: var(--paper-2);
  transition: box-shadow var(--dur-fast) ease, transform var(--dur-fast) ease;
}
.tile a:hover img { box-shadow: 0 0 0 2px var(--accent); }
.tile.is-on img { box-shadow: 0 0 0 3px var(--accent); }
@media (prefers-reduced-motion: no-preference) {
  .tile a:hover img { transform: translateY(-2px); }
}
.tile__name { display: block; margin-top: var(--s-2); font-weight: 600; line-height: 1.3; }
.tile__what { display: block; font-size: var(--t-small); color: var(--quiet); }
.tiles__none { margin: var(--s-6) 0 0; font-size: var(--t-lead); color: var(--quiet); }
.tile__name { font-size: var(--t-small); }
.tile__what { font-size: var(--t-micro); }
@media (min-width: 40em) {
  .tiles { grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); gap: var(--s-5) var(--s-4); }
  .tile__name { font-size: var(--t-body); }
  .tile__what { font-size: var(--t-small); }
}

/* ==================== 404 =============================================== */
.lost { padding-block: clamp(5rem, 16vh, 9rem); }
.lost .h1 { max-width: 18ch; }
.lost__acts { display: flex; flex-wrap: wrap; gap: var(--s-4); margin-top: var(--s-7); }

/* ---------------------------------------------------- stagger steps ----
   Written as attributes rather than inline style attributes, so style-src
   in _headers can stay 'self' with no 'unsafe-inline' anywhere on the site. */
[data-d="0"] { --d: 0; }
[data-d="1"] { --d: 1; }
[data-d="2"] { --d: 2; }
[data-d="3"] { --d: 3; }
[data-d="4"] { --d: 4; }

.mt-4 { margin: var(--s-4) 0 var(--s-2); }
.mt-7 { margin-top: var(--s-7); }

/* THE COMPOSED MESSAGE IS A PORCELAIN OBJECT ON A DARK BAND, like the fields
   it was typed into, so it sets its own ink. It inherits paper-on-board from
   the section otherwise, which on a paper-2 fill is paper on paper. */
#slip-outcome pre {
  margin: var(--s-4) 0 0;
  padding: var(--s-4);
  color: var(--ink);
  background: var(--paper-2);
  font-family: var(--font-text);
  font-size: var(--t-micro);
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
#slip-outcome pre:empty { display: none; }
#slip-outcome .btn { margin-top: var(--s-4); }

/* ---------------------------------------------------------------- the name --
   The colophon carries the OUTLINED WORDMARK on its own, which is one of the
   four configurations page 2 of the master approves, at the same size the
   masthead sets it. It is decorative here and says so with an empty alt: the
   footer is not a route home, and the route home is the linked mark at the
   top of the page.

   This is where two coloured spans used to live. They were "carverpoint" set
   as live text with the second half recoloured from a token, which is three
   items off page 9 in one line of markup: retyping the logotype, rebuilding
   its accent in CSS, and restoring the round i dot the font supplies where
   the master draws a custom angular point. Type cannot draw that point, so
   type was never going to be able to set this word. */
/* The full lockup, mark then wordmark, at the masthead's own unit and gap. */
.colophon__brand {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--lockup-gap) * var(--lockup-u));
  align-self: center;
}
.colophon__mk {
  display: block;
  width: calc(var(--mark-w) * var(--lockup-u));
  height: calc(var(--mark-h) * var(--lockup-u));
}
.colophon__wm {
  display: block;
  width: calc(var(--wm-w) * var(--lockup-u));
  height: calc(var(--wm-h) * var(--lockup-u));
  /* The colophon row aligns on the baseline and an image has none of its own,
     so it would sit on the row's baseline by its bottom edge and ride high
     against the links beside it. Its own optical centre is what should line
     up with their x-height. */
  align-self: center;
}

/* ==================== doc: the published policy pages ===================
   ONE block, added once, and this is the argument for why it did not already
   exist and why it has to now.

   index.html is cards, rules and a diagram. work.html is a list. Between them
   they render no heading inside running prose, no ordered list, no table and
   no blockquote, so the stylesheet carried a rule for none of those four, and
   a legal document is made of all four at once. Nothing below is a second
   design system: every colour is a token that tools/contrast.mjs has already
   weighed (ink, ink-quiet, accent, rule, rule-strong, paper-2), every size is
   a step on the existing type scale, and every gap is an --s-N. The contrast
   figures therefore stand unchanged and that script has no new pair to
   measure, which is the whole reason to reuse rather than to add.

   ONE LENGTH IS NOT A TOKEN, the 38rem column, and it is deliberate.
   --measure is 34rem and it is the right measure for a card of marketing
   copy; it renders the three column retention table in security.md at about
   eleven characters a cell. A document that is read from the top down rather
   than scanned gets a wider column. It is not promoted to tokens.css because
   exactly one thing uses it and a token is a promise that more than one thing
   will.

   THE NUMBER IS MEASURED, NOT ESTIMATED, and the first attempt at it was
   wrong in the direction this repository is least allowed to be wrong in.
   It was 44rem, on the reasoning that 44rem "holds roughly 80 characters at
   the body size: the upper end of the readable band rather than past it".
   Counting the characters actually rendered on the longest line of a real
   paragraph of terms.md, by walking Range rects rather than estimating from
   an em width, 44rem holds 94, because the body here is 0.9rem and not 1rem:
   44rem is 704px, which is 48.9 body-ems, not 44 of them. So the old comment
   claimed the upper end of the band while sitting eighteen per cent past it,
   in a stylesheet whose whole argument is that a measured figure beats a
   remembered one. 38rem measures 80 characters at 1440, at 1024 and at 768,
   and 48 at 390, which is the band the old comment was aiming at and named.
   If the body size ever moves, re-measure; do not re-estimate.           */
.h1--doc {
  font-size: var(--t-h2);
  line-height: var(--lh-heading);
  max-width: 24ch;
}

/* A document begins directly under its own title. --section-pad is 8.5rem at
   the top of a wide viewport, which is right when the banner above it is a
   5.6rem headline over a three line lead, as it is on /work, and reads as a
   missing element when the banner is a compact title and nothing else, as it
   is here. The bottom keeps the section rhythm. */
.doc-band { padding-top: clamp(var(--s-7), 6vh, var(--s-8)); }

.doc {
  max-width: 38rem;
  color: var(--ink);
}
.doc > :first-child { margin-top: 0; }
.doc p { margin: 0 0 var(--s-4); }
.doc strong { font-weight: 700; }

/* A numbered section heading, not a display size. There are eleven of them in
   terms.md and --t-h2 would set each one at up to 3.1rem, which turns a
   contract into a poster. --t-h3 is the step the site already has for a
   heading inside a section, and the hairline above it does the work of
   separating one clause from the next. */
.doc h2 {
  font-family: var(--font-display);
  font-size: var(--t-h3);
  line-height: var(--lh-heading);
  font-weight: 700;
  margin: var(--s-8) 0 var(--s-4);
  padding-top: var(--s-5);
  border-top: var(--hair) solid var(--rule);
}
.doc h2:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.doc h3 {
  font-size: var(--t-body);
  font-weight: 700;
  margin: var(--s-6) 0 var(--s-3);
}

.doc ul, .doc ol { margin: 0 0 var(--s-4); padding-left: var(--s-5); }
.doc li { margin-bottom: var(--s-2); }
.doc li::marker { color: var(--quiet); }

/* The author's own boxed notes: the draft banner at the top of every one of
   these documents, and the notes to the reader inside them. Quiet ink on
   paper-2 is 7.48:1, which is already in the measured set, and the left rule
   is --rule-strong, the same composited 3.2:1 edge every form field carries.
   It is set apart rather than hidden: a document that says it has not been
   through a lawyer has to say so where a reader sees it. */
.doc blockquote {
  margin: 0 0 var(--s-5);
  padding: var(--s-4) var(--s-5);
  border-left: 2px solid var(--rule-strong);
  background: var(--paper-2);
  color: var(--quiet);
  font-size: var(--t-small);
}
.doc blockquote > :last-child { margin-bottom: 0; }

/* A filename in prose. It stays in the page's one typeface on purpose: the
   site self hosts a single family and a system monospace would be a second
   face arriving for four words. The tint and the quiet ink are enough to say
   it names a file. */
.doc code {
  font-family: inherit;
  color: var(--quiet);
  background: var(--paper-2);
  padding: 0.08em 0.25em;
}

.doc hr { border: 0; border-top: var(--hair) solid var(--rule); margin: var(--s-7) 0; }

/* THE SCROLLER IS NOT OPTIONAL. `html` carries overflow-x: hidden and main.css
   says at the top why it lives there and only there. A table wider than the
   column would therefore be CLIPPED rather than scrolled, and a clipped column
   of a retention table or a price table is a missing legal statement, not a
   layout blemish. */
.doc__scroll { overflow-x: auto; margin: 0 0 var(--s-5); }
.doc table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-small);
  min-width: 30rem;
}
.doc th, .doc td {
  text-align: left;
  vertical-align: top;
  padding: var(--s-3) var(--s-4) var(--s-3) 0;
  border-bottom: var(--hair) solid var(--rule);
}
.doc th { font-weight: 700; border-bottom-color: var(--rule-strong); }
.doc td:last-child, .doc th:last-child { padding-right: 0; }

/* THE DEVICE THE READER IS ACTUALLY HOLDING.
 *
 * "Look yourself up on that phone" is the strongest line in the buy section
 * because it hands the reader a ten second check they can run without trusting
 * anybody -- and on a laptop it names a device that is not in the room, which
 * turns a checkable instruction into a stock phrase.
 *
 * DONE IN CSS, NOT IN SCRIPT, on purpose. This page prints every price and the
 * phone number with JavaScript absent, and that is a contract rather than a
 * preference; a sentence that needs a script to read correctly would be the
 * first thing on the page to break it. A media query is also the honest test:
 * pointer:coarse asks what the reader is touching the screen with, which is the
 * question, rather than sniffing a user agent string that lies.
 */
.on-desk { display: none; }
@media (hover: hover) and (pointer: fine) {
  .on-phone { display: none; }
  .on-desk { display: inline; }
}

/* ==================== studio: the asset wing =============================
   The second revenue line's page. Same system, no new primitives: hairlines
   and air, no card radius, no shadow, tokens only. The ONE dark band on this
   page is the ask at the foot of it, which is the same scarcity rule index.html
   follows -- a second dark band would make the first stop meaning anything.

   WHY THE INVENTORY IS A LIST AND NOT THE TILE GRID /sites USES. A demo site's
   tile is a picture, because what a visitor wants to know is what it LOOKS
   like. An asset's row is four facts -- what it is, what it earns, what stage
   it is at, what it costs -- and three of them are text. A picture grid would
   bury the figures under thirteen screenshots, and the figures are the page.

   THE BANDS TABLE TAKES .doc's TABLE RULES, which already exist for the policy
   pages: hairline row rules, no zebra, tabular figures. A second table idiom on
   one site would be a second thing to maintain for no gain.               */

.studio__plain,
.studio__note {
  max-width: var(--measure);
  color: var(--quiet);
  font-size: var(--t-small);
  line-height: var(--lh-body);
}
.studio__plain { margin-top: var(--s-5); }
.studio__note { margin-top: var(--s-6); }

/* ---------------------------------------------------- the two bands ------ */
.studio-why {
  padding: var(--section-pad) 0;
  background: var(--bg-2);

  & .studio-why__in {
    display: grid;
    gap: var(--s-7);
    margin-top: var(--s-6);
    align-items: start;
  }
  /* Two columns only where the prose still holds its measure beside a table
     that has four rows of its own. Below this the table goes under the copy,
     which is the order somebody reads them in anyway. */
  @media (min-width: 60rem) {
    & .studio-why__in { grid-template-columns: minmax(0, 1fr) minmax(0, 24rem); gap: var(--s-8); }
  }
  & .copy > p { max-width: var(--measure); }
  & .copy > p + p { margin-top: var(--s-5); }
  /* TWO-TONE PARAGRAPHS, decision 6: the claim in ink, the evidence in quiet
     ink. Done with a <b> rather than a second face, same as the section leads. */
  & .copy b { font-weight: 700; color: var(--fg); }
  & .copy p { color: var(--quiet); }
}

.studio-bands {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-small);
  font-variant-numeric: tabular-nums;

  & caption {
    text-align: left;
    color: var(--quiet);
    font-size: var(--t-micro);
    padding-bottom: var(--s-3);
  }
  & th, & td {
    text-align: left;
    padding: var(--s-3) var(--s-4) var(--s-3) 0;
    border-bottom: var(--hair) solid var(--rule);
    vertical-align: top;
  }
  & thead th { border-bottom-color: var(--rule-strong); font-weight: 700; }
  & tbody th { font-weight: 400; color: var(--quiet); }
  & td:last-child, & th:last-child { padding-right: 0; }
  /* The right-hand column is the one the page is arguing for, so it carries
     the weight. One emphasis, in the place the argument lands. */
  & td:last-child { font-weight: 600; color: var(--fg); }
}

/* ---------------------------------------------------- the inventory ----- */
.studio-stock {
  padding: var(--section-pad) 0;

  & .lead { max-width: var(--measure); }
}

.assets {
  list-style: none;
  margin: var(--s-7) 0 0;
  padding: 0;
  display: grid;
  gap: 0;
  border-top: var(--hair) solid var(--rule-strong);
}

/* THE CARD LOST ITS STAGE COLUMN. Every row on a page headed "For sale" is for
   sale, so an eleven-character label repeated thirteen times down the left edge
   was a column that carried no information and pushed the price off the line a
   buyer reads first. The price took its place at the end of the name's row,
   where a price list puts it. */
.asset {
  display: grid;
  gap: var(--s-2);
  padding: var(--s-6) 0;
  border-bottom: var(--hair) solid var(--rule);
}

.asset__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3) var(--s-5);
}
.asset__name { margin: 0; }

/* The loudest thing on the card, and deliberately. On a page whose job is to
   move thirteen assets, nobody should have to look for the number. */
.asset__price {
  margin: 0;
  font-size: var(--t-price);
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.asset[data-stage="sold"] .asset__price { font-size: var(--t-h3); color: var(--quiet); }

.asset__what,
.asset__figs {
  margin: 0;
  color: var(--quiet);
  font-size: var(--t-small);
  max-width: var(--measure);
}
.asset__figs { font-size: var(--t-micro); }

.asset__acts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
  margin: var(--s-3) 0 0;
}
.asset__venue { font-size: var(--t-small); color: var(--quiet); }

/* ---------------------------------------------------- what comes with it - */
.studio-pack {
  padding: var(--section-pad) 0;
  background: var(--bg-2);

  & .lead { max-width: var(--measure); }

  & .studio-pack__list {
    list-style: none;
    margin: var(--s-7) 0 0;
    padding: 0;
    display: grid;
    gap: var(--s-6);
  }
  @media (min-width: 46rem) {
    & .studio-pack__list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-7); }
  }
  & .studio-pack__list > li {
    padding-top: var(--s-4);
    border-top: var(--hair) solid var(--rule-strong);
  }
  & .studio-pack__list h3 { margin: 0 0 var(--s-3); }
  & .studio-pack__list p { color: var(--quiet); font-size: var(--t-small); }
}

/* ---------------------------------------------------- the one ask -------- */
.studio-ask {
  padding: var(--section-pad) 0;
  background: var(--bg);
  color: var(--fg);

  & .h2, & .lead { color: var(--fg); }
  & .lead { max-width: var(--measure); margin-top: var(--s-4); }

  /* The field rules are buy-06's, repeated here rather than shared, and the
     reason is the comment above #buy-06: the inputs keep the PRIMITIVES
     --paper and --ink rather than the remapped tokens, because light fields on
     a dark band read unambiguously as fields. A shared class would have to
     carry that exception for both bands and would then be wrong on a page that
     grows a porcelain form. */
  & form { margin-top: var(--s-6); display: grid; gap: var(--s-5); }
  & form > .btn { justify-self: start; }
  & .ask__fields {
    display: grid;
    gap: var(--s-5);
  }
  @media (min-width: 42rem) {
    & .ask__fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    & .field--wide { grid-column: 1 / -1; }
  }
  & .field { display: grid; gap: var(--s-2); }
  & label { font-size: var(--t-small); font-weight: 600; color: var(--fg); }
  & input, & textarea, & select {
    width: 100%;
    padding: 0.8rem 0.9rem;
    font: inherit;
    font-size: var(--t-small);
    color: var(--ink);
    background: var(--paper);
    border: var(--hair) solid var(--rule-field);
    transition: border-color var(--dur-fast) ease;
  }
  & textarea { min-height: 6.5rem; resize: vertical; }
  & input:hover, & textarea:hover, & select:hover { border-color: var(--ink); }

  /* THE HONEYPOT. Off-screen rather than display:none, because a field a
     browser will not render is also a field a browser will not autofill, and
     some bots read display:none and skip it. aria-hidden and tabindex="-1"
     keep it off the keyboard path and out of the accessibility tree, which is
     what stops it being a trap for a screen reader instead of a bot. */
  & .field--trap {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  & .ask__legal { margin-top: var(--s-5); color: var(--quiet); font-size: var(--t-micro); }
  & .ask--unset { display: grid; gap: var(--s-4); margin-top: var(--s-6); justify-items: start; }
  & .studio__note { color: var(--quiet); }
  & code {
    font-size: var(--t-micro);
    padding: 0.1em 0.3em;
    background: var(--bg-2);
  }
}

/* ---------------------------------------------------- the sources ------- */
.studio-src {
  padding: var(--s-8) 0;

  & .studio__note { margin-top: var(--s-4); }
  & .studio-src__list {
    margin: var(--s-5) 0 0;
    padding-left: 1.2em;
    max-width: var(--measure);
    color: var(--quiet);
    font-size: var(--t-small);
    line-height: var(--lh-body);
  }
  & .studio-src__list > li + li { margin-top: var(--s-3); }
}

/* ---------------------------------------------------- studio: the shop ---
   Added when /studio stopped explaining why nothing was for sale and started
   selling. Everything here composes with the blocks above; nothing overrides
   them.                                                                     */

/* The hero's two controls. Buying first, filled; looking second, outlined. */
.studio__acts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  align-items: center;
  margin-top: var(--s-6);
}

/* The comes-with table is a two-column ledger rather than a comparison, so the
   row header carries the quiet ink and the answer carries the weight. `no` is
   NOT red: "none, and we will not pretend" is the page being honest on purpose,
   and painting it as a warning would make an argument the copy is not making. */
.studio-bands td.yes { font-weight: 600; color: var(--fg); }
.studio-bands td.no  { font-weight: 400; color: var(--quiet); }
.studio-bands tbody th { white-space: nowrap; }

/* The four steps. NUMBERED, and the numbers are information: this is a real
   sequence a buyer moves through in order, which is the one case where a
   numbered marker earns its place. */
.studio-steps {
  list-style: none;
  counter-reset: step;
  margin: var(--s-7) 0 0;
  padding: 0;
  display: grid;
  gap: var(--s-6);
}
@media (min-width: 46rem) {
  .studio-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-7); }
}
@media (min-width: 72rem) {
  .studio-steps { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s-6); }
}
.studio-steps > li {
  counter-increment: step;
  padding-top: var(--s-4);
  border-top: var(--hair) solid var(--rule-strong);
}
.studio-steps > li::before {
  content: counter(step, decimal-leading-zero);
  display: block;
  font-size: var(--t-eyebrow);
  letter-spacing: var(--track-eyebrow);
  color: var(--quiet);
  margin-bottom: var(--s-2);
}
.studio-steps h3 { margin: 0 0 var(--s-3); }
.studio-steps p { margin: 0; color: var(--quiet); font-size: var(--t-small); }
