/* FitPal Coach marketing site (fitpal.ma).
   Field-manual register: warm graphite ground, the app's gold, one family pushed hard on both
   weight and width axes, visible rules as grammar. One stylesheet for both locales, no build.

   Palette is verified, not estimated: `node scripts/check-site-coach-contrast.mjs` reads the
   hexes below and fails on any pair that drops under WCAG 2.2 AA. DESIGN.md explains the roles.

   This is NOT site/ (fitpal.ma, gold on black) and NOT frontend/ (the app). Different buyer,
   deliberately different look. */

/* ---------------------------------------------------------------- tokens */

:root {
  /* ground */
  --ink:          #14120F;
  --graphite:     #1C1A17;
  --raised:       #26231F;

  /* lines. One token, because every rule on this page turned out to be load-bearing.

     There was a decorative --rule (#38342E) alongside this, at 1.40:1 on the ground. The
     intent was a quiet hairline for ornament; what actually happened is that it ended up
     carrying every boundary on the site — row separators, feature separators, the FAQ
     question edges, the sticky header's edge, the open mobile menu's bottom edge — which
     DESIGN.md explicitly forbade it from doing. At 1.40:1 on a dim phone, the structure the
     page calls its grammar was close to invisible, and hero copy showed through the open
     menu. All nine were promoted here and the decorative token was deleted rather than left
     lying about for someone to reach for again. */
  --rule-strong:  #746C62;

  /* text */
  --bone:         #F2EDE4;
  --muted:        #A9A198;
  --mute-bone:    #5E574E;

  /* accent. The app's gold, so this site reads as the same brand family; the ground stays
     warm graphite rather than the app's pure black.

     Gold fills ALWAYS take an --ink label: bone on gold is 1.80:1 and fails badly.

     Gold is 8.26:1 on the graphite ground, so unlike the orange it replaced it needs no
     lighter variant to survive small text — --gold carries every size on every dark ground,
     and the accent system is two values rather than three.

     --gold-deep exists only because the one light section breaks gold completely: gold on
     bone is 1.80:1, which is invisible text rather than merely tight. */
  --gold:         #D4AF37;
  --gold-deep:    #6F5A1C;

  /* type */
  /* Max lowered from 6.5rem. `.display` is the hero h1 and nothing else, and at 104px its
     three lines ran ~287px tall — which pushed the film, the one real product on the
     fold, down to 22% visible on a 1440x900 laptop. */
  --t-display: clamp(2.75rem, 8.6vw, 5.75rem);
  --t-h2:      clamp(2rem, 5vw, 3.25rem);
  --t-h3:      clamp(1.25rem, 2.2vw, 1.5rem);
  --t-body:    1.0625rem;
  --t-small:   0.9375rem;
  --t-label:   0.75rem;

  /* space */
  --sp-1: 4px;   --sp-2: 8px;   --sp-3: 12px;  --sp-4: 16px;
  --sp-5: 24px;  --sp-6: 32px;  --sp-7: 48px;  --sp-8: 64px;
  --sp-9: 96px;  --sp-10: 128px; --sp-11: 160px;

  --gutter:  clamp(20px, 5vw, 56px);
  --max:     1200px;
  --section: clamp(88px, 12vw, 168px);

  /* geometry: near-square on purpose. Nothing is pill-shaped.
     The device frame below is the one deliberate exception, and it is an exception because it
     DEPICTS AN OBJECT rather than styling a surface. A phone with 6px corners is not a phone. */
  --r-sm: 3px;
  --r:    6px;

  /* device frame. Geometry lifted from the house component the ten tutorial videos use
     (compositions/components/device-frame-stage.html); the skin is restated in these tokens
     as a schematic outline rather than its gradient bezel and five-layer shadow.
     --dev-aspect is the app's real phone viewport, so the body shape is not invented. */
  --dev-radius:        30px;
  --dev-bezel:         11px;
  --dev-screen-radius: 19px;   /* = radius - bezel, written out so the two curves cannot drift */
  --dev-aspect:        9 / 19.5;
  --dev-w:             300px;  /* body width; overridden per placement */

  /* motion: ease-out-quart, no spring, no bounce */
  --ease: cubic-bezier(0.165, 0.84, 0.44, 1);
  --fast: 180ms;
  --med:  320ms;
  --slow: 520ms;

  color-scheme: dark;
}

/* ---------------------------------------------------------------- base */

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

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

