/* ============================================================
   Gaye del Sol Studios — site stylesheet
   Black ink on white paper, with one dark green (--surface) for filled surfaces.
   Libre Bodoni (display) + Hanken Grotesk (body).

   One 12-column container grid, one spacing scale, one type scale.
   Priorities: readability, then hierarchy, then premium finish.
   ============================================================ */

/* ---------- Fonts (self-hosted, variable) ---------- */

@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 300 500;
  font-display: swap;
  src: url('../assets/fonts/hanken-grotesk-latin.woff2?v=1f21c6eaa0') 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: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 300 500;
  font-display: swap;
  src: url('../assets/fonts/hanken-grotesk-latin-ext.woff2?v=a1376a5637') 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: 'Libre Bodoni';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url('../assets/fonts/libre-bodoni-latin.woff2?v=749ca6b069') 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: 'Libre Bodoni';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url('../assets/fonts/libre-bodoni-latin-ext.woff2?v=dfad3c63d5') 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: 'Libre Bodoni';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/libre-bodoni-italic-latin.woff2?v=131a772a5c') 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: 'Libre Bodoni';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/libre-bodoni-italic-latin-ext.woff2?v=fd4decc070') 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;
}

/* ============================================================
   Design tokens
   ============================================================ */

:root {
  /* palette — black ink on white paper, one green for filled surfaces.
     Greys are ink at reduced alpha.
     Body >= 0.82 and every secondary tone >= 0.62 to hold WCAG AA. */
  --paper: #ffffff;
  --ink: #000000;
  --ink-body: rgba(0, 0, 0, 0.82);   /* all running body copy */
  --ink-label: rgba(0, 0, 0, 0.62);  /* labels, captions, attributions (AA on white) */
  --rule: rgba(0, 0, 0, 0.14);
  --rule-strong: rgba(0, 0, 0, 0.32);

  /* THE ONE FILLED-SURFACE COLOUR, and the only place it is written down.
     Every solid block on the site reads from it: buttons, the film band, the
     assessment result, the Kit submit button, the skip link, the selection
     highlight. Change this line and the whole site changes; nothing anywhere
     else hard-codes a fill.

     It is a SURFACE colour, never a text colour. Body copy, headings, rules
     and marks stay --ink. What goes ON a filled surface is one of the three
     --surface-fg tokens below and never a hard-coded colour, which is what
     makes the line above the only place the accent is decided.

     History: #39442a on 2026-09-02 replacing #000, darkened to #252919 the
     same day, the terracotta #D75A2B on 2026-09-04, and back to #252919 on
     2026-09-09. Every one of those at the client's request.

     ** THE FOREGROUND IS NOT A PREFERENCE. IT IS FORCED BY THE SURFACE. **
     It has now flipped twice, in both directions, because the two colours
     sit on opposite sides of the scale:

         surface           luminance   white      black
         #252919 (green)     0.0205    14.89:1     1.41:1
         #D75A2B (orange)    0.2193     3.90:1     5.39:1

     The .btn label is --type-label, 13px, which is normal text and needs
     4.5:1. On the green that is white; on the orange white FAILED and it
     had to be black. Do not assume either direction for the next colour.

     ** Run `python3 _partials/check_contrast.py` after changing the line
     below. ** It reads these four tokens, composites the alphas over the
     real surface, and fails on anything under AA. That is what makes this a
     one-line change: the check tells you which of the three foregrounds is
     now wrong, instead of a comment asking you to remember. */
  --surface: #252919;
  --surface-hover: #3a4127;  /* same hue (75 deg) and saturation, +7.5% lightness */
  --surface-fg: #ffffff;     /* 14.89:1 on --surface, 10.69:1 on --surface-hover */
  /* Two quieter tones, the mirror of --ink-body and --ink-label on paper.
     They exist only because this surface has 14.89:1 of headroom to spend.
     The orange had 5.39:1, where --soft was the only one that still cleared
     AA and --quiet did not exist at all: the places that use it took solid
     --surface-fg and separated by size and weight instead. If the accent
     ever goes back to something mid-scale, expect to lose --quiet again and
     let check_contrast.py tell you rather than guessing. */
  --surface-fg-soft: rgba(255, 255, 255, 0.86);   /* 11.35:1, body copy on a surface */
  --surface-fg-quiet: rgba(255, 255, 255, 0.62);  /*  6.65:1, labels on a surface */

  --btn-hover: var(--surface-hover);
  --band-bg: var(--surface);
  --band-fg: var(--surface-fg);
  --slot-bg: #eceae6;   /* the one placeholder tone; nothing on the site renders dark */

  /* type families */
  --font-display: 'Libre Bodoni', Didot, 'Times New Roman', serif;
  --font-body: 'Hanken Grotesk', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  /* strict type scale */
  --type-h1:        clamp(42px, 5.4vw, 92px);  /* hero only */
  /* Full-bleed statement lines. Sits below the section headings, which sit
     below the h1: a band is a statement, not the loudest thing on the page.
     It used to run at 5vw/84px, a hair off the h1 and half again the size
     of every h2, which read as the page shouting its own aside. */
  --type-display:   clamp(24px, 2.9vw, 46px);
  --type-statement: clamp(28px, 3.4vw, 54px);  /* a section's serif heading */
  --type-lede:      clamp(19px, 1.7vw, 23px);  /* opening line / quote reading scale */
  --type-body:      clamp(16px, 1.4vw, 18px);  /* 16 on mobile, ~17-18 on desktop */
  --type-label:     13px;                       /* eyebrow labels, attributions */
  --type-caption:   12px;                       /* fine print, mono notes */

  /* readability */
  --measure: 54ch;   /* renders ~56-60 actual characters, within 52-62 */
  --lh-body: 1.7;    /* within 1.65-1.75, used everywhere for body */

  /* grid — one container, one gutter, twelve columns */
  --container-max: 1200px;
  --margin: clamp(20px, 5vw, 72px);   /* page side margin */
  --gutter: clamp(16px, 1.8vw, 28px); /* the single gutter value */
  --content-w: min(100% - 2 * var(--margin), var(--container-max)); /* grid content width */

  /* Spacing scale — four steps, used exclusively.

     Tightened twice for the same reason: the client reads the site as
     needing too much scrolling. 2026-09-01 took --space-4 from 124px to
     88px. 2026-09-02 took it to 64px AND tightened the two steps beneath
     it, because with the section padding down the gap left over was coming
     from --space-3 (52px) between the blocks inside a section, which the
     first pass had not touched. Moving one token without the others just
     relocates the whitespace. --space-1 is unchanged: it is the label to
     heading step, it is already at 14px, and it is not what makes a page
     long.

     Two sections stacked put 2 x --space-4 between them, so the section
     boundary is the number that matters: it was 248px, then 176px, and is
     now 128px at full width. */
  --space-1: clamp(10px, 0.9vw, 14px);  /* label -> heading (unchanged) */
  --space-2: clamp(15px, 1.3vw, 19px);  /* heading -> body, para -> para */
  --space-3: clamp(22px, 2.4vw, 38px);  /* block -> block, inside a section */
  /* Section padding, sitewide. This is the only token .section, .hero,
     .band--statement, .filmband, .hiw and the footer take their rhythm
     from, so one value moves every page and nothing is patched per page. */
  --space-4: clamp(38px, 4.1vw, 64px);  /* section padding (sitewide) */

  /* The header bar's own height, written from the same two values the bar
     is built out of: its padding-block, twice, plus the wordmark's line box.
     It exists so the mobile flyout can start below the bar without anything
     measuring it at runtime. If either of those two values changes, this
     changes with them and nothing else has to.

     1.7 is Libre Bodoni's `normal` line box, measured, not guessed: the
     wordmark renders at exactly 1.7 x its font-size at every width from 360
     to 820. It shipped as 1.35 on 2026-09-09 and put this token 5.6px under
     the real bar, which ate a third of the flyout's 18px clearance. If the
     wordmark ever takes an explicit line-height, put that number here. */
  --nav-bar-h: calc(2 * clamp(16px, 2vw, 24px) + 1.7 * clamp(16px, 1.4vw, 20px));

  /* letter-spacing */
  --ls-tight: -0.02em;
  --ls-label: 0.04em;
}

/* ============================================================
   Reset / base
   ============================================================ */

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

html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink-body);
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--type-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3, p, blockquote, figure, dl, dd { margin: 0; }

a { color: inherit; text-decoration: none; }
a:hover { color: var(--ink-label); }

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

::selection { background: var(--surface); color: var(--surface-fg); }

:focus-visible { outline: 1px solid currentColor; outline-offset: 4px; }

/* The shared rule above draws the focus ring in currentColor, 4px OUTSIDE the
   box. On a filled surface currentColor is white and those 4px are paper, so
   the ring came out white on white and could not be seen. It was invisible
   against the old black fill too, so this is not new, but a contrast pass is
   the moment to settle it: a filled block on paper takes an ink ring (21:1 on
   the paper it lands on), and a white block on a filled band takes a white
   one. */
.btn:focus-visible,
.skip-link:focus-visible,
.kitform .formkit-submit:focus-visible,
.kitform button[type="submit"]:focus-visible { outline-color: var(--ink); }
.btn--invert:focus-visible { outline-color: var(--surface-fg); }

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--surface);
  color: var(--surface-fg);
  padding: 12px 18px;
  font-size: var(--type-label);
  letter-spacing: var(--ls-label);
}
.skip-link:focus { left: var(--margin); top: 12px; color: var(--surface-fg); }

/* ---------- Grain texture: removed 2026-08-21 ----------
   A fixed noise layer at mix-blend-mode: multiply sat over the whole
   document, photographs included, and it was the last thing on the
   site rendering an image at anything other than its own brightness.
   The design rule is that nothing sits on a photograph: no scrim, no
   overlay, no filter. A 3% multiply is still a filter. Every image
   now renders exactly as it was normalised, and the paper is paper.
   The .grain div went out of all 29 pages in the same pass. */

/* ============================================================
   Grid system
   ============================================================ */

.container {
  width: 100%;
  max-width: calc(var(--container-max) + 2 * var(--margin));
  margin-inline: auto;
  padding-inline: var(--margin);
}
.grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--gutter);
  row-gap: var(--space-3);
}

/* column spans — left edges always land on a grid line */
.c-12 { grid-column: 1 / 13; }
.c-8  { grid-column: 1 / 9; }
.c-7  { grid-column: 1 / 8; }
.c-6  { grid-column: 1 / 7; }
.c-5  { grid-column: 1 / 6; }
.c-r7 { grid-column: 6 / 13; }
.c-r6 { grid-column: 7 / 13; }
.c-r5 { grid-column: 8 / 13; }

@media (max-width: 820px) {
  .grid > * { grid-column: 1 / -1; }
}

/* ============================================================
   Copy set against a photograph — .copy-middle
   A grid row stretches every column to the height of the tallest, so a short
   block of copy beside a 4:5 photograph or a portrait film sits at the top
   of the row and the whole difference piles up underneath it. That is what
   reads as the column ending halfway down with a large empty area beside
   the picture, and the client flagged it on 2026-09-03.

   Centred, the same difference is split above and below and the two columns
   read as one row rather than as a column that ran out. It is one
   declaration on purpose: the photographs keep their own ratios, which are
   a design rule, and nothing is cropped to make a row shorter.

   Heroes are deliberately NOT in this: .hero__text sets its own rhythm
   against the hero frames and the client's note excluded them.
   ============================================================ */
.copy-middle { align-self: center; }

