/* ============================================================
   Stephen Cotton — personal site
   Plain CSS, no framework. Full-screen scrolling video panels.
   Tweak the tokens below to restyle.
   ============================================================ */

:root {
  --bg: #0a0a0c;
  --ink: #f5f3ef;          /* primary text */
  --ink-soft: #b9b6b0;     /* secondary text */
  --line: rgba(245, 243, 239, 0.16);
  --accent: #e9e6df;

  --max: 1180px;           /* content max width */
  --pad: clamp(1.25rem, 4vw, 3rem);

  /* Locked by JS to the LARGE viewport (100lvh probe) — v227. The old svh/innerHeight
     lock captured the bar-EXPANDED height at load and left every section short. */
  --app-h: 100svh;   /* fallback for engines without lvh */
  --app-h: 100lvh;

  --font-display: "Fraunces", Georgia, "Times New Roman", serif;
  --font-ui: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

* {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Gentle snap from one full-screen panel to the next. */
  scroll-snap-type: none;   /* JS momentum-settle handles paging now */
  scrollbar-width: none;    /* immersive scroll — hide the bar; it read as "moving while the pinned frame holds". Nav = menu + momentum-settle. */
  overflow-x: hidden;       /* clip horizontal at the ROOT (not body) — a body-level scroll container broke iOS Safari's position:sticky */
}
html::-webkit-scrollbar { display: none; }

/* PHONES: native scroll-snap so a fling STOPS at each section's top (like Stonecrop already
   does by being tall), instead of blowing past a shorter section without activating it (e.g.
   flicking straight through About so its internal bio-scroll never engages). Native snap —
   NOT the JS momentum-glide, which is deliberately OFF on phones because a *programmatic*
   scroll re-expands Safari's bar → the iOS video band. `proximity` + `scroll-snap-stop:always`
   = the fling stops at each top, yet a tall section's INTERIOR stays free to scroll (its next
   snap point is the following section's top, far below). Desktop keeps the JS settle. */
@media (max-width: 760px) {
  html { scroll-snap-type: y proximity; }
  .panel { scroll-snap-stop: always; }
}

body {
  margin: 0;
  font-family: var(--font-ui);
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a {
  color: inherit;
  text-decoration: none;
}

/* ============================================================
   Panels — each fills the screen and snaps into place
   ============================================================ */
.panel {
  position: relative;
  height: var(--app-h);
  min-height: var(--app-h);
  scroll-snap-align: start;
  display: flex;
  align-items: center;
  justify-content: center;
  /* viewport-fit=cover lets the video go edge-to-edge under the notch/home-indicator; keep the
     CONTENT clear of the safe areas (env() is 0 on desktop, so this is a no-op there). */
  padding:
    max(var(--pad), env(safe-area-inset-top))
    max(var(--pad), env(safe-area-inset-right))
    max(var(--pad), env(safe-area-inset-bottom))
    max(var(--pad), env(safe-area-inset-left));
  overflow: hidden;
  isolation: isolate;
}

.panel__inner {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--max);
}

/* Text-column panels: the text column itself is the scroll surface (tight to the
   text, catches the wheel under the cursor) rather than the full-width inner —
   keeps the panel one screen tall without a far-margin scrollbar. */
#work-1 .panel__inner,
#stonecrop .panel__inner { height: 100%; }
#work-1 .coord,
#stonecrop .stone {
  max-height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* The bio glass is heavily rounded (200px top-right corner); the native scrollbar
   sits on the straight right edge and pokes past the curve, so hide it — wheel /
   trackpad / drag still scroll. */
#work-1 .coord { scrollbar-width: none; }              /* Firefox */
#work-1 .coord::-webkit-scrollbar { display: none; }   /* WebKit */
/* Bio wrapped in a GLASSMORPHISM panel (experimental — revert by dropping all but padding).
   The glass is "off" until #work-1 scrolls into view, then FADES IN with the text reveal
   (transition triggered by the panel's .is-visible state) instead of popping in solid.
   The scroll box IS the glass, so it stays put while the text scrolls inside it. */
#work-1 .bio-view {
  padding: clamp(2.25rem, 5vh, 3.5rem) clamp(1.6rem, 3vw, 2.75rem) clamp(2rem, 5vh, 3.5rem);
  background-color: rgba(14, 14, 18, 0);
  -webkit-backdrop-filter: blur(0px) saturate(1);
  backdrop-filter: blur(0px) saturate(1);
  border: 1px solid rgba(255, 255, 255, 0);
  border-radius: 30px 200px 30px 76px;   /* asymmetric: TL TR BR BL — a big top-right sweep opens the corner so more of the video shows; bottom-left stays a gentle 76 */
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0);
  transition: background-color 0.9s ease, border-color 0.9s ease, box-shadow 0.9s ease,
              -webkit-backdrop-filter 0.9s ease, backdrop-filter 0.9s ease;
}
#work-1.is-visible .bio-view {
  background-color: rgba(14, 14, 18, 0.4);
  -webkit-backdrop-filter: blur(18px) saturate(1.15);
  backdrop-filter: blur(18px) saturate(1.15);
  border-color: rgba(255, 255, 255, 0.11);
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.34);
}

/* ----- Background: video over an animated gradient fallback ----- */
.panel__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: var(--bg);
  overflow: hidden;
}

.panel__bg::before {
  content: "";
  position: absolute;
  inset: -25%;
  background:
    radial-gradient(38% 48% at 22% 28%, rgba(86, 124, 209, 0.40), transparent 70%),
    radial-gradient(42% 52% at 80% 22%, rgba(206, 92, 142, 0.34), transparent 70%),
    radial-gradient(48% 58% at 62% 82%, rgba(72, 184, 162, 0.30), transparent 70%);
  filter: blur(48px) saturate(1.1);
  animation: drift 26s ease-in-out infinite alternate;
}

@keyframes drift {
  0%   { transform: translate3d(-3%, -2%, 0) scale(1.05); }
  100% { transform: translate3d(3%, 3%, 0) scale(1.18); }
}

html[data-motion-paused] .panel__bg::before {
  animation-play-state: paused;
}

.panel__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;                 /* fade in once it's actually playing */
  transition: opacity 1s ease;
  transform: translateZ(0);            /* iOS: own GPU layer so the picture actually paints */
  -webkit-transform: translateZ(0);
}

.panel__video.is-playing {
  opacity: 1;
}

/* Blurred full-screen background video (mobile reveal sections): fills the screen behind the
   sharp landscape video, which sits on top as a full-width band. Hidden on desktop. */
.panel__blur {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1s ease;
  transform: translateZ(0);
  display: none;              /* only shown on the mobile reveal sections (below) */
}
.panel__blur.is-playing { opacity: 1; }
/* On phones the blur fills the screen and the sharp landscape video becomes a full-width band on top. */
@media (max-width: 760px) {
  #stonecrop .panel__blur, #community-square .panel__blur,
  #work-1 .panel__blur { display: block; }   /* full-screen blurred twin behind the sharp band */
  /* AUDIO-FIRST v2: the sharp band is HIDDEN (the blur shows through) until the section has arrived
     (~85%, .vent-arrived) AND the sharp is genuinely painting (.is-playing) — so a cold video never
     flashes black on arrival, and the blur alone carries the approach (silent, keeps warm). */
  #stonecrop .panel__video, #community-square .panel__video { opacity: 0; transition: opacity 0.5s ease; }
  #stonecrop.vent-arrived .panel__video.is-playing,
  #community-square.vent-arrived .panel__video.is-playing { opacity: 1; }
  /* v225: REVERTED the v223 cover experiment — the DESIGN for stonecrop/democraci on
     mobile is the horizontal sharp video sized to the WIDTH (contain = a full-width
     band mid-screen) over the full-screen blur twin. The gray-at-the-pill complaint is
     about the twin's visibility under the bar, not the letterbox composition. */
  #stonecrop .panel__video, #community-square .panel__video { object-fit: contain; }
}

/* Poster as the panel backdrop — shows while the video buffers or hasn't painted yet,
   so a loading panel reads as a still image instead of black (matters most on mobile). */
.panel__bg {
  background-size: cover;
  background-position: center;
}
#home .panel__bg,
#connect .panel__bg,
#community-square-labs .panel__bg { background-image: url('assets/poster.jpg'); }
#work-1 .panel__bg { background-image: url('assets/murmuration-poster.jpg'); }
#community-square .panel__bg { background-image: url('assets/democraciMatters-poster.jpg'); }
#stonecrop .panel__bg { background-image: url('assets/stonecrop-poster.jpg'); }

/* ----- Overlay: dark scrim for legibility + per-section color grade ----- */
.panel__overlay {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom, rgba(10, 10, 12, 0.55) 0%, rgba(10, 10, 12, 0.28) 38%, rgba(10, 10, 12, 0.72) 100%),
    radial-gradient(120% 120% at 50% 0%, transparent 45%, rgba(10, 10, 12, 0.55) 100%);
}

/* Subtle mood wash so each section feels distinct (kept soft for legibility) */
.panel__overlay::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0.5;
  mix-blend-mode: soft-light;
  background: transparent;
}
.panel[data-grade="warm"] .panel__overlay::after {
  background: linear-gradient(120deg, #c0631e, #6e2a10);
}
.panel[data-grade="cool"] .panel__overlay::after {
  background: linear-gradient(120deg, #1e63c0, #0f3168);
}
.panel[data-grade="dark"] .panel__overlay::after {
  background: #05060a;
  opacity: 0.55;
  mix-blend-mode: normal;
}

/* ============================================================
   Fixed nav
   ============================================================ */
.nav {
  position: fixed;
  text-transform: lowercase;
  top: 0;
  left: 0;
  right: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: calc(clamp(1rem, 2.5vw, 1.75rem) + env(safe-area-inset-top, 0px)) var(--pad) clamp(1rem, 2.5vw, 1.75rem);
  transition: opacity 0.45s ease;
}

.nav::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, rgba(10, 10, 12, 0.5), transparent);
  pointer-events: none;
  z-index: -1;
}

/* The name is a PERSISTENT top-right brand that doubles as the menu trigger.
   Pulled out of the auto-hiding .nav so it stays visible throughout (even while
   idle / scroll-reading). Positioned at the menu's first-item spot + right edge,
   so when the menu opens it lands right on top of the name. */
.nav__brand {
  position: fixed;
  top: calc(clamp(1.4rem, 4vh, 3rem) + 1.5rem + 1px + 0.12rem + env(safe-area-inset-top, 0px));   /* lands EXACTLY on the first item's text: +1px (menu's border) +0.12rem (item's top padding) — so the name doesn't jump when the menu opens; +safe-area so it clears the notch */
  right: calc(clamp(1.2rem, 3vw, 2.8rem) + 1.3rem + 1px);         /* +1px (menu's border) so the right edges match too */
  z-index: 41;                                         /* above the menu (z40) — the name stays visible as the menu's header */
  text-align: right;
  white-space: nowrap;
  cursor: pointer;
  font-family: var(--font-ui);
  font-weight: 400;                          /* match the menu items (About, etc.) */
  font-size: clamp(1.1rem, 2vw, 1.5rem);     /* SAME size as the menu items, so the item copy is the same size too */
  letter-spacing: 0.015em;
  text-transform: lowercase;
  color: var(--ink);
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.85), 0 0 24px rgba(0, 0, 0, 0.5);
  opacity: 0;
  transition: opacity 0.25s ease;
}
/* The name sits in the top-right at 50% and IS the menu's first item ("s cotton
   jr." replaces "Welcome"). When the menu opens this persistent copy fades out and
   the real first item (.site-toc__item--home — identical text, same spot) takes
   over, so it can pick up the active white + caret on the home screen. */
body.entered .nav__brand { opacity: 0.5; }
body.menu-open .nav__brand { opacity: 0; pointer-events: none; }
/* v358 (Stephen: "remove s cotton jr. from the welcome screen"): on PHONES the welcome is an
   in-flow cover, so the page is born with body.entered already set (script.js ~211) — which
   otherwise reveals the brand right on top of the welcome gate. `html.gate-hold` is present
   only while that gate is up (removed on the tap that enters), so it's the exact hook. No
   effect on desktop, where gate-hold is never set and `entered` waits for the click.
   !important because the base reveal above matches at the same specificity. */
html.gate-hold .nav__brand { opacity: 0 !important; pointer-events: none !important; }

.nav__links {
  display: flex;
  gap: clamp(1.1rem, 3vw, 2.25rem);
  font-size: 0.95rem;
  color: var(--ink-soft);
}
.nav__links a {
  position: relative;
  padding: 0.25rem 0;
  transition: color 0.2s ease;
}
.nav__links a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.28s ease;
}
.nav__links a:hover { color: var(--ink); }
.nav__links a:hover::after { transform: scaleX(1); }

/* ============================================================
   Hero (panel 1)
   ============================================================ */
/* Hero content sits in the bottom-left, above the scroll cue (verse over name) */
.panel--hero {
  align-items: stretch;            /* inner spans the full panel height */
  justify-content: flex-start;
  text-transform: lowercase;
}
.panel--hero .panel__inner {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;       /* group sits low — quote bottom near the scroll cue, name just above */
  align-items: flex-start;
  padding-bottom: clamp(3.25rem, 7.5vh, 4.75rem);   /* lands the quote's bottom on the scroll-cue plane */
}
/* Mobile: lift the name/quote group for more separation from the URL pill (scroll cue is hidden here). */
@media (max-width: 760px) {
  .panel--hero .panel__inner { padding-bottom: clamp(5.25rem, 12vh, 8rem); }
}

/* ============================================================
   Coordination statement (#work-1) + Stonecrop (#stonecrop) —
   tall content panels: grow to fit, at least one screen tall.
   ============================================================ */
.panel--coord,
.panel--stonecrop {
  height: var(--app-h);
  padding-block: clamp(2.5rem, 5vh, 4rem);
}

/* Legibility scrim weighted to the left, where the text column sits,
   over the low-contrast murmuration / stonecrop frames. */
.panel--coord .panel__overlay::before,
.panel--stonecrop .panel__overlay::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to right,
    rgba(8, 8, 12, 0.86) 0%,
    rgba(8, 8, 12, 0.62) 30%,
    rgba(8, 8, 12, 0.22) 58%,
    transparent 80%
  );
}

/* Stonecrop runs a lighter scrim than #work-1 so the product-montage video
   reads through behind the copy (text will be repositioned to suit). */
.panel--stonecrop .panel__overlay::before { display: none; }   /* replaced by the venture__foot corner scrim */
/* #work-1: lighter base scrim than the generic (0.55/0.28/0.72) so the video reads
   brighter — the glass bio panel + the left radial below carry the text's legibility. */
#work-1 .panel__overlay {
  background:
    linear-gradient(to bottom, rgba(10, 10, 12, 0.20) 0%, rgba(10, 10, 12, 0.08) 38%, rgba(10, 10, 12, 0.30) 100%),
    radial-gradient(120% 120% at 50% 0%, transparent 45%, rgba(10, 10, 12, 0.20) 100%);
}
/* #work-1: a touch darker on the LEFT (the text column) via a soft radial pooled at the
   left edge — NOT a straight left-to-right band (per Stephen); fades organically toward the crowd. */
#work-1 .panel__overlay::before {
  background: radial-gradient(
    70% 170% at 0% 50%,
    rgba(8, 8, 12, 0.92) 0%,
    rgba(8, 8, 12, 0.6) 34%,
    rgba(8, 8, 12, 0.24) 60%,
    transparent 82%
  );
}
/* Lighter base scrim too — the venture__foot gradient + top-line text-shadow
   carry legibility now, so the coverage-map video reads through much brighter. */
#stonecrop .panel__overlay {
  background:
    linear-gradient(to bottom, rgba(10, 10, 12, 0.20) 0%, rgba(10, 10, 12, 0.06) 42%, rgba(10, 10, 12, 0.30) 100%),
    radial-gradient(120% 120% at 50% 0%, transparent 52%, rgba(10, 10, 12, 0.20) 100%);
}

/* Capstan product still, anchored in the Stonecrop section's bottom-right. */
.stone__products {
  position: absolute;
  right: clamp(1rem, 4vw, 3rem);
  bottom: clamp(1rem, 4vh, 3rem);
  width: clamp(320px, 44vw, 800px);
  height: auto;
  z-index: 2;
  pointer-events: none;
  filter: drop-shadow(0 8px 22px rgba(0, 0, 0, 0.5)) drop-shadow(0 28px 60px rgba(0, 0, 0, 0.6));
}
@media (max-width: 760px) {
  .stone__products { position: static; width: 100%; margin: 2rem 0 0; filter: none; }
}

/* Community Square gets the Stonecrop treatment: tall content panel, lighter left
   scrim so the democraciMatters video reads through, and its logo bottom-right. */
#community-square {
  height: auto;
  min-height: var(--app-h);
  padding-block: clamp(2.5rem, 5vh, 4rem);
}
/* Lighter scrim — section copy is hidden, so let the video read brighter */
#community-square .panel__overlay {
  background:
    linear-gradient(to bottom, rgba(10, 10, 12, 0.16) 0%, rgba(10, 10, 12, 0.05) 42%, rgba(10, 10, 12, 0.26) 100%),
    radial-gradient(120% 120% at 50% 0%, transparent 55%, rgba(10, 10, 12, 0.16) 100%);
}
#community-square .panel__overlay::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to right,
    rgba(8, 8, 12, 0.16) 0%,
    rgba(8, 8, 12, 0.05) 38%,
    transparent 65%
  );
}
/* Labs joins the venture treatment: lighter base scrim so the murmuration video reads
   through, and the dark grade eased — the venture__foot carries text legibility. */
#community-square-labs .panel__overlay {
  background:
    linear-gradient(to bottom, rgba(10, 10, 12, 0.30) 0%, rgba(10, 10, 12, 0.10) 42%, rgba(10, 10, 12, 0.50) 100%),
    radial-gradient(120% 120% at 50% 0%, transparent 52%, rgba(10, 10, 12, 0.32) 100%);
}
#community-square-labs .panel__overlay::after { opacity: 0.34; }
#community-square .cs-logo {
  position: absolute;
  right: clamp(1rem, 4vw, 3rem);
  bottom: clamp(1rem, 4vh, 3rem);
  width: clamp(320px, 44vw, 800px);
  height: auto;
  z-index: 2;
  pointer-events: none;
  filter: drop-shadow(0 8px 22px rgba(0, 0, 0, 0.5)) drop-shadow(0 28px 60px rgba(0, 0, 0, 0.6));
  transition: opacity 0.6s ease;
}
#community-square .cs-logo.is-idle { opacity: 0; }
/* democraci matters (company) wordmark — anchored top-LEFT to match the .venture__id
   identity strip on the other Work panels (was center-justified). White → needs a shadow. */
#community-square .cs-company {
  position: absolute;
  top: clamp(4.5rem, 9vh, 7rem);
  left: clamp(1.5rem, 5vw, 4rem);
  z-index: 3;
  text-align: left;
  pointer-events: none;
  transition: opacity 0.6s ease;
}
#community-square .cs-company.is-idle { opacity: 0; }
/* Stonecrop text + product logo auto-hide (same idle behavior as the logos) */
#stonecrop .stone,
#stonecrop .stone__products { transition: opacity 0.6s ease; }
#stonecrop .stone.is-idle,
#stonecrop .stone__products.is-idle { opacity: 0; }
#community-square .cs-company__logo {
  width: clamp(200px, 26vw, 360px);
  height: auto;
  filter: drop-shadow(0 6px 22px rgba(0, 0, 0, 0.6));
}
#community-square .cs-company__role {
  margin: 0.55rem 0 0;
  font-size: 0.78rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-soft);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.75), 0 2px 16px rgba(0, 0, 0, 0.55);
}
@media (max-width: 760px) {
  #community-square .cs-logo { position: static; width: 100%; margin: 2rem 0 0; filter: none; }
}

.coord { max-width: 45rem; }
.coord__headline {
  margin: 0 0 0.7rem;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.9rem, 4.4vw, 3.3rem);
  line-height: 1.08;
  letter-spacing: -0.01em;
  color: var(--ink);
}
/* Bio hero — prominent, a step below the deep-insight display headline. */
#work-1 .bio-view .coord__headline {
  font-size: clamp(1.7rem, 3vw, 2.5rem);
  line-height: 1.25;
  padding-right: clamp(1.75rem, 4vw, 3rem);   /* keep the headline clear of the big top-right corner sweep */
}
.coord__sub {
  margin: 0 0 1.5rem;
  max-width: 38rem;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.05rem, 1.7vw, 1.3rem);
  line-height: 1.4;
  color: var(--ink-soft);
}
.coord p {
  margin: 0 0 0.9rem;
  max-width: 40rem;
  font-size: clamp(1rem, 1.5vw, 1.15rem);
  line-height: 1.5;
  color: var(--ink-soft);
}
.coord__lead {
  margin: 0 0 1.4rem;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.3rem, 2.3vw, 1.75rem);
  line-height: 1.22;
  color: var(--ink);
}
.coord em { font-style: italic; color: var(--ink); }
/* About (#work-1): the glass card is a FIXED WINDOW; the bio text scrolls WITHIN it,
   driven by the ONE page scroll (no inner scrollbar). The video + glass frame stay put —
   only the text moves. Panel height is set by JS (bioScroll IIFE) = app-h + text overflow
   so the page scroll maps 1:1 to the text. */
#work-1 {
  /* GENEROUS floor so the panel — and thus the momentum-scroll's free-zone — is ALWAYS
     tall enough for the whole bio, even before the JS measures (a short panel was letting
     the settle snap out mid-read). JS sets an exact `height` that wins on short viewports
     where the text needs more than 100vh. */
  min-height: calc(var(--app-h) + 100vh);
  padding: 0;
  display: block;
  overflow: visible;
}
#work-1 .panel__bg {          /* video: sticky → stays fixed while the text scrolls */
  position: sticky;
  top: 0;
  height: var(--app-h);
}
#work-1 .panel__inner {       /* sticky holder over the video = positioning context for the window */
  position: sticky;
  top: 0;
  height: var(--app-h);
  margin: calc(-1 * var(--app-h)) auto 0;   /* pull up over the sticky video; center at --max */
  padding: 0;
}
#work-1 .bio-view {           /* the GLASS WINDOW — fixed height, stays put; clips the scrolling text */
  position: absolute;
  top: clamp(4rem, 10vh, 7rem);
  bottom: clamp(2.5rem, 7vh, 5rem);   /* separation from the bottom of the page */
  left: 0;
  width: min(45rem, 100%);
  max-height: none;
  padding: 0;                 /* padding moves onto .bio-scroll so it travels with the text */
  overflow: clip;             /* clip like hidden (rounded corners respected) BUT not a scroll
                                 container — so the wheel bubbles to the page even over the glass
                                 (overflow:hidden here was eating the scroll) */
  overscroll-behavior: auto;  /* undo the inherited `contain` (moot with clip, but explicit) */
}
#work-1 .bio-scroll {         /* the text — JS translates it up as you scroll the page */
  padding: clamp(2.25rem, 5vh, 3.5rem) clamp(1.6rem, 3vw, 2.75rem);
  will-change: transform;
}
#work-1 .deep-view {          /* the hidden alt view stays unclipped */
  overflow: visible;
  max-height: none;
}

/* ============================================================
   ABOUT — gradient-blur mode (DEFAULT on desktop; body.about-blur is added unless
   ?aboutblur=0). Swaps the glass reading CARD for a full-height LEFT→RIGHT gradient
   blur (mirror of the menu): heavy on the far left behind the text, tapering to clear
   so the RIGHT SIDE stays clear video. The text column eases in from the left, adaptive
   to viewport width. Desktop only. To revert to the glass card: load ?aboutblur=0, or
   disable the body.about-blur JS line (or delete this block).
   ============================================================ */
@media (min-width: 761px) {
  /* let the window reach the screen's left edge (the inner is normally centered at --max) */
  body.about-blur #work-1 .panel__inner { max-width: none; }
  /* strip the glass card — the gradient blur (below, on .panel__inner) is the backdrop now.
     Push the text left + narrow it so the right half stays clear video. A soft top/bottom
     fade replaces the card frame so the scroll-clipped text doesn't hard-cut. */
  body.about-blur #work-1 .bio-view,
  body.about-blur #work-1.is-visible .bio-view {
    /* ADAPTIVE left: hug the left (~3rem) on normal screens, but as the viewport grows
       past the 84rem "stage", ease the text rightward toward a centered reading column
       so it isn't marooned far-left on very wide monitors. Lower the 84rem to pull it
       further right; raise it to keep it more left. Width is capped, so the text always
       stays over the blurred zone. */
    left: max(3rem, calc((100vw - 84rem) / 2));
    width: min(40rem, 46vw);
    background-color: transparent;
    -webkit-backdrop-filter: none; backdrop-filter: none;
    border-color: transparent; box-shadow: none; border-radius: 0;
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 5%, #000 93%, transparent 100%);
    mask-image: linear-gradient(to bottom, transparent 0, #000 5%, #000 93%, transparent 100%);
  }
  /* twin-layer blur on the sticky INNER (not the overflow:clip .bio-view), masked to the
     LEFT (heavy far-left → clear by mid-screen). Fades in with the section, like the glass.
     ⚠ position:ABSOLUTE (not fixed) so it's scoped to this section's sticky inner — while
     About is on screen the inner pins to the viewport (blur covers it), and it scrolls away
     with the section afterward. (fixed made the blur bleed onto home/Stonecrop, because the
     .is-visible gate is a one-way "revealed" flag that never turns back off.) */
  body.about-blur #work-1 .panel__inner::before,
  body.about-blur #work-1 .panel__inner::after {
    content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
    opacity: 0; transition: opacity 0.9s ease;
  }
  body.about-blur #work-1.is-visible .panel__inner::before,
  body.about-blur #work-1.is-visible .panel__inner::after { opacity: 1; }
  body.about-blur #work-1 .panel__inner::before {   /* light base blur + left darken for legibility */
    background: linear-gradient(to right, rgba(10, 10, 13, 0.46) 0%, rgba(10, 10, 13, 0.18) 46%, transparent 70%);
    -webkit-backdrop-filter: blur(6px) saturate(1.05); backdrop-filter: blur(6px) saturate(1.05);
    -webkit-mask-image: linear-gradient(to right, #000 0%, rgba(0,0,0,0.5) 48%, transparent 72%);
    mask-image: linear-gradient(to right, #000 0%, rgba(0,0,0,0.5) 48%, transparent 72%);
  }
  /* heavier blur — carried FARTHER across the text before tapering (Stephen: "denser blur
     a bit farther before it tapers off", then "extend a little farther right"). */
  body.about-blur #work-1 .panel__inner::after {
    -webkit-backdrop-filter: blur(20px) saturate(1.12); backdrop-filter: blur(20px) saturate(1.12);
    -webkit-mask-image: linear-gradient(to right, #000 0%, rgba(0,0,0,0.5) 38%, transparent 62%);
    mask-image: linear-gradient(to right, #000 0%, rgba(0,0,0,0.5) 38%, transparent 62%);
  }
}
/* Deep-insight toggle button */
.insight-toggle {
  margin-top: 1.6rem;
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  font-size: 0.82rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-soft);
  cursor: pointer;
  transition: color 0.25s ease;
}
.insight-toggle:hover,
.insight-toggle:focus-visible { color: var(--ink); }
.insight-toggle--back { margin: 0 0 1.6rem; }
.coord__label {
  margin: 1.4rem 0 0;
  font-size: 0.78rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* Highlights strip — a scannable metrics line under the lead.
   Flex-wrap segments; a trailing ::after middot separates them
   (so a dot never leads a wrapped line). Key figures in Fraunces.
   Qualified with `.coord p` so it beats `.coord p` (0,1,1) on size/color/margin. */
.coord .coord__stats {
  display: flex;
  flex-direction: column;         /* v278: single vertical list, not wrapping columns */
  align-items: flex-start;
  gap: 0.4rem;
  margin: 1.5rem 0 1.9rem;
  padding-left: 1.1rem;           /* slight indentation */
  max-width: 44rem;
  font-family: var(--font-ui);
  font-size: 0.9rem;
  line-height: 1.45;
  color: var(--ink-soft);
}
.coord__stats > span { position: relative; }
.coord__stats > span:not(:last-child)::after {
  content: none;
  margin-left: 1rem;
  color: var(--ink-soft);
  opacity: 0.5;
}
.coord__stats b {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.05rem;
  color: var(--ink);
}

.coord__stats-nested {
  margin-top: 0.6rem;
  margin-left: 1.2rem;
  padding-left: 1rem;
  border-left: 2px solid rgba(245, 243, 239, 0.4);
}

/* That nested list uses .venture__did, which has bullets; the vertical line
   is meant to replace them, so hide the ::before pseudo-element. */
.venture__did.coord__stats-nested li::before {
  display: none;
}
/* Three exits — quiet menu rows that slide on hover/focus */
.exits {
  display: flex;
  margin-top: 1.6rem;
  border-top: 1px solid var(--line);
  overflow-x: auto;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}
.exit {
  flex: 1 1 0;
  min-width: 14.5rem;
  display: flex;
  flex-direction: column;
  padding: 1.5rem 1.7rem;
  border-left: 1px solid var(--line);
  color: inherit;
  transition: background 0.4s ease;
}
.exit:first-child {
  border-left: none;
  padding-left: 0;
}
.exit:hover,
.exit:focus-visible {
  background: rgba(245, 243, 239, 0.05);
  outline: none;
}
.exit__kicker {
  display: block;
  font-size: 0.68rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.exit__title {
  display: block;
  margin: 0.25rem 0 0.35rem;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.5rem, 3vw, 2.1rem);
  line-height: 1.04;
  color: var(--ink);
}
.exit__title::after {
  content: " →";
  font-size: 0.78em;
  opacity: 0;
  transition: opacity 0.45s ease;
}
.exit:hover .exit__title::after,
.exit:focus-visible .exit__title::after { opacity: 0.65; }
.exit__line {
  display: block;
  max-width: 40rem;
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--ink-soft);
}

