/* NanoPorter — direction L, "Clean". 11 September 2026.
   One calm document inside a hairline frame. Small, light type; one product visual per section.
   Every number in the token block comes from BRIEF.md and nowhere else.

   THE RULE THIS FILE IS WRITTEN AGAINST: with JavaScript off, nothing is hidden. Every selector
   that hides or dims something is scoped under `html.js`, which a one-line inline script sets.
   Search this file for `html.js` and you can read the entire set in one pass. */

/* ---------------------------------------------------------------- fonts (self-hosted, no service) */
@font-face { font-family: "InterVar"; src: url("/fonts/inter-var.woff2") format("woff2"); font-weight: 100 900; font-display: swap; }
/* the faces the nine widget skins ask for. Heritage asks for Lora; no self-hosted Lora exists in
   this folder, so it takes the designer's own documented fallback, Georgia. Flagged in handover. */
@font-face { font-family: "Nunito"; src: url("/fonts/nunito-var.woff2") format("woff2"); font-weight: 400 800; font-display: swap; }
@font-face { font-family: "Source Sans 3"; src: url("/fonts/source-sans-3-var.woff2") format("woff2"); font-weight: 400 700; font-display: swap; }
@font-face { font-family: "IBM Plex Mono"; src: url("/fonts/plex-mono-400.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "IBM Plex Mono"; src: url("/fonts/plex-mono-600.woff2") format("woff2"); font-weight: 600; font-display: swap; }

/* ---------------------------------------------------------------- tokens: BRAND-SPEC light set */
:root {
  --page:   #F1F7F5;
  --tint:   #E7F1EC;
  --card:   #FFFFFF;
  --ink:    #0E1618;
  --body:   #28332F;
  --muted:  #5C6E6A;
  --rule:   #BCD4CB;
  --accent: #0D7C56;

  --container: 1120px;
  --gutter: 24px;
  /* Justin, 12 September: the frame's two verticals sat exactly on the container edge, so the
     step numbers and the body text landed on the line. The rules move OUT by this, and the text
     measure is untouched. A flat length, not a viewport unit: `100vw` counts a classic scrollbar
     and the `50%` in background-position does not, so mixing them makes the geometry disagree
     with itself by half a scrollbar. */
  --frame-pad: 32px;
  --nav-h: 72px;   /* Justin, 12 September: the bar is 72 and the mark 40, not a favicon */
  /* Justin, 12 September: "the entire site feels too zoomed in / squashed". 88 was BRIEF.md's
     number; this is his, and it is the later instruction. */
  --pad-y: 112px;

  --hair: 1px solid var(--rule);
  --r-panel: 24px;
  --ease: cubic-bezier(.22, .61, .36, 1);
}

/* ---------------------------------------------------------------- reset and base */
*, *::before, *::after { box-sizing: border-box; }
/* No page-wide `scroll-behavior: smooth`. It animates scroll position, which is neither
   transform nor opacity, so it is out. An anchor click is a plain jump, as the browser intends.
   The two controls that scroll (the fixed button, the carousel arrows) ask for smooth explicitly
   and fall back to an instant jump under prefers-reduced-motion. */
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--page);
  color: var(--body);
  font-family: "InterVar", Inter, -apple-system, "Segoe UI", Arial, sans-serif;
  font-size: 16px;
  line-height: 1.6;
  font-weight: 400;
  font-variant-numeric: tabular-nums;      /* BRAND-SPEC: Inter's digits are proportional by default */
  -webkit-font-smoothing: antialiased;
  /* No overflow-x:hidden. It makes the body a clipping container, which can break position:fixed
     and silently crops anything that reaches past the viewport. The frame is a BACKGROUND on
     <main>, and backgrounds do not overflow; the carousel does its own clipping. The audit
     measures document.scrollWidth on the live page, so a real overflow still fails. */
}

img, svg { max-width: 100%; }
[hidden] { display: none !important; }

a { color: var(--accent); text-underline-offset: 2px; }
a:focus-visible, button:focus-visible, summary:focus-visible, input:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px;
}

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

/* ---------------------------------------------------------------- type scale (BRIEF.md, law)
   Nothing here exceeds 44px and nothing exceeds weight 500. The two PRODUCT ARTEFACTS on the page
   — the widget (.wf) and the real enquiry email (.email-sheet) — carry their own type, because
   they are things being shown, not the page's own writing. tools/audit.mjs excludes them by name. */
h1, h2, h3, h4 { margin: 0; font-weight: 400; color: var(--ink); letter-spacing: -0.012em; }
h1 { font-size: clamp(30px, 2.9vw, 38px); line-height: 1.14; letter-spacing: -0.01em; }
h2 { font-size: clamp(25px, 2.4vw, 30px); line-height: 1.2; }
h3 { font-size: 18px; line-height: 1.35; font-weight: 500; }

p { margin: 0; }
.lede { font-size: 16px; line-height: 1.6; color: var(--body); }
.note { font-size: 14px; line-height: 1.5; color: var(--muted); }
.eyebrow {
  font-size: 13px; line-height: 1.4; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); font-weight: 500; margin: 0 0 14px;
}

/* ---------------------------------------------------------------- the frame
   Two 1px rules at the container edges, running the whole page. Explicitly positioned,
   non-repeating gradients: no repeat means no stray extra lines, and the positions are whole
   pixels from the centre so they stay crisp. */
.frame-rules {
  background-image:
    linear-gradient(var(--rule), var(--rule)),
    linear-gradient(var(--rule), var(--rule));
  background-repeat: no-repeat, no-repeat;
  background-size: 1px 100%, 1px 100%;
  background-position: calc(50% - (var(--container) / 2) - var(--frame-pad)) 0,
                       calc(50% + (var(--container) / 2) + var(--frame-pad) - 1px) 0;
}
/* 1120 container + 2x32 pad + 2x24 minimum outside air = 1232. Below that the frame is blanked
   rather than squeezed against the viewport edge, which is what it already did at 1200. */
@media (max-width: 1231px) { .frame-rules { background-image: none; } }

.container {
  width: min(var(--container), 100% - (var(--gutter) * 2));
  margin-inline: auto;
}

/* a section is one screen or less, with a hairline under it, full bleed */
.section { padding: var(--pad-y) 0; border-bottom: var(--hair); position: relative; }
.section.band-tint { background: var(--tint); }

/* ---------------------------------------------------------------- nav */
.nav {
  position: fixed; inset: 0 0 auto; z-index: 60; height: var(--nav-h);
  background: var(--card); border-bottom: var(--hair);
}
.nav-inner { height: 100%; display: flex; align-items: center; gap: 28px; }
.brand { display: inline-flex; align-items: center; flex: none; min-height: 40px; }
.brand img { height: 40px; width: auto; display: block; }
.nav-links { display: flex; align-items: center; gap: 26px; margin-left: auto; }
/* every inline control clears the WCAG 2.2 AA 24px target minimum */
.nav-links a {
  font-size: 15px; font-weight: 400; color: var(--body); text-decoration: none; white-space: nowrap;
  display: inline-flex; align-items: center; min-height: 28px;
  transition: color .2s var(--ease);
}
.nav-links a:hover { color: var(--ink); text-decoration: underline; }
/* On a phone: lockup, the button, and a menu (BRIEF.md). The menu is a native <details>, so it
   opens and closes with no JavaScript at all; its four links are in every footer as well. */
.nav-menu { display: none; position: relative; margin-left: auto; }
.nav-menu > summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 7px;
  height: 40px; padding: 0 14px; border: var(--hair); border-radius: 999px;
  font-size: 14px; color: var(--ink); background: var(--card);
}
.nav-menu > summary::-webkit-details-marker { display: none; }
.nav-menu .bars { display: grid; gap: 3px; }
.nav-menu .bars i { display: block; width: 14px; height: 1.5px; background: currentColor; border-radius: 1px; }
.nav-menu .sheet {
  position: absolute; right: 0; top: calc(100% + 10px); z-index: 70;
  display: grid; min-width: 190px; padding: 8px;
  background: var(--card); border: var(--hair); border-radius: 16px;
  box-shadow: 0 14px 34px rgba(14, 22, 24, .12);
}
.nav-menu .sheet a { display: flex; align-items: center; min-height: 40px; padding: 0 12px; border-radius: 10px; color: var(--body); text-decoration: none; font-size: 15px; }
.nav-menu .sheet a:hover { background: var(--page); }
@media (max-width: 760px) {
  .nav-links { display: none; }
  .nav-menu { display: block; }
  .nav-inner { gap: 10px; }
  .nav .btn { margin-left: 0; }
}
@media (max-width: 400px) { .nav .btn { padding: 0 14px; } .nav-menu > summary { padding: 0 11px; } }
.nav .btn { flex: none; }

