/* ============================================================
   EMERGING SELF — site stylesheet
   Token blocks (colour, type, spacing, shadow, motion) are
   extracted verbatim from the Emerging Self Design System.
   Site-specific styles follow the tokens; every value not set
   explicitly here derives from a token.
   ============================================================ */

/* ---- Colour tokens ---- */
:root {
  /* Clay (primary — the mark's terracotta) */
  --clay-50:  #fbeee6;
  --clay-100: #f5dcc9;
  --clay-200: #eabb9c;
  --clay-300: #dd9a72;
  --clay-400: #ce7b52;
  --clay-500: #be5a32;
  --clay-600: #a2482a;
  --clay-700: #833a22;
  --clay-800: #63291b;
  --clay-900: #451d10;

  /* Sand (neutrals — cream paper to ink) */
  --sand-25:  #fdfaf5;
  --sand-50:  #f8f2e6;
  --sand-100: #f1e7d5;
  --sand-200: #e4d5bc;
  --sand-300: #cbb89a;
  --sand-400: #a99475;
  --sand-500: #8a765a;
  --sand-600: #6d5a43;
  --sand-700: #52412f;
  --sand-800: #3a2d20;
  --sand-900: #2a2620;
  --ink:      #323228;

  /* Ai (secondary — muted indigo) */
  --ai-50:  #eef3f4;
  --ai-100: #d7e1e3;
  --ai-200: #adc0c4;
  --ai-300: #82a0a6;
  --ai-400: #587f89;
  --ai-500: #3e5c63;
  --ai-600: #304850;
  --ai-700: #253941;
  --ai-800: #1b2a30;
  --ai-900: #121c20;

  /* Moss (tertiary — muted sage) */
  --moss-50:  #f1f3ed;
  --moss-100: #e3e7dc;
  --moss-200: #c7cfb9;
  --moss-300: #a9b493;
  --moss-400: #889871;
  --moss-500: #6b7a5e;
  --moss-600: #57654b;
  --moss-700: #46523c;
  --moss-800: #35402d;
  --moss-900: #262e20;

  /* Ochre (quiet accent gold) */
  --ochre-50:  #faf1df;
  --ochre-100: #f3e4c4;
  --ochre-200: #e6c98e;
  --ochre-500: #c99a44;
  --ochre-600: #a97f35;
  --ochre-700: #866227;

  /* Brick (deep muted error red) */
  --brick-100: #f0dad2;
  --brick-500: #9c4234;
  --brick-600: #7e352a;

  /* Semantic aliases */
  --surface-page:     var(--sand-50);
  --surface-raised:   var(--sand-25);
  --surface-sunken:   var(--sand-100);
  --surface-inverse:  var(--ink);
  --surface-accent-soft: var(--clay-50);
  --surface-accent-soft-strong: var(--clay-100);

  --text-primary:    var(--ink);
  --text-secondary:  var(--sand-600);
  --text-tertiary:   var(--sand-500);
  --text-on-accent:  var(--sand-50);
  --text-on-inverse: var(--sand-100);
  --text-link:       var(--clay-600);
  --text-link-hover: var(--clay-700);

  --border-subtle:  var(--sand-200);
  --border-default: var(--sand-300);
  --border-strong:  var(--sand-500);
  --border-accent:  var(--clay-300);

  --accent:        var(--clay-500);
  --accent-strong: var(--clay-600);
  --accent-soft:   var(--clay-100);
  --accent-contrast: var(--sand-50);

  --secondary:      var(--ai-600);
  --secondary-soft: var(--ai-50);

  --color-success:      var(--moss-600);
  --color-success-soft: var(--moss-50);
  --color-warning:      var(--ochre-600);
  --color-warning-soft: var(--ochre-50);
  --color-error:        var(--brick-500);
  --color-error-soft:   var(--brick-100);
  --color-info:         var(--ai-600);
  --color-info-soft:    var(--ai-50);

  --focus-ring: 0 0 0 3px rgba(190, 90, 50, 0.28);
}

/* ---- Self-hosted fonts (extracted from the Design System bundle;
        variable-weight woff2, latin + latin-ext subsets only) ---- */
@font-face {
  font-family: 'Cormorant';
  font-style: normal;
  font-weight: 300 600;
  font-display: swap;
  src: url("../fonts/cormorant-latin-ext.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Cormorant';
  font-style: normal;
  font-weight: 300 600;
  font-display: swap;
  src: url("../fonts/cormorant-latin.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Cormorant';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/cormorant-italic-latin-ext.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Cormorant';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/cormorant-italic-latin.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Josefin Sans';
  font-style: normal;
  font-weight: 300 600;
  font-display: swap;
  src: url("../fonts/josefin-sans-latin-ext.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Josefin Sans';
  font-style: normal;
  font-weight: 300 600;
  font-display: swap;
  src: url("../fonts/josefin-sans-latin.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Karla';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("../fonts/karla-latin-ext.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Karla';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("../fonts/karla-latin.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Karla';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/karla-italic-latin-ext.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Karla';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/karla-italic-latin.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---- Type tokens ---- */
:root {
  --font-display: 'Cormorant', 'Iowan Old Style', 'Georgia', serif;
  --font-label:   'Josefin Sans', 'Century Gothic', sans-serif;
  --font-body:    'Karla', 'Segoe UI', sans-serif;

  --tracking-tight: -0.01em;
  --tracking-normal: 0;
  --tracking-label: 0.16em;
  --tracking-wide: 0.06em;

  --text-xs:   0.8125rem;
  --text-sm:   0.9375rem;
  --text-base: 1.0625rem;
  --text-md:   1.25rem;
  --text-lg:   1.5rem;
  --text-xl:   1.875rem;
  --text-2xl:  2.375rem;
  --text-3xl:  3.125rem;
  --text-4xl:  4rem;
  --text-5xl:  5rem;

  --leading-tight: 1.15;
  --leading-snug: 1.35;
  --leading-normal: 1.6;
  --leading-relaxed: 1.8;

  --weight-light: 300;
  --weight-regular: 400;
  --weight-medium: 500;
  --weight-semibold: 600;
}

/* ---- Spacing, radius, layout tokens ---- */
:root {
  --space-1:  0.25rem;
  --space-2:  0.5rem;
  --space-3:  0.75rem;
  --space-4:  1rem;
  --space-5:  1.25rem;
  --space-6:  1.5rem;
  --space-8:  2rem;
  --space-10: 2.5rem;
  --space-12: 3rem;
  --space-16: 4rem;
  --space-20: 5rem;
  --space-24: 6rem;
  --space-32: 8rem;

  --radius-sm:   10px;
  --radius-md:   16px;
  --radius-lg:   24px;
  --radius-xl:   32px;
  --radius-full: 999px;

  --radius-organic-1: 42% 58% 61% 39% / 48% 44% 56% 52%;
  --radius-organic-2: 61% 39% 45% 55% / 40% 54% 46% 60%;

  --content-max-width: 1180px;
  --content-max-width-narrow: 760px;
  /* Driven by viewport *height*, so a landscape phone doesn't lose a
     quarter of its screen to the bar. Fluid — no breakpoint step.
     Deliberately vh and not dvh, unlike every other viewport height in
     this stylesheet. The Navigation Bar is the one thing on the page
     that must not answer the browser's chrome: dvh here would have the
     bar — and #nav-bar's height, which is derived from this — grow and
     shrink by several pixels every time that chrome slid, while the bar
     is also sliding back into place on the same gesture. vh is the
     large viewport and holds still, which is what a fixed header wants.
     On a phone tall enough for 11vh to reach the 88px cap, vh holds it
     there; dvh would drop it back under the cap every time the chrome
     returned, so the bar would resize on the very gesture that brings
     it back. */
  --header-height: clamp(56px, 11vh, 88px);
  /* The mark's rendered height, named so the bar behind the Navigation
     Bar can derive its own from it rather than repeat a number. */
  --nav-mark-height: min(40px, calc(var(--header-height) * 0.55));
  /* §4.1.1 — the lockup's rendered height. Standing it exactly where the
     mark alone used to left the wordmark's caps at 5.6px, judged too
     small, so it is grown against the mark's own height rather than set
     independently: the two stay in step at every viewport, and nothing
     here restates a number the header has already decided. The ceiling
     is --lockup-ratio (1.567), the point at which the *mark* inside the
     lockup regains its old full size; this stops short of it. 1.4 was
     then judged slightly too large by eye, so this sits midway between
     it and the original 1.0. */
  --nav-lockup-height: calc(var(--nav-mark-height) * 1.2);
}

/* ---- Shadow, border & motion tokens ---- */
:root {
  --shadow-sm: 0 1px 3px rgba(50, 50, 40, 0.07);
  --shadow-md: 0 6px 16px rgba(50, 50, 40, 0.08);
  --shadow-lg: 0 16px 40px rgba(50, 50, 40, 0.11);
  --shadow-inset: inset 0 1px 2px rgba(50, 50, 40, 0.06);

  --border-width-hairline: 1px;
  --border-width-default: 1.5px;
  --border-width-strong: 2px;

  --ease-gentle:   cubic-bezier(0.25, 0.1, 0.25, 1);
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --duration-fast: 150ms;
  --duration-base: 260ms;
  --duration-slow: 480ms;

  --blur-soft: 12px;
}

/* ---- Base element resets (from the Design System) ---- */
*, *::before, *::after { box-sizing: border-box; }

html {
  background: var(--surface-page);
  color-scheme: light;
  scroll-behavior: auto; /* glides are scripted; native smooth would fight them */
  -webkit-text-size-adjust: 100%;  /* iOS must not inflate text in landscape */
  text-size-adjust: 100%;
}

body {
  margin: 0;
  /* Pull-to-refresh and scroll chaining would interrupt the pinned
     sequences mid-transition on touch devices. */
  overscroll-behavior-y: none;
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--text-primary);
  background: var(--surface-page);
  -webkit-font-smoothing: antialiased;
  text-wrap: pretty;
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: var(--weight-medium);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  margin: 0;
}

p { margin: 0; }

a {
  color: var(--text-link);
  text-decoration-color: var(--border-accent);
  text-underline-offset: 3px;
}
/* Every hover state on the site is gated on the pointer actually being
   able to hover. A touch browser keeps :hover applied to the last thing
   tapped until something else is tapped, so without this a tapped
   control stays in its hover colour long after the tap — reported as
   the Email and WhatsApp buttons staying dark after their panel closed,
   which is this and not a state of the panel's. :focus-visible is never
   gated: that one is the keyboard's, and it is deliberately kept out of
   these blocks. */
@media (hover: hover) {
  a:hover { color: var(--text-link-hover); }
}

::selection {
  background: var(--clay-200);
  color: var(--ink);
}

button, input, select, textarea {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
}

:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--radius-sm);
}
/* §3.4 — the browser's own tap feedback, suppressed. A touch browser
   paints a translucent box over whatever was pressed: on this palette
   it reads as a grey flash, and it is a hard rectangle, so it squares
   off the lockup and the round-cornered Contact buttons as they are
   tapped. Every one of these controls already answers a press on its
   own terms — .btn's scale, the Contact buttons' open state, the
   panels opening — so the browser's box adds nothing but noise. The
   property is inherited, so :root carries it for the whole page.

   The keyboard's indicator is deliberately untouched. The ring above is
   on :focus-visible, which a tap never satisfies, and the rule below
   clears only the outline a browser would paint on a focus it has
   itself judged *not* to warrant showing — a tap or a mouse press. A
   blanket `:focus { outline: none }` would have taken the keyboard's
   with it; this cannot, by construction. */
:root { -webkit-tap-highlight-color: transparent; }
:focus:not(:focus-visible) { outline: none; }

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

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

/* ============================================================
   Site-specific values
   ============================================================ */
:root {
  /* §7.1 — size at which the text fades in. Fluid rather than stepped:
     the width term carries it smoothly from ~281px on a 390px phone to
     400px at 1280, and the 78vmin cap keeps it in bounds on short
     landscape screens. No discontinuity at any width. */
  --s2-d: min(calc(229px + 13.4vw), 78vmin);
  --s2-d-hold: calc(var(--s2-d) * 1.25);  /* §8 — held through the pause */
  --nav-fg: var(--ink);
}

/* The curtain for logo and menu-item clicks (§4.3). Above everything,
   including the Navigation Bar and the mobile menu panel, so the
   visitor sees only the departure and the arrival. */
#screen-cover {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: var(--ink);
  opacity: 0;
  pointer-events: none;
}

/* ============================================================
   Navigation Bar (§4)
   ============================================================ */
#nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 80;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--header-height);
  padding: 0 clamp(1rem, 4vw, 2rem);
  transition: transform var(--duration-slow) var(--ease-gentle);
  pointer-events: none;              /* bar is transparent; children interactive */
}
#nav > * { pointer-events: auto; }
#nav.hidden { transform: translateY(-110%); }
#nav[data-theme="light"] { --nav-fg: var(--sand-50); }
#nav[data-theme="dark"]  { --nav-fg: var(--ink); }
/* Third theme for Screen 3's Clay 500 field: the mark's Clay stroke
   would vanish there, so mark and text both switch to Sand 50. */