/* Stonecrop body */
.stone { max-width: 52rem; }
.stone__kicker {
  margin: 0 0 1.3rem;
  font-size: 0.8rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.stone__headline {
  margin: 0 0 1.6rem;
  max-width: 22em;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.75rem, 3.8vw, 3rem);
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.stone__body {
  margin: 0 0 1.1rem;
  max-width: 42rem;
  font-size: clamp(1rem, 1.45vw, 1.14rem);
  line-height: 1.62;
  color: var(--ink-soft);
}
.stone__body em { font-style: italic; color: var(--ink); }
.stats {
  list-style: none;
  margin: 1.8rem 0;
  padding: 1.4rem 0;
  max-width: 47rem;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.3rem 2.2rem;
}
.stats li { display: flex; flex-direction: column; gap: 0.28rem; }
.stats__num {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.5rem, 3vw, 2.05rem);
  line-height: 1;
  color: var(--ink);
}
.stats__cap {
  font-size: 0.84rem;
  line-height: 1.4;
  color: var(--ink-soft);
}
.stone__role {
  margin: 0;
  max-width: 42rem;
  font-size: 0.92rem;
  line-height: 1.55;
  color: var(--ink-soft);
}

/* ============================================================
   Venture panels: a persistent top-left identity LINE + a bottom-left
   key-info block over a corner-weighted scrim (darkest at the bottom-left
   text, clear elsewhere). The scrim + info fade together on cursor-idle
   (reusing #stonecrop's setIdle → .is-idle), so the video plays
   unobstructed once you stop moving. The top line stays.
   ============================================================ */
.venture__id {
  position: absolute;
  margin: 0;   /* it's a <p> — kill the UA ~1em margin; on an absolute box it was shoving the strip ~18px BELOW the "Welcome" menu item (the real cause of the height mismatch) */
  top: calc(clamp(1.4rem, 4vh, 3rem) + 1.9rem);   /* vertically CENTER the strip on the "Welcome" item: toc top + its 1.5rem padding + ~0.35rem to center this shorter caps line on the taller menu text */
  left: clamp(1.5rem, 5vw, 4rem);
  z-index: 3;
  max-width: 90%;   /* wide enough for the longest strip (Stonecrop + two roles) to stay one line on desktop */
  font-family: var(--font-ui);
  font-size: clamp(1rem, 1.4vw, 1.25rem);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.85), 0 0 26px rgba(0, 0, 0, 0.5);
}
/* "democraci matters" wordmark set in type (replaces the logo image): lowercase,
   with "i matter" bold, sitting inside the uppercase .venture__id metadata line. */
.dm-mark {
  font-family: var(--font-ui);   /* Inter — matches the tile even inside the serif menu */
  text-transform: none;
  letter-spacing: 0.01em;
  font-weight: 400;
}
.dm-mark b { font-weight: 700; }

/* Slight top-left → top radial on every Career panel, so the identity strip
   reads cleanly over the video without a hard box. Sits over the video/overlay
   (z1) but under the strip (z3) and foot (z2). */
.panel[data-nav-group="Career Portfolio"]::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(115% 62% at 0% 0%, rgba(6, 7, 10, 0.58) 0%, rgba(6, 7, 10, 0.25) 32%, transparent 64%);
}

/* SCROLL-DARKENING (2026-07-08): a bottom-weighted dark gradient whose opacity
   tracks `--reveal-progress` (0 at the top → 1 at the panel bottom, set by the
   scroll-reveal JS) so the view darkens ~1:1 as you pull the info up — a cue that
   the scroll is driving the video-stop + reveal. Doubles as the reading aid (dark
   by the time the copy is up). Bounded to the lower screen (see the media block).
   Under the text (foot z2, strip z3); pointer-none. */
.panel[data-nav-group="Career Portfolio"]::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0;                           /* OLD bottom-weighted ramp retired — darkening is now
                                           UNIFORM over the whole video via .panel__bg::after below */
  background: none;
}
/* Scroll-darkening (2026-07-09): the WHOLE video darkens UNIFORMLY 1:1 with the scroll,
   riding the STICKY bg so it stays on the video (not a gradient rising with the content,
   which read as unclear motion). As the video sinks dark, the cards + logo read clearly.
   Sits under the panel content (bg is z0; content is z1+). */
.panel[data-nav-group="Career Portfolio"] .panel__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(6, 7, 10, 0.6);   /* max scrim — eased from 0.78 so the video still reads through a bit at full scroll */
  opacity: var(--dim, 0);   /* JS-computed: rises FAST, then EASES BACK OFF near the section end so the handoff to the next section is video-to-video, not black-to-video */
  pointer-events: none;
}
.venture__foot {
  position: absolute;
  inset: 0;                       /* full-panel so the radial has no boxed/clipped edge */
  z-index: 2;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;      /* text pinned to the bottom… */
  align-items: flex-start;        /* …left */
  padding: 0 clamp(1.5rem, 5vw, 4rem) clamp(1.75rem, 4.5vh, 3rem);
  /* Soft radial pooled in the bottom-left, fading to transparent WELL WITHIN the panel
     (the old bounded box's clipped border was the visible "square"). */
  background: radial-gradient(76% 90% at 0% 100%,
    rgba(6, 7, 10, 0.85) 0%,
    rgba(6, 7, 10, 0.46) 38%,
    rgba(6, 7, 10, 0.20) 58%,
    transparent 82%);
  transition: opacity 0.6s ease;
}
.venture__foot.is-idle { opacity: 0; }
/* Bottom-right corner scrim — soft radial darkening behind the product logo,
   mirrors the foot's bottom-left radial; fades with the logo on idle. */
.venture__corner {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(58% 68% at 100% 100%,
    rgba(6, 7, 10, 0.78) 0%,
    rgba(6, 7, 10, 0.40) 40%,
    transparent 74%);
  transition: opacity 0.6s ease;
}
.venture__corner.is-idle { opacity: 0; }

/* Glassmorphism play button — appears centered over the video once the section
   auto-pauses (info fully up), so it's clear the video is paused. Click it to glide
   back to the section top (video plays); clicking the video elsewhere glides down to
   the reveal (pause). Hidden by default — mobile never sets .info-revealed. */
.work-play {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) scale(3.4);
  width: 100px;
  height: 100px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease, transform 0.5s ease;
  z-index: 30;
}
/* Frosted-glass FILL, clipped to a rounded-corner play triangle (strong radii). */
.work-play::before {
  content: "";
  position: absolute;
  inset: 0;
  clip-path: path("M35 38 L35 62 Q35 74 45.5 68.3 L68.5 55.8 Q79 50 68.5 44.2 L45.5 31.8 Q35 26 35 38 Z");
  background: rgba(255, 255, 255, 0.12);
  -webkit-backdrop-filter: blur(7px) saturate(1.15);
  backdrop-filter: blur(7px) saturate(1.15);
}
/* OUTER STROKE — the same rounded triangle, outlined. Lives above the fill and is NOT
   clipped, so the full stroke shows around the glass edge. */
.work-play svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  display: block;
}
.work-play svg path {
  fill: none;
  stroke: rgba(255, 255, 255, 0.24);   /* thin + subtle, like the site's 1px glass border */
  stroke-width: 0.28;                   /* ~1px once the button is scaled up (×3.6) */
  stroke-linejoin: round;
}

/* (banksy silhouette footer removed 2026-07-09 — was .page-fig on #home + #work-1) */
/* Venture footer marks — the democraci torch (#community-square .cs-footer) + the
   Stonecrop mark (#stonecrop .stone-footer) — centered at the bottom of their panels,
   ANCHORED to the video bottom: position:fixed while pinned (.id-pin), so they stay on
   the video's lower edge instead of scrolling. (Base absolute for mobile: one screen,
   no .id-pin.) */
#community-square .cs-footer,
#stonecrop .stone-footer {
  position: absolute;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom, 0px) + clamp(0.9rem, 2.6vh, 2rem));
  transform: translateX(-50%);
  height: clamp(54px, 7vh, 84px);
  width: auto;
  z-index: 4;
  pointer-events: none;
  opacity: 0.92;
  filter: drop-shadow(0 3px 10px rgba(0, 0, 0, 0.5));   /* small lift off the video */
}
#community-square.id-pin .cs-footer,
#stonecrop.id-pin .stone-footer {   /* anchored to the video bottom (viewport) while pinned */
  position: fixed;
}
/* v304 — MOBILE: fade the footer mark out as the cards rise on scroll, so the logo and the
   containers don't compete. Tracks --reveal-progress (0 at the section top → 1 at its bottom);
   the *5 makes it clear once you've started scrolling in. */
@media (max-width: 760px) {
  #community-square .cs-footer,
  #stonecrop .stone-footer { opacity: calc(0.92 - var(--reveal-progress, 0) * 5); }
}
/* v306 — MOBILE: fade the identity strip out as the cards rise (so the strip and the rising
   containers don't collide); it fades back in when scrolled to the top. Tracks the same
   --reveal-progress; no transition so it tracks the scroll directly. (id+class beats .reveal.) */
@media (max-width: 760px) {
  #stonecrop.is-visible .venture__id,
  #community-square.is-visible .venture__id {
    opacity: calc(1 - var(--reveal-progress, 0) * 3.3);   /* gone by ~0.30 — just as the first card reaches it (~0.32) */
    transition: none;
  }
}
.panel[data-nav-group="Career Portfolio"].info-revealed .work-play {
  opacity: 1;
  pointer-events: auto;
  transform: translate(-50%, -50%) scale(3.6);
}

/* ============================================================
   Work (Career) panels — SCROLL-REVEAL. Each panel is 2x tall with the video
   ANCHORED (position: sticky) full-screen; the bottom-corner info (foot left,
   product + corner right) sits in the LOWER screen — below the fold — and
   parallax-rises 1:1 as you scroll. At the bottom it's fully up and the video
   auto-pauses: JS toggles `.info-revealed`, which fades in the reading vignette.
   The identity strip stays at the top and scrolls away with the video. Replaces
   the old cursor-idle fade (its JS is retired; .is-idle simply never gets set).
   Desktop/tablet only (>760px); mobile keeps its stacked static layout, so the
   whole block sits inside a min-width media query (JS is width-guarded to match).
   ============================================================ */
@media (min-width: 761px) {
#stonecrop, #community-square, #community-square-labs {
  --work-pad: calc(var(--app-h) * 0.5);   /* end buffer BELOW the content (half a screen), before the next section */
  height: calc(var(--app-h) * 2 + var(--work-pad));
  min-height: calc(var(--app-h) * 2 + var(--work-pad));
  overflow: visible;                  /* let the sticky video stick against the page scroll */
  padding: 0;                         /* the 2x height carries the layout; ANY padding narrows the in-flow sticky bg → black side strips */
  display: block;                     /* was flex-centered — top-align the sticky video so it anchors cleanly on entry */
}
.panel[data-nav-group="Career Portfolio"] .panel__bg {
  position: sticky;
  top: 0;
  height: var(--app-h);
}
/* Top-left identity radial: keep it ONE screen tall (the video area) so it isn't
   oversized by the 2x panel; it scrolls away with the strip. */
.panel[data-nav-group="Career Portfolio"]::before {
  bottom: auto;
  height: var(--app-h);
}
/* Bottom info + darkening live in the LOWER screen: one viewport tall, sitting
   [1x → 2x] so the --work-pad band [2x → 2.25x] below stays empty (the end buffer).
   They start below the fold and rise into view as you scroll. */
.panel[data-nav-group="Career Portfolio"] .venture__corner {
  top: var(--app-h);
  bottom: 0;   /* product scrim runs to the section bottom (no hard-cut "line") */
}
/* Work details -> a glassmorphic CARD, bottom-left. Matches the site glass
   (dark translucent + blur + thin 1px border). Shrink-wraps the copy; replaces
   the old full-width bottom-left radial. */
.panel[data-nav-group="Career Portfolio"] .venture__foot {
  top: auto;
  right: auto;
  left: clamp(1.4rem, 4vw, 3rem);
  bottom: calc(var(--work-pad) + clamp(1.5rem, 3.5vh, 2.6rem));
  width: fit-content;
  max-width: min(40rem, 50vw);
  height: auto;
  padding: clamp(1.35rem, 2.2vw, 1.9rem) clamp(1.5rem, 2.4vw, 2.1rem);
  background: rgba(15, 15, 19, 0.34);
  -webkit-backdrop-filter: blur(16px) saturate(1.15);
  backdrop-filter: blur(16px) saturate(1.15);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 26px;
  box-shadow: 0 26px 70px rgba(0, 0, 0, 0.55);
}
.panel[data-nav-group="Career Portfolio"] .venture__foot > :last-child { margin-bottom: 0; }
/* The darkening runs PAST the copy, all the way to the section bottom (= the next
   section's top), so there's no lighter band left below the text. */
.panel[data-nav-group="Career Portfolio"]::after {
  top: var(--app-h);
  bottom: 0;
}
/* Identity strip stays anchored to the top through the reveal: JS toggles `.id-pin`
   while the panel is anchored, flipping it from absolute → fixed (same top/left, now
   viewport-relative), so it holds while the info pulls up beneath it. */
.panel[data-nav-group="Career Portfolio"].id-pin .venture__id { position: fixed; }
/* Product graphics: FADE IN 1:1 with scroll + stay anchored bottom-right of the video
   (position: fixed while the panel is anchored, via .id-pin) instead of sliding up. So
   the left card slides up while the logo just fades in, pinned to the video's corner. */
#stonecrop .stone__products, #stonecrop .venture__corner,
#community-square .cs-logo, #community-square .venture__corner {
  opacity: calc(var(--reveal-progress, 0) * 2);   /* full brightness by ~half the scroll — bright before the copy finishes rising (opacity clamps at 1) */
  transition: none;
}
#stonecrop.id-pin .stone__products,
#community-square.id-pin .cs-logo {
  position: fixed;
  bottom: clamp(1rem, 4vh, 3rem);
  right: clamp(1.2rem, 4vw, 3rem);
}
#stonecrop.id-pin .venture__corner,
#community-square.id-pin .venture__corner {
  position: fixed;
  inset: 0;
}
}  /* end @media (min-width: 761px) — Career scroll-reveal */

/* ============================================================
   STONECROP — MULTI-PANE chapter (desktop). Works EXACTLY like the democraci/Labs
   scroll-reveal, just with SEVERAL glass cards instead of one .venture__foot: the
   video stays sticky (anchored) + plays, dims 1:1 with scroll, and AUTO-PAUSES at ~92%
   reveal (script.js) so it doesn't compete with the copy. The cards ride in-flow BELOW
   the sticky video's 1x — so they start under the fold and RISE into view as you scroll
   (no negative margin: the video reserves the top screen, the cards follow beneath it).
   New .stone-* classes; the venture__lead/chips/did/lesson text styles are reused. */
@media (min-width: 761px) {
  #stonecrop, #community-square { height: auto; }  /* video (1x) + the cards define the height */
  /* This chapter is tall (many cards), so --reveal-progress climbs slowly; boost the dim
     ramp (×5 vs the shared ×2.2) so the video reaches full dark AS the first card rises —
     matching democraci's short-section timing rather than staying bright halfway in. */
  #stonecrop .panel__bg::after { opacity: var(--dim, 0); }   /* multiplier now lives in JS */
  /* Top fade band — fixed at the viewport top, layered ABOVE the rising cards (z2, this
     comes after .stone-panes in the DOM) but BELOW the identity strip (z3). Cards dissolve
     into it before they collide with the strip; it fades in with the scroll dim so the
     bright entry stays clean. */
  #stonecrop .stone-topfade {
    position: fixed;
    top: 0; left: 0; right: 0;
    height: 15vh;
    z-index: 2;
    pointer-events: none;
    background: linear-gradient(to bottom,
      rgba(6, 7, 10, 0.9) 0%,
      rgba(6, 7, 10, 0.82) 42%,
      rgba(6, 7, 10, 0.4) 70%,
      transparent 100%);
    opacity: calc(var(--reveal-progress, 0) * 3);
  }
  #stonecrop .stone-panes, #community-square .stone-panes {
    position: relative;
    z-index: 2;                                   /* over the video + darkening (z0), under the id (z3) */
    padding: 22vh clamp(1.4rem, 7vw, 9rem) 42vh;  /* v286: wider side gutters so the card column pulls off the left edge and reads more centered */
  }
  #stonecrop .stone-pane, #community-square .stone-pane {
    position: relative;
    margin-bottom: 12vh;                          /* gap between the rising cards */
  }
  #stonecrop .stone-pane:last-child, #community-square .stone-pane:last-child { margin-bottom: 0; }
  /* Zig-zag: odd cards (1,3) stay far-left (aligned with the id strip); even cards (2,4)
     shift toward the right so the column staggers as it rises. The shift caps at 40rem so
     it right-aligns on laptops but doesn't fling to the far edge on ultra-wide monitors. */
  #stonecrop .stone-pane:nth-child(even) .stone-card,
  #stonecrop .stone-pane:nth-child(even) .stone__products,
  #community-square .stone-pane:nth-child(even) .stone-card {
    margin-left: clamp(0px, 100% - 40rem, 40rem);
  }
  /* Glassmorphic card — the SAME site glass as the democraci/Labs .venture__foot */
  /* STONECROP: glasmorphic commented out — now using gradient blur backdrop */
  /*
  #stonecrop .stone-card, #community-square .stone-card {
    width: min(40rem, 100%);
    padding: clamp(1.35rem, 2.2vw, 1.9rem) clamp(1.5rem, 2.4vw, 2.1rem);
    background: rgba(15, 15, 19, 0.34);
    -webkit-backdrop-filter: blur(16px) saturate(1.15);
    backdrop-filter: blur(16px) saturate(1.15);
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: 26px;
    box-shadow: 0 26px 70px rgba(0, 0, 0, 0.55);
  }
  */
  /* Stonecrop: keep layout, remove glass background */
  #stonecrop .stone-card {
    width: min(40rem, 100%);
    padding: clamp(1.35rem, 2.2vw, 1.9rem) clamp(1.5rem, 2.4vw, 2.1rem);
  }
  /* Community Square (democraci matters): remove glass background, use gradient blur backdrop */
  #community-square .stone-card {
    width: min(40rem, 100%);
    padding: clamp(1.35rem, 2.2vw, 1.9rem) clamp(1.5rem, 2.4vw, 2.1rem);
  }
  #stonecrop .stone-card > :last-child, #community-square .stone-card > :last-child { margin-bottom: 0; }
  /* Capstan visual flows below the platform card (card 3) */
  #stonecrop .stone-pane .stone__products {
    position: static;
    left: auto; top: auto; right: auto; bottom: auto;
    width: min(40rem, 100%);
    margin: 1.4rem 0 0;
    opacity: 1;
  }
  /* Stonecrop gradient blur backdrop (left→right): anchored to left browser edge, extends to right viewport edge.
     Strong blur on left, tapers to clear by 75% of viewport width. Only appears in card section below video.
     Positioned absolutely on .stone-panes but pulls to viewport left edge. Behind text (z-index: -1). */
  #stonecrop .stone-panes::before,
  #stonecrop .stone-panes::after {
    /* FULL-BLEED breakout. ⚠ inset:0 is NOT enough: at ≥1150px the stagger block (search
       "v288 — Stonecrop desktop STAGGER") caps .stone-panes at `max-width: 76rem !important`
       and centers it, so inset:0 would paint a centered 1216px column with bare video showing
       on both sides — it only *looked* right below 1150px, where .stone-panes is full width.
       left:50% + translateX(-50%) + 100vw re-anchors the blur to the true browser edges at
       every desktop width. Any scrollbar overshoot is clipped by .panel's overflow:hidden. */
    content: ""; position: absolute; top: 0; bottom: 0;
    left: 50%; transform: translateX(-50%); width: 100vw;
    z-index: -1; pointer-events: none;
    opacity: 0; transition: opacity 0.9s ease;
  }
  #stonecrop.is-visible .stone-panes::before,
  #stonecrop.is-visible .stone-panes::after { opacity: 1; }
  /* Masks hold FULL strength to 75%, then feather out to 100% (the browser's right edge).
     The heavy layer starts its taper earlier so the two blurs blend instead of stepping. */
  #stonecrop .stone-panes::before {
    background: linear-gradient(to right, rgba(10, 10, 13, 0.46) 0%, rgba(10, 10, 13, 0.2) 60%, transparent 100%);
    -webkit-backdrop-filter: blur(6px) saturate(1.05); backdrop-filter: blur(6px) saturate(1.05);
    -webkit-mask-image: linear-gradient(to right, #000 0%, #000 75%, transparent 100%);
    mask-image: linear-gradient(to right, #000 0%, #000 75%, transparent 100%);
  }
  #stonecrop .stone-panes::after {
    -webkit-backdrop-filter: blur(20px) saturate(1.12); backdrop-filter: blur(20px) saturate(1.12);
    -webkit-mask-image: linear-gradient(to right, #000 0%, #000 62%, rgba(0,0,0,0.45) 82%, transparent 100%);
    mask-image: linear-gradient(to right, #000 0%, #000 62%, rgba(0,0,0,0.45) 82%, transparent 100%);
  }
  /* Community Square (democraci matters) gradient blur backdrop: anchored to left browser edge, extends to right viewport edge.
     Behind text (z-index: -1), only in card section below video. */
  #community-square .stone-panes::before,
  #community-square .stone-panes::after {
    /* same full-bleed breakout as Stonecrop — see the note there */
    content: ""; position: absolute; top: 0; bottom: 0;
    left: 50%; transform: translateX(-50%); width: 100vw;
    z-index: -1; pointer-events: none;
    opacity: 0; transition: opacity 0.9s ease;
  }
  #community-square.is-visible .stone-panes::before,
  #community-square.is-visible .stone-panes::after { opacity: 1; }
  #community-square .stone-panes::before {
    background: linear-gradient(to right, rgba(10, 10, 13, 0.46) 0%, rgba(10, 10, 13, 0.2) 60%, transparent 100%);
    -webkit-backdrop-filter: blur(6px) saturate(1.05); backdrop-filter: blur(6px) saturate(1.05);
    -webkit-mask-image: linear-gradient(to right, #000 0%, #000 75%, transparent 100%);
    mask-image: linear-gradient(to right, #000 0%, #000 75%, transparent 100%);
  }
  #community-square .stone-panes::after {
    -webkit-backdrop-filter: blur(20px) saturate(1.12); backdrop-filter: blur(20px) saturate(1.12);
    -webkit-mask-image: linear-gradient(to right, #000 0%, #000 62%, rgba(0,0,0,0.45) 82%, transparent 100%);
    mask-image: linear-gradient(to right, #000 0%, #000 62%, rgba(0,0,0,0.45) 82%, transparent 100%);
  }
}
/* Wide screens: dock the Capstan visual BESIDE the platform card (which is a left card) —
   it sits to the right and slightly overlaps the card's right edge (overlap 1.3rem stays
   inside the card's ≥1.5rem right padding, so no text is covered), reading as one unit. */
@media (min-width: 1150px) {
  #stonecrop .stone-pane .stone__products {
    position: absolute;
    top: clamp(0.5rem, 4vh, 2.5rem);
    left: calc(40rem - 1.3rem);
    right: auto; bottom: auto;
    width: min(30rem, calc(100% - 39.5rem));
    margin: 0;
    z-index: 1;
    filter: drop-shadow(0 12px 30px rgba(0, 0, 0, 0.5));
  }
}
/* ============================================================
   STONECROP on MOBILE (<=760px): the SAME reveal as desktop — the video stays sticky and
   dims as glass cards rise from below the fold — but full-width, no zig-zag, and the Capstan
   image flows below the last card. The scroll-reveal JS runs Stonecrop (only) on mobile too.
   Scoped to #stonecrop so democraci/Labs keep their stacked static mobile layout.
   ============================================================ */
@media (max-width: 760px) {
  #stonecrop { height: auto; min-height: 0; overflow: visible; padding: 0; display: block; }
  #stonecrop .panel__bg { position: sticky; top: 0; height: var(--app-h); height: calc(100lvh + 120px); }   /* lvh + overshoot: extend PAST the fold so video pixels exist behind Safari's translucent bottom bar (how ESPN reads under the pill — the document just continues there) */
  /* Drop the base scrim + colour-grade on mobile so the vertical video is full-bleed and vibrant
     top-to-bottom (Stephen's ask). Legibility now comes from the scroll-dim + glass cards + top fade. */
  #stonecrop .panel__overlay { display: none; }
  /* Scroll-linked dimming (same boosted ramp as desktop) so the video darkens as you read. */
  #stonecrop .panel__bg::after {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background: rgba(6, 7, 10, 0.62);
    opacity: var(--dim, 0);
  }
  #stonecrop .stone-panes, #community-square .stone-panes {
    position: relative; z-index: 2;
    /* Bottom padding ~0 so the LAST CARD ends where the VIDEO BOX ends (democraci's
       anatomy, Stephen's spot 2026-07-14): the card then covers the video's cut edge
       through the whole exit, and the section's own 38vh padding provides the soft
       buffer after. 26vh here left a naked-video tail where the cut showed in the open. */
    padding: 16vh clamp(0.9rem, 4.5vw, 1.4rem) 4vh;
  }
  #stonecrop .stone-pane, #community-square .stone-pane { position: relative; margin-bottom: 7vh; }
  #stonecrop .stone-pane:last-child, #community-square .stone-pane:last-child { margin-bottom: 0; }
  /* Full-width glassmorphic card (a touch more opaque than desktop, for legibility over the busy vertical video) */
  #stonecrop .stone-card, #community-square .stone-card {
    width: 100%;
    padding: clamp(1.1rem, 4.5vw, 1.5rem) clamp(1.15rem, 4.8vw, 1.6rem);
    background: rgba(15, 15, 19, 0.44);
    -webkit-backdrop-filter: blur(16px) saturate(1.15);
    backdrop-filter: blur(16px) saturate(1.15);
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: 20px;
    box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5);
  }
  #stonecrop .stone-card > :last-child, #community-square .stone-card > :last-child { margin-bottom: 0; }
  #stonecrop .stone-pane .stone__products { position: static; width: 100%; margin: 1.2rem 0 0; opacity: 1; filter: none; }
  /* Top fade so a rising card dissolves before it collides with the fixed identity strip. */
  #stonecrop .stone-topfade {
    position: fixed; top: 0; left: 0; right: 0; height: 20vh; z-index: 2; pointer-events: none;
    background: linear-gradient(to bottom, rgba(6,7,10,0.92) 0%, rgba(6,7,10,0.85) 46%, rgba(6,7,10,0.45) 74%, transparent 100%);
    opacity: calc(var(--reveal-progress, 0) * 3);
  }
  /* Pin the identity strip through the reveal (like desktop) + shrink it so it fits the narrow width. */
  #stonecrop.id-pin .venture__id { position: fixed; }
  #stonecrop .venture__id {
    top: calc(env(safe-area-inset-top, 0px) + clamp(0.9rem, 3vh, 1.8rem));
    font-size: 0.64rem;
    letter-spacing: 0.08em;
    line-height: 1.55;
    max-width: 84%;
  }
}
/* ============================================================
   DEMOCRACI (Community Square) on MOBILE (<=760px): the same reveal as Stonecrop, adapted to
   its single-card (.venture__foot) structure — the sticky video dims while the product montage
   and the foot glass card rise from below the fold. The scroll-reveal JS runs it on mobile too.
   ============================================================ */