/* Editorial placement, for pages that need more than the eight standard
   spans (/client-love). `.at-N` sets which grid line a block starts on and
   `.sp-N` how many columns it runs. Composable, and asymmetric by choice of
   line rather than by an arbitrary offset, so every left edge still lands on
   a grid line. Declared above 820px only: below that the rule above puts
   every block in one column, and these must not fight it. */
@media (min-width: 821px) {
  .at-1  { grid-column-start: 1; }  .at-2  { grid-column-start: 2; }
  .at-3  { grid-column-start: 3; }  .at-4  { grid-column-start: 4; }
  .at-5  { grid-column-start: 5; }  .at-6  { grid-column-start: 6; }
  .at-7  { grid-column-start: 7; }  .at-8  { grid-column-start: 8; }
  .at-9  { grid-column-start: 9; }  .at-10 { grid-column-start: 10; }
  .sp-2  { grid-column-end: span 2; }  .sp-3 { grid-column-end: span 3; }
  .sp-4  { grid-column-end: span 4; }  .sp-5 { grid-column-end: span 5; }
  .sp-6  { grid-column-end: span 6; }  .sp-7 { grid-column-end: span 7; }
  .sp-8  { grid-column-end: span 8; }
}

/* ---------- Section padding (one value, sitewide) ---------- */
.section { padding-block: var(--space-4); }

/* Hairline separator between two sections that share a background.
   Sits at the shared boundary; the two sections' equal padding gives
   it equal space above and below. Container-width, flush with the
   grid edges — never full-bleed. Only applied where a background
   change or a full-bleed band does not already separate the sections. */
.section--rule { position: relative; }
.section--rule::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: var(--content-w);
  height: 1px;
  background: rgba(0, 0, 0, 0.12);
}

/* ============================================================
   Sticky booking link, phones only
   ============================================================ */

/* A booking link that is always within reach on a phone, and restrained the
   way everything else here is: paper ground, one hairline above it, black
   text set at label size. No fill, no shadow, no colour, no arrow.

   It never covers anything. <body> reserves exactly the bar's height at the
   same breakpoint, safe-area inset included, so the last line of the footer
   clears it rather than sitting behind it. That is why the height is a
   custom property and not a number written twice.

   Hidden in edit mode: the editor puts its own save bar along the bottom at
   z-index 200, and two bars stacked there is one too many. It also sits
   below the header's z-index so the nav panel, which opens downward from the
   top bar on the same breakpoint, is never behind it. */

.book-sticky { display: none; }

@media (max-width: 820px) {
  :root { --book-sticky-h: 52px; }
  body { padding-bottom: calc(var(--book-sticky-h) + env(safe-area-inset-bottom, 0px)); }
  .book-sticky {
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 65;
    min-height: var(--book-sticky-h);
    padding: 0 var(--margin) env(safe-area-inset-bottom, 0px);
    background: var(--paper);
    border-top: 1px solid var(--rule-strong);
    color: var(--ink);
    font-size: var(--type-label);
    letter-spacing: var(--ls-label);
    text-decoration: none;
  }
  .gds-edit .book-sticky { display: none; }
  .gds-edit body { padding-bottom: 0; }
}

/* ============================================================
   Shared type pieces
   ============================================================ */

.eyebrow {
  display: block;
  margin: 0 0 var(--space-1);
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--type-label);
  letter-spacing: var(--ls-label);
  color: var(--ink-label);
}

/* ** text-wrap is `pretty`, and it is NOT a detail. **
   `balance` evens a heading's lines by SHORTENING every one of them, so a
   heading in a full-width column stops well short of the right edge while
   its box still measures the full 1200px. It is a max-width nothing wrote
   down: `grep max-width` never found it and audit_wide_copy.py never looked
   for it, which is why five rounds of the wide-copy fix went past it.

   Measured on the homepage, "I've had coaching before...", same 3 lines
   either way, longest line as a fraction of its 1200px box:

       viewport   balance   pretty
       1280px       79%      95%
       1366px       81%      97%
       1440px       85%      94%
       1600px       94%      93%

   The loss moves with the viewport, which is why it kept coming and going
   and why a fix checked at one width passed. `pretty` fills the same as
   plain `wrap` here and still protects against a one-word last line.
   Changed 2026-09-09; see CLAUDE.md, "Display type is copy too." */
.h2 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--type-statement);
  line-height: 1.05;
  letter-spacing: var(--ls-tight);
  color: var(--ink);
  text-wrap: pretty;
}

/* A section heading asked to run the full width of its section.
   --type-statement sets this quote on /results at 54px, where it wraps to
   two lines of 968 and 936 inside a 1200px column: the box is full width,
   the TEXT stops 232px short of the right edge, and that gap is what the
   client saw. Nothing about the column was wrong, so widening it again
   would have changed nothing.

   66px is where the same words fill: two lines of 1184 and 1143 of 1200,
   99%. 68px tips it to three short lines, so this is a real ceiling rather
   than a round number.

   4.95vw is the ramp that holds that proportion at every width, not a
   guess: the container is 0.9vw until it caps at 1200px around 1333px of
   viewport, so 0.055 of the container is 0.0495 of the viewport, and the
   66px ceiling lands exactly where the container's own cap does. The lines
   therefore fill the same 99% at 900px as at 1920px.

   ** Not --content-w. ** See the note at .hero--long: a percentage inside a
   font-size calc resolves against the parent font size, not a width, so
   that collapses to the clamp's floor.

   Scoped on purpose. --type-statement is untouched, so every other section
   heading on the site still runs at 54px, and the h1 above still runs to
   92px, so the type hierarchy holds. Added 2026-09-04. */
.h2--fill { font-size: clamp(28px, 4.95vw, 66px); }

.body {
  max-width: var(--measure);
  font-size: var(--type-body);
  line-height: var(--lh-body);
  color: var(--ink-body);
  text-wrap: pretty;
}
.lede {
  max-width: 42ch;
  font-size: var(--type-lede);
  line-height: 1.5;
  color: var(--ink-body);
  text-wrap: pretty;
}
.italic { font-style: italic; }

/* heading -> body spacing (one value) */
.h2 + .body,
.h2 + .lede { margin-top: var(--space-2); }
/* Same gap when the copy is wrapped in .copy-wide. It has to sit on the
   wrapper: a margin on the first paragraph would be inside the columns and
   would drop column one below column two. */
.h2 + .copy-wide { margin-top: var(--space-2); }
.body + .body,
.lede + .body { margin-top: var(--space-2); }

.btn {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  background: var(--surface);
  color: var(--surface-fg);
  padding: 13px 20px;
  font-size: var(--type-label);
  letter-spacing: var(--ls-label);
  font-family: inherit;
  border: 0;
  cursor: pointer;
  transition: background 0.4s ease;
}
.btn:hover { background: var(--btn-hover); color: var(--surface-fg); }
.btn--sm { padding: 11px 18px; }
.btn--invert { background: var(--paper); color: var(--ink); }
.btn--invert:hover { background: #e6e6e6; color: var(--ink); }

.link-underline {
  font-size: var(--type-label);
  letter-spacing: 0.03em;
  padding-bottom: 5px;
  border-bottom: 1px solid var(--rule-strong);
}
.link-underline:hover { color: var(--ink); border-color: var(--ink); }

/* ---------- Image placeholders ---------- */
.img-slot {
  position: relative;
  width: 100%;
  height: 100%;
  background: var(--slot-bg);
  overflow: hidden;
}
.img-slot img { width: 100%; height: 100%; object-fit: cover; }

/* Food still strip — restrained hairline row (/energy-nutrition) */
.food-strip { padding-block: clamp(40px, 6vw, 80px); } /* separator comes from .section--rule */
.food-strip__row { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--gutter); margin: 0; }
.food-strip__row .img-slot { aspect-ratio: 3 / 2; }
@media (max-width: 860px) { .food-strip__row { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 560px) { .food-strip__row { grid-template-columns: repeat(2, 1fr); } }

/* Recipes on /resources — plain, hairline-separated, no cards */
.recipe { border-top: 1px solid var(--rule); padding-top: var(--space-3); margin-top: var(--space-3); max-width: var(--measure); }
.recipe h3 { font-family: var(--font-display); font-weight: 400; font-size: var(--type-h3); line-height: 1.15; margin: 0; }
.recipe__desc { color: var(--ink-body); margin-top: var(--space-1); }
.recipe__meta { font-family: var(--font-mono); font-size: var(--type-caption); color: var(--ink-label); letter-spacing: .02em; margin-top: var(--space-1); }
.recipe ul, .recipe ol { margin: var(--space-1) 0 0; padding-left: 1.2em; }
.recipe li { font-size: var(--type-body); line-height: var(--lh-body); color: var(--ink-body); margin: 4px 0; }

/* ============================================================
   Header — minimal sticky bar that recedes over the hero and
   picks up a hairline once scrolled. No blur, no shadow, no pills.
   ============================================================ */

.site-header {
  position: sticky;
  top: 0;
  z-index: 70;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background 0.3s ease, border-color 0.3s ease;
}
.site-header.is-scrolled {
  background: var(--paper);
  border-bottom-color: rgba(0, 0, 0, 0.12);
}
.site-header__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding-block: clamp(16px, 2vw, 24px);
}
.wordmark {
  font-family: var(--font-display);
  font-size: clamp(16px, 1.4vw, 20px);
  letter-spacing: 0.01em;
}

/* ---------- Nav: a bar, not an overlay ----------
   Four pages fit in the top bar, so they live in it. The same list becomes
   a stacked panel under the bar below 820px, where a row will not fit. No
   modal, no focus trap: it is a disclosure, and tab order already follows
   the DOM. */
.site-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: center;
  gap: clamp(16px, 2.2vw, 32px);
}
.site-nav a {
  font-family: var(--font-body);
  font-size: var(--type-label);
  letter-spacing: var(--ls-label);
  color: var(--ink);
  padding-bottom: 3px;
  border-bottom: 1px solid transparent;
}
.site-nav a:hover { color: var(--ink); border-bottom-color: var(--rule-strong); }
.site-nav a[aria-current="page"] { color: var(--ink); border-bottom-color: var(--ink); }
.site-nav__book { color: var(--ink); }

/* Free resources carries three topic pages under it. On the bar that is a
   submenu on hover or focus; below 820px it is an indented stack, always
   open, because the bar is already a disclosure there. CSS only: no modal,
   no focus trap, no JS. Paper ground and a hairline, no shadow, no radius. */
.site-nav__group { position: relative; }
/* .site-nav ul already sets a horizontal flex row, so the submenu has to
   out-specify it rather than sit next to it. */
.site-nav .site-nav__sub {
  position: absolute;
  top: 100%;
  /* Anchored to the group's RIGHT edge, not its left. The panel is 333px of
     max-content and it is laid out even while hidden, because it is only
     visibility: hidden, so a left anchor pushed it past the viewport and
     every page on the site scrolled sideways by 7px at around 1500px wide.
     The nav sits at the right of the bar, so right is also where the panel
     belongs. Found and fixed 2026-09-03 while checking widths for the h1
     overflow; it was not caused by that work. */
  right: -18px;
  z-index: 1;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  min-width: max-content;
  margin-top: 12px;
  padding: 18px;
  background: var(--paper);
  border: 1px solid var(--rule);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0.3s ease;
}
.site-nav__group:hover .site-nav__sub,
.site-nav__group:focus-within .site-nav__sub {
  opacity: 1;
  visibility: visible;
}