body {
  margin: 0;
  background: var(--graphite);
  color: var(--bone);
  font-family: Archivo, 'Archivo Fallback', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: var(--t-body);
  line-height: 1.55;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

img, svg, video { display: block; max-width: 100%; }
img, video { height: auto; }

h1, h2, h3, p, ul, ol, figure, blockquote { margin: 0; }
ul, ol { padding: 0; list-style: none; }

a { color: inherit; }

/* One focus treatment, everywhere. */
:focus-visible {
  outline: 2px solid var(--bone);
  outline-offset: 2px;
  border-radius: 2px;
}

.skip {
  position: absolute; left: -9999px;
  background: var(--gold); color: var(--ink);
  padding: var(--sp-3) var(--sp-5);
  font-weight: 700; text-decoration: none; z-index: 100;
}
.skip:focus { left: var(--gutter); top: var(--sp-3); }

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

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

/* ---------------------------------------------------------------- type */

h1, h2, h3 { text-wrap: balance; }

.display {
  font-size: var(--t-display);
  font-variation-settings: 'wght' 800, 'wdth' 68;
  line-height: 0.92;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  margin: 0;
}

h2 {
  font-size: var(--t-h2);
  font-variation-settings: 'wght' 700, 'wdth' 78;
  line-height: 1.08;
  letter-spacing: -0.015em;
  text-transform: uppercase;
}

h3 {
  font-size: var(--t-h3);
  font-variation-settings: 'wght' 600, 'wdth' 100;
  line-height: 1.25;
  letter-spacing: -0.01em;
}

.lead {
  font-size: clamp(1.125rem, 2vw, 1.375rem);
  line-height: 1.5;
  color: var(--muted);
  max-width: 54ch;
}

p { max-width: 68ch; }

/* The only place caps and positive tracking are allowed. */
.label {
  font-size: var(--t-label);
  font-variation-settings: 'wght' 600, 'wdth' 100;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  line-height: 1.3;
}

.num { font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- header */

.hdr {
  position: sticky; top: 0; z-index: 50;
  background: var(--graphite);
  border-bottom: 1px solid var(--rule-strong);
}

.hdr-in {
  display: flex; align-items: center; gap: var(--sp-5);
  height: 68px;
}

.brand {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  text-decoration: none; margin-right: auto;
  font-variation-settings: 'wght' 800, 'wdth' 84;
  font-size: 1.0625rem;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}
.brand svg { width: 20px; height: 20px; flex: none; }
.brand b { font-weight: inherit; color: var(--gold); }

.nav { display: flex; gap: var(--sp-6); }
.nav a {
  text-decoration: none; color: var(--muted);
  font-size: var(--t-small);
  transition: color var(--fast) var(--ease);
}
.nav a:hover { color: var(--bone); }

.lang {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-size: var(--t-label); letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--muted); text-decoration: none;
  transition: color var(--fast) var(--ease);
}
.lang:hover { color: var(--bone); }

.burger {
  display: none;
  width: 40px; height: 40px;
  background: none; border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm); color: var(--bone);
  cursor: pointer; padding: 0;
}
.burger span {
  display: block; width: 16px; height: 1.5px;
  background: currentColor; margin: 3px auto;
}

/* ---------------------------------------------------------------- buttons */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  padding: 14px 24px;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font-size: var(--t-small);
  font-variation-settings: 'wght' 700, 'wdth' 100;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease), color var(--fast) var(--ease);
}

/* Gold fill, ink label. Never bone on gold: it measures 1.80:1. */
.btn.primary { background: var(--gold); color: var(--ink); border-color: var(--gold); }
.btn.primary:hover { background: var(--ink); color: var(--gold); }

.btn.ghost { border-color: var(--rule-strong); color: var(--bone); }
.btn.ghost:hover { background: var(--raised); border-color: var(--bone); }

.btn.sm { padding: 10px 16px; }

/* ---------------------------------------------------------------- sections */

.sec { padding-block: var(--section); }
.sec.tight { padding-block: clamp(56px, 8vw, 104px); }

/* The repeated object: gold numeral, full-width rule, heading beneath. Six of them, as a
   declared system rather than decoration. */
.sec-head { margin-bottom: var(--sp-8); }

.sec-head .rule {
  display: flex; align-items: baseline; gap: var(--sp-4);
  border-top: 1px solid var(--rule-strong);
  padding-top: var(--sp-3);
  margin-bottom: var(--sp-6);
}
.sec-head .rule .n { color: var(--gold); }
.sec-head .rule .k { color: var(--muted); }

/* Lead paragraph inside a section head, and the two spacing exceptions that would
   otherwise be inline styles. */
.sec-head .lead { margin-top: var(--sp-5); }
.cta .sec-head { margin-bottom: var(--sp-6); }
.ftr .brand    { margin-bottom: var(--sp-4); }

