/* ============================================================
   HEART’S PLENTY — public site
   Design system: "Cut Paper"

   The whole system has one idea: everything on this page is a
   piece of torn paper resting on another piece of torn paper.
   That means warm offset shadows (never black, never blurry),
   layered planes, and grain — and it means NO borders, no
   outlines, no gradients pretending to be light.
   ============================================================ */

:root{
  color-scheme: light dark;
  /* ---- paper planes, lightest to deepest ---- */
  --p-0:   #FFFCF5;   /* the sheet nearest the reader */
  --p-1:   #FDF8EE;   /* page */
  --p-2:   #F6EEDF;   /* raised card */
  --p-3:   #EDE2CE;   /* recessed well */
  --p-4:   #E2D3B8;   /* deepest paper */

  /* ---- water + weather ---- */
  --deep:      #06202D;
  --sea:       #0E3A4F;
  --tide:      #17607E;
  --shallow:   #3E93A8;
  --foam:      #BFE3E2;
  --fog:       #DCE7E7;

  /* ---- saltbox paint ---- */
  --buoy:      #D8452F;
  --buoy-dk:   #A9301F;
  --lamp:      #F2B33D;
  --seaglass:  #6FB7A8;
  --kelp:      #2F6E5C;
  --dory:      #E0763A;

  /* ---- ink ---- */
  --ink:    #14232B;
  --ink-2:  #3E5763;
  --ink-3:  #455B64;   /* darkened twice: #6D8792 failed on --p-1, #5A737D failed on --p-2/--p-3 */

  /* ---- type ---- */
  --display: "Fraunces", "Iowan Old Style", Georgia, serif;
  --body:    "Nunito", ui-rounded, "SF Pro Rounded", "Segoe UI", system-ui, sans-serif;

  /* ---- paper shadow: warm, offset, tight. Paper does not glow. ---- */
  --lift-1: 0 2px 0 rgba(20,35,43,.10), 0 6px 14px -6px rgba(90,64,40,.30);
  --lift-2: 0 4px 0 rgba(20,35,43,.12), 0 16px 30px -14px rgba(90,64,40,.40);
  --lift-3: 0 8px 0 rgba(20,35,43,.14), 0 30px 60px -22px rgba(90,64,40,.45);

  /* ---- surfaces that used to be hard-coded ---- */
  --nav-bg:   rgba(253,248,238,.90);
  --grain-a:  rgba(160,130,95,.045);
  --grain-b:  rgba(160,130,95,.038);
  --grain-c:  rgba(120,100,75,.030);
  --line:     #D9CDB6;
  --edge:     var(--ink);      /* button borders + offset shadow */
  --on-buoy:  #fff;            /* text on the buoy-red field */
  --buoy-text: var(--buoy-dk); /* brand red as TEXT: --buoy is only 4.12:1 on cream */
  --art-shadow: rgba(90,64,40,.28);
  --on-lamp:  #241404;   /* --lamp is a light gold field in BOTH themes; text on it is always dark */
  --on-kelp:  #fff;      /* light theme: --kelp is a dark green field */
  --buoy-field: #C13A26; /* white-on-buoy was 4.36:1, below AA; this is the field ONLY, --buoy itself (brand fill/art/eyebrow) is untouched */
  --on-deep:  #FFFCF5;   /* --deep is dark in both themes by design, so text on it never needs to flip */
  --focus:    var(--buoy);

  --gut: clamp(20px, 5vw, 64px);
  --measure: 62ch;
}

/* ============================================================
   DARK — "the cove at night"
   The cut-paper ART KEEPS ITS OWN COLOURS in both themes. Paper
   does not change colour when you turn the lights down; the table
   under it does. So only the page shell inverts, and the
   illustrations sit on it like cut-outs on a dark table. That is
   also why none of the SVG palette is touched here — re-colouring
   the art would break the locked brand and every printed asset.
   ============================================================ */
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    --p-0:  #143848;
    --p-1:  #08222E;
    --p-2:  #0E2E3C;
    --p-3:  #061B25;
    --p-4:  #04141C;

    --deep: #04141C;
    --fog:  #16303C;

    --ink:   #EDF5F3;
    --ink-2: #B7D0D2;
    --ink-3: #87A6AB;

    /* Lifted so they still clear AA against the dark field. */
    --buoy:    #FF7059;
    --buoy-dk: #FF8E7A;
    --lamp:    #F7C863;
    --kelp:    #74C29C;
    --seaglass:#8ED2C3;

    --lift-1: 0 2px 0 rgba(0,0,0,.5),  0 6px 14px -6px rgba(0,0,0,.6);
    --lift-2: 0 4px 0 rgba(0,0,0,.55), 0 16px 30px -14px rgba(0,0,0,.7);
    --lift-3: 0 8px 0 rgba(0,0,0,.6),  0 30px 60px -22px rgba(0,0,0,.75);

    --nav-bg:  rgba(8,34,46,.90);
    --grain-a: rgba(190,225,225,.030);
    --grain-b: rgba(190,225,225,.024);
    --grain-c: rgba(150,195,200,.020);
    --line:    #23485A;
    --edge:    #04141C;
    --on-buoy: #2A0B05;
    --art-shadow: rgba(0,0,0,.55);
    --buoy-text: var(--buoy);  /* already 6.03:1 on the dark field */
    --on-kelp: #0B1F17;      /* --kelp is lifted to a light mint here, so text flips dark */
    --buoy-field: var(--buoy); /* FF7059 already clears AA for white text, no separate field needed */
    --edge:    var(--ink-2);  /* a near-black edge vanished against dark surfaces; use the light ink-2 as a moonlit outline instead */
    /* NOT --on-buoy here. --on-buoy in dark is #2A0B05, a near-black meant
       as TEXT on the lifted coral field — as a global focus ring it put a
       near-black 3px outline on near-black surfaces everywhere on the site
       except the one place it was actually intended (.cta-red, scoped
       below). Measured before the fix: 1.02:1 to 1.47:1 across p-0..deep,
       against a 3:1 requirement (WCAG 1.4.11). Falling through to the
       inherited --focus: var(--buoy) from :root resolves to this block's
       own --buoy (#FF7059), which clears 3:1 everywhere it is used. */
  }
  :root:not([data-theme="light"]){ color-scheme: dark; }
}

