/* Jason Moccia — global styles (ported from Claude Design: "Jason Moccia.dc.html") */

/* Color roles. Indigo splits into three shades by job:
   --accent for fills, focus rings and large display type (≥3:1 on canvas);
   --accent-text for small text on dark (6.1:1 — base indigo is only 4.4:1);
   --accent-deep behind white text on filled controls (6:1). */
:root {
  --canvas: #131315;
  --ink: #ECEAE4;
  --text-2: #C9C6BE;
  --text-3: #8A8880;
  --line: #2C2C30;
  --line-strong: #4B4B50;
  --accent: #5B6CFF;
  --accent-text: #7B8AFF;
  --accent-deep: #4350E0;
  /* Page edge. Lives here rather than on the insights sub-page so the shared
     footer measures the same margin on every page that uses it. */
  --page-gutter: 40px;
}

/* ═══ Type system ═══════════════════════════════════════════════════════
   Three registers, one rule each. The rule is what makes it a system:
   a reader learns it once in the hero and it holds to the colophon.

   --font-display  Bricolage Grotesque. Structure and interface: headings,
                   nav, controls, wordmarks. The voice of the site.
   --font-text     Newsreader. Anything read as prose. Its italic is the
                   first-person voice — asides, the hero name, pull quotes.
   --font-record   Martian Mono. Anything that is a record: dates, places,
                   counts, index labels. Never decoration.

   All three are variable, so weight/width/optical size are set with the
   high-level properties (font-weight, font-stretch, font-optical-sizing)
   rather than font-variation-settings, which would clobber sibling axes. */

/* Metric-matched fallbacks, so the swap from fallback to webfont does not
   reflow the line. This matters here more than usual: the nav pill measures
   tab widths from rendered text and repositions its cursor on fonts.ready
   (main.js), so an unmatched fallback visibly jumps the cursor after paint.

   Each size-adjust is the measured ratio of the real face's advance width to
   its fallback's, over the same string at the same px:
     Bricolage Grotesque : Arial      1907/2123 = 89.8%
     Newsreader          : Georgia    1836/1995 = 92.0%
     Martian Mono        : Menlo      1907/2468 = 77.2%
   Martian Mono is narrower than Menlo, not wider — worth measuring rather than
   assuming, since guessing that one backwards widens every label instead of
   tightening it. Monaco and Courier New measure within 0.3% of Menlo, so one
   value covers the whole chain. Vertical metrics are deliberately not
   overridden: every role here sets its own line-height, so advance width is
   the only metric that moves layout. */
@font-face {
  font-family: 'Display Fallback';
  src: local('Arial'), local('Helvetica Neue'), local('Liberation Sans');
  size-adjust: 89.8%;
}
@font-face {
  font-family: 'Text Fallback';
  src: local('Georgia'), local('Times New Roman'), local('Liberation Serif');
  size-adjust: 92%;
}
@font-face {
  font-family: 'Record Fallback';
  src: local('Menlo'), local('Monaco'), local('Consolas'), local('Courier New'), local('DejaVu Sans Mono');
  size-adjust: 77.3%;
}

:root {
  --font-display: 'Bricolage Grotesque', 'Display Fallback', system-ui, sans-serif;
  --font-text: 'Newsreader', 'Text Fallback', Georgia, serif;
  --font-record: 'Martian Mono', 'Record Fallback', ui-monospace, monospace;

  /* Display ramp. Every peer heading shares one step — the old file had five
     near-identical clamps doing one job, which read as drift, not hierarchy. */
  --fs-hero: clamp(58px, 10.4vw, 176px);
  --fs-display-1: clamp(42px, 6.4vw, 108px);  /* section heads — all peers */
  --fs-display-2: clamp(32px, 4.3vw, 74px);   /* venture titles, contact email */
  --fs-display-3: clamp(20px, 1.75vw, 30px);  /* expertise list */
  --fs-marquee: clamp(20px, 2vw, 34px);

  /* Reading ramp */
  --fs-lead: clamp(24px, 2.5vw, 42px);
  --fs-body: clamp(17px, 1.28vw, 20px);
  --fs-body-sm: clamp(16px, 1.12vw, 18px);

  /* Record ramp — one size, because it is one role */
  --fs-record: 12px;
  --fs-record-lg: 14px;

  /* Display type wants negative tracking as it grows; the record voice is
     wide by drawing and needs air between characters to read as a label. */
  --track-display: -.022em;
  /* Martian Mono is wide by drawing, so it needs far less added tracking than
     a grotesque would to read as a label — and the long proof line falls apart
     past ~.1em. One value, so every record reads as the same voice. */
  --track-record: .1em;

  /* Light-on-dark compensation: prose gets a touch more tracking and one
     notch more weight than it would need on white. */
  --track-prose: .006em;
  --wght-prose: 420;

  /* 62ch, not 68: the ch unit is the width of "0", and Newsreader's lining
     figures run well wider than its average lowercase, so 68ch measured out at
     ~81 real characters — past the 75 ceiling. 62ch lands at ~74. */
  --measure: 62ch;
}

html { scroll-behavior: smooth; }
html, body { overflow-x: clip; }
body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--font-text);
  font-size: var(--fs-body);
  font-weight: var(--wght-prose);
  line-height: 1.65;
  letter-spacing: var(--track-prose);
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
}

/* Shared role classes. Type lives here, not in the elements' inline layout. */
.t-display {
  font-family: var(--font-display);
  font-weight: 800;
  font-stretch: 84%;
  font-optical-sizing: auto;
  text-transform: uppercase;
  letter-spacing: var(--track-display);
  line-height: .92;
  text-wrap: balance;
}
.t-section {
  margin: 0;
  font-size: var(--fs-display-1);
}
.t-prose {
  max-width: var(--measure);
  font-size: var(--fs-body);
  color: var(--text-2);
}
.t-prose strong { color: var(--ink); font-weight: 600; }
/* The first-person voice: the moments Jason addresses the reader directly.
   Not emphasis, and never a whole paragraph — sustained italic at 20px is a
   readability cost with no payoff. It steps forward in color as well as slope,
   and takes a little extra weight because italic strokes thin out on dark. */
.t-voice { font-style: italic; font-weight: 440; color: var(--ink); }
.t-record {
  font-family: var(--font-record);
  font-size: var(--fs-record);
  font-weight: 600;
  font-stretch: 75%;
  line-height: 1.4;
  text-transform: uppercase;
  letter-spacing: var(--track-record);
  font-variant-numeric: tabular-nums;
}
a { color: var(--ink); text-decoration: none; }
a:hover { color: var(--accent-text); }
a:focus-visible, button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}
::selection { background: var(--accent-deep); color: #FFFFFF; }

/* Skip link — hidden until keyboard focus */
.skip-link {
  position: fixed;
  top: 14px;
  left: 14px;
  z-index: 100;
  padding: 10px 20px;
  background: var(--accent-deep);
  color: #FFFFFF;
  border-radius: 999px;
  font-family: var(--font-display);
  font-size: var(--fs-record);
  font-weight: 700;
  font-stretch: 90%;
  text-transform: uppercase;
  letter-spacing: .06em;
  transform: translateY(-300%);
}
.skip-link:focus-visible { transform: none; }

/* Fixed header — a bare rail, no panel of its own. The hero photo is the first
   thing the page says, so nothing bands across the top of it; the nav pill
   carries its own dark track and border, which is what keeps the labels legible
   over the pale sea in the photo as well as the dark sections below. */
#siteHeader {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 24px;
  padding: 16px 40px;
  background: transparent;
  color: #ECEAE4;
  pointer-events: none;
}
/* Only the nav itself takes the pointer back — a transparent full-width rail
   would otherwise eat clicks across the top of whatever is under it. */
