/* ─── BRAND TOKENS ────────────────────────────────────────── */
/* Source of truth: brand guide/brand guide.png. Do not change values
   without updating the "Brand Guide — Source of Truth" section in
   CLAUDE.md first. */
:root {
  --color-ink-navy:    #1E2749;
  --color-parchment:   #ECE3D0;
  --color-bone-black:  #1F1B18;
  --color-oxide-teal:  #2BA7B8;
  --color-ochre-gold:  #DBA83C;
}

/* ─── FONTS ──────────────────────────────────────────────── */
/* Type cohesion pass 2026-06-04 (ref: claude feedback/image_103.png).
   FOUR voices, each with ONE job — do not mix roles:
     Bodoni Moda  — display headlines, names, pull-quotes
     Spectral     — body paragraphs (book serif)
     DM Mono      — label caps: kickers' old role, nav, links, buttons, tags
     Caveat       — handwritten accent: kickers, annotations (interim pick,
                    swap the @font-face src if Daniel sources a better hand)
   Inter remains for form UI + the hero subtitle (kept sans per Daniel).
   Manrope is RETIRED from production (faces kept on disk only).
   All faces self-hosted from assets/fonts/ — never network-loaded. */
@font-face { font-family: 'Bodoni Moda'; font-style: normal; font-weight: 100 900; font-display: swap; src: url('../assets/fonts/bodoni-moda-normal.woff2') format('woff2'); }
@font-face { font-family: 'Bodoni Moda'; font-style: italic; font-weight: 100 900; font-display: swap; src: url('../assets/fonts/bodoni-moda-italic.woff2') format('woff2'); }
@font-face { font-family: 'Manrope'; font-style: normal; font-weight: 200 800; font-display: swap; src: url('../assets/fonts/manrope-variable.woff2') format('woff2'); }

/* Supplementary self-hosted faces (confirmed present in assets/fonts/) */
@font-face { font-family: 'DM Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url('../assets/fonts/dm-mono-400.woff2') format('woff2'); }
@font-face { font-family: 'DM Mono'; font-style: normal; font-weight: 500; font-display: swap; src: url('../assets/fonts/dm-mono-500.woff2') format('woff2'); }
@font-face { font-family: 'Special Elite'; font-style: normal; font-weight: 400; font-display: swap; src: url('../assets/fonts/special-elite-400.woff2') format('woff2'); }
/* Courier Prime — cleaner typewriter than Special Elite, far more legible at small
   sizes (n_2txr1sh / n_fpox8b0). Used for small typewriter body text + the nav links
   where Special Elite got muddy; the hero HEADLINE keeps Special Elite. */
@font-face { font-family: 'Courier Prime'; font-style: normal; font-weight: 400; font-display: swap; src: url('../assets/fonts/courier-prime-400.woff2') format('woff2'); }
@font-face { font-family: 'Rubik Mono One'; font-style: normal; font-weight: 400; font-display: swap; src: url('../assets/fonts/rubik-mono-one-400.woff2') format('woff2'); }
@font-face { font-family: 'Roboto Condensed'; font-style: normal; font-weight: 800; font-display: swap; src: url('../assets/fonts/roboto-condensed-800.woff2') format('woff2'); }
@font-face { font-family: 'Spectral'; font-style: normal; font-weight: 300; font-display: swap; src: url('../assets/fonts/spectral-300.woff2') format('woff2'); }
@font-face { font-family: 'Spectral'; font-style: normal; font-weight: 400; font-display: swap; src: url('../assets/fonts/spectral-400.woff2') format('woff2'); }
@font-face { font-family: 'Spectral'; font-style: italic; font-weight: 400; font-display: swap; src: url('../assets/fonts/spectral-400-italic.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url('../assets/fonts/inter-400.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap; src: url('../assets/fonts/inter-500.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap; src: url('../assets/fonts/inter-600.woff2') format('woff2'); }
@font-face { font-family: 'Caveat'; font-style: normal; font-weight: 400 700; font-display: swap; src: url('../assets/fonts/caveat-variable.woff2') format('woff2'); }
/* Corinthia — formal calligraphy script (Google Fonts, self-hosted). Footer gold tagline. */
@font-face { font-family: 'Corinthia'; font-style: normal; font-weight: 400; font-display: swap; src: url('../assets/fonts/corinthia-400.woff2') format('woff2'); }
@font-face { font-family: 'Corinthia'; font-style: normal; font-weight: 700; font-display: swap; src: url('../assets/fonts/corinthia-700.woff2') format('woff2'); }
/* Hand-script candidates self-hosted for the hero headline emphasis. UNUSED since
   the v3 type pass (2026-06-15) moved "shut down," to Caveat (above); kept on disk
   as A/B alternates — point .emph--mark's font-family here to compare. */
@font-face { font-family: 'Sacramento'; font-style: normal; font-weight: 400; font-display: swap; src: url('../assets/fonts/sacramento-400.woff2') format('woff2'); }
@font-face { font-family: 'La Belle Aurore'; font-style: normal; font-weight: 400; font-display: swap; src: url('../assets/fonts/la-belle-aurore-400.woff2') format('woff2'); }

/* ─── RESET & BASE ────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: auto; /* Lenis handles smooth scroll */
  /* Clip decorative hero-collage tiles that bleed a few px past the viewport
     edge at some widths — they were creating a horizontal scrollbar (the "bar
     under the footer"). overflow-x:clip is x-only and does NOT create a scroll
     container, so Lenis vertical scroll + fixed ScrollTrigger pins are unaffected. */
  overflow-x: clip;
}

body {
  /* Flat parchment. The paper TEXTURE now lives on .paper-ground, a fixed
     multiply layer ABOVE the ink canvas, so ink bleeds up through the grain
     instead of being covered by it. Keeping body a solid colour means
     un-inked areas don't darken through two stacked paper layers. */
  background-color: #ECE3D0;
  cursor: none; /* replaced by custom cursor */
}

/* The UA (links) and the Tailwind CDN preflight (buttons — injected at
   runtime AFTER this sheet, so it wins order ties) both paint the NATIVE
   pointer over interactives, which put a second hand next to the manicule
   on hover (Daniel). The body- prefix outranks the preflight's bare element
   selectors. The manicule's 12-o'clock turn is the hover signal now. */
body a, body button, body input, body textarea, body select, body label,
body [role="button"] {
  cursor: none;
}

/* Reusable paper-surface treatment for full-bleed sections. Matches the body
   stack so hero and downstream sections share one continuous paper feel. */
.paper-surface {
  background-color: #ECE3D0;
  background-image:
    linear-gradient(rgba(236, 227, 208, 0.45), rgba(236, 227, 208, 0.45)),
    url('../assets/img/textures/paper/paper texture 1.webp');
  background-size: auto, 400px;
  background-blend-mode: normal, multiply;
}

/* Reusable kraft-cardboard surface. Used by nav, footer, and the lead-magnet
   section. Same multi-layer technique as .paper-surface but with a kraft
   base tone and a fibrous paper texture. */
.kraft-surface {
  position: relative;
  background-color: #C9B48C;
  background-image:
    linear-gradient(rgba(201, 180, 140, 0.45), rgba(201, 180, 140, 0.45)),
    url('/assets/img/textures/paper/paper-texture-3.webp');
  background-size: cover, cover;
  background-position: center center;
  background-blend-mode: multiply, normal;
}

/* Hide custom cursor on touch devices */
@media (pointer: coarse) {
  body, body a, body button, body input, body textarea, body select,
  body label, body [role="button"] { cursor: auto; }
  #cursor-hand { display: none; }
}

/* ─── SUMINAGASHI CANVAS ─────────────────────────────────── */
/* z-index:0 places the canvas at the bottom of the root stacking context.
   Subsequent sections (z-[5]) cover it as they scroll up over the pinned hero,
   so the atmospheric ink layer is anchored to the hero experience.            */
#sumi {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
}

/* ─── CUSTOM CURSOR ──────────────────────────────────────── */
/* Vintage manicule hand (Daniel's cursor/cursor 1.png, 2026-06-05 — replaced
   the ring+dot ink cursor). The wrapper is moved per-frame by js/main.js;
   the inner img carries the hover scale so the CSS transition isn't stomped
   by the JS transform writes. Fingertip hotspot: the tip sits at 21% across,
   ~top edge of the art (alpha-measured) — main.js offsets by that amount and
   the transform-origin below makes the hover scale grow AROUND the tip. */
#cursor-hand {
  position: fixed;
  top: 0;
  left: 0;
  width: 30px;
  pointer-events: none;
  z-index: 9999;
  will-change: transform;
  opacity: 0; /* revealed by main.js on first mousemove — no dead-centre hand on load */
  transition: opacity 0.2s ease;
}

#cursor-hand img {
  display: block;
  width: 100%;
  height: auto;
  transform-origin: 21% 1%;
  /* Rest state: leaned like a standard arrow cursor (~22° — Daniel: 45°
     leaned over too much) and smaller; the hover turn-and-grow below is
     the interactive signal. Rotation pivots on the fingertip so the tip
     never leaves the pointer. Transition kept FAST (0.1s) — Daniel wanted
     near-instant pop; a 0.25s turn read too smooth. */
  transform: rotate(-22deg) scale(0.78);
  transition: transform 0.1s ease;
}

/* Hover state — the hand turns upright to 12 o'clock and takes full size */
body.cursor-hover #cursor-hand img {
  transform: rotate(0deg) scale(1);
}

/* ─── NAV ────────────────────────────────────────────────── */
#nav {
  transition: opacity 0.45s ease;
}

/* ─── MASTHEAD (teal paper, torn lower edge) ─────────────── */
/* Replaces the kraft nav strip (2026-06-16). A teal-paper band that tears into
   the hero parchment along its bottom edge (same wavy-mask idea as the section
   seams, one-sided). Deliberately overrides three locked roles: nav surface
   (was kraft), nav-label voice (DM Mono only → links now Special Elite), and
   Special-Elite-is-hero-only. Logged in docs/build-state.md. */
/* GSAP drives the drop-in entrance through --nav-y (a length), NOT the transform
   property, so CSS keeps ownership of `transform` and can COMPOSE the entrance
   translate with the page-scale below. Animating the transform property directly
   (the old y:-150→0) would clobber the scale (Learning #18/#23). */
@property --nav-y { syntax: '<length>'; initial-value: 0px; inherits: false; }