#nav[data-theme="clay"]  { --nav-fg: var(--sand-50); }
#nav[data-theme="clay"] .js-mark svg circle,
#nav[data-theme="clay"] .js-mark svg path { stroke: var(--sand-50); }
.js-mark svg circle, .js-mark svg path {
  transition: stroke var(--duration-base) var(--ease-gentle);
}

.nav-logo {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  min-height: 44px;          /* touch target, independent of mark size */
}
.js-mark svg {
  display: block;
  height: var(--nav-mark-height);
  width: auto;
}

/* §4.1.1 — the Navigation Bar's lockup: mark over wordmark, as in the
   Logo Animation. It stands a little taller than the mark alone did, so
   the wordmark beneath is legible; the mark inside it is still smaller
   than the old one, so the bar keeps its former weight. */
.nav-logo .js-lockup svg {
  display: block;
  height: var(--nav-lockup-height);
  width: auto;
}
/* The mark's stroke, on the same terms as .js-mark above: Clay 500
   everywhere except Screen 3's Clay field, where it would vanish. */
#nav[data-theme="clay"] .js-lockup svg circle,
#nav[data-theme="clay"] .js-lockup svg path { stroke: var(--sand-50); }
.js-lockup svg circle, .js-lockup svg path {
  transition: stroke var(--duration-base) var(--ease-gentle);
}
/* The wordmark's ink. It is a raster and cannot take a token, so the
   choice is which of the two files to show. The condition is exactly
   --nav-fg's: the themes named for a *light* ink sit on dark ground and
   need the light wordmark, and `dark` is the one on light ground. Both
   images are in the SVG at the same coordinates, so this swaps the ink
   without moving anything. */
.js-lockup .wm-ink-light { display: none; }
#nav[data-theme="light"] .js-lockup .wm-ink-dark,
#nav[data-theme="clay"]  .js-lockup .wm-ink-dark { display: none; }
#nav[data-theme="light"] .js-lockup .wm-ink-light,
#nav[data-theme="clay"]  .js-lockup .wm-ink-light { display: block; }

/* The bar behind the Navigation Bar (§4). It rides inside #nav, so
   §4.4's hide-on-down-scroll carries it without any extra wiring, and
   sits at z-index -1 so it paints behind the mark and +MENU but in
   front of the page. No border, no shadow, no gradient: a flat edge.
   Its height is derived, not set — the artwork is vertically centred in
   the header, so its underside is half the header plus half the lockup,
   and Space 3 of air is added below that. Resize the lockup and this
   follows. The colour comes from --nav-bar-bg, which the script keeps
   in step with the composited field behind it. */
#nav-bar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: calc((var(--header-height) + var(--nav-lockup-height)) / 2 + var(--space-3));
  z-index: -1;
  background: var(--nav-bar-bg, transparent);
  pointer-events: none;
  /* No transition on the ground: the script writes the new colour the
     moment a different Screen comes under the bar and it takes effect
     on that frame (§4.6.2). A cross-fade was tried here and withdrawn.
     Because §4.4 hides the bar going down, the only handover anyone
     sees is an up-scroll one, and there a fade read as the bar holding
     the departing Screen's colour and catching up late rather than as
     a deliberate carry. Nothing here takes a transition without
     re-testing that case. */
}

#menu-area { position: relative; }
/* The navigation cluster — +MENU, the unrolled items, < BACK and the
   panel items — is set in Karla (agreed revision, supersedes the
   Cormorant round); the wordmark beside the logo stays Josefin. */
#menu-btn, #panel-back {
  font-family: var(--font-body);
  font-weight: var(--weight-medium);
  font-size: var(--text-sm);
  letter-spacing: 0.14em;
  color: var(--nav-fg);
  background: none;
  border: none;
  cursor: pointer;
  /* 44px minimum touch target */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0;
  user-select: none;
  transition: opacity var(--duration-fast) var(--ease-gentle),
              color var(--duration-base) var(--ease-gentle);
}
#menu-area.open #menu-btn { opacity: 0; pointer-events: none; }

/* The hover region, not just the type. Unpadded, this box is the height
   of one line — 24px — while the +MENU button it unfurls from is 44px,
   so travelling left along the items left a 10px dead band above and
   below for the whole 538px of their run, and the button stops holding
   the hover the moment the menu opens (pointer-events: none below). An
   item's height of padding each way takes the box to 72px, taller than
   the button, so the region is continuous from the icon across every
   item. Vertical only: the box is centred by the transform, so
   symmetric padding grows it about its own middle and the items do not
   move, and `right: 0` means any horizontal padding would push them off
   their anchor. Invisible, and out of flow, so no layout shifts. */