#siteHeader > * { pointer-events: auto; }
/* Sliding-pill nav. The cursor is the only thing that moves: it follows the
   hovered tab and settles back on the section you're actually reading. Labels
   sit above it and invert through difference blending, so one white pill reads
   correctly against both the dark track and the light label text.
   isolation keeps that blend inside the pill instead of punching through the
   header to the hero photo. */
#topNav { display: flex; min-width: 0; }
.slide-tabs {
  position: relative;
  isolation: isolate;
  display: flex;
  margin: 0;
  padding: 3px;
  max-width: 100%;
  overflow-x: auto;
  list-style: none;
  border: 1.5px solid var(--ink);
  border-radius: 999px;
  background: #1F1F23;
  scrollbar-width: none;
}
.slide-tabs::-webkit-scrollbar { display: none; }
/* The blend has to sit on the same element as the z-index: a stacking context
   would isolate a blended child from the cursor it needs to blend against. */
.slide-tab {
  position: relative;
  z-index: 1;
  mix-blend-mode: difference;
}
/* Nav is interface, not record — the display voice, widened to 90% because
   Bricolage's default width closes up too much at 12px. */
.slide-tab a {
  display: block;
  padding: 8px 16px;
  font-family: var(--font-display);
  font-size: var(--fs-record);
  font-weight: 700;
  font-stretch: 90%;
  letter-spacing: .05em;
  text-transform: uppercase;
  white-space: nowrap;
  color: #FFFFFF;
}
/* The blend already signals hover — the global accent hover would fight it */
.slide-tab a:hover { color: #FFFFFF; }
.slide-tabs-cursor {
  position: absolute;
  z-index: 0;
  top: 3px;
  left: 0;
  width: 0;
  height: calc(100% - 6px);
  opacity: 0;
  border-radius: 999px;
  background: var(--ink);
  transition: transform .3s cubic-bezier(.22, .61, .36, 1),
              width .3s cubic-bezier(.22, .61, .36, 1),
              opacity .2s linear;
}

/* Mobile nav — hamburger + left drawer.
   Six labels in one pill need ~636px of header. Below that the pill was
   overflowing its own box and clipping "Contact" off the right edge with
   `scrollbar-width: none` hiding any hint that it scrolled, so the last item
   was simply unreachable on a phone. Past the breakpoint the pill is replaced
   by a button and a drawer that can hold all six at a readable size.
   Both live in the DOM at every width; CSS decides which one is the nav. */
#navToggle {
  display: none;
  align-items: center;
  justify-content: center;
  flex: none;
  /* 44px is the touch-target floor, and it matches the pill's own height so
     the two states occupy the same slot in the header. */
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1.5px solid var(--ink);
  border-radius: 999px;
  background: #1F1F23;
  color: #FFFFFF;
  cursor: pointer;
}
.nav-toggle-bars {
  position: relative;
  display: block;
  width: 17px;
  height: 12px;
}
.nav-toggle-bars i {
  position: absolute;
  left: 0;
  width: 100%;
  height: 1.5px;
  border-radius: 2px;
  background: currentColor;
  transition: transform .32s cubic-bezier(.22, .61, .36, 1), opacity .18s linear;
}
.nav-toggle-bars i:nth-child(1) { top: 0; }
.nav-toggle-bars i:nth-child(2) { top: 5.25px; }
.nav-toggle-bars i:nth-child(3) { top: 10.5px; }
/* Bars collapse to the centre line and cross — the button becomes its own
   close control rather than the drawer carrying a second one. */
#navToggle[aria-expanded="true"] .nav-toggle-bars i:nth-child(1) { transform: translateY(5.25px) rotate(45deg); }
#navToggle[aria-expanded="true"] .nav-toggle-bars i:nth-child(2) { opacity: 0; }
#navToggle[aria-expanded="true"] .nav-toggle-bars i:nth-child(3) { transform: translateY(-5.25px) rotate(-45deg); }

#navScrim {
  position: fixed;
  z-index: 60;
  inset: 0;
  visibility: hidden;
  opacity: 0;
  background: rgba(19, 19, 21, .66);
  backdrop-filter: blur(3px);
  /* Visibility flips only after the fade finishes, so the scrim doesn't vanish
     mid-transition and expose the page underneath it. */
  transition: opacity .3s linear, visibility 0s linear .3s;
}
#navDrawer {
  position: fixed;
  z-index: 61;
  top: 0;
  bottom: 0;
  left: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  box-sizing: border-box;
  width: min(78vw, 300px);
  /* Top padding clears the toggle, which stays on top of the drawer. */
  padding: 92px 26px 32px;
  border-right: 1.5px solid var(--line-strong);
  background: #1F1F23;
  visibility: hidden;
  transform: translateX(-100%);
  transition: transform .38s cubic-bezier(.22, .61, .36, 1), visibility 0s linear .38s;
}
body.nav-open #navScrim {
  visibility: visible;
  opacity: 1;
  transition: opacity .3s linear;
}
body.nav-open #navDrawer {
  visibility: visible;
  transform: none;
  transition: transform .38s cubic-bezier(.22, .61, .36, 1);
}
/* The toggle has to outrank the scrim it just raised, or the X would sit under
   it and the menu could not be closed with the control that opened it. */
body.nav-open #siteHeader { z-index: 62; }

.nav-drawer-list {
  counter-reset: navitem;
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
}
.nav-drawer-list li { counter-increment: navitem; }
/* Same display voice as the pill, sized up for thumbs — a drawer that merely
   restacked 11px nav labels would waste the room it just bought. */
.nav-drawer-list a {
  display: block;
  padding: 11px 0;
  font-family: var(--font-display);
  font-size: clamp(20px, 6.2vw, 26px);
  font-weight: 700;
  font-stretch: 88%;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--ink);
}
/* Numerals in the record voice, from a counter — the index is presentation,
   so it stays out of the markup and out of the accessibility tree. */
.nav-drawer-list a::before {
  content: counter(navitem, decimal-leading-zero);
  margin-right: 12px;
  font-family: var(--font-record);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .1em;
  vertical-align: middle;
  color: var(--text-3);
}
.nav-drawer-list a:hover,
.nav-drawer-list a[aria-current] { color: var(--accent-text); }
.nav-drawer-list a[aria-current]::before { color: var(--accent-text); }

/* Hero layout. Lives here rather than inline on the section so the 900px/600px
   overrides below can win with plain selectors instead of !important. */