:root[data-theme="dark"]{
  --p-0:  #143848;
  --p-1:  #08222E;
  --p-2:  #0E2E3C;
  --p-3:  #061B25;
  --p-4:  #04141C;

  --deep: #04141C;
  --fog:  #16303C;

  --ink:   #EDF5F3;
  --ink-2: #B7D0D2;
  --ink-3: #87A6AB;

  --buoy:    #FF7059;
  --buoy-dk: #FF8E7A;
  --lamp:    #F7C863;
  --kelp:    #74C29C;
  --seaglass:#8ED2C3;

  --lift-1: 0 2px 0 rgba(0,0,0,.5),  0 6px 14px -6px rgba(0,0,0,.6);
  --lift-2: 0 4px 0 rgba(0,0,0,.55), 0 16px 30px -14px rgba(0,0,0,.7);
  --lift-3: 0 8px 0 rgba(0,0,0,.6),  0 30px 60px -22px rgba(0,0,0,.75);

  --nav-bg:  rgba(8,34,46,.90);
  --grain-a: rgba(190,225,225,.030);
  --grain-b: rgba(190,225,225,.024);
  --grain-c: rgba(150,195,200,.020);
  --line:    #23485A;
  --edge:    #04141C;
  --on-buoy: #2A0B05;
  --art-shadow: rgba(0,0,0,.55);
  --buoy-text: var(--buoy);
  --on-kelp: #0B1F17;
  --buoy-field: var(--buoy);
  --edge:    var(--ink-2);
  /* See the matching comment in the prefers-color-scheme dark block above:
     --on-buoy is the wrong token for a global focus ring in dark mode. */
  color-scheme: dark;
}
:root[data-theme="light"]{ color-scheme: light; }

*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
html{
  -webkit-text-size-adjust:100%;
  /* The nav is position:sticky, so a fragment jump (the skip link, #notify,
     #story-N, #safety, #privacy...) lands the target's top edge exactly
     under it, hiding the first line or two. --nav-h is the same live
     measurement film-hero uses (published by chrome.js), plus a little
     breathing room. WCAG 2.4.11 Focus Not Obscured. */
  scroll-padding-top:calc(var(--nav-h, 74px) + 14px);
}

body{
  font-family:var(--body);
  color:var(--ink);
  background:var(--p-1);
  line-height:1.62;
  font-size:17px;
  overflow-x:hidden;
  /* One page-level grain wash. Deliberately NOT a per-SVG feTurbulence
     filter: those composite on every repaint and are the single most
     expensive thing you can put behind a scroll animation. */
  background-image:
    radial-gradient(circle at 17% 23%, var(--grain-a) 0 1px, transparent 1px),
    radial-gradient(circle at 61% 71%, var(--grain-b) 0 1px, transparent 1px),
    radial-gradient(circle at 83% 41%, var(--grain-c) 0 1px, transparent 1px);
  background-size:37px 37px, 53px 53px, 23px 23px;
}

img,svg{display:block;max-width:100%;}
a{color:inherit;}
button{font:inherit;color:inherit;}

h1,h2,h3,h4{font-family:var(--display);font-weight:700;line-height:1.02;letter-spacing:-.02em;}

::selection{background:var(--lamp);color:var(--on-lamp);}

:where(a,button,input,summary):focus-visible{
  outline:3px solid var(--focus);
  outline-offset:3px;
  border-radius:4px;
}
/* --focus resolves to --buoy everywhere EXCEPT inside a field that IS
   --buoy (the closing CTA, and any future red panel) — there the ring
   would sit at 1:1 against its own background and vanish. Scoped
   overrides read the surface it needs to stand out from, not the
   theme, so a new buoy-coloured section is safe by default. */
.cta-red :where(a,button,input,summary):focus-visible{ outline-color:var(--on-buoy); }

/* ============================================================
   LAYOUT
   ============================================================ */
.wrap{max-width:1240px;margin:0 auto;padding:0 var(--gut);}
.wrap.tight{max-width:880px;}
.wrap.mid{max-width:1060px;}

.stack > * + *{margin-top:1.1em;}

section{position:relative;}

/* Full-height storytelling panel. */
.panel{min-height:100svh;display:grid;align-items:center;padding:12svh 0;}
.panel.short{min-height:auto;padding:clamp(70px,11vw,140px) 0;}

/* ============================================================
   TYPE SCALE
   ============================================================ */
.eyebrow{
  font-family:var(--body);font-size:11.5px;font-weight:800;
  letter-spacing:.2em;text-transform:uppercase;color:var(--buoy-text);
  display:flex;align-items:center;gap:11px;margin-bottom:20px;
}
.eyebrow::before{content:"";width:26px;height:3px;background:currentColor;border-radius:3px;flex:none;}
.eyebrow.center{justify-content:center;}
.eyebrow.on-dark{color:var(--lamp);}