@media (max-width: 820px) {
  .site-nav .site-nav__sub {
    position: static;
    right: auto;
    opacity: 1;
    visibility: visible;
    min-width: 0;
    margin: 12px 0 0;
    padding: 0 0 0 18px;
    border: 0;
    border-left: 1px solid var(--rule);
  }
  /* the stacked bar makes every li a row; the group holds a column */
  .site-nav__group { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .site-nav .site-nav__sub { transition: none; }
}

.nav-toggle {
  display: none;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: var(--type-label);
  letter-spacing: var(--ls-label);
  color: var(--ink);
}
.nav-toggle:hover { text-decoration: underline; text-underline-offset: 4px; }

/* ---------- The flyout, below 820px ----------
   The stacked panel this replaces sat inside the header bar and pushed the
   page down as it opened, so a list of eight links plus three children ran
   off the bottom of a phone with the page still showing under it. It is a
   full flyout now: fixed to the viewport, paper ground, and the page behind
   it locked and inert.

   The type, the spacing and the indented submenu are exactly what they were.
   Only the box changed.

   It sits UNDER the bar, not over it: the header carries z-index 70 and is
   therefore a stacking context, and inside it the wordmark and the toggle
   are lifted above the panel so the toggle stays on top of its own flyout
   and can close it. That is why the panel's top padding is --nav-bar-h.

   Fade only, no slide: the site's motion rule is fades, and a panel that
   fades in from nothing needs no direction.

   This is a modal in the way the old note said it would never be, and it has
   to be. A panel that covers the page while the page is still tabbable puts
   focus somewhere the reader cannot see. js/nav.js marks <main> and the
   footer `inert` while it is open, which takes them out of the tab order and
   out of the accessibility tree in one attribute, and locks the scroll.
   Esc closes it, as before. Asked for 2026-09-09. */
@media (max-width: 820px) {
  /* ** This is the flyout's only way out, so it has to read as a control and
     be hittable with a thumb. ** It was neither: 36 x 17 pixels of 13px type
     in the top corner, looking exactly like the nav labels beside it, and the
     underline that marks it as a control only existed on :hover, which a
     phone does not have. The client's report was that the flyout had no close
     button, and in every sense that mattered it did not.

     The padding makes a 45px target, over the 44 a finger needs, and it grows
     LEFT into empty bar rather than right, so the label stays flush with the
     container edge and lines up with the wordmark opposite. The negative
     block margin gives the height back to the layout, so the bar is the same
     height it was and --nav-bar-h still describes it.

     The underline is unconditional here, not on hover, and it is on Menu as
     well as Close: both states are the same control and neither could show
     it. Fixed 2026-09-09. */
  .nav-toggle {
    display: block;
    position: relative;
    z-index: 1;
    padding: 14px 0 14px 24px;
    margin-block: -14px;
    text-decoration: underline;
    text-underline-offset: 4px;
    text-decoration-thickness: 1px;
  }
  .site-header__bar > .wordmark { position: relative; z-index: 1; }

  .site-nav {
    position: fixed;
    inset: 0;
    z-index: 0;
    display: block;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: calc(var(--nav-bar-h) + 18px) var(--margin) var(--space-4);
    background: var(--paper);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s ease, visibility 0.3s ease;
  }
  .site-nav.is-open { opacity: 1; visibility: visible; }
  .site-nav ul {
    flex-direction: column;
    align-items: flex-start;
    gap: 14px;
  }
  /* the bar is transparent over the hero, so the open panel needs a ground
     under the bar too, and the hairline that separates the two */
  .site-header.is-nav-open { background: var(--paper); border-bottom-color: rgba(0, 0, 0, 0.12); }
  /* the page behind the flyout does not scroll */
  .is-nav-locked, .is-nav-locked body { overflow: hidden; }
}
@media (prefers-reduced-motion: reduce) {
  .site-nav { transition: none; }
}

/* ============================================================
   Hero
   ============================================================ */

.hero { padding-block: var(--space-3) var(--space-4); }
.hero .grid {
  min-height: clamp(520px, 78vh, 860px);
  align-items: center;
}
.hero__text { display: flex; flex-direction: column; gap: var(--space-4); }
.hero__kicker {
  font-size: var(--type-label);
  letter-spacing: var(--ls-label);
  color: var(--ink-label);
}
/* A long word cannot be made to fit by wrapping, so without this it runs
   straight out of the column: "Reprogramming" at 92px is 683px wide and the
   c-6 column is 586px, and the tail of it sat under the hero photograph.
   Hyphenation is the net, not the plan. The plan is .hero--long below, and
   this is what catches the title nobody sized for, including anything Gaye
   types into the h1 in the editor. Never remove it. */
.hero h1 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--type-h1);
  line-height: 1.08;
  letter-spacing: var(--ls-tight);
  max-width: 12ch;
  text-wrap: balance;
  overflow-wrap: break-word;
  hyphens: auto;
}

/* A hero whose title carries a word too long for the column at the full
   scale. It is --type-h1 with a lower CEILING and nothing else changed:
   the ramp below the cap already tracks the column, and only the top of
   the range was wrong. 92px puts "Reprogramming" at 683px in a 586px
   column; 74px puts it at 549px, which fits with room rather than to the
   pixel. Reach for this when a hero title carries a word past about twelve
   characters; the hyphenation above is what happens if nobody does.

   ** Do not size this from --content-w. ** It shipped that way on
   2026-09-03 and was wrong: --content-w is min(100% - 2 * margin, 1200px),
   and a percentage inside a font-size calc resolves against the PARENT'S
   FONT SIZE, not against a width. The calc came out negative, the clamp
   fell to its 38px floor, and the h1 was 38px at every width from 360 to
   1920. It passed an overflow sweep for exactly the wrong reason: nothing
   clipped because the type had collapsed. Corrected 2026-09-04.
   Added 2026-09-03 for /hypnotherapy-and-subconscious-reprogramming. */
.hero--long h1 {
  font-size: clamp(42px, 5.4vw, 74px);
  max-width: none;
}
.hero__lede {
  margin-top: var(--space-2);
  max-width: 34ch;
  font-size: var(--type-lede);
  line-height: 1.5;
  color: var(--ink-body);
  text-wrap: pretty;
}
.hero__actions {
  margin-top: var(--space-3);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 26px;
}
.hero__media { display: flex; gap: var(--gutter); align-self: stretch; min-height: clamp(400px, 62vh, 720px); }
.hero__frame { position: relative; flex: 1 1 0; min-width: 0; }
.hero__frame--narrow { flex: 0 1 34%; }

@media (max-width: 820px) {
  .hero .grid { min-height: 0; }
  .hero__media { min-height: clamp(340px, 52vh, 520px); }
}

/* ============================================================
   Full-bleed interruptions (rhythm)
   ============================================================ */

.band { width: 100%; }

/* a) image band — full viewport width. No caption, no overlay, no filter:
   nothing sits on it, so the photograph runs at its own brightness. */
.band--image {
  position: relative;
  height: clamp(58vh, 70vh, 80vh);
  background: var(--slot-bg);
  overflow: hidden;
}

/* b) statement band — a full-bleed photograph, then the statement on paper
   directly beneath it. The band used to be one layer: photograph, a flat
   0.55 black scrim, white type on top. The scrim was the only thing making
   that type legible, and it was also the thing making every band image read
   two stops darker than the same photograph anywhere else on the site.

   There is no image in the set that is naturally dark where a statement
   sits: these are pale linen, plaster and daylight still lifes, so white
   type over any of them without a scrim is unreadable. So the type comes
   off the photograph instead of the scrim coming off the type. The
   photograph runs at full brightness with nothing over it, and the
   statement is set in black on white below, where it needs no help.

   Consequence for separators: the block starts on a photograph (so it never
   takes a hairline above) and ends on paper (so the block after it does).
   apply_section_rules.py encodes exactly that. */
.band--statement { background: var(--paper); }
/* Band images are authored at 2:1, so the band holds 2:1 and shows the crop
   that was chosen, capped so it never takes more than two thirds of a tall
   viewport. The old band was a padded block whose height came from the type
   sitting inside it, which cropped the photograph hard at the centre. */
.band__bg {
  position: relative;
  width: 100%;
  aspect-ratio: 2 / 1;
  max-height: 66vh;
  background: var(--slot-bg);
  overflow: hidden;
}
.band__bg img { width: 100%; height: 100%; object-fit: cover; }
.band--statement > .container { padding-block: var(--space-4); }
.band__line {
  grid-column: 1 / 13;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--type-display);
  line-height: 1.14;
  letter-spacing: var(--ls-tight);
  color: var(--ink);
  /* No measure. 52ch is a BODY measure and this is a statement set across a
     full-bleed section: the band exists to run the width of the page, and a
     body cap on it is the same mistake as capping a heading. It bound below
     about 1330px of viewport and left the line at 79% of the band. */
  text-wrap: pretty;
}
@media (max-width: 820px) { .band__line { grid-column: 1 / -1; } }

/* ============================================================
   In her words — statement / elaboration / attribution
   ============================================================ */

.words__fig { margin: 0; }
.words__quote { margin: 0; }
.words__statement {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--type-display);
  line-height: 1.02;
  letter-spacing: var(--ls-tight);
  color: var(--ink);
  /* No 20ch cap and no `balance`: this is display type, and both of them
     narrow the line without anything sitting beside it. Same rule as .h2
     above. It set the 404 heading at 69% of its column. */
  text-wrap: pretty;
}
.words__elab {
  margin-top: var(--space-3);
  max-width: var(--measure);
  font-size: var(--type-lede);
  line-height: 1.5;
  color: var(--ink-body);
  text-wrap: pretty;
}
.words__attrib {
  margin-top: var(--space-2);
  font-size: var(--type-label);
  letter-spacing: var(--ls-label);
  color: var(--ink-label);
}

/* ============================================================
   Ways in
   ============================================================ */

.ways__head { margin-bottom: var(--space-3); }
.ways__figure { display: flex; flex-direction: column; gap: var(--space-3); }
.ways__image { position: relative; width: 100%; height: clamp(320px, 52vh, 560px); }
.ways__copy { min-height: clamp(120px, 14vh, 150px); }
.ways__copy p {
  max-width: var(--measure);
  font-size: var(--type-body);
  line-height: var(--lh-body);
  color: var(--ink-body);
  text-wrap: pretty;
}
.ways__copy p[hidden] { display: none; }

.ways__list { display: flex; flex-direction: column; }
.way {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  text-align: left;
  width: 100%;
  background: none;
  border: 0;
  border-top: 1px solid var(--rule);
  padding: var(--space-2) 0;
  cursor: pointer;
  color: inherit;
  font-family: inherit;
}
.ways__list .way:last-of-type { border-bottom: 1px solid var(--rule); }
.way__dot {
  flex: 0 0 10px;
  height: 10px;
  margin-top: 9px;
  border: 1px solid var(--rule-strong);
  background: transparent;
  transition: background 0.3s ease;
}
.way[aria-pressed="true"] .way__dot { background: var(--ink); }
.way__title {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(20px, 1.9vw, 27px);
  line-height: 1.2;
  color: var(--ink);
}
.way__note {
  display: block;
  padding-top: 5px;
  font-size: 14px;
  line-height: 1.55;
  color: var(--ink-label);
}
.ways__cta { padding-top: var(--space-3); }

/* ============================================================
   Assessment
   ============================================================ */

.assess__head { margin-bottom: var(--space-3); }