@media (max-width: 760px) {
  #community-square { display: block; height: auto; min-height: 0; overflow: visible; padding: 0 0 40vh; }
  #community-square .panel__bg { position: sticky; top: 0; height: var(--app-h); height: calc(100lvh + 120px); }
  #community-square .panel__overlay { display: none; }   /* full-bleed vertical video on mobile */
  #community-square .panel__bg::after {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background: rgba(6, 7, 10, 0.6);
    opacity: var(--dim, 0);
  }
  /* product montage rides in-flow (rises first, a beat below the fold) */
  #community-square .cs-logo {
    position: relative; z-index: 2;
    display: block; width: 100%; max-width: 21rem; margin: 15vh auto 1.4rem;
    filter: drop-shadow(0 12px 30px rgba(0, 0, 0, 0.5));
  }
  /* the foot → a full-width glass card, in-flow, rising up */
  #community-square .venture__foot {
    position: relative; z-index: 2; inset: auto;
    display: block; width: auto; margin: 0 clamp(1rem, 5vw, 1.4rem);
    padding: clamp(1.1rem, 4.5vw, 1.5rem) clamp(1.15rem, 4.8vw, 1.6rem);
    background: rgba(15, 15, 19, 0.44);
    -webkit-backdrop-filter: blur(16px) saturate(1.15);
    backdrop-filter: blur(16px) saturate(1.15);
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: 20px;
    box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5);
    pointer-events: auto;
  }
  #community-square .venture__foot > :last-child { margin-bottom: 0; }
  #community-square .venture__corner { display: none; }   /* the glass card carries legibility now */
  #community-square .work-play { display: none; }
  #community-square .stone-topfade {
    position: fixed; top: 0; left: 0; right: 0; height: 20vh; z-index: 2; pointer-events: none;
    background: linear-gradient(to bottom, rgba(6,7,10,0.92) 0%, rgba(6,7,10,0.85) 46%, rgba(6,7,10,0.45) 74%, transparent 100%);
    opacity: calc(var(--reveal-progress, 0) * 3);
  }
  #community-square.id-pin .venture__id { position: fixed; }
  #community-square .venture__id { top: calc(env(safe-area-inset-top, 0px) + clamp(0.9rem, 3vh, 1.8rem)); font-size: 0.72rem; letter-spacing: 0.08em; max-width: 88%; }
}
.stone-h {
  margin: 0 0 0.55rem;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.4rem, 2.1vw, 2.05rem);
  letter-spacing: -0.01em;
  line-height: 1.1;
  color: var(--ink);
  text-shadow: 0 1px 14px rgba(0, 0, 0, 0.62);
}
.stone-sub { margin: 0.3rem 0 0.9rem; opacity: 0.92; text-shadow: 0 1px 10px rgba(0, 0, 0, 0.55); }
.stone-clients { margin: 0.75rem 0 0; font-size: 0.85rem; opacity: 0.72; letter-spacing: 0.02em; text-shadow: 0 1px 10px rgba(0, 0, 0, 0.55); }
/* Narrative body paragraph inside a stone card (the company story) */
.stone-body { margin: 0 0 0.8rem; font-size: clamp(0.92rem, 1.2vw, 1.02rem); line-height: 1.5; color: var(--ink-soft); text-shadow: 0 1px 8px rgba(0, 0, 0, 0.5); }
.stone-body:last-child { margin-bottom: 0; }

/* Community Square vision card: the rhetorical "turn" beats + the failure list */
.cs-turn {
  margin: 1.2rem 0 1.1rem;
  font-family: var(--font-display);
  font-size: clamp(1.06rem, 1.5vw, 1.34rem);
  line-height: 1.28;
  color: var(--ink);
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.6);
}
.cs-turn--sub {                       /* the redesign line, paired tight under the beat above it */
  margin-top: -0.55rem;
  font-size: clamp(0.98rem, 1.3vw, 1.14rem);
  font-style: italic;
  color: var(--ink-soft);
}
.cs-fail { margin-bottom: 1.1rem; }
.cs-fail li b { color: var(--ink); font-weight: 600; }

/* Simple-UX screenshot for the "Commercial Efficiency" card. Rounded corners + soft frame;
   full-width below the card on mobile (base), docked in the right slot on wide (see the
   #community-square .stone-pane--csux .cs-shot rule in the 1150px stagger block). */
.cs-shot {
  display: block;
  width: 100%;
  max-width: min(40rem, 100%);
  margin: 1.3rem 0 0;
  border-radius: 18px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5);
}

.venture__lead {
  margin: 0 0 0.95rem;
  max-width: 34rem;
  font-size: clamp(1.02rem, 1.5vw, 1.28rem);
  line-height: 1.45;
  color: var(--ink);
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.6);
}
.venture__lead em { font-style: italic; }

/* Stat chips — a row of glass pills; key figures in Fraunces */
.venture__chips {
  list-style: none;
  margin: 0 0 1.05rem;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.42rem 0.45rem;
  max-width: 40rem;
}
.venture__chips li {
  font-family: var(--font-ui);
  font-size: 0.74rem;
  letter-spacing: 0.015em;
  line-height: 1.15;
  color: var(--ink);
  padding: 0.32rem 0.62rem;
  border: 1px solid rgba(245, 243, 239, 0.24);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  -webkit-backdrop-filter: blur(5px);
  backdrop-filter: blur(5px);
}
.venture__chips b { font-family: var(--font-display); font-weight: 600; font-size: 0.82rem; }

/* "What I did" — three tight bullets */
.venture__did {
  list-style: none;
  margin: 0 0 1.05rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  max-width: 33rem;
}
.venture__did li {
  position: relative;
  padding-left: 1rem;
  font-family: var(--font-ui);
  font-size: clamp(0.88rem, 1.15vw, 0.98rem);
  line-height: 1.4;
  color: var(--ink-soft);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
}
.venture__did li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.6em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.7;
}
/* About section's Physical/Social/Cognitive list — match the style of the stat numbers */
#work-1 .venture__did li {
  text-shadow: none;
}
#work-1 .venture__did b {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.05rem;
  color: var(--ink);
}

/* Lesson line — the pulled quote */
.venture__lesson {
  margin: 0;
  padding-left: 0.85rem;
  border-left: 2px solid rgba(245, 243, 239, 0.4);
  max-width: 30rem;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-size: clamp(1.05rem, 1.5vw, 1.32rem);
  line-height: 1.3;
  color: var(--ink);
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.6);
}

/* Placeholder domain sections (#community-square, #community-square-labs) */
.soon { max-width: 42rem; }
.soon__kicker {
  margin: 0 0 0.85rem;
  font-size: 0.8rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.soon__title {
  margin: 0 0 1.1rem;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(2.2rem, 6vw, 4rem);
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.soon__line {
  max-width: 36rem;
  font-size: clamp(1.02rem, 2vw, 1.22rem);
  line-height: 1.55;
  color: var(--ink-soft);
}
.soon__more {
  margin-top: 1.5rem;
  font-size: 0.72rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-soft);
  opacity: 0.6;
}

/* Mobile: stats stack; left scrim flips to top-to-bottom and strengthens */
@media (max-width: 720px) {
  .stats { grid-template-columns: 1fr; gap: 1.1rem; }
  .panel--coord .panel__overlay::before,
  .panel--stonecrop .panel__overlay::before {
    background: linear-gradient(
      to bottom,
      rgba(8, 8, 12, 0.5) 0%,
      rgba(8, 8, 12, 0.82) 100%
    );
  }
}

.hero__title {
  margin: 0;
  text-transform: none;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(3rem, 11vw, 7.5rem);
  line-height: 0.98;
  letter-spacing: -0.01em;
}

.hero__quote {
  max-width: min(46rem, 86vw);        /* sits in the flex column, just above the name (bottom-left) */
  margin: 0 0 clamp(1.1rem, 2.4vh, 2rem);   /* gap down to the name */
  text-align: left;
  font-family: var(--font-ui);
  font-weight: 400;
  font-size: clamp(1.05rem, 2.3vw, 1.5rem);
  line-height: 1.5;
  color: #e9e5dd;
}

.hero__quote .line {
  display: block;
}

.hero__quote .seg {
  display: inline-block;
  color: rgba(233, 229, 221, 0.5);   /* the verse sits at a steady 50% — via color (not opacity) so a highlighted word can go full white */
  -webkit-mask-image: linear-gradient(to right, #000 40%, transparent 60%);
  mask-image: linear-gradient(to right, #000 40%, transparent 60%);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: 300% 100%;
  mask-size: 300% 100%;
  -webkit-mask-position: 100% 0; /* start fully masked (hidden) */
  mask-position: 100% 0;
}
/* one emphasized word in the verse, full white (the rest stays at 50%) */
.hero__quote .hl { color: #fff; }

.hero__cite {
  display: block;
  margin-top: 1.3rem;
  font-family: var(--font-ui);
  font-style: normal;
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  color: var(--ink-soft);
}

.hero__cite .work {
  font-style: italic;
}

/* ============================================================
   Work / content panels
   ============================================================ */
.panel__eyebrow {
  margin: 0 0 1rem;
  font-size: 0.85rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.panel__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(2.4rem, 8vw, 5.5rem);
  line-height: 1.0;
  letter-spacing: -0.01em;
}

.panel__desc {
  margin: 1.5rem 0 0;
  max-width: 36rem;
  font-size: clamp(1.05rem, 2.2vw, 1.3rem);
  line-height: 1.55;
  color: var(--ink-soft);
}

/* ----------------------------------------------------------
   Poem panel — verse anchored to the right, right-justified.
   Grows to fit the full poem + vertical padding (min one screen).
   ---------------------------------------------------------- */
.panel--poem {
  height: auto;
  padding-block: clamp(2.5rem, 5vh, 4rem);
}

.panel--poem .panel__inner {
  max-width: none;
}
.stage {
  width: 100%;
}

/* Scrim down both edges so the quote (left) and poem (right) stay legible */
.panel--poem .panel__overlay::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      to left,
      rgba(8, 8, 12, 0.72) 0%,
      rgba(8, 8, 12, 0.28) 42%,
      transparent 72%
    ),
    linear-gradient(
      to right,
      rgba(8, 8, 12, 0.66) 0%,
      rgba(8, 8, 12, 0.24) 38%,
      transparent 62%
    );
}

/* Heidegger epigraph — sits left, opposite the cycling poem */
.hquote {
  max-width: 22rem;
  margin: 0;                  /* the quote sits on the left */
  text-align: left;
  font-family: var(--font-ui);
  font-size: clamp(0.95rem, 1.6vw, 1.15rem);
  line-height: 1.5;
  color: var(--ink);
  opacity: 0.5;               /* sits at 50%; lines reveal one by one */
  cursor: pointer;            /* click anywhere on it to advance */
}
.hquote .hline {
  display: block;
}
.hquote .hline--gap {
  margin-top: 0.9rem;
}
.hquote cite {
  display: block;
  margin-top: 1.3rem;
  font-style: normal;
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  color: var(--ink-soft);
}
/* Stage reveal — each line/stanza builds in, staggered, once the stage is
   marked "built" (on scroll-in, and again each time you cycle). */
.s-reveal {
  opacity: 0;
  transform: translateY(12px);
  filter: blur(4px);
  /* per-line transition-delay is set inline (JS), scaled to reading time */
  transition: opacity 1s ease, transform 1s cubic-bezier(0.2, 0.8, 0.2, 1), filter 1s ease;
}
.stage.is-built .s-reveal {
  opacity: 1;
  transform: none;
  filter: none;
}

/* Journal entry — fades in on the right once the Heidegger quote has built in.
   Hidden on narrow screens where it would collide with the quote. */
.journal {
  position: absolute;
  top: 50%;
  right: clamp(1.5rem, 5vw, 5rem);
  transform: translateY(-50%);
  width: clamp(18rem, 40vw, 36rem);
  max-height: 70vh;
  object-fit: contain;
  opacity: 0;
  transition: opacity 1.6s ease;
  pointer-events: none;
  z-index: 1;
}
.journal.is-shown {
  opacity: 1;
}
@media (max-width: 820px) {
  .journal { display: none; }
}

.poem {
  max-width: 32rem;
  margin-left: auto;            /* poems sit on the right */
  text-align: right;
  font-family: var(--font-ui);
  font-size: clamp(0.72rem, 1.2vw, 0.85rem);
  line-height: 1.45;
  color: var(--ink);
  opacity: 0.5;                 /* whole verse sits at 50% */
}

.poem p {
  margin: 0 0 0.9rem;
}
.poem p:last-child {
  margin-bottom: 0;
}

.poem__title {
  font-family: var(--font-ui);
  font-style: normal;
  font-size: clamp(0.85rem, 1.5vw, 1.0rem);
  color: var(--ink-soft);
  margin-bottom: 1.1rem;
  cursor: pointer;              /* click to load the next poem */
  transition: color 0.25s ease;
}
.poem__title:hover {
  color: var(--ink);
}

.poem__sign {
  margin-top: 1.5rem;
  font-family: var(--font-ui);
  font-style: normal;
  font-size: clamp(0.85rem, 1.5vw, 1.0rem);
  color: var(--ink-soft);
}

/* ============================================================
   Poetry carousel — multiple poems, each with its own video;
   chevrons / swipe slide horizontally. Each verse scrolls vertically.
   ============================================================ */
#poetry { position: relative; }
.verse-carousel { position: absolute; inset: 0; overflow: hidden; z-index: 0; }
/* v229: the poem panel is viewport-sized, so the glass bands (status zone top, pill
   zone bottom) showed the NEIGHBORING sections' content — Stephen: "it's letting me
   see the next section header" (+ photography's pink sliver at the top). Same recipe
   as the video sections' overshooting boxes: extend the carousel past both edges so
   the poem's own footage paints the bands. Mobile only. */
@media (max-width: 760px) {
  .verse-carousel { inset: -90px 0 -120px; }
}
.verse-track { display: flex; height: 100%; transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1); }
.verse-slide { position: relative; flex: 0 0 100%; width: 100%; height: 100%; }
.verse-slide .panel__bg { position: absolute; inset: 0; z-index: 0; }
.verse-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: translateZ(0);
}

/* Desktop: center the verse within the LEFT HALF (off the edge), leaving the
   right half open for the video. Mobile keeps the simple left inset above. */
@media (min-width: 761px) {
  #poetry .panel__inner {
    padding-left: 28%;   /* match the feed's centered position */
  }
  #poetry .verse { margin-inline-start: 0; }
}
.verse-slide .panel__inner {
  position: relative; z-index: 1; height: 100%; max-width: none;
  overflow-y: auto; overscroll-behavior: auto;   /* FULL-WIDTH scroller (like the feed): wheel anywhere reads the poem, chains at the ends */
  padding: 14svh var(--pad) 55svh;   /* big bottom buffer (~40vh of clear video below the poem) so reaching the poem's END doesn't immediately chain into the next section */
  /* stronger bottom fade so the scrolling poem text fully clears the control zone (dots +
     see-all pill) at the bottom instead of running behind them */
  -webkit-mask-image: linear-gradient(to bottom, #000 74%, transparent 88%);
  mask-image: linear-gradient(to bottom, #000 74%, transparent 88%);
}
/* internal-scroll GATE (mobile): the poem's inner scroller is LOCKED until the poetry
   section is fully in — its top has reached the top of the screen. So scrolling INTO the
   section brings it fully into view first, instead of the poem scroll capturing the gesture
   mid-approach. JS toggles #poetry.verse-ready on scroll. */
@media (max-width: 760px) {
  #poetry:not(.verse-ready) .verse-slide .panel__inner { overflow: hidden; }
}
.verse-slide .verse { height: auto; }                                          /* flow; the inner scrolls it */
.verse-slide .verse__body { overflow: visible; flex: 0 0 auto; height: auto; padding-bottom: 0; }
.verse-slide .verse__title { margin-bottom: 3.4rem; }                          /* more air under the title */
.verse-slide .panel__overlay {
  /* Deepest at the top-left where the poem begins; falls off to reveal the
     video toward the lower-right. */
  background:
    radial-gradient(64% 82% at 24% 33%,
      rgba(6, 6, 10, 0.92) 0%,
      rgba(6, 6, 10, 0.62) 33%,
      rgba(6, 6, 10, 0.18) 63%,
      rgba(6, 6, 10, 0) 90%);
}

.verse-nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 6;
  width: 2.8rem; height: 2.8rem; display: grid; place-items: center;
  border: none; border-radius: 0;
  background: none; color: var(--ink);   /* bare chevron — no circle/glass backing */
  font-family: var(--font-ui); font-size: 1.9rem; line-height: 1;
  cursor: pointer;
  /* the circle used to supply contrast; a shadow keeps the bare glyph legible over bright video */
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.85), 0 0 14px rgba(0, 0, 0, 0.55);
  /* hidden while reading; JS flashes them in on scroll/move, then they fade back out */
  opacity: 0; pointer-events: none;
  transition: transform 0.2s ease, opacity 0.5s ease;
}
#poetry.chrome-awake .verse-nav { opacity: 0.9; pointer-events: auto; }
.verse-nav:hover { transform: translateY(-50%) scale(1.15); opacity: 1; }
.verse-nav--prev { left: clamp(0.5rem, 2vw, 1.5rem); }
.verse-nav--next { right: clamp(0.5rem, 2vw, 1.5rem); }

.verse-dots {
  position: absolute; bottom: 1.4rem; left: 50%; transform: translateX(-50%);
  z-index: 6; display: flex; gap: 0.55rem;
}
.verse-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: rgba(255, 255, 255, 0.32); cursor: pointer;
  transition: background 0.25s ease, transform 0.25s ease;
}
.verse-dot.is-active { background: rgba(255, 255, 255, 0.92); transform: scale(1.15); }
/* mobile: tether the pagination dots to the Safari pill (fixed → tracks the bar), matching
   the see-all pill; shown only once poetry is fully in view */
@media (max-width: 760px) {
  .verse-dots {
    position: fixed; left: 50%; transform: translateX(-50%);
    bottom: calc(env(safe-area-inset-bottom, 0px) + 1.3rem);
    opacity: 0; pointer-events: none; transition: opacity 0.35s ease;
  }
  #poetry.poetry-live .verse-dots { opacity: 1; pointer-events: auto; }
}

.verse {
  max-width: 54rem;
  margin-inline-start: clamp(1.5rem, 6vw, 6rem);
  height: 100%;
  display: flex;
  flex-direction: column;
  font-family: var(--font-ui);
  color: var(--ink);
  text-align: left;
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.55), 0 1px 3px rgba(0, 0, 0, 0.5);
}
.verse__title {
  flex: 0 0 auto;
  width: max-content; max-width: 100%;
  margin: 0 0 1.9rem;
  font-style: italic;
  font-size: clamp(1.25rem, 2.3vw, 1.7rem);
  color: var(--ink);
}
.verse__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-bottom: 50svh;   /* tail space so the poem's last line can scroll up to mid-screen */
}
.verse__body p {
  margin: 0 0 1.5rem;
  font-size: clamp(1.05rem, 1.7vw, 1.2rem);
  line-height: 1.5;
}
.verse__body p:last-child { margin-bottom: 0; }

/* Per-line poem dissolve — JS (poetry IIFE) wraps each line in a .vline span + marks .lines-ready; revealLines fades them
   in left-to-right (fast cursor, slow per-word fade). No-JS fallback: words stay visible. */
.verse__body .vline { display: block; }
.verse.lines-ready .verse__title,
.verse.lines-ready .verse__body .vline { opacity: 0; }
.verse__photo {
  display: block; width: min(22rem, 70%); height: auto; margin: 2.8rem 0 0;
  border-radius: 3px; box-shadow: 0 8px 40px rgba(0, 0, 0, 0.5);
}
.verse.lines-ready .verse__photo { opacity: 0; }
.verse__body p.verse__word {
  margin: 1.8rem 0;
  font-style: italic;
  font-size: clamp(1.4rem, 2.6vw, 1.9rem);
  letter-spacing: 0.01em;
}


/* Closing panel */
.end__actions {
  margin-top: 2.25rem;
}
.social {
  display: flex;
  gap: 1.5rem;
  margin-top: 2rem;
  font-size: 0.95rem;
  color: var(--ink-soft);
}
.social a {
  transition: color 0.2s ease;
}
.social a:hover { color: var(--ink); }
.copyright {
  margin: 2.5rem 0 0;
  font-size: 0.85rem;
  color: var(--ink-soft);
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.8rem 1.5rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 0.95rem;
  color: var(--ink);
  transition: border-color 0.2s ease, background-color 0.2s ease, transform 0.2s ease;
}
.btn:hover {
  border-color: var(--ink);
  transform: translateY(-1px);
}
.btn--primary {
  background: var(--accent);
  color: #111;
  border-color: var(--accent);
}
.btn--primary:hover {
  background: #fff;
  border-color: #fff;
}

/* ============================================================
   Scroll cue (hero only)
   ============================================================ */
.scroll-cue {
  position: absolute;
  left: 50%;
  bottom: clamp(1.5rem, 4vh, 3rem);
  transform: translateX(-50%);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
  font-size: 0.72rem;
  letter-spacing: 0.25em;
  text-transform: lowercase;
  color: var(--ink-soft);
  animation: reveal 1s ease both;
  animation-delay: 20.9s;
  transition: color 0.2s ease;
}
.scroll-cue:hover { color: var(--ink); }

.scroll-cue__line {
  position: relative;
  width: 1px;
  height: 44px;
  background: rgba(245, 243, 239, 0.2);
  overflow: hidden;
}
.scroll-cue__line::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 45%;
  background: var(--ink);
  animation: cueDrop 1.9s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
@keyframes cueDrop {
  0%        { transform: translateY(-110%); }
  55%, 100% { transform: translateY(240%); }
}

/* ============================================================
   Motion toggle
   ============================================================ */
.controls {
  position: fixed;
  right: var(--pad);
  bottom: var(--pad);
  z-index: 11;
  display: grid;
  grid-template-columns: repeat(3, auto);
  align-items: center;
  justify-content: end;
  gap: 0.5rem;
}
.motion-toggle,
.sound-toggle,
.refresh-toggle {
  text-transform: lowercase;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.55rem 0.95rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgba(10, 10, 12, 0.4);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--ink-soft);
  font-family: inherit;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease;
}
.motion-toggle:hover,
.sound-toggle:hover,
.refresh-toggle:hover {
  color: var(--ink);
  border-color: var(--ink);
}
.refresh-toggle__icon {
  display: block;
}

/* ----------------------------------------------------------
   Chrome (top nav + pause) auto-hides when the pointer is idle.
   Any movement reveals it; the poem, name, and scroll cue stay.
   ---------------------------------------------------------- */
.nav,
.controls {
  opacity: 0;
  pointer-events: none;
}
.controls {
  transition: opacity 0.45s ease;
}
body.ui-active .nav {
  opacity: 1;
  pointer-events: auto;
}
/* Bottom-right player controls (home / about / stonecrop / democraci): JS toggles .is-shown
   — fade in on video play / click-to-pause / cursor move, fade out when idle. */
.controls.is-shown { opacity: 1; pointer-events: auto; }

/* NOTE: deliberately NO desktop .controls-mark lift. It existed only because v376 put the marks
   in the bottom-RIGHT corner, on top of .controls. v387 centres them on desktop, so that corner
   is .controls' alone again and lifting it would leave the buttons floating for no reason. The
   MOBILE lift further down still applies — there the mark really is in that corner. */

/* v295 — MOBILE layout: stack play/mute/replay vertically at the bottom-right, with the seek
   bar as a full-width line BENEATH the buttons (below the footer logo, above the URL pill). */