.h-mega{font-size:clamp(38px,5.6vw,80px);letter-spacing:-.03em;line-height:.96;max-width:13ch;}
.h-1{font-size:clamp(34px,5.6vw,74px);}
.h-2{font-size:clamp(28px,3.9vw,50px);}
.h-3{font-size:clamp(21px,2.2vw,28px);letter-spacing:-.01em;}

.lede{font-size:clamp(18px,1.75vw,22px);color:var(--ink-2);max-width:var(--measure);}
.lede.center{margin-inline:auto;text-align:center;}
.small{font-size:14.5px;color:var(--ink-3);}

.emph{color:var(--buoy);font-style:normal;}

/* ============================================================
   BUTTONS — a pressed paper tab
   ============================================================ */
.btn{
  display:inline-flex;align-items:center;gap:9px;
  font-family:var(--body);font-weight:800;font-size:16px;
  text-decoration:none;cursor:pointer;
  padding:14px 26px;border-radius:14px;
  background:var(--p-0);color:var(--ink);
  border:2.5px solid var(--edge);
  box-shadow:0 5px 0 var(--edge);
  transition:transform .13s ease, box-shadow .13s ease;
}
.btn:hover{transform:translateY(-2px);box-shadow:0 7px 0 var(--edge);}
.btn:active{transform:translateY(3px);box-shadow:0 2px 0 var(--edge);}
/* --buoy-field, not --buoy: pure white text on the raw brand red was
   4.36:1, under AA. --buoy itself stays untouched for the eyebrow, the
   art and anywhere it is not paired with on-buoy text. */
.btn.red{background:var(--buoy-field);color:var(--on-buoy);}
.btn.lamp{background:var(--lamp);color:var(--on-lamp);}
.btn.ghost{background:transparent;}
.btn[disabled]{opacity:.55;cursor:not-allowed;transform:none;box-shadow:0 5px 0 var(--edge);}
.btn.on-dark{border-color:var(--p-0);box-shadow:0 5px 0 rgba(0,0,0,.45);}

.textlink{
  font-weight:800;text-decoration:none;
  box-shadow:inset 0 -3px 0 var(--lamp);
  transition:box-shadow .15s ease, color .15s ease;
}
/* The hover fill is --lamp, a light gold in both themes, so the text
   sitting on it must always be dark ink — not `inherit`, which is
   near-white in dark mode and disappears into its own highlight. */
.textlink:hover{box-shadow:inset 0 -14px 0 var(--lamp);color:var(--on-lamp);}

/* ============================================================
   NAV — a strip of paper laid over the page
   ============================================================ */
.nav{
  position:sticky;top:0;z-index:60;
  background:var(--nav-bg);
  backdrop-filter:blur(10px);
  -webkit-backdrop-filter:blur(10px);
  border-bottom:2px solid transparent;
  transition:border-color .25s ease, box-shadow .25s ease;
}
.nav.stuck{border-bottom-color:var(--ink);box-shadow:var(--lift-1);}
.nav-in{display:flex;align-items:center;justify-content:space-between;gap:24px;padding:13px 0;}

.mark{display:flex;align-items:center;gap:12px;text-decoration:none;flex:none;}
.mark svg{width:42px;height:42px;}
.mark b{font-family:var(--display);font-size:22px;line-height:1;letter-spacing:-.02em;display:block;}
.mark small{
  display:block;font-size:9.5px;font-weight:800;letter-spacing:.22em;
  text-transform:uppercase;color:var(--ink-3);margin-top:3px;
}

.nav-links{display:flex;gap:6px;}
.nav-links a{
  text-decoration:none;font-weight:700;font-size:15px;
  padding:9px 14px;border-radius:10px;color:var(--ink-2);
  transition:background .15s ease,color .15s ease;
}
.nav-links a:hover{background:var(--p-3);color:var(--ink);}
.nav-links a[aria-current="page"]{color:var(--ink);background:var(--p-3);}

.nav-toggle{display:none;background:none;border:2.5px solid var(--edge);border-radius:11px;
  padding:8px 13px;font-weight:800;cursor:pointer;}

@media (max-width:940px){
  .nav-links{
    display:none;position:absolute;top:100%;left:0;right:0;
    flex-direction:column;gap:2px;padding:14px var(--gut) 20px;
    background:var(--p-1);border-bottom:2px solid var(--ink);box-shadow:var(--lift-2);
  }
  .nav-links.open{display:flex;}
  .nav-toggle{display:block;}
  .nav .btn{display:none;}
}

/* ============================================================
   THE PAPER STACK — the signature surface
   ============================================================ */
.sheet{
  background:var(--p-2);
  border-radius:22px;
  box-shadow:var(--lift-2);
  padding:clamp(24px,3.2vw,44px);
  position:relative;
}
.sheet.deep{background:var(--p-3);}
.sheet.pale{background:var(--p-0);}

/* A second sheet peeking out behind the first. Pure decoration, and the
   single cheapest way to say "this is paper" without an image. */
.sheet.stacked::before{
  content:"";position:absolute;inset:14px -10px -12px 12px;
  background:var(--p-4);border-radius:22px;z-index:-1;
  transform:rotate(-1.1deg);
}
.sheet.stacked{z-index:0;}

/* ============================================================
   GRIDS
   ============================================================ */
.grid{display:grid;gap:clamp(16px,2vw,26px);}
.g2{grid-template-columns:repeat(2,1fr);}
.g3{grid-template-columns:repeat(3,1fr);}
.g4{grid-template-columns:repeat(4,1fr);}
.split{display:grid;grid-template-columns:1.05fr .95fr;gap:clamp(30px,5vw,72px);align-items:center;}
.split.art-first > :first-child{order:2;}
.split.art-first > :last-child{order:1;}