/* ---------------------------------------------------------------- hero */

/* Tighter than the page's other sections on purpose. The film beneath this copy is 16:9 and
   about 650px tall on a laptop; every pixel spent above it is a pixel of film below the fold,
   so the hero's vertical rhythm runs one step tighter than the section rhythm everywhere. */
.hero { padding-top: clamp(40px, 4.5vw, 68px); padding-bottom: var(--section); }

/* The eyebrow carries three jobs that were missing or underweighted, and costs no extra
   height for any of them — it was already a one-line row with ~300px of dead space at its
   right end.

   Left, in gold: what this is AND who it is for. The category used to sit here alone at 12px,
   the smallest type on a page whose headline states only the problem — so the one line saying
   what the product IS was the quietest thing in the hero. The client range is the stated ICP,
   so printing it costs no honesty and filters the calls this page exists to book: success is a
   QUALIFIED call, not a call.

   Right, in muted: the evidence claim. With no customers yet there is no social proof to show,
   and the brand bans logo walls, so the only honest credibility lever is the product itself —
   which is exactly what the film and the four captures are. Saying so above the fold is what
   makes a skeptical reader keep scrolling to them.

   Laid out as label-left / value-right, the same spec-row grammar the page uses elsewhere. */
.hero .eyebrow {
  color: var(--gold);
  margin-bottom: var(--sp-4);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2) var(--sp-5);
  /* `p { max-width: 68ch }` is a READING measure, and at 12px it caps this element at ~469px —
     narrower than its own two labels, so the strip wrapped to two lines and gave back the
     height the smaller headline had just bought. This row is a spec strip, not prose. */
  max-width: none;
}

.hero .eyebrow .proof {
  margin-left: auto;      /* wraps to its own line before it would collide */
  color: var(--muted);
}

.hero .display { margin-bottom: var(--sp-5); }
.hero .display span { display: block; }

.hero .lead { margin-bottom: var(--sp-6); }

.hero .actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-bottom: var(--sp-5); }

.hero .note { font-size: var(--t-small); color: var(--muted); }

/* The hero STACKS: copy, then the film full width beneath it.
   Side by side, a 16:9 film caps out around 530px wide, because the copy column cannot drop
   below ~600px without breaking the 118px display measure — and 530px is LESS screen area than
   the portrait cut had. Stacked, the film gets ~1000px, which is the difference between a
   thumbnail and something you can read. */
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(28px, 3.4vw, 44px);
}

.hero-stage {
  position: relative;
  min-width: 0;
  display: grid;
  justify-items: center;
  gap: var(--sp-3);
  width: 100%;
}

/* The photograph that fills the space beside the headline.

   The copy runs to a text measure, so on a wide screen the right half of the hero was empty.
   A photograph there is the one place on this page where a human face appears above the fold,
   which is what keeps a field-manual layout from reading as machinery.

   It is presented UNFRAMED — no device body, no border. That is the same contrast DESIGN.md
   draws for the photo band lower down: app screens are evidence and sit in a phone; a
   photograph is atmosphere and does not.

   Two mechanics matter here:
   - It is absolutely positioned inside a stretched figure, so it takes its height FROM the
     copy column and contributes none of its own. Let it size itself and a 4:5 portrait stands
     ~110px taller than the copy, which pushes the film down and undoes the fold work above.
   - Below the breakpoint it is not rendered at all. Stacked on a phone it would cost a full
     screen of scrolling before the film, and the film plus the four screenshots already carry
     that view.

   The breakpoint is 1040px, not 1000px, and the extra 40px is deliberate. Chrome matches a
   media query against `innerWidth`, which INCLUDES the classic scrollbar, while layout gets
   `clientWidth`, which does not. At a 1000px window the two-column rule therefore fired with
   only 985px to lay out, the second track computed to `0px`, and this figure's absolutely
   positioned caption — `left: 0; right: 0` on a zero-width box — spilled 78px past the page
   and gave the whole document a horizontal scrollbar. Starting at 1040 keeps the rule out of
   that 15px band entirely. */
.hero-portrait { display: none; }

