/* ============================================================
   v3-chapters.css — Red Banana v3 "THE OBJECT", CHAPTERS LANE.
   Contract: redesign-2026/RB-V3-DIRECTION-CONTRACT.md §7 (lane
   files). Creative spec: redesign-2026/rb-v3/SIGNATURE-MOMENTS.md,
   Moment 5 (the three photo chapters) and Moment 7 (the last beat,
   #footer-statement). Motion law §5. Companion file: v3-chapters.js
   (injects .chapter-rule and .footer-glow, splits the footer h2,
   drives every scrub/timeline below).

   OWNS: [data-slot="photo-chapters"] .photo-chapter's parallax
   geometry and its injected .chapter-tombstone/.chapter-rule, and
   #footer-statement's ground-darkening/glow/magnetic-button styling.
   case-study and replicas (slots 6/7) carry no rules here — the base
   reveal primitive in v3.css already covers their kicker/h2/paragraph
   copy; see this lane's own report for that judgement call.

   Loads AFTER v3.css (see index.html's <head> hand-off order per
   parts/WIRING.md), so every rule below overrides v3.css per-property
   without !important, exactly as parts/SLOTS.md's "Reserved classes"
   section describes.
   ============================================================ */

/* ---------- Vermilion budget: ONE of this lane's two places ----------
   --chapter-rule is the fourth site-wide --red use, ruled IN by
   contract §8 amendment A2 ("up to four places, exactly those listed
   in SIGNATURE-MOMENTS.md"). The footer primary button's fill is this
   lane's OTHER budgeted place, but it is already declared in v3.css
   (#footer-statement .btn-primary { background: var(--red); }) by the
   INTEGRATOR — not redeclared here, to avoid two lanes owning the
   same cascade declaration. See this lane's report. */
:root {
  --chapter-rule: var(--red);
}

/* ============================================================
   MOMENT 5 — the three photo chapters
   ============================================================ */

/* Explicit height on the chapter itself (v3.css only set it on the
   <img>): the img's height below is percentage-based (118%), which
   needs a concrete ancestor height to resolve against. overflow:
   hidden clips the 18% of image that intentionally spills past the
   frame at rest and across the whole -9%/+9% scrub range. */
.photo-chapter {
  height: 80vh;
  overflow: hidden;
}