.assess__scalehead {
  display: flex;
  align-items: baseline;
  gap: clamp(12px, 3vw, 48px);
  padding-bottom: 8px;
}
.assess__scalehead .spacer { flex: 1 1 auto; }
.assess__scalehead .ends {
  flex: 0 0 174px;
  display: flex;
  justify-content: space-between;
  font-size: var(--type-label);
  letter-spacing: var(--ls-label);
  color: var(--ink-label);
}

.assess__list { border-bottom: 1px solid var(--rule); }
.statement {
  border-top: 1px solid var(--rule);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px clamp(12px, 3vw, 48px);
  padding: clamp(14px, 1.6vw, 22px) 0;
}
.statement__text {
  flex: 1 1 300px;
  font-size: clamp(15px, 1.25vw, 17.5px);
  line-height: 1.5;
  color: var(--ink-body);
  text-wrap: pretty;
}
.statement__cells { flex: 0 0 174px; display: flex; gap: 4px; margin: -8px -6px; }
.cell {
  width: 34px;
  height: 44px;
  padding: 11px 6px;
  background: none;
  border: 0;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.cell__mark {
  display: block;
  width: 22px;
  height: 22px;
  border: 1px solid var(--rule-strong);
  background: transparent;
  transition: background 0.2s ease, border-color 0.2s ease;
}
.cell:hover .cell__mark { border-color: var(--ink); }
.cell[aria-checked="true"] .cell__mark { background: var(--ink); border-color: var(--ink); }

.assess__foot {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  justify-content: space-between;
  align-items: baseline;
  padding-top: 16px;
  font-size: var(--type-label);
  letter-spacing: var(--ls-label);
  color: var(--ink-label);
}
.assess__reset {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: inherit;
  font-family: inherit;
  font-size: var(--type-label);
  letter-spacing: var(--ls-label);
  text-decoration: underline;
  text-underline-offset: 4px;
}
.assess__reset[hidden] { display: none; }

.result {
  margin-top: var(--space-3);
  background: var(--surface);
  color: var(--surface-fg);
  padding: clamp(28px, 4vw, 64px);
  display: flex;
  flex-wrap: wrap;
  gap: clamp(24px, 5vw, 80px);
  align-items: flex-start;
}
.result[hidden] { display: none; }
.result__score { flex: 0 1 260px; }
.result__num {
  font-family: var(--font-display);
  font-size: clamp(52px, 7.5vw, 120px);
  line-height: 0.86;
  letter-spacing: -0.03em;
}
.result__outof { font-size: var(--type-label); letter-spacing: 0.05em; color: var(--surface-fg-quiet); padding-top: 12px; }
.result__body { flex: 1 1 380px; max-width: 58ch; }
.result__band {
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(24px, 3vw, 44px);
  line-height: 1.1;
  letter-spacing: var(--ls-tight);
}
.result__reading {
  margin: var(--space-2) 0 0;
  font-size: var(--type-body);
  line-height: 1.75;
  color: var(--surface-fg-soft);
  text-wrap: pretty;
}
.result__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 24px;
  margin-top: var(--space-3);
}
.result__share {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  /* inside .result, which is painted --surface, so this follows the surface */
  color: var(--surface-fg);
  font-family: inherit;
  font-size: var(--type-label);
  letter-spacing: var(--ls-label);
  text-decoration: underline;
  text-underline-offset: 4px;
}
.result__share:hover { color: var(--surface-fg-soft); }

/* ============================================================
   About
   ============================================================ */

.about__portrait { position: relative; min-height: clamp(420px, 72vh, 760px); }
.about__body p {
  max-width: var(--measure);
  font-size: var(--type-body);
  line-height: var(--lh-body);
  color: var(--ink-body);
  text-wrap: pretty;
}
.about__body .h2 + p { margin-top: var(--space-2); }
.about__body p + p { margin-top: var(--space-2); }
.facts { margin-top: var(--space-3); border-top: 1px solid var(--rule); }
.facts > div {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 32px;
  justify-content: space-between;
  padding: 14px 0;
  border-bottom: 1px solid var(--rule);
}
.facts dt { font-size: var(--type-label); letter-spacing: var(--ls-label); color: var(--ink-label); }
.facts dd { margin: 0; font-family: var(--font-display); font-size: 18px; color: var(--ink); }
.facts dd.right { text-align: right; }

/* ============================================================
   For firms
   ============================================================ */

.firms__body .h2 + p { margin-top: var(--space-2); }
.firms__body p { max-width: var(--measure); color: var(--ink-body); }
.firms__body .btn { margin-top: var(--space-3); }

/* ============================================================
   Book
   ============================================================ */

.book__text { align-self: center; }

/* The last picture on a page, at 60% of its column.
   Asked for 2026-09-04 for the booking block at the foot of / and
   /how-it-works: a full-column 4:5 photograph or portrait loop was reading
   larger than a closing block wants. Width only, so the ratio is untouched
   and nothing is cropped, which is the same position .copy-middle takes.

   Pushed to the column's END, so the block still finishes on the last grid
   line rather than floating in the middle of its column: the grid rule is
   that edges land on grid lines, and a centred 60% box lands on neither.

   Only in the two-column layout. Below 820px the grid is already one column
   and the picture is the full width of the page, where 60% of it would read
   as a thumbnail rather than as a restrained close. */
@media (min-width: 821px) {
  /* :first-child, not *, to win on SPECIFICITY rather than on source order.
     .film sets `margin: 0` and sits 400 lines further down the stylesheet,
     so at equal specificity it silently took the auto margin back off and
     the picture stayed at the column's left edge. That is the same bug the
     note at .film__frame describes; the fix there was the same. */
  .media-60 > :first-child { max-width: 60%; margin-inline-start: auto; }
}
.book__text .h2 + p { margin-top: var(--space-2); }
.book__text p { max-width: 40ch; color: var(--ink-body); }
.book__text p + p { margin-top: var(--space-2); }

/* ============================================================
   Footer
   ============================================================ */

.site-footer { border-top: 1px solid var(--rule); padding-block: var(--space-4) var(--space-3); }
.footer__cols {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(28px, 3vw, 48px) clamp(28px, 4vw, 72px);
}
.footer__label {
  margin: 0 0 12px;
  font-family: var(--font-body);
  font-size: var(--type-label);
  letter-spacing: var(--ls-label);
  color: var(--ink-label);
}
.footer__col ul {
  list-style: none;
  margin: 0 0 clamp(20px, 2.5vw, 32px);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.footer__col ul:last-child { margin-bottom: 0; }
.footer__col a { font-size: 15px; color: var(--ink-body); }
.footer__col a:hover { color: var(--ink); }
.footer__col a[aria-current="page"] {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 5px;
  text-decoration-thickness: 1px;
}
.footer__contact { margin: 0 0 14px; font-size: 15px; line-height: 1.6; color: var(--ink-body); }
.footer__socials { display: flex; gap: 16px; font-size: 15px; }
.footer__bottom {
  margin-top: clamp(36px, 4.5vw, 64px);
  padding-top: clamp(20px, 2.5vw, 30px);
  border-top: 1px solid var(--rule);
  display: flex;
  flex-wrap: wrap;
  gap: 10px 40px;
  justify-content: space-between;
  /* flex-start, not baseline: the legal block is four stacked lines now, and
     baseline would hang the standing sentence off the first of them. */
  align-items: flex-start;
}
/* Company name, contact, VAT, then the legal pages. Four lines rather than
   one middot-separated run, because a registration number and a company
   name read as records, not as a sentence. */
.footer__legalblock { display: flex; flex-direction: column; gap: 4px; }
.footer__legal, .footer__standing {
  margin: 0;
  font-size: var(--type-caption);
  line-height: 1.6;
  letter-spacing: 0.01em;
  color: var(--ink-label);
}
.footer__legal a:hover { color: var(--ink); }
.footer__standing { max-width: 62ch; }

@media (max-width: 820px) { .footer__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 480px) { .footer__cols { grid-template-columns: 1fr; } }

/* ============================================================
   Service pages (shared) — /burnout-recovery and the four that copy it.
   Built from the same tokens as the homepage; no page-specific values.
   ============================================================ */

/* breadcrumb (small, in the hero) */
.breadcrumb {
  font-size: var(--type-label);
  letter-spacing: var(--ls-label);
  color: var(--ink-label);
}
.breadcrumb a:hover { color: var(--ink); }
.breadcrumb [aria-current] { color: var(--ink); }
.breadcrumb .sep { padding: 0 8px; color: var(--rule-strong); }

/* "Does this sound familiar" — the recognitions as a clean, aligned grid.
   No stagger anywhere: consistent columns, shared top alignment, left edges on
   grid lines, hairline-separated, read top-left to bottom-right. Fades on scroll. */
.week {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: var(--gutter);
}
.week li {
  padding: var(--space-3) 0;
  border-top: 1px solid var(--rule);
  font-size: clamp(16px, 1.4vw, 19px);
  line-height: 1.45;
  color: var(--ink-body);
  text-wrap: pretty;
}
@media (max-width: 820px) { .week { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) { .week { grid-template-columns: 1fr; } }

/* ---- THE METHOD (/hypnotherapy): expository left-heading / right-body rhythm.
   Narrow heading column, wide body — a different heading↔body relationship and
   column proportions from the work pages. The main "what happens" heading is
   sticky (its signature). Pure CSS; stacks on mobile; JS-off safe. ---- */
.hyp-row__head { grid-column: 1 / 5; }
.hyp-row__body { grid-column: 5 / 13; }
.hyp-row__body > * + * { margin-top: var(--space-3); }
@media (min-width: 821px) {
  .hyp-row--sticky .hyp-row__head { position: sticky; top: clamp(88px, 13vh, 128px); align-self: start; }
}
@media (max-width: 820px) {
  .hyp-row__head, .hyp-row__body { grid-column: 1 / -1; }
  .hyp-row--sticky .hyp-row__head { position: static; margin-bottom: var(--space-3); }
}
.hyp-list { list-style: none; margin: 0; padding: 0; }
.hyp-list li {
  padding: var(--space-3) 0;
  border-top: 1px solid var(--rule);
  font-size: clamp(17px, 1.5vw, 21px);
  line-height: 1.4;
  color: var(--ink-body);
  text-wrap: pretty;
  max-width: var(--measure);
}
.hyp-step { max-width: var(--measure); }
.hyp-step__label { margin: 0 0 var(--space-1); font-size: var(--type-label); letter-spacing: var(--ls-label); color: var(--ink-label); }

/* ---- THE PRACTICE (/results): quiet, static testimonial grid — no marquee ---- */
.testimonials {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--gutter);
}
.testimonials li { padding: var(--space-3) 0; border-top: 1px solid var(--rule); }
.testimonials blockquote { margin: 0; font-size: clamp(18px, 1.6vw, 22px); line-height: 1.4; color: var(--ink); text-wrap: pretty; }
.testimonials cite { display: block; margin-top: 14px; font-style: normal; font-size: var(--type-label); letter-spacing: var(--ls-label); color: var(--ink-label); }
@media (max-width: 640px) { .testimonials { grid-template-columns: 1fr; } }

/* two-sided "who this is for / who it isn't" */
.split__col .body { margin-top: var(--space-2); }
.split__col .body + .body { margin-top: var(--space-2); }

/* FAQ — accessible toggle accordion. Authored open, so answers are in the
   DOM (and visible without JS); faq.js collapses all but the first. The
   answer text is unchanged, so the FAQPage JSON-LD still matches exactly. */
.faq { max-width: 74ch; }
.faq__item { border-top: 1px solid rgba(0, 0, 0, 0.12); }
.faq__item:last-child { border-bottom: 1px solid rgba(0, 0, 0, 0.12); }
.faq__q { margin: 0; }
.faq__trigger {
  width: 100%;
  background: none;
  border: 0;
  cursor: pointer;
  font-family: inherit;
  color: var(--ink);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
  padding: var(--space-3) 0;
  text-align: left;
}
.faq__label {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: clamp(18px, 1.5vw, 21px);
  line-height: 1.35;
}
.faq__icon { position: relative; flex: 0 0 16px; height: 16px; margin-top: 5px; }
.faq__icon::before, .faq__icon::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  width: 16px;
  height: 1px;
  background: var(--ink);
}
.faq__icon::after { transform: rotate(90deg); transition: opacity 0.3s ease; }
.faq__trigger[aria-expanded="true"] .faq__icon::after { opacity: 0; } /* plus -> minus */