@media (min-width: 1040px) {
  .hero-grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.5fr);
    column-gap: clamp(32px, 4vw, 64px);
    align-items: start;
  }
  .hero-copy    { grid-column: 1; grid-row: 1; }
  .hero-stage   { grid-column: 1 / -1; }
  .hero-portrait {
    display: block;
    position: relative;
    grid-column: 2;
    grid-row: 1;
    align-self: stretch;   /* height comes from the copy column */
    min-height: 0;
    min-width: 0;
    margin: 0;
    /* Belt and braces for the collapse described above: if this track is ever squeezed to
       nothing again, the caption is clipped rather than pushing the page sideways. */
    overflow: hidden;
  }
  .hero-portrait img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center 18%;   /* both faces sit high in the frame */
    border-radius: var(--r);
  }
  /* Same disclosure the photo band carries, and for the same reason: a stock portrait beside a
     headline is the easiest place on the page to imply a customer by accident. Overlaid rather
     than placed under the figure, so it adds no height and the film below does not move. */
  .hero-portrait figcaption {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    padding: 32px var(--sp-4) var(--sp-3);
    color: var(--muted);
    background: linear-gradient(to bottom, transparent, rgba(20, 18, 15, .92) 64%);
    border-end-start-radius: var(--r);
    border-end-end-radius: var(--r);
  }
}

/* The film is NOT in a phone. The four screenshots are phone screens and belong in a phone
   body; the film is a film, and a bezel around it only makes it smaller. */
.hero-media {
  position: relative;          /* anchors .media-toggle */
  display: inline-block;
  max-width: 100%;
  border-radius: var(--r);
  overflow: hidden;
  background: #000;
  border: 1px solid var(--rule-strong);
  line-height: 0;          /* no descender gap under an inline-block video */
}
.hero-media video {
  display: block;
  width: auto; height: auto;
  max-width: 100%;
  /* Two caps. 78svh is the ceiling on a tall window. The second yields to the copy above:
     on a short laptop the film shrinks rather than pushing itself entirely past the fold.
     svh, not vh, so a mobile URL bar collapsing does not resize the film mid-scroll. */
  max-height: min(78svh, calc(100svh - 240px));
}

/* The film's pause control. Only ever inserted by site.js, on the one path where the native
   controls were removed — see the WCAG 2.2.2 note there.

   Sized and placed to acknowledge rather than perform: 34px, low opacity at rest, in the
   corner of the film rather than over its centre. The glyphs are drawn in CSS rather than set
   as a character, because a text pause glyph renders differently in every font fallback. */
.media-toggle {
  position: absolute;
  right: 10px;
  bottom: 10px;
  width: 34px;
  height: 34px;
  padding: 0;
  display: grid;
  place-items: center;
  background: rgba(20, 18, 15, .72);
  border: 1px solid var(--rule-strong);
  border-radius: var(--r);
  cursor: pointer;
  line-height: 0;
  opacity: .55;
  transition: opacity .16s var(--ease), background-color .16s var(--ease);
}

.media-toggle:hover,
.media-toggle:focus-visible { opacity: 1; background: rgba(20, 18, 15, .92); }

.media-toggle::before { content: ''; display: block; }

/* two bars */
.media-toggle[data-state="playing"]::before {
  width: 10px;
  height: 12px;
  border-left: 3px solid var(--bone);
  border-right: 3px solid var(--bone);
}

/* one triangle */
.media-toggle[data-state="paused"]::before {
  width: 0;
  height: 0;
  margin-left: 3px;
  border-left: 11px solid var(--bone);
  border-top: 7px solid transparent;
  border-bottom: 7px solid transparent;
}

@media (prefers-reduced-motion: reduce) {
  .media-toggle { transition: none; }
}

.feat-body, .feat figure { min-width: 0; }

/* The device frame. Every screen on this site is a phone screen, so there is no flat variant
   left; the old `.shot` hairline-and-mat is gone rather than kept as dead CSS.

   The frame is schematic on purpose: a 1px outline and an ink bezel, no gradient, no shadow,
   no imitation hardware. It depicts a device without pretending to be a photograph of one,
   which is the only device treatment that sits with numbered sections and visible rules.

   Evidence positioning: the flat frame used to be what separated a real capture from
   atmosphere. That job now belongs to the visible caption under every device ("Capture
   réelle" / "Real capture") — see .shot-cap. Moving the claim from a frame treatment to an
   explicit label is stronger, because a reader does not have to infer it. */
.device {
  position: relative;
  box-sizing: border-box;
  margin-inline: auto;
  width: min(100%, var(--dev-w));
  padding: var(--dev-bezel);
  border: 1px solid var(--rule-strong);
  border-radius: var(--dev-radius);
  background: var(--ink);
}

/* Speaker slot: one pseudo-element, no image. */
.device::before {
  content: '';
  position: absolute; left: 50%; transform: translateX(-50%);
  top: calc((var(--dev-bezel) - 3px) / 2);
  width: 22%; max-width: 62px; height: 3px;
  border-radius: 2px;
  background: var(--rule-strong);
  opacity: 0.55;
}