@media (max-width:1000px){
  .g4{grid-template-columns:repeat(2,1fr);}
  .g3{grid-template-columns:repeat(2,1fr);}
  .split{grid-template-columns:1fr;}
  .split.art-first > :first-child,
  .split.art-first > :last-child{order:0;}
}
@media (max-width:620px){
  .g2,.g3,.g4{grid-template-columns:1fr;}
}

/* ============================================================
   SCROLL REVEAL
   Transform + opacity only — the two properties the compositor
   can animate without touching layout or paint.
   ============================================================ */
/* Content is VISIBLE by default and is only hidden once JS has proved it
   can reveal it again. `.reveal-ready` is set by chrome.js and only when
   IntersectionObserver exists, motion is allowed, and the document is
   actually visible. Hiding first and revealing later means any failure —
   a JS error, a blocked module, a tab opened in the background where
   IntersectionObserver callbacks are never delivered — leaves a blank
   page. This ordering is the difference between a degraded page and no
   page at all. */
.reveal-ready .rv{opacity:0;transform:translateY(26px);}
/* Scoped under .reveal-ready deliberately: `.rv.in` alone has the SAME
   specificity as `.reveal-ready .rv`, so it would win only by source
   order and a future reorder would silently break every reveal. */
.reveal-ready .rv.in{opacity:1;transform:none;
  transition:opacity .7s cubic-bezier(.22,.61,.36,1),
             transform .7s cubic-bezier(.22,.61,.36,1);}
.rv.d1.in{transition-delay:.09s;}
.rv.d2.in{transition-delay:.18s;}
.rv.d3.in{transition-delay:.27s;}
.rv.d4.in{transition-delay:.36s;}

/* Paper slides in from the edge, the way a cut-out is laid down. */
.reveal-ready .rv.tear-l{transform:translateX(-34px) rotate(-2deg);}
.reveal-ready .rv.tear-r{transform:translateX(34px) rotate(2deg);}
.reveal-ready .rv.tear-l.in,.reveal-ready .rv.tear-r.in{transform:none;}

/* ============================================================
   STICKY STAGE — art pins while the copy scrolls past it
   ============================================================ */
.stage{display:grid;grid-template-columns:1fr 1fr;gap:clamp(30px,5vw,80px);align-items:start;}
.stage-pin{position:sticky;top:calc(50svh - 26svh);height:52svh;display:grid;place-items:center;}
/* The art SVGs carry a viewBox but no width/height attribute, so they have
   no intrinsic size. `width:auto` collapses them to 0 — the box must be
   sized here and the viewBox supplies the aspect ratio. */
.stage-pin > *{width:min(100%,420px);}
.stage-pin svg{width:100%;height:auto;filter:drop-shadow(0 18px 26px var(--art-shadow));}
.stage-steps > * + *{margin-top:26svh;}
.stage-step{min-height:44svh;display:grid;align-content:center;}

@media (max-width:1000px){
  .stage{grid-template-columns:1fr;}
  .stage-pin{position:relative;top:auto;height:auto;margin-bottom:26px;}
  .stage-pin svg{max-height:42svh;}
  .stage-steps > * + *{margin-top:8svh;}
  .stage-step{min-height:auto;}
}

/* ============================================================
   DARK PANEL — the cove at dusk
   ============================================================ */
.dark{background:var(--deep);color:var(--foam);}
.dark h1,.dark h2,.dark h3{color:var(--on-deep);}
.dark .lede{color:rgba(191,227,226,.88);}
.dark .sheet{background:rgba(255,255,255,.09);box-shadow:none;border:1px solid rgba(255,255,255,.14);}
.dark .small{color:rgba(191,227,226,.68);}

/* ============================================================
   CARDS
   ============================================================ */
.card{
  background:var(--p-2);border-radius:20px;padding:clamp(20px,2.2vw,28px);
  box-shadow:var(--lift-1);
  transition:transform .18s ease, box-shadow .18s ease;
}
a.card,button.card{display:block;text-align:left;text-decoration:none;border:0;cursor:pointer;width:100%;}
.card:hover{transform:translateY(-4px);box-shadow:var(--lift-2);}
.card h3{margin-bottom:8px;}
.card p{font-size:15.5px;color:var(--ink-2);}