#menu {
  position: absolute;
  top: 50%;
  right: 0;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-6) 0;
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--duration-fast) var(--ease-gentle),
              visibility 0s linear var(--duration-fast);
}
#menu-area.open #menu {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--duration-fast) var(--ease-gentle);
}
#menu a {
  font-family: var(--font-body);
  font-weight: var(--weight-medium);
  font-size: var(--text-sm);
  letter-spacing: 0.14em;
  color: var(--nav-fg);
  text-decoration: none;
  transition: color var(--duration-base) var(--ease-gentle);
}
/* Split from the hover so the keyboard keeps its underline everywhere. */
#menu a:focus-visible {
  text-decoration: underline;
  text-underline-offset: 3px;
}
@media (hover: hover) {
  #menu a:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
  }
}
#menu .sep {
  font-family: var(--font-body);
  font-weight: var(--weight-medium);
  font-size: var(--text-sm);
  color: var(--nav-fg);
  opacity: 0.55;
  user-select: none;
  /* The separators take --nav-fg like the items either side of them.
     No transition, matching the bar's ground above (§4.6.2). */
}

/* ============================================================
   Mobile menu panel (§4.2)
   ============================================================ */
#menu-panel {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--sand-900);
  color: var(--sand-50);
  transform: translateX(100%);
  visibility: hidden;
  transition: transform var(--duration-slow) var(--ease-gentle),
              visibility 0s linear var(--duration-slow);
  display: flex;
  flex-direction: column;
  padding: 0 var(--space-8);
}
#menu-panel.open {
  transform: none;
  visibility: visible;
  transition: transform var(--duration-slow) var(--ease-gentle);
}
@media (min-width: 768px) {
  #menu-panel { display: none; }
}
.panel-bar {
  height: var(--header-height);
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;
}
.panel-bar .js-mark svg { height: 40px; width: auto; }
#panel-back { color: var(--sand-50); }
.panel-nav {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-8);
  margin-top: var(--space-12);
}
.panel-nav a {
  font-family: var(--font-body);
  font-weight: var(--weight-regular);
  font-size: var(--text-lg);
  letter-spacing: 0.14em;
  color: var(--sand-50);
  text-decoration: none;
  /* 44px minimum touch target */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* ============================================================
   Pinned Screens 1–2 (§3, §5–§8)
   ============================================================ */
#pin-track { position: relative; }
#pin-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
  z-index: 1;
}
/* dvh, not svh. svh is the viewport with the browser's chrome showing,
   so it never moves when that chrome does — but it also leaves the
   stage short of the window whenever the chrome is hidden, and every
   Screen boundary then sits a chrome's height inside the window instead
   of on its edge. Restoring the address bar closed that gap in one
   step, which is the jolt: pronounced in the Google app, slight in
   Chrome, absent in Safari. dvh tracks the visible viewport, so the
   stage always fills exactly what can be seen and the boundary stays on
   the window's edge through the whole of the chrome's travel.
   The 100vh above stands as the fallback for browsers with neither. */
@supports (height: 100dvh) {
  #pin-stage { height: 100dvh; }
}

/* ---- Screen 1 (§5) ---- */
#screen-1 {
  position: absolute;
  inset: 0;
  background: var(--sand-50);
  display: flex;
  align-items: center;
  justify-content: center;
}
.anim-stage { width: min(100%, calc(100vh * 4 / 3)); }
/* Follows #pin-stage's unit: this is the artwork's 4:3 box fitted to the
   stage's height, so if the stage tracks the visible viewport this must
   too, or the animation stops being contained in it. */
@supports (height: 100dvh) {
  .anim-stage { width: min(100%, calc(100dvh * 4 / 3)); }
}
.anim-stage svg { display: block; width: 100%; height: auto; }
#es-anim { transition: opacity 250ms var(--ease-gentle); }
#es-anim.done { cursor: pointer; }     /* clickable only when static (§5.3.3) */

/* ---- Dim layer (§6) ---- */
#dim {
  position: absolute;
  inset: 0;
  background: var(--sand-900);
  opacity: 0;
  pointer-events: none;
}

/* ---- Screen 2 (§7–§8) ---- */
#screen-2 {
  position: absolute;
  inset: 0;
  background: var(--sand-900);
  opacity: 0;
  pointer-events: none;
  overflow: hidden;
}
#s2-circle {
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--s2-d);
  height: var(--s2-d);
  border-radius: 50%;
  background: var(--clay-500);   /* never changes (agreed revision) */
  transform: translate(-50%, -50%) scale(1);
  will-change: transform, filter;
}
#s2-text {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: calc(var(--s2-d) * 0.85);
  text-align: center;
  font-family: var(--font-body);
  font-weight: var(--weight-regular);
  font-size: calc(var(--s2-d) * 0.075);
  line-height: 1.55;
  letter-spacing: 0.14em;
  color: var(--sand-50);
  opacity: 0;
}

/* ---- Mist (§7.2, §8) — layered radial gradients from the Sand palette.
        Undulation runs on its own clock; scroll controls only opacity,
        so the haze thins in place (§8). ---- */
/* The two mist layers composite inside one container, and the falloff
   that lets the sun's light through is masked onto that container —
   applied once to the composite, never twice. The container is
   translated to follow the sun, so a fixed 50%/50% mask centre always
   lands on it. The mask itself is a Gaussian profile written from JS
   (see updateMistMask): no plateau, no ramp endpoint, nothing that
   could read as a ring. */
#mist, #mist-outer, #mist-core {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.blob {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
}
@keyframes mist-a {
  from { transform: translate(calc(-50% - 2.6vmin), calc(-50% + 1.8vmin)) scale(1); }
  to   { transform: translate(calc(-50% + 2.6vmin), calc(-50% - 1.8vmin)) scale(1.08); }
}
@keyframes mist-b {
  from { transform: translate(calc(-50% + 2.2vmin), calc(-50% + 1.2vmin)) scale(1.05); }
  to   { transform: translate(calc(-50% - 2.2vmin), calc(-50% - 1.6vmin)) scale(0.97); }
}
@keyframes mist-c {
  from { transform: translate(calc(-50% - 1.4vmin), calc(-50% - 2.2vmin)) scale(0.96); }
  to   { transform: translate(calc(-50% + 1.8vmin), calc(-50% + 2.4vmin)) scale(1.06); }
}
/* A wide static bed keeps the haze continuous — dense centre thinning
   toward the Screen edges — while the drifting blobs modulate it.
   Densities raised (agreed revision): the veiled sun must read as a
   faint fuzzy glow with no traceable outline. */
.blob.bed { left: 50%; top: 50%; width: 170vmax; height: 170vmax;
  background: radial-gradient(closest-side, rgba(228,213,188,.56) 0%, rgba(228,213,188,.42) 30%, rgba(228,213,188,.16) 60%, rgba(228,213,188,0) 85%);
  transform: translate(-50%, -50%); }
.blob.o1 { left: 50%; top: 50%; width: 115vmin; height: 115vmin;
  background: radial-gradient(closest-side, rgba(228,213,188,.48), rgba(228,213,188,0) 80%);
  animation: mist-a 13s var(--ease-gentle) -2s infinite alternate; }
.blob.o2 { left: 31%; top: 38%; width: 95vmin; height: 95vmin;
  background: radial-gradient(closest-side, rgba(203,184,154,.40), rgba(203,184,154,0) 80%);
  animation: mist-b 16s var(--ease-gentle) -6s infinite alternate; }
.blob.o3 { left: 71%; top: 35%; width: 90vmin; height: 90vmin;
  background: radial-gradient(closest-side, rgba(228,213,188,.38), rgba(228,213,188,0) 80%);
  animation: mist-c 12s var(--ease-gentle) -4s infinite alternate; }
.blob.o4 { left: 33%; top: 67%; width: 95vmin; height: 95vmin;
  background: radial-gradient(closest-side, rgba(203,184,154,.38), rgba(203,184,154,0) 80%);
  animation: mist-b 11s var(--ease-gentle) -1s infinite alternate; }
.blob.o5 { left: 70%; top: 66%; width: 92vmin; height: 92vmin;
  background: radial-gradient(closest-side, rgba(241,231,213,.40), rgba(241,231,213,0) 80%);
  animation: mist-a 15s var(--ease-gentle) -8s infinite alternate; }
.blob.c1 { left: 50%; top: 50%; width: 74vmin; height: 74vmin;
  background: radial-gradient(closest-side, rgba(241,231,213,.92), rgba(241,231,213,0) 78%);
  animation: mist-c 12s var(--ease-gentle) -3s infinite alternate; }
.blob.c2 { left: 45%; top: 46%; width: 54vmin; height: 54vmin;
  background: radial-gradient(closest-side, rgba(241,231,213,.78), rgba(241,231,213,0) 78%);
  animation: mist-b 9.5s var(--ease-gentle) -5s infinite alternate; }
.blob.c3 { left: 56%; top: 55%; width: 60vmin; height: 60vmin;
  background: radial-gradient(closest-side, rgba(228,213,188,.78), rgba(228,213,188,0) 78%);
  animation: mist-a 10.5s var(--ease-gentle) -7s infinite alternate; }