.photo-chapter picture {
  position: absolute;
  inset: 0;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* 118% of container height, vertically centred by the flex parent
   above (so the 18% overflow splits 9%/9% top and bottom) — the
   exact sizing SIGNATURE-MOMENTS.md Moment 5 specifies. Do not
   change this figure; see the moment's own "arithmetic that lanes
   get wrong" note. Transform (yPercent -9..+9, scale 1.06..1.00) is
   applied by v3-chapters.js via GSAP scrub, never in this stylesheet,
   so scripts-off and reduced-motion both fall through to "no
   transform" for free. */
.photo-chapter picture img {
  position: relative;
  width: 100%;
  height: 118%;
  object-fit: cover;
  will-change: transform;
}

/* Scripts-off floor, required verbatim by Moment 5's own spec text:
   "the CHAPTERS LANE adds the matching html:not(.js) .photo-chapter
   img { transform: none } block." Belt-and-braces alongside the fact
   that no transform is ever written to this element without JS. */
html:not(.js) .photo-chapter img {
  transform: none;
}

/* The tombstone: rule + line, injected as one wrapper by
   v3-chapters.js (chapter-rule as first child, the existing
   .chapter-line reparented in as the second) so the rule always sits
   immediately above the caption regardless of how many lines the
   caption wraps to — position:absolute with only "bottom" set lets
   the box grow upward with its content instead of guessing an offset
   in pixels. */
.photo-chapter .chapter-tombstone {
  position: absolute;
  left: var(--section-pad-x);
  right: var(--section-pad-x);
  bottom: 40px;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* ---- Tombstone reveal state + transition ----
   FIX (24 Sep, this lane's own defect — see v3-chapters.js's comment
   at the ScrollTrigger.create() call below for the full diagnosis):
   this used to be a gsap.timeline() tweening clip-path/yPercent
   directly, driven by scrollTrigger's toggleActions "play none none
   reverse" — the exact shape v3-core.js's reveal() primitive used to
   use before a confirmed, reproducible bug (fixed there in commit
   9edc52a) where a below-fold trigger's stale cached start/end gets
   crossed twice in quick succession, leaving the SAME tween's
   yPercent-driven transform stuck at a partial value (reproduced here
   too: all three chapter-line captions stuck at yPercent 40's
   equivalent, matrix ty ~91.5px, on repeated fast/reversed scroll).
   Same fix principle: the hidden/visible states below are plain CSS,
   animated by a CSS transition (immune to that GSAP-tween
   interruption); JS only toggles .is-revealed on the article via a
   bare ScrollTrigger.create() with no tween attached. The old
   timeline's 0 / 0.18s / 0.9s stagger between rule-draw, line-rise
   and rule-fade is reproduced below as per-property transition-delay.
   Durations/easing (0.42s / 0.72s / 0.5s, TORQUE.out) are unchanged —
   cubic-bezier(0.16, 1, 0.3, 1) is the exact curve
   cubicBezierEase(0.16, 1, 0.3, 1) in the JS approximates, natively
   supported by CSS so no approximation is needed here. */
.photo-chapter .chapter-rule {
  width: 72px;
  height: 1px;
  background: var(--chapter-rule);
  transform-origin: left center;
  transform: scaleX(0);
  opacity: 1;
  transition: transform 0.42s cubic-bezier(0.16, 1, 0.3, 1) 0s,
    opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.9s;
}

/* .chapter-line already carries v3.css's font/colour/shadow rules;
   only its position changes here now that it lives inside
   .chapter-tombstone instead of being independently bottom:40px. */
.photo-chapter .chapter-line {
  position: static;
  left: auto;
  bottom: auto;
  max-width: 44ch;
}

/* The tombstone's own hidden rest state (clip-path + translateY,
   opacity/filter left at their visible values — this moment never
   does a blur/opacity fade, only a clip/rise). Gated behind
   html.js:not(.reduced-motion), same guard v3.css's own [data-reveal]
   rest-state rule uses, for two reasons: (1) specificity — .chapter-
   line still carries [data-reveal] in the raw markup, so v3.css's
   `html.js:not(.reduced-motion) [data-reveal]` rule (opacity:0,
   blur(5px), translateY(110%)) is still in the cascade; matching its
   html.js:not(.reduced-motion) prefix here brings this rule to equal
   specificity, and this file's later load order (documented at the
   top of this file) wins the tie per-property, overriding opacity/
   filter/transform correctly — an earlier version of this rule
   omitted the prefix, lost that specificity fight, and reintroduced
   the exact stuck-at-hidden-state bug this whole block exists to fix
   (caught by tests/settled-state.test.mjs). (2) correctness — without
   the html.js guard this rule would also clip/hide the caption for
   no-JS visitors, who get none of the below is-revealed toggle to
   ever undo it; gating it the same way v3.css does means the
   no-JS/reduced-motion fallback in v3.css ([data-reveal] forced to
   fully visible) is the only rule that ever touches this element in
   those cases, exactly as before. */
html.js:not(.reduced-motion) .photo-chapter .chapter-line {
  opacity: 1;
  filter: none;
  clip-path: inset(100% 0% 0% 0%);
  transform: translateY(40%);
  transition: clip-path 0.72s cubic-bezier(0.16, 1, 0.3, 1) 0.18s,
    transform 0.72s cubic-bezier(0.16, 1, 0.3, 1) 0.18s;
}

.photo-chapter.is-revealed .chapter-rule {
  transform: scaleX(1);
  opacity: 0.25;
}

html.js:not(.reduced-motion) .photo-chapter.is-revealed .chapter-line {
  clip-path: inset(0% 0% 0% 0%);
  transform: translateY(0);
}

/* Reduced motion: "Rules at full width and 0.25 opacity, drawn by
   CSS, no tween. Lines at rest state, fully visible." Belt-and-braces
   override so this mode never depends on v3-chapters.js reaching (or
   correctly timing) the .is-revealed class toggle — matches v3.css's
   own site-wide reduced-motion pattern of overriding by media query
   directly, not only by the html.reduced-motion class v3-core.js
   sets from matchMedia. */
html.reduced-motion .photo-chapter .chapter-rule {
  transform: scaleX(1);
  opacity: 0.25;
  transition: none;
}
html.reduced-motion .photo-chapter .chapter-line {
  clip-path: none;
  transform: none;
  transition: none;
}
@media (prefers-reduced-motion: reduce) {
  .photo-chapter .chapter-rule {
    transform: scaleX(1) !important;
    opacity: 0.25 !important;
    transition: none !important;
  }
  .photo-chapter .chapter-line {
    clip-path: none !important;
    transform: none !important;
    transition: none !important;
  }
}

/* ============================================================
   MOMENT 7 — the last beat (#footer-statement)
   ============================================================ */

/* Ground-darkening scrub target. #080808 -> #000 is driven by GSAP
   tweening backgroundColor; this starting value just keeps the
   footer visually seamless with the rest of the page (matches
   v3.css's --ground) before the scrub reaches it. */
#footer-statement {
  position: relative;
  background: var(--ground);
  overflow: hidden;
}

/* Soft radial falloff "centred on the h2" — a plain CSS
   radial-gradient (Moment 7: "not an image", 0 network bytes),
   injected by v3-chapters.js as a sibling of .foot-grid so it paints
   behind the copy but above the darkening background. Its opacity is
   the only thing GSAP scrubs (0 -> 0.06); position/size are fixed by
   this stylesheet once JS centres it on the h2's own box. */
#footer-statement .footer-glow {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  opacity: 0;
  z-index: 0;
  background: radial-gradient(
    circle at var(--glow-x, 30%) var(--glow-y, 35%),
    var(--ink) 0%,
    transparent 62%
  );
}

#footer-statement .wrap {
  position: relative;
  z-index: 1;
}

/* SplitType ("lines") wraps each rendered line of the h2 in a
   .line div and this lane adds one inner span per line (see
   v3-chapters.js) so the outer .line can act as the overflow-hidden
   mask while the inner span is the element GSAP actually translates/
   blurs/fades — "each line in its own overflow: hidden mask" per
   Moment 7. Only present once v3-chapters.js has run in normal
   motion; under reduced motion or with JS absent the h2's own
   original text nodes are untouched (see the JS file's guard). */
#footer-statement h2 .line {
  overflow: hidden;
  display: block;
}

#footer-statement h2 .footer-line-inner {
  display: inline-block;
  will-change: transform, filter, opacity;
}

/* Buttons: hidden/offset state before the timeline plays is written
   inline by GSAP (gsap.set), never in this stylesheet, so scripts-off
   and a GSAP-failed-to-load page both leave .cta-row at its ordinary,
   fully visible flow position by construction. */
#footer-statement .cta-row {
  will-change: transform, filter, opacity;
}

/* The one magnetic element on the site (Moment 7: "One magnetic
   element on the entire site, at the one place we want a click").
   v3-chapters.js writes x/y via gsap.set/gsap.to each frame while a
   pointer is over the button and releases with a single tween on
   pointerleave; this rule only declares the transform origin so the
   translate reads as the button itself moving rather than skewing
   around a corner. */
#footer-statement .btn-primary {
  transform-origin: center center;
}