.tag{
  display:inline-block;font-size:11px;font-weight:800;letter-spacing:.14em;
  text-transform:uppercase;padding:6px 12px;border-radius:999px;
  background:var(--lamp);color:var(--on-lamp);
}
.tag.sea{background:var(--tide);color:#fff;}
/* --kelp is lifted to a light mint in dark mode for use AS TEXT
   elsewhere (.rule-list check-mark, word-row accents), so a white label
   on top of it as a FIELD collapses to 2.11:1 there. --on-kelp flips
   with the theme instead of being hard-coded. */
.tag.kelp{background:var(--kelp);color:var(--on-kelp);}

/* ============================================================
   RIBBON — the catchphrase, running
   ============================================================ */
.ribbon{background:var(--buoy);color:var(--on-buoy);overflow:hidden;padding:15px 0;}
.ribbon-track{display:flex;gap:44px;white-space:nowrap;width:max-content;
  animation:march 34s linear infinite;}
.ribbon span{font-family:var(--display);font-size:23px;font-weight:700;opacity:.94;}
.ribbon span:nth-child(even){opacity:.72;font-style:italic;}
@keyframes march{to{transform:translateX(-50%);}}

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

/* The skip link must be able to escape sr-only ON FOCUS, which .sr-only
   alone never does — a keyboard user tabbing to it got nothing visible.
   Kept separate from .sr-only rather than adding a :focus escape to that
   class, because .sr-only is also used for genuinely-always-hidden label
   text that must never appear. */
.skip-link{
  position:absolute;left:-9999px;top:0;z-index:200;
  width:1px;height:1px;overflow:hidden;
}
.skip-link:focus{
  position:fixed;left:16px;top:16px;width:auto;height:auto;overflow:visible;
}
.pot-field{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden;}

.signup{display:flex;gap:12px;flex-wrap:wrap;margin-top:24px;}
.signup.center{justify-content:center;}
.signup input[type=email]{
  font-family:var(--body);font-size:16.5px;font-weight:600;color:var(--ink);
  background:var(--p-0);border:2.5px solid var(--edge);border-radius:14px;
  padding:14px 20px;width:min(350px,100%);box-shadow:0 5px 0 var(--edge);
}
.signup input[type=email]::placeholder{color:var(--ink-3);font-weight:400;}
.signup-status{margin-top:15px;font-weight:800;font-size:15px;min-height:1.3em;color:var(--ink);}
.signup-status.bad{color:var(--buoy-dk);}
/* NOT --lamp here. Measured: lamp-on-buoy is 2.35:1 in light and 1.74:1 in
   dark — an error message a reader who mistyped their email cannot read in
   either theme. --on-buoy solid is 5.38:1 (light) / 6.72:1 (dark) on the
   panel this actually appears on (see .cta-red below), plus underline so
   the "this is an error" signal never depends on colour alone. */
.dark .signup-status.bad,.cta-red .signup-status.bad{color:var(--on-buoy);text-decoration:underline;}
.signup-note{margin-top:16px;max-width:58ch;font-size:12.5px;line-height:1.65;color:var(--ink-3);}
.signup-note.center{margin-inline:auto;text-align:center;}
/* Solid --on-buoy, not opacity-dimmed. Measured: on-buoy at the opacity
   this used to carry (.8 for the note, .92 for .lede below) drops to
   3.31:1 and 3.91:1 in light, 4.84:1 / 6.01:1 in dark — the LIGHT values
   fail AA body text (4.5:1), and there is no alpha between 0 and 1 that
   clears 4.5:1 in light except 1 (solid). Hierarchy comes from size and
   weight now, not from see-through ink. */
.cta-red .signup-note{color:var(--on-buoy);}
/* buoy-field, not raw --buoy: the field token exists exactly because raw
   --buoy under white text is 4.36:1, and every dependent text case here
   (the .92-opacity .lede, in particular) needs the extra margin buoy-field
   provides to still clear AA once its own opacity math is removed. */
.cta-red{background:var(--buoy-field);color:var(--on-buoy);}
.cta-red h2{color:var(--on-buoy);}
.cta-red .lede{color:var(--on-buoy);}
.cta-red .signup input[type=email]{box-shadow:0 5px 0 var(--edge);}

@media (max-width:560px){
  .signup{flex-direction:column;align-items:stretch;}
  .signup input[type=email]{width:100%;}
  .signup .btn{justify-content:center;}
}

/* ============================================================
   FOOTER
   ============================================================ */
footer{background:var(--deep);color:var(--foam);padding:62px 0 40px;font-size:14px;}
.foot-grid{display:grid;grid-template-columns:1.5fr 1fr 1fr;gap:34px;}
footer h5{font-family:var(--body);font-size:10.5px;letter-spacing:.19em;text-transform:uppercase;
  color:var(--lamp);font-weight:800;margin-bottom:13px;}
footer ul{list-style:none;display:grid;gap:8px;}
footer a{text-decoration:none;opacity:.86;}
footer a:hover{opacity:1;text-decoration:underline;}
.foot-mark{display:flex;align-items:center;gap:12px;margin-bottom:15px;}
.foot-mark svg{width:46px;height:46px;}
.foot-mark b{font-family:var(--display);font-size:23px;color:var(--on-deep);}
.foot-fine{margin-top:40px;padding-top:22px;border-top:1px solid rgba(255,252,245,.18);
  font-size:12.5px;line-height:1.7;color:rgba(255,252,245,.68);}
@media (max-width:820px){.foot-grid{grid-template-columns:1fr 1fr;}}
@media (max-width:520px){.foot-grid{grid-template-columns:1fr;}}

/* ============================================================
   REDUCED MOTION — the whole system degrades to stillness.
   Compositions must still read as paper with zero animation.
   ============================================================ */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
    scroll-behavior:auto !important;
  }
  .rv{opacity:1;transform:none;}
  .ribbon-track{animation:none;transform:none;}
  .stage-pin{position:relative;top:auto;height:auto;}
  .stage-steps > * + *{margin-top:6svh;}
  .stage-step{min-height:auto;}
  .panel{min-height:auto;padding:clamp(60px,9vw,110px) 0;}
}

/* ============================================================
   RULE LISTS — the grown-ups page
   ============================================================ */
.rule-list{list-style:none;display:grid;gap:14px;}
.rule-list li{
  background:var(--p-0);border-radius:16px;padding:18px 22px 18px 56px;
  position:relative;box-shadow:var(--lift-1);color:var(--ink-2);
}
.rule-list li::before{
  content:"✓";position:absolute;left:20px;top:17px;
  font-weight:900;font-size:19px;color:var(--kelp);
}
.rule-list.no li::before{content:"✕";color:var(--buoy);}