/* ---- Screen 3 (§8A) — four text blocks on the Clay 500 field, one at
        a time, each in the exact centre. Clip-reveal: each line sits
        inside an overflow-hidden clip and travels upward through it —
        from +110% (below, hidden) to 0 (in place) to −110% (above,
        hidden), so entry and exit are the same motion, mirrored. Only
        the text moves; the field is static. Desktop and phone are
        identical. ---- */
#screen-3 {
  position: absolute;
  inset: 0;
  background: var(--clay-500);
  opacity: 0;
  pointer-events: none;
  overflow: hidden;
  display: grid;
  place-items: center;
  padding: 0 clamp(1.25rem, 7vw, 8rem);
  /* No will-change: the Screen itself no longer moves. Its roll out of
     view (§8A.4) is the stage's sticky release, so only the lines
     inside it are transformed. */
}
.s3-block {
  width: 100%;
  max-width: min(34rem, 86vw);   /* fluid, no breakpoint step */
  text-align: center;
  /* Transparent from the first painted frame. The script writes this
     from scroll position, but until it does — on first load, or if the
     script fails — the line must not be standing on a field that has
     not arrived. The clip-and-drift that used to hide it went with the
     sequence: one line that fades needs no clip to rise out of. */
  opacity: 0;
}
/* Screen 3 is set in Karla, Sand 50 (§8A.3). The italic subtext style
   went with blocks 1–3, which were the only things that used it. */
.s3-lead {
  font-family: var(--font-body);
  font-weight: var(--weight-regular);
  font-size: clamp(1.9rem, 3.2vw, 2.375rem);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  color: var(--sand-50);
  margin: 0;
}

/* ============================================================
   Ordinary Screens 4–8 (§3.6)
   ============================================================ */
main {
  position: relative;
  z-index: 2;              /* above the stage while their edges meet (§8A.4) */
}
/* §3.6.1 — the ordinary Screens carry no viewport height at all.
   They were `min-height: 100dvh` with their content vertically
   centred, which is a bad pair on a phone: dvh tracks the visible
   viewport, so every time the address bar and footer came or went the
   box changed height and the centred content moved by half of it.
   Chrome and the Google app move more chrome than Safari, which is why
   it read as worse there.

   About was the tell. It had the same `min-height` as the rest and no
   symptom, because on phone widths it is `align-content: start` for
   the photograph's bleed — the box still resized, the content simply
   did not move with it. The height was never the problem on its own;
   the height and the centring together were.

   These are ordinary scrolling content and do not need to be a
   viewport tall, so they are laid out from the top at their natural
   height and the padding below carries the rhythm instead. Nothing
   here resolves against the viewport, so address-bar movement cannot
   reach it. Screens 1–3 keep dvh — the pinned sequence is measured in
   viewport-heights and genuinely needs it. */
.screen { padding-block: var(--space-20); }
#therapy      { background: var(--sand-200); }  /* Screen 5 — §11.1 */
#endorsements { background: var(--sand-100); }  /* Screen 6 — decision */
/* Screens 7–9 alternate ground so each reads as its own block: the FAQ
   is held on the page ground §12A.2 requires, and Contact takes the
   sunken one either side of it. Both are set on the section itself, so
   the colour runs the full bleed with nothing to leak past at the edges. */
#faq          { background: var(--surface-page); }   /* Screen 8 — §12A.2 */
#contact      { background: var(--surface-sunken); } /* Screen 8 — decision */

/* ============================================================
   Screen 4 — About (§9). Two columns: text left, portrait right,
   with the photograph's sea horizon aligned to the rule in the text.
   Arrives still — Screens 1–3 carry the motion.
   ============================================================ */
/* §9.1.1 — the landing out of the pinned run is handled in layout(), by
   releasing the stage before the sequence ends, and that is all. The
   overshoot it leaves is accepted.

   Scroll snap is deliberately absent, and must not be reintroduced
   here: no `scroll-snap-type`, no `scroll-snap-align`, no
   `scroll-snap-stop`, on this element or on the root. Five attempts
   across two mechanisms are recorded in DECISIONS.md item 84, with the
   evidence that killed each. The one worth knowing before trying again:
   setting the container to `scroll-snap-type: none` does not release
   the scroller — a gesture that latched after `scrollend`, with the
   container already reading `none`, still froze the page — so a
   script-armed window cannot be switched off once a visitor is on it. */
#about {
  background: var(--surface-page);
  display: grid;
  /* §3.6.1 — no `align-content`, and no height for it to work against.
     Vertical rhythm is `.screen`'s padding-block. */
  padding-inline: var(--space-8);
}
.about-grid {
  width: 100%;
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: grid;
  /* The columns now carry equal width — the figure was 0.72fr, which
     held the portrait to 460px. Equal tracks take it to 550px, and its
     own proportions carry the height with it. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-20);
  align-items: start;
}
.about-text { max-width: 46ch; }

.about-eyebrow {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  /* Clay 600, not 500: at 13px uppercase and spaced, 500 measures
     4.01:1 on Sand 50 — under AA. 600 is 5.39:1. */
  color: var(--clay-600);
}
.about-lede {
  margin-top: var(--space-8);
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 3.4vw, var(--text-2xl));
  font-weight: var(--weight-light);
  line-height: 1.2;
  letter-spacing: -0.005em;
  color: var(--text-primary);
}
/* Full ink at weight 500 against the sand-600 / 400 of the paragraphs
   below: the tonal step is what makes the thesis register, without
   reaching for a second typeface. */
.about-thesis {
  margin-top: var(--space-6);
  font-family: var(--font-body);
  font-size: clamp(1.125rem, 1.8vw, var(--text-lg));
  font-weight: var(--weight-medium);
  line-height: var(--leading-normal);
  color: var(--text-primary);
}
/* The horizon rule — a hairline across the text column, one colour end
   to end. It carried a Space 16 accent segment at its left over a
   Border Subtle remainder; that two-tone split reads as a progress bar
   with a filled portion, which it never was — every instance is
   `role="presentation"` and nothing in the script touches any of them.
   Each Screen keeps its own accent, since the tint was chosen against
   its ground: Clay 300 on Sand 50 and Sand 100, Clay 400 on Therapy's
   darker Sand 200, so the line holds the same weight throughout. Same
   1px the segment always was. --rule-line is retired with the
   remainder it coloured. */
.horizon-rule {
  height: 1px;
  margin-top: var(--space-12);
  margin-bottom: var(--space-10);
  background: var(--rule-accent, var(--clay-300));
}
.about-body {
  font-family: var(--font-body);
  font-size: clamp(1.125rem, 1.8vw, var(--text-lg));
  font-weight: var(--weight-regular);
  line-height: var(--leading-relaxed);
  color: var(--text-secondary);
}
.about-body + .about-body { margin-top: var(--space-6); }

/* The portrait's top edge sits level with the lede, not with the eyebrow
   above it. Derived rather than measured: the eyebrow is one Text XS
   line at line-height 1, and Space 8 is the lede's own margin above it,
   so the two together are exactly the drop. Holds at any width, since
   neither term depends on how the text reflows. */
.about-figure {
  margin: 0;
  margin-top: calc(var(--text-xs) + var(--space-8));
}
/* The portrait keeps its own 960×1151 proportions — no aspect-ratio and
   no object-fit, so nothing is cropped or stretched. The width and
   height attributes carry the intrinsic ratio, which reserves the space
   before the file arrives; height:auto is what lets that ratio drive the
   box rather than the height attribute being taken as a used height. */
.about-figure img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
}
@media (max-width: 899.98px) {
  /* The photograph runs edge to edge and sits against the top of the
     Screen, so the section carries no padding on those three sides —
     the text below takes its own instead. align-content goes to start
     for the same reason: centred content would leave a band above the
     photograph. */
  #about {
    /* The photograph runs to all three edges, so this Screen alone
       drops the shared top padding; the foot keeps it. */
    padding-inline: 0;
    padding-block-start: 0;
  }
  .about-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-12);
  }
  .about-text {
    max-width: none;
    padding-inline: var(--space-6);
  }
  /* Photograph first, full width, at its natural height. The cap and
     the radius both go: a rounded corner against the viewport edge
     reads as a mistake rather than a detail. */
  .about-figure {
    order: -1;
    max-width: none;
    margin: 0;
  }
  .about-figure img { border-radius: 0; }
}
@media (max-width: 899.98px) {
  .horizon-rule {
    margin-top: var(--space-8);
    margin-bottom: var(--space-8);
  }
}

/* ============================================================
   Screen 5 — Therapy (§11). A single measure on a deeper ground.
   Same vertical rhythm and rule motif as About, so the two read as
   one system; the inks step down a stop because Sand 200 is darker.
   ============================================================ */