.faq__panel {
  display: grid;
  grid-template-rows: 1fr;               /* authored open: no-JS + crawlers see every answer */
  transition: grid-template-rows 0.35s ease;
}
/* Collapse only once JS is confirmed (html.js set by the inline head script,
   before first paint) — so a slow load never flashes the answers open. */
.js .faq__panel { grid-template-rows: 0fr; }
.js .faq__item:has(.faq__trigger[aria-expanded="true"]) .faq__panel { grid-template-rows: 1fr; }
.faq__inner { overflow: hidden; }
.faq__a {
  margin: 0;
  padding-bottom: var(--space-3);
  max-width: var(--measure);
  font-size: var(--type-body);
  line-height: var(--lh-body);
  color: var(--ink-body);
  text-wrap: pretty;
}
@media (prefers-reduced-motion: reduce) {
  .faq__panel { transition: none; }
}

/* Related services — shared cross-link block at the foot of every service page */
.related__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: clamp(16px, 2vw, 28px) clamp(32px, 5vw, 72px);
}
.related__list a {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(22px, 2.4vw, 34px);
  line-height: 1.1;
  letter-spacing: var(--ls-tight);
  color: var(--ink);
}
.related__list a:hover { color: var(--ink-label); }

/* Long-form prose — legal pages and insights articles */
.prose { max-width: 68ch; }
.prose > * + * { margin-top: var(--space-2); }
.prose h2 {
  margin-top: var(--space-3);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(22px, 2.2vw, 30px);
  line-height: 1.15;
  letter-spacing: var(--ls-tight);
  color: var(--ink);
}
.prose h3 {
  margin-top: var(--space-3);
  font-family: var(--font-body);
  font-weight: 500;
  font-size: clamp(17px, 1.4vw, 20px);
  color: var(--ink);
}
.prose p, .prose li {
  font-size: var(--type-body);
  line-height: var(--lh-body);
  color: var(--ink-body);
  text-wrap: pretty;
}
.prose ul, .prose ol { padding-left: 1.2em; }
.prose li + li { margin-top: 6px; }
.prose li::marker { color: var(--ink-label); }
.prose a { text-decoration: underline; text-underline-offset: 3px; }
.prose a:hover { color: var(--ink-label); }
.prose .lede { max-width: none; margin-bottom: var(--space-3); }

/* Insights index — article list */
.article-list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--rule); }
.article-list li { border-bottom: 1px solid var(--rule); }
.article-list a { display: block; padding: var(--space-3) 0; color: var(--ink); }
.article-list a:hover .article-list__title { color: var(--ink-label); }
.article-list__meta { font-size: var(--type-label); letter-spacing: var(--ls-label); color: var(--ink-label); }
.article-list__title {
  margin: 10px 0 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(24px, 2.8vw, 40px);
  line-height: 1.1;
  letter-spacing: var(--ls-tight);
}
.article-list__excerpt { margin: 10px 0 0; max-width: 60ch; color: var(--ink-body); }

/* [GAYE] markers — genuine unknowns, left visible as clear placeholders */
/* .gaye / .gaye--block removed 2026-08-21 with the last visible
   [GAYE] note. Every one of them is an HTML comment in the page now
   and BUILD-LOG.md lists what is still owed, so nothing on the wire
   is styling copy that no longer exists. Restore from git if a
   visible note is ever wanted again. */

/* deck short lists — hairline-separated, no bullets, aligned */
.deck-list { list-style: none; margin: var(--space-2) 0 0; padding: 0; max-width: var(--measure); }
.deck-list li {
  padding: var(--space-2) 0;
  border-top: 1px solid var(--rule);
  font-size: var(--type-body);
  line-height: var(--lh-body);
  color: var(--ink-body);
  text-wrap: pretty;
}
.deck-list li:last-child { border-bottom: 1px solid var(--rule); }
.deck-list--linked li { color: var(--ink-body); }
.deck-list--linked a { text-decoration: none; color: var(--ink); }
.deck-list--linked a strong { font-weight: 600; }
.deck-list--linked a:hover strong,
.deck-list--linked a:focus-visible strong { text-decoration: underline; text-underline-offset: 3px; }

/* ============================================================
   V1 launch surface — the chrome carries four pages, so the footer is
   two columns rather than four. Remove when the full nav returns in V2.
   ============================================================ */

.footer__cols--v1 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 820px) { .footer__cols--v1 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 480px) { .footer__cols--v1 { grid-template-columns: 1fr; } }

/* ============================================================
   Cards — the photograph, then the words under it
   No border, no radius, no shadow, no badge, and now no scrim. The card
   used to set its title and note in white over a flat 0.62 black laid on
   the photograph, which is the single darkest thing the site did to an
   image: the same still life ran two stops down inside a card and at its
   own exposure everywhere else.

   So the type comes off the photograph. The image holds the 4:5 tile at
   its own brightness with nothing over it, and the title and note sit on
   paper directly beneath in black, where they need no scrim to be read.
   The slow hover scale stays, because it belongs to the image, not to the
   overlay. Still one .cards component with no variations, still one image
   per topic reused wherever that topic appears.
   ============================================================ */

.cards {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-3) var(--gutter);
}
.card { position: relative; background: none; }
.card__media {
  position: relative;
  height: auto;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--slot-bg);
}
.card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1);
  transition: transform 1.8s cubic-bezier(0.22, 0.61, 0.24, 1);
}
.card__body { padding-top: var(--space-2); }
/* Hanken, not Bodoni: the section keeps one display element, its h2. */
.card__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: clamp(19px, 1.7vw, 23px);
  line-height: 1.2;
  letter-spacing: 0.01em;
  color: var(--ink);
}
.card__title a { color: var(--ink); }
.card__title a:hover { color: var(--ink-label); }
/* the whole card is the target; the button sits above it in edit mode */
.card__title a::after { content: ""; position: absolute; inset: 0; z-index: 3; }
/* No measure. A card column is never wider than the measure in the first
   place: three across a 1200px container is 378px, and 54ch of 16px copy is
   483px, so --measure could not bind here even if it were the value. 32ch
   is 287px, which set the note at 85% of the card on a phone and 75% at
   three across, and that is the "body under a card at half width" the
   client flagged. The card IS the measure. Cap removed 2026-09-09. */
.card__note {
  margin: 10px 0 0;
  font-size: 16px;
  line-height: 1.55;
  color: var(--ink-body);
  text-wrap: pretty;
}
.card:hover .card__media img { transform: scale(1.05); }