.device-screen {
  position: relative;
  overflow: hidden;
  aspect-ratio: var(--dev-aspect);
  border-radius: var(--dev-screen-radius);
  /* The app's own ground, not --ink: a graphite bezel against a pure-black screenshot would
     leave a visible seam at the screen edge. */
  background: #000;
}

/* Beats the global `img, video { height: auto }` on specificity, which would otherwise fight
   height:100% and let the media set the box height instead of the aspect ratio. Same trap
   .people-img img documents. */
.device-screen > img,
.device-screen > video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: top center;
  display: block;
}

.device :focus-visible { border-radius: var(--dev-screen-radius); }

/* The figure holding a device and its caption. */
.shot-wrap {
  display: grid;
  justify-items: center;
  gap: var(--sp-3);
  min-width: 0;
}

.shot-cap {
  display: flex; flex-wrap: wrap; justify-content: space-between;
  gap: var(--sp-1) var(--sp-4);
  width: 100%; max-width: var(--dev-w, none);
  color: var(--muted);
}

/* A device in a wide feature column is constrained rather than blown up to the column width,
   which would render the app's UI text larger than life. --dev-w carries that.

   Sizing this correctly turns on one fact that is easy to get wrong: the captures are 720px
   wide but were shot at a 360 CSS px viewport with --force-device-scale-factor=2, so those
   720 pixels encode 360 CSS px of UI, not 720. The life-size ratio is therefore
   (--dev-w - 2 * --dev-bezel) / 360, NOT / 720. Reading it as /720 makes the type look half
   as big as it is and invites a "fix" that doubles every feature row's height for nothing.

   At 360px the screen is 338px, a ratio of 0.94 — near life-size, so the app's 17px body text
   lands at ~16px, in the same register as this page's own 17px body. The row runs ~780px tall
   at 9:19.5, which the two-column feature grid absorbs (each column is ~560px at the 1200px
   container). */
.feat .shot-wrap { --dev-w: 360px; }

/* ---------------------------------------------------------------- problem rows */

.rows { border-top: 1px solid var(--rule-strong); }

.row {
  display: grid;
  grid-template-columns: 3.5rem minmax(0, 1fr) minmax(0, 1.1fr);
  gap: var(--sp-5);
  padding-block: var(--sp-6);
  border-bottom: 1px solid var(--rule-strong);
  align-items: start;
}
.row .n { color: var(--gold); padding-top: 0.35em; }
.row h3 { margin-bottom: var(--sp-2); }
.row p { color: var(--muted); }

/* ---------------------------------------------------------------- features */

/* Alternating, not an even card grid. */
.feat {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(32px, 5vw, 80px);
  align-items: center;
  padding-block: var(--sp-9);
  border-bottom: 1px solid var(--rule-strong);
}
.feat:last-child { border-bottom: 0; }
.feat:nth-child(even) .feat-body { order: 2; }

.feat h3 { margin-bottom: var(--sp-4); }
.feat p { color: var(--muted); margin-bottom: var(--sp-5); }

.feat ul { display: grid; gap: var(--sp-3); }
.feat li {
  display: grid; grid-template-columns: 1.25rem 1fr; gap: var(--sp-3);
  font-size: var(--t-small); color: var(--muted);
}
.feat li::before {
  content: ''; width: 9px; height: 9px; margin-top: 0.45em;
  background: var(--gold);
}

/* ---------------------------------------------------------------- spec (light) */

/* The one light section. The manual metaphor is literal here, so the ground is paper. */
.spec {
  background: var(--bone);
  color: var(--graphite);
}
.spec h2 { color: var(--graphite); }
.spec .sec-head .rule { border-top-color: var(--mute-bone); }
.spec .sec-head .rule .n { color: var(--gold-deep); }
.spec .sec-head .rule .k { color: var(--mute-bone); }
.spec .lead { color: var(--mute-bone); }

/* The two terms rows closing the spec sheet. Same .rows object as section 01, restated for the
   bone ground: --gold measures 1.80:1 on bone and --muted is a dark-ground token, so both would
   fail here. --gold-deep (5.71:1) and --mute-bone are the documented light-ground values, and
   the separator matches the spec table's own tint rather than the dark-ground rule token. */
.spec .terms {
  border-top: 1px solid var(--mute-bone);
  margin-top: var(--sp-7);
}
.spec .terms .row { border-bottom-color: rgba(94, 87, 78, 0.28); }
.spec .terms .row:last-child { border-bottom: 0; }
.spec .terms .row .n { color: var(--gold-deep); }
.spec .terms .row h3 { color: var(--graphite); }
.spec .terms .row p { color: var(--mute-bone); }