/* ============================================================
   WORD LIST — the words page
   ============================================================ */
.word-row{
  display:grid;grid-template-columns:minmax(150px,220px) 1fr;
  gap:clamp(16px,3vw,40px);align-items:start;
  padding:26px 0;border-top:2px dashed var(--line);
}
.word-row:first-child{border-top:0;}
.word-w{
  font-family:var(--display);font-size:clamp(26px,3.2vw,40px);
  line-height:1.05;color:var(--buoy);letter-spacing:-.02em;
}
.word-row p{color:var(--ink-2);}
.word-u{
  margin-top:10px;font-style:italic;color:var(--ink-3);
  border-left:3px solid var(--lamp);padding-left:14px;
}
@media (max-width:700px){
  .word-row{grid-template-columns:1fr;gap:8px;}
}

/* ============================================================
   THEME TOGGLE
   ============================================================ */
.theme-btn{
  display:inline-grid;place-items:center;flex:none;
  width:44px;height:44px;border-radius:12px;cursor:pointer;
  background:var(--p-0);border:2.5px solid var(--edge);
  box-shadow:0 4px 0 var(--edge);
  transition:transform .13s ease, box-shadow .13s ease;
}
.theme-btn:hover{transform:translateY(-2px);box-shadow:0 6px 0 var(--edge);}
.theme-btn:active{transform:translateY(2px);box-shadow:0 2px 0 var(--edge);}
.theme-btn svg{width:21px;height:21px;fill:none;stroke:var(--ink);stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round;}
/* Sun in light, moon in dark. One icon is always hidden. */
.theme-btn .moon{display:none;}
:root[data-theme="dark"] .theme-btn .moon{display:block;}
:root[data-theme="dark"] .theme-btn .sun{display:none;}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .theme-btn .moon{display:block;}
  :root:not([data-theme="light"]) .theme-btn .sun{display:none;}
}
@media (max-width:940px){ .nav .theme-btn{display:inline-grid;} }

/* ============================================================
   VIDEO PAUSE CONTROL — WCAG 2.2.2. See mountVideoPauseControl()
   in chrome.js for why this exists on every autoplaying video.
   Styled as a small chip in the same register as the honesty
   caption it sits beside, so it reads as part of the hero rather
   than as a stray media-player widget.
   ============================================================ */
.video-pause-btn{
  position:absolute;z-index:2;
  padding:8px 14px;border-radius:11px;
  background:rgba(4,20,28,.86);
  -webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);
  color:#fff;font-family:var(--body);font-weight:800;font-size:12px;
  letter-spacing:.04em;
  border:0;cursor:pointer;
  transition:transform .13s ease, background .15s ease;
}
.video-pause-btn:hover{transform:translateY(-1px);background:rgba(4,20,28,.95);}
.video-pause-btn:active{transform:translateY(1px);}

/* Placement is per-hero, because the two heroes measure "top" from
   different origins: .film-hero is pulled up behind the sticky nav (see
   its own margin-top comment), .teaser-hero is a normal-flow section that
   already starts below it. */
.film-hero .video-pause-btn{top:calc(var(--nav-h, 74px) + 20px);right:var(--gut);}
.teaser-hero .video-pause-btn{top:20px;right:var(--gut);}
@media (max-width:820px){
  .film-hero .video-pause-btn{top:calc(var(--nav-h, 74px) + 14px);right:16px;}
}

/* ============================================================
   THE FILM HERO — full-bleed teaser, with the paper world
   resting on top of it.

   WHY THE WORDS SIT ON PAPER RATHER THAN ON THE FILM.
   Measured, not guessed. A luminance histogram of every frame of
   hero-film.mp4 (259,200 samples, 50 frames x 96x54) says the
   film is bright nearly everywhere:

       p50 Y=105   p75 Y=173   p90 Y=210   p95 Y=221   p99 Y=235

   and the per-band p99 never drops below Y=209 — there is no dark
   corner anywhere in the frame to hide text in, in any second of
   the 25. For cream #FFFCF5 to clear 4.5:1 against the worst case
   a black scrim would have to run at alpha 0.52, which is a
   different, uglier film. The hero therefore uses translucent paper:
   52% in light mode so the footage stays visible, and 82% in dark mode
   so the lighter lede still clears body-text contrast.

   So: the scrim here is ATMOSPHERE, not a contrast device. Nothing
   that has to be read sits on the film unbacked. Run
   `node contrast-check.mjs` — it proves the composited-over-film cases
   against those measured luminances, and it fails the build if this
   ever stops holding.
   ============================================================ */
.film-hero{
  position:relative;
  /* The film runs from the very top of the page, BEHIND the nav, so the
     shot is genuinely edge to edge — the nav then reads as a strip of
     paper laid over it, which is the same metaphor as everything else
     here. Pulling up by the nav's height and padding it back means the
     hero is exactly one viewport tall INCLUDING the nav, so nothing at
     the bottom of it (the caption, the cue) lands under the fold.
     --nav-h is measured and kept current by chrome.js; the 74px fallback
     only has to hold for the instant before that runs. */
  margin-top:calc(-1 * var(--nav-h, 74px));
  padding-top:var(--nav-h, 74px);
  /* Clearance for the torn edge, which is absolutely positioned at the
     bottom on a higher layer. Without this the tear paints over the last
     line of the honesty caption — measured: tear top 765, caption bottom
     806. Derived from --tear-h so the two can never drift apart. */
  --tear-h:clamp(34px,4.4vw,62px);
  padding-bottom:calc(var(--tear-h) + 16px);
  min-height:100svh;
  display:grid;
  align-items:center;
  overflow:hidden;
  /* --deep, not paper: the section paints dark before the poster
     decodes, so the hero never flashes cream-then-dark. */
  background:var(--deep);
  isolation:isolate;
}