@media (max-width: 900px) { .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { .cards { grid-template-columns: 1fr; } }
@media (prefers-reduced-motion: reduce) {
  .card__media img { transition: none; }
  .card:hover .card__media img { transform: none; }
}

/* ============================================================
   Film band — a muted loop that starts when it scrolls into view
   The poster still is what ships in the HTML; film.js attaches the source
   and plays only once the band is near the viewport, so nothing downloads
   for a visitor who never reaches it. No controls, no button, no caption.
   Under prefers-reduced-motion the still is all there is.
   ============================================================ */

.filmband { background: var(--band-bg); color: var(--band-fg); padding-block: var(--space-4); }
.filmband .h2, .filmband .eyebrow, .filmband p { color: var(--band-fg); }
.filmband .eyebrow { color: var(--surface-fg-quiet); }
.filmband__copy { align-self: center; }
.filmband__copy p { max-width: var(--measure); color: var(--surface-fg-soft); }
.filmband__copy .h2 + p { margin-top: var(--space-2); }

.film { margin: 0; position: relative; }
/* The source is a 9:16 phone recording. Held at 9:16 it runs taller than
   the viewport in a five-column slot, so the frame crops to 4:5 and both
   the poster and the video cover it. The speaker sits mid-frame, so a
   centre crop keeps her. */
/* A loop beside the h1 keeps the clip's own 9:16 rather than being stretched
   to the hero's full height, which cropped so hard it cut the subject out. */
.hero__frame--film { align-self: center; }
/* The clip keeps its own shape and its own pixels; what is capped is how
   tall the hero is allowed to get. A 9:16 loop across a c-r6 column comes
   out 1042px on a 951px viewport, which pushes the h1 and the button off
   the screen. width: fit-content with a definite max-height lets
   aspect-ratio size the width from the height instead of the other way
   round, so the frame shrinks whole rather than being trimmed. Nothing is
   cropped and nothing is letterboxed: there is no object-fit involved,
   the box itself is smaller. */
.hero__frame--film .film__frame {
  --film-ar: 9 / 16;
  height: auto;
  /* --film-ar is a ratio, so it is also a valid division inside calc: this
     resolves to 78vh * 1080 / 1920 for the skin loop. Capping the WIDTH is
     what caps the height, because aspect-ratio derives the height from it.
     Capping the height directly does not work here: the frame's children
     are absolutely positioned, so there is no in-flow content for
     width: fit-content to measure and the box collapses to zero. */
  max-width: calc(78vh * var(--film-ar, 9 / 16));
  /* Left edge of its column, not centred. A centred box lands on no grid
     line at all, which is the same objection the note at .media-60 makes
     about a centred 60% picture. Asked for 2026-09-09 for /holistic-beauty's
     hero loop; /work-with-me's hero film is the same component and moves
     with it. */
  margin-inline: 0 auto;
}
.film__frame {
  position: relative;
  aspect-ratio: var(--film-ar, 4 / 5);
  /* A video ground, not a design surface, so it stays black rather than
     taking --surface: this is what sits behind the frame for the moment
     before the poster paints, and black is what a video letterboxes onto
     everywhere else. Same reasoning as .player below. */
  background: #000;
  overflow: hidden;
}
/* The ratio is carried in a custom property the base rule only reads as a
   fallback, rather than being set with aspect-ratio directly in each
   modifier. Two reasons.

   A video slot is swappable through the editor now, and every clip has its
   own shape. save.mjs writes the uploaded clip's ratio onto the frame as an
   inline --film-ar, which wins over whatever modifier class the frame was
   built with, so a replacement is never letterboxed or cropped into.

   It also settles a bug that was live for weeks. The modifiers and the base
   rule all match on a single class, so specificity ties and source order
   alone decided which aspect-ratio applied: a modifier written above the
   base lost silently and the frame fell back to 4:5. A variable the base
   only reads cannot tie with the modifiers that set it, wherever they sit. */
.film__frame--tall { --film-ar: 1200 / 1698; }
/* The gathering loop is a near-square screen recording. Held at its own ratio
   the frame crops nothing, which matters here: the shot is worth showing
   because of how many people are in it, and a 4:5 centre crop takes the women
   at both edges out. */
.film__frame--room { --film-ar: 694 / 720; }
.film__frame img,
.film__frame video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.film__frame video { opacity: 0; transition: opacity 0.5s ease; }
.film__frame.is-playing video { opacity: 1; }
.film__frame.is-playing img { opacity: 0; transition: opacity 0.5s ease; }
@media (prefers-reduced-motion: reduce) {
  /* the still is the whole component; film.js never starts the video */
  .film__frame video, .film__frame img { transition: none; }
  .film__frame video { display: none; }
}

/* ============================================================
   Client love — one wall of what people wrote

   Every card is the height of what it holds. A two-line "I'm feeling
   lighter today" and a fifteen-line message about a year of change are not
   the same object and are not shown at the same size, and the wall flows
   around that rather than forcing both into a row.

   The screenshots are small, which means a long one is not readable at a
   glance. Every one is therefore a link to its own full-size image:
   js/shots.js opens it in a native <dialog>, and with JS off the link still
   opens the image.
   ============================================================ */

/* The wall — one masonry of mixed cards
   The screenshots used to be split into three grids, one per device frame,
   every tile in a group the same size and every group in rigid rows. That is
   what made the page read as an ad for the devices rather than as a wall of
   what people wrote. There is one wall now and a card is a quote, a
   screenshot or the film loop, at whatever height its content comes to.

   CSS multicol does the masonry: the varying heights are the content's own
   and nothing is measured or positioned in script, so there is no reflow to
   get wrong and no layout that needs JS to exist. Reading order runs down
   each column in turn, which is right here because every card is a peer and
   none of them continues the one before it.

   A card is a hairline on paper. Never a fill, never a radius, never a
   shadow: the only thing separating a card from the page is one rule-weight
   line, the same line every other component on the site uses.

   Nothing is written over a photograph. A card that holds an image holds it
   edge to edge with the type, where there is any, on paper underneath it.

   Each screenshot is still an <a> around an editable image slot for the
   lightbox. That is the shape freeLinks() in js/edit.js makes inert for the
   length of an edit session, and it is why Replace and Adjust crop reach
   these slots. Do not put anything else interactive over them. */

.wall {
  columns: 3;
  column-gap: var(--gutter);
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
}
@media (max-width: 900px) { .wall { columns: 2; } }
@media (max-width: 560px) { .wall { columns: 1; } }

.wall__card {
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  margin: 0 0 var(--gutter);
  border: 1px solid var(--rule);
  background: var(--paper);
}

/* A quote is set in body type, not display. There is one Libre Bodoni
   element in this section and it is the heading above the wall; twenty-one
   display quotes would leave nothing for it to be louder than. */
.wall__card--quote { padding: var(--space-3); }
.wall__quote { margin: 0; }
.wall__quote blockquote {
  margin: 0;
  font-size: var(--type-lede);
  line-height: 1.4;
  color: var(--ink);
  text-wrap: pretty;
}
.wall__by {
  margin-top: var(--space-2);
  font-size: var(--type-label);
  line-height: 1.5;
  letter-spacing: var(--ls-label);
  color: var(--ink-label);
}

/* The image fills its card: the card's own hairline is the frame, so the
   tile does not get a second one. */
.wall__card--shot,
.wall__card--film { padding: 0; }
.wall__card--shot .shot,
.wall__card--film .film { margin: 0; }
.wall__card--shot .shot a { border: 0; background: var(--slot-bg); }
.wall__card--shot .shot__slot { height: auto; }
.wall__card--shot .shot__slot img { width: 100%; height: auto; }
/* the hairline is what answers the pointer, not a border inside the card */
.wall__card--shot:hover,
.wall__card--shot:focus-within { border-color: var(--rule-strong); }

/* the film's caption sits on paper under the frame, never over it */
.wall__card--film .wall__by { margin: var(--space-2); }

/* .shot is the tile itself: the link, the slot and the lightbox. Where it
   sits is the wall's business, above. */
.shot { margin: 0; }
.shot a {
  display: block;
  background: var(--slot-bg);
  border: 1px solid transparent;
  transition: border-color 0.3s ease;
}
.shot a:hover, .shot a:focus-visible { border-color: var(--rule-strong); }
/* the tile is an editable image slot, so it opts out of .img-slot's
   fill-the-parent height and keeps the screenshot's own proportions */
.shot .img-slot { position: relative; height: auto; }
.shot .img-slot img,
.shot img { width: 100%; height: auto; }
.shot__hint {
  margin: var(--space-2) 0 0;
  font-size: var(--type-label);
  letter-spacing: var(--ls-label);
  color: var(--ink-label);
}

/* Full size, on white, because the page is black on white and a dark
   backdrop would be the only place on the site that is not. */
.lightbox {
  border: 0;
  padding: 0;
  background: var(--paper);
  max-width: min(92vw, 820px);
  max-height: 92vh;
  overflow: auto;
  padding: 6px clamp(12px, 2vw, 20px) clamp(12px, 2vw, 20px);
}
.lightbox::backdrop { background: rgba(255, 255, 255, 0.97); }
.lightbox img { display: block; width: 100%; height: auto; }
.lightbox__close {
  display: block;
  margin: 0 0 10px auto;
  background: none;
  border: 0;
  padding: 12px 4px;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: var(--type-label);
  letter-spacing: var(--ls-label);
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 4px;
}

/* ============================================================
   Steps (/how-it-works) — four columns across, not four blocks down
   The client's reference is a horizontal row: a numeral above each column
   with a hairline running out either side of it, and the copy in a hairline
   box beneath. Four steps stacked vertically made the page long, which is
   the problem this solves.

   The numeral row IS the box's top edge. .steps__num is a flex row whose
   ::before and ::after are the two segments of that rule, so they meet the
   box's own left and right borders exactly and the numeral sits in the gap.
   The box therefore carries no border-top of its own.

   The numerals are the section's display element: this block deliberately
   has no heading of its own (the section above it carries that), so "one
   Libre Bodoni element per section" is spent here, on four peers in a row,
   the way .cards spends one style across its titles.
   ============================================================ */

.steps {
  /* one value, so the numeral's size and the amount the box is pulled up to
     meet its midline can never drift apart */
  --steps-num: clamp(28px, 2.8vw, 42px);
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--gutter);
  list-style: none;
  margin: 0;
  padding: 0;
}
.steps__item { display: flex; flex-direction: column; }

/* A quiet aside under the row, not a fifth step. Label size on the label
   grey, held to the measure, its own grid child so the grid's row-gap sets
   the space above and below it and nothing is patched with a margin. It
   carries no eyebrow, no numeral and no rule on purpose: it is a footnote to
   the four boxes, and anything louder would read as another step.
   Added 2026-09-02 at the client's request. */
.steps__note {
  margin: 0;
  max-width: var(--measure);
  font-size: var(--type-label);
  line-height: 1.6;
  color: var(--ink-label);
}

.steps__num {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--steps-num);
  line-height: 1;
  color: var(--ink);
}
/* the two halves of the box's top rule */
.steps__num::before,
.steps__num::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--rule);
}

.steps__box {
  flex: 1 1 auto;
  border: 1px solid var(--rule);
  border-top: 0;          /* the numeral row above is the top edge */
  /* pulled up by half the numeral so the box's top corners meet the two rule
     segments exactly, which is where the reference puts them. The same half
     is added back as padding, so the copy still clears the numeral. */
  margin-top: calc(var(--steps-num) / -2);
  padding: calc(var(--space-3) + var(--steps-num) / 2) clamp(16px, 1.6vw, 24px) var(--space-3);
}
.steps__box .eyebrow { margin-bottom: var(--space-1); }
.steps__box .card__title { margin-bottom: var(--space-2); }
.steps__box .body { max-width: none; }
.steps__box .body + .body { margin-top: var(--space-2); }

@media (max-width: 900px) { .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: var(--space-3); } }
@media (max-width: 560px) { .steps { grid-template-columns: 1fr; } }

/* ============================================================
   Credentials — the certificates, over the page, in a scrollable dialog
   A native <dialog> opened with showModal(), same as .lightbox above, so Esc,
   the backdrop and the focus trap come from the platform rather than from
   script. The backdrop is the same near-opaque paper the lightbox uses: the
   panel reads as floating over the page without dimming the photographs
   behind it, which a translucent black backdrop would do.

   The panel scrolls, and the bar holding Close is sticky inside it, so the
   way out is on screen at every scroll position. Hairline and paper, no
   radius and no shadow, like everything else.

   The whole dialog lives OUTSIDE <main> on purpose. apply_edit_attrs.py only
   tags inside <main>, so nothing in here is editable: a credential is a fact
   read off a document, and CLAUDE.md says never invent one. It is also the
   shape the editor cannot reach anyway, because a closed dialog is
   display:none and a caret cannot land in it.
   ============================================================ */

.creds {
  border: 1px solid var(--rule);
  padding: 0;
  background: var(--paper);
  color: var(--ink-body);
  width: min(92vw, 900px);
  max-width: min(92vw, 900px);
  max-height: 88vh;
  overflow: auto;
  overscroll-behavior: contain;
}
.creds::backdrop { background: rgba(255, 255, 255, 0.97); }

/* sticky, so Close never scrolls away in a long sheet of certificates */
.creds__bar {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  padding: clamp(16px, 2vw, 24px) clamp(18px, 2.4vw, 32px) 12px;
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
}
.creds__bar .eyebrow { margin: 0; }
.creds__close {
  background: none;
  border: 0;
  padding: 4px;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: var(--type-label);
  letter-spacing: var(--ls-label);
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 4px;
}
.creds__body { padding: clamp(20px, 2.6vw, 34px) clamp(18px, 2.4vw, 32px) clamp(24px, 3vw, 40px); }
/* the panel holds the five documents and nothing else: no heading, no
   explanatory line, no list, no captions. By request, 2026-09-02. */
.creds__sheet:first-child { margin-top: 0; }
.creds__sheet li:first-child { padding-top: 0; border-top: 0; }

/* one certificate per row: a document is read, not skimmed in a grid */
.creds__sheet {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
}
.creds__sheet li {
  padding-top: var(--space-3);
  border-top: 1px solid var(--rule);
}
.creds__sheet li + li { margin-top: var(--space-3); }
.creds__sheet img { display: block; width: 100%; height: auto; }

/* ============================================================
   Freebie — one component for every free guide and every signup
   One look, two layouts, and the difference between them is only what the
   reader does next:

     .freebie--link   the aside is a photograph and the action is a button
                      under the copy. This is /free-resources, where each
                      block hands the reader on to the page that holds the
                      form.
     .freebie--form   the form IS the action and sits under the copy, in
                      the copy column, where a button sits in --link. The
                      aside is the photograph. This is every individual page.

   Both run the copy in a c-7 column with the same eyebrow, heading and body
   every other section uses, so a freebie reads as part of the page rather
   than as an ad dropped into it. Below 820px the grid is already one column
   and the aside falls under the copy, which is the right order: she reads
   what it is before she is asked for an address.

   The form moved out of the aside on 2026-09-03. Stacked under the
   photograph it left the copy column ending halfway down the block, with a
   tall empty area beside the picture, which is what the client flagged; and
   it put the ask under the picture rather than under the sentence that
   makes it. Under the copy, the two columns end together and the action
   follows the argument. The aside now holds a photograph and nothing else,
   the same as .freebie--link.
   ============================================================ */

.freebie__cta { margin-top: var(--space-3); }
.freebie__copy { align-self: center; }
.freebie__aside {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}
/* The declared ratio matches the photographs' own width/height attributes,
   which is what the editor's crop frame reads. Do not set a ratio here that
   the markup does not also declare, or a crop drawn in the dialog lands
   somewhere else on the page. */