.spec-table {
  border-top: 1px solid var(--mute-bone);
  margin-top: var(--sp-7);
}
.spec-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
  gap: var(--sp-5);
  padding-block: var(--sp-4);
  border-bottom: 1px solid rgba(94, 87, 78, 0.28);
  align-items: baseline;
}
.spec-row dt { color: var(--mute-bone); }
.spec-row dd { margin: 0; font-variation-settings: 'wght' 500, 'wdth' 100; }
.spec-row dd .y { color: var(--gold-deep); font-variation-settings: 'wght' 700, 'wdth' 100; }

/* ---------------------------------------------------------------- steps */

/* Four graduations on one rule, not four cards.

   DESIGN.md: "Cards are used once, for the spec block... Feature content is laid out as ruled
   rows, not as a repeating icon-heading-text grid." This was the single place that broke that,
   and it was the one section a visitor would have called generic SaaS — which is the exact
   anti-reference PRODUCT.md names first.

   Drawing it as a scale is also what the content means. "De l'appel au premier lundi" is a
   sequence in time, not four peers, and a ruled scale with four ticks says so without a word. */
.steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(20px, 2.6vw, 44px);
  border-top: 1px solid var(--rule-strong);
}

.step {
  position: relative;
  padding-top: var(--sp-5);
}

/* The graduation itself: a tick standing on the rule. Square-ended, like everything else in
   this system — a round dot would be the one pill-shaped object on the page. */
.step::before {
  content: '';
  position: absolute;
  top: -1px;
  left: 0;
  width: 2px;
  height: 10px;
  background: var(--gold);
}

.step .n {
  display: block; color: var(--gold);
  margin-bottom: var(--sp-3);
}
/* Was hard-coded to 1.0625rem, exactly body size, so the heading was distinguished from its
   own paragraph by weight alone. It is a heading; it takes the heading step. */
.step h3 { margin-bottom: var(--sp-2); font-size: var(--t-h3); }
.step p { color: var(--muted); font-size: var(--t-small); }

@media (max-width: 760px) {
  /* Stacked, a horizontal scale stops meaning anything, so each step carries its own rule and
     the object degrades into exactly the ruled rows the rest of the page is built from. */
  .steps { grid-template-columns: minmax(0, 1fr); gap: 0; border-top: 0; }
  .step { border-top: 1px solid var(--rule-strong); padding-block: var(--sp-5); }
  .step::before { top: -1px; }
}

/* ---------------------------------------------------------------- people */

/* The one photograph on the site, and the only image here that is NOT evidence.
   It is deliberately the inverse of .device: full-bleed and unframed, where a screenshot sits
   inside a phone body with a visible "real capture" caption under it. That contrast is how a
   reader tells atmosphere from proof without being told which is which.

   Unnumbered on purpose. 01 to 06 is a declared system; a "07" would give a stock photograph
   the same standing as the spec sheet.

   Full-bleed WITHOUT 100vw: that unit includes the scrollbar and is the usual way the
   320-1920 no-horizontal-overflow rule gets broken. This is a block-level section outside
   .wrap, the same pattern .cta already uses. */
.people { background: var(--ink); }

.people-img { position: relative; }

/* Beats the global `img { height: auto }` on specificity, so a 2.4:1 source cannot become
   700px tall on a phone. */
.people-img img {
  display: block; width: 100%;
  height: clamp(200px, 30vw, 420px);
  object-fit: cover; object-position: center 34%;
}

/* The scrim lives here rather than baked into the JPEG, so it survives any responsive crop. */
.people-img::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 42%;
  background: linear-gradient(0deg, var(--ink), transparent);
  pointer-events: none;
}

/* The illustrative disclosure is VISIBLE, not hidden in the alt attribute. A sighted reader
   needs to know this is stock as much as a screen-reader user does, and the site's whole
   credibility position is that you cannot mistake atmosphere for evidence. */
.people-cap {
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-5);
  justify-content: space-between;
  color: var(--muted);
  padding-block: var(--sp-4) var(--sp-6);
  max-width: none;
}

/* ---------------------------------------------------------------- cta band */

.cta {
  background: var(--gold);
  color: var(--ink);
}
.cta h2 { color: var(--ink); }
.cta p { color: var(--ink); opacity: 0.82; max-width: 52ch; }
.cta .actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-6); }

.cta .btn.solid { background: var(--ink); color: var(--bone); }
.cta .btn.solid:hover { background: var(--graphite); color: var(--bone); }
.cta .btn.line { border-color: var(--ink); color: var(--ink); }
.cta .btn.line:hover { background: rgba(20, 18, 15, 0.12); }
.cta :focus-visible { outline-color: var(--ink); }