@media (max-width: 760px) {
  .controls {
    left: var(--pad);
    right: var(--pad);
    bottom: 0.4rem;          /* v299: ride lower — seek + buttons sit just above the URL pill */
    display: flex;
    flex-direction: column;
    align-items: flex-end;   /* the button stack hugs the right edge */
    gap: 0.5rem;
  }
  .controls .seek-bar {
    order: 1;                /* drop the scrubber below the buttons */
    align-self: flex-end;    /* v316 (Stephen): right-justified, hugging the right edge */
    width: 40%;              /* v316: narrow — sits to the right, just left of the logo (no longer crosses it) */
    height: 24px;            /* v298: more padding around the 3px track (bigger touch target) */
    margin-top: 0.6rem;
  }
  /* v298: ICON-ONLY control pills on mobile — hide the text labels, compact each to a round
     icon button (less clutter, more room). */
  .controls .motion-toggle__label,
  .controls .sound-toggle__label,
  .controls .refresh-toggle__label { display: none; }
  .controls .motion-toggle,
  .controls .sound-toggle,
  .controls .refresh-toggle {
    gap: 0;
    padding: 0;
    width: 2.7rem;
    height: 2.7rem;
    justify-content: center;
  }
  /* Lift the venture footer mark above the full-width seek line so the scrubber reads as its
     own row (below the logo, above the pill) instead of crossing the mark. */
  #community-square .cs-footer,
  #stonecrop .stone-footer { bottom: calc(var(--pad) + 0.8rem); }   /* v305: sit just above the seek line (was too high) */
  /* v297: SOLID (not frosted) control pills on mobile. The controls now show over the About
     screen, and a 2nd backdrop-filter stacked over its video makes iOS/WebKit flatten the
     bio-glass gradient blur (it renders fine in Chrome). Drop the pills' backdrop-filter here
     and use a matched solid tint so the About blur survives. */
  .controls .motion-toggle,
  .controls .sound-toggle,
  .controls .refresh-toggle {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background: transparent;                                 /* v300: drop the dark fill — too distracting */
    border-color: rgba(255, 255, 255, 0.15);                /* v301: fainter outline so it draws less attention */
    color: #f4f2ee;
    filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.55));     /* keep the icon legible over bright frames */
  }
  /* Mobile: drop the hero scroll cue (the "scroll" label + line). */
  #home .scroll-cue { display: none; }
  /* v303: on Stonecrop / democraci, sit the button stack a touch lower (closer to the seek). */
  .controls.controls-low { gap: 0.3rem; }
  .controls.controls-low .seek-bar { margin-top: 0.1rem; }
  /* v386 — THE MARK IS THE BOTTOM ELEMENT (Stephen: "put the murmuration logo as the bottom
     element, below the video controls ... without the seek activated the logo tends to look too
     high"). v378 parked it in a gap BETWEEN the buttons and the seek, which meant its position
     depended on the seek being present — with the scrubber hidden the gap collapsed and the mark
     floated. Now the mark anchors to the bottom on its own and the whole .controls block lifts
     above it, so the stack reads buttons → seek → mark and nothing moves when the seek hides.
     3.5rem ≈ the 45px mark box + a ~12px gap. Applies only where a mark exists (About, Labs). */
  .controls.controls-mark { bottom: calc(0.4rem + 3.5rem); }
  /* v378 — panels that carry a murmuration mark (Stephen: "for the screens that have the video
     play buttons, have the murmuration to far right, keep the seek where it is, and move the 3
     control buttons above the murmuration").
     ⚠ Do NOT do this by moving `.controls` itself. The block is bottom-anchored, so shifting its
       `bottom` and trying to cancel that with a negative margin on the seek makes the box shrink
       and BOTH rows drift (measured: seek moved 14px, buttons lifted only 13). Instead add the
       space ABOVE the seek: the seek is the last row, so it stays pinned to the container's
       bottom edge while the box grows upward and carries the 3 buttons with it. One property,
       and the scrubber cannot move. 3.75rem ≈ the 45px mark box + a 15px gap (0.6rem is the
       seek's existing margin-top from the mobile rule, preserved). */
}

/* ----------------------------------------------------------
   Seek bar — thin scrubber pinned to the bottom edge. Drag to
   move the active video back and forth. Hides with the chrome.
   ---------------------------------------------------------- */
.seek-bar {
  grid-column: 1 / -1;     /* span the full width of the button cluster */
  width: 100%;
  height: 14px;
  margin: 0;
  padding: 0;
  background: transparent;
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
}
.seek-bar::-webkit-slider-runnable-track {
  height: 3px;
  border-radius: 2px;
  background: linear-gradient(to right,
    var(--ink) 0%, var(--ink) var(--seek, 0%),
    rgba(255, 255, 255, 0.18) var(--seek, 0%), rgba(255, 255, 255, 0.18) 100%);
}
.seek-bar::-moz-range-track {
  height: 3px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.18);
}
.seek-bar::-moz-range-progress {
  height: 3px;
  border-radius: 2px;
  background: var(--ink);
}
.seek-bar::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 12px;
  height: 12px;
  margin-top: -4.5px;
  border-radius: 50%;
  background: var(--ink);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3);
  opacity: 0;
  transition: opacity 0.2s ease;
}
.seek-bar:hover::-webkit-slider-thumb,
.seek-bar:active::-webkit-slider-thumb {
  opacity: 1;
}
.seek-bar::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border: none;
  border-radius: 50%;
  background: var(--ink);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3);
  opacity: 0;
}
.seek-bar:hover::-moz-range-thumb,
.seek-bar:active::-moz-range-thumb {
  opacity: 1;
}
.motion-toggle__icon {
  width: 9px;
  height: 10px;
  border-left: 3px solid currentColor;
  border-right: 3px solid currentColor;
}
.motion-toggle.is-paused .motion-toggle__icon {
  width: 0;
  height: 0;
  border-left: 9px solid currentColor;
  border-right: 0;
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
}
.sound-toggle__icon {
  display: block;
}
.sound-toggle__wave {
  opacity: 0;
  transition: opacity 0.2s ease;
}
.sound-toggle.is-on .sound-toggle__wave {
  opacity: 1;
}
.sound-toggle.is-on .sound-toggle__slash {
  opacity: 0;
}

/* ============================================================
   Welcome gate — covers everything until the visitor enters
   ============================================================ */
/* Set by the inline <head> script, removed by script.js once it installs its own gate.
   Exists so the document is never scrollable during load — the browser cannot restore a scroll
   position into a document that has no scrollport, which is what stops a reload landing back on
   About. Deliberately !important and matched on html AND body, same as the gate locks below. */
html.preload-lock, html.preload-lock body { overflow: hidden !important; }

html.intro-open {
  overflow: hidden;
}
/* While the gate is up, hide the hero's backdrop entirely: iOS composites the glass-band
   zones (status bar / pill surround) from the SCROLLING layer only — the FIXED gate's
   overshoot gets clipped to the viewport — so the hero video showed around the gate.
   With the backdrop hidden, the bands read as the dark page background = a solid gate.
   (The video keeps buffering while hidden; it appears the moment enterSite removes this.) */
/* v228: hide ALL of home behind the gate, not just its video box. The fixed gate is
   CLIPPED at the viewport line (fixed/sticky can't paint under the bar) while in-flow
   page content DOES paint there — so home's scroll cue peeked out as a white line in
   the strip below the gate (Stephen's red-arrow screenshot). visibility keeps the video
   buffering; everything appears the moment enterSite lifts the gate. */
html.intro-open .panel { visibility: hidden; }
/* v233: one uniform color under the gate. setChromeTint paints html/body with the active
   panel's tint (#16130f warm for home) AT LOAD — under the gate's #0a0a0c that shade
   difference was the visible "black doesn't reach the bottom" line. CSS !important beats
   the inline style writes. */
html.intro-open, html.intro-open body { background-color: var(--bg) !important; background-image: none !important; }   /* v232: ALL sections, not just home.
   The fixed gate is clipped at the viewport bottom (viewport-anchored content always is),
   and the strip below it showed ABOUT's video-box top — document-flowing content paints
   under the pill (that's the whole rule). Nothing to cover = nothing leaks; page bg is
   gate-black. Videos keep buffering while hidden. */
.intro {
  position: fixed;
  inset: -90px 0 -160px;   /* overshoot past the viewport: iOS renders the glass-band zones (status bar / pill) from canvas pixels beyond the layout viewport — inset:0 left the hero video peeking out below the gate. v224: bottom overshoot raised to 160px — with the bar expanded at load, 90px still left the gate short of the physical screen bottom (Stephen's welcome screenshot). */
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  /* v233: FLAT black again (the v226 gradient itself created a shade mismatch). While
     the gate is up, gate + html + body are all forced to ONE color below, so the strip
     the fixed gate can't reach is indistinguishable from the gate. */
  background: var(--bg);
  transition: opacity 0.8s ease;
}
.intro.is-dismissed {
  opacity: 0;
  pointer-events: none;
}
/* Chladni sand canvas — fills the gate; clicks fall through to .intro */
.intro__canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.intro.is-canvas {
  cursor: pointer;
}
/* with the sand active (or pending, via the <head> .canvas-intro flag), keep the button for
   keyboard/screen-readers but hide it — so the plain "welcome" text never flashes before the sand */
.intro.is-canvas .intro__btn,
.canvas-intro .intro__btn {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
  opacity: 0;
  animation: none;
}
.intro__btn {
  font-family: var(--font-display);
  font-weight: 500;
  text-transform: lowercase;
  font-size: clamp(1.05rem, 3.4vw, 1.6rem);
  letter-spacing: 0.06em;
  border: none;
  background-color: transparent;
  padding: 0.4rem 0.8rem;
  cursor: pointer;
  /* shimmer — a soft highlight drifts across the word to invite the click */
  color: var(--ink-soft);
  background-image: linear-gradient(100deg, var(--ink-soft) 30%, var(--ink) 50%, var(--ink-soft) 70%);
  background-size: 250% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: introFade 1.2s ease both, shimmer 3.4s ease-in-out 1.2s infinite;
  transition: letter-spacing 0.3s ease;
}
.intro__btn:hover {
  letter-spacing: 0.12em;
}
@keyframes introFade {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
@keyframes shimmer {
  0%   { background-position: 180% center; }
  100% { background-position: -80% center; }
}

/* ============================================================
   Intro build (hero, on load) + per-panel reveal (on scroll)
   animation-fill-mode "both" keeps elements hidden through
   their delay, so the opening frame is (almost) empty.
   ============================================================ */
.hero__title .word {
  display: inline-block;
  animation: revealWord 1.1s cubic-bezier(0.2, 0.8, 0.2, 1) both;
  will-change: transform, filter, opacity;
}
.hero__title .word:nth-child(1) { animation-delay: 2.00s; } /* Hi.      (longer hold) */
.hero__title .word:nth-child(2) { animation-delay: 4.00s; } /* I'm      (2s after Hi.) */
.hero__title .word:nth-child(3) { animation-delay: 4.60s; } /* Stephen. */

/* Each phrase fades in (revealQuote), then dims to 50% as the NEXT phrase
   arrives (dim). Two animations per segment; each segment's second delay is
   the next segment's reveal time, so they cross-fade. */
.hero__quote .seg { animation: wipeIn 1.4s ease-out both; }
.hero__quote .line:nth-child(1) .seg { animation-delay: 7.00s; }    /* The first thing we do                */
.hero__quote .line:nth-child(2) .seg { animation-delay: 9.20s; }    /* when we see something beautiful      */
.hero__quote .line:nth-child(3) .seg { animation-delay: 11.40s; }   /* is look to someone else,             */
.hero__quote .line:nth-child(4) .seg { animation-delay: 13.60s; }   /* because when shared,                 */
.hero__quote .line:nth-child(5) .seg { animation-delay: 15.80s; }   /* it becomes all the more beautiful.   */
.hero__cite { animation: revealQuote 1.4s ease-out both; animation-delay: 18.00s; }

/* The hero build-out stays frozen until the visitor clicks "welcome", so the
   staged reveal plays on entry instead of running behind the gate. */
.hero__title .word,
.hero__quote .seg,
.hero__cite {
  animation-play-state: paused;
}
body.entered .hero__title .word,
body.entered .hero__quote .seg,
body.entered .hero__cite {
  animation-play-state: running;
}

/* Work/closing panels: content reveals when the panel scrolls into view */
.reveal {
  opacity: 0;
  transform: translateY(20px);
  filter: blur(8px);
  transition:
    opacity 0.9s ease,
    transform 0.9s cubic-bezier(0.2, 0.8, 0.2, 1),
    filter 0.9s ease;
}
.panel.is-visible .reveal {
  opacity: 1;
  transform: none;
  filter: none;
}
/* Stagger the reveal inside a panel */
.panel.is-visible .reveal:nth-child(1) { transition-delay: 0.05s; }
.panel.is-visible .reveal:nth-child(2) { transition-delay: 0.18s; }
.panel.is-visible .reveal:nth-child(3) { transition-delay: 0.31s; }
.panel.is-visible .reveal:nth-child(4) { transition-delay: 0.44s; }
.panel.is-visible .reveal:nth-child(5) { transition-delay: 0.57s; }
.panel.is-visible .reveal:nth-child(6) { transition-delay: 0.70s; }
.panel.is-visible .reveal:nth-child(7) { transition-delay: 0.83s; }
.panel.is-visible .reveal:nth-child(8) { transition-delay: 0.96s; }
.panel.is-visible .reveal:nth-child(9) { transition-delay: 1.09s; }
.panel.is-visible .reveal:nth-child(10) { transition-delay: 1.22s; }
.panel.is-visible .reveal:nth-child(11) { transition-delay: 1.35s; }

/* #work-1 overrides the quick per-element stagger with three deliberate beats:
   header (0s) → ~1s pause → body (2s) → ~3s pause → tiles (6s).
   The ID selector outranks the .reveal:nth-child rules above. */
#work-1.is-visible .coord__headline { transition-delay: 0s; }
#work-1.is-visible .coord__lead     { transition-delay: 0.25s; }
#work-1.is-visible .coord__lead ~ p { transition-delay: 0.4s; }   /* was 2s — render fast so the scroll-lock is short */
#work-1.is-visible .exits           { transition-delay: 6s; }

@keyframes revealWord {
  from { opacity: 0; transform: translateY(0.45em) scale(0.985); filter: blur(14px); }
  to   { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
}
@keyframes reveal {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Soft "emerge" used by the poem — fades up out of a gentle blur, no slide */
@keyframes revealQuote {
  from { opacity: 0; filter: blur(6px); }
  to   { opacity: 1; filter: blur(0); }
}

/* Each phrase wipes in left-to-right via a soft gradient mask */
@keyframes wipeIn {
  from { -webkit-mask-position: 100% 0; mask-position: 100% 0; }
  to   { -webkit-mask-position: 0% 0;   mask-position: 0% 0; }
}

/* ============================================================
   Respect users who prefer reduced motion
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .panel__bg::before { animation: none; }
  .panel__video { transition: none; }
  .scroll-cue__line::after { animation: none; }
  .nav,
  .scroll-cue,
  .motion-toggle,
  .hero__title .word,
  .hero__quote .seg,
  .hero__cite { animation: none; }
  .intro__btn { animation: none; -webkit-text-fill-color: var(--ink); }
  .hero__quote .seg { -webkit-mask-image: none; mask-image: none; }
  .reveal,
  .s-reveal {
    opacity: 1;
    transform: none;
    filter: none;
    transition: none;
  }
}

/* Small screens */
@media (max-width: 600px) {
  .nav__links { gap: 1.2rem; font-size: 0.9rem; }
}

/* ============================================================ */
/*  Photography — section cards (in the scroll) + gallery pages   */
/* ============================================================ */

/* The #photography panel: three collection cards */
.panel--photos .panel__inner {
  display: flex;
  flex-direction: column;
  gap: clamp(1.4rem, 3.5vh, 2.4rem);
  width: 100%;
  max-width: var(--max);
}
.photo-cats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(0.5rem, 1.4vw, 1rem);
}
.photo-cat {
  position: relative;
  display: block;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  border-radius: 4px;
  text-decoration: none;
}
.photo-cat__thumb {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: brightness(0.62);
  transition: filter 0.45s ease, transform 0.7s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.photo-cat:hover .photo-cat__thumb,
.photo-cat:focus-visible .photo-cat__thumb {
  filter: brightness(0.85);
  transform: scale(1.05);
}
.photo-cat__label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.1rem, 2.4vw, 2rem);
  letter-spacing: 0.05em;
  color: var(--ink);
  text-shadow: 0 2px 24px rgba(0, 0, 0, 0.7);
  pointer-events: none;
}

/* Gallery page (gallery.html) — masonry mosaic + lightbox */
.gallery-page {
  background: var(--bg);
  color: var(--ink);
  min-height: 100vh;
  min-height: 100dvh;
}
.gallery-head {
  position: relative;
  text-align: center;
  padding: clamp(1.3rem, 3vw, 2.2rem) 1rem clamp(0.8rem, 2vw, 1.4rem);
}
.gallery-back {
  position: absolute;
  left: clamp(1rem, 3vw, 2.2rem);
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-ui);
  font-size: 0.85rem;
  color: var(--ink-soft);
  text-decoration: none;
  transition: color 0.25s ease;
}
.gallery-back:hover { color: var(--ink); }
.gallery-title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.5rem, 4vw, 2.5rem);
  letter-spacing: 0.09em;
  text-transform: uppercase;
}
/* gallery-page album switcher — the SAME tabs as the homepage filter, in the header */
.gallery-filter { display: inline-flex; align-items: center; gap: clamp(1.4rem, 5vw, 3.5rem); vertical-align: middle; }
.gallery-filter .photo-filter__tab { font-size: clamp(1.25rem, 3vw, 1.9rem); }
.mosaic {
  padding: 0 clamp(0.4rem, 1vw, 0.8rem) clamp(1rem, 3vw, 2rem);
}
.mosaic__row {
  display: flex;
  gap: 8px;
  margin-bottom: 8px;
}
.mosaic__block {           /* a collage group of 1–3 photos; width/height set inline by gallery.js */
  display: flex;
  gap: 8px;
  flex: 0 0 auto;
  overflow: hidden;
}
.mosaic__block.is-col { flex-direction: column; }
.mosaic__sub {             /* the stacked pair inside a 3-photo block */
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 0 0 auto;
  overflow: hidden;
}
.mosaic__item {            /* a single photo; exact width + height set inline (so aspect is held) */
  position: relative;
  flex: 0 0 auto;
  margin: 0;               /* reset the <figure> default ~40px margin */
  overflow: hidden;
  border-radius: 2px;
}
.mosaic__item img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;       /* box matches the photo's aspect, so nothing is cropped */
  cursor: zoom-in;
  background: rgba(255, 255, 255, 0.03);
}
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.93);
  cursor: zoom-out;
  padding: 2vmin;
}
.lightbox[hidden] { display: none; }
.lightbox__img {
  max-width: 96vw;
  max-height: 96vh;
  object-fit: contain;
  box-shadow: 0 12px 70px rgba(0, 0, 0, 0.55);
}

@media (max-width: 560px) {
  .photo-cats { grid-template-columns: 1fr; }
  .photo-cat { aspect-ratio: 16 / 10; }
}

/* ============================================================
   Photography section — a FULL-BLEED carousel of the collections,
   each slide rendered as the gallery's justified mosaic (mosaic.js).
   ============================================================ */
#photography { padding: 0; height: auto; min-height: var(--app-h); display: flex; flex-direction: column; align-items: stretch; justify-content: flex-start; --pf-h: clamp(3.25rem, 7vh, 4.5rem); }
/* Two stacked mosaics, each its own natural height (exactly N complete rows), so
   the album bar sits in the gap between them and never slices a photo. */
.photo-mosaic { width: 100%; padding-bottom: 0; overflow: hidden; cursor: pointer; z-index: 1; }
.photo-carousel { position: relative; flex: 1 1 0; width: 100%; min-height: 0; overflow: hidden; z-index: 1; }
.photo-track { display: flex; height: 100%; transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1); }
.photo-slide { flex: 0 0 100%; min-width: 0; height: 100%; }
.photo-slide__label { flex: 0 0 auto; display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; padding: clamp(1.4rem, 3.5vh, 2.4rem) clamp(2.2rem, 5vw, 4rem) clamp(0.8rem, 2vh, 1.2rem); }
.photo-slide__name { font-family: var(--font-display); font-size: clamp(1.5rem, 3.5vw, 2.4rem); color: var(--ink); }
.photo-slide__more { font-family: var(--font-ui); font-size: 0.95rem; letter-spacing: 0.02em; color: var(--ink-soft); white-space: nowrap; transition: color 0.2s ease; }
.photo-slide__more:hover { color: var(--ink); }
.photo-slide .mosaic { width: 100%; height: 100%; overflow: hidden; cursor: pointer; }
.photo-nav { position: absolute; top: 50%; transform: translateY(-50%); z-index: 6; width: 2.8rem; height: 2.8rem; display: grid; place-items: center; border: none; border-radius: 50%; background: rgba(10, 10, 14, 0.45); color: var(--ink); font-family: var(--font-ui); font-size: 1.7rem; line-height: 1; cursor: pointer; -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px); transition: background 0.25s ease; }
.photo-nav:hover { background: rgba(10, 10, 14, 0.72); }
.photo-nav--prev { left: clamp(0.5rem, 1.5vw, 1.4rem); }
.photo-nav--next { right: clamp(0.5rem, 1.5vw, 1.4rem); }
.photo-dots { position: absolute; bottom: calc(var(--app-h) + clamp(0.9rem, 2.5vh, 1.6rem)); left: 50%; transform: translateX(-50%); z-index: 5; display: none; gap: 0.5rem; }
.photo-filter { position: relative; flex: 0 0 auto; margin: 1.5rem 0; height: var(--pf-h); z-index: 5; display: flex; align-items: center; justify-content: center; gap: clamp(1.5rem, 5vw, 4rem); background: transparent; }
/* "see all" — a small fluorescent liquid-color pill on the right of the bar;
   opens the active collection's full gallery (gallery.html?c=...). */