#top {
  position: relative;
  height: 100vh;
  min-height: 640px;
  overflow: hidden;
}
#heroImgWrap {
  position: absolute;
  inset: 0;
  z-index: 0;
}
#heroImg {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
#heroText {
  position: relative;
  z-index: 2;
  pointer-events: none;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 0 40px;
  box-sizing: border-box;
}

/* Hero. Two registers in one lockup, which is the whole thesis: "Jason" is set
   in the display voice (the operator), "Moccia" in the Newsreader italic (the
   person). Cormorant used to carry this and could not — its hairlines vanish
   once the ≤900px scrim flips the copy to light ink on near-black. Newsreader's
   italic holds a real stem weight at display size, so the same lockup survives
   both the pale sea behind it and the dark scrim below. */
#heroTitle {
  margin: 0 0 10vh;
  display: flex;
  flex-direction: column;
  font-size: var(--fs-hero);
  line-height: .88;
  color: #131315;
}
.hero-first {
  font-family: var(--font-display);
  font-weight: 800;
  font-stretch: 84%;
  font-optical-sizing: auto;
  text-transform: uppercase;
  letter-spacing: -.03em;
}
/* .923em is not a guess: at a shared font-size Newsreader's cap height measured
   107.1px against Bricolage's 98.8px, so the surname sat 8.3% taller than the
   given name and the lockup read as two sizes instead of one name. This divides
   that ratio out, leaving both caps on the same line. */
.hero-last {
  margin-left: clamp(80px, 13vw, 260px);
  font-family: var(--font-text);
  font-style: italic;
  font-weight: 500;
  font-size: .923em;
  font-optical-sizing: auto;
  letter-spacing: -.015em;
}
#heroTag {
  margin: 0;
  font-family: var(--font-text);
  font-style: italic;
  font-weight: 400;
  font-size: var(--fs-lead);
  line-height: 1.2;
  letter-spacing: -.005em;
  color: #131315;
}
/* Hero × — deep indigo so it holds against the pale sea; the mobile
   scrim flips the hero copy light, so it lightens with it below 900px. */
.hero-x {
  display: inline-block;
  font-family: var(--font-display);
  font-size: .5em;
  font-weight: 700;
  font-style: normal;
  letter-spacing: .04em;
  vertical-align: .12em;
  color: var(--accent-deep);
}

/* Hero proof line — the first thing the record voice says, so the reader
   learns the rule (mono = verifiable fact) before meeting it anywhere else. */
#heroProof {
  margin: 28px 0 0;
  max-width: 62ch;
  text-wrap: balance;
  color: #131315;
}

/* Hero entrance. The lockup, the tagline and the proof line lift in on load,
   staggered so the eye reads them in the order they're meant to land.

   The curve matters more than the duration here. The Framer Motion hero ships
   [0.16, 1, 0.3, 1], which spends ~75% of its travel in the first fifth of the
   run and then crawls the remainder — at any duration that front-loaded burst
   reads as a snap, not a glide. This is an ease-out-quad instead: the fastest
   moment is still the first frame, but the falloff is gradual enough that the
   whole 1.5s reads as one continuous movement.

   `both` holds the from-state before the delay elapses, so nothing flashes at
   full opacity first. */
@keyframes heroRise {
  from { opacity: 0; transform: translateY(23px); }
  to   { opacity: 1; transform: translateY(0); }
}
/* No will-change here: animating transform/opacity already composites the
   three for the run, and a permanent hint would hold the layers alive long
   after the 1.5s entrance is over. */
#heroTitle, #heroTag, #heroProof {
  animation: heroRise 1.5s cubic-bezier(.25, .46, .45, .94) both;
}
#heroTag { animation-delay: .2s; }
#heroProof { animation-delay: .4s; }

/* Hero image fallback — sky-to-sea tones matched to assets/hero.jpg, shown if the photo fails */
#heroImgWrap {
  background: linear-gradient(160deg, #E9EEEC 0%, #C4D9DB 45%, #7FB4BC 100%);
}

/* Shared index label — record voice. Carries .t-record in markup; this only
   sets the spacing and color that belong to the label's position on the page. */
.eyebrow {
  margin: 0 0 14px;
  color: var(--text-3);
}

/* Scroll reveal */
[data-reveal] {
  opacity: 0;
  transform: translateY(40px);
  transition: opacity .9s cubic-bezier(.22,1,.36,1), transform .9s cubic-bezier(.22,1,.36,1);
}
[data-reveal].in { opacity: 1; transform: translateY(0); }

/* Marquees */
@keyframes marqueeL { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@keyframes marqueeR { from { transform: translateX(-50%); } to { transform: translateX(0); } }
.mq-row { overflow: hidden; padding: 26px 0; border-bottom: 1px solid var(--line); }
.mq { display: flex; gap: 60px; width: max-content; }
.mq-track {
  display: flex;
  gap: 60px;
  font-family: var(--font-display);
  font-weight: 700;
  font-stretch: 84%;
  font-optical-sizing: auto;
  font-size: var(--fs-marquee);
  letter-spacing: -.01em;
  text-transform: uppercase;
  white-space: nowrap;
}
.mq-track i { font-style: normal; color: var(--accent); }

/* Track record — sticky stacked panels.
   --covered (0-1, set from JS) drives how far the panel has receded
   under the next one: scale, dim, and corner rounding all key off it. */
.venture {
  position: sticky;
  top: 0;
  height: 100vh;
  min-height: 560px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 40px;
  box-sizing: border-box;
  box-shadow: 0 -32px 64px rgba(0, 0, 0, .4);
  border-radius: calc(var(--covered, 0) * 24px);
  /* No will-change: main.js writes transform on every scrolled frame, which
     keeps the panel layerized while the stack is moving — a permanent hint
     would pin four viewport-sized GPU layers for the life of the page. */
}
.venture--tight {
  height: 90vh;
  min-height: 504px;
}
.venture::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: #0C0C0E;
  opacity: calc(var(--covered, 0) * .55);
  pointer-events: none;
}
.venture-grid {
  width: 100%;
  max-width: 1440px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 40px;
  align-items: center;
}
/* Subordinate to a section head by one full step, so a panel title never
   competes with the section it sits inside. */
.venture-title {
  margin: 0 0 34px;
  font-size: var(--fs-display-2);
  max-width: 12ch;
}
/* One line of plain-language positioning between a panel title and its
   bullets. Brighter than the bullets and a step larger, so the panel answers
   "what is this" before it starts listing proof. Matches .nl-lead's role on
   the newsletter panel. */
.venture-lead {
  margin: -18px 0 24px;
  max-width: 30ch;
  font-size: clamp(19px, 1.5vw, 26px);
  line-height: 1.3;
  letter-spacing: 0;
  color: var(--ink);
  text-wrap: balance;
}
.venture-bullets {
  margin: 0 0 40px;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-width: 48ch;
  font-size: var(--fs-body-sm);
  line-height: 1.55;
  color: var(--text-2);
}
.venture-bullets li { position: relative; padding-left: 1.3em; }
.venture-bullets li::before {
  content: '•';
  position: absolute;
  left: 0;
  color: var(--text-3);
}
.venture-link {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 14px 28px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  font-family: var(--font-display);
  font-size: var(--fs-record-lg);
  font-weight: 700;
  font-stretch: 90%;
  text-transform: uppercase;
  letter-spacing: .05em;
}
.venture-link:hover { border-color: var(--accent); color: var(--accent-text); }