/* The signature at the ask: a named human where the page previously had nobody.

   Text only. A portrait was here and was removed at the owner's request; if one ever returns,
   note that it has to be graded so its background lands on --ink, because a light studio
   backdrop reads as a hole punched in the gold band. */
.signed {
  margin-top: var(--sp-7);
  padding-top: var(--sp-6);
  border-top: 1px solid rgba(20, 18, 15, 0.28);
  max-width: 62ch;
}

.signed p {
  margin: 0;
  color: var(--ink);
  opacity: 0.82;
  font-size: var(--t-small);
  max-width: none;
}

.signed b {
  display: block;
  opacity: 1;
  font-variation-settings: 'wght' 700, 'wdth' 100;
  margin-bottom: var(--sp-2);
}

/* Ink on gold, underlined, because colour alone cannot carry a link on a single-accent band. */
.signed a { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }
/* Section 06 completes the 01-06 system, which previously stopped at 05 at the one section
   that matters most. On the gold ground the numeral and the rule take ink: gold on gold is
   invisible, and bone on gold is 1.80:1. */
.cta .sec-head .rule { border-color: rgba(20, 18, 15, 0.28); }
.cta .sec-head .n,
.cta .sec-head .k { color: var(--ink); }
.cta .note { color: var(--ink); opacity: 0.74; margin-top: var(--sp-5); }

/* ------------------------------------------------------- mobile ask bar */

/* PRODUCT.md puts this buyer "standing in a gym between sessions with ten minutes on their
   phone". At 390px this page runs about 11,200px, which puts the ask on screen twelve, and
   the header CTA is hidden below 620px. Without this the primary action appears once above
   the fold and then not again for 10,000px.

   Three deliberate choices:
   - It links to the ask, not straight to WhatsApp. Success is a QUALIFIED call; a one-tap
     jump to the chat from screen one would buy volume at the cost of the thing being bought.
   - It is pure CSS and always present below the breakpoint. Gating it on JS would put the
     only conversion behind the same script failure that already blanks every `.rv` element.
   - `body` gets bottom padding to match, so the footer can always clear it. */
.ask-bar { display: none; }

@media (max-width: 620px) {
  .ask-bar {
    display: block;
    position: fixed;
    inset: auto 0 0 0;
    z-index: 60;              /* above the sticky header at 50, below the skip link at 100 */
    padding: var(--sp-3) var(--gutter);
    padding-bottom: calc(var(--sp-3) + env(safe-area-inset-bottom));
    background: var(--ink);
    border-top: 1px solid var(--rule-strong);
  }
  .ask-bar .btn { width: 100%; }
  body { padding-bottom: 92px; }
}

/* ---------------------------------------------------------------- faq */

.faq { border-top: 1px solid var(--rule-strong); }
.faq details { border-bottom: 1px solid var(--rule-strong); }
.faq summary {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4);
  padding-block: var(--sp-5);
  cursor: pointer;
  font-variation-settings: 'wght' 600, 'wdth' 100;
  list-style: none;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: '+'; color: var(--gold);
  font-variation-settings: 'wght' 700;
  flex: none; font-size: 1.25rem; line-height: 1;
}
.faq details[open] summary::after { content: '\2212'; }
.faq details p { color: var(--muted); padding-bottom: var(--sp-5); }

/* ---------------------------------------------------------------- footer */

.ftr {
  background: var(--ink);
  border-top: 1px solid var(--rule-strong);
  padding-block: var(--sp-8);
  padding-bottom: calc(var(--sp-8) + env(safe-area-inset-bottom));
}
.ftr-in {
  display: flex; flex-wrap: wrap; gap: var(--sp-6);
  justify-content: space-between; align-items: flex-start;
}
.ftr a { color: var(--muted); text-decoration: none; transition: color var(--fast) var(--ease); }
.ftr a:hover { color: var(--bone); }
.ftr ul { display: grid; gap: var(--sp-2); font-size: var(--t-small); }
.ftr .fine { color: var(--muted); font-size: var(--t-small); max-width: 42ch; }

/* ---------------------------------------------------------------- responsive */

@media (max-width: 1000px) {
  .hero-grid { grid-template-columns: minmax(0, 1fr); }
  /* A phone-sized picture of a phone, shown on a phone, is the classic mockup absurdity. */
  .hero-media video { max-height: 56svh; }
  .feat { grid-template-columns: minmax(0, 1fr); }
  .feat:nth-child(even) .feat-body { order: 0; }
}