/* The film itself. `poster` is always set, so this element is never
   empty: with JS off, on Save-Data, or before the file arrives, the
   poster frame IS the hero and the composition is unchanged. The
   poster is deliberately the LCP element — 84KB of JPEG rather than
   2.1MB of video. */
.film-hero__media{
  position:absolute;inset:0;z-index:0;
  width:100%;height:100%;
  object-fit:cover;
  /* object-position favours the upper-middle: the film's subject (the
     cove, then the window, then the pot) lives there, and the bottom
     of the frame is the part a short viewport should lose. */
  object-position:50% 42%;
}

.film-hero__scrim{
  position:absolute;inset:0;z-index:1;pointer-events:none;
  background:
    radial-gradient(126% 96% at 64% 34%,
      rgba(6,32,45,0) 0%, rgba(6,32,45,.18) 54%, rgba(6,32,45,.58) 100%),
    linear-gradient(to bottom,
      rgba(6,32,45,.44) 0%, rgba(6,32,45,.06) 22%,
      rgba(6,32,45,.06) 54%, rgba(6,32,45,.60) 100%);
}

.film-hero__inner{position:relative;z-index:2;width:100%;}

/* The card: a translucent sheet of paper set down on the film. The
   footage should remain visible through the hero instead of losing its
   main subject behind an opaque slab. */
.hero-card{
  position:relative;
  background:color-mix(in srgb,var(--p-0) 52%,transparent);
  -webkit-backdrop-filter:blur(2px) saturate(.96);
  backdrop-filter:blur(2px) saturate(.96);
  border-radius:26px;
  padding:clamp(24px,2.9vw,44px);
  box-shadow:var(--lift-3);
  max-width:min(43rem,100%);
}
/* Same trick as .sheet.stacked, kept translucent so it does not turn the
   video underneath the card into another opaque block. */
.hero-card::before{
  content:"";position:absolute;inset:16px -13px -15px 14px;
  background:color-mix(in srgb,var(--p-3) 18%,transparent);border-radius:26px;z-index:-1;
  transform:rotate(-1.3deg);
  box-shadow:0 22px 50px -26px rgba(0,0,0,.65);
}

/* Dark mode keeps a little more paper in the mix so the light lede remains
   readable over the brightest sampled frame while the video still shows. */
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .hero-card{
    background:color-mix(in srgb,var(--p-0) 88%,transparent);
  }
}
:root[data-theme="dark"] .hero-card{
  background:color-mix(in srgb,var(--p-0) 88%,transparent);
}

/* Kettle, standing at the corner of the card in FLAT finish. FLAT is
   not a shortcut here — it is the rule: PAPER for print and the
   illustrated site, FLAT for the video register, and this cat is
   standing on a film. FLAT also emits no feTurbulence at all, so he
   costs nothing next to a playing video.

   He stands on the card's bottom-right corner, half on the paper and
   half off it onto the film — which is the depth cue that sells the
   card as a real object sitting on a real image. Deliberately NOT
   above the card's top edge: there he collides with the sticky nav
   and gets clipped by the hero's overflow:hidden. */
.hero-kettle{
  position:absolute;
  right:clamp(-24px,-1.8vw,-12px);
  bottom:clamp(-20px,-1.5vw,-10px);
  width:clamp(92px,10.5vw,150px);
  z-index:1;
  filter:drop-shadow(0 14px 20px rgba(0,0,0,.38));
  pointer-events:none;
}

/* The honesty caption. It sits ON the film, so it gets its own
   backing rather than trusting the scrim — see the note above. */
.film-hero__caption{
  position:relative;z-index:2;
  margin-top:clamp(16px,2.2vw,26px);
  display:inline-flex;flex-wrap:wrap;gap:6px;
  padding:11px 17px;border-radius:14px;
  background:rgba(6,32,45,.86);
  -webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);
  font-size:12.5px;line-height:1.6;color:rgba(255,252,245,.94);
  max-width:62ch;
}
.film-hero__caption a{color:#fff;box-shadow:inset 0 -2px 0 var(--lamp);
  text-decoration:none;font-weight:800;}

/* Scroll cue. Purely decorative, and its motion is the first thing
   prefers-reduced-motion turns off. */
.scroll-cue{
  /* Sits above the torn edge, not under it — same clearance reasoning as
     .film-hero's padding-bottom. */
  position:absolute;left:50%;bottom:calc(var(--tear-h) + 12px);z-index:2;
  transform:translateX(-50%);
  display:flex;flex-direction:column;align-items:center;gap:8px;
  color:rgba(255,252,245,.82);
  font-size:10.5px;font-weight:800;letter-spacing:.2em;text-transform:uppercase;
  text-shadow:0 2px 10px rgba(6,32,45,.9);
}
.scroll-cue span{
  display:block;width:1.5px;height:34px;border-radius:2px;
  background:linear-gradient(to bottom,rgba(255,252,245,.9),rgba(255,252,245,0));
  animation:cue 2.4s ease-in-out infinite;
  transform-origin:top center;
}
@keyframes cue{
  0%,100%{transform:scaleY(.35);opacity:.45;}
  50%    {transform:scaleY(1);opacity:1;}
}

/* The torn paper edge where the film ends and the paper world
   begins. preserveAspectRatio="none" so one path stretches to any
   width without a second asset. */