#therapy {
  display: grid;
  /* §3.6.1 — laid out from the top; rhythm from `.screen`. */
  padding-inline: var(--space-8);
  /* Clay 600 is only 4.16:1 on Sand 200 — under AA for a 13px spaced
     label. Clay 700 is 5.61:1. Sand 600 would scrape 4.55:1, so the
     body takes Sand 700 at 6.75:1. */
  /* A stop stronger than the other Screens' Clay 300, because Sand 200
     is a stop darker and the line has to hold the same weight on it. */
  --rule-accent: var(--clay-400);
}
.therapy-text {
  width: 100%;
  /* 47ch, not 46: at the body clamp's 24px ceiling — every viewport from
     1333px up — 46ch dropped "entrenched" onto a line of its own and
     left the "and" line short. One ch is the smallest step that pulls it
     back. Endorsements carries the same measure. */
  max-width: 47ch;
  margin: 0 auto;
}
.therapy-eyebrow {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--clay-700);
}
.therapy-body {
  margin-top: var(--space-8);
  font-family: var(--font-body);
  font-size: clamp(1.125rem, 1.8vw, var(--text-lg));
  font-weight: var(--weight-regular);
  line-height: var(--leading-relaxed);
  color: var(--sand-700);
}
.therapy-body + .therapy-body { margin-top: var(--space-6); }
/* The closing line is the frame around everything above it, so it
   takes the display face — the same role Cormorant plays in About's
   lede, rather than a third voice. */
.therapy-close {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.6vw, var(--text-xl));
  font-weight: var(--weight-light);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  color: var(--text-primary);
}
@media (max-width: 899.98px) {
  #therapy { padding: var(--space-20) var(--space-6); }
  .therapy-text { max-width: none; }
}

/* ============================================================
   Screen 6 — Endorsements (§12). One endorsement at a time on the
   same 47ch measure Screen 5 uses, so the reading rhythm is
   unchanged from Screen to Screen. The quotation takes the display
   face at exactly the size and weight of Screen 6's closing line —
   the voice the site already reserves for a weighted statement,
   rather than a fourth one.

   The Design System has no carousel, so its frame and the dots are
   designed here from its tokens under §1.5 and recorded in DECISIONS;
   the arrows are its own IconButton, outline variant at lg.
   ============================================================ */
#endorsements {
  display: grid;
  /* Anchored to the top, as every Screen now is. */
  align-content: start;
  /* §3.6.1 — laid out from the top; rhythm from `.screen`. The foot
     used to be Space 16 rather than Space 32 to sit the dots near the
     bottom edge of a viewport-tall Screen. There is no bottom edge to
     sit near any more, so it takes the shared value with the rest. */
  padding-inline: var(--space-8);
  /* Sand 100 sits close enough to Sand 50 that the rule takes About's
     tint rather than Therapy's stronger one. */
  --rule-accent: var(--clay-300);
  /* §3.6.3 — a budget for the *type*, not a height for any box. No
     rule anywhere sets a length from this; the only thing that reads
     it is the script that picks a body size, and the box that results
     is whatever that content comes to.
     It is the room an endorsement has if the eyebrow at the top and
     the dots at the bottom are to be on screen together with nothing
     to scroll: the viewport less everything else the Screen has to
     hold — the section's own padding, the eyebrow (one Text XS line at
     line-height 1), the gap above the area, the rule with its margins,
     and the dot row. Derived, so it follows if any of those change.
     Still measured against the viewport, because "fits on screen" is a
     question about the viewport and cannot be asked without one. That
     is safe now in a way it was not while it sized a box: it is read
     once at load and again only on a resize large enough to clear the
     chrome tolerance, so a moving address bar neither re-runs it nor
     moves anything if it did. */
  --endorse-frame: calc(100vh
    - var(--space-20) - var(--space-20)
    - var(--text-xs)
    - var(--space-8)
    - (var(--space-12) + var(--border-width-hairline) + var(--space-10))
    - 44px);
  /* The name the script reads. One number now, kept as two because
     these had to be parted once and the reason should survive: the
     frame used to size the box and followed the visible viewport, so
     it grew when the chrome hid while the budget held at the smallest
     the box could ever be. Fitting to the frame would have sized a
     quote to the tall state and clipped it when the chrome came back —
     or re-fitted, rescaling type under the reader's thumb mid-scroll.
     There is no box to differ from any more, so they have converged. */
  --endorse-budget: var(--endorse-frame);
  /* The floor the body will not scale below. Text Base is the size the
     site reads at everywhere else, so nothing here goes under it. */
  --endorse-quote-min: var(--text-base);
}
/* svh — the small viewport, the size with the browser's chrome
   showing. It is the worst case, so a body that fits it fits every
   state. §3.6.1 moved this off dvh on the reasoning that svh is a
   constant; §3.6.3 records that it moved anyway on a real phone, which
   is why nothing is *sized* from it any more. It survives here only as
   the budget, where a wrong answer costs a slightly small quote rather
   than a Screen that breathes. */
@supports (height: 100dvh) {
  #endorsements {
    --endorse-frame: calc(100svh
      - var(--space-20) - var(--space-20)
      - var(--text-xs)
      - var(--space-8)
      - (var(--space-12) + var(--border-width-hairline) + var(--space-10))
      - 44px);
  }
}
.endorse-wrap {
  width: 100%;
  max-width: 47ch;          /* the same measure as Therapy */
  margin: 0 auto;
}
.endorse-eyebrow {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  /* Clay 600 measures 4.90:1 on Sand 100 — over AA, and the same token
     About uses one ground lighter. Clay 700 was only needed on Sand
     200, where 600 falls to 4.16:1. */
  color: var(--clay-600);
}

/* The arrows sit outside the measure so the text keeps its 47ch: the
   stage is the width of the reading column and they hang off it, level
   with the middle of the endorsement area rather than the middle of the
   Screen. */
.endorse-stage {
  position: relative;
  margin-top: var(--space-8);
}

/* The slides share one grid cell, so the carousel is as tall as its
   longest endorsement at every width and the rule beneath never moves
   as the visitor flips through. The cap is what keeps the dots on
   screen; overflow is hidden because the slides travel a full width in
   and out, and because a body that will not fit even at the floor is
   meant to be shortened, not scrolled. */
.endorse-viewport {
  display: grid;
  /* The endorsement sits at the top of the area and the surplus falls
     below it, rather than being split above and below — splitting it
     was what made the area look full and drove the body size down. */
  align-items: start;
  /* §3.6.3 — no height. This was `height: var(--endorse-frame)`, and it
     was the last thing on this Screen a phone's chrome could reach: the
     box was viewport-derived, so it changed height while the heading
     and the endorsement above it did not, and the rule and the dots
     beneath rode up and down on their own.

     The height existed so the rule and the dots land in the same place
     whichever endorsement is showing. They still do, and the grid is
     what does it: every slide occupies the same cell — the idle ones
     are `visibility: hidden`, which still takes up space — so the box
     is as tall as the *longest* endorsement at all times, whatever is
     on screen. Measured 292px against a longest slide of 292px, which
     is the fixed frame's job done by the content instead.

     The ceiling that kept the eyebrow and the dots on screen together
     has not gone either; it moved to where it belongs. It is enforced
     on the type, by fitting the body to --endorse-budget, rather than
     on the box. Content that fits the budget makes a box that fits. */
  overflow: hidden;
}
.endorse-slide {
  grid-area: 1 / 1;
  margin: 0;
  visibility: hidden;
  /* Parked one full width out, on the side the next turn will bring it
     in from. Idle slides carry no transition: that reseating has to be
     instant or it becomes the animation itself. */
  transform: translateX(100%);
  transition: none;
}
.endorse-carousel.dir-prev .endorse-slide { transform: translateX(-100%); }
/* Duration Slow, not Base. The turn kept Base from the days when it was
   a crossfade and the slide only drifted Space 6 to say which way it had
   gone; the rework made the travel a full width — 474px on desktop —
   without revisiting the time, so the same 260ms had to carry twenty
   times the distance. Ease Standard rather than Ease Gentle for the
   curve: Gentle is CSS `ease`, which leaves at four-tenths of average
   speed rather than from rest, and over a full width that reads as a
   launch. Standard is the Design System's own ease-in-out, flat at both
   ends. §3.4 judgement, tune on review. */
.endorse-slide.is-current,
.endorse-slide.is-leaving {
  visibility: visible;
  transition: transform var(--duration-slow) var(--ease-standard);
}
/* Qualified by the carousel, and placed after, so these outrank the
   parking rule above — which is a class deeper and would otherwise take
   the live slide off screen with it on a backwards turn. */
.endorse-carousel .endorse-slide.is-current { transform: translateX(0); }
.endorse-carousel .endorse-slide.is-leaving { transform: translateX(-100%); }
.endorse-carousel.dir-prev .endorse-slide.is-leaving {
  transform: translateX(100%);
}

.endorse-quote { margin: 0; }
/* --quote-size is written per slide by the script when a body will not
   fit the frame at the standard size; a slide that fits never gets one
   and keeps the clamp below. Scaling is one-way — nothing is ever
   enlarged to fill space. */