/* The header collapses well before the body does. Five French nav labels plus the CTA need
   about 940px; below that they overflowed the bar between this breakpoint and the old 760px
   one, which is a band only a tablet ever lands in and no desktop check would surface. */
@media (max-width: 940px) {
  .nav {
    display: none;
    position: absolute; left: 0; right: 0; top: 100%;
    flex-direction: column; gap: 0;
    background: var(--graphite);
    border-bottom: 1px solid var(--rule-strong);
    padding: var(--sp-3) var(--gutter) var(--sp-5);
  }
  .nav.open { display: flex; }
  .nav a { padding-block: var(--sp-3); font-size: var(--t-body); }
  .burger { display: block; }
  .hdr-in { position: relative; }
}

/* The header CTA goes last: on a tablet there is still room for it next to the burger. */
@media (max-width: 620px) {
  .hdr .btn.primary { display: none; }
}

@media (max-width: 760px) {
  .row { grid-template-columns: 2.5rem minmax(0, 1fr); }
  .row p { grid-column: 2; }

  .spec-row { grid-template-columns: 1fr; gap: var(--sp-1); }
}

/* ---------------------------------------------------------------- motion */

/* Scroll reveal. JS-gated on `.js` so a reader without JS sees everything immediately. */
.js .rv {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity var(--slow) var(--ease), transform var(--slow) var(--ease);
}
.js .rv.in { opacity: 1; transform: none; }

/* First-visit hero intro. The `.intro` class is set pre-paint by an inline head script, so the
   hero never paints and then hides. Staggered so the screenshot arrives last and reads as
   depth. */
@keyframes introUp {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}
@keyframes introRise {
  from { opacity: 0; transform: translateY(32px) scale(0.988); }
  to   { opacity: 1; transform: none; }
}

.js.intro .hero .eyebrow,
.js.intro .hero .display span,
.js.intro .hero .lead,
.js.intro .hero .actions,
.js.intro .hero .note,
.js.intro .hero-stage {
  opacity: 0;
  animation: introUp var(--slow) var(--ease) forwards;
}
.js.intro .hero .eyebrow           { animation-delay: 40ms; }
.js.intro .hero .display span:nth-child(1) { animation-delay: 90ms; }
.js.intro .hero .display span:nth-child(2) { animation-delay: 150ms; }
.js.intro .hero .display span:nth-child(3) { animation-delay: 210ms; }
.js.intro .hero .lead              { animation-delay: 300ms; }
.js.intro .hero .actions           { animation-delay: 380ms; }
.js.intro .hero .note              { animation-delay: 430ms; }
.js.intro .hero-stage {
  animation-name: introRise;
  animation-delay: 460ms;
  animation-duration: 680ms;
}

/* Reduced motion is a first-class state, not an afterthought: the observer never arms, the
   intro class is never added, and everything here is neutralised. */
@media (prefers-reduced-motion: reduce) {
  html:focus-within { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .js .rv { opacity: 1; transform: none; }
  .js.intro .hero .eyebrow,
  .js.intro .hero .display span,
  .js.intro .hero .lead,
  .js.intro .hero .actions,
  .js.intro .hero .note,
  .js.intro .hero-stage { opacity: 1; animation: none; }
}

/* ---------------------------------------------------------------- doc pages */

/* Legal and privacy prose. Same system, narrower measure, no hero. */
.doc { padding-block: var(--sp-8) var(--sp-9); }
.doc h1 {
  font-size: var(--t-h2);
  font-variation-settings: 'wght' 700, 'wdth' 78;
  line-height: 1.08;
  letter-spacing: -0.015em;
  text-transform: uppercase;
  margin-bottom: var(--sp-6);
}
.doc h2 {
  font-size: var(--t-h3);
  font-variation-settings: 'wght' 600, 'wdth' 100;
  text-transform: none;
  letter-spacing: -0.01em;
  margin-top: var(--sp-7);
  margin-bottom: var(--sp-3);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--rule-strong);
}
.doc p { color: var(--muted); margin-bottom: var(--sp-4); }
.doc a { color: var(--gold); }
.doc .todo { color: var(--gold); }

/* Smallest screens: shrink again so nothing dominates a real phone. */
@media (max-width: 420px) {
  .hero-media video { max-height: 44svh; }
  /* No narrowing here. `.device` is already `width: min(100%, var(--dev-w))`, so on a phone
     the column caps it at roughly 350px, which is ~0.97 life-size — the app rendered at
     very nearly the size it actually runs at on the reader's own phone. Capping it at
     230px made the one context where the capture can be life-size the blurriest. */
}