/* Paired actions on a venture panel — primary fill first, outline second */
.venture-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
}

/* The one filled action on the page — the closing panel's "Get in touch" */
.venture-link--primary {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
  color: #FFFFFF;
}
.venture-link--primary:hover {
  background: #4E5CF2;
  border-color: #4E5CF2;
  color: #FFFFFF;
}

/* Closing panel — the stack's destination gets the indigo wash; secondary
   text is tinted from the surface hue instead of the warm gray. */
.venture--cta { background: #1F2136; }
.venture--cta .venture-bullets { color: #C6C9DA; }
.venture--cta .venture-bullets li::before { color: #8E92A8; }
.venture--cta .venture-meta { color: #8E92A8; }
.venture-side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: space-between;
  align-self: stretch;
  padding: 6vh 0;
}
.venture-meta {
  margin: 0;
  color: var(--text-3);
  text-align: right;
}
.venture-word {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-stretch: 84%;
  font-optical-sizing: auto;
  font-size: clamp(38px, 6.2vw, 104px);
  line-height: .9;
  text-transform: uppercase;
  letter-spacing: -.03em;
  text-align: right;
}

/* Real logomarks stand in for the ghost wordmark on the panels that have one.
   Width tracks the wordmark's rendered footprint (~28vw) so the right column
   keeps the same meta-top / mark-bottom rhythm as the sibling panels, and the
   block box makes it obey .venture-side's flex-end / flex-start alignment. */
.venture-logo {
  --venture-logo-w: clamp(200px, 34vw, 500px);
  display: block;
  width: var(--venture-logo-w);
  height: auto;
  /* The sibling wordmarks are uppercase on line-height .9, so their ink stops
     .125em above the box bottom. The logo's ink runs to its own bottom edge, so
     it needs that gap back or it hangs low against the panels above and below. */
  margin-bottom: clamp(4.8px, .78vw, 13px);
  /* Full-strength white would make the mark the loudest thing on the panel,
     above the title. Held at the body-text step: clearly the logo, still behind
     the headline like every other panel's mark. */
  opacity: .55;
}

/* TalentLoft panel — a lockup (icon + wordmark), not a bare wordmark, so equal
   widths would render its type smaller than ONESPRING's. Scaled by the ratio of
   the two files' cap-height-to-width (94/783 ÷ 146/1161 = .955) instead, so both
   panels' lettering reads at the same size as you scroll between them. */
.venture-logo--talentloft {
  width: calc(var(--venture-logo-w) * .955);
}

/* ── Closing panel: the masthead lockup ──────────────────────────────────
   Every panel above ends its right column with a mark — the ghost wordmark,
   or a real logo where the venture has one. This panel's mark is both at
   once, because its subject is a person and a role, so the two are set as
   one object: the word behind, the man in front of it, overlapping.

   The figure is a foreground cutout, duotoned to the panel's own two colours
   (shadows to indigo, highlights to the site's bone) before it ever reaches
   the browser — see tools/. Full colour, the golden-hour background out of
   the original shot was the loudest thing on the panel and read as a
   photograph someone had dropped onto the design. Two colours, it reads as
   part of it. */
.venture-side--advisor {
  /* The lockup is wider than the flex-end alignment its siblings use — the
     wordmark deliberately runs past the column on both sides — so this column
     hands it the full width and lets the lockup do its own alignment. */
  align-items: stretch;
}
.advisor-lockup {
  position: relative;
  display: flex;
  justify-content: flex-end;
  /* The wordmark overhangs the column. html/body clip the page's horizontal
     overflow, so this can never open a scrollbar. */
  isolation: isolate;
}

/* Bigger than the sibling panels' wordmarks, and it has to be: those sit in
   clear space, this one is read through a figure standing on top of it, so it
   needs enough letter left showing on each side to still resolve as a word. */
.venture-word--advisor {
  position: absolute;
  z-index: 0;
  /* Set wide enough that it clearly outruns the figure on both sides. Matched
     to his width it read as a smudge behind him rather than a word he is
     standing in front of, which is the whole point of the lockup. The right
     end runs into the panel's own 40px gutter and stops there. */
  right: -.18em;
  /* Crosses the figure at the chest — high enough that the word is not a
     plinth he stands on, low enough to leave the face completely clear. */
  bottom: 24%;
  font-size: clamp(54px, 15.5vw, 258px);
  line-height: .8;
  color: #3B3F63;
  white-space: nowrap;
  pointer-events: none;
  /* --drift runs 1 → 0 as the panel slides up to its pin (main.js). The word
     and the figure take it in opposite directions, so the lockup separates on
     the way in and closes as it settles. */
  transform: translate3d(0, calc(var(--drift, 0) * -26px), 0);
}
.advisor-figure {
  position: relative;
  z-index: 1;
  display: block;
  width: clamp(215px, 27vw, 400px);
  height: auto;
  transform: translate3d(0, calc(var(--drift, 0) * 34px), 0);
}

/* Newsletter panel, left column. Four voices in the site's own order:
   display title → Newsreader promise → Newsreader detail → record counts.
   This panel argues its case in sentences instead of bullets, so it carries
   more blocks than its siblings; every seam below is tighter than the shared
   .venture rhythm to keep the whole column inside one 100vh box. */
.venture-title--nl { margin-bottom: 22px; max-width: 14ch; }

/* The promise. Upright Newsreader at a step above body — near-lead size, ink
   colour — so it reads as the panel's subtitle without competing with the
   display title above it. Not italic: that slope is Jason's own voice. */
.nl-lead {
  margin: 0 0 14px;
  max-width: 26ch;
  font-size: clamp(19px, 1.5vw, 26px);
  line-height: 1.3;
  letter-spacing: 0;
  color: var(--ink);
  text-wrap: balance;
}
.nl-desc {
  margin: 0 0 28px;
  max-width: 46ch;
  font-size: var(--fs-body-sm);
  line-height: 1.55;
  color: var(--text-2);
}

/* Counts are records, so they take Martian Mono — the same voice as the dates
   and places on the sibling panels, just at a size that can be scanned. The
   hairline above turns three numbers into one band rather than three orphans. */
.nl-stats {
  margin: 0 0 26px;
  padding: 24px 0 0;
  border-top: 1px solid var(--line-strong);
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 18px clamp(30px, 3.6vw, 60px);
  max-width: 46ch;
}
.nl-stats li { display: flex; flex-direction: column; gap: 6px; }
.nl-stat-num {
  font-family: var(--font-record);
  font-weight: 700;
  font-stretch: 75%;
  font-size: clamp(26px, 2.4vw, 38px);
  line-height: 1;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.nl-stat-label { color: var(--text-3); }

/* Social proof sits last, directly above the action it justifies. The reader
   stack is decorative evidence for the count below it: five overlapping
   headshots, no interaction. The panel-colored border punches each circle out
   of its neighbor, and the same hairline ring the quote avatars wear draws at
   the outer edge to unify the mixed backdrops. */
.nl-avatars {
  display: flex;
  list-style: none;
  margin: 0 0 12px;
  padding: 0;
}
.nl-avatars li + li { margin-left: -10px; }
.nl-avatar {
  display: block;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--line);
  border: 2px solid #1B1C24;
  outline: 1px solid var(--line-strong);
  outline-offset: -1px;
}
/* The closing bubble says "and more" — a plus in the interface voice, filled
   like a control surface rather than a photo so it reads as a count, not a
   missing face. */
.nl-avatar--more {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 700;
  line-height: 1;
  color: var(--text-2);
}

.nl-join {
  margin: 0 0 28px;
  max-width: 40ch;
  font-size: var(--fs-body-sm);
  line-height: 1.5;
  color: var(--text-3);
}
.nl-join strong { color: var(--ink); font-weight: 600; }

/* Signup — the panel's action. Two pill fields in the interface voice sit on
   one row, with the filled Subscribe below them; the pills share the
   venture-link's radius and padding so field and button read as one control
   family. Typed text is input, not prose, so it takes the display face at
   sentence case. */
.nl-form { max-width: 46ch; }
.nl-form-fields {
  display: flex;
  gap: 12px;
  margin-bottom: 16px;
}
.nl-input {
  flex: 1 1 0;
  min-width: 0;
  padding: 13px 22px;
  background: transparent;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  font-family: var(--font-display);
  font-size: 15px;
  font-stretch: 90%;
  letter-spacing: .01em;
  color: var(--ink);
}
.nl-input::placeholder { color: var(--text-3); opacity: 1; }
.nl-input:hover { border-color: var(--text-3); }
/* The shared focus rule squares its outline to 2px; a pill keeps its shape. */
.nl-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 999px;
}
.nl-submit { cursor: pointer; }
.nl-submit:disabled { opacity: .6; cursor: default; }

/* Submit outcome, spoken in prose under the controls. On success the controls
   step aside (main.js adds the class) and the confirmation stands alone. */
.nl-form-status {
  margin: 12px 0 0;
  max-width: 46ch;
  font-size: var(--fs-body-sm);
  line-height: 1.5;
  color: var(--text-2);
}
.nl-form-status[data-state="error"] { color: #E8A9A0; }
/* The risk-reversal line under the controls — a record, so it takes the
   record voice at its quietest step. */
.nl-consent {
  margin: 12px 0 0;
  color: var(--text-3);
}
.nl-form--done .nl-form-fields,
.nl-form--done .nl-submit,
.nl-form--done .nl-consent { display: none; }
.nl-form--done .nl-form-status { margin-top: 0; }

/* Screen-reader-only labels for the placeholder-labelled fields. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Newsletter sample sheets — a fanned, draggable card stack.
   This panel's right column carries artefacts instead of a wordmark, so it
   centres its content rather than pinning meta top / wordmark bottom. */
.venture-side--sheets {
  align-items: center;
  justify-content: center;
  padding: 0;
}
.sheets {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
}
/* Every measurement inside a card is a ratio of --card-w (written by JS), so
   the stack stays proportional at any column width instead of snapping
   between breakpoints. */
.sheets-viewport {
  position: relative;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: 16px;
}
.sheets-viewport:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 8px;
}
/* One surface over the whole stack: a grab anywhere in the column moves the
   fan, not just a hit on the front card. pan-y leaves the page's own vertical
   scroll alone — the axis lock in JS decides which gesture wins. */
.sheets-drag {
  position: absolute;
  inset: 0;
  z-index: 50;
  cursor: grab;
  touch-action: pan-y;
}
.sheets-drag:active { cursor: grabbing; }

.sheet-card {
  position: absolute;
  margin: 0;
  border-radius: calc(var(--card-w) * .055);
  overflow: hidden;
  background: #1F1F23;
  pointer-events: none;
  will-change: transform, opacity;
}
/* No drop shadow on the cards. A 44px-blur shadow was tinting the panel below
   the stack a few units off #1B1C24, and because the viewport clips (for the
   mobile card bleed) the tint ended on a hard edge instead of fading out. The
   cards are bright artwork on a near-black panel and need no help separating;
   depth comes from the dim wash, the scale falloff and the inset ring. */
.sheet-card-img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Deepens with distance from centre (opacity written by JS), so the fan reads
   as depth rather than as five equally-lit rectangles. */
.sheet-card-dim {
  position: absolute;
  inset: 0;
  background: #000;
}
.sheet-card-ring {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .1);
}

/* Caption. The sheets carry their own headlines, so this names the centred one
   once, in the site's own three voices: record for the label, display for the
   title, Newsreader for the summary. */
.sheets-caption {
  width: 100%;
  max-width: calc(var(--card-w) * 1.5);
  text-align: center;
}
.sheets-caption-top {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin: 0 0 8px;
  color: var(--text-3);
}
.sheets-badge {
  padding: .3em .7em;
  border-radius: 999px;
  background: rgba(123, 138, 255, .14);
  color: var(--accent-text);
}
.sheets-count { font-variant-numeric: tabular-nums; }
.sheets-caption-title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-stretch: 88%;
  font-size: clamp(15px, calc(var(--card-w) * .088), 24px);
  line-height: 1.15;
  letter-spacing: var(--track-display);
  text-wrap: balance;
}
/* Newsreader upright, not italic: the italic is reserved for Jason's own
   voice, and a sheet summary isn't that. */