.photo-seeall {
  position: absolute; right: clamp(1rem, 4vw, 3.5rem); top: 50%; transform: translateY(-50%);
  display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.55rem 1.4rem; border: none; border-radius: 999px;
  font-family: var(--font-ui); font-size: 0.82rem; font-weight: 500; letter-spacing: 0.06em; color: #fff;
  cursor: pointer; overflow: hidden; isolation: isolate; background: #0b0b16;
  box-shadow: 0 0 18px rgba(150, 100, 255, 0.4);
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}
/* fluorescent liquid blobs — large + overlapping so the whole pill stays vivid (no dark center) */
.photo-seeall::before {
  content: ""; position: absolute; inset: -70%; z-index: 0;
  background:
    radial-gradient(circle at 26% 30%, #ff2d95 0%, transparent 58%),
    radial-gradient(circle at 74% 28%, #00e5ff 0%, transparent 58%),
    radial-gradient(circle at 50% 80%, #39ff14 0%, transparent 56%),
    radial-gradient(circle at 84% 72%, #b026ff 0%, transparent 58%),
    radial-gradient(circle at 16% 74%, #ff8a00 0%, transparent 58%);
  filter: blur(7px) saturate(1.7);
  animation: seeallLiquid 9s ease-in-out infinite;
}
/* oscillating white stroke so the eye catches the button */
.photo-seeall::after {
  content: ""; position: absolute; inset: 0; z-index: 2; border-radius: inherit;
  border: 1.5px solid #fff; pointer-events: none;
  animation: seeallStroke 1.6s ease-in-out infinite;
}
.photo-seeall > span { position: relative; z-index: 1; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6); }
.photo-seeall__chev { position: relative; z-index: 1; flex: 0 0 auto; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.55)); }
.photo-seeall:hover { transform: translateY(-50%) scale(1.06); box-shadow: 0 0 28px rgba(180, 120, 255, 0.65); }
@keyframes seeallLiquid {
  0%   { transform: rotate(0deg)   scale(1.3); }
  33%  { transform: rotate(140deg) scale(1.55); }
  66%  { transform: rotate(255deg) scale(1.35); }
  100% { transform: rotate(360deg) scale(1.3); }
}
@keyframes seeallStroke {
  0%, 100% { opacity: 0.3;  box-shadow: 0 0 4px rgba(255, 255, 255, 0.2); }
  50%      { opacity: 0.95; box-shadow: 0 0 13px rgba(255, 255, 255, 0.6); }
}
@media (max-width: 620px) {
  /* position:RELATIVE, not static — the pill must stay a positioned element so its
     fluorescent ::before (absolute, inset:-70%) anchors to the PILL and is clipped by
     its overflow:hidden. As `static` the ::before anchored to .photo-filter (the nearest
     positioned ancestor) and ballooned across the whole filter row. relative flows
     identically to static in the flex row. */
  .photo-seeall { position: relative; transform: none; margin-left: 0.4rem; padding: 0.45rem 1.1rem; font-size: 0.74rem; }
  .photo-seeall:hover { transform: scale(1.06); }
}
@media (prefers-reduced-motion: reduce) { .photo-seeall::before, .photo-seeall::after { animation: none; } }
/* Photography's "All" — plain text (NO fluorescent pill), flowing in the filter row like a
   tab. (Poetry's .poem-seeall keeps the fluorescent pill.) */
#photography .photo-seeall {
  position: relative; right: auto; left: auto; top: auto; transform: none;
  background: none; box-shadow: none; border: none; isolation: auto; overflow: visible;
  margin: 0; padding: 0.4rem 0.4rem;
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(1.15rem, 2.2vw, 1.6rem); letter-spacing: 0.01em;
  color: var(--ink-soft); text-shadow: none;
}
#photography .photo-seeall::before, #photography .photo-seeall::after { content: none; }
#photography .photo-seeall > span { text-shadow: none; }
#photography .photo-seeall:hover { color: var(--ink); transform: none; box-shadow: none; }
.photo-filter__tab { background: none; border: none; cursor: pointer; font-family: var(--font-display); font-size: clamp(1.15rem, 2.2vw, 1.6rem); color: var(--ink-soft); letter-spacing: 0.01em; padding: 0.4rem 0.3rem; transition: color 0.25s ease; }
.photo-filter__tab:hover { color: var(--ink); }
/* active tab text flows with the SAME fluorescent palette as the "see all" pill, so they read as linked */
.photo-filter__tab.is-active {
  background: linear-gradient(90deg, #ff2d95, #00e5ff, #39ff14, #b026ff, #ff8a00, #ff2d95);
  background-size: 200% auto;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  animation: tabFlow 5s linear infinite;
}
@keyframes tabFlow { to { background-position: 200% center; } }
@media (prefers-reduced-motion: reduce) { .photo-filter__tab.is-active { animation: none; } }

/* Section title flash — the large section name fades in, holds, then out the
   first time a section scrolls into view (driven by .is-visible from the panel observer). */
.section-intro {
  position: fixed; inset: 0; z-index: 40;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none; opacity: 0;
  font-family: var(--font-display); font-weight: 500;
  font-size: clamp(3.5rem, 15vw, 12rem); letter-spacing: -0.01em; color: var(--ink);
  text-shadow: 0 2px 30px rgba(0, 0, 0, 0.6), 0 1px 6px rgba(0, 0, 0, 0.5);
}
#photography.is-visible .section-intro,
#poetry.intro-fire .section-intro { animation: sectionIntro 2.2s ease both; }
@keyframes sectionIntro {
  0%   { opacity: 0; }
  22%  { opacity: 1; }
  55%  { opacity: 1; }
  100% { opacity: 0; }
}

/* v291 — Career title-cards (Stonecrop = "Start-up 1", democraci = "Start-up 2"): the big
   name flashes over a dark field, THEN the whole section (video reset to the top + playing)
   fades in. Generic on .intro-armed, which JS adds ONLY to these sections on desktop + motion
   — so mobile keeps its untouched keep-alive video (the iOS band fix) and reduced-motion users
   get the plain video. While armed & not yet lit, EVERY direct child but the flashing title is
   hidden; .video-lit releases each element back to its natural state (reveal / idle-fade /
   scroll-reveal), transitioning in over 1.1s. */
.panel.intro-armed { background: #06070a; }
.panel.intro-armed > *:not(.section-intro) { transition: opacity 1.1s ease; }
.panel.intro-armed:not(.video-lit) > *:not(.section-intro) { opacity: 0 !important; }
.panel.intro-armed.intro-fire .section-intro { animation: sectionIntro 2.6s ease both; }

/* Photo content (collage + controls) fades in just after the "photography" title flash. */
#photography .photo-mosaic,
#photography .photo-filter { opacity: 0; }
#photography.is-visible .photo-mosaic,
#photography.is-visible .photo-filter { animation: photosFadeIn 1.2s ease 1.5s both; }
@keyframes photosFadeIn { from { opacity: 0; } to { opacity: 1; } }
/* MOBILE (≤760px): pin Photography to EXACTLY one viewport so it fills top-to-bottom
   like its neighbors (phones have no scroll-snap — a 1.3x section left CSL peeking above
   and Poetry below). The two mosaics flex to split the height after the album bar; the JS
   (photography IIFE) sizes their rows via targetH to fill cleanly, and overflow:hidden
   clips any small remainder so a neighbour never shows. Desktop keeps its natural height. */
@media (max-width: 760px) {
  /* Fill the whole PHONE screen. ⚠ 100lvh ALONE is ~120px SHORT of the real screen on iOS
     (lvh ≈836 < screen ≈956) — that shortfall is exactly what let CSL peek above + Poetry
     below. The +120px overshoot (same recipe as the video sections) reaches behind both
     glass bars. Each mosaic FLEX-fills exactly half the space (after the album bar); the JS
     builds enough tall rows to overflow that half, and overflow:hidden clips the remainder —
     so the two mosaics fill edge-to-edge at ANY width and the bar sits snug between them
     (natural row height is width-dependent, so it can't be left to fill on its own). */
  #photography { height: var(--app-h); height: calc(100lvh + 120px); min-height: 0; overflow: hidden; }
  #photography .photo-mosaic { flex: 1 1 0; min-height: 0; }
}
.photo-dot { width: 8px; height: 8px; border-radius: 50%; background: rgba(255, 255, 255, 0.35); cursor: pointer; transition: background 0.25s ease, transform 0.25s ease; }
.photo-dot.is-active { background: rgba(255, 255, 255, 0.92); transform: scale(1.15); }

/* ============================================================ */
/*  Poetry feed page (poetry.html) — poems stacked vertically;   */
/*  each poem's video is pinned (sticky) while you scroll its     */
/*  text, then the next poem's video takes over.                 */
/* ============================================================ */
.poetry-feed { background: var(--bg); color: var(--ink); height: 100svh; overflow: hidden; }
/* Paged feed: each poem is ONE snap page (a full screen). The poem's text scrolls
   WITHIN its page (the .verse__body scroller, overscroll contained), and only once
   you've scrolled to the end does another scroll snap the whole page up to the next
   poem — so you always read the entire poem before advancing. */
.poem-feed { height: 100svh; overflow-y: scroll; scroll-snap-type: y mandatory; -webkit-overflow-scrolling: touch; }
.poem-panel { position: relative; height: 100svh; scroll-snap-align: start; scroll-snap-stop: always; overflow: hidden; background-color: #06060a; }
/* video fills the whole page (full browser height); the poem TEXT is what's padded */
.poem-panel .panel__bg { position: absolute; inset: 0; z-index: 0; }
.poem-panel .panel__overlay {
  position: absolute; inset: 0;
  background: radial-gradient(64% 82% at 24% 33%,
    rgba(6, 6, 10, 0.92) 0%, rgba(6, 6, 10, 0.62) 33%, rgba(6, 6, 10, 0.18) 63%, rgba(6, 6, 10, 0) 90%);
}
/* The panel__inner is the FULL-WIDTH scroller — the wheel-catch spans the whole screen,
   so scrolling ANYWHERE reads through the poem; only at its bottom does the scroll chain
   (overscroll auto, vs the carousel's 'contain') to the feed and snap to the next poem.
   (The text itself stays a narrow left-of-center column inside this full-width scroller.) */
.poem-panel .panel__inner {
  position: relative; z-index: 1; height: 100%; max-width: none;
  overflow-y: auto; overscroll-behavior: auto;
  padding: 14svh var(--pad) 17svh;   /* roughly symmetric, a touch more room at the bottom */
  /* fade the last couple of lines so the reader senses there's more below; lines
     sharpen back to full strength as they scroll up out of the fade zone. */
  -webkit-mask-image: linear-gradient(to bottom, #000 87%, rgba(0, 0, 0, 0.22) 100%);
  mask-image: linear-gradient(to bottom, #000 87%, rgba(0, 0, 0, 0.22) 100%);
}
.poem-panel .verse__title { margin-bottom: 3.4rem; }   /* more air under the title (was 1.9rem) */
.poem-panel .verse { height: auto; }                                          /* flow; the inner scrolls it */
.poem-panel .verse__body { overflow: visible; flex: 0 0 auto; height: auto; padding-bottom: 0; }
.poem-panel .verse__photo { margin-top: 2rem; max-width: min(100%, 30rem); border-radius: 3px; }
@media (min-width: 761px) {
  .poem-panel .panel__inner { padding-left: 28%; }   /* left-of-center */
  .poem-panel .verse { margin-inline-start: 0; }
}
.poem-feed-back {
  position: fixed; top: clamp(1rem, 2.5vw, 1.8rem); left: clamp(1rem, 3vw, 2.2rem); z-index: 50;
  font-family: var(--font-ui); font-size: 0.9rem; color: var(--ink); text-decoration: none;
  padding: 0.45rem 0.9rem; border-radius: 999px;
  background: rgba(10, 10, 14, 0.42); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  transition: background 0.25s ease;
}
.poem-feed-back:hover { background: rgba(10, 10, 14, 0.72); }

/* Floating right-side table-of-contents — shared by the poem TOC (poetry feed)
   and the site-section TOC (homepage). Fades in on cursor move / scroll / near a
   poem's end; the current item reads full-white, the rest dimmed; click to jump. */
.poem-toc, .site-toc {
  position: fixed; right: clamp(1.2rem, 3vw, 2.8rem); top: 50%; transform: translateY(-50%);
  z-index: 40; display: flex; flex-direction: column; align-items: flex-end; gap: 0.65rem;
  opacity: 0; transition: opacity 0.5s ease; pointer-events: none;
}
.poem-toc.is-visible, .site-toc.is-visible { opacity: 1; }
.poem-toc.is-visible .poem-toc__item,
.site-toc.is-visible .site-toc__item,
.site-toc.is-visible .site-toc__download { pointer-events: auto; }   /* the container is pointer-events:none; re-enable the download control too (not just __item) */
.poem-toc__item, .site-toc__item {
  position: relative; background: none; border: none; cursor: pointer; text-align: right; white-space: nowrap;
  font-family: var(--font-ui); font-size: clamp(1.1rem, 2vw, 1.5rem); letter-spacing: 0.015em;
  color: rgba(255, 255, 255, 0.42); padding: 0.12rem 0;
  /* no scrim — a stronger shadow keeps the titles legible over bright footage */
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.85), 0 0 12px rgba(0, 0, 0, 0.6); transition: color 0.3s ease;
}
.poem-toc__item:hover, .site-toc__item:hover { color: rgba(255, 255, 255, 0.72); }
/* active item + a vertical marker line to its right — pure white */
.poem-toc__item.is-active, .site-toc__item.is-active { color: #fff; }
.poem-toc__item.is-active::after,
.site-toc__item.is-active::after {
  content: ""; position: absolute; right: -0.85rem; top: 50%; transform: translateY(-50%);
  width: 2.5px; height: 1.15em; border-radius: 2px;
  background: #fff; box-shadow: 0 0 5px rgba(0, 0, 0, 0.55);
}
/* Poem-feed TOC stays hidden on phones (the feed carries its own back control).
   The SITE menu, by contrast, RETURNS on mobile as a full-screen overlay — see
   the mobile block below the desktop panel rules. */
@media (max-width: 640px) { .poem-toc { display: none; } }

/* Homepage site-section TOC ONLY: a frosted-glass panel anchored TOP-right (the
   shared rule above centers it vertically for the poem feed). Grouped Work/Creative. */
.site-toc {
  top: clamp(1.4rem, 4vh, 3rem);
  transform: none;                 /* cancel the shared vertical centering */
  transition: opacity 0.25s ease;  /* quicker than the shared 0.5s — snaps in as the name fades */
  gap: 0.42rem;
  padding: 1.5rem 1.3rem;   /* the name is the first item now (site-toc__item--home), not a floating header */
  border-radius: 200px 22px 22px 22px;   /* big top-left sweep (TL TR BR BL) mirroring the About body's big top-right corner */
  background: rgba(15, 15, 19, 0.3);
  -webkit-backdrop-filter: blur(16px) saturate(1.15);
  backdrop-filter: blur(16px) saturate(1.15);
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.3);
}
/* the glass panel supplies the contrast, so drop the text-shadow the poem TOC needs over bare video */
.site-toc__item { text-shadow: none; }
/* the home item IS the brand "s cotton jr." — keep the brand's small Inter
   size/weight so it matches the persistent copy that fades out over it (it still
   picks up the shared active white + caret when the home section is current) */
.site-toc__item--home { text-transform: lowercase; }   /* same size/weight as the other items (inherits the base); just keep it lowercase */
/* Work / Creative group heading — a small uppercase eyebrow above its sections */
.site-toc__group {
  align-self: flex-end; text-align: right; white-space: nowrap;
  font-family: var(--font-ui); font-size: clamp(0.58rem, 0.9vw, 0.68rem);
  letter-spacing: 0.24em; text-transform: uppercase; font-weight: 500;
  color: rgba(255, 255, 255, 0.5);
  margin: 1.3rem 0 0.1rem; pointer-events: none;   /* strong air ABOVE each group heading, tight below to its items */
}
.site-toc__item--gap { margin-top: 1.3rem; }   /* Connect, detached from the Creative group */

/* Download-as-markdown control — sits at the bottom of the DESKTOP menu panel, a quiet
   utility below the nav list (not a destination). Builds + downloads a Markdown snapshot
   of the site's content. Hidden on the mobile overlay (see the max-width:640px block). */
.site-toc__download {
  display: inline-flex; align-items: center; gap: 0.42em;
  align-self: flex-end; margin-top: 1.5rem;
  background: none; border: 0; padding: 0.2rem 0; cursor: pointer;
  font-family: var(--font-ui); font-size: 0.8rem; letter-spacing: 0.06em;
  text-transform: uppercase; color: rgba(255, 255, 255, 0.4);
  transition: color 0.25s ease;
}
.site-toc__download:hover { color: rgba(255, 255, 255, 0.85); }
.site-toc__download svg { width: 0.95em; height: 0.95em; display: block; }
.site-toc__download-label { line-height: 1; }

/* Media controls folded into the panel bottom ("control panel"): reset the fixed
   positioning, add a divider from the nav, and let the panel's reveal govern them. */
.site-toc .controls {
  position: static; right: auto; bottom: auto; z-index: auto;
  opacity: 1;   /* constant — the panel's own opacity does the fade (no double-fade) */
  width: 100%; margin-top: 1.2rem;   /* breathing room above the controls — NO divider line */
  justify-content: end;   /* right-align the button cluster */
}
.site-toc.is-visible .controls { pointer-events: auto; }
/* pull the scrubber in a touch so its solid line doesn't visually overshoot the rounded buttons */
.site-toc .controls .seek-bar { width: calc(100% - 1.3rem); margin-inline: auto; }

/* ---- DESKTOP menu (>640px): the same graduated blur as mobile, but confined to the
   RIGHT ~THIRD (the menu side) so the left two-thirds stay clear and the video reads.
   Drops the frosted glass PANEL for the mobile twin-layer blur, masked to the right.
   (Opening the menu — name click OR the top-right hover zone — also force-shows the
   media controls at the bottom; see the JS.) Placed after the desktop panel rules. */
@media (min-width: 641px) {
  .site-toc {
    /* drop the glass panel — the ::before/::after gradient blur is the backdrop now */
    background: none; border: none; box-shadow: none; border-radius: 0;
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  /* the labels lose the panel's contrast — restore the base scrim shadow for legibility */
  .site-toc__item { text-shadow: 0 1px 4px rgba(0, 0, 0, 0.85), 0 0 12px rgba(0, 0, 0, 0.6); }
  /* LAYER 1 — light base blur, fades IN across the right third (no hard vertical edge) */
  .site-toc::before {
    content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
    background: linear-gradient(to right, transparent 55%, rgba(10, 10, 13, 0.30) 100%);
    -webkit-backdrop-filter: blur(6px) saturate(1.05);
    backdrop-filter: blur(6px) saturate(1.05);
    -webkit-mask-image: linear-gradient(to right, transparent 52%, rgba(0,0,0,0.55) 72%, #000 88%);
    mask-image: linear-gradient(to right, transparent 52%, rgba(0,0,0,0.55) 72%, #000 88%);
  }
  /* LAYER 2 — heavier blur, concentrated in the right third behind the labels */
  .site-toc::after {
    content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
    -webkit-backdrop-filter: blur(17px) saturate(1.12);
    backdrop-filter: blur(17px) saturate(1.12);
    -webkit-mask-image: linear-gradient(to right, transparent 62%, rgba(0,0,0,0.5) 82%, #000 100%);
    mask-image: linear-gradient(to right, transparent 62%, rgba(0,0,0,0.5) 82%, #000 100%);
  }
  /* the blur third covers the bottom-right where the controls live — lift them above it
     (z-index:40 site-toc > 11 controls) while the menu is open so they stay crisp/usable */
  body.menu-open .controls { z-index: 41; }
}

/* ---- MOBILE site menu (≤640px): no glass-on-glass. Tapping the name opens a
   full-screen overlay whose backdrop is a FULL-WIDTH graduated blur — genuinely
   blurred edge-to-edge, lightest on the left (you keep your place in the page)
   ramping EVENLY to heaviest behind the right-anchored list. Tap a label to jump;
   tap anywhere on the backdrop to dismiss. Stephen's brief: "full-screen blur,
   stretch full width, balanced gradient as it becomes less blurry to the left;
   darken a touch; don't black it out."

   TWIN-LAYER progressive blur (verified vs iOS Safari + Blink over a
   design/adversarial panel): a single backdrop-filter can't graduate its blur
   RADIUS — a mask only varies opacity, which over live video reads as a sharp
   double-image ghost, not a lighter blur. So:
     ::before  = LIGHT blur, unmasked, full width  → guarantees no clear strip &
                 no sharp bleed-through (the load-bearing base)
     ::after   = HEAVY blur, masked to ramp in L→R → the masked-out left reveals
                 the base BLUR (never sharp video), so it graduates cleanly
   Both keep -webkit- prefixes; .site-toc itself stays backdrop-filter:none so it
   is NOT a backdrop root (else the child blurs would sample an empty layer).
   Placed AFTER the desktop .site-toc rules so it overrides them inside the query. */
@media (max-width: 640px) {
  .site-toc {
    display: flex; flex-direction: column;
    position: fixed; inset: 0; top: 0; transform: none;
    justify-content: center; align-items: flex-end;   /* right-anchored list, vertically centered */
    gap: 0.5rem;
    padding: 0 clamp(1.5rem, 6.5vw, 2.6rem) 0 0;      /* items inset from the right edge */
    /* strip the frosted panel entirely — the ::before backdrop is the whole effect */
    background: none; border: none; box-shadow: none; border-radius: 0;
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  /* LAYER 1 — LIGHT blur across the WHOLE width, unmasked. Guarantees edge-to-edge
     blur (no clear strip) with zero sharp bleed-through, so the heavy layer above
     always blends blur-over-blur (never a sharp-video ghost). Carries the gentle,
     EVEN, full-width darken — never fully transparent at the left. */
  .site-toc::before {
    /* ⚠ Do NOT reach the bottom. On newer iOS a fixed backdrop-filter near the pill zone
       triggers the video-band → a BLACK BANNER (Stephen's real device shows it; the 26.0
       sim doesn't, because 26.0 clips fixed elements above the pill anyway).
       ★ v359 — THE KEY LESSON: masking the blur away is NOT enough. Up to v358 the box was
         `height: var(--app-h)`, whose bottom lands exactly ON the do-not-cross line, and only
         the MASK lifted the visible blur 0.75rem clear. It still banded — because iOS
         composites the backdrop-filter LAYER at its full BOX size regardless of the mask.
         So the BOX itself must be pulled up: height is now `var(--app-h) - 3rem`, putting the
         element's bottom ~48px ABOVE the do-not-cross line, with the feather ending exactly
         at that new bottom (transparent 100%) so there's still no hard edge.
       KNOB: the `- 3rem`. If the banner still appears, raise it (4rem, 5rem…) — that lifts the
       whole layer further off the pill. The 4.5rem feather start is tied to it (see below). */
    content: ""; position: fixed; top: 0; left: 0; right: 0;
    height: calc(var(--app-h) - 3rem);   /* v359: box PULLED UP off the pill line — see note */
    z-index: -1; pointer-events: none;
    background: linear-gradient(to right,
      rgba(8, 8, 11, 0.16) 0%, rgba(8, 8, 11, 0.34) 50%, rgba(8, 8, 11, 0.56) 100%);
    -webkit-backdrop-filter: blur(6px) saturate(1.06);
    backdrop-filter: blur(6px) saturate(1.06);
    /* VERTICAL feather (v359 geometry): the box now ends ~48px (3rem) above the do-not-cross
       line, so `calc(100% - 4.5rem)` = 72px above the box bottom = ~120px above the line —
       still exactly the BOTTOM of the hero title "Hi. I'm Stephen." (~121px up), so the title
       stays fully/evenly blurred. It then fades to 0 by 100% = the box's own bottom, 48px clear
       of the pill. (Was 7.5rem→0.75rem against a full-height box.) If you change the `- 3rem`
       height, move this 4.5rem by the same amount to keep the title on the line.
       HORIZONTAL taper: this LIGHT base is full across the right
       ~7/8, tapering to 0 at the far-LEFT edge (0%→full by 12%) so the far left is truly clear
       (Stephen keeps his place). It reaches zero a bit LEFT of the heavy layer, so the heavy's
       taper dissolves onto light blur (not sharp video) = no ghost. The two masks INTERSECT. */
    -webkit-mask-image:
      linear-gradient(to right, transparent 0%, #000 12%, #000 100%),
      linear-gradient(to bottom, #000 calc(100% - 4.5rem), transparent 100%);
    mask-image:
      linear-gradient(to right, transparent 0%, #000 12%, #000 100%),
      linear-gradient(to bottom, #000 calc(100% - 4.5rem), transparent 100%);
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
  }
  /* LAYER 2 — HEAVIER blur, masked STRONG across the right ~2/3 (full behind the right-
     anchored list) and tapering to 0 over the left third. It sits over Layer 1, so where
     it's masked out it reveals the LIGHT base (not sharp video) until the light base itself
     tapers to clear at the far-left edge. Net: clear (far left) → light → very strong (right). */
  .site-toc::after {
    /* Box PULLED UP 3rem off the do-not-cross line, same as the light base (v359 — the mask
       alone didn't stop the band; iOS composites the whole layer box). The horizontal mask
       (L→R, heavy-right) is INTERSECTED with the same bottom feather so the heavy plane also
       fades out well above the pill (no hard line on the right side). Two masks + composite. */
    content: ""; position: fixed; top: 0; left: 0; right: 0;
    height: calc(var(--app-h) - 3rem);   /* v359: box PULLED UP off the pill line — see note */
    z-index: -1; pointer-events: none;
    -webkit-backdrop-filter: blur(20px) saturate(1.12);
    backdrop-filter: blur(20px) saturate(1.12);
    /* VERTICAL fade over the SAME 4.5rem → box-bottom range as the light base. HORIZONTAL: VERY
       STRONG across the right ~2/3 (full from 33% to 100%, behind the list), tapering to 0 over
       the left third (full→0 by 5%) so it dissolves to clear at the far left. Its masked-out
       left reveals the LIGHT base (which extends further left) = no sharp ghost. Masks INTERSECT. */
    -webkit-mask-image:
      linear-gradient(to right, transparent 5%, #000 33%, #000 100%),
      linear-gradient(to bottom, #000 calc(100% - 4.5rem), transparent 100%);
    mask-image:
      linear-gradient(to right, transparent 5%, #000 33%, #000 100%),
      linear-gradient(to bottom, #000 calc(100% - 4.5rem), transparent 100%);
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
  }
  /* the full-screen container catches taps on the clear area to dismiss
     (JS closes when the tap target IS the container, i.e. not an item) */
  .site-toc.is-visible { pointer-events: auto; }
  /* download control is DESKTOP-MENU-ONLY (Stephen's call) — hide it on the mobile overlay */
  .site-toc__download { display: none !important; }
  /* ⚠ Fully DROP the overlay from hit-testing when CLOSED. A full-screen fixed
     backdrop-filter that lingers at opacity:0 keeps capturing touch/scroll on iOS —
     pointer-events:none is NOT enough there (Stephen: "can't scroll after closing the
     menu"). visibility:hidden (applied AFTER the fade) removes it from hit-testing AND
     stops the pseudos' backdrop-filter from compositing. Visible immediately on open. */
  .site-toc { visibility: hidden; transition: opacity 0.3s ease, visibility 0s 0.3s; }
  .site-toc.is-visible { visibility: visible; pointer-events: auto; transition: opacity 0.3s ease, visibility 0s 0s; }
  /* bigger touch targets + restore legibility (the panel's contrast is gone now) */
  .site-toc__item {
    font-size: clamp(1.35rem, 5.4vw, 1.7rem);
    padding: 0.34rem 0;
    text-shadow: 0 1px 6px rgba(0, 0, 0, 0.7), 0 0 16px rgba(0, 0, 0, 0.5);
  }
  .site-toc__group { font-size: 0.62rem; margin-top: 1.5rem; }
  .site-toc__item--gap { margin-top: 1.5rem; }
}

/* poetry "see all" — a GLASSMORPHIC pill (not the vibrant fluorescent one), bottom-right of
   #poetry. Kills the fluorescent blobs + white stroke; frosted glass instead. */
.poem-seeall {
  top: auto; bottom: clamp(1.1rem, 3.5vh, 2.2rem); right: clamp(1rem, 3vw, 2.4rem);
  transform: none; z-index: 7; text-decoration: none;
  background: rgba(16, 16, 20, 0.38); box-shadow: 0 8px 26px rgba(0, 0, 0, 0.3);
  border: 1px solid rgba(255, 255, 255, 0.16);
  -webkit-backdrop-filter: blur(14px) saturate(1.12); backdrop-filter: blur(14px) saturate(1.12);
}
.poem-seeall::before, .poem-seeall::after { content: none; }   /* remove the fluorescent blobs + white stroke */
.poem-seeall > span { text-shadow: none; }
.poem-seeall:hover { transform: scale(1.06); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); }
@media (max-width: 760px) {
  /* TETHER to the Safari pill: position:fixed pins it just above the bottom toolbar and
     TRACKS the bar as it expands/collapses (iOS pins fixed to the area above the bar).
     Shown only once poetry is fully in view (a fade avoids a hard pop-in). */
  #poetry .poem-seeall {
    position: fixed; margin-left: 0; right: 1rem; left: auto;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 1.3rem);
    opacity: 0; pointer-events: none; transition: opacity 0.35s ease;
  }
  #poetry.poetry-live .poem-seeall { opacity: 1; pointer-events: auto; }
  #poetry .poem-seeall:hover { transform: scale(1.06); }
}

/* ============================================================
   iOS 26 glass bands — feed them VIDEO (2026-07-13). Debug forensics showed: in-browser
   portrait Safari gives env() = 0 (cover is a no-op) and lvh (836) < screen (956) — but the
   RENDER CANVAS spans the full screen, and the glass chrome (status-bar zone + bottom pill
   zone, ~62pt + ~58pt) shows the ADJACENT DOCUMENT PIXELS (that's how ESPN reads under the
   pill). So: stick the tall sections' videos 70px ABOVE the viewport and overshoot below —
   their pixels then fill both bands whenever the video is pinned. Mobile + lvh-capable only.
   ============================================================ */
@media (max-width: 760px) {
  @supports (height: 100lvh) {
    /* All three tall sections pin IDENTICALLY at -70 (the earlier About "-70 failed" test was
       confounded — its video wasn't painting at all back then). Matching the proven config. */
    #stonecrop .panel__bg,
    #community-square .panel__bg,
    #work-1 .panel__bg {
      top: 0;
      height: calc(100lvh + 120px);   /* THE PROVEN CONFIG (stonecrop worked with exactly
                                         this on 7/13): all 120px of overshoot go BELOW the
                                         fold, where iOS's bottom glass lives. The later
                                         "-70px/+140" unification stole 50px from the bottom
                                         for the top and broke the working bottom band. */
      /* Safari 26 tints its glass bars from background-color on fixed/STICKY elements near
         the screen edges (video/absolute children are never sampled; theme-color ignored).
         var(--bg) here painted the bars near-black regardless of the pixels behind them. */
      background-color: transparent;
    }
    /* About's glass window is positioned by a sticky holder pulled up by the OLD bg height —
       re-cancel the new one so the window still lands at the section top. */
    #work-1 .panel__inner {
      margin-top: calc(-1 * (100lvh + 120px));
    }
    /* HERO: it was hard-clipped to exactly one screen, so its video had nothing to give the
       bottom band. Run the section (and so its inset:0 video) 70px past the fold; lift the
       bottom-anchored content the same 70px so nothing sinks behind the pill. Top band at
       page-top stays tint-only (the one true in-browser limit). */
    #home {
      height: calc(100lvh + 70px);
      min-height: calc(100lvh + 70px);
    }
    #home .panel__inner { margin-bottom: 70px; }
    /* Hero video box also reaches the screen bottom (the section stays lvh+70 so the
       scroll pacing is untouched — only the bg overshoots further down). v224: -50px put
       the box at EXACTLY the screen bottom (906 section + 50) with zero margin — any
       device where lvh resolves differently left a visible gap under the pill. -120px
       buys real margin; the overhang tucks harmlessly behind the next section. */
    #home .panel__bg { bottom: -120px; }
    #home .scroll-cue { bottom: calc(clamp(1.5rem, 4vh, 3rem) + 70px); }
  }
  /* About mid-read band: the overlay spans the FULL 976px video box, so its darkest gradient
     stops (~85% black at 100%) landed exactly in the band zone below the viewport — burying
     the video pixels that are genuinely there. End the overlay at the viewport's bottom edge:
     identical look on-screen, raw murmuration in the band (like stonecrop/democraci). */
  @supports (height: 100lvh) {
    /* Box pins at -70, so +70 keeps the overlay ending exactly at the viewport's bottom edge:
       full graded look on-screen, raw video in the band overshoot below. */
    #work-1 .panel__overlay { bottom: auto; height: calc(100lvh + 120px); }   /* v234: match the BOX (lvh+120) — at 100lvh the veil was exactly the banner-height short, leaving an undimmed strip of footage at the box bottom (Stephen's yellow-seam screenshot) */
  }
  /* About→Stonecrop handoff: the glass band shows the NEXT section's pixels at a boundary
     (adjacent-document rendering) — Stonecrop's dark entry read as a hard "black strip"
     against the bright murmuration. Fade in DOCUMENT space at the SECTION'S END (not on the
     pinned video box — that darkened the band permanently): invisible mid-read (band = pure
     video), it scrolls into the band only as the handoff approaches, easing bright → dark. */
  #work-1::after {
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 280px;
    background: linear-gradient(to bottom, rgba(6, 7, 10, 0) 0%, rgba(6, 7, 10, 0.92) 100%);
    z-index: 1;
    pointer-events: none;
  }
}

/* ============================================================
   LAST STRUCTURAL SUSPECT for the About band (2026-07-14): About is the ONLY section whose
   videos live under a PERMANENTLY-PINNED backdrop-filter (the bio glass) — WebKit flattens
   the backdrop-sampled subtree into a special buffer, plausibly viewport-sized, which would
   clip EVERY below-fold pixel (video, twin, poster alike → the black band; also why the
   muted twin didn't help). On phones, swap the frosted blur for a flat smoky tint of the
   same weight — visually close over the dark graded footage — and drop the will-change
   hint. Desktop keeps the real frosted glass.
   ============================================================ */

/* ============================================================
   THE About band root cause (2026-07-14, proven by simulator A/B): `.bio-scroll`'s
   `will-change: transform` — a permanently-promoted, every-frame-mutated layer pinned
   over the videos — made iOS clip ALL video layers beneath it to the viewport (black
   band, and why no video-side fix ever worked). Unpromote it on phones: the JS translate
   still applies fine, and the murmuration paints full-bleed. The frosted glass was
   innocent — restored. Desktop keeps the will-change (no such clipping there).
   ============================================================ */
@media (max-width: 760px) {
  #work-1 .bio-scroll { will-change: auto; }
}

/* About's under-card strip read BLACK even with video painting (2026-07-14): the overlay's
   bottom stop + the left-radial's lower tail + the glass card's 70px drop shadow all STACK
   in the region between the card and the toolbar. Ease only the BOTTOM of that stack on
   phones — the top/mid grading Stephen approved is unchanged. */
@media (max-width: 760px) {
  #work-1 .panel__overlay {
    background:
      linear-gradient(to bottom, rgba(10, 10, 12, 0.20) 0%, rgba(10, 10, 12, 0.08) 38%, rgba(10, 10, 12, 0.22) 78%, rgba(10, 10, 12, 0.10) 100%),
      radial-gradient(120% 120% at 50% 0%, transparent 45%, rgba(10, 10, 12, 0.12) 100%);
  }
  #work-1 .panel__overlay::before {
    background: radial-gradient(70% 120% at 0% 42%,
      rgba(8, 8, 12, 0.8) 0%, rgba(8, 8, 12, 0.5) 34%, rgba(8, 8, 12, 0.18) 58%, transparent 78%);
  }
  #work-1.is-visible .bio-view { box-shadow: 0 14px 34px rgba(0, 0, 0, 0.2); }
}

/* ============================================================
   ABOUT on MOBILE — reveal redesign (2026-07-14, Stephen's spec): make it behave like the
   other sections. The About was the ONE section with a LOCKED bottom edge (the glass
   window's box froze at load-time innerHeight, leaving a permanent backdrop-only zone at
   the toolbar = the eternal "strip"). Now: the window tracks the REAL screen (lvh), RISES
   IN from below the fold on arrival (like the others' cards), settles at ~28% from the top
   so the murmuration stays visible above, and gets horizontal padding. The under-card zone
   belongs to the video + blur (murmuration-blur twin). Desktop unchanged.
   ============================================================ */
@media (max-width: 760px) {
  #work-1 .panel__inner { height: 100lvh; }        /* track the true screen, not load-time innerH */
  #work-1 .bio-view {
    top: 28vh;                                     /* settled position: video visible above the card */
    bottom: max(11vh, 104px);                      /* card ends above the toolbar zone; blur/video below */
    left: 4vw;
    right: 4vw;
    width: auto;                                   /* (base sets width; left+right now size it) */
    transform: translateY(80vh);                   /* start OFF-SCREEN below, like the others */
    /* v221: backdrop-filter dropped from the mobile transition list — animating a filter
       from blur(0) promotes the pane exactly like having one. */
    transition: background-color 0.9s ease, border-color 0.9s ease, box-shadow 0.9s ease,
                transform 1s cubic-bezier(0.22, 0.9, 0.3, 1);
  }
  #work-1.is-visible .bio-view { transform: translateY(0); }
}







/* About's end-fade (::after) + overlay scrim: RESTORED 2026-07-14 (v222). They were
   removed chasing the footer band, but the band was proven to be iOS 26.0 clipping
   pinned sticky video planes (fixed by Apple in 26.1) — the overlays were never it. */

/* ============================================================
   THE ROOT CAUSE, PROVEN BY ABLATION (2026-07-14, strip=5/7 + Stephen's clone test):
   .bio-view's backdrop-filter — a huge, viewport-anchored frosted pane held over the
   video for About's entire read — makes iOS clip EVERY video plane on the page to the
   viewport while it exists (the neighbors' "blurry footer"). With ONLY the frost
   removed (strip=7), stonecrop composites full-bleed under the pill on real gestures.
   Phones: flat smoky pane for legibility instead. Desktop keeps the real frost.
   ============================================================ */
/* v221: THE ABOVE FIX NEVER SHIPPED. `#work-1 .bio-view` (1,1,0) loses the cascade to
   `#work-1.is-visible .bio-view` (1,2,0) at the top of this file, which restores
   blur(18px) the moment About becomes visible — permanently. strip=7 healed the device
   because its injected rule used !important; this one didn't. Match both. */
@media (max-width: 760px) {
  #work-1 .bio-view,
  #work-1.is-visible .bio-view {
    /* v251: glassmorphism restored — backdrop-filter was never the band's cause (that
       was the scrollTo + encodes). Softer dark wash + a real frost. */
    -webkit-backdrop-filter: blur(20px) saturate(1.2) !important;
    backdrop-filter: blur(20px) saturate(1.2) !important;
    background: rgba(13, 14, 18, 0.42) !important;
  }
}

/* Neutral seam-breathers — OFF (v220). On the verified v109 ride the ONLY two
   sections with a black band at the pill were the two that FOLLOW a .panel-gap
   (About, stonecrop): the 22vh #0a0a0c breather itself IS the band, riding through
   the pill zone during the handoff. Every gapless seam (stonecrop->democraci->labs)
   rode clean. Re-enable with:
   .panel-gap { display: block; height: 22vh; background: #0a0a0c; } */
.panel-gap { display: none; }


/* ============================================================
   v230 — ABOUT = DEMOCRACI'S ANATOMY ON PHONES (Stephen, 2026-07-14):
   the bio is a normal in-flow glass card that scrolls up from below the screen
   over the pinned murmuration, exactly like stonecrop/democraci's cards. The
   sticky reading window + scroll-jacked text remain DESKTOP-only (the JS driver
   no-ops on <=760px as of v128). !important throughout — the older mobile rules
   for these elements are a specificity minefield (see the v221 lesson).
   ============================================================ */
@media (max-width: 760px) {
  #work-1 { height: auto !important; min-height: calc(100lvh + 120px); }
  #work-1 .panel__inner {
    /* v231: relative + z-index, NOT static — the sticky video box is a positioned
       element, so it paints OVER static content: the card's glass rendered UNDER the
       video (Stephen: "zIndex looks off", text floating naked on the fish). relative
       keeps normal flow but lifts the whole card above the box, like stonecrop's panes. */
    position: relative !important;
    z-index: 2 !important;
    height: auto !important;
    margin: calc(-1 * (100lvh + 120px)) 0 0 !important;   /* ride over the sticky video box */
    /* 65vh of bare murmuration before the card arrives; 48vh tail after it (the
       unpinned ride-out that hands off to stonecrop as footage, democraci-style) */
    padding: 65vh 0 48vh !important;
    display: block !important;
  }
  #work-1 .bio-view {
    position: static !important;
    inset: auto !important;
    top: auto !important; bottom: auto !important; left: auto !important; right: auto !important;
    height: auto !important;
    width: auto !important;
    margin: 0 4vw !important;
    transform: none !important;
    transition: none !important;
    overflow: visible !important;
    -webkit-mask-image: none !important;
    mask-image: none !important;
  }
  #work-1 .bio-scroll {
    position: static !important;
    height: auto !important;
    margin: 0 !important;
    transform: none !important;
    overflow: visible !important;
    -webkit-mask-image: none !important;
    mask-image: none !important;
  }
}


/* ============================================================
   v235 — RAW FOOTAGE IN THE GLASS BAND (Stephen's translucent-banner observation:
   the videos now PAINT under the pill — the "banner" was our own veils blanketing
   the box's +120px under-pill overshoot). Every darkening layer fades out over its
   last 150px, so the band shows pure video while on-screen reading contrast stays.
   ============================================================ */
@media (max-width: 760px) {
  #work-1 .panel__overlay,
  #stonecrop .panel__bg::after,
  #community-square .panel__bg::after {
    -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 calc(100% - 150px), transparent 100%);
    mask-image: linear-gradient(to bottom, #000 0%, #000 calc(100% - 150px), transparent 100%);
  }
}


/* v236 — REBUILD PARITY, round 2 (healthy-file era). The rebuild page (perfect on
   device) has: ONE plane per box, no filter animations over it, no root-bg writes.
   About's blur twin is invisible redundancy behind a full-cover sharp — drop its
   plane entirely on phones; reveal animations lose their filter component. */
@media (max-width: 760px) {
  #work-1 .panel__blur { display: none !important; }
  #work-1 .reveal { -webkit-filter: none !important; filter: none !important; }
}


/* v237 — the welcome as an IN-FLOW cover page on phones (Stephen: "make the welcome a
   scroll"). No fixed overlay, no lock: the gate is the page's first section, painting
   bezel to bezel like all in-flow content, and you scroll past it into the hero. */
@media (max-width: 760px) {
  .intro {
    position: relative !important;
    inset: auto !important;
    height: calc(100lvh + 160px);
    padding-bottom: 280px;
    box-sizing: border-box;
    z-index: auto !important;
  }
  .intro.is-dismissed { opacity: 1 !important; pointer-events: auto !important; }
}


/* v238 — ABOUT'S BACKDROP GOES IN-FLOW ON PHONES (the rebuild's proven geometry).
   Sticky+healthy-file was never tested anywhere until v137-139 — and it bands: the
   pinned-plane cap is real on-device INDEPENDENTLY of the cursed encodes. The rebuild's
   blessed form (v9/v10) = absolute backdrop filling the whole section, riding with the
   page. Same conversion here; desktop keeps the pinned design. */
@media (max-width: 760px) {
  #work-1 .panel__bg {
    position: absolute !important;
    inset: 0 !important;
    height: auto !important;
  }
  #work-1 .panel__inner {
    margin-top: 0 !important;   /* no pull-up needed — the bg is out of flow now */
  }
}


/* v239 — freed backdrops: the work sections' blur twins ride in-flow behind the pinned
   sharp band (see script v140). Fill the WHOLE section; the sticky box above them keeps
   the letterband + veil design untouched. */
@media (max-width: 760px) {
  .blur-freed > .panel__blur {
    display: block !important;
    position: absolute !important;
    inset: 0 !important;
    width: 100% !important;
    height: 100% !important;
    object-fit: cover;
    z-index: 0;
  }
}


/* v240 — clear the sticky box's own opaque layers where the backdrop was freed: the
   box's poster background-image was hiding the riding blur entirely (crisp frozen
   imagery instead of the smear). The box keeps only the contain sharp + veil. */
@media (max-width: 760px) {
  .blur-freed .panel__bg {
    background-image: none !important;
    background-color: transparent !important;
  }
}


/* v241 — the pinned letterbands sit a touch high of center (Stephen: "they sit below
   the middle; better a little higher"). */
@media (max-width: 760px) {
  #stonecrop .panel__video, #community-square .panel__video { object-position: center 36%; }
}


/* v242 — tap-to-enter hold: the in-flow gate keeps the page from scrolling until the
   tap (no fixed overlay, no birth poisons — just overflow). */
html.gate-hold, html.gate-hold body { overflow: hidden; }



/* v248 (Stephen): About = a FIXED-HEIGHT reading window over a screen-height in-flow
   video. The section is locked to ~screen height, so the in-flow .panel__bg video is
   framed right AND wraps the pill; the .bio-view card is 64vh with the text scrolling
   INSIDE it, so the section never grows tall no matter how long the copy is. */
@media (max-width: 760px) {
  #work-1 {
    height: calc(100lvh + 100px) !important;      /* v253: box a touch less tall (was +120 = exact screen) */
    min-height: calc(100lvh + 100px) !important;
  }
  #work-1 .panel__inner {
    position: absolute !important;
    inset: 0 !important;
    margin: 0 !important;
    padding: 0 !important;
    height: auto !important;
    z-index: 2 !important;
    display: block !important;
  }
  #work-1 .bio-view {
    position: absolute !important;
    top: 16vh !important;              /* v265: moved up slightly */
    left: 0 !important;                /* anchored flush to the left edge */
    right: 20vw !important;   /* v257: a bit more narrow */
    margin: 0 !important;              /* v250: kill the leftover 4vw margin pushing it off the edge */
    bottom: auto !important;
    height: 80vh !important;           /* v259: slightly taller (added at bottom) */
    width: auto !important;
    overflow-y: auto !important;
    -webkit-overflow-scrolling: touch !important;
    transform: none !important;
    -webkit-mask-image: linear-gradient(to bottom, #000 84%, rgba(0,0,0,0.18) 100%) !important;   /* v259: fade last lines = scroll cue */
    mask-image: linear-gradient(to bottom, #000 84%, rgba(0,0,0,0.18) 100%) !important;
    border-top-left-radius: 0 !important;      /* square the edge-touching corners */
    border-bottom-left-radius: 0 !important;
    border-top-right-radius: 0 !important;          /* v348 (Stephen): sweep removed — square the top-right on mobile */
    border-bottom-right-radius: 100px !important;   /* v255: half the sweep on the bottom-right */
  }
  #work-1 .bio-scroll {
    position: static !important;
    height: auto !important;
    transform: none !important;
    margin: 0 !important;
    overflow: visible !important;
    -webkit-mask-image: none !important;
    mask-image: none !important;
  }
}