.masthead {
  /* Band sits 15px higher (-top-[15px] in HTML) so it cuts less of the page; this padding
     pushes the CONTENT back down 15px (top 16→31) without growing the box (bottom 20→5),
     so the raised band still fully backs the nav. ~5px of paper under the CTA before the tear. */
  padding: 31px 32px 5px;
  /* NO background here (Daniel 2026-06-17). The torn paper (.masthead-torn-wrap)
     spans the whole header and is the ONLY header surface. A second teal bg on
     this box was peeking out below the torn paper's solid mask zone as a faint
     "old masthead" line — removed. No box-shadow either: the torn wrapper carries
     the single drop-shadow. */
  /* Scale the whole bar down with the page on narrow windows (Daniel 2026-06-30). The
     masthead lives OUTSIDE the uniform-scale .hero-stage, so without this it stays full
     size while the body shrinks — a towering header, and the flex row crushes the CTA
     stamp. Scale it as one unit like the stage, FLOORED at 0.5 (stays legible, e.g. on a
     phone) and CAPPED at 1.0 (never grows past the desktop look, incl. 5K). width:100%/scale
     pre-inflates the box so the full-bleed torn band still spans the viewport after the
     transform; transform composes the GSAP entrance translate (--nav-y) with the scale. */
  --bar-scale: clamp(0.5, var(--hero-scale, 1), 1);
  transform-origin: top left;
  transform: translateY(var(--nav-y, 0px)) scale(var(--bar-scale));
  width: calc(100% / var(--bar-scale));
  right: auto;                  /* override Tailwind right-0 so the width override drives the box */
}
@media (min-width: 1024px) { .masthead { padding-left: 48px; padding-right: 48px; } }

.masthead-inner { position: relative; z-index: 2; } /* content above the torn strip */

/* Identity (left) — name as a small label, role + registration beneath. No
   second logo; the hero wordmark carries the big name. */
.masthead-id { display: inline-flex; flex-direction: column; gap: 5px; line-height: 1; }
.masthead-name {
  font-family: 'DM Mono', monospace;
  font-size: 14px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: #ECE3D0;
}
.masthead-role {
  font-family: 'DM Mono', monospace;
  font-size: 13px;            /* up from 10px — Daniel wanted the credential bigger */
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(236, 227, 208, 0.78);  /* a touch brighter so it reads at the larger size */
}

/* Nav cluster (right) */
.masthead-nav { display: flex; align-items: center; gap: 26px; }
@media (min-width: 1024px) { .masthead-nav { gap: 34px; } }

/* Text links — Courier Prime typewriter (was Special Elite; Daniel wanted the
   cleaner, more legible typewriter here too), sized up hard (was 10px DM Mono) */
.masthead-link {
  font-family: 'Courier Prime', 'Courier New', monospace;
  font-size: 19px;
  letter-spacing: 0.01em;
  color: rgba(236, 227, 208, 0.92);
  transition: color 0.25s ease;
}
.masthead-link:hover { color: #ECE3D0; }

/* CTA — gold paper button, much larger. Hover changes background + shadow only
   (NOT transform) so it composes with the GSAP y-nudge on #nav anchors. */
.masthead-cta {
  font-family: 'Special Elite', 'Courier New', monospace;
  font-size: 18px;
  letter-spacing: 0.02em;
  color: #1F1B18;
  background: #DBA83C;
  padding: 11px 22px;
  box-shadow: 0 2px 0 rgba(31, 27, 24, 0.22);
  transition: background-color 0.25s ease, box-shadow 0.25s ease;
  flex-shrink: 0;               /* never let the flex row crush the CTA stamp (it has no intrinsic min) */
  /* The baked rest scale() (see the --img variant's inline style, e.g. scale(1.373))
     grows from the box's CENTRE by default, so it visually bleeds past its own
     flex-allocated width on BOTH sides. As the last flex item in the nav row that's
     invisible at wide widths (plenty of slack past it) but bleeds past the true
     viewport edge once the row compresses enough — the button "peeking over the
     right edge" the further you scale down (Daniel, 2026-07-10). Right-anchoring the
     origin makes it grow only to the LEFT, into the row's own space, so its right
     edge always lands where flex layout actually put it. */
  transform-origin: right center;
}
.masthead-cta:hover {
  background: #E8B94E;
  box-shadow: 0 4px 12px -3px rgba(31, 27, 24, 0.45);
}

/* Image-stamp variant (join-waitlist stamp, note n_x72gilo) — strips the gold
   paper button so the stamp art stands alone. Stays a .masthead-cta so the nav
   drop-in entrance + flex layout still include it; only the box styling is reset. */
/* NO will-change / backface-visibility here (removed 2026-07-10). This button
   carries a baked rest scale() (1.373). Promoting it to a GPU layer via
   will-change/backface rasterizes the texture at 1x CSS px, then the compositor
   upscales it to the display's device pixels AND the baked scale — so on any
   high-DPI screen it renders blurry at rest and only snapped crisp mid-hover
   (when the scale tween forced a re-raster). Confirmed via a 2x-DPI A/B. The
   #88/#89/#92 "keep raster consistent" reasoning was wrong for baked-scale image
   buttons: leaving them un-promoted lets the browser rasterize at full device
   resolution, crisp at rest and through the GSAP hover tween alike. */
.masthead-cta--img { background: none; padding: 0; border-radius: 0; box-shadow: none; }
.masthead-cta--img:hover { background: none; box-shadow: none; }
/* n_cjen1us: lift the torn-paper button off the masthead with a contact + soft
   cast (same shadow vocabulary as the lead-magnet tiles). */
/* height was a flat 52px — a LAYOUT box, unlike the button's own scale() transform,
   which is paint-only and can't fix overflow (a transform-origin:right scale never
   moves the right edge, at any scale value). Once --bar-scale floors at 0.5 this flat
   height stopped shrinking while the row around it kept narrowing, so the button
   spilled past the viewport edge. Same /--bar-scale cancel-and-remultiply as
   .masthead-social img, added the same day for the same floor-crowding reason
   (Daniel, 2026-07-10): keeps this box shrinking past the floor instead of stalling. */
.masthead-cta--img img {
  height: calc(52px * var(--hero-scale, 1) / var(--bar-scale, 1));
  width: auto;
  display: block;
  transition: filter 0.2s ease;
  filter:
    drop-shadow(1px 3px 3px rgba(0, 0, 0, 0.38))
    drop-shadow(2px 6px 10px rgba(31, 27, 24, 0.26));
}
.masthead-cta--img:hover img {
  filter:
    brightness(1.06)
    drop-shadow(1px 4px 4px rgba(0, 0, 0, 0.42))
    drop-shadow(2px 7px 12px rgba(31, 27, 24, 0.30));
}

/* Torn lower edge — the SAME baked torn-paper strip as the footer, flipped vertically
   (masthead-strip-v7.webp = footer-strip-v7.webp run through sharp .flip()) so the tear
   hangs DOWN into the hero instead of UP into the workshops. Top + bottom of the page are
   now matching dark-teal torn BOOKENDS (Daniel 2026-06-30). The strip carries its own
   straight-alpha tear, so there is NO mask — the old teal-paint + band-bottom-raw-mask
   technique (and --tear-scale) is retired.
   Structure unchanged: the WRAPPER carries geometry + the (unmasked) drop-shadow; the child
   .masthead-torn paints the strip. The shadow lives on the wrapper so it traces the strip's
   own alpha tear (a filter on a masked child is clipped by its mask, Learning #48) and because
   a bare filter-wrapper with no size would collapse the absolute child to width 0. */
.masthead-torn-wrap {
  position: absolute;
  z-index: 1; /* behind .masthead-inner — strip sits under the text, fibres hang below */
  left: 0; right: 0;
  top: 0;
  /* Show the WHOLE strip at its natural proportions — NO crop (Daniel 2026-06-30: the footer is
     deliberately cropped shorter, but the header should be the full-size strip). aspect-ratio +
     full width auto-heights the band to the art's true height, so the header sits a touch taller
     than the footer and the body + tear read full-size. Width-coupled like the footer (.footer-strip),
     so both scale together. The band overhangs the nav content (z-1 behind it) into the hero. */
  aspect-ratio: 6230 / 327;
  pointer-events: none;
  /* Sharp, close drop shadow on the torn fibres — IDENTICAL to the footer's cast (same strip
     art now; css/style.css .footer-torn-wrap), only y is positive because this tear hangs
     DOWN. Blur 1px = sharp (Daniel wanted it sharper). On the wrapper, not the painted child,
     so it follows the strip's own alpha tear. */
  filter: drop-shadow(3px 6px 4px rgba(0, 0, 0, 0.85));
}
.masthead-torn {
  position: absolute;
  inset: 0;                  /* fill the wrapper (which carries the real geometry) */
  /* NO background-color: the strip's torn edge is TRANSPARENT below the fibres, so any solid fill
     here shows THROUGH that alpha as a teal block under the tear (the "dead space" Daniel saw).
     The box == the art aspect, so the strip already fills it exactly — no gap to insure against.
     Matches the footer (.footer-torn also paints no fill behind its tear). */
  background-image: url('../assets/img/textures/paper/masthead-strip-v8b.webp');
  /* Box == art aspect (.masthead-torn-wrap aspect-ratio), so 100% 100% fills it with NO crop and
     NO distortion — the FULL strip shows (vs the footer, which crops). Same width-scale as the
     footer's tear, just uncropped. */
  background-size: 100% 100%;
  background-repeat: no-repeat;
  pointer-events: none;
}

/* ─── HERO TEXT LEGIBILITY ───────────────────────────────── */
/* Faint parchment glow keeps the kicker readable when ink passes
   behind. It's subtle enough to read as crisp, not a halo. */
.hero-kicker {
  text-shadow: 0 0 20px rgba(243, 235, 221, 0.3);
}
/* Subtitle: NO glow (debug session). It used to carry a heavy double glow which
   read as a fuzzy drop-shadow over the ink. The fix mirrors why the kicker stays
   crisp over ink — not the glow, but its muted-GOLD colour, which contrasts against
   both the light parchment and the dark ink. So the subtitle now takes that same
   gold and drops the glow entirely: crisp on parchment, visibly gold over ink, no
   halo. (A near-black subtitle can't be both "visible on dark ink" and "no glow".) */
.hero-subtitle {
  text-shadow: none;
  /* gold colour set in the typography rule below */
}
/* Kicker uses Inter per brand guide (overrides Tailwind font-mono class).
   width:fit-content + the negative right margin cancel the trailing 0.32em
   letter-spacing so there's no dead space after "2020" (n_0mbyof2). */
.hero-kicker {
  font-family: 'Inter', sans-serif;
  width: fit-content;
  margin-right: -0.32em;
}

/* ─── HERO GRID (Direction 04 — Collage Intelligence) ─────── */
/* Two-column layout: left = wordmark/CTAs, right reserved for the
   absolute-positioned collage layer above. The collage layer is a sibling
   of this grid so its child positions match the dev-tool snapshot exactly. */
.hero-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 60px;
  min-height: calc(1274px - 96px);   /* fixed to the 2552x1274 design frame (was 100vh) so vertical scales with the stage */
  align-items: center;
  padding-top: 24px;
  padding-bottom: 64px;
}

@media (max-width: 1024px) {
  .hero-grid { grid-template-columns: 1fr; gap: 32px; }
}

.hero-left {
  position: relative;
  z-index: 2; /* above the collage layer */
}