.sheets-caption-desc {
  margin: 6px 0 0;
  font-size: var(--fs-body-sm);
  line-height: 1.45;
  color: var(--text-2);
}
/* Once the column is this tight, the summary costs more room than it earns. */
.sheets[data-compact='true'] .sheets-caption-desc { display: none; }

.sheets-hint {
  margin: 0;
  color: var(--text-3);
  font-size: var(--fs-record);
  letter-spacing: var(--track-record);
  text-transform: uppercase;
  transition: opacity .5s;
}
/* The hint has done its job once the stack has been moved. */
.sheets[data-used='true'] .sheets-hint { opacity: 0; }
/* The carousel's live region reuses .visually-hidden (sheet-carousel.js). */

/* Expertise roles. A rule list, not headings — one step below the marquee so
   the eye reads section head → roles → keywords in that order. */
.role-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  font-family: var(--font-display);
  font-weight: 700;
  font-stretch: 88%;
  font-optical-sizing: auto;
  font-size: var(--fs-display-3);
  line-height: 1.15;
  letter-spacing: -.01em;
  text-transform: uppercase;
}
.role-list li { padding: 18px 0; border-bottom: 1px solid var(--line); }

/* ═══ Consultant → Founder ══════════════════════════════════════════════
   One scroll-driven number, --p (0→1), drives the whole figure: the rule
   draws left to right, a lit tip rides its leading edge, and the two words
   trade emphasis as it passes. The words are the point — the line is what
   makes the handoff read as one move rather than two unrelated fades.
   main.js writes --p; everything below only reads it.

   --p rests at 1, not 0. That is the graceful state for no-JS and for
   reduced motion, where main.js leaves it alone: rule drawn, both words
   legible, no stray dot. */