/* v260 — LEFT->RIGHT gradient blur on the About glass (Stephen): most blurred at the
   anchored LEFT edge, tapering to clear on the RIGHT so more video reads through. Since
   backdrop-filter has no gradient value, a masked ::before blur layer does it; the mask
   runs horizontally so it's independent of the vertical scroll. .bio-view is the fixed
   frame; text scrolls inside .bio-scroll. (Bottom text-fade removed per Stephen.) */
@media (max-width: 760px) {
  #work-1 .bio-view,
  #work-1.is-visible .bio-view {
    overflow: hidden !important;
    -webkit-backdrop-filter: none !important;
    backdrop-filter: none !important;
    -webkit-mask-image: none !important;
    mask-image: none !important;
    /* v262: pure gradient blur — no tint, no stroke, no shadow (shape/radius kept) */
    background: transparent !important;
    background-color: transparent !important;
    border: 1px solid rgba(255, 255, 255, 0.22) !important;   /* v264: light glass stroke so the container reads */
    box-shadow: none !important;
  }
  #work-1 .bio-view::before {
    content: "" !important;
    position: absolute !important;
    inset: 0 !important;
    z-index: 0 !important;
    pointer-events: none !important;
    border-radius: inherit !important;
    -webkit-backdrop-filter: blur(24px) saturate(1.2) !important;
    backdrop-filter: blur(24px) saturate(1.2) !important;
    -webkit-mask-image: linear-gradient(to right, #000 0%, #000 48%, rgba(0,0,0,0.15) 86%, transparent 100%) !important;
    mask-image: linear-gradient(to right, #000 0%, #000 48%, rgba(0,0,0,0.15) 86%, transparent 100%) !important;
  }
  #work-1 .bio-scroll {
    position: relative !important;
    z-index: 1 !important;
    height: 100% !important;
    overflow-y: auto !important;
    -webkit-overflow-scrolling: touch !important;
    -webkit-mask-image: none !important;
    mask-image: none !important;
  }
}


/* v266 — FULL-WIDTH scroll/touch container (Stephen): the container extends edge-to-edge
   so a swipe anywhere scrolls the card's text correctly; the visible blur + text stay on
   the LEFT (right portion is transparent, scrollable space over the video). Stroke removed. */
@media (max-width: 760px) {
  #work-1 .bio-view,
  #work-1.is-visible .bio-view {
    right: 0 !important;          /* full-width frame */
    border: none !important;      /* stroke removed */
    padding: 0 !important;        /* bio-scroll owns the insets, edge to edge */
  }
  /* v376 — the 68vh squeeze from v375 is REVERTED. It existed to clear a bottom-CENTRE mark;
     the mark now sits in the right corner, so the copy gets its full height back — which is
     the whole point of the corner move. */
  #work-1 .bio-scroll {
    /* v376: right gutter 24vw -> 32vw. On phones the copy is full-bleed, so the bottom-right
       mark would otherwise sit ON the lines (measured: ink reached x296, mark ink began x264).
       Widening the gutter buys the corner back. This trades a little HORIZONTAL width for the
       full VERTICAL height, which is the trade Stephen asked for. */
    padding: 1.2vh 32vw 5vh 4.5vw !important;
    box-sizing: border-box !important;
    /* v350: fade the READING EDGES — copy dissolves as it scrolls off the TOP and fades in
       from the BOTTOM, instead of cutting hard at the frame. The mask sits on the SCROLL
       CONTAINER, so it stays pinned to the window edges while the text moves through it.
       ⚠ NOT on .bio-view — a mask there would make it a backdrop root and kill the ::before
       blur (same trap as .site-toc). Knobs: 2rem (top exit) / 3.5rem (bottom entry). */
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 2rem, #000 calc(100% - 3.5rem), transparent 100%) !important;
    mask-image: linear-gradient(to bottom, transparent 0, #000 2rem, #000 calc(100% - 3.5rem), transparent 100%) !important;
  }
  #work-1 .bio-view::before {
    /* blur ends ~80% so it stays visually where it was (was relative to the old 80vw frame).
       v349: squaring the top-right (v348) left a HARD blur line across the TOP, so a second
       mask layer feathers the blur in from nothing over the first 4.5rem. INTERSECTED with the
       L→R fade, so the blur dissolves on the top AND the right — no hard edge anywhere.
       Knob: the 4.5rem (raise = longer/softer top fade, lower = tighter). */
    -webkit-mask-image:
      linear-gradient(to right, #000 0%, #000 48%, rgba(0,0,0,0.15) 78%, transparent 90%),
      linear-gradient(to bottom, transparent 0, #000 4.5rem, #000 calc(100% - 4.5rem), transparent 100%) !important;
    mask-image:
      linear-gradient(to right, #000 0%, #000 48%, rgba(0,0,0,0.15) 78%, transparent 90%),
      linear-gradient(to bottom, transparent 0, #000 4.5rem, #000 calc(100% - 4.5rem), transparent 100%) !important;
    -webkit-mask-composite: source-in !important;
    mask-composite: intersect !important;
  }
}


/* v270 — stonecrop identity strip: two lines on MOBILE (Stephen). Line 1 "Start-up 1 —
   Stonecrop Technologies" (slightly transparent), line 2 the role/dates; and the whole
   block drops below the "s cotton jr." brand so they don't crowd. Desktop = single line. */
.vid-prefix { display: none; }
@media (max-width: 760px) {
  #stonecrop .venture__id { top: 22vh !important; }   /* v272: sit just above the sharp video band */
  #stonecrop .vid-prefix { display: inline; }
  #stonecrop .vid-title { display: block; opacity: 0.5; }
  #stonecrop .vid-sep { display: none; }
  #stonecrop .vid-role { display: block; }
}


/* v273 — stonecrop: sharp video nudged up, identity tag moved BELOW it (Stephen). */
@media (max-width: 760px) {
  #stonecrop .panel__video { object-position: center 30% !important; }
  #stonecrop .venture__id { top: 18vh !important; }   /* v276: back above the sharp video band */
}


/* v275 — community-square (democraci): SAME as stonecrop (Stephen) — two-line identity
   tag ("Start-up 2 — democraci matters") below the sharp video, video nudged up. */
@media (max-width: 760px) {
  #community-square .panel__video { object-position: center 30% !important; }
  #community-square .venture__id { top: 18vh !important; }
  #community-square .vid-prefix { display: inline; }
  #community-square .vid-title { display: block; opacity: 0.5; }
  #community-square .vid-sep { display: none; }
  #community-square .vid-role { display: block; }
}


/* Orealis Labs: SAME two-line identity strip as stonecrop / democraci on mobile (Stephen).
   As one line it ran the full width and collided with the "s cotton jr." brand in the top-right
   corner. Splitting at the first separator drops the role/dates to their own line, and the
   max-width keeps even the longer line clear of the brand.
   (The .vid-* spans were added to this section's markup for exactly this — desktop still renders
   one line, because these rules are mobile-only.) */
@media (max-width: 760px) {
  #community-square-labs .vid-title { display: block; opacity: 0.5; }
  #community-square-labs .vid-sep { display: none; }
  #community-square-labs .vid-role { display: block; }
  #community-square-labs .venture__id { max-width: 62%; }   /* base is 90% — too wide beside the brand */
}


/* v277 — About: founder label + Heidegger pull-quote styling. */
#work-1 .coord__founder { margin-top: 1.4rem; padding-left: 1.1rem; font-family: var(--font-ui); font-size: 0.9rem; line-height: 1.6; color: var(--ink-soft); }
#work-1 .coord__founder b { font-family: var(--font-display); font-weight: 500; font-size: 1.05rem; color: var(--ink); }
#work-1 .coord__quote { margin-top: 1.8rem; font-style: italic; opacity: 0.9; line-height: 1.5; font-size: 0.85rem; }
#work-1 .coord__cite { display: block; margin-top: 0.5rem; font-style: normal; opacity: 0.6; }


/* v279 — About: the through-line "loop" thesis + Today section header. */
#work-1 .coord__loop {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 0.3rem 0.5rem; margin: 1.4rem 0 0.2rem;
  font-family: var(--font-ui); font-size: 0.8rem; line-height: 1.5; color: var(--ink);
}
#work-1 .coord__loop .loop-node { font-weight: 500; }
#work-1 .coord__loop .loop-arrow { color: var(--ink-soft); opacity: 0.6; font-size: 0.95em; }
#work-1 .coord__todayhead,
#work-1 .coord__subhead { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid var(--line); }
#work-1 .coord__todayhead b,
#work-1 .coord__subhead b { font-family: var(--font-display); font-size: 1.2rem; font-weight: 500; color: var(--ink); }

/* v401 — callout quote in the Today section */
#work-1 .coord__callout {
  margin: 1.2rem 0;
  padding-left: 1.1rem;
  border-left: 2px solid rgba(245, 243, 239, 0.4);
  font-weight: 600;
  color: var(--ink);
}


/* v288 — Stonecrop desktop STAGGER (Stephen's wireframe): a centered two-column cascade.
   Odd panes (Overview, Supply Chain) hold the LEFT column; even panes (Capstan, National
   Security) hold the RIGHT column. Cards are narrowed so the two columns clear each other,
   then each card interleaves UP into the previous one's lower portion — alternating columns
   never collide because they are horizontally separated. The Capstan product image fills the
   otherwise-empty LEFT slot beside its right-column card and overlaps the card's left edge. */
@media (min-width: 1150px) {
  /* centered, capped cluster so the two columns stay cohesive at any width */
  #stonecrop .stone-panes, #community-square .stone-panes {
    max-width: 76rem !important;
    margin-left: auto !important; margin-right: auto !important;
    padding-left: 0 !important; padding-right: 0 !important;
  }
  /* card width -> room for two columns + a gutter; wider = shorter cards (v289) */
  #stonecrop .stone-pane .stone-card,
  #community-square .stone-pane .stone-card { width: 35rem !important; max-width: none !important; }
  #stonecrop .stone-pane:nth-child(odd)  .stone-card,
  #community-square .stone-pane:nth-child(odd)  .stone-card { margin-left: 0 !important; }
  #stonecrop .stone-pane:nth-child(even) .stone-card,
  #community-square .stone-pane:nth-child(even) .stone-card { margin-left: calc(100% - 35rem) !important; }
  /* v312 (Stephen): the BOTTOM of each card aligns with the TOP of the next — no interleave,
     no gap. Each card sits directly below the previous, alternating left/right columns, so
     they never overlap vertically. (Was a -9vh interleave that pulled them into each other.) */
  #stonecrop .stone-pane, #community-square .stone-pane { margin-bottom: 0 !important; }
  #stonecrop .stone-pane:nth-child(n+2),
  #community-square .stone-pane:nth-child(n+2) { margin-top: 0 !important; }
  /* Image panes dock the visual absolutely in the empty column slot, floored so it's fully
     contained. Capstan (Stonecrop) + Community Square's logo pane are RIGHT-column cards, so
     the image fills the LEFT slot. */
  #stonecrop .stone-pane--capstan,
  #community-square .stone-pane--capstan { min-height: calc(25vh + 23.5rem) !important; }
  #stonecrop .stone-pane--capstan .stone__products,
  #community-square .stone-pane--capstan .stone__products {
    position: absolute !important;
    top: 12vh !important;
    left: 0 !important; right: auto !important; bottom: auto !important;
    width: 43rem !important; max-width: none !important;
    margin: 0 !important; z-index: 1 !important;
    filter: drop-shadow(0 12px 30px rgba(0, 0, 0, 0.5)) !important;
  }
  /* Community Square's Commercial-Efficiency pane — its UX shot fills the slot OPPOSITE its card.
     ⚠ v393: this flipped from the right slot to the LEFT. The card's column comes from
       .stone-pane:nth-child(odd/even), so inserting the Feedback Loops pane ahead of it moved it
       from child 3 (odd → left column) to child 4 (even → RIGHT column) — and the image, still
       pinned right, landed on top of the card. Any future pane inserted before this one flips it
       back again: check the card's column before trusting the side here. */
  #community-square .stone-pane--csux { display: block !important; min-height: calc(25vh + 20rem) !important; }
  #community-square .stone-pane--csux .cs-shot {
    position: absolute !important;
    top: 12vh !important;
    left: 0 !important; right: auto !important; bottom: auto !important;
    width: 34rem !important; max-width: none !important;
    margin: 0 !important; z-index: 1 !important;
  }
  /* Stonecrop's Loops diagram — RIGHT slot, same as the UX shot above. */
  #stonecrop .stone-pane--loops .loop-shot {
    position: absolute !important;
    top: 12vh !important;
    right: 0 !important; left: auto !important; bottom: auto !important;
    width: 34rem !important; max-width: none !important;
    margin: 0 !important; z-index: 1 !important;
  }
}

/* v313/314 (Stephen): on WIDE browsers, let the venture graphics (Stonecrop's Capstan + Community
   Square's logo & UX shot) BLEED OUT past the feed into the side margins — bigger the wider the
   window, capped at ~25% of the feed (`--cs-bleed`). Cards get a touch more width, and each image
   gets extra padding on its side of the card so the copy isn't crowded (it still overlaps the
   card's edge, just with breathing room). Stonecrop + Community Square kept consistent. */
@media (min-width: 1150px) {
  #stonecrop .stone-panes, #community-square .stone-panes {
    /* how far a graphic may reach past the feed edge: grows with the window, always leaving
       ~2.5rem of viewport margin, capped at 10rem (~25% of the 76rem feed, split per side). */
    --cs-bleed: clamp(0rem, calc((100vw - 76rem) / 2 - 2.5rem), 10rem);
  }
  /* slightly wider copy cards */
  #stonecrop .stone-pane .stone-card,
  #community-square .stone-pane .stone-card { width: 37rem !important; }
  #stonecrop .stone-pane:nth-child(even) .stone-card,
  #community-square .stone-pane:nth-child(even) .stone-card { margin-left: calc(100% - 37rem) !important; }
  /* LEFT-slot image (Capstan / logo) — bleeds left; +padding so the copy clears it */
  #stonecrop .stone-pane--capstan .stone__products,
  #community-square .stone-pane--capstan .stone__products {
    left: calc(-1 * var(--cs-bleed)) !important; right: auto !important;
    width: calc(40.5rem + var(--cs-bleed)) !important;
  }
  #stonecrop .stone-pane--capstan .stone-card,
  #community-square .stone-pane--capstan .stone-card { padding-left: 3.4rem !important; }
  /* floor the Capstan pane's height so the now-bigger left image stays contained above the next
     card (its card is short; grows with the bleed — image aspect ≈ 0.55 h/w). */
  #stonecrop .stone-pane--capstan,
  #community-square .stone-pane--capstan {
    min-height: calc(14vh + (40.5rem + var(--cs-bleed)) * 0.55 + 2rem) !important;
  }
  /* Commercial-Efficiency's UX shot fills the LEFT slot + bleeds left; +padding so the copy clears
     the overlap (see the flip note in the block above). */
  #community-square .stone-pane--csux .cs-shot {
    left: calc(-1 * var(--cs-bleed)) !important; right: auto !important;
    width: calc(40.5rem + var(--cs-bleed)) !important;
  }
  #community-square .stone-pane--csux .stone-card { padding-left: 3.4rem !important; }
  /* Loops diagram bleeds right and OVERLAPS the card's right edge: at 76rem feed / 37rem card,
     a 40.5rem image starting at right:0 has its left edge at 35.5rem — 1.5rem over the card.
     The card's padding-right keeps the copy off that strip, and z-index 1 tucks the image
     UNDER the glass, which is what makes it read as an overlap rather than a collision. */
  #stonecrop .stone-pane--loops .loop-shot {
    right: calc(-1 * var(--cs-bleed)) !important; left: auto !important;
    width: calc(40.5rem + var(--cs-bleed)) !important;
  }
  #stonecrop .stone-pane--loops .stone-card { padding-right: 3.4rem !important; }
}