/* the page starts under the fixed bar; anchors land below it too */
main { padding-top: var(--nav-h); }
:target, [id] { scroll-margin-top: calc(var(--nav-h) + 20px); }

/* ---------------------------------------------------------------- buttons: one filled, one outlined */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  height: 40px; padding: 0 20px; border-radius: 999px;
  font: inherit; font-size: 14px; font-weight: 500; line-height: 1;
  text-decoration: none; cursor: pointer; white-space: nowrap;
  border: 1px solid transparent; background: none;
  transition: background-color .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease);
}
.btn-fill { background: var(--accent); color: #fff; }
.btn-fill:hover { background: #0a6446; }
.btn-line { border-color: var(--ink); color: var(--ink); }
.btn-line:hover { background: rgba(14, 22, 24, .05); }
.btn-big { height: 44px; padding: 0 26px; font-size: 15px; }

/* a chip: hairline, small, for the six questions and the business switcher */
.chip {
  display: inline-flex; align-items: center; min-height: 34px; padding: 0 14px;
  border: var(--hair); border-radius: 999px; background: var(--card);
  font: inherit; font-size: 13px; color: var(--body); cursor: pointer;
  transition: border-color .2s var(--ease), background-color .2s var(--ease), color .2s var(--ease);
}
.chip:hover { border-color: var(--ink); }
.chip.is-on { background: var(--ink); border-color: var(--ink); color: #fff; }

/* ---------------------------------------------------------------- arrival
   Content is VISIBLE by default. `html.js` hides it; a passive, rAF-coalesced scroll pass reveals
   it; a 2s safety timer reveals everything regardless. Readability never waits on the animation.
   IntersectionObserver is deliberately not used: the previous build recorded it never firing here. */
html.js .arrive { opacity: 0; transform: translateY(12px); }
html.js .arrive.is-in { opacity: 1; transform: none; transition: opacity .38s var(--ease), transform .38s var(--ease); }
html.js .arrive.is-in[data-step="1"] { transition-delay: .07s; }
html.js .arrive.is-in[data-step="2"] { transition-delay: .14s; }
html.js .arrive.is-in[data-step="3"] { transition-delay: .21s; }
@media (prefers-reduced-motion: reduce) {
  html.js .arrive { opacity: 1; transform: none; }
  html.js .arrive.is-in { transition: none; }
}

/* ================================================================ 01 HERO: THE STORY
   HERO-STORY-2026-09-12.md. Eight beats on a 31s loop, driven by ONE attribute on .scene.

   TWO RULES THIS BLOCK IS WRITTEN AGAINST:
   1. With JavaScript OFF the scene is ordinary flow: the chat, then the email, both untransformed,
      unclipped and fully readable. EVERY piece of positioning, clipping and transform below is
      under `html.js`. Read this block by that prefix and the no-JS state is the rest.
   2. prefers-reduced-motion does not merely stop the timer: it PINS the scene to beat 8, its
      resting state, explicitly.

   The brief's motion budget ("200-400ms, one thing moving per screen") does not govern this
   section: HERO-STORY-2026-09-12.md is Justin's own instruction and supersedes it FOR THE HERO.
   Every other section still keeps the budget. */
.hero { padding-top: 56px; padding-bottom: 72px; }
.hero-grid { display: grid; grid-template-columns: .84fr 1.16fr; column-gap: 64px; row-gap: 16px; align-items: center; }
.hero-copy h1 { max-width: 15ch; margin-bottom: 18px; }
.hero-copy .lede { max-width: 46ch; color: var(--body); }
.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin: 28px 0 14px; }

/* the panel the scene plays on. 600x520 (Justin, 12 September: the old 620x560 with a 340-wide
   widget was "too zoomed in"), and the mark behind it is a WATERMARK, not a picture. */
.hero-panel {
  position: relative; border-radius: var(--r-panel);
  min-height: 520px; max-width: 600px; padding: 24px;
  background-color: #146B51;
  background-image:
    radial-gradient(78% 58% at 22% 8%, rgba(255, 255, 255, .34), rgba(255, 255, 255, 0) 62%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='.62'/%3E%3C/svg%3E"),
    linear-gradient(152deg, #0B5F44 0%, #0D7C56 26%, #3C8A70 58%, #9FC7B6 84%, #D6E7DF 100%);
  background-size: cover, 160px 160px, cover;
  background-blend-mode: soft-light, overlay, normal;
}
/* EVERY rule that makes the panel a fixed, clipped stage is gated on `.is-animated`, which the
   script adds only when GSAP is really there and the scene has been prepared. Without it the
   panel grows to fit and the scene reads as ordinary flow — which is what a missing gsap.min.js
   used to turn into an empty gradient. */
html.js .hero-panel:has(.scene.is-animated) { height: 520px; min-height: 0; overflow: hidden; }
.hero-mark {
  position: absolute; right: -18%; bottom: -22%; width: 760px; height: auto;
  opacity: .09; pointer-events: none; will-change: transform;
}
.hero-caption { grid-column: 2; margin: 16px 0 0; }
@media (max-width: 900px) { .hero-caption { grid-column: 1; order: -1; margin: -8px 0 0; } }

/* ---- the scene ---------------------------------------------------------------------------
   No JavaScript, or no GSAP: a plain column, chat then email then caption, nothing hidden,
   nothing moved, nothing clipped. Everything below is gated on BOTH `html.js` and
   `.scene.is-animated`. */
.scene { position: relative; display: grid; gap: 24px; justify-items: center; }
.scene-chat, .scene-mail { margin: 0; width: min(300px, 100%); }
.scene-launcher, .scene-envelope, .scene-inbox { display: none; }   /* decoration: aria-hidden, no text */
.scene-wf { width: 100%; }
.scene-pane { max-height: none; }
.scene-caption { font-size: 13px; line-height: 1.5; color: rgba(255, 255, 255, .92); text-align: center; margin: 0; }
/* The scene widget is the product seen SMALLER: 300px at 13px with 10px bubble padding, which is
   the 340/14 cut-out's proportions one size down (Justin: "making the text smaller so the chat
   window doesn't feel so flooded"). */
/* `--fs` is the widget's OWN size token, written inline by build-data.mjs and used by the header,
   the input and the send button as well. Setting `font-size` alone left those at 14px and the
   bubbles inherited from the token, not from the element: the fit test read 14px and said so. */
.scene-wf { --fs: 13px; font-size: 13px; }
.scene-wf .msg { padding: 10px; line-height: 1.4; }
.scene-wf .head { padding: 11px 13px; }
.scene-wf .bar { padding: 9px 10px; }

html.js .scene.is-animated { display: block; height: 100%; }
html.js .scene.is-animated .scene-chat,
html.js .scene.is-animated .scene-mail {
  position: absolute; left: 50%; top: 50%; width: min(300px, 100%);
  margin: 0; opacity: 0; pointer-events: none;
  transform: translate(-50%, -50%);
}
/* the window's height is the panel less 24px of clear air top and bottom, and the LOG is the
   clipped middle: header and input bar are `flex: none`, so the log takes what is left and
   clips it. Nothing inside it is ever display-toggled. */
html.js .scene.is-animated .scene-wf { height: 472px; }
html.js .scene.is-animated .scene-pane { height: 472px; max-height: 472px; overflow: hidden; }
/* TOP-anchored, and the track is what moves. The old bottom-anchored log is why round 2 had to
   `display: none` the unspoken turns: with the log filling from the bottom, six laid-out turns
   pushed the spoken ones out of the window. Top-anchored, every turn keeps its place and the
   track slides. */
html.js .scene.is-animated .scene-wf .log {
  justify-content: flex-start; overflow: hidden; position: relative; padding: 12px;
}
html.js .scene.is-animated .log-track { display: flex; flex-direction: column; gap: 8px; will-change: transform; }
html.js .scene.is-animated .scene-caption {
  position: absolute; left: 0; right: 0; bottom: 14px; opacity: 0;
}

/* the decorative pieces come back as blocks only when the script is driving them */
html.js .scene.is-animated .scene-launcher {
  display: grid; place-items: center;
  position: absolute; right: 6%; bottom: 7%; width: 58px; height: 58px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 8px 22px rgba(6, 40, 28, .38);
  opacity: 0;
}
html.js .scene.is-animated .scene-launcher img { width: 30px; height: auto; display: block; }

html.js .scene.is-animated .scene-envelope {
  display: block; position: absolute; left: 50%; top: 50%; width: 190px; height: 128px;
  margin: -64px 0 0 -95px; opacity: 0;
}
html.js .scene.is-animated .scene-envelope .sheet {
  position: absolute; inset: 0; border-radius: 10px; background: #fff;
  box-shadow: 0 14px 34px rgba(6, 40, 28, .32);
}
html.js .scene.is-animated .scene-envelope .flap {
  position: absolute; left: 0; right: 0; top: 0; height: 64px; z-index: 2;
  background: #EAF3EE; border-radius: 10px 10px 0 0;
  clip-path: polygon(0 0, 100% 0, 50% 100%);
  transform-origin: 50% 0; transform: rotateX(180deg);
}
html.js .scene.is-animated .scene-inbox {
  display: grid; gap: 8px; position: absolute; left: 50%; top: 50%; width: 260px;
  margin: -46px 0 0 -130px; opacity: 0;
}
html.js .scene.is-animated .scene-inbox i {
  display: block; height: 26px; border-radius: 6px; background: rgba(255, 255, 255, .55);
  position: relative;
}
/* the highlight is its own layer, so the row lights by OPACITY and never by background-color:
   T-MOTION allows transform and opacity, and a colour tween would fail it */
html.js .scene.is-animated .scene-inbox i.lit b {
  content: ""; position: absolute; inset: 0; border-radius: 6px; display: block;
  background: #fff; box-shadow: 0 6px 18px rgba(6, 40, 28, .22); opacity: 0;
}

/* the per-word reveal, spans written at BUILD time. The start state only; GSAP owns the rest,
   one word at a time, which is what stops the whole turn arriving as a block. */
html.js .scene.is-animated .w { opacity: 0; }
/* the BUBBLE starts invisible too: a bubble has a background, so an unspoken turn would sit
   there as an empty coloured shape. Opacity, never display: the line keeps its space, which is
   what the measured slide distances are computed against. */
html.js .scene.is-animated .log-track > .line[data-turn],
html.js .scene.is-animated .log-track > .line[data-greeting] { opacity: 0; }
html.js .scene.is-animated .dots-line { opacity: 0; }

/* the tick that closes the conversation. Decoration: aria-hidden and wordless, so T-JSOFF's
   decorative test passes it, and with JavaScript off it simply sits there. */
.scene-tick {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; margin-top: 6px; color: var(--accent);
}
.scene-tick svg { width: 100%; height: 100%; }
html.js .scene.is-animated .scene-tick { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  html.js .scene.is-animated .scene-tick { opacity: 1 !important; }
}

/* ---- reduced motion: the scene rests on the 16-second frame, in CSS, and no timeline is ever
   built. Two independent mechanisms: this block does not need GSAP, and the script's own
   decision not to animate does not need this block. ---- */
@media (prefers-reduced-motion: reduce) {
  html.js .scene.is-animated .scene-launcher,
  html.js .scene.is-animated .scene-envelope,
  html.js .scene.is-animated .scene-inbox,
  html.js .scene.is-animated .scene-chat { opacity: 0 !important; }
  html.js .scene.is-animated .scene-mail { opacity: 1 !important; transform: translate(-50%, -50%) !important; }
  /* the resting frame is the SUMMARY, so reduced motion keeps the MEASURED scroll and only drops
     the movement. transform:none would show the top of the email, which is not where it ends. */
  html.js .scene.is-animated .scene-pane .email-sheet { transform: translateY(calc(-1 * var(--email-rest, 0px))) !important; }
  html.js .scene.is-animated .scene-caption { opacity: 1 !important; }
  html.js .scene.is-animated .w { opacity: 1 !important; }
  html.js .scene.is-animated .log-track > .line[data-turn],
  html.js .scene.is-animated .log-track > .line[data-greeting] { opacity: 1 !important; }
  html.js .scene.is-animated .dots-line { opacity: 0 !important; }
}

@media (max-width: 900px) {
  /* the phone gets the scene ABOVE the copy (BRIEF.md), cropped to the widget */
  .hero-grid { grid-template-columns: 1fr; column-gap: 0; row-gap: 28px; }
  .hero-panel { order: -1; padding: 20px 14px; min-height: 0; max-width: none; }
  html.js .hero-panel:has(.scene.is-animated) { height: 470px; }
  html.js .scene.is-animated .scene-wf,
  html.js .scene.is-animated .scene-pane { height: 422px; max-height: 422px; }
  .scene-chat, .scene-mail,
  html.js .scene.is-animated .scene-chat,
  html.js .scene.is-animated .scene-mail { width: min(300px, 100%); }
}

/* ================================================================ 02 SCRIPTED OR AI */
.headline-centre { text-align: center; max-width: 760px; margin-inline: auto; }
.headline-centre .lede { color: var(--muted); max-width: 62ch; margin: 16px auto 0; }

.ab { display: grid; grid-template-columns: 1fr 1fr; margin-top: 48px; }
.ab-col { padding: 0 40px; }
.ab-col + .ab-col { border-left: var(--hair); }
.ab-col h3 { margin-bottom: 26px; }
.ab-col ul { list-style: none; margin: 0; padding: 0; }
.ab-col li {
  display: grid; grid-template-columns: 22px 1fr; gap: 12px; align-items: start;
  padding: 18px 0; font-size: 16px; line-height: 1.5;
}
.ab-col li + li { border-top: 1px dotted var(--rule); }
.ab-col .g { font-size: 15px; line-height: 1.6; }
.ab-no li, .ab-no h3 { color: var(--muted); }
.ab-no .g { color: var(--muted); }
.ab-yes li { color: var(--ink); }
.ab-yes .g { color: var(--accent); }
.ab-close { text-align: center; margin-top: 36px; }

@media (max-width: 860px) {
  .ab { grid-template-columns: 1fr; margin-top: 36px; }
  .ab-col { padding: 0; }
  .ab-col + .ab-col { border-left: 0; border-top: var(--hair); margin-top: 28px; padding-top: 28px; }
}

/* ================================================================ 03 THREE VOICES */
.voices-head { max-width: 900px; }
.voices-head .lede { color: var(--muted); margin-top: 16px; }
.qchips { display: flex; flex-wrap: wrap; gap: 8px; margin: 26px 0 12px; }
.handoff-label { max-width: 74ch; margin: 0 0 18px; }
html.js .handoff-label { display: none; }
html.js .voices[data-q="q6"] .handoff-label { display: block; }

/* The three widgets are drawn at their TRUE size and then zoomed out, so what is on the page is
   the real product seen from a little further back rather than a squashed version of it. Scaling
   the whole widget keeps every proportion the client would actually get. */
.voice-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 28px; align-items: start; }
.voices .voice-row { margin-bottom: -6px; }
.voice { --zoom: .82; --true-w: 360px; --true-h: 470px; display: grid; justify-items: center; }
/* Justin, 12 September: 12.5px, and the log fills from the TOP with the greeting above the
   exchange, so a short pair does not leave a void at the top of the window. */
.voice .wf { font-size: 12.5px; }
.voice .wf .log { justify-content: flex-start; }
.voice .wf {
  width: var(--true-w); min-height: var(--true-h);
  transform: scale(var(--zoom)); transform-origin: top center;
}
html.js .voice .wf { height: var(--true-h); }
/* the zoom is a transform, so the box it leaves behind is still full size: reserve the seen height */
.voice-stage { height: calc(var(--true-h) * var(--zoom)); display: grid; justify-items: center; }
.voice-name { margin: 12px 0 2px; }
.voice-note { font-size: 14px; color: var(--muted); }
.biz-switch { display: none; }

/* the assistant's own opening line, above whichever exchange is chosen, so a widget showing one
   short answer is not two-thirds empty (punch-list 5). Only the Tipsy Ferret has one on record. */
.voice-greet { opacity: .9; }

/* the 18 answers: all in the HTML, one generic rule, scoped under html.js */
html.js .voices .vx { display: none; }
html.js .voices[data-q="q1"] .vx[data-q="q1"],
html.js .voices[data-q="q2"] .vx[data-q="q2"],
html.js .voices[data-q="q3"] .vx[data-q="q3"],
html.js .voices[data-q="q4"] .vx[data-q="q4"],
html.js .voices[data-q="q5"] .vx[data-q="q5"],
html.js .voices[data-q="q6"] .vx[data-q="q6"] { display: block; }

/* the [Passing you to …] line is a visible STATE row, not body text */
.state {
  display: flex; align-items: center; gap: 8px; align-self: center;
  margin: 4px 0; padding: 5px 12px; border-radius: 999px;
  border: 1px dashed var(--line, #e5e7eb); color: var(--muted, #6b7280);
  font-size: 12px; letter-spacing: .04em; text-transform: uppercase;
}

@media (max-width: 1180px) { .voice { --zoom: .76; } }
@media (max-width: 1040px) { .voice { --zoom: .70; } }
@media (max-width: 980px) {
  /* one at a time on a phone, so it can go back to full size */
  .voice { --zoom: 1; --true-w: min(360px, 100%); }
  .voice-row { grid-template-columns: 1fr; gap: 0; }
  .biz-switch { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 22px; }
  html.js .voice { display: none; }
  html.js .voices[data-biz="two-elms"]     .voice[data-biz="two-elms"],
  html.js .voices[data-biz="tipsy-ferret"] .voice[data-biz="tipsy-ferret"],
  html.js .voices[data-biz="nethercote-dental"] .voice[data-biz="nethercote-dental"] { display: grid; }
  .voice + .voice { margin-top: 40px; }
}

/* ================================================================ 04 WHAT ARRIVES */
.split { display: grid; grid-template-columns: .9fr 1.1fr; gap: 64px; align-items: start; }
.split .lede + .lede { margin-top: 18px; }
.lede-gap { margin-top: 20px; }
.note-gap { margin-top: 24px; }
.email-panel {
  background: var(--tint); border-radius: var(--r-panel); padding: 28px;
  border: var(--hair);
}
.email-pane {
  max-height: 640px; overflow-y: auto; background: #fff; border-radius: 14px;
  padding: 22px 20px; border: 1px solid #e5e7eb;
}
/* the email is the REAL generated artefact, inline styles and all. Reset what it inherits from
   the page and otherwise do not touch it: hand-editing it would void the only thing that makes
   it evidence. Its own type is its own; the page's 44px/500 rule is about the page. */
.email-sheet { font-size: 13.5px; line-height: 1.5; color: #1d1d1f; text-align: left; }
.email-sheet * { max-width: 100%; }

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

/* ================================================================ 05 HOW IT WORKS */
.head-split { display: grid; grid-template-columns: 1fr 1fr; gap: 64px; align-items: start; }
.head-split .lede { color: var(--muted); }
.head-split .lede + .lede { margin-top: 16px; }

.siteframe-panel {
  margin-top: 48px; background: var(--tint); border: var(--hair);
  border-radius: var(--r-panel); padding: 34px 34px 0;
}
.siteframe {
  position: relative; background: #fff; border: 1px solid #dfe8e4;
  border-radius: 12px 12px 0 0; overflow: hidden; box-shadow: 0 10px 30px rgba(14, 22, 24, .07);
}
.sf-chrome {
  display: flex; align-items: center; gap: 10px; padding: 10px 14px;
  border-bottom: 1px solid #edf2f0; background: #fafcfb;
}
.sf-dots { display: flex; gap: 5px; }
.sf-dots i { width: 8px; height: 8px; border-radius: 50%; background: #dfe8e4; display: block; }
.sf-url {
  font-size: 12px; color: var(--muted); background: #f1f5f3; border-radius: 999px;
  padding: 4px 12px;
}
.sf-body { position: relative; height: 210px; padding: 22px 26px; }
.sf-nav { display: flex; gap: 20px; font-size: 12px; color: var(--muted); margin-bottom: 26px; }
.sf-h { height: 13px; width: 46%; border-radius: 7px; background: #e6efeb; margin-bottom: 12px; }
.sf-l { height: 8px; border-radius: 4px; background: #eef4f1; margin-bottom: 9px; }
.sf-l.w1 { width: 62%; } .sf-l.w2 { width: 54%; } .sf-l.w3 { width: 40%; }
.sf-launcher {
  position: absolute; right: 20px; bottom: 20px; width: 44px; height: 44px; border-radius: 50%;
  background: var(--accent); display: grid; place-items: center;
  box-shadow: 0 6px 18px rgba(13, 124, 86, .28);
}
.sf-launcher svg { width: 22px; height: 22px; fill: #fff; }
.sf-caption { text-align: center; padding: 16px 0 20px; }

.steps { display: grid; grid-template-columns: repeat(4, 1fr); gap: 32px; margin-top: 44px; }
.step-n { font-size: 13px; letter-spacing: .09em; color: var(--accent); margin: 0 0 10px; }
.step h3 { margin-bottom: 8px; }
.step p { font-size: 14px; line-height: 1.55; color: var(--muted); }

@media (max-width: 900px) {
  .head-split { grid-template-columns: 1fr; gap: 24px; }
  .steps { grid-template-columns: 1fr 1fr; gap: 26px; }
  .siteframe-panel { padding: 20px 20px 0; }
  .sf-body { height: 170px; padding: 18px; }
}
@media (max-width: 560px) { .steps { grid-template-columns: 1fr; } }

/* ================================================================ 06 PICK A LOOK
   No box round each sample. The widget floats on the page ground and the skin's own colour glows
   behind it — the asset handover's "the glow colour washes the carousel section" — so the colour
   comes off the product rather than being drawn round it in a frame.
   The wash is six PRE-PAINTED layers crossfaded by opacity, never an animated background colour,
   so nothing but opacity ever moves. */
.looks { position: relative; }
.looks-head { max-width: 760px; }
.looks-head .lede { color: var(--muted); max-width: 52ch; margin-top: 16px; }

.arrow {
  width: 40px; height: 40px; border-radius: 50%; background: var(--card);
  border: 1px solid color-mix(in srgb, var(--live-skin, var(--rule)) 45%, var(--rule));
  display: grid; place-items: center; cursor: pointer; padding: 0; flex: none;
  transition: border-color .25s var(--ease), background-color .2s var(--ease);
}
.arrow:hover { border-color: color-mix(in srgb, var(--live-skin, var(--ink)) 85%, var(--ink)); }
.arrow svg { width: 16px; height: 16px; display: block; stroke: var(--ink); fill: none; stroke-width: 1.8; }

.looks-viewport { position: relative; margin-top: 30px; }
/* the section fades at its top and bottom edges so it never reads as a block dropped in */
.looks-viewport::before, .looks-viewport::after {
  content: ""; position: absolute; left: 0; right: 0; height: 56px; z-index: 3; pointer-events: none;
}
.looks-viewport::before { top: -2px; background: linear-gradient(var(--page), rgba(241, 247, 245, 0)); }
.looks-viewport::after { bottom: -2px; background: linear-gradient(rgba(241, 247, 245, 0), var(--page)); }

/* the glow: one layer per skin, all six painted at first byte, crossfaded by opacity alone */
.looks-glow { position: absolute; inset: -30px 0 40px; z-index: 0; pointer-events: none; }
.looks-glow i {
  position: absolute; inset: 0; display: block;
  background:
    radial-gradient(26% 40% at 50% 42%, color-mix(in srgb, var(--skin) 62%, transparent) 0%, transparent 72%),
    radial-gradient(48% 56% at 50% 44%, color-mix(in srgb, var(--skin) 30%, transparent) 0%, transparent 74%),
    radial-gradient(92% 74% at 50% 46%, color-mix(in srgb, var(--skin) 12%, transparent) 0%, transparent 76%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 18%, #000 82%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 18%, #000 82%, transparent 100%);
}
.looks-glow i + i { opacity: 0; }
html.js .looks-glow i { opacity: 0; transition: opacity .5s var(--ease); }
html.js .looks-glow i.is-on { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  html.js .looks-glow i { transition: none; }
  .arrow { transition: none; }
}

.looks-track {
  position: relative; z-index: 1;
  display: flex; gap: 40px; overflow-x: auto; scroll-snap-type: x mandatory;
  padding: 24px calc(50% - 170px) 8px; margin: 0; list-style: none;
  scrollbar-width: none; -ms-overflow-style: none;
}
.looks-track::-webkit-scrollbar { display: none; }
/* the neighbours fade out before they reach the hairline frame, so nothing is cut by the page
   edge (punch-list 5). The mask is on the track, so the clones that make the loop continuous
   disappear into the ground rather than announcing themselves. */
.looks-track {
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 14%, #000 86%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 14%, #000 86%, transparent 100%);
}
.look { flex: none; width: 340px; scroll-snap-align: center; }
/* Variant M: the neighbours are still smaller and quieter, but the DIM is on the card (the
   widget in its wash), not on the label. A label dimmed to 40% on the mid band is 2.6:1, which
   tools/bands.mjs measured and refused; the name and tier under a neighbour now read at full ink. */
/* Justin, 12 September (second look): the left and right samples 20% smaller again, so .7 of
   full size, and the chevrons on the samples' vertical middle. The scale is on the CARD, about
   its own centre, so every sample's middle sits on one line with the arrows; the label keeps its
   full size and is lifted by half the height the card gave up, so it still sits just under it.
   Transform and opacity only, as everywhere. */
html.js .look .look-card { opacity: .4; transform: scale(.7); transform-origin: 50% 50%;
  transition: opacity .4s var(--ease), transform .4s var(--ease); }
html.js .look .look-label { transform: translateY(calc(var(--look-h) * -0.15)); transition: transform .4s var(--ease); }
html.js .look.is-live .look-card { opacity: 1; transform: none; }
html.js .look.is-live .look-label { transform: none; }
@media (prefers-reduced-motion: reduce) {
  html.js .look .look-card { opacity: 1; transform: none; transition: none; }
  html.js .look .look-label { transform: none; transition: none; }
}
/* the widget carries the bloom itself, in its own skin colour, so the colour belongs to the
   product rather than to a panel drawn around it */
.look .wf {
  width: 340px; min-height: 528px;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .62), 0 0 0 2px rgba(0, 0, 0, .5),
              0 18px 40px color-mix(in srgb, var(--skin) 26%, transparent),
              0 26px 60px rgba(14, 22, 24, .14);
}
html.js .look .wf { height: 528px; }

/* the label sits under the sample, and the chevrons flank it */
.look-label { text-align: center; margin-top: 22px; }
.look-label h3 { color: var(--ink); }
.look-label p { font-size: 14px; color: var(--muted); }

.looks-controls {
  position: absolute; left: 0; right: 0; bottom: 22px; z-index: 4;
  display: flex; align-items: center; justify-content: center; pointer-events: none;
}
.looks-controls .arrow { pointer-events: auto; }
.looks-controls .spacer { width: 340px; flex: none; }
.looks-controls .arrow:first-child { margin-right: 26px; }
.looks-controls .arrow:last-child { margin-left: 26px; }
.looks-hint { text-align: center; margin-top: 10px; }

@media (max-width: 760px) {
  .looks-track { padding-inline: calc(50% - 148px); gap: 28px; }
  .look, .looks-controls .spacer, .look .wf { width: 296px; }
  .looks-controls .arrow:first-child { margin-right: 8px; }
  .looks-controls .arrow:last-child { margin-left: 8px; }
}
@media (max-width: 420px) {
  .look, .looks-controls .spacer, .look .wf { width: 250px; }
  .looks-track { padding-inline: calc(50% - 125px); }
  .looks-controls .arrow:first-child { margin-right: 2px; }
  .looks-controls .arrow:last-child { margin-left: 2px; }
}

/* ================================================================ 07 PRICES */
/* The switch from assets/pricing-ladder-mock.html: above the table, on the right, default OFF.
   The mock swaps the figures with JavaScript; here BOTH figures and BOTH notes are in the HTML at
   first byte and CSS chooses, so the prices read and switch with JavaScript off. */
.prices-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 28px; }
.prices-head .lede { color: var(--body); margin-top: 10px; }
.vat { display: flex; align-items: center; gap: 10px; font-size: 14px; color: var(--body); white-space: nowrap; flex: none; padding-bottom: 4px; }
.vat label { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; min-height: 32px; }
.vat .sw { position: relative; width: 40px; height: 22px; border-radius: 999px; background: #C9D8D2; flex: none; transition: background-color .2s var(--ease); }
.vat .sw::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%;
  background: #fff; transition: transform .2s var(--ease);
}
html:has(#vat-inc:checked) .vat .sw { background: var(--accent); }
html:has(#vat-inc:checked) .vat .sw::after { transform: translateX(18px); }
.vat input:focus-visible + label .sw { outline: 2px solid var(--accent); outline-offset: 3px; }
.vatnote { font-size: 12px; color: var(--muted); }

/* ---- the short ladder. Every column is the same fixed grid, so the dashed dividers, the prices
   and the "plus" lines align across all four, and the conversations line comes FIRST in each
   list so nobody adds 150 and 500 together. ---- */
/* The rows are declared ONCE, on the ladder, and every column subgrids onto them, so the dashed
   dividers, the prices and the "plus" lines align across all four without a single hard-coded
   height. The previous `26px 66px 116px 1px 30px 1fr 1px auto` had to be re-tuned whenever the
   copy or the type size moved, and let content cross a divider if an audience line wrapped. */
.ladder {
  display: grid; grid-template-columns: repeat(4, 1fr);
  grid-template-rows: auto auto auto auto auto 1fr auto auto;
  border: 1px dashed var(--rule); background: var(--card); margin-top: 26px;
}
.lcol {
  padding: 24px 22px 22px; border-left: 1px dashed var(--rule);
  display: grid; grid-row: 1 / -1; grid-template-rows: subgrid; align-items: start;
}
.lcol:first-child { border-left: 0; }
/* The mock tints the featured column, but the whole section already sits on the tint band, so
   the tint would not read. Enhanced is marked the way Justin asked for it on 11 September
   instead: the accent edge, on BOTH sides. */
.lcol.featured { background: var(--tint); box-shadow: inset 2px 0 0 var(--accent), inset -2px 0 0 var(--accent); }
.lname { font-size: 17px; font-weight: 500; color: var(--ink); margin: 0; }
/* Justin, 12 September: the ladder's general text a few points smaller. 12px is the floor here:
   the page's own smallest type is 12px, and below that the detector's tiny-text warning starts.
   The tier names and the prices are NOT reduced; they are the thing being read. */
.lwho { font-size: 13px; color: var(--body); line-height: 1.4; margin: 2px 0 0; }
.lpricebox { padding-top: 6px; }
.lprice { color: var(--ink); margin: 0; line-height: 1.1; white-space: nowrap; }
.lprice b { font-size: 30px; font-weight: 400; letter-spacing: -.02em; }
.lprice s { font-size: 18px; color: var(--muted); font-weight: 400; margin-right: 8px; text-decoration-thickness: 1.5px; }
.lprice .from { font-size: 16px; color: var(--body); margin-right: 4px; }
.lper { font-size: 12px; color: var(--body); margin: 6px 0 0; }
.lthen { font-size: 12px; color: var(--muted); margin: 2px 0 0; white-space: nowrap; }   /* one fact per line; never wraps into the divider */
.ldiv { border-top: 1px dashed var(--rule); }
.lplus { font-size: 12px; color: var(--muted); margin: 0; padding-top: 10px; }
.llist { list-style: none; padding: 12px 0; margin: 0; }
.llist li { display: flex; gap: 10px; align-items: flex-start; font-size: 13px; color: var(--body); margin: 0 0 9px; }
.ltick { flex: none; width: 16px; height: 16px; border-radius: 50%; background: #D5EBE1; display: inline-flex; align-items: center; justify-content: center; margin-top: 2px; }
.ltick::after { content: ""; width: 5px; height: 8px; border: solid var(--accent); border-width: 0 1.5px 1.5px 0; transform: rotate(45deg) translate(-1px, -1px); }
.lbtn { width: 100%; margin-top: 16px; align-self: end; }

/* ---- the full settled grid, one click beneath. Ships OPEN so it reads with JavaScript off. ---- */
.compare { margin-top: 22px; border-top: var(--hair); }
.compare > summary {
  display: inline-flex; align-items: center; gap: 8px; min-height: 44px;
  cursor: pointer; list-style: none; color: var(--accent); font-size: 15px;
}
.compare > summary::-webkit-details-marker { display: none; }
.compare > summary:hover span:first-child { text-decoration: underline; }
.compare .chev { transition: transform .2s var(--ease); }
.compare[open] .chev { transform: rotate(90deg); }

@media (max-width: 980px) {
  /* two abreast, so TWO bands of the same eight rows: the pair that is side by side still aligns.
     No `grid-template-rows: auto` on .lcol here — that is exactly what would cancel the subgrid. */
  .ladder { grid-template-columns: 1fr 1fr; grid-template-rows: repeat(2, auto auto auto auto auto 1fr auto auto); }
  .lcol:nth-child(-n+2) { grid-row: 1 / 9; border-top: 0; }
  .lcol:nth-child(n+3) { grid-row: 9 / -1; border-top: 1px dashed var(--rule); }
  .lcol:nth-child(odd) { border-left: 0; }
}
@media (max-width: 700px) {
  .prices-head { flex-direction: column; align-items: flex-start; gap: 16px; }
  /* one column: there is nothing beside a column to align to, so each sizes itself.
     The reset has to match the 980px block's SPECIFICITY. `.lcol { grid-row: auto }` loses to
     `.lcol:nth-child(-n+2) { grid-row: 1 / 9 }` whatever the order, and the four columns then
     stack into two row bands on top of each other. Measured, not assumed: ladder-rows.mjs read
     all four buttons at the same y before this line was written the long way. */
  .ladder { grid-template-columns: 1fr; grid-template-rows: none; }
  .lcol:nth-child(-n+2), .lcol:nth-child(n+3) { grid-row: auto; }
  .lcol { grid-template-rows: auto; border-left: 0; }
  .lcol:first-child { border-top: 0; }
  .lcol:nth-child(2) { border-top: 1px dashed var(--rule); }
}
/* CSS only, so it works with JavaScript off. Both figures are in the HTML at first byte. */
html:has(#vat-inc:checked) [data-ex] { display: none !important; }
html:has(#vat-inc:checked) [data-inc] { display: inline !important; }

.tiers-wrap {
  margin: 40px 0 0; overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: 8px;
  /* the right edge fades while there is more table to the right: an affordance, not a sentence */
  -webkit-mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 40px), transparent 100%);
  mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 40px), transparent 100%);
}
@media (min-width: 1100px) { .tiers-wrap { -webkit-mask-image: none; mask-image: none; } }
.tiers { width: 100%; min-width: 920px; border-collapse: collapse; font-size: 14px; text-align: left; }
.tiers th, .tiers td { vertical-align: top; padding: 12px 18px; border-bottom: var(--hair); }
.tiers thead th { border-bottom: 1px solid var(--ink); padding-top: 0; font-weight: 400; }
.tiers th[scope="row"] { width: 19%; min-width: 160px; padding-left: 0; font-weight: 400; color: var(--body); }
.tiers .tier { background: var(--card); width: 20%; }
/* Enhanced is edged in the accent on BOTH sides, so the column reads as one bounded thing */
.tiers .tier.is-featured { background: #fff; box-shadow: inset 2px 0 0 var(--accent), inset -2px 0 0 var(--accent); }
.tier-name { display: block; font-size: 18px; font-weight: 500; color: var(--ink); }
.tier-for { display: block; font-size: 13px; color: var(--muted); margin-top: 6px; line-height: 1.45; }
.row-price td { padding-top: 18px; padding-bottom: 18px; }
.was { color: var(--muted); margin-right: 8px; }
.now { font-size: 26px; font-weight: 500; color: var(--ink); display: inline-block; }
.now small { font-size: 14px; font-weight: 400; color: var(--muted); }
.per { display: block; font-size: 13px; color: var(--muted); margin-top: 4px; }
.tick { color: var(--accent); }
.no { color: var(--muted); }
.fn-mark { color: var(--accent); text-decoration: none; display: inline-grid; place-items: center; min-width: 24px; min-height: 24px; margin: -4px 0 -4px -2px; }
.row-cta td { border-bottom: 0; padding-top: 22px; }
.footnotes { margin: 22px 0 0; padding-left: 18px; font-size: 13px; line-height: 1.6; color: var(--muted); max-width: 96ch; }
.footnotes li + li { margin-top: 6px; }

/* ================================================================ 08 GET IN TOUCH */
.contact-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 56px; align-items: start; }
.contact-sub { font-size: 18px; color: var(--muted); margin-top: 10px; }
.contact-grid .lede { margin-top: 20px; max-width: 52ch; }
.contact-cta { margin-top: 26px; }
.contact-lines { display: flex; flex-wrap: wrap; gap: 8px 22px; margin-top: 22px; font-size: 14px; }
.contact-lines a { display: inline-flex; align-items: center; min-height: 28px; }
.contact-form { display: grid; gap: 16px; background: var(--card); border: var(--hair); border-radius: var(--r-panel); padding: 28px; }
.field { display: grid; gap: 6px; font-size: 13px; color: var(--muted); }
.field input, .field textarea {
  font: inherit; font-size: 15px; color: var(--ink); background: var(--page);
  border: var(--hair); border-radius: 10px; padding: 11px 13px; width: 100%;
}
.field textarea { resize: vertical; }
/* the slot the Turnstile widget occupies when the site goes live. A marked, empty shell, not a
   sentence: the decks give no wording for it and none is invented here. */
.turnstile-slot {
  border: 1px dashed var(--rule); border-radius: 10px; min-height: 62px;
  display: grid; place-items: center; font-size: 12px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted);
}
.form-note { font-size: 13px; color: var(--muted); }
.contact-form .btn { justify-self: start; }

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

/* ================================================================ 09 QUESTIONS */
.qa-list { margin-top: 36px; border-top: var(--hair); }
.qa { border-bottom: var(--hair); }
.qa > summary {
  display: flex; align-items: center; gap: 20px; justify-content: space-between;
  padding: 16px 4px; cursor: pointer; list-style: none;
}
.qa > summary::-webkit-details-marker { display: none; }
.qa > summary h3 { font-weight: 400; font-size: 18px; color: var(--ink); }
.qa-chev { flex: none; width: 16px; height: 16px; stroke: var(--muted); fill: none; stroke-width: 1.6;
  transition: transform .25s var(--ease); }
.qa[open] .qa-chev { transform: rotate(180deg); }
.qa .answer { padding: 0 40px 20px 4px; max-width: 84ch; }
.qa .answer p + p { margin-top: 14px; }
.more-link { font-size: 15px; display: inline-flex; align-items: center; min-height: 28px; }
.qa-more { margin-top: 26px; }

/* ================================================================ FOOTER */
.foot { padding: 56px 0 40px; background: var(--card); }
.foot-top { display: flex; flex-wrap: wrap; gap: 24px; align-items: center; justify-content: space-between; }
.foot img { height: 40px; width: auto; display: block; }
.foot-links { display: flex; flex-wrap: wrap; gap: 22px; }
.foot-links a { font-size: 14px; color: var(--body); text-decoration: none; display: inline-flex; align-items: center; min-height: 28px; }
.foot-links a:hover { text-decoration: underline; }
.foot-legal { margin-top: 32px; padding-top: 26px; border-top: var(--hair); font-size: 13px; line-height: 1.7; color: var(--muted); max-width: 92ch; }
.foot-legal p + p { margin-top: 10px; }

/* the fixed bottom-left Get in contact button was removed on variant M (Justin, 12 September);
   its rules went with it. The nav button carries the same data-ask and data-scroll. */

/* ================================================================ inner pages */
.page-head { padding: 72px 0 44px; border-bottom: var(--hair); }
.page-head h1 { max-width: 22ch; }
.page-head .lede { margin-top: 18px; color: var(--muted); max-width: 62ch; }
.prose { max-width: 68ch; padding: 56px 0; }
.prose h2 { margin: 40px 0 14px; }
.prose h2:first-child { margin-top: 0; }
.prose h3 { margin: 28px 0 10px; }
.prose p { margin: 0 0 16px; }
.prose ul, .prose ol { margin: 0 0 16px; padding-left: 22px; }
.prose li { margin-bottom: 8px; }
.prose blockquote { margin: 0 0 16px; padding: 0; }
.headshot { width: 200px; height: 200px; border-radius: 50%; display: block; margin: 0 0 28px; }
.faq-group { padding: 44px 0; border-bottom: var(--hair); }
.faq-group > h2 { margin-bottom: 8px; }
.placeholder {
  border: 1px dashed var(--rule); border-radius: var(--r-panel); padding: 28px;
  color: var(--muted); font-size: 15px;
}

/* ================================================================ VARIANT M: "BOXED"
   VARIANT-M-2026-09-12.md. L with depth and colour bands: the same copy, order, scale, frame,
   hero, ladder and carousel; only the SURFACES change. This block sits after every page rule so
   it wins the cascade at equal specificity, and BEFORE the widget block so T-TYPE reads it.

   Surfaces, top to bottom: white (nav, hero) -> tint (AI list) -> DARK band -> tint (voices,
   what you get) -> white (how it works, cards on a tint panel) -> MID band (pick a look) -> tint
   (prices, ladder on white) -> white (get in touch, form on tint) -> tint (questions) -> ink foot.

   Nothing here hides anything, animates anything, or adds a type size: the band statement reuses
   h2's clamp. Every colour on a band is checked twice: statically by T-BANDS (audit.mjs), which
   parses THESE rules, and on the rendered page by tools/bands.mjs. */
:root {
  --card-r: 16px;
  --card-shadow: 0 8px 30px rgba(14, 22, 24, .08);
  --card-line: 1px solid #BCD4CB;
  --look-h: 570px;     /* a carousel card: the 528px widget, 20px padding each side, 1px border each side */
}

/* Justin, 12 September, after the first look at M: "page looks better at 90% zoom, reduce
   everything on the site by 10%". This IS that: CSS zoom on the body is what the browser's 90%
   does, applied to every length on every page, the hero included, and to whatever hero is lifted
   from L later. Scroll positions are in the element's own pixels while client rects are in
   visual ones, so app.js divides the two measured deltas it uses by currentCSSZoom. */
body { zoom: .9; }
/* at zoom .9 a 24px target is 21.6 on screen: the one control sized exactly at the WCAG 2.2
   minimum, the footnote mark, grows so it is still 24 or more after the zoom (a11y.mjs fired) */
.fn-mark { min-width: 28px; min-height: 28px; }

/* ---- surfaces. Named by a data attribute so no class string a check anchors on changes. */
[data-surface="white"] { background: var(--card); }
[data-surface="tint"] { background: var(--tint); }
[data-surface="dark"] { background: #0E1618; }
[data-surface="mid"] { background: #22312E; }
.foot { background: #0E1618; }

/* Light type on the three dark surfaces: the tokens are re-declared HERE, and the inherited
   colour is set again from the new token (a descendant re-declaring --body does not recompute a
   colour the body already resolved: Codex, round 1). --hair is re-derived for the same reason:
   the root's --hair froze the light rule. The widgets inside are untouched: every skin writes its
   own --ink/--muted/--line inline, and inline wins. Bands have HARD edges: no hairline. */
[data-surface="dark"], [data-surface="mid"], .foot {
  --ink: #F1F7F5;
  --body: #E1EAE6;
  --muted: #B7C7C1;
  --rule: rgba(255, 255, 255, .18);
  --hair: 1px solid var(--rule);
  --ring: #F1F7F5;
  color: var(--body);
  border-bottom: 0;
}
/* the accent focus ring is 2.6:1 on the mid band, so on the dark surfaces the ring is --ring, a
   token of its OWN: tools/bands.mjs caught the arrows' ring going dark when it read var(--ink),
   because the arrow pins --ink back to the page ink for its chevron. */
[data-surface="dark"] :focus-visible, [data-surface="mid"] :focus-visible, .foot :focus-visible { outline-color: var(--ring); }
[data-surface="dark"] .btn-line:hover, [data-surface="mid"] .btn-line:hover { background: rgba(255, 255, 255, .08); }

/* ---- the dark band: one statement, one outlined button, nothing else (VARIANT-M change 3) */
.band { padding: var(--pad-y) 0; position: relative; }
.band-inner { display: grid; justify-items: center; text-align: center; gap: 28px; }
.band-statement {
  font-size: clamp(25px, 2.4vw, 30px); line-height: 1.3; font-weight: 400; letter-spacing: -0.012em;
  max-width: 30ch; margin: 0; color: var(--ink);
}

/* ---- the card: white, 16px radius, the soft shadow, the hairline. Cards never touch: 24px. */
.card, .ab-col, .voice, .qa-list.cards .qa {
  background: var(--card); border: var(--card-line); border-radius: var(--card-r); box-shadow: var(--card-shadow);
}

/* 02: the two halves */
.ab { gap: 24px; }
.ab-col { padding: 28px 32px; }
.ab-col + .ab-col { border-left: var(--card-line); }
@media (max-width: 860px) {
  .ab-col { padding: 24px; }
  .ab-col + .ab-col { border-top: var(--card-line); margin-top: 0; padding-top: 24px; }
}

/* 03: the three voices. The row stretches so the three cards are one height whatever their
   note wraps to; the widget and the label inside still sit at the top.
   Justin, 12 September: "the 3 voices are off centre, so the right hand one is cut out". Measured
   at 1280: the third card ran to 1303px. The widget is laid out at its TRUE 360px and only
   scaled by transform, so each card's min-content was 394px and three of them overflowed the
   1120 container by 110px. The tracks are minmax(0, 1fr) and the card may shrink below its
   content, so the row is exactly the container; the 360px box overflows its card equally on both
   sides and the SEEN widget (360 x zoom) is narrower than the card, so nothing is cut. */
.voice-row { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 24px; align-items: stretch; }
.voice { align-content: start; min-width: 0; }
.voice-stage { width: 100%; }
@media (max-width: 980px) { .voice-row { grid-template-columns: minmax(0, 1fr); } }
.voice { padding: 22px 16px 20px; }
@media (max-width: 980px) { .voice + .voice { margin-top: 24px; } }

/* 05: the four steps, white cards on a tint panel, each with its line icon */
/* the panel is GROUND for the cards, not a card itself: no hairline, no shadow, so the page has
   one level of box here (the Impeccable detector reads a bordered panel round cards as a card in
   a card) */
.steps-panel { margin-top: 24px; background: var(--tint); border-radius: var(--r-panel); padding: 24px; }
.steps { gap: 24px; margin-top: 0; }
.step { padding: 24px 22px 26px; }
.step-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 18px; }
.step-icon { width: 48px; height: 48px; flex: none; display: block; color: var(--ink); }
.step-n { margin: 0; }
@media (max-width: 900px) { .steps { gap: 24px; } .steps-panel { padding: 20px; } }

/* 06: the mid band. Hard edges (the fades go); each widget in a card washed with its own glow;
   the label under the card on the band, so its contrast never depends on a skin. The arrows keep
   L's ink chevron, border and hover on their white circle: their tokens are pinned back. */
.looks-viewport::before, .looks-viewport::after { content: none; }
.look-card {
  border-radius: var(--card-r); padding: 20px;
  background: color-mix(in srgb, var(--skin) 40%, #22312E);
  border: 1px solid rgba(255, 255, 255, .14);
}
.look { width: 380px; }
.looks-track { padding: 24px calc(50% - 190px) 8px; }
.looks-controls .spacer { width: 380px; }
/* the chevrons sit on the vertical middle of the live sample: the track's 24px top padding plus
   half a card. They flank the live card in the gap the smaller neighbours leave. */
.looks-controls { top: calc(24px + var(--look-h) / 2); bottom: auto; transform: translateY(-50%); }
[data-surface="mid"] .arrow { --ink: #0E1618; --rule: #BCD4CB; }
@media (max-width: 760px) {
  .look, .looks-controls .spacer { width: 336px; }
  .looks-track { padding-inline: calc(50% - 168px); }
}
@media (max-width: 420px) {
  .look, .looks-controls .spacer { width: 290px; }
  .looks-track { padding-inline: calc(50% - 145px); }
}

/* 07: the ladder on white reads as one object (its text and inner dividers are untouched).
   Justin, 12 September (second look): the feature list a couple of points smaller again, and
   its air trimmed, to bring the whole ladder into one screen. 11px is below the 12px floor L
   noted for the detector's tiny-text warning; Justin's instruction is later and specific. */
.ladder { border: var(--card-line); border-radius: var(--card-r); box-shadow: var(--card-shadow); overflow: hidden; }
.llist { padding: 8px 0; }
.llist li { font-size: 11px; margin: 0 0 7px; }
.lplus { font-size: 11px; }
.lwho { font-size: 12px; }

/* 08: on the white section the form is the tint panel, its fields white */
[data-surface="white"] .contact-form { background: var(--tint); }
[data-surface="white"] .field input, [data-surface="white"] .field textarea { background: var(--card); }

/* 09: the five questions as cards (home page only; the FAQ page keeps its rows) */
.qa-list.cards { border-top: 0; display: grid; gap: 24px; }
.qa-list.cards .qa { padding: 6px 22px; }

/* ================================================================ THE WIDGET
   Lifted from Website/assets/widget-designer.html's <style> block, which IS the engine's widget
   CSS parameterised. Every variable and data-attribute is set per skin from the settled JSON by
   tools/build.mjs, using the engine's own derivation and contrast gate. Do not restyle it: this
   is the product, not decoration. `.wf` replaces the designer's `.frame` so nothing here can
   collide with the page's own class names. */
.wf {
  position: relative; display: flex; flex-direction: column; overflow: hidden;
  border-radius: var(--r-frame, 16px);
  background: var(--surface, #fff); color: var(--ink, #1d1d1f);
  font-family: var(--font, Inter, system-ui, sans-serif); font-size: var(--fs, 14px);
  line-height: 1.45; text-align: left;
  box-shadow: 0 0 0 1px rgba(255,255,255,.62), 0 0 0 2px rgba(0,0,0,.62),
              0 14px 44px rgba(0,0,0,.28), var(--glow, 0 0 0 0 transparent);
}
.wf[data-frame=flat]     { box-shadow: 0 0 0 1px var(--line), var(--glow, 0 0 0 0 transparent); }
.wf[data-frame=outlined] { box-shadow: 0 0 0 2px var(--accent), 0 14px 44px rgba(0,0,0,.18), var(--glow, 0 0 0 0 transparent); }
.wf[data-frame=hard]     { border: 0; border-radius: 0; box-shadow: 0 0 0 2px #1d1d1f, 8px 8px 0 0 var(--accent), var(--glow, 0 0 0 0 transparent); }
.wf[data-texture=dots]  { background-image: radial-gradient(color-mix(in srgb, var(--ink) 9%, transparent) 1px, transparent 1px); background-size: 14px 14px; }
.wf[data-texture=grid]  { background-image: linear-gradient(color-mix(in srgb, var(--accent) 14%, transparent) 1px, transparent 1px), linear-gradient(90deg, color-mix(in srgb, var(--accent) 14%, transparent) 1px, transparent 1px); background-size: 22px 22px; }
.wf[data-texture=lines] { background-image: repeating-linear-gradient(0deg, transparent 0 7px, color-mix(in srgb, var(--ink) 5%, transparent) 7px 8px); }

.wf .head { padding: 14px 16px; border-bottom: 1px solid var(--line, #e5e7eb); background: var(--head-bg, var(--surface)); color: var(--head-ink, var(--ink)); display: flex; align-items: center; gap: 10px; flex: none; }
.wf .av { width: 30px; height: 30px; border-radius: 50%; background: var(--accent); display: none; align-items: center; justify-content: center; flex: none; }
.wf .av svg { width: 18px; height: 18px; fill: #fff; }
.wf[data-avatar=head] .av, .wf[data-avatar=both] .head .av { display: flex; }
.wf .head .t { min-width: 0; }
.wf .head .t .wname { margin: 0; font-size: calc(var(--fs) + 1px); display: flex; align-items: center; gap: 8px; font-weight: 700; color: var(--head-ink, var(--ink)); }
.wf[data-title=upper] .head .t .wname { text-transform: uppercase; letter-spacing: .12em; font-size: calc(var(--fs) - 1px); }
.wf .head .t .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); display: none; flex: none; }
.wf[data-header=surface] .head .t .dot, .wf[data-header=underline] .head .t .dot { display: inline-block; }
.wf[data-header=underline] .head { border-bottom: 2px solid var(--accent); }
.wf[data-header=gradient] .head { background: linear-gradient(100deg, var(--head-bg), var(--accent)); }
.wf .head .t .wsub { margin: 2px 0 0; font-size: 11px; color: var(--head-muted, var(--muted)); }
.wf .head .t .wsub span { text-decoration: underline; }
.wf .endbtn { text-decoration: none; border: 1px solid var(--head-line, var(--line)); border-radius: 999px; padding: 3px 9px; font-weight: 600; color: var(--head-ink, var(--ink)); }
.wf[data-corners=none] .endbtn { border-radius: 3px; }

.wf .log { flex: 1; overflow: hidden; padding: 14px; display: flex; flex-direction: column; justify-content: flex-end; gap: 9px; }
html.js .wf .log { overflow-y: auto; }
.wf .line { display: flex; gap: 8px; align-items: flex-end; max-width: 88%; }
.wf .line.me { align-self: flex-end; flex-direction: row-reverse; }
.wf .mav { width: 24px; height: 24px; border-radius: 50%; background: var(--accent); display: none; flex: none; align-items: center; justify-content: center; margin-bottom: 2px; }
.wf .mav svg { width: 14px; height: 14px; fill: #fff; }
.wf[data-avatar=messages] .line.bot .mav, .wf[data-avatar=both] .line.bot .mav { display: flex; }
.wf[data-layout=stacked] .line, .wf[data-layout=stacked] .line.me { align-self: flex-start; flex-direction: row; max-width: 100%; }
.wf[data-layout=stacked] .msg { border-radius: var(--r-bubble); }
.wf[data-layout=stacked] .who { display: block; }
.wf .who { display: none; font-size: 10px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin: 0 0 3px; }
.wf .msg { padding: 9px 12px; border-radius: var(--r-bubble, 14px); line-height: 1.45; }
.wf .msg p { margin: 0; }
.wf .msg p + p { margin-top: 8px; }
.wf .line.bot .msg { background: var(--bot, #f3f4f6); color: var(--bot-ink, var(--ink)); border-bottom-left-radius: var(--r-tail, 4px); }
.wf .line.me  .msg { background: var(--me); color: var(--me-ink, #fff); border-bottom-right-radius: var(--r-tail, 4px); }
.wf[data-bubbles=square] .msg { border-radius: 0 !important; }
.wf[data-bubbles=outlined] .line.bot .msg { background: transparent; border: 1.5px solid var(--line); color: var(--ink); }
.wf[data-bubbles=outlined] .line.me  .msg { background: transparent; border: 1.5px solid var(--me); color: var(--ink); }
.wf[data-bubbles=notail] .msg { border-radius: var(--r-bubble) !important; }
.wf .msg strong { font-weight: 600; }
/* The nine widgets are PICTURES of the product: a link inside one goes nowhere, so it is a span
   styled like a link, not an anchor. Nothing inside them is a tab stop or a tap target. */
.wf .msg a, .wf .msg .linkish { color: var(--accent); font-weight: 600; text-decoration: underline; }
.wf .dots { display: inline-flex; gap: 5px; align-items: center; padding: 3px 1px; }
.wf .dots span { width: 7px; height: 7px; border-radius: 50%; background: var(--muted); display: inline-block; opacity: .6; }

.wf .bar { display: flex; gap: 8px; padding: 10px; border-top: 1px solid var(--line, #e5e7eb); margin: 0; align-items: center; background: var(--surface); flex: none; }
.wf .msgin { flex: 1; padding: 10px 12px; border: 1px solid var(--line, #e5e7eb); border-radius: var(--r-input, 10px); font-size: var(--fs); background: var(--input-bg, var(--surface)); color: var(--muted); min-width: 0; }
.wf[data-input=underline] .msgin { border: 0; border-bottom: 2px solid var(--line); border-radius: 0; background: transparent; }
.wf[data-input=pill] .msgin { border-radius: 999px; }
.wf[data-input=filled] .msgin { border: 0; background: var(--bot); color: var(--bot-ink, var(--ink)); }
.wf .send { padding: 10px 14px; border: 0; background: var(--send-bg, var(--me)); color: var(--send-ink, var(--me-ink, #fff)); border-radius: var(--r-input, 10px); font-size: var(--fs); font-weight: 600; display: flex; align-items: center; justify-content: center; gap: 6px; flex: none; }
.wf .send svg { width: 18px; height: 18px; display: none; }
.wf[data-send=round] .send { width: 40px; height: 40px; padding: 0; border-radius: 50%; }
.wf[data-send=round] .send span, .wf[data-send=icon] .send span { display: none; }
.wf[data-send=round] .send svg, .wf[data-send=icon] .send svg { display: block; stroke: currentColor; fill: none; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.wf[data-send=icon]  .send { background: transparent; color: var(--accent); padding: 0 6px; }
.wf[data-send=ghost] .send { background: transparent; color: var(--accent); border: 1.5px solid var(--accent); }
.wf .powered { font-size: 10px; color: var(--muted); text-align: center; padding: 0 0 6px; background: var(--surface); flex: none; }