/* The composition's deliberate +120px collage shift (Daniel 2026-06-13) is part
   of the fixed 2552 design frame now, so it applies UNCONDITIONALLY on the base
   .hero-collage rule below (was gated to >=1280px, which would have toggled the
   layout at that width). The render loop owns per-TILE transforms, not the
   .hero-collage container's, so this translate is safe. .hero-left keeps its
   default margin-left:0. */

/* ─── WORDMARK SPACER ─────────────────────────────────────── */
/* The #wordmark element was killed (n_8ks9po0). This spacer preserves the
   vertical room it reserved so the subtitle/sub-copy/CTAs keep their position.
   Purely presentational; the page h1 is now the sr-only heading in the hero. */
.hero-wordmark-spacer {
  min-height: 280px;
  margin-bottom: 28px;
}

/* ─── HERO SUBTITLE (Inter, replaces taped chip) ─────────── */
.hero-subtitle {
  font-family: 'Inter', sans-serif;
  font-weight: 700; /* bold per n_b16l9nf */
  font-size: 16px;  /* fixed px, not vw — see .headline-lead (Learning #41): vw double-scales inside .hero-stage. 16px == clamp max @2552 */
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgb(122, 106, 77); /* muted gold matching the kicker — crisp over ink, no glow needed (debug session) */
  margin-bottom: 24px;
}


/* ─── HERO HEADLINE (Hero v2 — 2026-06-13) ────────────────────
   HERO V2 (2026-06-13): left-column headline rebuild, replacing the Special
   Elite "marker goes down" hook. A Bodoni serif headline on a yellow torn-paper
   rectangle, the headline type set mix-blend:multiply so it sinks into the paper
   grain; the two emphasis phrases ride a thin hand-script, each circled by a
   procedural turbulent-displaced SVG stroke (#emph-rough); then a Spectral body
   line + DM Mono credibility row. Right collage untouched.
   Spec: docs/superpowers/specs/2026-06-13-hero-v2-left-design.md */
.hero-hook { max-width: 620px; margin-bottom: 36px; }

/* 0-size host for the reusable #emph-rough turbulence filter (markup in index.html). */
.svg-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

/* Parchment textbox backing (209, swapped from the yellow rect per note n_jzsiqdl:
   "paper replaced with the one sitting on top"). background-size:100% 100% stretches
   the source to the shrink-wrapped text box; the trimmed vertical padding compresses
   the parchment toward the ~20% vertical squash Daniel asked for (n_3mjceij) without
   distorting a fixed asset. inline-block shrink-wraps to the widest line. */
.headline-paper {
  display: inline-block;
  background: url('../assets/img/dev-drops/209_paper-textbox_large-parchment-textbox.webp') no-repeat center center / 100% 100%;
  padding: 26px 44px 28px 38px;
  margin: 0 0 26px;
  /* Paper drop shadow so the parchment reads as lying ON the surface, matching the
     collage tiles next to Jess (note n_futxzye). Fixed offset (not a render-loop tile,
     so no per-tile --shadow-depth); drop-shadow follows the paper image's alpha. The
     inner .headline-lead multiply still blends within this filter buffer. */
  filter: drop-shadow(6px 7px 5px rgba(0, 0, 0, 0.42));
}

.headline-lead {
  /* Special Elite typewriter (v3 type pass per note n_kiw2hix + image_140), swapped
     from Bodoni. Special Elite was the pre-v2 hero-hook face, so it stays inside the
     hero's locked tactile vocabulary rather than introducing a new token. */
  font-family: 'Special Elite', 'Courier New', monospace;
  font-weight: 400;          /* Special Elite ships a single weight */
  /* Fixed px, NOT vw: this lives inside the transform-scaled .hero-stage, so a
     vw font-size double-scales (clamp recomputes against the raw viewport, then
     the stage transform scales it again) — the shrink-wrapped paper backing then
     resized oddly vs the fixed-px collage/wordmark (Learning #41). Value == its
     size at the 2552 design viewport, so the stage owns all the scaling. */
  font-size: 40px;           /* typewriter runs wider/heavier than Bodoni — sized to image_140 */
  line-height: 1.36;
  color: #221d19;            /* dark ink, multiplies warm over the cream paper */
  margin: 0;
  /* Sink the type INTO the paper grain (Daniel's brief): multiply darkens the
     headline against the paper backing instead of sitting flat on top. */
  mix-blend-mode: multiply;
}

/* Emphasis phrases (image_140.png). Only "shut down," is script; "go quiet" stays in
   the headline typewriter face, framed by the oval. The teal is shared via the var
   (the underline still uses it); --emph-mark retired with the old SVG highlighter. */
.headline-lead {
  --emph-teal: #15707E;                 /* deepened brand teal — oval + underline */
}
.emph {
  position: relative;
  display: inline-block;
  line-height: 1;
  padding: 0 0.06em;
}

/* "shut down," — handwritten script accent (Caveat, already loaded, per note
   n_kiw2hix). The typewriter x-height runs short, so bump the size to flow over
   the tan strip. */
.emph--mark {
  font-family: 'Caveat', cursive;
  font-weight: 700;
  font-size: 1.34em;
  padding: 0 0.18em;
}
/* 198 tan paper strip — sits BEHIND the script glyphs (negative z within the
   .headline-lead multiply context, note n_xjd1sah) so the ink reads on top while
   the real paper tints into the parchment like a marker swipe. */
.emph-mark-img {
  position: absolute;
  /* Nudged up + right to sit under "shut down," better (note n_u3s95gz). */
  left: -3%;
  top: -6%;
  width: 122%;
  height: auto;
  z-index: -1;
  pointer-events: none;
  mix-blend-mode: multiply;
}

/* "go quiet" — kept in the headline face; the 199 teal oval outline frames it ON
   TOP (note n_usldfom). The oval centre is transparent so the text shows through;
   pointer-events:none keeps it inert. */
.emph-ring-img {
  position: absolute;
  /* Slightly larger + shifted right to frame "go quiet" better (note n_tkuoowo). */
  left: -9%;
  top: -42%;
  width: 142%;
  height: auto;
  pointer-events: none;
}

/* "in session." underline is now Daniel's 084 paint smear (note n_1fjebjt), replacing
   the procedural SVG swoosh (removed per note n_futxzye). The blue-grey smear is pushed
   toward the brand teal (#15707E) and darkened via filter so it reads as a teal ink
   underline; squashed into a thin band and multiplied into the parchment.
   (The #emph-rough turbulence filter in index.html is now unused but left inert.) */
.in-session {
  position: relative;
  display: inline-block;
}
.emph-uline-img {
  position: absolute;
  left: -2%;
  bottom: -0.30em;
  width: 104%;
  height: 0.5em;          /* squash the wide smear into a thin underline band */
  pointer-events: none;
  mix-blend-mode: multiply;
  /* 084 is a blue-grey smear; shift hue toward teal, lift saturation, darken. */
  filter: brightness(0.6) saturate(1.8) hue-rotate(-25deg);
}

.headline-body {
  /* Courier Prime typewriter (was Special Elite, note n_fpox8b0) — keeps the
     typewriter look but reads cleaner at this small running-sentence size. Still a
     departure from the locked body role (Spectral); one-line revert if wanted. */
  font-family: 'Courier Prime', 'Courier New', monospace;
  font-weight: 400;
  font-size: 18px;  /* fixed px, not vw (Learning #41). n_fryvmn7: bumped 17->18 + tightened to match .lm-body p; nudge both together if more is wanted */
  line-height: 1.5;
  letter-spacing: -0.01em;  /* n_fryvmn7 "bring characters closer together" */
  /* Muted gold, ink-legible over both parchment and the dark ink river
     (Learning #17). The headline above gets its legibility from the paper. */
  color: rgb(74, 67, 54);
  max-width: 450px;
  margin: 0 0 18px;
}

.headline-cred {
  font-family: 'DM Mono', monospace;
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(74, 67, 54, 0.92);
  margin: 0;
}

/* Hero kicker — handwritten accent (type-cohesion pass 2026-06-04). The muted
   gold colour stays: it's what keeps it legible over passing ink (Learning #17). */
.hero-kicker {
  font-family: 'Caveat', cursive;
  font-weight: 600;
  font-size: 26px;
  letter-spacing: 0.04em;
}

/* ─── HERO CTAs (matching treatment, primary style for both) ─ */
.hero-cta {
  /* DM Mono — the label-caps voice, matching nav + .lm-btn */
  font-family: 'DM Mono', monospace;
  font-weight: 500;
  font-size: 12px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  padding: 16px 28px;
  display: inline-block;
  transition: background 0.3s ease, color 0.3s ease, transform 0.3s ease;
}

.hero-cta-primary {
  background: #1F1B18;
  color: #ECE3D0;
}