/* ============================================================
   v394 — VENTURE CARDS (About → "What I've built"). Three glass cards in a row, each linking to
   that venture's section. Same glass vocabulary as .stone-card / .bio-view: a dark translucent
   fill, a hairline stroke, a backdrop blur, and a drop shadow.
   ⚠ auto-fit + minmax, NOT `repeat(3, 1fr)`. The About column is only ~40rem wide on desktop and
     goes full-bleed on phones, so a hard 3-up would crush the cards to ~120px there. auto-fit
     keeps three across while they fit and folds to 2 then 1 on its own — the "balanced margin"
     falls out of the equal gap rather than being hand-tuned per breakpoint.
   ⚠ backdrop-filter here is SAFE: these cards live inside .bio-scroll, which already scrolls as
     a composited layer, so no NEW promoted plane reaches the video bands. Do not lift this
     pattern onto anything positioned over a panel video without re-reading the band rules. */
#work-1 .venture-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: 0.85rem;
  margin: 1.5rem 0 1.9rem;
  max-width: 34rem;             /* matches .venture__lead, so the row sits on the text's measure */
}
#work-1 .vcard {
  display: flex;
  flex-direction: column;
  padding: 0.55rem 0.55rem 0.75rem;
  border-radius: 14px;
  text-decoration: none;
  color: inherit;
  background: rgba(14, 14, 18, 0.42);
  border: 1px solid rgba(255, 255, 255, 0.13);
  -webkit-backdrop-filter: blur(14px) saturate(1.15);
  backdrop-filter: blur(14px) saturate(1.15);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.38);
  transition: transform 0.28s ease, border-color 0.28s ease, box-shadow 0.28s ease,
              background-color 0.28s ease;
}
#work-1 .vcard__shot {
  display: block;
  border-radius: 9px;
  overflow: hidden;             /* clips the image to the card's inner radius */
  line-height: 0;
  background: rgba(255, 255, 255, 0.04);
}
#work-1 .vcard__shot img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;            /* uniform card height even if art ratios differ later */
}
#work-1 .vcard__name {
  display: block;
  margin: 0.6rem 0 0.2rem;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 0.98rem;
  line-height: 1.2;
  color: var(--ink);
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.5);
}
#work-1 .vcard__blurb {
  display: block;
  font-size: 0.8rem;
  line-height: 1.42;
  color: var(--ink-soft);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.45);
}
#work-1 .vcard__blurb-hl { color: var(--ink); }
/* Lift on hover — pointer devices only, so a phone tap doesn't leave a card stuck in hover. */
@media (hover: hover) {
  #work-1 .vcard:hover {
    transform: translateY(-3px);
    background: rgba(18, 18, 24, 0.52);
    border-color: rgba(255, 255, 255, 0.26);
    box-shadow: 0 16px 38px rgba(0, 0, 0, 0.5);
  }
}
#work-1 .vcard:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.65);
  outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
  #work-1 .vcard { transition: none; }
  #work-1 .vcard:hover { transform: none; }
}
/* PHONES — the cards STAY VERTICAL (art on top, text beneath) and simply stack down the page.
   Only the GRID folds to one column; the card's own axis never changes.
   ⚠ HISTORY, so this doesn't get "fixed" back: these used to flip HORIZONTAL here (art left, text
     right) to save height — About's mobile copy carries a 32vw right gutter for the murmuration
     mark, leaving only ~250-275px of usable column, where three vertical cards measured ~274px
     EACH (~800px of extra scrolling inside an already long read). Stephen's call (2026-07-27) is
     that the height is worth it: the sideways cards crowded the name into the blurb and shrank
     the art to a thumbnail. Taller cards, full-width art, no internal row.
   ⚠ Still deliberately NOT widened past the 32vw gutter: the row would then pass under the
     .about-flock mark as it scrolls, and the mark sits above this content (z-index 4). */
@media (max-width: 760px) {
  #work-1 .venture-cards {
    grid-template-columns: 1fr;   /* fold to a single column — the CARDS keep their own column axis */
    max-width: none;
    gap: 0.75rem;
    /* ESCAPE THE MARK'S GUTTER (Stephen, 2026-07-27: "since the cards are vertical you can make
       them get close to the width of the screen"). .bio-scroll reserves 32vw on the right for the
       murmuration mark; the prose still needs that, but the cards don't — so ONLY this row pulls
       back out to sit 4.5vw from the edge, mirroring the copy's left inset. 32vw − 4.5vw = 27.5vw.
       ⚠ Safe from clipping: overflow clips at the PADDING BOX, and .bio-view/.bio-scroll are the
         full screen width on phones, so the row is reaching into reserved padding, not past the box.
       ⚠ Accepted trade: the .about-flock mark (z-index 4) now passes OVER a card's lower-right
         corner as it scrolls by. That is the cost of the width and it was asked for knowingly. */
    margin-right: -27.5vw;
  }
  #work-1 .vcard {
    /* no flex-direction override — inherits `column` from the base rule */
    padding: 0.55rem 0.55rem 0.8rem;
  }
  #work-1 .vcard__shot { width: 100%; }   /* art spans the card instead of a fixed side column */
  #work-1 .vcard__name { margin: 0.6rem 0 0.2rem; font-size: 1rem; }
  #work-1 .vcard__blurb {
    font-size: 0.82rem;
    line-height: 1.45;
    /* No line-clamp: stacked cards are never side by side, so they don't need matching
       heights — and the blurb has the full card width to breathe now. */
  }
}

/* ============================================================
   v396 — LET THE VENTURE CARDS RUN WIDER THAN THE READING COLUMN (Stephen: "is it possible that
   these cards can be wider than the feed's container? I'm ok if they extend beyond the blur
   container / text container").
   ⚠ THE CARDS DO NOT OVERFLOW — the COLUMN gets wider and the TEXT is held back. Overflowing was
     tried first and is a dead end: three separate things clip this column, and every one of them
     is load-bearing. .bio-view has overflow:clip AND a vertical reading-edge mask; .bio-scroll
     has its own overflow. Worse, `overflow-x: visible` + `overflow-y: hidden` computes the
     visible back to `auto` per spec, so it still clips — only the visible/clip pair survives, and
     even then the mask cropped the overhang (measured: layout extended 297px, painted nothing).
     Widening the box sidesteps all of it.
   ⚠ Safe for the blur: the About gradient blur lives on .panel__inner::before/::after (inset:0,
     its own left→right mask), NOT on .bio-view — so a wider column does not widen the blur.
   The reading measure is pinned to 34.75rem, exactly what it was at the old 40rem column width,
   so not a line of prose reflows. Only .venture-cards opts out. */
@media (min-width: 1024px) {
  body.about-blur #work-1 .bio-view,
  body.about-blur #work-1.is-visible .bio-view {
    /* MIRRORED MARGIN (Stephen): the gutter on the right is exactly the gutter the text has on
       the left, so the card row spans the whole stage. `left` keeps its existing adaptive value;
       `right` simply matches it, and width goes auto so the two ends define the box. */
    right: max(3rem, calc((100vw - 84rem) / 2));
    width: auto;
    /* ⚠ .coord (the shared class on this element) sets max-width:45rem = 720px, which silently
       capped the widened column — release it or the box above does nothing. */
    max-width: none;
  }
  /* ⚠ POINTER-EVENTS, or the video loses its click target. The column is now nearly the full
     width of the stage, and it is an invisible box sitting on top of the video — left alone it
     would swallow every click-to-pause across the whole panel. So the BOX goes transparent to the
     pointer and only its real content is re-armed: the prose blocks are narrow (34.75rem), so the
     empty region to their right stays pass-through, exactly as it was before the widening. The
     card row is wide, but that is the point — it is meant to be clicked. */
  body.about-blur #work-1 .bio-view { pointer-events: none; }
  body.about-blur #work-1 .bio-scroll > * { pointer-events: auto; }

  /* hold every prose block at the original measure … */
  #work-1 .bio-scroll > * { max-width: 34.75rem; }
  /* … and let ONLY the card row use the new width */
  #work-1 .venture-cards { max-width: none; }

  /* Bigger and clearer at this size — the art carries the card, so the type scales with it. */
  #work-1 .venture-cards { gap: 1.15rem; margin: 1.9rem 0 2.2rem; }
  #work-1 .vcard { padding: 0.75rem 0.75rem 1rem; border-radius: 18px; }
  #work-1 .vcard__shot { border-radius: 12px; }
  #work-1 .vcard__name { margin: 0.8rem 0 0.3rem; font-size: 1.2rem; }
  #work-1 .vcard__blurb { font-size: 0.92rem; line-height: 1.5; }
}

/* v391 — Stonecrop's Feedback-Loops diagram. Mirrors .cs-shot: in normal flow BELOW the copy at
   narrow widths, and docked into the free column — overlapping the card's edge — only once the
   1150px two-column stagger kicks in (see the two min-width:1150 blocks below).
   ⚠ Modelled on .cs-shot, NOT .stone__products. The latter is position:absolute even at base,
     which between 760 and 1150px leaves the graphic lying across the card (measured: 222px of
     overlap at 1100px when I first copied that pattern). In-flow is correct until there is
     actually a second column to dock into.
   ⚠ The Loops card is .stone-pane:nth-child(3) — ODD — so it is a LEFT-column card and its free
     slot is the RIGHT one (the mirror of Capstan, the same side as Commercial Efficiency).
   ⚠ Scoped to #stonecrop: democraci matters has a .stone-pane--loops too, with no diagram. */
#stonecrop .stone-pane--loops .loop-shot {
  display: block;
  width: 100%;
  max-width: min(40rem, 100%);
  margin: 1.4rem 0 0;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5);
}

/* v283 — Capstan pane on MOBILE (Stephen): product visual ABOVE the text, so the user
   sees the image before the software copy. Flex order only — DOM/desktop unchanged. */
@media (max-width: 760px) {
  #stonecrop .stone-pane--capstan,
  #community-square .stone-pane--capstan { display: flex; flex-direction: column; }
  #stonecrop .stone-pane--capstan .stone__products,
  #community-square .stone-pane--capstan .stone__products { order: -1; margin: 0 0 1.4rem; }
}


/* v284 — "Turn-key Services:" label introducing the Supply Chain services list. */
/* ============================================================
   FEEDBACK LOOPS panes (v388/v390) — Stonecrop AND democraci matters share this styling. Denser than the other cards: grouped blocks, each
   a loop NAME → the sensing/correcting chain → the failure it corrects. Styled as a hierarchy
   rather than a list so the eye can skim group → loop → correction.
   ⚠ Uses h4/h5, which carry big browser defaults — every margin/size below is explicit for that
     reason, not decoration. Same over-video text-shadows as .stone-h/.stone-body, since these
     cards sit on moving footage.
   ============================================================ */
#stonecrop .loop-group,
#community-square .loop-group {
  margin: 0.95rem 0 0.55rem;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 0.78rem;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  line-height: 1.2;
  color: var(--ink);
  opacity: 0.74;
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.55);
}
#stonecrop .loop-group::after,
#community-square .loop-group::after {          /* short rule — separates groups without a full divider */
  content: "";
  display: block;
  margin-top: 0.34rem;
  width: 2.1rem;
  height: 1px;
  background: rgba(255, 255, 255, 0.3);
}
#stonecrop .loop,
#community-square .loop,
#community-square-labs .loop { margin: 0 0 0.72rem; max-width: 34rem; }   /* 33rem matches .venture__did */
#stonecrop .loop:last-child,
#community-square .loop:last-child,
#community-square-labs .loop:last-child { margin-bottom: 0; }
#stonecrop .loop-name,
#community-square .loop-name,
#community-square-labs .loop-name {
  margin: 0 0 0.22rem;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(0.98rem, 1.3vw, 1.11rem);
  line-height: 1.2;
  letter-spacing: -0.005em;
  color: var(--ink);
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.6);
}
#stonecrop .loop-name .loop-alt,
#community-square .loop-name .loop-alt,
#community-square-labs .loop-name .loop-alt { opacity: 0.66; font-style: italic; }
#stonecrop .loop-flow,
#community-square .loop-flow,
#community-square-labs .loop-flow {
  margin: 0 0 0.24rem;
  font-size: clamp(0.86rem, 1.1vw, 0.95rem);
  line-height: 1.44;
  color: var(--ink-soft);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
}
#stonecrop .loop-fix,
#community-square .loop-fix,
#community-square-labs .loop-fix {
  margin: 0;
  font-size: clamp(0.83rem, 1.05vw, 0.92rem);
  line-height: 1.45;
  color: var(--ink-soft);
  opacity: 0.8;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
}
#stonecrop .loop-fix b,
#community-square .loop-fix b,
#community-square-labs .loop-fix b {
  font-weight: 500;
  color: var(--ink);
  opacity: 0.92;
}
/* Orealis Labs loops ride in a glass panel, not over video — drop the shadow */
#community-square-labs .loop {
  margin-bottom: 1.2rem;
}
#community-square-labs .loop-name,
#community-square-labs .loop-flow,
#community-square-labs .loop-fix {
  text-shadow: none;
}

#stonecrop .stone-services-label {
  display: block;
  margin: 0.2rem 0 0.55rem;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1rem;
  color: var(--ink);
}


/* v287 — bright-white highlight for the emphasized bold phrase in the About reading card.
   Body copy is a warm gray (rgb(185,182,176)); this lifts the phrase to pure white so it
   reads as emphasis, not just bold. Color-only (no opacity clamp on the ancestors). */
#work-1 b.hl { color: #fff; }


/* ============================================================================
   v353 — MOBILE stonecrop / democraci: ONE CONTINUOUS GRADIENT-BLUR instead of GLASS CARDS.
   ★ TOGGLE — NOTHING here deletes the glass: the original glassmorphic card rules (~line
     1227) are untouched, and this only ADDS a `body.card-blur` mode, so switching back is
     lossless. Flip `CARD_BLUR_DEFAULT` in script.js, or use ?cardblur=0 / ?cardblur=1.
   The copy stops living in floating cards and instead rides a single CONTINUOUS blur that
   runs the whole length of the content — top of the first card to the bottom of the last —
   STRONG at FULL strength edge to edge (v356; was 80%, then 66%, then mid-screen — the horizontal taper is now a no-op solid mask, so only the vertical end-feathers shape it). The stop is kept as a gradient across
   a single stop so dialing a taper back in is a one-number change (the About mobile treatment;
   the menu's ramp reversed).
   ⚠ The planes live on .stone-panes (the CONTAINER of every card), NOT on .stone-card —
     per-card planes gave discrete blurred bands with gaps between them. Container-level =
     continuous.
   ⚠ The blur must be a ::before, never a backdrop-filter on the container itself, or it
     becomes a backdrop root and the child blur samples an empty layer (.site-toc/.bio-view trap).
   TWO planes on purpose: the BLUR tapers to nothing at the right (the ask), but the copy
   runs nearly full-width, so a SEPARATE, gentler darken persists under the text and only
   fades at the very edge — one shared mask tapered both away and left the right-hand words
   sitting on bright, sharp footage.
   Vertical feathers land in .stone-panes' own padding (16vh top / 4vh bottom), so the blur
   eases in before the first card and out after the last.
   Knobs: the horizontal stop (currently 100% = flat), the darken alpha (flat), blur(20px), the darken ramp, the 14vh/3vh end feathers.
   ============================================================================ */
@media (max-width: 760px) {
  body.card-blur #stonecrop .stone-card,
  body.card-blur #community-square .stone-card {
    background: transparent !important;
    -webkit-backdrop-filter: none !important;
    backdrop-filter: none !important;
    border: none !important;
    border-radius: 0 !important;
    box-shadow: none !important;
  }
  /* CONTINUOUS blur plane — spans the entire cards container, full-bleed */
  body.card-blur #stonecrop .stone-panes::before,
  body.card-blur #community-square .stone-panes::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    -webkit-backdrop-filter: blur(20px) saturate(1.15);
    backdrop-filter: blur(20px) saturate(1.15);
    -webkit-mask-image:
      linear-gradient(to right, #000 0%, #000 100%),
      linear-gradient(to bottom, transparent 0, #000 14vh, #000 calc(100% - 3vh), transparent 100%);
    mask-image:
      linear-gradient(to right, #000 0%, #000 100%),
      linear-gradient(to bottom, transparent 0, #000 14vh, #000 calc(100% - 3vh), transparent 100%);
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
  }
  /* CONTINUOUS darken plane — legibility for the copy that runs past the blur's taper */
  body.card-blur #stonecrop .stone-panes::after,
  body.card-blur #community-square .stone-panes::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    /* v357: FLAT across (was a L→R ramp 0.58→0). Now that the blur is full-width, the darken
       matches it — one uniform value edge to edge, so legibility is even wherever the copy
       runs. Single knob: this one alpha. */
    background: rgba(8, 8, 11, 0.58);
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 14vh, #000 calc(100% - 3vh), transparent 100%);
    mask-image: linear-gradient(to bottom, transparent 0, #000 14vh, #000 calc(100% - 3vh), transparent 100%);
  }
  /* every card rides above both planes */
  body.card-blur #stonecrop .stone-pane,
  body.card-blur #community-square .stone-pane { z-index: 1; }
}


/* ============================================================================
   v360 — COMMUNITY SQUARE LABS: a SCROLLABLE READING PANEL (Stephen's call).
   The Labs copy grew into an essay, far too long for the old compact
   `.venture__foot` (lead + chips + one lesson line), so it now reads like
   About's bio: a fixed reading window with the text scrolling INSIDE it.
   ⚠ Deliberately uses its OWN class names (.labs-view / .labs-scroll) instead
     of reusing About's .bio-view / .bio-scroll. Those carry band-tuned iOS
     geometry (#work-1 heights, 16vh/80vh insets, the gradient-blur planes);
     sharing selectors would couple two very different sections and risk the
     footer band. Self-contained = About stays untouched.
   The section is `app-h * 2 + work-pad` tall with a sticky video, so the panel
   is STICKY: it holds in view while the section rides past, and the essay
   scrolls within it. ============================================================ */
#community-square-labs .labs-view {
  position: sticky; top: 0;
  /* ⚠ height is the VISIBLE viewport (svh), NOT --app-h. --app-h is 100lvh, which is LARGER
     than the visible area whenever browser chrome shows (iPhone: 836 lvh vs ~796 visible;
     measured 850 vs 600 in the preview). Sizing this panel to app-h pushed its bottom below
     the fold, which stranded the bottom-anchored murmuration mark down in the pill zone and
     off-centred the reading panel. svh = what the reader can actually see. */
  height: var(--app-h);                /* fallback for browsers without svh */
  height: 100svh;
  /* ⚠ the sticky .panel__bg still CONSUMES a full app-h of normal flow, so without this
     pull-up .labs-view starts one screen down and doesn't pin until you've scrolled past
     it (measured: flow top 416 in a 600 viewport). Pulling it up by one viewport puts its
     flow position back at the section top, so it pins the moment the section arrives. */
  margin-top: calc(var(--app-h) * -1);
  z-index: 2;
  display: flex; align-items: center;
  padding: 0 clamp(1.5rem, 5vw, 4rem);
  pointer-events: none;                 /* only the scroller takes input */
}
#community-square-labs .labs-scroll {
  pointer-events: auto;
  width: min(42rem, 54vw);
  max-height: 76vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding: clamp(1.5rem, 3.2vh, 2.6rem) clamp(1.4rem, 2.4vw, 2.2rem);
  border-radius: 26px;
  background: rgba(14, 14, 18, 0.42);
  -webkit-backdrop-filter: blur(18px) saturate(1.12);
  backdrop-filter: blur(18px) saturate(1.12);
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.34);
}
#community-square-labs .labs-scroll::-webkit-scrollbar { display: none; }

/* ---- type ---- */
#community-square-labs .labs-lead {
  font-family: var(--font-display, var(--font-ui));
  font-size: clamp(1.15rem, 2.1vw, 1.5rem);
  line-height: 1.34; color: #fff; margin: 0 0 1.6rem;
}
#community-square-labs .labs-h {
  font-family: var(--font-display, var(--font-ui));
  font-size: clamp(1rem, 1.7vw, 1.22rem);
  font-weight: 600; color: #fff;
  margin: 2rem 0 0.6rem; letter-spacing: 0.005em;
}
#community-square-labs .labs-h:first-of-type { margin-top: 1.2rem; }
#community-square-labs .labs-scroll p {
  font-size: clamp(0.92rem, 1.35vw, 1rem);
  line-height: 1.62; margin: 0 0 0.95rem;
  color: rgb(185, 182, 176);
}
/* the pulled one-liners that land a section */
#community-square-labs .labs-turn {
  color: #fff; font-weight: 500;
  font-size: clamp(0.98rem, 1.5vw, 1.1rem) !important;
  margin: 1.1rem 0 1.1rem !important;
}
/* the honesty caveat — set back so it reads as an aside, not a claim */
#community-square-labs .labs-note {
  border-left: 2px solid rgba(255, 255, 255, 0.22);
  padding-left: 0.9rem; margin-top: 1.3rem !important;
  color: rgba(185, 182, 176, 0.82);
  font-size: clamp(0.86rem, 1.2vw, 0.94rem) !important;
}
/* diagnosis → principle → build */
#community-square-labs .labs-pairs { list-style: none; margin: 0.9rem 0 0; padding: 0; }
#community-square-labs .labs-pairs li {
  position: relative; padding: 0.62rem 0 0.62rem 0.9rem;
  border-left: 2px solid rgba(255, 255, 255, 0.16);
  margin-bottom: 0.55rem;
  font-size: clamp(0.88rem, 1.28vw, 0.96rem);
  line-height: 1.5; color: rgb(185, 182, 176);
}
#community-square-labs .labs-pairs li b { color: #fff; }
#community-square-labs .labs-pairs li i { font-style: italic; color: rgba(255, 255, 255, 0.9); }
#community-square-labs .labs-scroll > :last-child { margin-bottom: 0; }

/* ---- MOBILE: drop the glass card for the same left→right gradient blur the rest
   of the mobile site now uses — full-bleed, strong from the left, the copy sitting
   on blurred video rather than in a floating panel. ---- */
@media (max-width: 760px) {
  #community-square-labs .labs-view { padding: 0; align-items: stretch; }
  #community-square-labs .labs-scroll {
    width: 100%; max-height: none; height: 100%;
    padding: 16vh 6vw 12vh 5.5vw;
    border-radius: 0; border: none; box-shadow: none;
    background: rgba(8, 8, 11, 0.58);          /* flat darken, matching the venture cards */
    -webkit-backdrop-filter: blur(20px) saturate(1.15);
    backdrop-filter: blur(20px) saturate(1.15);
    /* fade the reading edges so copy dissolves at the top/bottom instead of cutting */
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 3rem, #000 calc(100% - 3.5rem), transparent 100%);
    mask-image: linear-gradient(to bottom, transparent 0, #000 3rem, #000 calc(100% - 3.5rem), transparent 100%);
  }
}


/* v362 — LABS' ANIMATED FOOTER MARK. Stonecrop and democraci each close on a static PNG
   mark (.stone-footer / .cs-footer); this is Labs' equivalent, but alive — a canvas
   murmuration (flock.js). Placed on exactly the same geometry as those two so the three
   ventures share a footer rhythm: centred on the panel's lower edge, and fixed to the
   video's bottom while the section is pinned. Compact by design (Stephen: "compact mark").
   Sits clear of the reading panel: .labs-scroll is centred at max 76vh, this rides the
   bottom ~2–10vh. Knobs: the width/height clamps and the 0.85 opacity. */
/* v376 — BOTTOM-RIGHT CORNER (Stephen: "move the murmuration logo to the bottom right corner
   for about / community lab / poetry, since their text tends to require more space vertically").
   Getting the mark out of the vertical stack is the point: bottom-CENTRE forced the copy to end
   above it, so the corner hands that height back to the text. */
#community-square-labs .labs-flock {
  position: absolute;
  /* v387 — CENTRED on desktop (Stephen: "for desktop the murmuration logo should be in the
     center (footer) ... it won't compete for finite space"). The bottom-RIGHT corner from v376
     was a MOBILE compromise — on a phone the copy is full-bleed and the corner is the only free
     pocket. Desktop has the whole footer, so the mark centres and the right corner goes back to
     .controls alone. The mobile override further down re-anchors it right. */
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(env(safe-area-inset-bottom, 0px) + clamp(0.9rem, 2.6vh, 2rem));
  /* Now the Orealis brand mark (orealis-logo.css) instead of the flock canvas — a square
     ring+aurora icon, sized via --size (the .orealis-mark base rule reads it for width/height
     AND for the ring stroke + blur radius, so this is the only knob to grow/shrink it). */
  --size: clamp(28px, 3.5vw, 42px);
  z-index: 4;
  pointer-events: none;
  opacity: 0.85;
}

/* v376 — LABS mark on MOBILE. .labs-view is position:sticky; top:0; height:100svh, so the mark
   is already pinned to the visible bottom — no lvh/svh correction needed here (unlike About,
   whose section overhangs by 100px). What DOES need fixing is width: the essay is full-bleed on
   phones, so a corner mark lands on the lines (measured: ink reached x347 of 350). Shrink the
   mark and carve a right gutter out of the copy, same trade as About. */
@media (max-width: 760px) {
  #community-square-labs .labs-flock {
    left: auto;                        /* v387: undo the desktop centring */
    transform: none;
    right: var(--pad);   /* v383: same edge as .controls, so the mark sits under the button stack */
    --size: clamp(44px, 11vw, 60px);
  }
  /* .labs-view is position:sticky, top:0, height:100svh — so its own bottom edge is the SMALL
     viewport. (100svh - 100dvh) is 0 with the bar expanded and NEGATIVE as it collapses, which
     walks the mark down past labs-view's edge onto the real visible bottom. Same intent as the
     About override above, expressed against a different containing block. */
  @supports (height: 100dvh) {
    #community-square-labs .labs-flock {
      bottom: calc(100svh - 100dvh + env(safe-area-inset-bottom, 0px) + 0.5rem);
    }
  }
  /* ⚠ WAS 32vw — sized for the murmuration CANVAS, which was 100-120px wide. The Orealis mark
     that replaced it is a ~44-60px SQUARE, so a third of the screen was being reserved for
     something occupying about a seventh of it: the copy ran narrow and the full-bleed blurred
     backdrop read as if it were inset (Stephen: "make sure the background blur is full width on
     mobile"). The backdrop itself was always edge-to-edge — .labs-view is padding:0 here and
     .labs-scroll is width:100%, so the blur spans the viewport; it was the TEXT inside it that
     was pulled in. Reserve only what the mark actually occupies (mark ≈11vw + --pad). */
  #community-square-labs .labs-scroll { padding-right: 18vw !important; }
}
/* NOTE: no .id-pin → fixed rule here (unlike .stone-footer / .cs-footer). The canvas lives
   inside the STICKY .labs-view, which is itself a positioned containing block — so absolute
   already anchors it to the viewport's lower edge for as long as the panel is pinned. */