.freebie__media .img-slot,
.freebie__media.img-slot { position: relative; height: auto; aspect-ratio: 1200 / 1698; }
/* The form sits under the copy and keeps the copy's measure, so the fields
   line up with the paragraph above them rather than running the column. */
.freebie__copy .kitform { margin-top: var(--space-3); }

/* ============================================================
   Kit (ConvertKit) embedded forms
   Kit ships its own type, gold buttons, rounded corners and a "Built with
   Kit" badge. None of that belongs here, and none of it can be changed in
   Kit's builder without a paid plan. So the embed is pasted in untouched
   inside .kitform and restyled from outside: same markup, same field names,
   same action, so the form keeps working exactly as Kit expects. Only the
   appearance changes.

   Kit's own inline styles sit on the elements, which is why these rules
   carry !important. That is the one place on this site they are warranted:
   the alternative is editing vendor markup by hand on every paste.
   ============================================================ */

.kitform { max-width: var(--measure); }
.kitform * { font-family: var(--font-body) !important; }
.kitform .formkit-form,
.kitform form {
  background: transparent !important;
  border: 0 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  padding: 0 !important;
  max-width: none !important;
  margin: 0 !important;
}
.kitform .formkit-background,
.kitform .formkit-header,
.kitform .formkit-subheader,
.kitform .formkit-image { display: none !important; }
.kitform .formkit-field { margin: 0 0 var(--space-2) !important; }
.kitform .formkit-input,
.kitform input[type="text"],
.kitform input[type="email"] {
  width: 100% !important;
  background: var(--paper) !important;
  color: var(--ink) !important;
  border: 1px solid var(--rule-strong) !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  padding: 13px 14px !important;
  font-size: var(--type-body) !important;
  line-height: 1.4 !important;
}
.kitform .formkit-input::placeholder { color: var(--ink-label) !important; }
.kitform .formkit-input:focus,
.kitform input:focus { outline: 1px solid var(--ink) !important; outline-offset: 2px; border-color: var(--ink) !important; }
/* the site button, not Kit's gold pill */
.kitform .formkit-submit,
.kitform button[type="submit"] {
  width: auto !important;
  background: var(--surface) !important;
  border: 0 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  padding: 0 !important;
  transition: background 0.4s ease;
}
.kitform .formkit-submit > span,
.kitform button[type="submit"] > span {
  display: inline-flex !important;
  align-items: center;
  background: transparent !important;
  border-radius: 0 !important;
  padding: 13px 20px !important;
  /* --surface-fg, not --paper: the submit sits on --surface and has to
     follow it when the surface colour changes. It was hard-coded white here
     once, which went to 3.90:1 the moment the surface became #D75A2B. It is
     white again today, but only because the token says so. */
  color: var(--surface-fg) !important;
  font-size: var(--type-label) !important;
  letter-spacing: var(--ls-label) !important;
  font-weight: 400 !important;
}
.kitform .formkit-submit:hover,
.kitform button[type="submit"]:hover { background: var(--btn-hover) !important; }
/* Kit animates the button background on hover with a coloured pseudo layer */
.kitform .formkit-submit span[style*="background"] { background: transparent !important; }
.kitform .formkit-guarantee,
.kitform .formkit-disclaimer {
  margin-top: var(--space-2) !important;
  font-size: var(--type-caption) !important;
  line-height: 1.6 !important;
  color: var(--ink-label) !important;
}
.kitform .formkit-alert {
  border-radius: 0 !important;
  border: 1px solid var(--rule-strong) !important;
  background: var(--paper) !important;
  color: var(--ink) !important;
  font-size: var(--type-label) !important;
}
/* The badge. Kit's free plan requires it, so this only takes effect where the
   licence allows hiding it; on a paid plan Kit stops emitting it anyway. */
.kitform .formkit-powered-by-convertkit,
.kitform .formkit-powered-by-convertkit-container,
.kitform [data-element="powered-by"] { display: none !important; }

/* ============================================================
   Player — a film the visitor watches, not a loop that decorates
   .film__frame is muted, autoplaying, aria-hidden decoration. A tutorial is
   the opposite: it has narration, the visitor starts it, and it needs real
   controls. So it is its own component, with the browser's own controls
   rather than a set drawn here, and preload="none" so a page that is not
   watched costs a poster and nothing else.
   ============================================================ */

.player {
  position: relative;
  /* object-fit: contain below, so this ground IS the letterbox bar when the
     clip's ratio and the frame's disagree. It stays black on purpose and is
     deliberately not --surface: green bars either side of a video read as a
     broken player. */
  background: #000;
  aspect-ratio: var(--player-ar, 9 / 16);
  max-width: 420px;
}
.player video { display: block; width: 100%; height: 100%; object-fit: contain; }

/* ============================================================
   Embed slots — a block that holds pasted markup, not copy
   Outlined in edit mode only (js/edit.js draws the control); to a visitor it
   is whatever the embed renders, with no chrome of its own.
   ============================================================ */

.embed-slot { min-height: 1px; }
.gds-edit .embed-slot:empty,
.gds-edit .kitform:empty { min-height: 90px; }

/* ============================================================
   Wide copy — the fix for a section that sits hard left
   THIS IS THE ONE ANSWER TO THAT SHAPE. Do not invent a second one.

   A section whose only content is a heading and running copy puts that copy
   in a 54ch column, which is about 535px inside a 1200px container. The
   section then occupies the left 40% of the page and reads as a strip with
   two thirds of the page empty beside it. It has now been flagged by the
   client three times, on three different pages, which is why this is a named
   component and why _partials/audit_wide_copy.py exists to find the next one
   before she does.

   The fix releases the measure to the block, runs the block the full
   container width, and sets the copy in two columns, so the section occupies
   the page left to right while every line still reads at 52 to 62
   characters. Put it on the <p> when the section has one paragraph, or on a
   plain <div> wrapping the paragraphs when it has several; the column is
   widened to c-12 in the same edit.

   One column is the DEFAULT and two is the enhancement, the same shape as
   .hiw--live and .ba--live. The threshold is 1100px rather than the 820px
   this shipped with on 2026-08-26: below about 1100px the container is under
   1000px, two columns come out around 45 characters, and a too-narrow column
   is a worse read than a strip. Renamed from .axis__body 2026-09-02, when it
   went from one page's patch to the sitewide component.

   ---- 2026-09-03. The fourth flag, and the rule behind it. ----

   The three earlier fixes each widened one section. The client flagged it
   again anyway, because the shape was never confined to the sections the
   audit could see: EVERY copy class carries its own max-width (.body and
   most lists at --measure, .lede at 42ch, .steps__note at --measure), and
   that cap applies whether or not anything sits beside the block. Where a
   column stands alone in the grid the cap is the only thing setting the
   width, so the block is a strip by default and staying out of the strip
   was the thing an author had to remember.

   The measure exists to stop a LINE running too long. It only has work to
   do where the copy wraps. That gives the two treatments below, and which
   one a block takes is decided by how much copy it holds, not by taste:

     .copy-wide         copy that wraps past one line at the container's
                        own width (about 110 characters of .body, about 85
                        of .lede). Two columns above 1100px, so the block
                        runs the full width and every line still reads at
                        52 to 62 characters.

     .copy-wide--lead   copy short enough to set as one line at that width.
                        The cap comes off and nothing replaces it: a block
                        that does not wrap cannot have an over-long line,
                        so capping it only manufactures the dead space.
                        Two columns would be worse here, since balancing
                        one line across two columns empties the second and
                        rebuilds the strip it is meant to fix.

   A heading needs neither: .h2 carries no measure, so a heading that sits
   hard left is a narrow COLUMN and the fix is to widen the column to c-12.

   audit_wide_copy.py now checks both shapes, and checks that a block has
   not taken the wrong one of the two.
   ============================================================ */

/* One column and the measure intact is the default. The measure is only
   released where two columns take over the job of holding it: without this
   line a 1000px page set the copy at about 100 characters, which is a worse
   read than the strip this component exists to fix. */
.copy-wide,
.copy-wide > * { max-width: var(--measure); }

/* ** The threshold is 900px, not 1100px, and that was the bug. **
   The release of the measure lives in the same query as the two columns, so
   below the threshold this component did NOTHING AT ALL: the copy went
   straight back to 54ch in a full-width column, which is the exact strip it
   exists to fix. Measured on /burnout-recovery, the .body.copy-wide block as
   a fraction of its own column:

       viewport   column   block   fill
       900px       810px   484px    60%
       1024px      922px   484px    52%
       1099px      989px   484px    49%
       1100px      990px   990px   100%

   A one-pixel cliff, and 49% of the page on the wrong side of it. Every
   round of this fix was checked on a wide desktop, where the release is on;
   the client is looking at a laptop under 1100px, where it is off. That is
   why the same complaint kept coming back to a component that reads as
   though it had already answered it. 23 blocks across 20 pages sat like
   this.

   Why 900 and not lower: the column has to hold a readable line. At 1099px
   two columns come out at 53 characters, at 1024px 49, and at 900px 43. The
   note that stood here put that last figure at "around 45" and stopped
   there, which is fair as far as it goes: 43 is under the 52 to 62 band. It
   is still a better read than a block at 60% of the page with nothing beside
   it, and that is the trade the client has now made five times. Below 900px
   the cap stays, and by about 620px the container is narrower than the
   measure and the cap has nothing left to do on its own. */
@media (min-width: 900px) {
  .copy-wide:not(.copy-wide--lead) {
    max-width: none;
    columns: 2;
    column-gap: clamp(40px, 4.2vw, 72px);
    column-fill: balance;
  }
  .copy-wide:not(.copy-wide--lead) > * { max-width: none; }
}

/* Short copy: the cap comes off at every width and no columns replace it.
   Below 900px the container is narrow enough to hold the line on its own,
   and above it the copy sets as one line, which is the whole reason this
   modifier exists rather than the two columns above. */
.copy-wide--lead,
.copy-wide--lead > * { max-width: none; }

/* ============================================================
   Photo set — photographs, presented plainly
   A grid of photographs and nothing else: no captions, no labels, no
   controls, no pairing.

   ** No page uses this at the moment. ** It replaced the drag-to-compare .ba
   on /holistic-beauty on 2026-09-03, and on 2026-09-09 the client asked for
   the slider back, so .ba is live again in css/before-after.css and this is
   what /holistic-beauty is not using. It is six declarations and it is the
   right answer for a grid of photographs that is not a comparison, which is
   a thing this site has now wanted twice in a week. Kept here rather than
   parked in a file of its own: the parking rule exists to keep whole
   components with their own script off every page's stylesheet, and this is
   a grid.

   Same column counts as .ba-set, so the photographs sit where they sat
   either way. The 3/4 ratio is declared here rather than on the image, which
   is what the editor's crop dialog reads.
   ============================================================ */