.film-hero__tear{
  position:absolute;left:0;right:0;bottom:-1px;z-index:3;
  width:100%;height:clamp(34px,4.4vw,62px);
  display:block;pointer-events:none;
  fill:var(--p-1);
}

@media (max-width:820px){
  .film-hero{min-height:auto;padding:clamp(96px,22vh,150px) 0 clamp(84px,14vh,120px);}
  .hero-kettle{width:clamp(78px,17vw,104px);right:4px;}
  .scroll-cue{display:none;}
}

@media (prefers-reduced-motion: reduce){
  .scroll-cue span{animation:none;transform:scaleY(1);opacity:.8;}
}

/* ============================================================
   FILM CARDS — the teasers, given a place on the illustrated
   site without pretending to be the illustrated site.

   preload="none" is deliberate: three 720p files is several
   megabytes a parent never asked for. Nothing but the poster
   frame is fetched until somebody presses play.
   ============================================================ */
.film-grid{display:grid;gap:clamp(20px,2.6vw,32px);grid-template-columns:repeat(2,1fr);}
.film{
  background:var(--p-2);border-radius:20px;overflow:hidden;
  box-shadow:var(--lift-2);display:flex;flex-direction:column;
}
.film video{
  width:100%;aspect-ratio:16/9;height:auto;
  object-fit:cover;background:var(--deep);border-radius:3px;
  /* The one-look rule (PHASE-1-PLAN.md): the painterly register never
     appears unframed. A cut-paper mat separates the footage from the
     page, so the AI-atmosphere study reads as weather pinned inside the
     paper world rather than as the page's own art. --p-0 is a theme
     token, so the mat stays paper in both themes. */
  border:clamp(9px,1.1vw,14px) solid var(--p-0);
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.18);
}
.film-body{padding:clamp(18px,2.2vw,26px);display:flex;flex-direction:column;gap:10px;}
.film-body p{color:var(--ink-2);font-size:15.5px;}
.film-body .textlink{margin-top:2px;}
@media (max-width:820px){ .film-grid{grid-template-columns:1fr;} }

/* ============================================================
   TEASER HERO — a deliberately different register: full-bleed
   video, not the illustrated cut-paper system.

   MEASURED, not guessed (matching the film-hero methodology): both
   teaser-book-01.mp4 and teaser-book-02.mp4 hit their brightest, most
   washed-out fog at the exact band this copy used to sit in — p99
   luminance of Y=240 and Y=248 respectively, in EVERY vertical band,
   at some point across their 25 seconds. The gradient scrim this used
   to be (peaking at .15 alpha exactly where the text sits) produced a
   real, measured 1.01:1-1.49:1 on those frames — invisible, not just
   under AA. There is no scrim gradient value that fixes this and also
   reads as atmosphere; the fix is the same one the home hero uses:
   an opaque chip under the text, proven against both videos' worst
   measured case (Y=248 -> cream 10.93:1, coral 6.4:1 at alpha .86).
   ============================================================ */
.teaser-hero{
  position:relative;min-height:100svh;
  display:grid;place-items:center;
  overflow:hidden;background:var(--deep);
}
.teaser-video{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;z-index:0;
}
/* Atmosphere only now, not a contrast device — same division of labour
   as .film-hero__scrim. It can be as light as the footage wants. */
.teaser-scrim{
  position:absolute;inset:0;z-index:1;
  background:
    linear-gradient(to bottom, rgba(4,20,28,.42) 0%, rgba(4,20,28,.06) 30%,
      rgba(4,20,28,.06) 60%, rgba(4,20,28,.5) 100%);
}
.teaser-copy{
  position:relative;z-index:2;text-align:center;color:var(--on-deep);
  padding:clamp(26px,4vw,46px) clamp(22px,4.5vw,52px);
  max-width:min(52rem,92vw);
  border-radius:26px;
  /* The opaque chip. .86 proven above against BOTH teasers' worst case;
     the film-hero caption uses the same value for the same reason. */
  background:rgba(4,20,28,.86);
  -webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);
}
.teaser-copy .eyebrow{justify-content:center;}
.teaser-copy .h-1{margin-inline:auto;color:var(--on-deep);}
.teaser-copy .emph{color:#FFB199;} /* proven 6.4:1+ on the chip at both videos' worst case */

/* Absolute, not in the grid flow (same as before) — .teaser-hero is a
   single-cell grid, so an in-flow sibling of .teaser-copy would just
   stack on top of it rather than sit below. Now a centered CHIP rather
   than a full-width band: the old band's edges sat on unguarded video at
   full width, and only the text itself needs the guarantee. */
.teaser-caption{
  position:absolute;left:50%;bottom:clamp(20px,3.2vw,34px);z-index:2;
  transform:translateX(-50%);
  width:max-content;max-width:min(64ch,88vw);
  padding:11px 17px;border-radius:14px;
  background:rgba(4,20,28,.86);
  -webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);
  text-align:center;
  font-size:13px;line-height:1.6;color:rgba(255,252,245,.96);
}
.teaser-caption a{color:#fff;box-shadow:inset 0 -2px 0 var(--lamp);text-decoration:none;font-weight:800;}

@media (prefers-reduced-motion: reduce){
  /* The video itself keeps playing (muted, looped, decorative, no motion
     the reader didn't ask to see move fast) but autoplay is not forced
     from JS — see teaser.js. This rule only guards the scrim/copy, which
     have no motion of their own regardless. */
  .teaser-hero{min-height:auto;padding:14svh 0;}
}