@property --p {
  syntax: '<number>';
  inherits: true;
  initial-value: 1;
}
.roles { --p: 1; }
.roles-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: clamp(16px, 3vw, 40px);
  padding-bottom: 30px;
}
/* These two have to share one line — the rule travelling left to right is the
   whole point, and stacked words would make that motion mean nothing. So they
   are the one place --fs-display-1's 42px floor cannot hold: two display words
   plus a gap do not fit 310px of phone. The vw term only overrides the floor
   below ~568px, so on every other viewport these stay peers with the other
   section heads. */
.roles-word-from, .roles-word-to { font-size: min(var(--fs-display-1), 7.4vw); }
/* Consultant starts lit and recedes as the tip leaves it; Founder arrives.
   Mixed in oklab so the midpoint stays neutral — sRGB interpolation between
   a warm off-white and a grey dips visibly muddy in the middle. */
.roles-word-from { color: color-mix(in oklab, var(--ink), var(--text-3) calc(var(--p) * 100%)); }
.roles-word-to { color: color-mix(in oklab, var(--text-3), var(--ink) calc(var(--p) * 100%)); }
@supports not (color: color-mix(in oklab, #000, #fff 50%)) {
  .roles-word-from, .roles-word-to { color: var(--ink); }
}

/* The undrawn remainder stays --line, so the row keeps the same 1px rule it
   had before any of this existed. The animation only lights part of it. */
.roles-rule {
  position: relative;
  height: 1px;
  background: var(--line);
}
.roles-rule-fill {
  position: absolute;
  inset: 0;
  transform: scaleX(var(--p));
  transform-origin: left center;
  /* The gradient rides the scale, so the drawn run is always dim at its
     origin and brightest at the tip — brightness reads as direction.
     Accent lands by 45% rather than at the far end: at 1px on this canvas a
     gradient that only reaches indigo in its last stretch reads as plain grey.
     The tip ramps on past it to --accent-text, the shade picked for exactly
     this problem — base indigo goes muddy at small sizes on dark. */
  background: linear-gradient(90deg, var(--line-strong), var(--accent) 45%, var(--accent-text));
}
.roles-rule-tip {
  position: absolute;
  top: 50%;
  left: calc(var(--p) * 100%);
  width: 6px;
  height: 6px;
  /* Centered with margin rather than translate: the element has no other
     transform, and this leaves `left` as the single positional input. */
  margin: -3px 0 0 -3px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 14px 3px rgba(91, 108, 255, .45);
  /* Gone over the last tenth, so the settled state is a clean drawn rule
     rather than a dot parked under Founder. */
  opacity: calc((1 - var(--p)) * 10);
}

/* ═══ Footer ════════════════════════════════════════════════════════════
   One footer for the whole site — the home page and every page under
   /insights/ use this same markup, so the address, the signup and the
   wordmark sit in the same places wherever a reader lands. Layout lives in
   classes rather than inline styles because the insights pages are generated. */
.site-foot {
  padding: 0 var(--page-gutter) 30px;
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 100px;
}
.site-foot-top {
  max-width: 1440px;
  margin: 0 auto;
  width: 100%;
  padding-top: 120px;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-end;
  gap: 40px;
}
/* Address above signup: the mail link is the direct route and the form is the
   standing one, so they read top to bottom as one column of ways to reach in. */
.site-foot-reach { display: flex; flex-direction: column; gap: 36px; }
.site-foot-mail { display: flex; align-items: baseline; gap: 20px; flex-wrap: wrap; }
.site-foot-nl-label { margin: 0 0 14px; }
.site-foot-bottom { overflow: hidden; }
.site-foot-fine {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 12px 20px;
  padding-top: 24px;
  border-top: 1px solid var(--line);
  color: var(--text-3);
}
/* The column's own gap sets the space under the label; the eyebrow's default
   bottom margin would add a second, unequal one on top of it. */
.footer-socials { display: flex; flex-direction: column; gap: 12px; }
.footer-socials .eyebrow { margin: 0; }
.footer-socials-row { display: flex; gap: 16px; }

/* Outline icon button — shadcn's `variant="outline" size="icon"` ported to
   this palette: 40px box, 6px radius, hairline border, tinted hover fill.
   40px is under the 44px tap minimum, so the row pads itself out below. */
.social-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  background: transparent;
  color: #fff;
  transition: background-color .2s ease, border-color .2s ease;
}

.social-icon:hover,
.social-icon:focus-visible {
  /* Lifted off the canvas rather than dimmed — the glyph stays full white so
     the mark itself never reads as disabled. */
  background: #1F1F23;
  border-color: var(--ink);
  color: #fff;
}

.social-icon svg { display: block; }

/* Restores the 44px tap target the 40px button gives up, without moving the
   button's own edges or the row's alignment with the label above it. */
.footer-socials-row a::after {
  content: '';
  position: absolute;
  inset: -2px;
}
.footer-socials-row a { position: relative; }

/* Testimonials */
.quote-card {
  margin: 0;
  padding: 40px 34px;
  background: #1B1B1E;
  border: 1px solid var(--line);
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 34px;
}
/* Roman, not italic: the italic is reserved for Jason's own voice, and these
   are other people speaking. The quote marks are hung so the text edge stays
   flush with the attribution below it. */
.quote-card blockquote {
  margin: 0;
  font-size: var(--fs-body-sm);
  line-height: 1.6;
  color: var(--text-2);
  hanging-punctuation: first;
  text-wrap: pretty;
}
.quote-card figcaption { display: flex; align-items: center; gap: 14px; }
/* Headshots, not initials. The three photos come from different shoots with
   different backdrops (office, blue, white), so the hairline ring does the
   unifying work: it gives every avatar the same edge and keeps the white-backed
   one from dissolving into the card. */
.quote-avatar {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  flex-shrink: 0;
  object-fit: cover;
  background: var(--line);
  /* An inset box-shadow would paint behind the image's replaced content, and a
     border would grow the box without a border-box rule; a pulled-in outline
     draws over the photo and stays out of layout. */
  outline: 1px solid var(--line-strong);
  outline-offset: -1px;
}
/* Attribution is a credit line, so it takes the record voice and sits well
   below the quote it credits — in the body face it rendered larger than the
   quotation itself and inverted the hierarchy. */
.quote-attr { color: var(--text-3); }
.quote-attr strong { color: var(--ink); font-weight: 700; }

/* Contact / footer */
/* The page's one action, so it sits at the venture-title step — the largest
   thing in the footer that is not decoration. Lowercase: it is an address,
   and uppercasing an email address makes it read as something to be shouted
   rather than something to be copied. */