.photo-set {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gutter);
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
}
.photo-set__item { margin: 0; min-width: 0; }
.photo-set__item .img-slot { position: relative; height: auto; aspect-ratio: 3 / 4; }
@media (max-width: 900px) { .photo-set { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { .photo-set { grid-template-columns: minmax(0, 1fr); } }

/* ============================================================
   Emptied fields take no space
   Clearing a field in the editor leaves the element behind, because it has
   to stay there for her to type into again. Left alone it still occupies a
   line box and still triggers the adjacent-sibling margins around it, which
   is what a deleted eyebrow or paragraph used to look like: a gap with
   nothing in it. So an empty editable element is removed from the flow for
   visitors, and put back with a visible outline in edit mode, where she
   needs something to aim at. js/edit.js sets .gds-edit on <html>.
   ============================================================ */

main [data-edit]:empty { display: none; }
.gds-edit main [data-edit]:empty {
  display: block;
  min-height: 1.2em;
  outline: 1px dashed rgba(0, 0, 0, 0.35);
  outline-offset: 3px;
}

/* ============================================================
   How the work fits together: pinned figure, three panels
   ============================================================ */

/* Written the other way up on purpose. The DEFAULT here is three blocks of
   ordinary copy in reading order with the figure once above them: that is
   what a phone gets, what prefers-reduced-motion gets, and what anyone with
   no JavaScript or no IntersectionObserver gets. The pinned stage is an
   enhancement layered on top of it, and it only applies when js/howiwork.js
   has added .hiw--live AND the viewport is wide enough AND no one has asked
   for less motion. Building it the usual way round put three absolutely
   positioned panels on top of each other whenever the enhancement did not
   run, which is a stack of overlapping text rather than a fallback.

   The type is on paper, never on the photograph. The client's reference put
   white type over a scrimmed version of the same image, and a scrim is the
   thing that came off every photograph on the site the same day. */

.hiw { padding-top: var(--space-4); }

.hiw__figure {
  margin: 0 auto var(--space-3);
  width: min(62%, 320px);
}
.hiw__figure picture, .hiw__figure img { display: block; width: 100%; height: auto; }

.hiw__panel { padding-top: var(--space-4); }
.hiw__panel:first-of-type { padding-top: 0; }
.hiw__panel + .hiw__panel {
  border-top: 1px solid var(--rule);
  margin-top: var(--space-4);
}
.hiw__head { margin-bottom: var(--space-2); }
.hiw__title {
  font-family: "Libre Bodoni", Georgia, serif;
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.9rem, 3.4vw, 3.1rem);
  line-height: 1.02;
  letter-spacing: -0.01em;
  margin: 0;
}
.hiw__rule, .hiw__dot, .hiw__mark, .hiw__cues { display: none; }

.hiw__points { margin: 0; padding: 0; list-style: none; max-width: var(--measure); }
.hiw__point {
  position: relative;
  padding-left: 20px;
  margin-bottom: var(--space-2);
  font-size: var(--type-body);
  line-height: 1.55;
  color: var(--ink-body);
}
.hiw__point:last-child { margin-bottom: 0; }
.hiw__point::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--ink);
}

.hiw__sample { margin-top: var(--space-3); max-width: var(--measure); }
.hiw__sample-label {
  margin: 0 0 6px;
  font-size: var(--type-label);
  letter-spacing: var(--ls-label);
}
.hiw__sample-body {
  margin: 0;
  font-size: var(--type-small, 0.9rem);
  line-height: 1.55;
  color: var(--ink-body);
}

/* ---- the enhancement: a figure pinned while three panels come up ----
   The stage is sticky inside a track three viewports tall, so the page keeps
   its own scroll and nothing is hijacked. The figure is sized from the
   VIEWPORT HEIGHT rather than from a grid column, so every offset below is
   in one unit and the marker positions stay exact. Its width follows from
   aspect-ratio. The crop is centred on the marker column, so the marker is
   always the figure's horizontal centre, which is the stage's centre, which
   is why the heading row can simply end at 50% with nothing measured. */
@media (min-width: 821px) and (prefers-reduced-motion: no-preference) {
  .hiw--live {
    --hiw-fig-h: clamp(340px, 64vh, 600px);
    --hiw-fig-top: calc(50% - var(--hiw-fig-h) / 2);
    /* half the figure's width, from its height and its 3:4 crop. Every column
       beside the figure is kept off it with this rather than with a guessed
       percentage, which is what let "body & nervous system" run across the
       painting at some window sizes. */
    --hiw-halfw: calc(0.375 * var(--hiw-fig-h));
    position: relative;
    padding-top: 0;   /* the stage is a full viewport; it needs no lead-in */
  }
  .hiw--live .hiw__track { position: relative; height: 300vh; }
  .hiw--live .hiw__cues { display: block; position: absolute; inset: 0; pointer-events: none; }
  .hiw--live .hiw__cue { height: 33.3333%; }

  .hiw--live .hiw__stage {
    position: sticky;
    top: 0;
    height: 100vh;
    overflow: hidden;
  }
  .hiw--live .hiw__inner { position: relative; height: 100%; width: 100%; }

  .hiw--live .hiw__figure {
    position: absolute;
    left: 50%;
    top: var(--hiw-fig-top);
    transform: translateX(-50%);
    height: var(--hiw-fig-h);
    width: auto;
    aspect-ratio: 576 / 768;
    margin: 0;
  }
  .hiw--live .hiw__figure img { height: 100%; object-fit: cover; }

  /* All three panels stay in the DOM and in the accessibility tree; only
     opacity changes, so nothing is announced or withheld depending on where
     the page happens to be scrolled. */
  .hiw--live .hiw__panel {
    position: absolute;
    inset: 0;
    padding-top: 0;
    margin-top: 0;
    border-top: 0;
    opacity: 0;
    transition: opacity 0.7s ease;
    pointer-events: none;
  }
  .hiw--live .hiw__panel.is-on { opacity: 1; }

  /* heading, hairline, dot: the row ends on the marker, and its vertical
     centre is that marker's own height on the figure */
  .hiw--live .hiw__head {
    position: absolute;
    left: var(--margin);
    right: 50%;
    top: calc(var(--hiw-fig-top) + var(--my) * var(--hiw-fig-h));
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    margin: 0;
  }
  /* 100% here is the heading row, which runs from the page margin to the
     midline. Taking the figure's half-width off it stops the title reaching
     the painting; a long one wraps to two lines instead, which is what the
     reference does with "body & nervous system". */
  .hiw--live .hiw__title {
    flex: 0 1 auto;
    max-width: calc(100% - var(--hiw-halfw) - 56px);
  }
  /* the gap belongs between the title and the rule only: put it on the rule
     rather than on the row, or the row's gap also lands between the rule and
     the dot and the line stops short of the thing it is pointing at */
  .hiw--live .hiw__rule {
    display: block;
    flex: 1 1 40px;
    min-width: 30px;
    height: 1px;
    margin-left: 24px;
    background: var(--ink);
  }
  .hiw--live .hiw__dot {
    display: block;
    flex: 0 0 auto;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--ink);
    margin-right: -4.5px;   /* its centre lands exactly on the 50% line */
  }

  .hiw--live .hiw__points {
    position: absolute;
    left: var(--margin);
    width: min(460px, calc(50% - var(--hiw-halfw) - var(--margin) - 32px));
    top: calc(var(--hiw-fig-top) + var(--my) * var(--hiw-fig-h) + 7vh);
  }

  /* the sample session sits in one place on all three panels, which is where
     the reference has it: right of the figure, a little above centre */
  .hiw--live .hiw__sample {
    position: absolute;
    left: calc(50% + var(--hiw-halfw) + 32px);
    right: var(--margin);
    top: 42%;
    margin: 0;
  }

  /* the icon sits on the figure at the marker, at the same hairline weight
     as the rule that reaches it */
  .hiw--live .hiw__mark {
    display: block;
    position: absolute;
    left: 50%;
    top: var(--hiw-fig-top);
    width: var(--hiw-fig-h);
    height: var(--hiw-fig-h);
    transform: translateX(-50%);
    pointer-events: none;
  }
  .hiw--live .hiw__icon {
    position: absolute;
    left: var(--ix);
    top: var(--iy);
    width: var(--isz);
    height: var(--isz);
    transform: translate(-50%, -50%);
    /* ink, not paper. The reference drew these white because its copy of the
       painting was scrimmed down; this one runs at the brightness the whole
       photograph set runs at, so white disappeared into the skin at the torso
       and the abdomen. */
    color: var(--ink);
    opacity: 0.55;
  }
}

/* ============================================================
   One reading order on a phone
   Below 820px every grid in the site collapses to a single column, and the
   order the reader then gets is DOM order: whatever the two-column desktop
   layout happened to need. In three components that came out wrong, and the
   client asked for the same shape in all three:

     hero      title, copy, picture, then the buttons
     closing   picture, title, copy, then the button
     freebie   label, title, copy, picture, then the form

   The pattern underneath all three is the same: THE PICTURE COMES BEFORE
   THE THING THE READER IS ASKED TO DO. She reads what it is, sees it, then
   is asked. On desktop the two columns put the picture beside the copy and
   the question does not arise.

   The mechanism is `order` on grid items, plus `display: contents` where the
   items that have to be reordered are wrapped in a column div rather than
   being children of the grid. Two consequences of `display: contents` that
   both have to be handled here, or the layout silently breaks:

     - `.grid > *` sets `grid-column: 1 / -1`, and it matches DOM children.
       A promoted grandchild is a grid item but is NOT matched by that
       selector, so it has to be given the span here.
     - the wrapper's own `gap` goes with it. So the row gap is taken to zero
       and the spacing comes from the margins the components already carry,
       which is where the rest of the site keeps it anyway.

   Added 2026-09-09.
   ============================================================ */

@media (max-width: 820px) {

  /* ---- hero: title, copy, picture, buttons ---- */
  .hero .grid { row-gap: 0; }
  .hero__text,
  .hero__head { display: contents; }
  .hero .breadcrumb,
  .hero__kicker,
  .hero h1,
  .hero__lede,
  .hero__actions { grid-column: 1 / -1; }

  .hero .breadcrumb { order: 1; }
  .hero__kicker     { order: 1; }
  .hero h1          { order: 2; margin-top: var(--space-2); }
  .hero__lede       { order: 3; }
  .hero__media      { order: 4; margin-top: var(--space-3); }
  .hero__actions    { order: 5; }

  /* The hero's two caps are sized for the DESKTOP layout, where a photograph
     sits beside the copy and the measure is doing real work. Below 820px the
     hero is one column with nothing beside it and both caps are pure dead
     space: at 768px the h1 ran at 48% of the column and the lede at 52%, on
     all 27 pages that have a hero. The title is display type and takes no
     measure at all; the lede keeps one, but --measure rather than 34ch, so
     it fills a phone and still cannot run a long line on a tablet.
     `overflow-wrap` and `hyphens` on .hero h1 stay: they are the net for a
     word too long for the column and are unrelated to this. */
  .hero h1    { max-width: none; }
  .hero__lede { max-width: var(--measure); }

  /* ---- closing block: picture, title, copy, button ---- */
  /* .book__text is a grid child and the picture beside it is the only other
     one, so moving the copy to second is the whole rule. On the 26 pages
     whose closing block has no picture there is nothing to reorder and this
     does nothing. */
  .book__text { order: 2; }

  /* ---- freebie: label, title, copy, picture, form ---- */
  .freebie .grid { row-gap: 0; }
  .freebie__copy { display: contents; }
  .freebie__copy > * { grid-column: 1 / -1; }
  .freebie__aside { order: 4; margin-top: var(--space-3); }
  /* The action last, whichever of the two it is: the Kit form on
     --form, the button on --link. */
  .freebie__copy > .kitform,
  .freebie__copy > .freebie__cta { order: 5; }
}

/* ============================================================
   Motion: fades only
   ============================================================ */

.reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 1.1s cubic-bezier(0.22, 0.61, 0.24, 1),
              transform 1.1s cubic-bezier(0.22, 0.61, 0.24, 1);
  will-change: opacity, transform;
}
.reveal.is-visible { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; transition: none; }
  * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}