.endorse-quote p {
  font-family: var(--font-display);
  font-size: var(--quote-size, clamp(1.5rem, 2.6vw, var(--text-xl)));
  font-weight: var(--weight-light);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  color: var(--text-primary);
}
.endorse-by {
  margin-top: var(--space-6);
  display: grid;
  gap: var(--space-1);
}
.endorse-by-name {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--sand-700);
}
/* Sand 600 is 5.36:1 here; Sand 500 would be 3.55:1 and fail AA at
   this size. */
.endorse-by-role {
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--text-sm);
  font-weight: var(--weight-regular);
  line-height: var(--leading-snug);
  color: var(--sand-600);
}

/* Nothing shares this line with the dots any more, so they sit on the
   Screen's centre rather than against its left edge. */
.endorse-controls {
  display: flex;
  align-items: center;
  justify-content: center;
}
.endorse-dots {
  display: flex;
  align-items: center;
}
/* A narrow button around a small dot: the target is a full 44px tall
   without the mark itself growing to match. */
.endorse-dot {
  width: var(--space-6);
  height: 44px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  display: grid;
  place-items: center;
}
.endorse-dot::before {
  content: '';
  width: var(--space-2);
  height: var(--space-2);
  border-radius: var(--radius-full);
  /* Unset dots are rings and the current one is solid, so the state
     survives without colour having to carry it — Clay 500 and Sand 500
     sit at almost the same luminance. */
  box-shadow: inset 0 0 0 1px var(--border-strong);
  background: transparent;
  transition: background var(--duration-fast) var(--ease-gentle),
              box-shadow var(--duration-fast) var(--ease-gentle);
}
.endorse-dot.is-current::before {
  background: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}
@media (hover: hover) {
  .endorse-dot:hover::before { box-shadow: inset 0 0 0 1px var(--clay-500); }
}
.endorse-dot:focus-visible { border-radius: var(--radius-sm); }

/* The Design System's IconButton at its lg size — 48px, which clears the
   44px touch target on its own. One on each side of the endorsement,
   centred against the area rather than the Screen, and clear of the
   measure by Space 6.
   The outline variant's ring is gone: `border: 0`, not a transparent
   colour, so nothing is left to reappear. The box stays 48×48 because
   the reset is `box-sizing: border-box` — the border was inside the
   declared size, so dropping it moves no edge and the hit area is
   untouched. Radius, fill and transition all stay: the hover is the
   same Clay 100 disc over the same Duration Base and Ease Gentle. */
.endorse-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: var(--space-12);
  height: var(--space-12);
  display: grid;
  place-items: center;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: var(--radius-full);
  color: var(--accent-strong);
  cursor: pointer;
  transition: background var(--duration-base) var(--ease-gentle);
}
.endorse-arrow svg { width: var(--space-6); height: var(--space-6); }
@media (hover: hover) {
  .endorse-arrow:hover { background: var(--accent-soft); }
}
.endorse-arrow-prev { right: calc(100% + var(--space-6)); }
.endorse-arrow-next { left:  calc(100% + var(--space-6)); }
/* The ring was never the focus indicator — that is the global
   :focus-visible rule's Focus Ring box-shadow, which is unaffected by
   the border coming off. This only holds it round, since the global
   rule squares controls to Radius SM. */
.endorse-arrow:focus-visible {
  border-radius: var(--radius-full);
  box-shadow: var(--focus-ring);
}

@media (max-width: 899.98px) {
  #endorsements {
    padding-inline: var(--space-6);
    /* Same derivation, this Screen's own phone values: Space 20 of
       padding and a rule whose margins collapse to Space 8 either side. */
    --endorse-frame: calc(100vh
      - 2 * var(--space-20)
      - var(--text-xs)
      - var(--space-8)
      - (var(--space-8) + var(--border-width-hairline) + var(--space-8))
      - 44px);
  }
  .endorse-wrap { max-width: none; }
  /* No room beside the measure for them, and no pointer to hover them
     with: the swipe carries the turn and the dots carry the affordance. */
  .endorse-arrow { display: none; }
}
/* §3.6.1 — svh, as the desktop frame above, and for the same reason. */
@supports (height: 100dvh) {
  @media (max-width: 899.98px) {
    #endorsements {
      --endorse-frame: calc(100svh
        - 2 * var(--space-20)
        - var(--text-xs)
        - var(--space-8)
        - (var(--space-8) + var(--border-width-hairline) + var(--space-8))
        - 44px);
    }
  }
}

/* ============================================================
   Forms (§13.2) — the Design System's Forms section, transcribed
   from its Button, IconButton, Input and Textarea components. The
   pixel values below (button padding, field padding) are the
   components' own literals, not values invented here.
   ============================================================ */
.btn {
  font-family: var(--font-label);
  letter-spacing: var(--tracking-wide);
  font-weight: var(--weight-medium);
  border-radius: var(--radius-full);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  cursor: pointer;
  text-decoration: none;
  transition: background var(--duration-base) var(--ease-gentle),
              color var(--duration-base) var(--ease-gentle),
              transform var(--duration-fast) var(--ease-gentle),
              border-color var(--duration-base) var(--ease-gentle);
}
.btn:active { transform: scale(0.98); }   /* the component's soft press */
.btn-md { padding: 12px 26px; font-size: var(--text-base); }
.btn-lg { padding: 15px 34px; font-size: var(--text-md); }
/* The component fills `primary` with --accent. Clay 500 carries Sand 50
   at only 4.01:1 — the same failure that moved Screen 4's eyebrow off
   Clay 500 — so the fill is Clay 600 (5.39:1) and the hover deepens to
   Clay 700 (7.26:1) rather than stopping at 600. "Deepens on hover,
   never lightens" is preserved; only the starting stop moved. */
.btn-primary {
  background: var(--accent-strong);
  color: var(--text-on-accent);
  border: var(--border-width-default) solid var(--accent-strong);
}
@media (hover: hover) {
  .btn-primary:hover {
    background: var(--clay-700);
    border-color: var(--clay-700);
    color: var(--text-on-accent);
  }
}
/* §12 — the open state, and the reason it has to be one. A button whose
   panel is open deepens to Clay 700 and returns to Clay 600 when it
   closes. That was never actually expressed: the darker tone only ever
   came from :hover, which on a mouse happens to persist over an open
   panel and so passed for a state. Gating hover behind (hover: hover) —
   which was right, and is what stopped a tapped button staying dark
   after its panel had closed — left the phone with no open state at all,
   only the browser's own press flash. This is keyed to the attribute the
   script already writes in both directions, so it is a true state on
   every pointer. It follows the hover block deliberately: the two carry
   equal specificity and the open state must win. Only the two Contact
   buttons match — the menu button and the FAQ questions are not .btn-primary. */
.btn-primary[aria-expanded="true"] {
  background: var(--clay-700);
  border-color: var(--clay-700);
  color: var(--text-on-accent);
}
.btn:focus-visible { border-radius: var(--radius-full); }
.btn-chevron {
  width: var(--space-5);
  height: var(--space-5);
  transition: transform var(--duration-base) var(--ease-gentle);
}
.btn[aria-expanded="true"] .btn-chevron { transform: rotate(180deg); }

/* IconButton, ghost variant */
.icon-btn {
  width: var(--space-10);       /* the component's md, 40px */
  height: var(--space-10);
  padding: 0;
  display: inline-grid;
  place-items: center;
  border-radius: var(--radius-full);
  background: transparent;
  border: var(--border-width-default) solid transparent;
  color: var(--text-primary);
  cursor: pointer;
  transition: background var(--duration-base) var(--ease-gentle);
}
@media (hover: hover) {
  .icon-btn:hover { background: var(--sand-100); }
}
.icon-btn:focus-visible { border-radius: var(--radius-full); }
.icon-btn svg { width: var(--space-5); height: var(--space-5); }

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: 100%;
}
.field label {
  font-family: var(--font-label);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  color: var(--text-secondary);
}
.field input,
.field textarea {
  font-family: var(--font-body);
  font-size: var(--text-base);
  padding: 13px 18px;
  border-radius: var(--radius-md);
  border: var(--border-width-default) solid var(--border-default);
  background: var(--surface-raised);
  color: var(--text-primary);
  outline: none;
  transition: border-color var(--duration-base) var(--ease-gentle),
              box-shadow var(--duration-base) var(--ease-gentle);
}
.field textarea {
  resize: vertical;
  line-height: var(--leading-normal);
}
.field input:focus,
.field textarea:focus {
  box-shadow: var(--focus-ring);
  border-color: var(--accent);
}
.field[data-invalid] input,
.field[data-invalid] textarea { border-color: var(--color-error); }
.field-error,
.form-error {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
  color: var(--color-error);      /* Brick 500 — 6.22:1 on Sand 25 */
}

/* ============================================================
   Screen 8 — FAQ (§12A). Transcribed from the Design System's own
   FAQ section and its Accordion component: the eyebrow and heading
   above, the single-expand disclosure list below, on the page
   ground the Design System uses for both.
   ============================================================ */