.contact-email {
  font-family: var(--font-display);
  font-weight: 800;
  font-stretch: 84%;
  font-optical-sizing: auto;
  font-size: clamp(26px, 3.5vw, 60px);
  letter-spacing: -.025em;
  border-bottom: 2px solid var(--accent);
  padding-bottom: 6px;
}
.contact-email:hover { color: var(--accent-text); }
.copy-btn {
  padding: 8px 18px;
  background: none;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  font-family: var(--font-display);
  font-size: var(--fs-record);
  font-weight: 700;
  font-stretch: 90%;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--text-3);
  cursor: pointer;
}
.copy-btn:hover { border-color: var(--accent); color: var(--accent-text); }
.copy-btn.done {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
  color: #FFFFFF;
}
/* Sized from its own metrics rather than a guessed vw: 11 uppercase Bricolage
   characters at wdth 84 and -.04em run ~0.62em each, so the line fits the
   viewport at (100vw − the footer's own gutter) / 4.9 — the divisor measured
   from the rendered line (11 characters at 0.44em each), not estimated. Solving
   for the gutter instead of guessing a vw means it fits at every width, so the
   separate mobile override this rule used to need is gone. The old 10.5vw/220px
   pair clipped the final 'a' between roughly 1400px and 2100px wide. */
.footer-wordmark {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-stretch: 84%;
  font-optical-sizing: auto;
  font-size: min(calc((100vw - 88px) / 5.3), 300px);
  line-height: .95;
  letter-spacing: -.04em;
  text-transform: uppercase;
  text-align: center;
  white-space: nowrap;
  color: #1F1F23;
  -webkit-text-stroke: 1px #3C4060;
}

/* Responsive (from the design) */
@media (max-width: 900px) {
  /* The portrait sits at roughly 72% across the source frame. Widening the wrap
     off the left edge used to push it into view, but the crop that produced was
     height-dependent: `cover` centres its own crop, so a short viewport (a phone
     with browser chrome) slid the window left and left-justified him, while a
     tall one read centred. Naming the focal point instead holds him a hair right
     of centre at every height, which is where he belongs — the copy runs bottom
     left, so the face wants the other side of the axis. */
  #heroImgWrap { inset: -4% 0; }
  #heroImg { object-position: 78% center; }
  /* Scrim: the cropped photo puts dark clothing behind the text, so the
     hero copy switches to light ink over a bottom gradient. */
  #top::after {
    content: '';
    position: absolute;
    z-index: 1;
    inset: 35% 0 0;
    background: linear-gradient(180deg, rgba(19, 19, 21, 0), rgba(19, 19, 21, .9) 72%);
    pointer-events: none;
  }
  #heroTitle, #heroTag, #heroProof { color: #ECEAE4; }
  .hero-x { color: var(--accent-text); }
  .venture-grid { grid-template-columns: 1fr; }
  /* One column stacks each panel's copy vertically, and on a phone every
     panel — not just the closing one — can outgrow a short viewport. A pinned
     box taller than the viewport hides its own bottom: the next panel slides
     over content that is still on screen. So below the grid collapse the
     deck defaults to full-height slabs in normal flow; the min-height query
     after this block restores sticky on viewports tall enough to hold a whole
     panel. main.js gates the recede effects (scale, dim, rounding) on the
     same width+height pair, so they only run where the deck actually pins.
     Relative rather than static so each panel still paints over its
     neighbour's seam shadow whatever the source order says. */
  .venture {
    position: relative;
    height: auto;
    min-height: 100vh;
    padding-top: 64px;
    padding-bottom: 64px;
  }
  .venture-side { align-items: flex-start; padding: 0; gap: 20px; }
  .venture-meta, .venture-word { text-align: left; }
  /* One column, so the lockup turns around to overhang left instead of right
     and sits under the title it belongs to rather than opposite it. */
  .advisor-lockup { justify-content: flex-start; }
  .venture-word--advisor { right: auto; left: -.05em; }
  /* The stack stays centred once the grid collapses — a left-aligned fan with
     its outer cards bleeding off only one edge reads as broken, not deliberate. */
  .venture-side--sheets { align-items: center; gap: 0; }
  .sheets { gap: 14px; }
}
/* The nav breakpoint. 720px rather than a device width: it is where the pill
   stops fitting the header at a legible size, which is a property of the six
   labels, not of any phone. Gated on .has-js — with scripting off the drawer
   can never open, so the scrolling pill below stays as the fallback. */
@media (max-width: 720px) {
  .has-js #topNav { display: none; }
  .has-js #navToggle { display: inline-flex; }
}
@media (max-width: 600px) {
  /* Phone widths crop tighter, so the same focal point lands him further right;
     77% holds the face at ~55% of the frame from 360px to 430px wide. */
  #heroImg { object-position: 77% center; }
  #heroText { justify-content: flex-end; padding-bottom: 11vh; }
  #heroTitle { font-size: clamp(46px, 15vw, 68px); margin-bottom: 4vh; }
  .hero-last { margin-left: 11vw; }
  #heroTag { font-size: 21px; }
  /* The proof line is eight facts on one string; at 390px it needs to wrap as
     a small block rather than shrink to an unreadable single line. */
  #heroProof { margin-top: 16px; font-size: 11px; letter-spacing: .07em; }
  #siteHeader { padding: 10px 14px; }
  .slide-tab a { padding: 7px 11px; font-size: 11px; font-stretch: 80%; letter-spacing: .02em; }
  /* Section heads carry long compounds ("ENTREPRENEURSHIP"); at narrow widths
     Bricolage's tighter widths keep them on-canvas instead of clipping. */
  .t-display { font-stretch: 78%; }
  .venture-bullets { max-width: none; }
  /* On a phone this panel is still one 100vh box holding a title, three
     bullets, a button and the stack. Tightening the seams keeps all of it
     inside the panel on a 667px-tall screen instead of clipping. */
  .venture-grid { gap: 24px; }
  /* Same budget on the closing panel, where the figure is the tallest block in
     a now-single column. Capped so it cannot eat the button's room, and the
     wordmark comes down with it — at phone widths the lockup has to stay a
     lockup, so the word tracks the figure's size rather than holding the
     desktop step and swallowing him. */
  .advisor-figure { width: clamp(158px, 44vw, 196px); }
  /* The word has to stay wider than the figure or it disappears behind him and
     the lockup reads as a smudge. Roughly 1.6x his width, which is the ratio
     the full-size panel lands on. */
  .venture-word--advisor { font-size: clamp(72px, 24vw, 96px); bottom: 22%; }
  /* That panel now argues its case in four bullets under a lead — the most copy
     any bullet panel carries — above two buttons and the figure. Same trade the
     newsletter panel makes below: every seam gives back what it can so all of
     it stays inside the box on a short phone. */
  [data-advisor] .venture-title { margin-bottom: 20px; }
  [data-advisor] .venture-lead { margin: -8px 0 16px; font-size: 18px; }
  [data-advisor] .venture-bullets { margin-bottom: 26px; gap: 11px; }
  /* Same problem, harder: the newsletter column is a title, two paragraphs,
     three counts, a proof line and a button, above the stack. Every seam gives
     back what it can, and the detail paragraph — the one block the lead already
     summarises — gives back all of it. */
  .venture-title--nl { margin-bottom: 12px; }
  .nl-lead { margin-bottom: 10px; font-size: 18px; }
  .nl-desc { display: none; }
  .nl-stats { margin-bottom: 14px; padding-top: 14px; gap: 12px 28px; }
  .nl-stat-num { font-size: 24px; }
  .nl-join { margin-bottom: 16px; }
  /* Side-by-side pills leave no room to read what you typed; stack them. */
  .nl-form-fields { flex-direction: column; gap: 10px; margin-bottom: 12px; }
  .nl-input { padding: 11px 20px; }
  /* The footer's two columns have stacked by now, so the seams that separated
     them side by side are buying nothing. */
  .site-foot { gap: 60px; }
  .site-foot-top { padding-top: 80px; }
}