/* ============================================================================
   THE ORI APP LOOP (2026-08-02) — the phone-shaped clip that sits beside Labs' copy, over
   the section's background video. Two videos play here at once: the background owns the
   sound and the scroll behaviour, this one just loops silently forever.
   ⚠ DESKTOP ONLY. On phones the copy runs full-bleed and the only free pocket is the 32vw
     gutter already holding the brand mark — there is nowhere to put it. Hiding it there also
     keeps a SECOND decoding video plane off iOS, which is precisely the ingredient the whole
     glass-band saga was made of. Do not "restore" it on mobile without a real device ride.
   ============================================================================ */
/* Shared by both instances (the desktop dock + the phone inline copy).
   ⚠ THE CORNER RADIUS IS NOT DECORATIVE — it clips the artwork's own black corners.
     The phone mockup is drawn flush to the frame edges with rounded corners, so the video's
     square corners are pure black and read as four black wedges over the aurora. MEASURED off
     a decoded frame by tracing where the lit pixels begin per row and fitting the arc:
     radius ≈ 95px on a 720-wide frame = 13.2% OF THE WIDTH. Hence the calc against --ori-w
     rather than a fixed px (a fixed value only matches at one size) and rather than a
     percentage (percentage radii resolve per-axis, which on a 1:2.2 box gives ellipses).
     If the mockup art is ever re-cut, re-measure — do not guess. */
/* Both Ori clips take their own clicks (tap to pause, tap to play — script.js). They were
   pointer-events:none so clicks fell through to the panel's click-to-pause; now they are their
   own control surface. The `data-no-pause` attribute in the markup is what keeps a click here
   from ALSO pausing the section's background video — it is already in script.js's NO_PAUSE
   selector list, so no JS change was needed for that half. */
#community-square-labs .ori-phone,
#community-square-labs .ori-desktop {
  pointer-events: auto;
  cursor: pointer;
}

/* ---- THE CLIP SHELL ----
   Each clip is a <div> wrapping the <video> plus a scrim and a scrubber. A bare <video>
   cannot carry children, so the paused overlay and the seek bar need this container — which
   is why the positioning/ratio/radius/shadow classes now live on the DIV and the video simply
   fills it. overflow:hidden makes the container's radius clip the footage, which is what
   removes the artwork's black corners (see the radius note above). */
#community-square-labs .ori-clip {
  /* relative so the scrim + scrubber anchor to the clip. The DOCK variants override this with
     position:absolute further down (later in source, same specificity), which serves the same
     purpose — this line is what gives the in-flow MOBILE copies their positioning context. */
  position: relative;
  overflow: hidden;
  display: block;
  line-height: 0;             /* no inline-descender gap under the video */
}
#community-square-labs .ori-clip .ori-vid {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}
/* Paused state: a scrim darkens the footage so it reads as deliberately stopped rather than
   stalled, and the scrubber appears. Both are invisible and inert while playing. */
#community-square-labs .ori-scrim {
  position: absolute;
  inset: 0;
  background: rgba(4, 6, 12, 0.52);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.28s ease;
}
#community-square-labs .ori-clip.is-paused .ori-scrim { opacity: 1; }
#community-square-labs .ori-seek {
  position: absolute;
  left: 6%;
  right: 6%;
  bottom: 5%;
  width: auto;                /* override .seek-bar's width:100% — left/right define it here */
  z-index: 2;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.28s ease;
}
#community-square-labs .ori-clip.is-paused .ori-seek {
  opacity: 1;
  pointer-events: auto;       /* only scrubbable while paused */
}
#community-square-labs .ori-phone {
  aspect-ratio: 870 / 1920;   /* the source's true ratio — no letterbox, no crop */
  height: auto;
  object-fit: cover;
  border-radius: calc(var(--ori-w, 240px) * 0.165);
  /* Aurora-cyan glow (#22D3EE @ 50%) offset to the BOTTOM-RIGHT, not a black drop shadow —
     this clip sits on near-black footage, so black-on-black read as nothing (Stephen). The
     cyan is one of the mark's own aurora stops, so it belongs to the palette. Two layers:
     a wide glow that separates the device from the sky, and a tighter one for contact. */
  box-shadow: 18px 24px 64px rgba(34, 211, 238, 0.5), 6px 8px 22px rgba(34, 211, 238, 0.38);
}
@media (min-width: 761px) {
  #community-square-labs .ori-phone--inline { display: none; }   /* the inline copy is phones-only */
  #community-square-labs .ori-phone--dock {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    /* CENTRED IN THE RIGHT HALF (Stephen). Was anchored by `right:` to the stage inset
       (100vw − 84rem)/2 — which COLLAPSES TO ITS FLOOR on any viewport under 84rem (1344px),
       so on a typical laptop both clips pinned to the far right edge with ~48px of margin and
       the page read lopsided. left:50% + right:0 + auto inline margins centres the clip in the
       right half at every width instead, mirroring what the copy now does on the left. */
    left: 50%;
    right: 0;
    margin-inline: auto;
    --ori-w: clamp(190px, 19vw, 330px);
    width: var(--ori-w);
    z-index: 3;                 /* above the video + the blur pseudos (z −1), below the mark (z 4) */
    opacity: 0;
    /* HOLD, THEN FADE (Stephen: on arrival "the video isn't visible immediately"). .is-visible
       lands at 30% of the section, which is still mid-scroll-in, so an undelayed fade was over
       before the section had settled. The 1s delay lets the reader arrive on clear footage and
       the clip resolve after. ⚠ The delay is cancelled on the crossfade-out below — carrying it
       there would stall the hand-off to the desktop clip by a second. */
    transition: opacity 0.9s ease 1s;
  }
  /* Fades in with the section, like every other Labs element. */
  #community-square-labs.is-visible .ori-phone--dock { opacity: 1; }
}
/* ---- the DESKTOP app shot: the second half of the dock, and its phone-inline twin ----
   Shares the cyan glow with the phone clip so the two read as one family. Corners are
   rounded far less than the phone (Stephen) — a phone's radius is a device signature, a
   desktop window's is a small chamfer. Ratio is the CROPPED 1280/648: the source carried
   black letterbox bars, they are cropped out of the encode, and using 16/9 here would
   letterbox it right back. */
#community-square-labs .ori-desktop {
  aspect-ratio: 1280 / 648;
  height: auto;
  object-fit: cover;
  border-radius: clamp(6px, calc(var(--ori-dw, 600px) * 0.016), 14px);
  box-shadow: 18px 24px 64px rgba(34, 211, 238, 0.5), 6px 8px 22px rgba(34, 211, 238, 0.38);
}
@media (min-width: 761px) {
  #community-square-labs .ori-desktop--inline { display: none; }   /* inline copy is phones-only */
  /* THE CROSSFADE. Both clips occupy the same dock slot; script.js adds .ori-desk-on to the
     section at ~40% of the read and the pair swaps. Only opacity animates — no display/visibility
     toggling, which would kill the fade and restart playback. */
  #community-square-labs .ori-desktop--dock {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    /* CENTRED IN THE RIGHT HALF, same as the phone clip above — see the note there for why the
       old `right:`-anchored version pinned to the edge on sub-1344px screens.
       Width is the widest the half comfortably carries with a margin either side; the landscape
       ratio then sets the height (588 wide → ~298 tall at 1400px), which clears the copy.
       ⚠ Deliberately NOT full-bleed. The gradient blur that keeps the copy legible sits at
         z-index -1 on .labs-view, BELOW these clips (z 3) — so a clip spanning the full width
         would put raw video under the text with no blur between them. */
    left: 50%;
    right: 0;
    margin-inline: auto;
    --ori-dw: min(42vw, 780px);
    width: var(--ori-dw);
    z-index: 3;
    opacity: 0;
    transition: opacity 0.75s ease;
  }
  #community-square-labs.ori-desk-on .ori-desktop--dock { opacity: 1; }
  /* …and the phone steps back out as it does. */
  /* transition-delay:0 — the 1s arrival hold above must not apply to the hand-off. */
  #community-square-labs.ori-desk-on .ori-phone--dock { opacity: 0; transition-delay: 0s; }
}
@media (max-width: 760px) {
  #community-square-labs .ori-desktop--dock { display: none; }
  /* In flow, full column width — landscape needs the width more than the phone clip did. */
  #community-square-labs .ori-desktop--inline {
    display: block;
    /* width is 100% of the column; --ori-dw is only the radius' reference, so it needs a LENGTH
       (a % there would resolve per-axis and give elliptical corners). The mobile column is
       100vw − 5.5vw left pad − 18vw right gutter ≈ 76vw. */
    --ori-dw: 76vw;
    width: 100%;
    margin: 1.7rem 0 1.9rem;
  }
}

@media (max-width: 760px) {
  #community-square-labs .ori-phone--dock { display: none; }   /* no clear right-hand zone on a phone */
  /* IN FLOW between two paragraphs, centred at 60% of the screen (Stephen). Sits in the reading
     column, so it scrolls with the copy rather than being pinned — which is also the safer shape
     on iOS: in-flow video planes are the ones that have always behaved, pinned ones are not. */
  #community-square-labs .ori-phone--inline {
    display: block;
    --ori-w: 60vw;
    width: var(--ori-w);
    margin: 1.7rem auto 1.9rem;
  }
}


/* ============================================================================
   v366 — LABS DESKTOP: GRADIENT BLUR instead of the glass reading card (Stephen),
   mirroring About's desktop treatment: heavy blur anchored on the LEFT behind the
   copy, tapering to clear so the RIGHT side stays sharp video. Same mask stops as
   `body.about-blur` so the two long-form sections read as a matched pair.
   Desktop only — mobile Labs already has its own full-bleed blur (v360).
   ============================================================================ */
@media (min-width: 761px) {
  #community-square-labs .labs-view {
    /* CENTRED IN THE LEFT HALF (Stephen: "you could do the same with the text on the left").
       Was the adaptive stage inset borrowed from About — max(3rem, (100vw−84rem)/2) — which
       hugs the far-left edge on anything under 84rem and left the page reading unbalanced now
       that the clips are centred in the right half. Pad by half the slack instead, so the
       column sits centred in 0..50vw, with the 3rem floor as the narrow-screen fallback.
       ⚠ Only Labs does this. About keeps the stage inset — it has no right-hand companion to
         balance against, so centring its copy would just push it toward the middle for nothing. */
    padding: 0 0 0 max(3rem, calc((50vw - min(36rem, 42vw)) / 2));
  }
  /* Strip the glass — the gradient blur below IS the backdrop now. A soft top/bottom mask
     replaces the card frame so the scroll-clipped copy doesn't hard-cut at the edges. */
  #community-square-labs .labs-scroll {
    /* ⚠ MUST MATCH the min() in .labs-view's padding-left above — that padding is
       (50vw − thisWidth)/2, so if the two drift apart the copy stops being centred in its half.
       Narrowed from min(40rem, 46vw): at 46vw the column filled ~92% of the left half, leaving
       no slack to centre within. The essay reflows taller as a result, which the scroll driver
       re-measures on its own (it sizes the section from text.scrollHeight). */
    width: min(36rem, 42vw);
    max-height: 78vh;
    /* v367: CLIP, don't scroll. The inner scrollbar swallowed the wheel (2284px of essay in a
       624px box) so the page felt frozen. The copy is now driven by page scroll in script.js,
       exactly like About's bio — page scroll IS reading progress, and there's no trap. */
    overflow: hidden;
    padding: 0 1.6rem 0 0;
    background: transparent;
    -webkit-backdrop-filter: none; backdrop-filter: none;
    border: none; border-radius: 0; box-shadow: none;
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 5%, #000 93%, transparent 100%);
    mask-image: linear-gradient(to bottom, transparent 0, #000 5%, #000 93%, transparent 100%);
  }
  /* Twin-layer blur on the sticky view.
     ⚠ MUST be pseudos here, never backdrop-filter on .labs-view itself — that would make it
       a backdrop root and the child blur would sample an empty layer (the .site-toc /
       .bio-view trap that has bitten this codebase repeatedly).
     ⚠ z-index -1 puts them behind the copy AND behind the murmuration canvas (z-index 4),
       so the mark rides OVER the blur wherever the two overlap (Stephen's ask). */
  #community-square-labs .labs-view::before,
  #community-square-labs .labs-view::after {
    content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  }
  #community-square-labs .labs-view::before {   /* light base blur + left darken for legibility */
    background: linear-gradient(to right, rgba(10, 10, 13, 0.46) 0%, rgba(10, 10, 13, 0.18) 46%, transparent 70%);
    -webkit-backdrop-filter: blur(6px) saturate(1.05); backdrop-filter: blur(6px) saturate(1.05);
    -webkit-mask-image: linear-gradient(to right, #000 0%, rgba(0,0,0,0.5) 48%, transparent 72%);
    mask-image: linear-gradient(to right, #000 0%, rgba(0,0,0,0.5) 48%, transparent 72%);
  }
  #community-square-labs .labs-view::after {    /* heavier blur, carried farther before tapering */
    -webkit-backdrop-filter: blur(20px) saturate(1.12); backdrop-filter: blur(20px) saturate(1.12);
    -webkit-mask-image: linear-gradient(to right, #000 0%, rgba(0,0,0,0.5) 38%, transparent 62%);
    mask-image: linear-gradient(to right, #000 0%, rgba(0,0,0,0.5) 38%, transparent 62%);
  }
}


/* v369 — ABOUT's murmuration mark. The same live canvas as Labs' footer (flock.js drives
   every canvas.flock-mark), placed on matching geometry so the two marks read as one motif.
   Especially apt here: this section's background footage IS a starling murmuration, so the
   mark echoes the film behind it.
   It sits inside #work-1 .panel__inner, which is STICKY — so absolute positioning anchors it
   to the viewport while About is pinned, exactly like .labs-flock inside .labs-view.
   ⚠ z-index 4 keeps it ABOVE the gradient-blur pseudos (z-index -1), so it never gets
     blurred by its own section's backdrop. */
/* v376 — BOTTOM-RIGHT CORNER (Stephen: "move the murmuration logo to the bottom right corner
   for about / community lab / poetry, since their text tends to require more space vertically").
   Getting the mark out of the vertical stack is the point: bottom-CENTRE forced the copy to end
   above it, so the corner hands that height back to the text. */
#work-1 .about-flock {
  position: absolute;
  /* v387 — CENTRED on desktop (Stephen: "for desktop the murmuration logo should be in the
     center (footer) ... it won't compete for finite space"). The bottom-RIGHT corner from v376
     was a MOBILE compromise — on a phone the copy is full-bleed and the corner is the only free
     pocket. Desktop has the whole footer, so the mark centres and the right corner goes back to
     .controls alone. The mobile override further down re-anchors it right. */
  left: 50%;
  transform: translateX(-50%);
  /* ⚠ .panel__inner is var(--app-h) tall (= 100lvh), which EXCEEDS the visible viewport
     whenever browser chrome shows (iPhone: 836 vs ~796). Anchoring to its bottom would drop
     the mark below the fold and into the pill zone — the same trap that stranded the Labs
     mark. Adding the lvh−svh difference lifts it back to the visible bottom; on desktop the
     two are equal, so this reduces to the plain offset. */
  bottom: calc(var(--app-h) - 100svh + env(safe-area-inset-bottom, 0px) + clamp(0.9rem, 2.6vh, 2rem));
  /* Now the Orealis mark: a SQUARE icon sized by --size (the .orealis-mark base rule reads it
     for width/height AND for the ring stroke + blur radius, so it is the only knob here).
     Same value as the Labs mark so every instance reads as one brand at one weight. */
  --size: clamp(28px, 3.5vw, 42px);
  z-index: 4;
  pointer-events: none;
  opacity: 0.85;
}

/* v375 — ABOUT's footer mark on MOBILE: smaller, and lifted so it is actually ON SCREEN.
   ⚠ MUST STAY AFTER the base rule above — same specificity, so source order decides.
   ⚠ THE BUG THIS FIXES: the base rule corrects for lvh−svh, but on phones #work-1 is
     `height: calc(100lvh + 100px)` and .panel__inner is `inset: 0`, so the containing block is
     100px TALLER than --app-h and that correction under-compensates by exactly that 100px.
     MEASURED at 390x796: the mark sat at 776–875 inside an 896px section whose visible window
     is only 0–796 — just its top ~20px cleared the fold, the rest sat in the URL-pill zone.
     It has effectively never been visible on a phone.
   The section's overhang is (var(--app-h) + 100px), so subtracting 100svh from THAT lands the
   box on the true bottom edge; the final term is then a real gap above it. */
@media (max-width: 760px) {
  #work-1 .about-flock {
    left: auto;                        /* v387: undo the desktop centring */
    transform: none;
    right: var(--pad);   /* v383: same edge as .controls, so the mark sits under the button stack */
    bottom: calc(var(--app-h) + 100px - 100svh + env(safe-area-inset-bottom, 0px) + 2.5rem);
    /* ≈ the old box's HEIGHT (the flock box was 13/5, so ~38-45px tall), so every bottom
       offset measured above still lands where it was measured. */
    --size: clamp(34px, 9vw, 42px);
  }
/* v384 — TRACK THE PILL. Everything below re-anchors the mobile marks from svh to dvh.
   ⚠ svh is the SMALLEST viewport — the height with Safari's bar fully EXPANDED. Anchoring to it
     pins the mark to where the bottom edge would be in the worst case, so with the bar collapsed
     it floats ~116px too high and cannot follow the pill at all (Stephen: "both the controls and
     murmuration should move up or down with the pill as it does elsewhere"). dvh is the CURRENT
     visible height, so a dvh-anchored box rides the bar up and down by construction.
   ⚠ Kept as an @supports override rather than editing the base rule, so the svh version stays as
     the fallback on anything without dvh. Do not collapse the two.
   The offset then parks the mark just above the visible bottom — it is the BOTTOM element, and
   .controls lifts above it (see .controls.controls-mark). It deliberately does NOT sit between
   the buttons and the seek any more: that made its position depend on the seek being visible, so
   it floated whenever the scrubber was hidden. */
  @supports (height: 100dvh) {
    #work-1 .about-flock {
      bottom: calc(var(--app-h) + 100px - 100dvh + env(safe-area-inset-bottom, 0px) + 0.5rem);
    }
  }
}


/* v374 — HOME's murmuration mark as the hero's FOOTER, matching About + Labs (both of which
   sit bottom-centre at 260x100). It briefly went dead-centre in v372/373; Stephen clarified he
   wants it as the bottom footer, so it now takes the slot the deleted .scroll-cue vacated.

   ⚠ aspect-ratio, NOT a second clamp. flock.js mixes FRACTIONAL shape constants
     (CORE_RX/RY, BRAIN_RX/RY are fractions of W/H) with ABSOLUTE-PIXEL motion constants. If
     the box ratio drifts with viewport height — which two independent clamps guarantee — the
     brain silhouette stretches. Locking 13/5 (=2.6, the ratio it was tuned at) keeps every
     size a pure zoom of the shape Stephen approved. Don't reintroduce a height clamp.

   ⚠ WHY 240 AND NOT 260 like the other two. The hero's footer band is the ONLY free strip on
     this panel and it is tight: MEASURED at 1400x800 the title's baseline box ends at y692 and
     the viewport at y800, so the band is 108px — versus About/Labs, whose copy is a narrow
     column that leaves their whole bottom open. A 260-wide mark is 100 tall and would eat 100
     of those 108px. At 240 (=92 tall) the ink clears "Hi. I'm Stephen." by ~12px and the
     screen bottom by ~22px. Judge collisions on VISIBLE INK, not the box: the canvas is
     deliberately oversized so the flock dissolves before its bounds (see EDGE_FADE in
     flock.js), and the ink is only centre ±0.40·boxH vertically.

   ⚠ The bottom anchor carries the lvh/svh correction. #home is var(--app-h) (=100lvh) tall,
     which overruns the visible viewport whenever browser chrome shows, so a plain `bottom`
     would drop the mark under the fold on a phone. Adding the lvh−svh difference lifts it
     back onto the glass. Same fix the old bottom-right placement used. */
#home .home-flock {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--app-h) - 100svh + env(safe-area-inset-bottom, 0px) + clamp(0.5rem, 1.5vh, 1rem));
  --size: clamp(28px, 3.5vw, 42px);   /* square Orealis mark — see the About note above */
  z-index: 4;
  pointer-events: none;
  opacity: 0.95;
}
/* MOBILE: the mark stays, but CENTRED rather than in the footer — there is no footer band to
   sit in here. The hero text is deliberately lifted on phones to clear Safari's URL pill
   (see .panel--hero .panel__inner), and the strip below it is pill territory: measured at
   390x796 the title ends at y680, leaving 116px that the floating pill then occupies. That is
   the same reason the old .scroll-cue was display:none at this width. So the mark takes the
   open field above the text instead, at 40svh (=318 on that device) — 82px of clearance below
   the ink, and clear of the nav above. */
/* v377 — MOBILE home: the mark is a CENTRE-BOTTOM FOOTER here too (Stephen: "the home screen
   should have it as a center bottom footer"). v373-376 had it floating at 40svh because I'd
   judged the phone footer band unusable; his simulator screenshot shows otherwise — there is
   real space between the title and the URL pill.
   ⚠⚠ 100svh ALREADY EXCLUDES THE PILL — do not budget for it twice. v377 added a ~60px gap on
      top of the svh bottom edge "to clear the pill", which on a real device floated the mark UP
      into "Hi. I'm Stephen." (Stephen's device shot: title ends ~y653, pill starts ~y736, and the
      mark was sitting ~y630–676 — above the title's baseline). On iOS 26 the small viewport is
      the pill-free one, so anchoring a few px off the svh bottom is exactly "just clears the
      pill", which is what was asked for. The gap is now ~10px, not ~60.
      This is also why it measured fine locally and failed on hardware: in the preview
      lvh == svh, so the whole (lvh − svh) term is 0 and the error is invisible. Any change here
      has to be checked on a device with browser chrome showing, not in a desktop viewport. */
@media (max-width: 760px) {
  #home .home-flock {
    top: auto;
    /* ⚠ CENTRED — do NOT nudge this one right. v381 did, and it was wrong: the right-shift
       exists so a RIGHT-justified mark lines up under the video button stack (About / Labs).
       Home's mark is centre-justified, so it has nothing to align to and the nudge just
       decentres it (Stephen: "if the murmaration is center justified don't move it to the
       right -- only if it's right justified"). */
    left: 50%;
    bottom: calc(var(--app-h) - 100svh + env(safe-area-inset-bottom, 0px) + clamp(0.2rem, 0.6vh, 0.4rem));
    transform: translateX(-50%);
    /* ⚠ SMALL ON PURPOSE — the band is small. On a 852-tall phone the title's bottom is pinned at
       ~730 in section coords (the hero's 12vh padding-bottom + the 70px inner margin fix it there,
       independent of chrome) and the pill-free bottom is ~776–786. That is ~46px of usable band.
       At the previous 122x47 the box literally could not fit and landed ON the title. 79x30 leaves
       ~10–20px of clearance across the plausible svh range (766–800). If you want it bigger, the
       band has to be opened first by lifting the hero copy — raise .panel--hero .panel__inner's
       mobile padding-bottom — not by growing this. */
    /* ⚠ Sized to the OLD BOX'S HEIGHT, not its width. The flock box was 13/5, so 72-84px wide
       was only ~28-32px TALL — and that height is what had to fit the ~46px band. A square mark
       at the old WIDTH would be 2.5x taller and land straight on "Hi. I'm Stephen.", which is
       the exact collision the note above documents. Keep this ≈30px. */
    --size: clamp(26px, 7vw, 32px);
  }
  /* ⚠ In THIS block #home is calc(100lvh + 70px) and .panel__inner gets margin-bottom:70px to
     lift its content clear of the pill. The mark is a child of the SECTION, not of
     .panel__inner, so it gets none of that and must add the same 70px by hand — exactly what
     the old `#home .scroll-cue { bottom: calc(... + 70px) }` above did, for the same reason.
     Without this the mark sinks 70px and lands behind the pill. */
  @supports (height: 100lvh) {
    #home .home-flock {
      bottom: calc(var(--app-h) - 100svh + env(safe-area-inset-bottom, 0px) + clamp(0.2rem, 0.6vh, 0.4rem) + 70px);
    }
  }
  /* dvh version — rides the pill like the others. Moving to dvh only ever pushes this mark DOWN
     (dvh >= svh), i.e. further from "Hi. I'm Stephen.", so it cannot reintroduce the collision. */
  @supports (height: 100dvh) {
    #home .home-flock {
      bottom: calc(var(--app-h) - 100dvh + env(safe-area-inset-bottom, 0px) + clamp(0.2rem, 0.6vh, 0.4rem) + 70px);
    }
  }
}


/* v371 — POETRY's murmuration mark. Fourth instance of the shared canvas (flock.js drives
   every canvas.flock-mark).
   ⚠ Poetry is the most crowded section on the site, so placement is tight: .verse-dots own
     bottom-centre, .poem-seeall owns bottom-right, .verse-nav chevrons own both mid-edges,
     and .verse (the poem) is a full-height column on the LEFT capped at 54rem. The only
     genuinely free pocket is the lower right, lifted to clear the see-all pill beneath it. */
#poetry .poem-flock {
  position: absolute;
  /* v376 — RIGHT gutter. The earlier note here said the right side was too narrow because the
     .verse COLUMN box runs to x1256. That was measuring the wrong thing: the box is full-width
     but the lines are left-aligned, so what matters is the text INK. Measured with Ranges over
     all 7 poems, the widest line ends at x929 (worst case, slide 0) — leaving 433px of real
     gutter, not 144. A right-anchored mark clears every poem.
     ⚠ It must also sit ABOVE .poem-seeall, which owns this exact corner (x1249–1362, y733–768).
       The larger bottom offset lifts the box to ~y716, ~17px clear of that pill, and well below
       the next-chevron at y376–420. */
  left: 50%;
  transform: translateX(-50%);
  /* v387: centred like the others. That also dissolves the .poem-seeall clash — the pill owns the
     bottom-RIGHT corner, which is the only reason the right-anchored version had to be lifted to
     ~y716. Centred, the mark sits at the normal footer height. */
  bottom: calc(var(--app-h) - 100svh + clamp(2rem, 5vh, 3rem));
  --size: clamp(28px, 3.5vw, 42px);   /* square Orealis mark — see the About note above */
  z-index: 5;              /* above the carousel (z0), below the dots/see-all furniture */
  pointer-events: none;
  opacity: 0.8;
}
/* MOBILE: hidden. On phones the dots and see-all are both lifted to ~7rem above the pill and
   the poem scrolls the full height — there is no free pocket left at that size. */
@media (max-width: 760px) {
  #poetry .poem-flock { display: none; }
}

/* ============================================================================
   OUTBOUND VENTURE LINK (Orealis Labs → orealis.ai). Styled to sit inside the identity
   strip without shouting: inherits the strip's type, gains an underline only on hover.
   The strip is small and the link is the venture's own name, so a link colour would fight
   the design — the ↗ carries the affordance instead.
   ============================================================================ */
#community-square-labs .vid-link {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color 0.22s ease, border-color 0.22s ease;
  pointer-events: auto;        /* the strip itself is decorative; the link must take clicks */
}
#community-square-labs .vid-link:hover,
#community-square-labs .vid-link:focus-visible {
  color: #fff;
  border-bottom-color: rgba(255, 255, 255, 0.55);
}