#faq {
  display: grid;
  /* §3.6.1 — laid out from the top; rhythm from `.screen`. */
  padding-inline: var(--space-8);
}
.faq-wrap {
  width: 100%;
  max-width: var(--content-max-width-narrow);   /* the section's own width */
  margin: 0 auto;
}
/* The Design System's FAQ eyebrow: the label face, not the body face
   the other Screens' eyebrows use, and its own 12px literal. */
.faq-eyebrow {
  font-family: var(--font-label);
  font-size: 12px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--accent-strong);      /* Clay 600 — 5.39:1 on Sand 50 */
  margin-bottom: var(--space-5);
}
.faq-heading {
  font-family: var(--font-display);
  font-weight: var(--weight-regular);
  font-size: clamp(28px, 3vw, 38px);
  line-height: 1.2;
  letter-spacing: var(--tracking-tight);
  color: var(--text-primary);
  margin-bottom: var(--space-8);
}

.acc-item { border-bottom: var(--border-width-default) solid var(--border-subtle); }
.acc-item h3 {
  margin: 0;
  font: inherit;
  letter-spacing: inherit;
}
.acc-q {
  width: 100%;
  background: none;
  border: none;
  cursor: pointer;
  padding: var(--space-5) 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
  text-align: left;
  font-family: var(--font-body);
  font-size: var(--text-md);
  line-height: var(--leading-snug);
  color: var(--text-primary);
}
.acc-q svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  transition: transform var(--duration-base) var(--ease-gentle);
}
.acc-q[aria-expanded="true"] svg { transform: rotate(180deg); }
/* The component caps the open panel at a fixed 240px, which would cut
   the second answer off. 0fr→1fr opens to whatever the answer needs
   and is identical to the eye for anything under that cap. */
.acc-panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--duration-slow) var(--ease-gentle);
}
.acc-panel.open { grid-template-rows: 1fr; }
.acc-panel > .acc-body {
  overflow: hidden;
  visibility: hidden;
  transition: visibility 0s linear var(--duration-slow);
}
.acc-panel.open > .acc-body {
  visibility: visible;
  transition-delay: 0s;
}
.acc-body p,
.acc-body li {
  color: var(--text-secondary);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
}
.acc-body > :last-child { padding-bottom: var(--space-5); }
/* The credentials are four separate qualifications, so they are set as
   a list rather than run together into a sentence. */
.acc-list {
  margin: 0;
  padding-bottom: var(--space-5);
  padding-left: var(--space-5);
}
.acc-list li + li { margin-top: var(--space-2); }

@media (max-width: 899.98px) {
  #faq { padding: var(--space-20) var(--space-6); }
}

/* ============================================================
   Screen 8 — Contact (§13). Two ways to reach out, carrying equal
   weight, each opening downward into its own panel.
   ============================================================ */
#contact {
  display: grid;
  /* §3.6.1 — laid out from the top; rhythm from `.screen`. This is
     also the CONTACT heading fix. The heading sat lower than every
     other Screen's because the whole block was centred in a
     viewport-tall box rather than starting at the top padding, and it
     rose when a panel opened because the block grew taller and a
     centred block grows from both ends. Neither can happen now. */
  padding-inline: var(--space-8);
  /* §3.6.2 — the last Screen alone keeps a height, and it is about the
     menu rather than about the layout. A jump can only scroll to
     `scrollHeight - innerHeight`, so the last Screen's top is only
     reachable while there is a viewport of document below it. With
     natural heights, Contact plus the footer came to 815px against a
     760px viewport — 55px of headroom, and negative on a taller
     window, which would land a CONTACT click short of its own heading.

     §3.6.3 — but not on a phone. This was `min-height: 100lvh`, on the
     reasoning that lvh is a constant and so cannot be moved by the
     address bar. On the evidence of three mobile browsers it moved
     anyway, and the bottom of this Screen expanded and contracted with
     the bars. Whatever the specification says, the units are not
     dependable in a phone's browser, so none of them is used anywhere
     a phone can see.

     The reachability problem it solved is a desktop problem in any
     case. It only bites when the viewport is taller than the Screen's
     content plus the footer, and a desktop window can be 1400px tall
     where a phone cannot. At 390px wide this Screen's content is 606px
     and the footer 331px — 937px below its own top, more than any
     phone viewport — so on a phone it is reachable with no height at
     all. Above the phone breakpoint `vh` is safe for the plain reason
     that a desktop browser has no chrome that moves while scrolling. */
}
@media (min-width: 900px) {
  #contact { min-height: 100vh; }
}
.contact-wrap {
  width: 100%;
  /* Wider than the 46ch the reading Screens use, because the form
     needs the room; the prose inside is still held to that measure so
     the reading rhythm does not change. */
  max-width: var(--content-max-width-narrow);
  margin: 0 auto;
}
.contact-eyebrow {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--clay-600);         /* 5.39:1 on Sand 50, as Screen 4 */
}
/* This carried a 46ch cap so the measure matched Screens 4, 5 and 6.
   Removed: Contact sits directly under the FAQ, and against the
   accordion's full 760px the shorter measure read as a narrower column
   pushed to the left rather than as a deliberate one. The line length
   now matches the Screen above it, which is the comparison a visitor
   actually makes. */
.contact-prose { max-width: none; }
.contact-lede {
  margin-top: var(--space-8);
  font-family: var(--font-body);
  font-size: clamp(1.125rem, 1.8vw, var(--text-lg));
  font-weight: var(--weight-regular);
  line-height: var(--leading-relaxed);
  color: var(--text-secondary);
}
/* A sign-off, so it takes the display face rather than trailing the
   paragraph as another sentence. */
.contact-signoff {
  margin-top: var(--space-4);
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.6vw, var(--text-xl));
  font-weight: var(--weight-light);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  color: var(--text-primary);
}
.contact-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;   /* centred in the 760px column, not left-hung */
  gap: var(--space-4);
}

/* The drawers open on their own height — 0fr to 1fr needs no measuring
   and so cannot disagree with the content inside it. Duration Slow, not
   Base: the open was already eased on Base and still read as abrupt,
   because these panels are tall enough that 260ms is most of a Screen
   travelling in a quarter of a second. */
.contact-drawer {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--duration-slow) var(--ease-gentle);
}
.contact-drawer.open { grid-template-rows: 1fr; }
.contact-drawer > .drawer-inner {
  overflow: hidden;
  /* Nothing inside a closed drawer is focusable or reachable by a
     screen reader; the delay holds it visible until the collapse has
     finished, so it tracks the duration above. */
  visibility: hidden;
  transition: visibility 0s linear var(--duration-slow);
}
.contact-drawer.open > .drawer-inner {
  visibility: visible;
  transition-delay: 0s;
}
/* Both drawers open the same panel — one class, two instances — so the
   cap belongs on the WhatsApp one alone. A number and a button need
   half the column; a four-field form does not, and reverting the email
   panel to the full 760px is what the cap cost it. */
#whatsapp-drawer .contact-panel {
  max-width: calc(var(--content-max-width-narrow) / 2);
  margin-inline: auto;
}
.contact-panel {
  margin-top: var(--space-6);
  background: var(--surface-raised);
  border: var(--border-width-default) solid var(--border-subtle);
  border-radius: var(--radius-xl);
  padding: clamp(var(--space-6), 4vw, var(--space-12));
  box-shadow: var(--shadow-sm);
}
#contact-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}
/* The send button and its confirmation, on one line. They wrap onto two
   when the panel is too narrow to hold both — the message is a whole
   sentence, so it needs the room more than the button does. Centring on
   the cross axis keeps the message's baseline sitting against the
   button's middle rather than its top. */
.form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
}
/* Name and Email share a row only when the panel is wide enough to give
   each of them a usable field. The query is on the panel, not the
   viewport, so raising or lowering its cap above is the only edit
   needed — this follows on its own. */
.contact-panel { container-type: inline-size; }
.field-pair {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
}
@container (min-width: 380px) {
  .field-pair { grid-template-columns: 1fr 1fr; }
}
/* The two buttons that actually send something go to Moss, leaving Clay
   to the pair that only open a drawer — so the ground the visitor is
   standing on is a different colour from the step that leaves it. The
   fill was Clay 600, not Clay 500: the Design System's own Clay 500
   primary was moved a stop for contrast when the Forms section was
   transcribed, and the hover sat a stop below at Clay 700 rather than
   at 600. Moss keeps that shape — 700 at rest, 800 on hover — and its
   inks are stronger than the Clay they replace: Sand 50 reads 7.44:1
   on Moss 700 and 9.80:1 on Moss 800, against 5.39 and 7.26 before. */
#cf-submit,
.wa-reach {
  background: var(--moss-700);
  border-color: var(--moss-700);
}
@media (hover: hover) {
  #cf-submit:hover,
  .wa-reach:hover {
    background: var(--moss-800);
    border-color: var(--moss-800);
  }
}