/* Short phones only: the two stacked pills stand ~76px taller than the single
   button they replaced, and the panel is a fixed 100vh box. The counts band is
   the one block the join line already paraphrases, so it gives its space back —
   same trade the detail paragraph made at 600px wide. */
@media (max-width: 600px) and (max-height: 700px) {
  .nl-stats { display: none; }
  .nl-join { margin-bottom: 10px; }
  /* The counts alone give back ~40px of the ~76; the rest comes a few px at a
     time from every remaining seam and the controls' vertical padding. */
  .venture-title--nl { margin-bottom: 8px; }
  .nl-lead { margin-bottom: 8px; }
  .nl-form-fields { gap: 8px; margin-bottom: 10px; }
  .nl-input { padding: 9px 20px; }
  .nl-form .nl-submit { padding: 11px 24px; }
  .venture:has(.nl) .venture-grid { gap: 14px; }
  /* The header is fixed and floats over these panels, so a panel that centres
     its content in the full 100vh centres it partly underneath the header. The
     wordmark panels were short enough to get away with it; a panel carrying the
     stack is not, so the centring band starts below the header instead.
     Padding only — the panel is border-box and stays exactly 100vh, which the
     sticky-stack maths in main.js depends on. */
  .venture { padding-top: 56px; }
  .sheets { gap: 10px; }
  /* The cards visibly bleed past both edges here, which says "swipe" more
     plainly than a caption can — so the hint gives its space back. */
  .sheets-hint { display: none; }
}

/* Sticky stack on phones. The slab fallback at the 900px query exists because
   a pinned panel taller than the viewport hides its own bottom; on viewports
   at least 700px tall the one-column panels can be made to fit, so the deck
   pins again and the stacking reads the same as desktop. Short phones
   (SE-class, 667px) and landscape viewports keep the normal-flow slabs — at
   those heights the closing panel cannot give back enough to fit a fixed
   box. 100vh rather than svh: on phones the browser chrome is collapsed
   while the user is mid-scroll — the moment the stacking is visible — so the
   large viewport is the one the seam has to meet; svh would let the next
   panel peek through a strip under a too-short pinned box. main.js skips its
   coverage effect on the complementary condition (max-width 900px and
   max-height 699px), so transform/--covered are only written where the deck
   actually pins.
   This block sits after the 600px and short-phone queries on purpose: in
   sticky mode the box is fixed at 100vh at every phone height, so the
   space-give-back trades those queries make only under 700px have to hold
   here unconditionally, and later-in-file is what lets these win. */
@media (max-width: 900px) and (min-height: 700px) {
  .venture {
    position: sticky;
    top: 0;
    height: 100vh;
    min-height: 0;
    padding-top: 56px;
    padding-bottom: 20px;
  }
  .venture-grid { gap: 14px; }
  /* The newsletter panel is the tallest: in a fixed box the blocks the join
     line and lead already paraphrase (counts, avatars, detail paragraph) give
     their space back at every sticky height, same trade the short-phone
     query makes. */
  .nl-stats, .nl-avatars, .nl-desc { display: none; }
  .venture-title--nl { margin-bottom: 8px; }
  .nl-lead { margin-bottom: 8px; }
  .nl-join { margin-bottom: 10px; }
  .nl-form-fields { gap: 8px; margin-bottom: 10px; }
  .nl-input { padding: 10px 20px; }
  .sheets { gap: 12px; }
  .sheets-hint { display: none; }
  /* Same trade data-compact makes: in a fixed box the summary costs more
     room than it earns, and it is the slack that keeps real-device font
     metrics from pushing the caption past the panel edge. */
  .sheets-caption-desc { display: none; }
  /* Closing panel: the figure cedes a step so the four bullets, two actions
     and the lockup all stay inside the box; the wordmark tracks it. */
  .advisor-figure { width: clamp(120px, 34vw, 168px); }
  .venture-word--advisor { font-size: clamp(60px, 20vw, 84px); }
  [data-advisor] .venture-title { margin-bottom: 12px; }
  [data-advisor] .venture-lead { margin: -8px 0 12px; }
  [data-advisor] .venture-bullets { margin-bottom: 14px; gap: 8px; }
  .venture-side--advisor { gap: 10px; }
  .venture-actions { gap: 10px; }
}
/* Narrowest sticky phones (Galaxy-class, 360px wide): the four advisor
   bullets wrap a line further than they do at 390px, which alone pushes the
   closing panel past the fixed box. The bullets step down a size and the
   figure cedes one more step; every other panel already fits at this width. */
@media (max-width: 380px) and (min-height: 700px) {
  [data-advisor] .venture-lead { font-size: 17px; }
  [data-advisor] .venture-bullets { font-size: 14px; }
  .advisor-figure { width: clamp(110px, 30vw, 140px); }
  .venture-word--advisor { font-size: clamp(54px, 18vw, 72px); }
}

/* Lenis smooth scroll.
   Lenis drives the scroll position itself, so the native smooth behaviour has
   to step aside while it's running — otherwise the two fight over anchor jumps.
   These rules only apply once JS adds .lenis to <html>. */
html.lenis, html.lenis body { height: auto; }
html.lenis { scroll-behavior: auto; }
.lenis:not(.lenis-autoToggle).lenis-stopped { overflow: clip; }
.lenis [data-lenis-prevent],
.lenis [data-lenis-prevent-wheel],
.lenis [data-lenis-prevent-touch] { overscroll-behavior: contain; }
.lenis.lenis-smooth iframe { pointer-events: none; }

/* Scroll lock for the open drawer. With Lenis running its own `.lenis-stopped`
   rule above does this; this covers the reduced-motion path, where Lenis never
   starts and there is nothing to stop. */
body.nav-open { overflow: hidden; }

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .mq { animation-play-state: paused !important; }
  .slide-tabs-cursor { transition: none; }
  [data-reveal] { opacity: 1; transform: none; transition: none; }
  /* The hero copy is already in place — it just doesn't travel to get there. */
  #heroTitle, #heroTag, #heroProof { animation: none; }
  /* The drawer still opens and closes — it just arrives rather than slides. */
  #navDrawer, #navScrim, .nav-toggle-bars i { transition: none; }
}