.hero-cta-primary:hover { background: #1E2749; }

/* Image CTA buttons (Daniel's graphics, 2026-06-04). Hover scale comes from
   the .hero-ctas a binding in animations.js. NO will-change/backface-visibility
   (removed 2026-07-10) — promoting these image buttons to a GPU layer rasterized
   them at 1x and let the compositor upscale to high-DPI, reading as blurry at
   rest and only crisp mid-hover. Confirmed via a 2x-DPI A/B. Same removal applied
   to .masthead-cta--img above and .ws-cta > img below. */
.hero-cta-img { display: inline-block; }
.hero-cta-img img { display: block; height: auto; }
.hero-cta-img:focus-visible {
  outline: 2px solid #DBA83C;
  outline-offset: 4px;
}

/* Header nav "circling" hover (n_bgs047i): Daniel's 199 teal oval recoloured pure
   white and drawn ON around the "Free Guide" / "Workshops" link with a clock-wipe —
   a conic-gradient mask reveals a growing pie from 12 o'clock, so the oval's ring arc
   paints on clockwise like a hand circling the link. GSAP owns --cta-wipe (0→1 on
   enter, 1→0 on leave) in animations.js. Decorative + hover-only: opacity 0 at rest,
   so it degrades to nothing where conic masks aren't supported. White reads cleanly
   on the teal masthead. */
@property --cta-wipe { syntax: '<number>'; initial-value: 0; inherits: false; }
.masthead-link { position: relative; }
.masthead-link .cta-circle {
  position: absolute;
  left: 50%; top: 48%;
  transform: translate(-50%, -50%);
  width: 150%;               /* elongated horizontally so the oval clears the ends of "Free Guide" / "Workshops" */
  max-width: none;           /* Tailwind preflight img{max-width:100%} was clamping the oval to the text width */
  height: 240%;              /* flatter than wide → reads as a horizontal ring around the label */
  object-fit: fill;
  filter: brightness(0) invert(1);   /* teal outline → pure white */
  opacity: 0;
  pointer-events: none;
  z-index: -1;               /* behind the glyphs so the label stays crisp */
  --cta-wipe: 0;
  -webkit-mask-image: conic-gradient(from -90deg, #000 0deg, #000 calc(var(--cta-wipe) * 360deg), transparent calc(var(--cta-wipe) * 360deg));
          mask-image: conic-gradient(from -90deg, #000 0deg, #000 calc(var(--cta-wipe) * 360deg), transparent calc(var(--cta-wipe) * 360deg));
}

/* Hero height tracks its SCALED CONTENT, not the viewport (Learning #58,
   "flow sections together" 2026-06-29). The .hero-stage is transform-scaled
   by --hero-scale and absolute (out of flow), so transform does NOT shrink its
   layout box; the section must reserve the height explicitly. The old
   calc(100vh + 70px) reserved a FULL viewport regardless of scale, so on any
   window taller than the 2:1 design (e.g. 2560×1440, far worse as the width
   narrows) the scaled composition (1274px×scale) left a big dead band below it
   showing the fixed parchment/suminagashi ground. Tracking the scaled content
   height removes that band: the into-kraft seam + lead-magnet now rise to meet
   the hero bottom (the lead-magnet peeking at the fold on tall windows is the
   INTENDED continuous-flow look, not a regression).
     The +70 is the seam fold-clearance from Learning #28: the into-kraft seam
   sits right after #hero (pinSpacing:false, so the seam's doc-top == this
   height), and its torn cardboard fibres overhang UP onto the hero. The buffer
   now scales WITH --hero-scale (Daniel 2026-06-29): the seam itself was changed
   to shrink with the page (style.css multiplies --seam-scale by
   --hero-scale), so its overhang is ~39px×scale, and the clearance must track it
   in lockstep — a fixed 70px would over-reserve at small scales and leave dead
   cardboard below the composition (the very gap the flow-to-scaled-height pass
   was removing). Folding the +70 into the scaled term keeps the overhang and the
   buffer proportional at every width, so the tear never bites in.
     At the design viewport (≥2552, scale 1.0) this resolves to (1274+70)=1344px,
   identical to the old 1274+70, so the canonical 2552×1274 render is
   pixel-unchanged. id beats the .min-h-screen class. */
#hero {
  min-height: calc((1274px + 70px) * var(--hero-scale));
}

/* ─── HERO STAGE (uniform-scale design frame) ─────────────── */
/* Fixed 2552x1274 frame holding the whole hero composition; scaled by
   --hero-scale so every viewport shows the identical layout, just sized to fit
   (so Daniel + Claude finally see the same render).
     position:absolute (NOT a flow/flex child) is deliberate: transform:scale does
   NOT shrink an element's LAYOUT box, so a 1274px-tall flow stage would inflate
   #hero to 1274px on every viewport, adding dead scroll below the fold on short
   screens. Absolute takes the stage out of flow, so #hero keeps its intended
   min-height while the stage still scales visually.
     left:0 + transform-origin:top left is exact, not centred: at any viewport
   <= 2552 the scaled width (2552 * vw/2552) equals the viewport width, so the
   stage fills [0, vw] from the left edge -- pixel-identical to the pre-stage
   layout (collage was absolute at left:0; the scrollbar just clips the right).
   Centring via left:50% would offset by ~half the scrollbar. right:0 +
   margin-inline:auto only kicks in above 2552 (scale capped at 1) to centre the
   frame on ultrawides; below that it resolves to left:0.
     padding-top 96px is the relocated #hero pt-24: it pushes the in-flow grid
   down while the absolute .hero-collage (top:0) stays at the stage top,
   preserving the exact scale-1.0 layout. The render loop owns .collage-item
   transforms, not this element's, so this scale is never stomped (Learning #23). */
.hero-stage {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  margin-inline: auto;
  width: 2552px;
  height: 1274px;
  padding-top: 96px;
  transform-origin: top left;
  transform: scale(var(--hero-scale, 1));
}

/* ─── LEAD-MAGNET + WORKSHOPS UNIFORM-SCALE STAGES ───────── */
/* Same mechanism as .hero-stage (Learning #39): a fixed 2552x1274 design frame
   scaled to fit by --hero-scale — the ONE page scale set in index.html's <head>
   before first paint, reused here so hero / lead-magnet / workshops miniaturise in
   lockstep. position:absolute keeps the 1274px box out of flow so each section's
   height stays min-height:100vh. transform-origin:top left + left:0 fills [0,vw]
   from the left edge at viewports <=2552 (pixel-identical to the pre-stage 2552
   render); right:0 + margin-inline:auto only centre on >2552 ultrawides. NO
   padding-top (unlike .hero-stage — the lead-magnet copy carries its own top
   offset). REMINDER: nothing inside may use vw/vh/viewport-% — they double-scale
   (Learning #41); size everything in fixed 2552-design px. */
.lm-stage,
.ws-stage {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  margin-inline: auto;
  width: 2552px;
  height: 1274px;
  transform-origin: top left;
  transform: scale(var(--hero-scale, 1));
}

/* ─── WORKSHOPS CONTENT (2026-06-18) ─────────────────────────
   The workshop background is a finished 16:9 paper, so its stage is TALLER than
   the 1274 hero/lm frame — override to the bg's 16:9 height (2552 / 1.778) so the
   whole paper shows in one shot. The section's min-height tracks this scaled (see
   index.html inline style on #workshops). All children sit in fixed 2552-design
   px; width-only sizing (height:auto) avoids the squash trap (Learning #2/#32). */
.ws-stage { height: 1274px; }   /* one screen; the 16:9 bg is cropped to fit (below) */

.ws-bg {
  position: absolute;
  left: 0; top: 0;
  width: 2552px; height: 1274px;
  /* v5 FULL is 16:9 (3840x2160), taller than this 2552x1274 (=2.003:1) box. object-fit:
     cover scales it to full width (~2552x1436) and object-position:center bottom crops
     the ~162px excess off the TOP, pinning the bottom decorations to the section bottom
     (Daniel testing the uncropped fit, 2026-06-27). No manual top trim — the cover-crop
     handles the top, and the bottom-pin sits the composition ~50px lower than the prior
     cropped+(-50px) fit (which had floated it up with a parchment band beneath). */
  object-fit: cover; object-position: center bottom;
  user-select: none; -webkit-user-drag: none;
}

/* Placed finished graphics (cards + CTA buttons) — positioned inline per element. */
img.ws-item { position: absolute; display: block; height: auto; user-select: none; -webkit-user-drag: none; }
a.ws-item   { position: absolute; display: block; }
a.ws-item > img { display: block; width: 100%; height: auto; }
/* Grow the inner img from centre on hover (NOT the <a>, which carries the baked per-button
   scale() — a transform on the <a> would clobber that scale). GSAP-driven
   (js/animations.js buildHoverStates) — was a CSS transition, which this project's
   animation rule bans (GSAP for ALL animation) and which also re-rasterized the
   image between the animating and at-rest states, reading as "crisper mid-scale,
   blurry once landed" (Daniel, 2026-07-08). Removing the CSS transition (done that
   session) fixed that. NO will-change/backface-visibility here (removed 2026-07-10):
   promoting a baked-scale image button to a GPU layer rasterizes it at 1x and lets
   the compositor upscale to high-DPI, which is what actually caused the residual
   blur-at-rest Daniel still saw. (.collage-item legitimately keeps will-change —
   it's transformed every frame; these buttons are static at rest.) */
.ws-cta { cursor: none; }
/* Cards paint ABOVE their CTA buttons so a button can hide behind its card and
   emerge from under it as the card settles (buildWorkshopReveal reveal). */
img.ws-item { z-index: 3; }
a.ws-cta    { z-index: 2; }
/* Cards + CTA buttons carry their shadow BAKED INTO the WebP alpha (Daniel, 2026-07-01) —
   no CSS drop-shadow here, or it would double. The testimonial below is the exception:
   its art is shadowless, so CSS still casts it. */
/* Testimonial card: a SOFTER, more lifted cast so it does not read identical to the
   crisp card/button shadow (Daniel wanted it distinct). */
/* CSS drop-shadow removed 2026-07-01: jess feedback v2 bakes its own shadow into the alpha
   (was for the flat "jess feedback blank"). To restore: filter: drop-shadow(4px 12px 16px rgba(31,27,24,0.32)); */

/* Left intro copy */
.ws-intro { position: absolute; }
.ws-headline {
  margin: 0;
  font-family: 'Bodoni Moda', Georgia, serif;
  font-weight: 700;
  font-size: 104px;
  line-height: 1.04;
  letter-spacing: -0.015em;
  color: #1F1B18;
}
.ws-emph { position: relative; display: inline-block; white-space: nowrap; }
/* Teal hand-drawn circle around "Forward." (reuses the nav .cta-circle oval asset).
   Draws ON with the nav clock-wipe (--cta-wipe conic mask) after the headline types,
   scrubbed. Default 1 = fully shown, so reduced-motion and no-JS both keep the circle;
   buildWorkshopReveal sets it to 0 then animates back to 1. */
.ws-circle {
  position: absolute;
  left: -7%; top: -33%;
  width: 116%; height: auto;
  max-width: none;            /* beat Tailwind preflight img{max-width:100%} (Learning #49) */
  pointer-events: none;
  --cta-wipe: 1;
  -webkit-mask-image: conic-gradient(from -90deg, #000 0deg, #000 calc(var(--cta-wipe) * 360deg), transparent calc(var(--cta-wipe) * 360deg));
          mask-image: conic-gradient(from -90deg, #000 0deg, #000 calc(var(--cta-wipe) * 360deg), transparent calc(var(--cta-wipe) * 360deg));
}
.ws-sub {
  margin: 42px 0 0;
  max-width: 480px;
  font-family: 'Courier Prime', 'Courier New', monospace;
  font-size: 26px;
  line-height: 1.55;
  color: #1F1B18;
}

/* Testimonial — authored text over the blank quote-strip graphic */
.ws-testimonial { position: absolute; }
.ws-testimonial-bg { display: block; width: 100%; height: auto; }
.ws-quote {
  position: absolute;
  left: 13%; top: 50%;
  width: 43%;
  margin: 0;
  transform: translateY(-50%);
  font-family: 'Special Elite', 'Courier New', monospace;  /* Daniel's pick 2026-06-18 (n_y9zyffc); was Courier Prime */
  font-size: 23px;
  line-height: 1.5;
  color: #ECE3D0;   /* cream — jess feedback v2 backing is dark charcoal (2026-07-01) */
}
.ws-cite {
  position: absolute;
  left: 64.5%; top: 50%;
  transform: translateY(-50%);
  display: flex; flex-direction: column; gap: 6px;
  font-family: 'DM Mono', monospace;
  letter-spacing: 0.04em;
  color: #ECE3D0;   /* cream — reads on the dark v2 backing (2026-07-01) */
}
.ws-cite-name { font-size: 16px; font-weight: 500; }
.ws-cite-role { font-size: 13px; opacity: 0.68; }

/* ─── HERO COLLAGE LAYER ─────────────────────────────────── */
/* Direct child of #hero. Position absolute so children can use the
   page-absolute coords captured by dev-positions.js. pointer-events:none
   keeps the collage out of the way of the left-column CTAs; per-item
   pointer-events restored if/when interactivity is added. */
.hero-collage,
.hero-collage-front {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 1;
  transform: translateX(120px);   /* fixed composition shift (de-gated from @media; part of the 2552 design frame) */
  /* Per-element perspective() is now written inline by js/hero.js
     so each tile has its own vanishing point at its own centre,
     instead of all tiles sharing a single point at the collage
     centre. Feels more like individual pieces of paper. */
}

/* Front collage layer — identical coordinate frame to .hero-collage (same
   absolute 0,0 origin + translateX shift, so dev-tool/baked stage coords carry
   over unchanged) but rendered ABOVE the copy column (.hero-left is z-index:2).
   Empty by default; the dev tool's PageUp promotes a collage tile here so it
   sits in FRONT of the headline paper (PageDown sends it back). Each collage
   layer is its own sealed stacking context, which is exactly why a tile must
   change LAYERS, not just z-index, to cross the copy — see Learning #40. */
.hero-collage-front { z-index: 3; }

.collage-item {
  position: absolute;
  display: block;
  user-select: none;
  -webkit-user-drag: none;
  /* Mirror the dev-tool render so what the user dragged is what ships:
     image scales to fit the bounding box preserving its natural ratio,
     no stretching to fill. */
  object-fit: contain;
  /* Restore pointer-events on the individual items so the dev tool can grab
     them. The .hero-collage parent has pointer-events:none so empty space
     between items doesn't intercept clicks meant for the left-column copy. */
  pointer-events: auto;
  /* Drop-shadow with depth-scaled distance. The x/y OFFSETS multiply by the
     per-tile --shadow-depth CSS var (set in js/hero.js initTileState, ranges
     from SHADOW_DISTANCE_MIN at the back to SHADOW_DISTANCE_MAX at the front).
     Back tiles get shorter offset = sit closer to the page. Blur and opacity
     are constant across all tiles — darkness doesn't change with depth.
       7px / 7px  - max offset (front tiles get this; back tiles scale down)
       3px        - blur
       0.65       - opacity                                                  */
  filter: drop-shadow(
    calc(7px * var(--shadow-depth, 1))
    calc(7px * var(--shadow-depth, 1))
    3px
    rgba(0, 0, 0, 0.55)
  );
  /* GPU compositing hints — tiles are transformed every frame, so promote
     each to its own layer. Also kills the sub-pixel "tearing" artefacts
     that show up under rotateX/rotateY without backface-visibility set. */
  will-change: transform;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  transform-origin: center center;
}

/* Hide tiles until js/hero.js has had a chance to run initTileState.
   The .js class is added synchronously in the <head> of index.html;
   the rule only applies when JS is enabled so no-JS users still see the
   composition. Reduced-motion users get opacity:1 written by JS as soon
   as initTileState runs, so the hidden window is brief. */
html.js .collage-item {
  opacity: 0;
}

/* Ink-wipe entrance for scenery tiles (2026-06-05): tiles materialise in
   place behind a turbulent-edged mask sweep — ink soaking into paper —
   instead of sliding in. Vars are set + animated from js/hero.js; the class
   is removed once each wipe completes so settled tiles pay no mask cost. */
.collage-item.ink-wipe {
  -webkit-mask-image: var(--wipe-img);
          mask-image: var(--wipe-img);
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-size: var(--wipe-size);
          mask-size: var(--wipe-size);
  -webkit-mask-position: var(--wipe-pos);
          mask-position: var(--wipe-pos);
}

/* ─── BOTTOM-LEFT CORNER CLUSTER (pinned, 2026-06-15) ─────── */
/* Four scenery pieces decoupled from the uniform-scale stage so they stay in the
   bottom-left CORNER on resize (Daniel: "stay bottom left, ok if they shrink")
   instead of drifting toward the stage's top-left origin. The anchor sits at the
   hero's SCALED-CONTENT bottom — top: calc(1274px * --hero-scale) — which matches
   the .hero-stage bottom, so the cluster pins to the bottom-left of the COMPOSITION
   (where it belongs in the collage) at every width.
     This was top:100vh, which anchored the cluster to the WINDOW bottom. Fine while
   #hero was a full viewport tall, but once #hero shrank to track its scaled content
   ("flow sections together", 2026-06-29) the window bottom dropped far below the
   composition, so the flowers detached and floated over the lead-magnet. Re-anchoring
   to 1274px*--hero-scale (the stage-content bottom) re-glues them to the composition.
   design-px is exact here and SAFE (this is OUTSIDE the transform-scaled stage, so
   Learning #41's vw/vh-double-scale ban doesn't apply); at the design viewport
   (scale 1.0) it resolves to 1274px == the old 100vh there, so the cluster's resting
   position is pixel-unchanged at 2552×1274. */
.hero-corner-bl {
  position: absolute;
  left: 0;
  top: calc(1274px * var(--hero-scale));
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 0;            /* behind .hero-stage (later in DOM, same level) — never covers copy */
}
/* The 2552x1274 design frame, mirrored from the stage but anchored to the BOTTOM
   and scaled from the bottom-left corner. scale() BEFORE translateX so the +120px
   composition shift scales with the frame, matching .hero-collage inside the scaled
   stage — so each tile's baked coords render IDENTICALLY at the 2552 window yet pin
   to the corner (rather than the top) as the window shrinks. */
.hero-corner-bl-frame {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 2552px;
  height: 1274px;
  transform-origin: bottom left;
  transform: scale(var(--hero-scale, 1)) translateX(120px);
}
.corner-tile {
  position: absolute;
  display: block;
  object-fit: contain;
  user-select: none;
  -webkit-user-drag: none;
  /* Match the collage's resting drop-shadow feel (these aren't render-loop tiles,
     so no per-tile --shadow-depth — a fixed mid-depth shadow). */
  filter: drop-shadow(5px 6px 3px rgba(0, 0, 0, 0.52));
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  will-change: transform;
}
/* FOUC guard: hidden until js/hero.js arms the rise-from-below entrance. JS-only
   (matches the .collage-item guard) so no-JS users still see the cluster. */
html.js .hero-corner-bl { opacity: 0; }

/* Per-tile position rules live here once captured by dev-positions.js.
   Origin is the hero section's top-left. Cleared 2026-05-19 — re-populate
   from the dev tool after dropping new tiles. */

/* ─── INK DIVIDER (SVG, draws on scroll) ─────────────────── */
.ink-divider-svg {
  display: block;
  width: 100%;
  height: 2px;
  opacity: 0.28;
  margin: 0;
  pointer-events: none;
}
.ink-divider-svg line { stroke: #1F1B18; }

/* ─── PAPER GROUND ───────────────────────────────────────── */
/* Paper-texture layer — a fixed layer ABOVE the ink canvas (#sumi at z-index 0),
   multiply-blended so the paper grain sits over the ink. The base is WHITE on
   purpose: white is the multiply identity, so over the bare parchment body this
   layer leaves the colour untouched and only lays down grain — no parchment-on-
   parchment "double yellow" tinge. The parchment COLOUR comes from the body
   (showing through where there's no ink, and through the semi-transparent ink
   where there is). A white softening gradient knocks the raw scan's contrast
   back so it reads as fine grain, not gritty "sand". Note: a multiply layer can
   only darken — ink subtlety is controlled by MAX_ALPHA in js/suminagashi.js,
   not here. */
.paper-ground {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  mix-blend-mode: multiply;
  background-color: #ffffff;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0.55)),
    url('../assets/img/paper-clean.webp');
  background-size: auto, cover;
  background-position: center;
  background-attachment: fixed;
  background-blend-mode: normal, multiply;
}

/* Ink-stream SVG and ransom-row CSS removed 2026-05-11. The old hero wordmark
   used CSS-styled spans on coloured backgrounds; Direction 04 replaces these
   with photographed paper-tile WebPs (individual <img> elements). */

/* ─── SCROLL INDICATOR ───────────────────────────────────── */
.scroll-ind {
  position: absolute;
  bottom: 40px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  z-index: 10;
}

.scroll-line-inner {
  width: 1px;
  height: 56px;
  background: linear-gradient(to bottom, #1F1B18, transparent);
  opacity: 0.3;
  margin-bottom: 12px;
  animation: scroll-pulse 2.4s ease-in-out infinite;
}

.scroll-ind-label {
  font-family: 'Inter', sans-serif;
  font-weight: 500;
  font-size: 14px;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: rgba(31, 27, 24, 0.55);
  animation: scroll-label-pulse 2.4s ease-in-out infinite;
  display: block;
}

@keyframes scroll-pulse {
  0%, 100% { opacity: 0.12; transform: scaleY(0.6); transform-origin: top; }
  50%      { opacity: 0.35; transform: scaleY(1);   transform-origin: top; }
}

@keyframes scroll-label-pulse {
  0%, 100% { opacity: 0.5;  transform: translateY(0); }
  50%      { opacity: 0.85; transform: translateY(4px); }
}


/* ─── TORN PAPER SEAM (V3 masked cardboard) ──────────────── */
/* The seam is a real torn-cardboard edge: the .seam-mock band masks the
   lead-magnet cardboard paint so it TEARS over the parchment neighbours
   with perfect colour continuity. The base .torn-paper-seam below is just
   the 40px flow-spacer; the render lives in the V3 block further down.
   (Legacy wavy-curve pseudos removed in the 2026-07 fold-in cleanup.) */
.torn-paper-seam {
  width: 100%;
  /* Flow-spacer height scales with the page (×--hero-scale) so it stays covered
     by the now-scaled .seam-mock band — a fixed 40px box would poke out below the
     shrunken band at narrow widths (transparent, since the ::before/::after CSS
     curve is hidden under the V3 mock), leaving a gap. 1.0 → 40px, unchanged
     (Daniel 2026-06-29, seam-scales-with-width pass). */
  height: calc(40px * var(--hero-scale, 1));
  position: relative;
  z-index: 6;
  pointer-events: none;
}
/* (wavy-curve ::before/::after pseudos removed — the V3 band renders the seam) */


/* ─── TORN PAPER SEAM — V3 MASKED CARDBOARD (production render) ────
   Locked V3 treatment (Daniel 2026-06-05): the lead-magnet cardboard
   surface is CSS-masked by the alpha of a real torn-cardboard edge, so the
   section visibly TEARS over the parchment neighbours with perfect colour
   continuity. Band divs (.seam-mock-under + .seam-mock > .seam-mock-fill)
   are static in index.html; renders with NO JS and NO body attributes.
   The throwaway V1/V2/V4 + field-cycler variants were removed with this
   cleanup (git history holds them). V3 reads only the band's ALPHA. */

body {
  /* Tear scale at the DESIGN width (0.5 = locked V3 value) × --hero-scale so
     the WHOLE seam shrinks in lockstep with the scaled collage. At
     --hero-scale:1 → 0.5, the 2552 canonical render. */
  --seam-scale: calc(0.5 * var(--hero-scale, 1));
}

/* Beneath-page strip: a replica of the real .paper-ground look
   (#ECE3D0 × [0.55 white + 0.45 paper-clean grain], multiply). Its only job
   is hiding the canvas in the 40px strip; the tear dips above/below floor on
   the sections' real surfaces. attachment:fixed aligns the grain with the
   real fixed layer (iOS ignores fixed — coarse-pointer fallback below). */
.seam-mock-under {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: calc(40px * var(--hero-scale, 1));
  background-color: #ECE3D0;
  background-image:
    linear-gradient(rgba(236, 227, 208, 0.55), rgba(236, 227, 208, 0.55)),
    url('/assets/img/paper-clean.webp');
  background-size: auto, cover;
  background-position: center;
  background-attachment: fixed;
  background-blend-mode: normal, multiply;
  pointer-events: none;
}
/* iOS ignores background-attachment:fixed, so the grain can't align — fall
   back to flat parchment there (the straight-edge tell is far less visible
   than a mismatched grain). UNVERIFIED on real iOS. */
@media (pointer: coarse) {
  .seam-mock-under {
    background-image: none;
    background-attachment: scroll;
  }
}

/* ── band container ── */
.seam-mock {
  position: absolute;
  left: 0;
  right: 0;
  height: calc(320px * var(--seam-scale));
  pointer-events: none;
}
/* into-kraft: the solid zone bottom lands on the strip bottom = the lifted
   lead-magnet field's straight top edge (#lead-magnet z-7), so the opaque
   solid covers that flat edge and only ragged fibres ride up onto the
   parchment. from-kraft: fibre bottoms clear the strip bottom; min() keeps
   the solid zone over the strip top at small scales. */
.torn-paper-seam.into-kraft .seam-mock {
  top: max(calc(-120px * var(--seam-scale)), calc(40px * var(--hero-scale, 1) - 200px * var(--seam-scale)));
  filter: drop-shadow(0 -1px 2px rgba(31, 27, 24, 0.15));
}
.torn-paper-seam.from-kraft .seam-mock {
  top: min(calc(40px * var(--hero-scale, 1) - 288px * var(--seam-scale)), calc(-120px * var(--seam-scale)));
  filter: drop-shadow(0 1px 2px rgba(31, 27, 24, 0.15));
}

/* V3 fill: the lead-magnet cardboard paint (matches the field), stretched to
   cover, masked by the band's torn ALPHA tiled horizontally. */
.seam-mock-fill {
  position: absolute;
  inset: 0;
  background-color: #bd915c;
  background-image: url('/assets/img/textures/paper/cardboard-1.webp');
  background-size: cover;
  background-position: center center;
  background-repeat: no-repeat;
  background-blend-mode: normal;
  -webkit-mask-size: calc(4610px * var(--seam-scale)) auto;
          mask-size: calc(4610px * var(--seam-scale)) auto;
  -webkit-mask-position: 0 0;
          mask-position: 0 0;
  -webkit-mask-repeat: repeat-x;
          mask-repeat: repeat-x;
}
.torn-paper-seam.into-kraft .seam-mock-fill {
  -webkit-mask-image: url('/assets/img/textures/torn-edge/band-top-raw.webp');
          mask-image: url('/assets/img/textures/torn-edge/band-top-raw.webp');
}
.torn-paper-seam.from-kraft .seam-mock-fill {
  -webkit-mask-image: url('/assets/img/textures/torn-edge/band-bottom-raw.webp');
          mask-image: url('/assets/img/textures/torn-edge/band-bottom-raw.webp');
}

/* Lead-magnet field: raw cardboard (corrugated kraft photo, placed directly;
   id beats the .kraft-surface class). Was body[data-lm-field="cardboard"]. */
#lead-magnet {
  background-color: #bd915c; /* tan fallback before the webp decodes */
  background-image: url('/assets/img/textures/paper/cardboard-1.webp');
  background-size: cover;
  background-position: center center;
  background-blend-mode: normal;
}

/* The two LM-adjacent torn seams adopt the cardboard material so the
   section reads as ONE torn piece overhanging the parchment above and
   below — this override (two attribute selectors) outranks the V3 kraft
   paint above, keeping the V3 torn-alpha mask + contact shadow. This is
   the seam fill actually rendered in production. */
body[data-seam-mock="3"][data-lm-field="cardboard"] .into-kraft .seam-mock-fill,
body[data-seam-mock="3"][data-lm-field="cardboard"] .from-kraft .seam-mock-fill {
  background-color: #bd915c;
  background-image: url('/assets/img/textures/paper/cardboard-1.webp');
  background-size: cover;
  background-position: center center;
  background-blend-mode: normal;
}


/* ─── REDUCED MOTION ─────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .scroll-line-inner { animation: none; opacity: 0.2; }
  #sumi { display: none; }
  #cursor-hand { display: none; }
  body, body a, body button, body input, body textarea, body select,
  body label, body [role="button"] { cursor: auto; }
}


/* ─── DEFAULT VISIBLE — GSAP overrides these at runtime ─── */
/* opacity:1 here is the hard fallback: if GSAP fails to initialise for any
   reason (CDN timeout, JS error, reduced-motion bail-out) all content stays
   readable. GSAP's gsap.set() will override this only when it is certain the
   reveal timeline will run. */
.reveal-block {
  opacity: 1;
}


/* ─── LEAD MAGNET SCENE (baked collage tiles) ─────────────── */
/* Absolute overlay spanning the full section. overflow:visible lets tiles
   extend past the section's natural content height without clipping. */
.lm-scene {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  overflow: visible;
}
.lm-scene > img,
.lm-scene > video {
  position: absolute;
  display: block;
  height: auto; /* never set height inline — let each image keep its natural ratio */
  transform-origin: center center;
  /* Crisp contact shadow (like the hero tiles) carries the lift; a much softer
     directional cast just adds a little weight. --sx (set inline per tile) leans the
     cast left/right by which side of the composition the tile sits on (n_z24u6ic).
     Note: drop-shadow follows each image's alpha, so cut-outs with feathered edges
     (the hands) render even the crisp layer softer than hard-edged tiles do. */
  --sx: 0px;
  filter:
    drop-shadow(2px 4px 2px rgba(0, 0, 0, 0.55))
    drop-shadow(var(--sx) 13px 20px rgba(31, 27, 24, 0.30));
}

/* ─── §02 LEAD MAGNET ────────────────────────────────────── */
/* Built on .kraft-surface. PDF cover sits left, copy zone right.
   Side tiles (.lm-tile) drift in via dev-tool-baked positions later.
   Naming mirrors .g-* and .bio-b-* patterns. */

#lead-magnet {
  /* Height tracks the SCALED collage, not the viewport (Learnings #58/#65,
     "flow sections together", 2026-06-29). The .lm-stage is transform-scaled by
     --hero-scale and absolute (out of flow), so its layout box never shrinks; the
     section must reserve the height explicitly. At 100vh the collage (a 2:1 layout
     that scales to the WIDTH) left a growing band of empty cardboard below it on
     tall windows (~166px at 2560×1440, ~640px at 1600 wide) — Daniel's "huge dead
     space." Tracking the scaled collage height removes it: the from-kraft seam +
     workshops now rise to meet the collage bottom, so the section flows continuously
     into the next like the hero does, with no empty desk. The collage's build-in is
     no longer pinned (animations.js): it assembles on the scroll-in and the section
     reaches the top fully built, then flows on.
       The +70 scales WITH --hero-scale (Daniel 2026-06-29): the from-kraft seam at
     the section bottom now shrinks with the page (style.css multiplies
     --seam-scale by --hero-scale), so its ~39px×scale fibre overhang and this
     clearance must track together — a fixed 70px would over-reserve at small
     scales and re-open the "huge dead cardboard" gap below the collage. At the
     design viewport (≥2552, scale 1.0) this resolves to (1274+70)=1344px. */
  min-height: calc((1274px + 70px) * var(--hero-scale));
  /* Sits ABOVE the torn-paper seams (z-6). The seam bands are siblings at
     z-6 so their torn fibres can overhang BOTH parchment neighbours (hero
     above, workshops below) regardless of section paint order. The cost was
     that each band's semi-transparent INTO-LM overhang also painted over
     this section's collage near the edges (the "faded edge covering the
     textures" + the edge appearing to fade up on scroll). Lifting the
     section above the seam hides that into-LM overhang behind the field,
     while the fibres still overhang the parchment neighbours (z-5). The
     field is opaque, so the torn edge reads as this section's own torn
     edge with the collage sitting on top. (Overrides the z-[5] utility.) */
  z-index: 7;
}

/* Side tiles container — full-section absolute backdrop for collage tiles.
   Tiles inside use inline style="left/top/width/height/transform/z-index"
   baked by dev-positions.js. */
.lm-tiles {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
}
.lm-tile {
  position: absolute;
  display: block;
  filter: drop-shadow(0 6px 14px rgba(31, 27, 24, 0.18));
}

/* Kicker row — handwritten accent, bold (type-cohesion pass 2026-06-04;
   notes n_pc2hg3t + n_yx01cx0 "go bold, handwritten"). Mixed-case text is
   set in the HTML — handwriting in caps + heavy tracking reads wrong.
   z-index 0 sits the kickers BEHIND .lm-scene (z1) so they genuinely rise
   from behind the book; requires the copy container to stay transform-free
   (see the comment on it in index.html). */
.lm-kicker {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 56px;
  font-family: 'Caveat', cursive;
  font-size: 24px;
  letter-spacing: 0.02em;
  font-weight: 700;
  color: rgba(31, 27, 24, 0.65);
  position: relative;
  z-index: 0;
}
.lm-kicker-right {
  color: #1F1B18; /* full bone black — Daniel: "make black" (snapshot note n_37eenpn, 2026-06-12) */
}
/* Right kicker pulled in toward the cover (dev-baked 2026-06-04 at the 1280px
   content width). Now UNCONDITIONAL: inside the uniform-scale .lm-stage the copy
   box is always 1280 design px regardless of viewport, so the old narrow-viewport
   collision concern (and the @media min-width:1280 gate that fixed it) no longer
   applies — the offset is part of the 2552 design frame and scales with the stage. */
.lm-kicker-right { position: relative; left: -546px; }

/* Two-column grid: cover left (0.85fr), copy zone right (1.15fr).
   UNCONDITIONAL 2-column — NO narrow-viewport stack. This grid lives inside the
   uniform-scale .lm-stage (always 2552 design px wide internally, just scaled to
   fit), so a real-viewport media query that collapsed it to 1 column (the old
   @media max-width:1023) reflowed the copy column while the absolutely-positioned
   .lm-scene magazine collage stayed put — the headline + email strip then slid
   left ONTO the cover stack and overlapped it at every width below 1024 (Daniel:
   "the email field and the text overlap, not scaling like the rest"). Same family
   as Learning #41 (no vw/vh in the stage): viewport-keyed rules must not alter
   layout inside the fixed design frame. Keeping it 2-column at all widths makes
   the composition a pure scaled-down copy of the 2552 render — no collision. */
.lm-grid {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: 64px;
  align-items: start;
  /* Above .lm-scene (z1) so the form stays readable + clickable while the
     kicker row (z0) sits behind the tiles. */
  position: relative;
  z-index: 5;
}

/* PDF cover slot — centring wrapper for the cover asset dropped into the
   left column of the lead-magnet grid. */
.lm-cover-wrap {
  display: flex;
  justify-content: center;
}

/* Copy zone — headline + form + body stack on the right */
.lm-copy-zone {
  display: flex;
  flex-direction: column;
  gap: 40px;
}
.lm-headline {
  font-family: 'Bodoni Moda', serif;
  font-weight: 500; /* thicker (was 400) per user */
  font-style: italic;
  font-size: 60px; /* fixed px, not vw — inside the scaled .lm-stage, vw double-scales (Learning #41). 60px == the old clamp(34,4.4vw,60) max @2552 */
  line-height: 1.08;
  letter-spacing: -0.01em;
  color: #1F1B18;
  margin: 0 0 28px;
}
.lm-headline em {
  font-style: italic;
  /* "off-paper" gold — #DBA83C sat too close to the kraft tone to read
     (n_795a8fr "too hard to see"); lift lightness so it stands off the board. */
  color: #F0CE82;
  font-weight: 600;
}

/* ─── Email capture: torn-paper "DOWNLOAD FREE" strip ───────── */
/* The strip webp IS the visual (its dark right end is the button art). A
   transparent email input sits over the light paper; a transparent submit
   button covers the dark button region (measured x65-96%, y24-76%). Temp +
   unwired (n_0x900me / n_lxv4v5l). */
.lm-capture {
  position: relative;
  width: 100%;
  max-width: 720px;
  margin-bottom: 12px;
}
.lm-capture-paper {
  display: block;
  width: 100%;
  height: auto;
  user-select: none;
  -webkit-user-drag: none;
  pointer-events: none;
  /* contact + soft cast, same vocabulary as the .lm-scene tiles */
  filter:
    drop-shadow(2px 4px 3px rgba(0, 0, 0, 0.30))
    drop-shadow(-4px 14px 20px rgba(31, 27, 24, 0.22));
}
.lm-capture-input {
  position: absolute;
  left: 6%;
  top: 32%;
  width: 54%;
  height: 36%;
  background: transparent;
  border: none;
  outline: none;
  font-family: 'Inter', sans-serif;
  font-size: 16px; /* 16px avoids iOS focus-zoom */
  color: #1F1B18;
  padding: 0 2px;
}
.lm-capture-input::placeholder { color: rgba(31, 27, 24, 0.45); }
.lm-capture-btn {
  position: absolute;
  left: 65%;
  top: 24%;
  width: 31%;
  height: 52%;
  background: transparent;
  border: none;
  /* the label lives in the strip art; keep any JS-injected text ("Sending...") invisible */
  color: transparent;
  font-size: 0;
}
.lm-capture-btn:focus-visible {
  outline: 2px solid #DBA83C;
  outline-offset: 3px;
  border-radius: 4px;
}
/* Legible fine-print: was 10px wide-tracked uppercase grey at 0.5 opacity,
   which read as too thin and off-brand (n_hv4v0ut). Now 12px Inter 500, sentence
   case, darker, with normal tracking. */
.lm-disclaimer {
  font-family: 'Inter', sans-serif;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: rgba(244, 236, 214, 0.85); /* n_og1hqzu: white on the cardboard */
  margin: 0;
}
/* The form is the positioning context for the absolute status message below. */
#lead-form { position: relative; }
.lm-form-msg {
  /* Absolute so a status note (validation / success) appears right under the email
     strip WITHOUT reflowing the copy below it (Daniel: the popup "pushed everything
     down because it wasn't sitting in the right section"). It lands in the disclaimer's
     slot just under the .lm-capture strip; the disclaimer hides while it shows so they
     never stack. */
  position: absolute;
  left: 51px;           /* n_-: Daniel dev-nudged +1 (07:40) */
  top: 46px;            /* right under the email input field; Daniel nudged up 20px (66->46) on 2026-06-17 so it tucks closer under the strip */
  margin: 0;
  font-family: 'DM Mono', monospace;
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #DBA83C;
}
/* While a status message is showing, hide the fine-print disclaimer so the two
   don't overlap in the same slot under the strip. */
#lead-form.lm-has-msg .lm-disclaimer { visibility: hidden; }

/* Body copy — secondary text below the form */
/* Body copy now matches the hero sub-paragraph exactly (Inter, clamp 16–18px,
   line-height 1.6) — the size/leading the user confirmed as correct in the hero
   (n_jkyzq88). Was off-brand Manrope at 14px. */
.lm-body p {
  /* White typewriter body. Font is now Courier Prime (n_2txr1sh) — was Special
     Elite (n_6prhfpp), swapped for legibility at this small size, same look. Still
     a departure from the locked body role (Spectral); one-line revert if wanted. */
  font-family: 'Courier Prime', 'Courier New', monospace;
  font-size: 18px; /* fixed px, not vw (Learning #41). n_fryvmn7: bumped 17->18 + tightened, matched to .headline-body */
  line-height: 1.5;
  letter-spacing: -0.01em;  /* n_fryvmn7 "bring characters closer together" */
  color: #F4ECD6;
  margin: 0 0 14px;
}
.lm-body p:last-child {
  margin-bottom: 0;
}

/* ─── FOOTER — one baked torn-paper strip (footer-strip-v7) ─────────────── */
/* As of 2026-06-30 the whole footer is a single baked image: a dark-teal torn
   sheet with the colophon, tagline, and an ink-marble flourish printed into it,
   replacing the old cream lined-paper + live-text colophon. The torn edge is the
   art's TOP; it tears up into the workshops parchment above with NO flip and NO
   mask (the asset carries its own straight-alpha torn edge). The colophon/tagline
   are pixels now, so the live <span>s are gone — an sr-only text equivalent stays
   in the HTML for screen readers, and the contact/social LINKS return as overlaid
   buttons in the next pass (Daniel's "links → buttons"). */
.footer-strip {
  position: relative;
  width: 100%;
  /* Box is SHORTER than the art's full aspect (Daniel 2026-06-30: "still too huge, knock it
     down ~30-40%"): 6230/210 vs the art's 6230/327 = ~36% shorter. The strip paints at
     `100% auto` anchored top (see .footer-torn) so the box CROPS the bottom of the art —
     keeps the torn top edge + the body/text band, trims the lower solid teal (and the
     bottom of the marble flourish). Full width keeps the art's torn L/R edges at the page
     edges; height auto-scales with the page. v7 is a BLANK strip, so the colophon is LIVE
     text (.footer-content) sized in vw — vw is safe here (the footer is NOT inside the
     transform-scaled hero stage) and scales the text in lockstep with the strip. */
  aspect-ratio: 6230 / 210;
  /* Overlap the workshops above so the transparent torn-top notch always sits over OPAQUE
     parchment, never the fixed #sumi. The box now tracks the image height (not a fixed band),
     so the #68 4K-gap failure mode — cream falling below a fixed band — can't recur; this
     overlap just keeps the torn notch off the section seam. Sized to clear the ~11% notch with
     margin (notch ≈ 0.70vw) while staying off the workshops testimonial at wide widths — at 4K,
     −1.6vw grazed the quote card's bottom padding; −1.2vw clears it both ways. */
  margin-top: -1.2vw;
}
.footer-torn-wrap {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* Drop-shadow removed 2026-07-01 (Daniel: "kill the drop shadow on the footer, not loving it").
     The wrapper still hosts the painted .footer-torn; there is no cast under the tear now. The
     masthead keeps its own shadow. To restore: filter: drop-shadow(5px -3px 1px rgba(0,0,0,0.72)). */
}
.footer-torn {
  position: absolute;
  inset: 0;
  background-image: url('../assets/img/textures/paper/footer-strip-v8b.webp');
  /* 100% auto = fill width at the art's natural aspect (no vertical squash); the box is
     shorter than that natural height, so anchoring top crops the BOTTOM — torn edge + body
     kept, lower solid teal + marble-bottom trimmed. (Was 100% 100% when the box matched the
     full aspect.) */
  background-size: 100% auto;
  background-position: center top;
  background-repeat: no-repeat;
  pointer-events: none;
}
/* Live colophon over the blank v7 strip. Sits in the OPAQUE body (top:14% clears the torn
   notch). Name + title left, gold tagline right; the MIDDLE is left empty for Daniel's link
   buttons. Sizes/positions are a first pass matching the old baked v5 look — the final text
   treatment is Daniel's (he'll do it custom). pointer-events:none so it never blocks clicks;
   re-enable on any interactive children added later. */
.footer-content {
  /* Mirror the masthead's own scale (the masthead is transform:scale(--bar-scale)) so the
     colophon text tracks the masthead size at every width. The footer is NOT inside the hero
     stage, so it reads --hero-scale off :root directly, same clamp the masthead uses. */
  --bar-scale: clamp(0.5, var(--hero-scale, 1), 1);
  position: absolute;
  left: 0; right: 0;
  top: 22%;            /* below the torn-top notch (~22% of the shorter box) */
  bottom: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 11.5% 0 3.5%;   /* right inset clears the marble flourish; tune in-browser */
  gap: 2vw;
  pointer-events: none;
}
.footer-name {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: calc(5px * var(--bar-scale, 1));        /* == .masthead-id gap:5px */
  font-family: 'DM Mono', monospace;   /* n_q7q6c7q: match the masthead name font EXACTLY */
  font-size: calc(14px * var(--bar-scale, 1)); /* == .masthead-name 14px (scaled like the masthead) */
  line-height: 1;                              /* == .masthead-id line-height:1 */
  color: #ECE3D0;            /* parchment/cream on the dark teal strip */
  letter-spacing: 0.22em;                      /* == .masthead-name letter-spacing */
  text-transform: uppercase;                   /* == .masthead-name */
  white-space: nowrap;
  /* Re-enable on the text glyph box (container stays none) so the dev-tool picker
     can grab it — elementsFromPoint skips pointer-events:none, which inherits (Learning #5).
     Harmless: a non-interactive <p>, no hover/link, cursor unaffected. */
  pointer-events: auto;
}
.footer-name .footer-title { font-weight: 400; font-size: calc(13px * var(--bar-scale, 1)); letter-spacing: 0.16em; opacity: 0.78; } /* == .masthead-role (13px, 0.16em, 0.78) */
.footer-name .footer-sep  { opacity: 0.45; margin: 0 0.35em; }
/* .footer-tagline ("practice with presence, create with meaning") removed 2026-07-01 (Daniel). */

/* Social link rows -- the SAME four icons in the header (dead-centre in the nav row)
   and the footer (centred between name + tagline). hrefs are PLACEHOLDERS until real
   handles land (launch blocker). Footer in vw (scales with the strip); header in fixed
   px (sits inside the --bar-scale masthead). */
/* No longer absolute/centred: now the RIGHT-hand flex item in .footer-content (space-between),
   so with the tagline gone it sits where the tagline used to (Daniel 2026-07-01). */
/* gap scales with --hero-scale (uncapped) to match the icon height below — otherwise
   the icons shrink but the gap between them doesn't, so the row reads increasingly
   spread-out as the page narrows (Daniel, 2026-07-10). */
.footer-social { display: flex; align-items: center; gap: calc(28px * var(--hero-scale, 1)); z-index: 3; pointer-events: auto; }
.footer-social a, .masthead-social a { display: block; line-height: 0; }
/* Was a fixed 60px — didn't shrink with the page at all, so the icons grew
   disproportionately large relative to the (--bar-scale-scaled) colophon text as the
   viewport narrowed, eventually forcing the flex row to wrap and the icons to jump
   position (Daniel, 2026-07-10). Uses the UNCAPPED --hero-scale, not --bar-scale: the
   icon cluster also carries a fixed -205px dev-tool left offset (index.html) to clear
   the torn-paper flourish at full width, scaled by this same var — --bar-scale floors
   at 0.5, and a floored offset next to a still-shrinking container started overlapping
   the name text below ~1276px. Matching both to --hero-scale keeps icon size and
   position in lockstep at every width. */
.footer-social img { height: calc(52px * var(--hero-scale, 1)); width: auto; display: block; transition: transform 0.2s ease; filter: saturate(0.8); }
.footer-social a:hover img, .masthead-social a:hover img { transform: scale(1.08); }
.masthead-social {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  /* gap scales with --hero-scale, same reasoning as .footer-social above. */
  display: flex; align-items: center; gap: calc(28px * var(--hero-scale, 1)); z-index: 3;
}
/* Sits inside .masthead, which is transform:scale(--bar-scale) as a whole, so a plain
   60px here already rides that ancestor scale — but --bar-scale is FLOORED at 0.5 (kept
   the identity text/nav legible), which meant the icons stopped shrinking at 30px visual
   while the row around them kept losing width, crowding/colliding at narrow viewports.
   Dividing back out --bar-scale and re-multiplying by the uncapped --hero-scale cancels
   the ancestor's floor so these icons alone keep shrinking past it (Daniel asked for
   "scale down forever", 2026-07-10). Identical to the old fixed 60px above the 0.5 floor
   (hero-scale/bar-scale == 1 there), so no visual change on normal desktop widths. */
.masthead-social img { height: calc(52px * var(--hero-scale, 1) / var(--bar-scale, 1)); width: auto; display: block; transition: transform 0.2s ease; filter: saturate(0.8); }

.footer-strip-inner {
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(14px * var(--bar-scale, 1)) calc(28px * var(--bar-scale, 1));
  flex-wrap: wrap;
  font-family: 'DM Mono', monospace;
  font-size: calc(11px * var(--bar-scale, 1));   /* scales with the page (see .footer-strip) */
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.footer-strip-name {
  color: rgba(31, 27, 24, 0.72); /* bone black, muted — on the light notebook paper */
}
.footer-strip-contact {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
}
/* Scoped tighter than the structural .footer-strip-link reset (later in file)
   so these win without depending on source order. */
.footer-strip-contact a,
.footer-strip-contact .footer-strip-link {
  color: rgba(31, 27, 24, 0.88);
  text-decoration: none;
  border-bottom: 1px solid rgba(31, 27, 24, 0.3);
  padding-bottom: 2px;
  transition: color 0.2s ease, border-color 0.2s ease;
}
.footer-strip-contact a:hover,
.footer-strip-contact .footer-strip-link:hover {
  color: #1F1B18;
  border-color: #1F1B18;
}
.footer-strip .contact-sep {
  color: rgba(31, 27, 24, 0.45);
}

/* ─── Contact modal ─────────────────────────────────────────── */
.contact-modal { position: fixed; inset: 0; z-index: 9000; display: flex;
  align-items: center; justify-content: center; padding: 24px; }
.contact-modal[hidden] { display: none; }
.contact-modal-backdrop { position: absolute; inset: 0;
  background: rgba(31, 27, 24, 0.55); backdrop-filter: blur(3px); }
/* Fixed-size wrapper: shrinks the WHOLE card uniformly below ~660px in either
   viewport dimension so it always fits inside .contact-modal's 24px padding.
   Kept SEPARATE from .contact-modal-card because the card also carries a GSAP
   entrance tween on `scale` (js/contact-modal.js open()/close()) — GSAP writes
   the element's full inline `transform` and would silently clobber a CSS
   transform declared on the same element (session-learnings #13/#18 family:
   two things owning the same transform sub-property don't compose). Splitting
   the two scale concerns onto parent/child avoids the collision entirely.
   Above the breakpoint, --contact-scale resolves to exactly 1 (fixed size). */
.contact-modal-scale {
  --contact-scale: min(1, calc((100vw - 48px) / 932px), calc((100vh - 48px) / 1157px));
  transform: scale(var(--contact-scale));
  transform-origin: center;
}
/* Card is baked art at 600x745 base, scaled up 55% total (35% + a further 15%,
   Daniel's calls 2026-07-08 — the base size crowded validation errors against
   the next field's baked label / the Send tab, see the screenshots in
   "claude feedback/"). All position/size values below are the original
   pixel-measured coords x1.5525 EXCEPT .contact-field-error and .contact-msg,
   deliberately left at their original font-size/margin/padding — growing the
   box without growing the error text is what actually buys the extra
   breathing room; scaling both by the same factor would reproduce the same
   crowding at a bigger size. Input font-size is set smaller than a proportional
   scale would give (Daniel's call) so the boxes don't feel over-filled. */
.contact-modal-card { position: relative; width: 932px; height: 1157px;
  background-color: transparent; border: none; box-shadow: none; padding: 0; }
/* Carries the card art. Sized via inset:0 to exactly fill .contact-modal-card
   (same 932x1157 box), so every pixel-measured .contact-field/.contact-submit
   coordinate below still resolves correctly against ITS containing block. */
.contact-card-art { position: absolute; inset: 0;
  background-color: transparent;
  background-image: url('../assets/img/dev-drops/contact jess card v3.webp');
  background-size: 100% 100%; background-repeat: no-repeat; }
/* Toggled by js/contact-modal.js while the Turnstile gate is up. Applied to
   .contact-card-art (not <form>) so the blur actually hits the visible baked
   art — the form's own input/button elements are transparent hit-targets and
   blurring them alone is invisible. */
.contact-card-gated { filter: blur(4px); pointer-events: none; }
.contact-modal-close { position: absolute; top: 16px; right: 16px; z-index: 2;
  width: 50px; height: 50px; display: flex; align-items: center; justify-content: center;
  border: none; border-radius: 50%; font-size: 34px; line-height: 1;
  background: rgba(31, 27, 24, 0.55); color: #ECE3D0; padding: 0 0 8px; }
  /* padding-bottom (not a transform, box is border-box) shifts the flex-centered
     line box up ~4px — the × glyph's ink sits below true visual centre of its
     line box, so geometric centring alone renders it low (Daniel, 2026-07-10). */
.contact-modal-close:hover { background: rgba(31, 27, 24, 0.75); }
.contact-modal-close:focus-visible { outline: 2px solid #15707E; outline-offset: 2px; }
/* Heading, subhead, and field labels are baked into the art — kept as real DOM
   elements for screen readers + aria-labelledby, never shown visually. */
.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; }
.contact-field { position: absolute; left: 273px; width: 415px; }
.contact-field--name { top: 498px; height: 70px; }
.contact-field--email { top: 626px; height: 70px; }
.contact-field--message { top: 751px; height: 140px; }
.contact-field input, .contact-field textarea { width: 100%; height: 100%; box-sizing: border-box;
  font-family: 'Inter', system-ui, sans-serif; font-size: 17px; color: #1F1B18;
  background: transparent; border: none; padding: 16px 18px; }
.contact-field textarea { resize: none; overflow-y: auto; }
.contact-field input:focus, .contact-field textarea:focus { outline: 2px solid #15707E;
  outline-offset: 1px; border-radius: 2px; }
.contact-field.has-error input, .contact-field.has-error textarea { outline: 2px solid #cd3a6a; }
/* No background chip — a flat solid-colour rectangle sitting on the photographic
   card art read as a pasted-on sticker rather than text on paper (Daniel's call). */
.contact-field-error { position: absolute; left: 0; top: 100%; margin-top: 2px;
  font-family: 'Inter', sans-serif; font-size: 12px; color: #cd3a6a; }
/* Honeypot — visually gone, still in the DOM for bots. */
.contact-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
/* Positioned in case Turnstile ever falls back to a visible challenge (rare —
   the invisible-mode dev key normally shows no chrome at all); sits low in
   the panel so it doesn't collide with the cat art above it. */
#contact-turnstile { position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%); }
.contact-gate { position: absolute; inset: 0; z-index: 1; display: flex;
  align-items: center; justify-content: center; }
.contact-gate[hidden] { display: none; }
/* Torn-paper collage art (Daniel's asset, 2026-07-08): a cat photo with
   "Cloudflare is checking you're a real human. Please enjoy an image of my
   cat while you wait." baked in. Same transparent/background-image pattern
   as .contact-modal-card — the art carries its own card shape, so no
   background-color/border/box-shadow here (a boxed panel behind it would
   show as a mismatched rectangle around the collage's irregular edges). */
.contact-gate-panel { position: relative; width: 420px; height: 525px;
  background-color: transparent; border: none; box-shadow: none; padding: 0;
  background-image: url('../assets/img/dev-drops/contact jess gate cat.webp');
  background-size: 100% 100%; background-repeat: no-repeat; }
.contact-gate-panel:focus-visible { outline: 2px solid #15707E; outline-offset: 2px; }
.contact-submit { position: absolute; left: 343px; top: 916px; width: 222px; height: 70px;
  background: none; border: none; padding: 0; }
.contact-submit:focus-visible { outline: 2px solid #15707E; outline-offset: 2px; }
.contact-form-busy .contact-submit { opacity: 0.5; pointer-events: none; }
.contact-msg { position: absolute; left: 273px; top: 1087px; width: 415px;
  font-family: 'Inter', sans-serif; font-size: 13px; }
.contact-msg.hidden { display: none; }
.footer-strip-link { border: none; background: none; cursor: pointer; padding: 0;
  font: inherit; color: inherit; text-decoration: underline; }
@media (prefers-reduced-motion: reduce) { .contact-modal-backdrop { backdrop-filter: none; } }