/* The tick rides inside the button and only appears once the send has
   been confirmed, so the button's width grows by the icon and its gap
   at that moment. .btn already supplies the gap. */
.btn-tick { display: none; width: var(--space-5); height: var(--space-5); }
#cf-submit[data-state="sent"] .btn-tick { display: block; }
/* Sent. The control stops presenting itself as one: the Design System's
   success-soft ground with its success ink and border, rather than the
   filled Moss it wore as an available action — a finished step should
   not look like one still on offer. Moss 600 reads 5.57:1 on Moss 50,
   and the border carries the shape at 5.99:1 on the panel's Sand 25,
   well past the 3:1 a boundary needs. The hover is overridden rather
   than merely unset, or a pointer would still deepen it to Moss 800. */
#cf-submit[data-state="sent"],
#cf-submit[data-state="sent"]:hover {
  background: var(--color-success-soft);
  border-color: var(--color-success);
  color: var(--color-success);
  cursor: default;
}

/* Karla, per the same rule the field errors follow: the Design System's
   semantic colour for the state, at body-copy size. Moss 600 is 5.99:1
   on Sand 25. */
.form-thanks {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  color: var(--color-success);
  margin: 0;
}

/* The panel's two children sit on its centre line at every width: the
   button first, the number under it. text-align carries the button,
   which is inline-flex; the number is a flex row and needs its own. */
#whatsapp-drawer .contact-panel { text-align: center; }
.wa-number {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  margin-top: var(--space-6);
}
/* Plain text beside the copy control — the input-style frame this
   carried has been removed; the card around it is containment enough. */
.wa-digits {
  font-family: var(--font-body);
  /* One step down the scale at both ends — LG to MD at the ceiling, and
     the 1.125rem literal the other Contact type uses becomes the Base
     token below it. Starting point, to be confirmed. */
  font-size: clamp(var(--text-base), 1.8vw, var(--text-md));
  color: var(--text-primary);
  /* A phone number broken across two lines is no longer a phone number */
  white-space: nowrap;
}
/* The IconButton's ghost variant is transparent until hover. This one
   instance keeps its circle at all times, so the copy control reads as
   a control before anyone touches it — scoped to the id, leaving the
   component itself as the Design System defines it. The hover then
   deepens a stop rather than appearing from nothing, which is how every
   other control on this Screen moves: Clay 600→700 on the drawer
   toggles, Moss 700→800 on the two send buttons. Guarded on hover
   capability so a tap on a phone cannot leave the darker state stuck.
   The transition on .icon-btn already carries it, on Duration Base. */
#wa-copy { background: var(--sand-100); }
@media (hover: hover) {
  #wa-copy:hover { background: var(--sand-200); }
}

/* One control, two glyphs: the tick takes the copy icon's place for a
   moment rather than being added beside it. */
.icon-btn .icon-tick { display: none; color: var(--color-success); }
.icon-btn.copied .icon-copy { display: none; }
.icon-btn.copied .icon-tick { display: block; }

@media (max-width: 899.98px) {
  #contact { padding: var(--space-20) var(--space-6); }
  /* The buttons were stretched edge to edge here so that neither way to
     reach out looked like the lesser one. They are pills at every width
     now: equal weight comes from the two being identical and sitting
     side by side, which survives the change. Nothing overrides the base
     rules any more — .btn is inline-flex, so each one is its label plus
     the component's own padding, exactly as on desktop. */
}

/* ============================================================
   Closing element (§14) — a bar at the end of Screen 8, on the
   inverse ground. Plain text, no link styling, but the number and
   the address are live (§14.4).
   ============================================================ */
#site-footer {
  background: var(--surface-inverse);
  color: var(--text-on-inverse);
  padding: var(--space-12) var(--space-8);
  /* §14.1 — it arrives at the end rather than being there all along */
  opacity: 0;
  transform: translateY(var(--space-4));
  transition: opacity var(--duration-slow) var(--ease-gentle),
              transform var(--duration-slow) var(--ease-gentle);
}
#site-footer.shown {
  opacity: 1;
  transform: none;
}
.footer-inner {
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: grid;
  justify-items: center;
  gap: var(--space-6);
}
/* §14 — the lockup is sized by its mark, and the mark is deliberately
   NOT tied to --nav-mark-height: at the nav's size the wordmark's caps
   come out at 8.6px, and the nav is capped at 40px so it never clears
   10px at any viewport. Space 12 puts the mark at 48px and the caps at
   11.3px. The footer and the Navigation Bar are different contexts and
   do not have to agree.
   --lockup-ratio is written by the script from the artwork's own
   geometry — how tall the whole lockup stands per unit of mark — so the
   proportions are never restated here. */
#site-footer { --footer-mark-height: var(--space-12); }
.footer-lockup {
  display: block;
  text-decoration: none;
  /* The lockup stands 75px, so the target clears 44px on its own. */
}
.footer-lockup svg {
  display: block;
  height: calc(var(--footer-mark-height) * var(--lockup-ratio, 1.5669));
  width: auto;
}
/* The footer's ground is the inverse one and never changes, so it takes
   the light wordmark outright rather than answering the nav's theme. */
.footer-lockup .wm-ink-dark  { display: none; }
.footer-lockup .wm-ink-light { display: block; }
.footer-contacts {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-10);
}
/* §14.4 — styled as plain text: no underline, no link ink, no hover
   that suggests a link. Still a live tel:/mailto: target. */
.footer-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 44px;
  font-family: var(--font-body);
  font-size: var(--text-base);
  color: var(--text-on-inverse);
  text-decoration: none;
}
@media (hover: hover) {
  .footer-item:hover { color: var(--text-on-inverse); }
}
.footer-item svg {
  width: var(--space-5);
  height: var(--space-5);
  flex-shrink: 0;
}
.footer-copy {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--sand-200);         /* 8.92:1 on the ink ground */
}
@media (max-width: 899.98px) {
  #site-footer { padding: var(--space-10) var(--space-6); }
  /* §14.3 — phone number, then email, then copyright */
  .footer-contacts {
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
  }
  .footer-inner { gap: var(--space-4); }
  /* On a phone the lockup sat on the same Space 4 as everything else, so
     it read as one more line in the stack rather than the mark above it.
     Space 6 on top of the gap gives it 40px of its own — tune on review.
     Desktop keeps the even Space 6 rhythm, which it carries at that
     size. */
  .footer-lockup { margin-bottom: var(--space-6); }
}

/* ============================================================
   Reduced motion (§3.5, answer 11): Screens appear one after
   another; Screen 1 static final frame; Screen 2 mid state.
   ============================================================ */
html.no-motion #pin-track { height: auto !important; }
html.no-motion #pin-stage {
  position: static;
  height: auto;
  overflow: visible;
}
html.no-motion #screen-1,
html.no-motion #screen-2,
html.no-motion #screen-3 {
  position: relative;
  inset: auto;
  opacity: 1 !important;
  height: 100vh;
  visibility: visible !important;
}
html.no-motion #dim,
html.no-motion #mist { display: none; }
html.no-motion #s2-text { opacity: 1 !important; }
/* Screen 3 static state (§8A.5). This is now one line: show it. The
   Screen already centres its own content, so the flex column, the gap,
   the auto height and the extra padding that existed to stack five
   blocks down a taller-than-viewport Screen have all gone with them,
   and Screen 3 is an ordinary one-viewport Screen like 1 and 2 above.
   The clip-drift reset went too — there is no longer a transform to
   undo. */
html.no-motion .s3-block { opacity: 1 !important; }
/* The reduced-motion Screens are ordinary sections, and a phone's
   browser chrome moves whether motion is on or off — so they take the
   same visible-viewport unit as .screen above, with the 100vh in the
   rule preceding as the fallback. */
@supports (height: 100dvh) {
  html.no-motion #screen-1,
  html.no-motion #screen-2,
  html.no-motion #screen-3 { height: 100dvh; }
}
html.no-motion main { margin-top: 0 !important; }
html.no-motion #nav-bar,
html.no-motion #nav,
html.no-motion #menu,
html.no-motion #menu-btn,
html.no-motion #menu-panel,
html.no-motion #es-anim { transition: none; }
html.no-motion #es-anim { cursor: default; }
/* §12 — the endorsement changes, it does not travel or fade */
html.no-motion .endorse-slide {
  transform: none !important;
  transition: none !important;
}
html.no-motion .endorse-dot::before,
html.no-motion .endorse-arrow { transition: none; }
/* §12A/§13/§14 — panels open and the footer is simply there */
html.no-motion .acc-panel,
html.no-motion .acc-panel > .acc-body,
html.no-motion .acc-q svg,
html.no-motion .contact-drawer,
html.no-motion .contact-drawer > .drawer-inner,
html.no-motion .btn,
html.no-motion .btn-chevron,
html.no-motion .icon-btn,
html.no-motion .field input,
html.no-motion .field textarea { transition: none; }
html.no-motion #site-footer {
  opacity: 1;
  transform: none;
  transition: none;
}
