/* ===========================================================
   SYDRTH — portfolio v2
   Faithful to mock: cream pill nav, frosted glass card,
   "Human-first / AI-second" → "Hello, I am Siddharth" arc.
   Fonts: Canela (display) + Google Sans (UI/body)
   =========================================================== */

/* ---------- Local fonts (commercial — keep in assets/fonts/) ---------- */
@font-face {
  font-family: 'Canela';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('assets/fonts/Canela-Light.woff2') format('woff2');
}
@font-face {
  font-family: 'Canela';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('assets/fonts/Canela-Regular.woff2') format('woff2');
}
@font-face {
  font-family: 'Canela';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('assets/fonts/Canela-Medium.woff2') format('woff2');
}
@font-face {
  font-family: 'Google Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('assets/fonts/GoogleSans-Regular.woff2') format('woff2');
}
@font-face {
  font-family: 'Google Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('assets/fonts/GoogleSans-Medium.woff2') format('woff2');
}
@font-face {
  font-family: 'Google Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('assets/fonts/GoogleSans-Bold.woff2') format('woff2');
}

:root {
  /* Palette pulled from the video footage */
  --bg-deep:    #0a1220;     /* fallback when video isn't rendered */
  --blue-deep:  #1a4769;     /* dominant blue from mock */
  --blue-mid:   #2f678c;
  --steel:      #d4d9df;
  --white:      #ffffff;
  --pale-blue:  #b8d3e6;     /* "AI-second" tint */
  --steel-dim:  #8a96a6;
  --steel-faint:#3b475b;

  /* Pill nav cream */
  --cream:      #f0e6d4;
  --cream-soft: #e8dbc4;
  --ink:        #14253a;     /* dark text on cream */

  /* Type — wordmark uses Helvetica per spec, body uses Google Sans, display uses Canela */
  --display:  'Canela', 'Times New Roman', serif;
  --sans:     'Google Sans', system-ui, sans-serif;
  --wordmark: 'Helvetica Neue', Helvetica, 'Arial Black', sans-serif;

  --ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-cinema:   cubic-bezier(0.65, 0, 0.35, 1);
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* ===== ASSET PROTECTION (FRICTION LAYER) =====
   Discourages casual saving of images and video via right-click /
   drag-to-desktop / long-press save menus. This does NOT prevent a
   determined user with browser DevTools from accessing the files —
   that's not technically possible in any web browser. But it stops
   most accidental "save image as" attempts and signals intent.

   Protection scope:
     - All <img> elements (chapter media, portrait, wordmarks)
     - The hero <video>
     - Background images applied via CSS (poster fallback)

   Note: pointer-events stays default on <img> so click handlers on
   parent links (e.g. wordmark <a>) still work. We protect via drag
   prevention, user-select disable, and the script-level right-click
   block in index.html. */
img,
video {
  /* Block native drag-to-desktop on images and the video element. */
  -webkit-user-drag: none;
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  /* iOS/Safari: disable the long-press image preview/save menu. */
  -webkit-touch-callout: none;
}

html {
  scroll-behavior: smooth;
  /* Proximity snap (not mandatory). Mandatory hijacks scrolls inside
     the stage's 500vh scrub and jumps straight to My Work. Proximity
     only kicks in when the user pauses near a snap target — inside
     the stage there are no snap targets so scrubbing works free, but
     at section boundaries (work-intro / each chapter) the browser
     gently glides into place. */
  scroll-snap-type: y proximity;
}

/* Snap targets — one stop per content "page" after the intro stage. */
.work-intro-panel,
.chapter {
  scroll-snap-align: start;
}

body {
  background: var(--bg-deep);
  color: var(--white);
  font-family: var(--sans);
  font-weight: 400;
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  cursor: default;
}

img, video { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
em { font-style: italic; }

/* ========= WORDMARK (top-left, scales on scroll) =========
   Now an image asset (assets/sydrth-logo.png). The <a> remains the
   transform target — JS still writes --wordmark-scale on this element
   during phase 4, and transform-origin: top left keeps the mark
   anchored to the corner as it shrinks. The <img> inside is sized by
   height; width follows from the asset's intrinsic aspect ratio. */
.wordmark {
  position: fixed;
  top: 36px;
  left: 48px;
  z-index: 60;
  display: inline-block;
  transform-origin: top left;
  transform: scale(var(--wordmark-scale, 1));
  will-change: transform, opacity;
  /* Keep the link sized exactly to its image — no inherited line-box quirks. */
  line-height: 0;
  /* Smoothly fade out when user enters work section (is-hidden added by JS) */
  transition: opacity 0.5s ease;
}

.wordmark.is-hidden {
  opacity: 0;
  pointer-events: none;
}

.wordmark__img {
  display: block;
  /* Height-driven sizing to roughly match the previous text wordmark's
     visual weight. Width follows from aspect ratio (~2.94:1). */
  height: clamp(60px, 9.5vw, 140px);
  width: auto;
  /* Ensure crisp scaling on retina; no smoothing artefacts when the
     transform shrinks it during phase 4. */
  user-select: none;
  -webkit-user-drag: none;
}

/* ========= PILL NAV (top-right) — dark glass with cream CTA inside ========= */
.pill-nav {
  position: fixed;
  top: 36px;
  right: 48px;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 4px;
  /* Frosted glass — a near-neutral tint (slight cool cast) at higher
     opacity so the pill reads as luminous against navy. The previous
     navy-tinted fill made it read as "dark blue translucent panel"
     instead of "frosted glass". Reference: white-leaning frost. */
  background: rgba(255, 255, 255, 0.10);
  backdrop-filter: blur(20px) saturate(1.4);
  -webkit-backdrop-filter: blur(20px) saturate(1.4);
  border-radius: 999px;
  padding: 6px;
  /* Glass edge: brighter top highlight + soft hairline border */
  border: 1px solid rgba(255, 255, 255, 0.22);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.35),
    inset 0 -1px 0 rgba(0, 0, 0, 0.10),
    0 8px 28px rgba(0, 0, 0, 0.20);
  font-family: var(--sans);
  /* Smoothly fade in/out when phase 1 ends — see .is-hidden below */
  transition: opacity 0.5s ease, transform 0.5s ease;
}

/* Zero-state: wordmark only. JS removes this class when stage exits phase 1. */
.pill-nav.is-hidden {
  opacity: 0;
  pointer-events: none;
  transform: translateY(-8px);
}

.pill-nav__items {
  display: flex;
  list-style: none;
  align-items: center;
  gap: 0;
}

.pill-nav__link {
  position: relative;
  display: inline-block;
  padding: 10px 22px;
  font-size: 15px;
  font-weight: 400;
  color: rgba(255, 255, 255, 0.82);
  border-radius: 999px;
  transition: color 0.3s var(--ease-out-soft);
}

.pill-nav__link:hover { color: var(--white); }

/* Active state — short underline beneath the label, white */
.pill-nav__link.is-active {
  color: var(--white);
  font-weight: 500;
}
.pill-nav__link.is-active::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 4px;
  width: 18px;
  height: 1.5px;
  background: var(--white);
  transform: translateX(-50%);
  border-radius: 2px;
}

/* The CTA — cream pill nested inside the dark glass pill */
.pill-nav__cta {
  display: inline-flex;
  align-items: center;
  /* Tighter spacing between text and arrow */
  gap: 8px;
  /* Asymmetric padding: more on the left (where text starts), less on
     the right (where arrow sits closer to the edge). */
  padding: 10px 18px 10px 24px;
  margin-left: 6px;
  background: var(--cream);
  color: var(--ink);
  border-radius: 999px;
  font-size: 15px;
  font-weight: 500;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.6),
    0 1px 2px rgba(0, 0, 0, 0.08);
  transition: background 0.3s var(--ease-out-soft), transform 0.3s var(--ease-out-soft);
}

.pill-nav__cta:hover {
  background: #f5e9d0;
  transform: translateY(-1px);
}

.pill-nav__arrow { display: inline-block; }

/* ========= STAGE (scroll-pinned hero) ========= */
.stage {
  position: relative;
  /* z-index 1 so the stage renders ABOVE the .work__glow fixed
     gradient (z-index 0). Without this explicit z-index, the fixed
     gradient at z-index 0 wins over the stage's "auto" z-index, and
     paints on top of the video. */
  z-index: 1;
  /* Was 500vh — Sid flagged that both held frames ("Human-first.
     AI-second." Hold A and "Hello, I am Siddharth" Hold B) felt too
     fast on scroll. Bumped to 650vh to give every phase more pixels
     of scroll distance; the new phase boundaries in script.js skew
     the extra budget toward the two holds (P3 and P5). */
  height: 650vh;
  width: 100%;
  /* Opaque navy — covers the .work__glow gradient layer (z-index 0)
     during intro phases. Phase 6 fades this stage's opacity to 0,
     revealing the gradient underneath. */
  background: var(--bg-deep);
}

.stage__sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  width: 100%;
  overflow: hidden;
}

/* ===== PAGE LOADER =====
   Full-screen overlay shown until the hero video has buffered enough
   to scrub smoothly. Subtle aesthetic per Sid's spec: just a small
   "LOADING" wordmark in the same vocabulary as the SCROLL hint, on
   the same navy backdrop the stage uses. Fades out once JS confirms
   the video is ready (loadeddata + >=3s buffered, or 8s fallback
   timeout — whichever comes first). */
.page-loader {
  position: fixed;
  inset: 0;
  z-index: 999;          /* above everything except dev panels */
  background: var(--bg-deep);
  display: flex;
  align-items: center;
  justify-content: center;
  /* Smooth fade out once the video is ready. */
  transition: opacity 0.5s var(--ease-out-soft),
              visibility 0s linear 0.5s;
  opacity: 1;
  visibility: visible;
}

/* When body has finished loading, drop the loader. visibility delay
   matches the opacity transition so the element doesn't capture
   pointer events during the fade. */
body:not(.is-loading) .page-loader {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.page-loader__label {
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.32em;
  /* Optical balance — wide letter-spacing pushes mass right of center. */
  padding-left: 0.32em;
  /* Same desaturated steel-teal as the SCROLL hint for visual
     consistency between affordances. */
  color: rgba(184, 211, 230, 0.55);
  /* Gentle pulse so the label feels alive (it IS waiting on something).
     1.6s loop at low contrast — reads as "in progress" without being
     distracting. */
  animation: page-loader-pulse 1.6s var(--ease-out-soft) infinite;
}

@keyframes page-loader-pulse {
  0%, 100% { opacity: 0.4; }
  50%      { opacity: 1; }
}

/* Lock scroll on body while loading — prevents user scroll-attempts
   from racing the video buffer. */
body.is-loading {
  overflow: hidden;
  height: 100vh;
  height: 100dvh;
}

/* Reduced motion: keep the label visible but skip the pulse. */
@media (prefers-reduced-motion: reduce) {
  .page-loader__label { animation: none; opacity: 0.7; }
}

/* ===== SCROLL HINT =====
   Subtle scroll affordance shown during the stage's zero-state.
   Sits bottom-center of the sticky viewport so it travels with the
   video. Fades to 0 the moment the user begins scrolling — script.js
   toggles `.is-faded` based on stage scroll progress. Once faded,
   stays hidden for the rest of the visit. */
.stage__scroll-hint {
  position: absolute;
  /* Bottom-center of the viewport. The 56px clearance keeps it above
     the bottom edge so it doesn't kiss anything; large enough to feel
     intentional without crowding the wordmark or pill nav. */
  left: 50%;
  bottom: 56px;
  transform: translateX(-50%);
  /* Stack: pill mouse glyph above, label below. */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  /* Quiet on the dark navy — desaturated steel-teal that reads as a
     UI hint rather than competing with the wordmark or any layer text. */
  color: rgba(184, 211, 230, 0.55);
  /* Above the video and vignette but below the layer text. */
  z-index: 4;
  pointer-events: none;
  /* Initial fade-IN so it doesn't pop on page load — eases up to full
     opacity over the first ~600ms of viewing. */
  opacity: 0;
  animation: scroll-hint-fade-in 0.8s 0.4s var(--ease-out-soft) forwards;
  transition: opacity 0.5s var(--ease-out-soft),
              transform 0.5s var(--ease-out-soft);
}

/* Fade-out state: triggered by JS the moment the user starts scrolling.
   Once applied, the hint never returns — even if the user scrolls back
   to the top, this class stays on. */
.stage__scroll-hint.is-faded {
  opacity: 0 !important;
  /* Tiny downward drift on fade-out — feels like the hint is "going
     away" because the user did the thing it asked for. */
  transform: translateX(-50%) translateY(8px);
  /* Disable the dot animation once hidden so it's not running invisibly. */
  animation: none;
}

/* Variant for the My Work intro panel — same visual hint, but
   anchored inside the panel rather than the stage's sticky container.
   JS toggles .is-faded based on whether the panel is on screen. */
.stage__scroll-hint--work {
  /* Pinned to the bottom of the My Work panel rather than the
     viewport; appears when the panel is in view and fades when the
     user continues scrolling toward chapter 1. */
  z-index: 5;  /* above curves and any panel chrome */
  /* Re-apply the fade-in animation specifically to this instance —
     the parent rule already declares it but we want it to start
     when the panel becomes visible, which JS controls via classes. */
}

@keyframes scroll-hint-fade-in {
  to { opacity: 1; }
}

/* The pill outline ("mouse" body) — slim 1px stroke, rounded ends. */
.stage__scroll-hint-mouse {
  position: relative;
  width: 26px;
  height: 42px;
  border: 1.5px solid currentColor;
  border-radius: 14px;
}

/* The animated dot inside the pill. Drifts down on a 1.6s loop to
   suggest "scroll downward". Pure CSS, no JS. */
.stage__scroll-hint-dot {
  position: absolute;
  left: 50%;
  top: 8px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
  transform: translateX(-50%);
  animation: scroll-hint-dot 1.6s var(--ease-out-soft) infinite;
}

@keyframes scroll-hint-dot {
  /* Start near the top, drift down, fade out near the bottom, reset. */
  0%   { transform: translate(-50%, 0); opacity: 0; }
  20%  { opacity: 1; }
  70%  { transform: translate(-50%, 16px); opacity: 1; }
  100% { transform: translate(-50%, 22px); opacity: 0; }
}

.stage__scroll-hint-label {
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.32em;
  /* Optical balance — wide letter-spacing pushes mass right of center. */
  padding-left: 0.32em;
}

/* Reduced motion: keep the hint visible but stop animating the dot. */
@media (prefers-reduced-motion: reduce) {
  .stage__scroll-hint {
    animation: none;
    opacity: 1;
  }
  .stage__scroll-hint-dot { animation: none; }
}

.stage__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* CSS-level poster fallback. If the video file fails to decode (which
     can happen on file:// protocol in some browsers), this poster image
     is still shown as a background-image. When the video DOES load,
     its frames paint on top. */
  background-image: url('assets/hero-poster.jpg');
  background-size: cover;
  background-position: center;
  filter: blur(40px) saturate(0.7) brightness(0.75);
  transform: scale(1.1);
  will-change: filter, transform;
}

.stage__vignette {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(to bottom, rgba(10, 18, 32, 0.25) 0%, transparent 30%, transparent 70%, rgba(5, 12, 24, 0.6) 100%);
}

/* ========= STAGE LAYERS ========= */
.stage__layer {
  position: absolute;
  inset: 0;
  z-index: 3;
  opacity: 0;
  pointer-events: none;
  will-change: opacity;
}

/* ----- LAYER A: Human-first / AI-second + glass card ----- */
.stage__layer--a {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: end;
  /* Equal padding on left, right, and bottom so both children
     read as equidistant from the viewport edges. */
  padding: 0 48px 48px;
  gap: 40px;
}

.hero-a__title {
  font-family: var(--display);
  font-weight: 300;
  /* Bumped per Sid's mock 92.png — title fills more of the lower-left
     quadrant. Range cap raised so on big monitors (1920px+) it doesn't
     look small relative to the video subject. */
  font-size: clamp(2.4rem, 7.4vw, 7.2rem);
  line-height: 1.02;             /* slightly looser to give the f-tail room visually */
  letter-spacing: -0.025em;
  color: var(--white);
  max-width: 16ch;
  font-style: normal;            /* "Human-first." stays upright */
}

.hero-a__title em {
  font-style: normal;            /* both lines upright per mock */
  color: var(--pale-blue);
}

/* Frosted glass card — matches mock spec.
   Visible 1px hairline border on all four sides (mock shows it equally
   bright on every edge — no top-heavy inset highlight). Card body lifts
   the backdrop slightly via low-opacity white + saturation bump from
   backdrop-filter. */
/* --- "Lead UX Designer / at google" callout ---
   Per Sid's update: glass tile chrome (background, backdrop-filter,
   border, shadow) is stripped — just naked text, right-aligned,
   pinned to the lower-right of layer A. The previous tile aesthetic
   was making the moment feel boxed-in; the bare typography reads
   cleaner against the video. */
.glass-card {
  position: relative;
  align-self: end;
  /* Padding zeroed — card-as-container is gone, this is just a text
     block now. Bottom/right anchoring still comes from the parent
     `.stage__layer--a` grid's padding and align/justify-self below. */
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: none;
  /* Both lines right-aligned per mock. */
  text-align: right;
  color: var(--white);
}

.glass-card__title {
  font-family: var(--sans);
  font-weight: 700;                            /* Bold — heaviest weight loaded */
  /* Bumped per mock 92.png — "LEAD UX / DESIGNER" reads as a major
     piece of typography in the lower-right, not a small badge. Was
     clamp(1.7rem, 2.4vw, 2.3rem) which capped at ~37px. New range
     scales to ~50-55px on a 1440 viewport. */
  font-size: clamp(1.9rem, 3.6vw, 3.4rem);
  line-height: 1.05;
  letter-spacing: -0.01em;                     /* tighter — adds visual heft to compensate */
  text-transform: uppercase;
  color: var(--white);
  text-align: right;
}

.glass-card__sub {
  margin-top: 14px;
  font-family: var(--sans);
  font-weight: 700;                            /* bold per latest spec */
  /* Bumped along with title to maintain proportion (~0.45x of title). */
  font-size: clamp(1.05rem, 1.55vw, 1.5rem);
  letter-spacing: 0.06em;                       /* slightly more open per mock */
  text-transform: uppercase;
  /* Desaturated pale-blue-gray, sits quietly against the video */
  color: rgba(216, 226, 236, 0.55);
  text-align: right;
}

/* ----- LAYER B: "Hello, I am Siddharth." + intro paragraph
        Right-side stacked. Title and body both pinned to right column,
        body sits directly under title. Figure occupies left half. */
.stage__layer--b {
  display: flex;
  align-items: center;
  /* Per Sid's latest mock: the title block (now sized to ~393×352 on a
     ~1440 viewport) sits at the right edge with a modest gutter. The
     mock measurement puts the right edge of the block ~80-100px from
     the viewport's right edge — let's keep 100px gutter so the larger
     title block doesn't crowd the viewport edge. */
  justify-content: flex-end;
  padding: 0 100px 0 0;
}

.stage__layer--b .hero-b {
  /* Mock 2 (annotated screenshot): title block measures 393px wide on
     a ~1440px viewport — that's ~27% width. With the new -10% title
     size (per Sid v55 ask), 380px is the snug width: "Hello, I am"
     at ~93px renders ~382px, which fits in 380 with single-pixel
     headroom; "Siddharth." at ~93px renders ~340px, fits with room
     to spare. Was min(28%, 420). */
  width: min(26%, 380px);
}

.hero-b {
  /* Wrapper that holds title above body, both left-aligned within the column. */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;                                 /* tighter title→body gap per mock */
  width: min(34%, 480px);
}

.hero-b__title {
  font-family: var(--display);
  font-weight: 300;
  /* Sid v55: reduce title -10% from prior clamp(2.5rem, 7vw, 6.5rem).
     Math: 2.5×0.9=2.25rem, 7×0.9=6.3vw, 6.5×0.9=5.85rem. At a 1440
     viewport that lands at 90.7px (vs 100.8px before). At the wider
     6.5rem ceiling (104px) the title was overflowing the tight block
     Sid wants — the new 5.85rem (93.6px) ceiling fits the 380px
     container with breathing room. */
  font-size: clamp(2.25rem, 6.3vw, 5.85rem);
  line-height: 1.0;
  letter-spacing: -0.025em;
  color: var(--pale-blue);
  font-style: normal;          /* upright per mock */
  /* Defensive: never let the title be wider than its container, even
     if a single word's natural width exceeds the column at certain
     viewport sizes. With the lighter ceiling above this shouldn't
     trigger, but it's a no-cost belt-and-braces against future
     ceiling drift. */
  max-width: 100%;
  overflow-wrap: break-word;
}

.hero-b__title em {
  font-style: normal;          /* keep upright, no italic */
  color: var(--white);         /* "Siddharth." — white per latest mock */
}

.hero-b__intro {
  font-family: var(--sans);
  font-weight: 400;
  font-size: clamp(1rem, 1.2vw, 1.2rem);
  line-height: 1.5;
  /* Sid v55: body opacity dropped to 80% (from 82%). The -2% looks
     marginal but reads visibly more recessed against the navy/blue
     stage backdrop, letting the title carry more visual weight. */
  color: rgba(255, 255, 255, 0.80);
  /* Per Sid's annotated mock: the body paragraph occupies the SAME
     horizontal extent as the title block above — both sit inside the
     selection box. Width:100% locks body to the parent .hero-b column
     (now 380px capped). */
  width: 100%;
  max-width: 100%;
}

/* Split text — words start hidden, JS reveals.
   Wrapper uses padding + clip-path instead of overflow:hidden so that
   descenders, italic serif tails (the "f" in "first"), and any glyph
   that excurses outside the em-box don't get cropped. The clip-path
   masks during reveal but releases at rest so glyphs render in full. */
[data-split="words"] .word {
  display: inline-block;
  vertical-align: top;
  /* Generous vertical padding so glyph excursions never touch the box edges */
  padding: 0.18em 0 0.22em;
  margin: -0.18em 0 -0.22em;
  /* clip-path masks the inner during the reveal; once the inner has
     translated to y:0%, this still sits beyond the visible glyph and
     no longer crops anything. */
  clip-path: inset(0);
  -webkit-clip-path: inset(0);
}
[data-split="words"] .word-inner {
  display: inline-block;
  transform: translateY(110%);
  opacity: 0;
}

/* ========= WORK SECTION
     Backdrop architecture:
     - .work__grid is the faint grid overlay.
     - .work__glow is the bottom gradient — pale-blue light rising from
       both bottom corners, with a darker valley running down the
       center. Sampled directly from the 34.png reference.
     - Both are POSITION: FIXED so they stay locked to viewport.
     - Cards live in normal scroll flow.
   ========================================================= */
.work {
  position: relative;
  /* No background — the .work__glow fixed layer (z-index 0) is what
     paints behind the work section. Setting a background here would
     override the gradient. */
  background: transparent;
  padding: 0;
  /* NOTE: perspective lives on each .chapter, not here, so position:
     fixed children stay anchored to the viewport instead of being
     trapped inside this element's transform context. */
}

/* Grid is baked into work-bg.jpg — separate overlay disabled. */
.work__grid { display: none; }

/* The 34.png gradient — POSITION FIXED, ALWAYS PAINTED at opacity 1
   from page load. During intro phases the stage's solid background
   covers it (we never see it). During phase 6 the stage fades to
   opacity 0, revealing the gradient underneath instantly — no fade-in
   delay, no navy strip. After phase 6, the gradient is the only thing
   between viewport and body, so it shows through across My Work and
   all chapters. */
.work__glow {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image: url('assets/work-bg.jpg');
  background-size: cover;
  background-position: center bottom;
  background-repeat: no-repeat;
  opacity: 1;
}

/* ===== "MY WORK" INTRO PANEL ===== */
.work-intro-panel {
  position: relative;
  z-index: 1;
  height: 100vh;
  min-height: 720px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Clip the curve animations to the panel's bounds — without this, a
     curve drifting near the panel's bottom edge can peek into the
     chapter 1 section during scroll transitions. */
  overflow: hidden;
}

/* ===== Math curves layer =====
   Five trail-follower curves at scattered fixed positions around the
   title. Each .curve hosts an SVG (built by curves.js) running its
   own particle-trail animation. Containers fade in/out + drift in the
   anticlockwise tangent direction over each cycle, with motion blur
   peaking on the fade edges (suggests inertia — the curve is "moving"
   even though it's only translating slightly). Hover snaps to white. */
/* Curves are POSITION FIXED — they don't scroll with the page. They
   appear after a brief delay when user lands on My Work, and fade out
   (fizzle) before chapter 1 emerges. JS toggles `.is-curves-on` on
   body to drive the master enter/leave fade. */
/* Curves removed v53 — keeping CSS commented in case reinstated.
   Forced display:none above any other rule guarantees no render path
   even if markup were re-added or curves.js were re-included. */
.curves { display: none !important; }

.curves {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  /* Pale-blue color flows through to <path stroke="currentColor"> and
     particle <circle fill="currentColor"> inside each curve's SVG. */
  color: rgb(184, 211, 230);
  /* Master visibility — JS toggles body.is-curves-on to fade in/out */
  opacity: 0;
  transition: opacity 0.6s ease-out;
}

body.is-curves-on .curves {
  opacity: 1;
}

/* Collective motion — the whole group of 5 curves rotates anticlockwise
   around the panel's center as a unit. Slow (90s/revolution) so the
   eye reads it as ambient drift rather than active spinning. */
.curves__group {
  position: absolute;
  inset: 0;
  animation: curves-group-rotate 90s linear infinite;
  transform-origin: 50% 50%;
}

@keyframes curves-group-rotate {
  from { transform: rotate(0deg); }
  to   { transform: rotate(-360deg); }   /* negative = anticlockwise */
}

.curve {
  position: absolute;
  /* --x and --y are positions in % of the panel, set inline per curve */
  left: var(--x);
  top: var(--y);
  width: var(--size, 200px);
  height: var(--size, 200px);
  /* Cycle handles BOTH centering translate AND in-place wobble together.
     Total cycle = 20s. Visible window ~25% (5s) so most of the time the
     curve is hidden — matches reel where 1-2 max are lit at any moment. */
  animation: curve-cycle 20s ease-in-out var(--fade-delay, 0s) infinite;
  opacity: 0;
  /* pointer-events DEFAULT off — only enabled while curve is visible
     enough for a user to actually see it. JS animates a CSS variable
     that flips this on near peak opacity. Even simpler trick: set
     `pointer-events: auto` on a visible-only state (.is-visible) we
     toggle via JS based on the cycle phase. We do that below. */
  pointer-events: none;
  /* Smooth transition for the hover snap */
  transition: filter 0.25s ease, color 0.25s ease;
}

/* Single keyframe combining: opacity fade + centering + in-place wobble.
   Wobble is a tiny rotation that runs throughout the visible window —
   creates the "alive" feel from the reel where each sketch shimmers
   subtly even while standing still. Most of the cycle is invisible.
   pointer-events animates as discrete steps so hover only works while
   the curve is actually visible. */
@keyframes curve-cycle {
  0% {
    opacity: 0;
    transform: translate(-50%, -50%) rotate(-3deg) scale(0.96);
    filter: blur(4px);
    pointer-events: none;
  }
  8% {
    pointer-events: none;
  }
  10% {
    opacity: 0.9;
    transform: translate(-50%, -50%) rotate(-1deg) scale(0.99);
    filter: blur(0.8px);
    pointer-events: auto;
  }
  20% {
    opacity: 0.9;
    transform: translate(-50%, -50%) rotate(0deg) scale(1);
    filter: blur(0);
    pointer-events: auto;
  }
  30% {
    opacity: 0.9;
    transform: translate(-50%, -50%) rotate(2deg) scale(1.01);
    filter: blur(0);
    pointer-events: auto;
  }
  35% {
    pointer-events: none;
  }
  40% {
    opacity: 0;
    transform: translate(-50%, -50%) rotate(3deg) scale(0.98);
    filter: blur(4px);
  }
  100% {
    opacity: 0;
    transform: translate(-50%, -50%) rotate(0deg) scale(0.96);
    filter: blur(4px);
  }
}

/* Hover: snap stroke + particles to white, add a soft glow halo,
   pause both the cycle and the group rotation so the hover target
   stays still. */
.curve:hover {
  color: rgb(255, 255, 255);
  filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.55))
          drop-shadow(0 0 18px rgba(255, 255, 255, 0.35)) !important;
  animation-play-state: paused;
  opacity: 1 !important;
}

/* When any curve is hovered, also pause the parent group rotation */
.curves__group:has(.curve:hover) {
  animation-play-state: paused;
}

.work-intro-panel__inner {
  position: relative;
  /* Above curves */
  z-index: 2;
  text-align: center;
  max-width: 720px;
  padding: 0 40px;
}

.work-intro-panel__title {
  font-family: var(--display);
  font-weight: 300;
  font-size: clamp(4.5rem, 11vw, 11rem);
  line-height: 1;
  letter-spacing: -0.025em;
  color: var(--white);
  margin-bottom: 28px;
}

.work-intro-panel__note {
  font-family: var(--sans);
  /* Bigger & brighter for proper visibility against the gradient */
  font-size: clamp(1.05rem, 1.25vw, 1.18rem);
  line-height: 1.55;
  font-weight: 400;
  color: rgba(216, 226, 236, 0.85);    /* ~85% pale-blue-white */
  max-width: 64ch;
  margin: 24px auto 0;
}

/* ===== CHAPTER CARDS =====
   Each card enters with a 3D tilt that flattens as it comes into view —
   like the card is being pulled forward through space. */
.chapter {
  position: relative;
  z-index: 1;
  /* Single viewport per chapter. Each chapter card now strictly fits
     inside one viewfold so snap-scroll feels precise and consistent
     across all four chapters regardless of media-vs-empty state.
     Using `dvh` so mobile browser-chrome doesn't push content out of
     view; falling back to `vh` for older browsers. */
  height: 100vh;
  height: 100dvh;
  /* Floor so the card has room on very short viewports (rare, but
     covers things like split-screen on Surface tablets). */
  min-height: 640px;
  padding: 60px 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Perspective on each chapter so we can tilt the inner card */
  perspective: 2000px;
}

.chapter__card {
  position: relative;
  width: 100%;
  max-width: 1480px;
  /* Asymmetric padding: top + sides for the header, ZERO bottom so
     the media zone bleeds flush to the card's bottom border. The
     media's negative side-margins already swallow the 56px side
     padding; with bottom padding gone, the image hits all three of
     left, right, bottom edges and only the top of the card holds
     header content. Per Sid's spec: "extend the image to the bottom
     of the card, don't change header." */
  padding: 40px 56px 0;
  /* Card now uses flex-column so the bottom media zone can absorb
     all leftover height, ensuring the entire card fits within one
     viewfold no matter what's inside (image vs "Under construction"). */
  display: flex;
  flex-direction: column;
  /* Card fills its parent .chapter exactly. Section padding (60+60)
     is the only thing sitting outside it. */
  height: 100%;
  background: rgba(255, 255, 255, 0.04);
  backdrop-filter: blur(14px) saturate(1.15);
  -webkit-backdrop-filter: blur(14px) saturate(1.15);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 0;
  box-shadow:
    inset 1.5px 1.5px 0 rgba(255, 255, 255, 0.16),
    inset 0 -1px 0 rgba(0, 0, 0, 0.08),
    0 12px 40px rgba(0, 0, 0, 0.18);
  overflow: hidden;
  transform-style: preserve-3d;
  /* Cursor-tracking glow lives here */
}

/* Cursor-following spotlight inside the card — coords driven by JS */
.chapter__card::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(
    420px circle at var(--mx, 50%) var(--my, 50%),
    rgba(184, 211, 230, 0.10),
    transparent 60%
  );
  opacity: 0;
  transition: opacity 0.5s var(--ease-out-soft);
  pointer-events: none;
  z-index: 2;
}
.chapter__card:hover::before {
  opacity: 1;
}

/* A second highlight that catches the upper-left edge — like rim light */
.chapter__card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    135deg,
    rgba(255, 255, 255, 0.06) 0%,
    transparent 30%
  );
  pointer-events: none;
  z-index: 1;
}

.chapter__head {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: 80px;
  margin-bottom: 56px;
  align-items: start;
  position: relative;
  z-index: 3;
  /* Natural height — header doesn't compete with media for the leftover
     space in the flex column. The media zone below uses flex: 1 to
     absorb whatever remains. */
  flex: 0 0 auto;
}

.chapter__col--copy {
  padding-top: 38px;
  display: flex;
  flex-direction: column;
  gap: 36px;
  align-items: flex-start;
}

.chapter__eyebrow {
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--steel-dim);
  margin-bottom: 24px;
}

.chapter__title {
  font-family: var(--display);
  font-weight: 300;
  font-size: clamp(2.2rem, 4.6vw, 4.4rem);
  line-height: 1.02;
  letter-spacing: -0.02em;
  color: var(--white);
  max-width: 14ch;
}

.chapter__body {
  font-family: var(--sans);
  font-size: clamp(1rem, 1.15vw, 1.15rem);
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.78);
  max-width: 50ch;
}

/* CTA group: button on the LEFT, lock + microcopy aside on the RIGHT.
   Per Sid's directive: don't increase the vertical height of the CTA
   row, so the aside sits next to the button rather than beneath it.
   Wraps to a new line only at narrow widths (mobile). */
.chapter__cta-group {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px 20px; /* row-gap (only matters on wrap) | column-gap */
}

/* The lock + microcopy unit, sitting to the right of the button. Lock
   icon is a small inline glyph; the text is a short reassurance line.
   Both align centered with the button's vertical center. */
.chapter__cta-aside {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  /* Cap width so the microcopy doesn't push the row absurdly wide on
     huge viewports; on narrow ones it'll wrap to its own line via
     flex-wrap above. */
  max-width: 32ch;
}

.chapter__cta {
  display: inline-flex;
  align-items: center;
  /* Slightly tighter gap (label | arrow) now that the lock icon is
     no longer part of the CTA per the new mocks. */
  gap: 8px;
  /* Asymmetric: more left, less right so arrow sits close to the edge */
  padding: 14px 22px 14px 24px;
  border-radius: 999px;
  background: rgba(20, 37, 58, 0.5);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: var(--white);
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 500;
  text-decoration: none;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.12),
    0 2px 8px rgba(0, 0, 0, 0.16);
  transition: background 0.3s var(--ease-out-soft), transform 0.3s var(--ease-out-soft);
}
.chapter__cta:hover {
  background: rgba(35, 60, 90, 0.65);
  transform: translateY(-1px);
}

.chapter__cta-arrow {
  display: inline-block;
  transition: transform 0.3s var(--ease-out-soft);
}
.chapter__cta:hover .chapter__cta-arrow {
  transform: translate(2px, -2px);
}

/* Lock icon — sits inside the aside, to the left of the microcopy.
   Slightly dim so it reads as auxiliary info ("this is gated") rather
   than as a button. Color matches the microcopy text for visual unity. */
.chapter__cta-lock {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Match the microcopy's tone — both register as the same visual
     unit (icon + label) rather than competing weights. */
  color: rgba(216, 226, 236, 0.65);
  flex: 0 0 auto;
}

/* Microcopy text — sits to the right of the lock. Light, small,
   single line. White-space allowed to wrap inside the aside if the
   container is tight, but max-width on the parent caps it. */
.chapter__cta-note {
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.01em;
  /* Same tone as the lock — they read as one quiet group next to the
     prominent button. */
  color: rgba(216, 226, 236, 0.65);
  line-height: 1.35;
  margin: 0;
}

.chapter__media {
  position: relative;
  z-index: 3;
  margin: 0 -72px;
  width: calc(100% + 144px);
  overflow: hidden;
  /* Absorb remaining height in the flex column so the card always
     fits exactly one viewfold. The image inside uses object-fit:cover
     to crop gracefully when this slot is shorter than the image's
     natural aspect would render. */
  flex: 1 1 0;
  /* `min-height: 0` is required in flex children to allow them to
     shrink below their natural size — without it, the image content
     would force the chapter to grow past its container. */
  min-height: 0;
}
.chapter__media img,
.chapter__media video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Center the image so cropping eats top/bottom evenly when the slot
     is shorter than the image's natural rendered height — keeps the
     central subject (AI Mode badge / silhouettes) framed. */
  object-position: center;
}

/* "Under construction" state — replaces the old hatched placeholder.
   Used on chapter__media when no real visual exists yet (chapters 3 & 4).
   Per the mock: italic, dim, centered in the leftover flex slot. No
   min-height here — the slot already gets whatever space remains
   inside the viewport-bound card. */
.chapter__media--empty {
  display: flex;
  align-items: center;
  justify-content: center;
}

.chapter__construction {
  /* Italic dim text per mock — reads as "this slot is reserved" rather
     than as content. */
  font-family: var(--display);
  font-style: italic;
  font-weight: 300;
  font-size: clamp(1.4rem, 2.2vw, 2rem);
  letter-spacing: -0.005em;
  color: rgba(255, 255, 255, 0.55);
  margin: 0;
}

.chapter [data-split="words"] .word-inner {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.6em);
}

@media (max-width: 900px) {
  .work { padding: 0 0 120px; }
  .work-intro-panel { min-height: 560px; }
  .chapter { padding: 40px 20px; perspective: none; }
  .chapter__card { padding: 40px 24px 0; }
  .chapter__head {
    grid-template-columns: 1fr;
    gap: 32px;
    margin-bottom: 40px;
  }
  .chapter__col--copy { padding-top: 0; gap: 24px; }
  .chapter__title { max-width: none; }
  .chapter__media {
    margin: 0 -24px;
    width: calc(100% + 48px);
  }
  .chapter__media--empty { min-height: 280px; }
}

@media (prefers-reduced-motion: reduce) {
  .chapter [data-split="words"] .word-inner {
    opacity: 1 !important;
    transform: none !important;
  }
  .chapter__card { transform: none !important; }
}

/* ========= CONTACT SECTION =========
   Single 100vh snap target. Layout per Sid's "Hit me up" mock:
     - Hero: portrait card on the left, content stack on the right
       (title, bio paragraph, links row).
     - Wordmark "sydrth.*" centered horizontally below, sized smaller
       than before per mock — about 80% of viewport width, bleeding
       off the bottom edge but not the sides. */
.contact {
  position: relative;
  height: 100vh;
  min-height: 720px;
  scroll-snap-align: start;
  /* Two-region grid: hero (intro+portrait), wordmark below */
  display: grid;
  grid-template-rows: 1fr auto;
  padding: 80px 64px 0;
  overflow: hidden;
  z-index: 1;
}

/* --- Hero row: portrait card on LEFT, copy on RIGHT --- */
.contact__hero {
  display: grid;
  /* Both columns now have intrinsic, bounded widths. Previously the
     copy column was minmax(0, 1fr) which expanded to consume all
     available space, pushing the visual center off to the left because
     the portrait stayed pinned to its 360px width while the copy
     stretched to the right viewport edge. With both columns bounded,
     the grid forms a real block that auto-centers via margin: 0 auto. */
  grid-template-columns: minmax(280px, 360px) minmax(420px, 640px);
  gap: clamp(48px, 6vw, 96px);
  align-items: center;
  /* justify-content centers the grid track within .contact__hero's
     own width — belt-and-braces alongside the auto margins below. */
  justify-content: center;
  position: relative;
  z-index: 2;
  align-self: center;
  width: 100%;
  /* Width auto-derives from the columns + gap; max-width keeps it
     sane on ultrawide screens. */
  max-width: 1180px;
  margin: 0 auto;
}

/* --- Portrait card --- the new PNG ships with its own rounded mask and
   ambient glow already baked in, so the CSS frame is intentionally
   stripped: no border, no inset highlight, no shadow, no background
   tint, no padding, no border-radius, no edge feathering. Container is
   a pure transparent slot — the image renders exactly as exported. */
.contact__portrait {
  position: relative;
  /* 3:4 aspect (matches the source image) */
  aspect-ratio: 3 / 4;
  background: transparent;
  border: 0;
  box-shadow: none;
  padding: 0;
  border-radius: 0;
  overflow: visible;
}

.contact__portrait img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  border-radius: 0;
  -webkit-mask-image: none;
          mask-image: none;
}

/* --- Right column: title, paragraph, links --- */
.contact__copy {
  display: flex;
  flex-direction: column;
  gap: 24px;
  /* Tight max-width so paragraph wraps similar to mock */
  max-width: 64ch;
}

.contact__title {
  font-family: var(--display);
  font-weight: 300;
  font-size: clamp(2.4rem, 4vw, 3.6rem);
  line-height: 1;
  letter-spacing: -0.015em;
  color: var(--white);
  margin: 0;
}

.contact__paragraph {
  font-family: var(--sans);
  font-size: clamp(0.95rem, 1.05vw, 1.05rem);
  line-height: 1.55;
  font-weight: 400;
  color: rgba(216, 226, 236, 0.78);
  margin: 0;
}

/* Links: row, evenly distributed across the column */
.contact__links {
  display: flex;
  align-items: center;
  gap: clamp(28px, 4vw, 64px);
  margin-top: 8px;
}

.contact__link {
  font-family: var(--sans);
  font-size: clamp(0.95rem, 1.1vw, 1.1rem);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--white);
  text-decoration: none;
  position: relative;
  padding: 4px 0;
  transition: color 0.25s var(--ease-out-soft);
  white-space: nowrap;
}

.contact__link::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.35s var(--ease-out-soft);
}

.contact__link:hover { color: rgb(184, 211, 230); }
.contact__link:hover::after { transform: scaleX(1); }

/* --- Wordmark: centered, sized per mock (NOT a giant bleed) --- */
.contact__wordmark {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: flex-end;
  /* Pull slightly past the bottom edge so the period is clipped a touch,
     matching the mock where the descenders just kiss the edge. */
  margin-bottom: -4vh;
  pointer-events: none;
  z-index: 0;
}

.contact__wordmark img {
  display: block;
  /* Per Sid: keep at high quality; don't down-scale aggressively.
     Width capped so the wordmark sits as a centered block in the
     viewport (bounded + auto margins via the flex parent). The cap
     is large enough that on most screens the wordmark visually
     reads as "fills the bottom of the page" while still leaving
     small margins of gradient at the left and right edges. */
  width: min(94vw, 1600px);
  max-width: 100%;
  height: auto;
  margin: 0 auto;
  user-select: none;
  -webkit-user-drag: none;
}

/* ========= RESPONSIVE ========= */
@media (max-width: 900px) {
  .wordmark { left: 20px; top: 20px; }
  .pill-nav { right: 20px; top: 20px; padding: 4px; }
  .pill-nav__link, .pill-nav__cta { padding: 8px 12px; font-size: 12px; }
  .pill-nav__items { gap: 0; }

  .stage__layer--a {
    grid-template-columns: 1fr;
    padding: 0 20px 40px;
    align-items: end;
  }
  .glass-card { min-width: auto; padding: 0; align-self: start; justify-self: end; }
  .stage__layer--b {
    grid-template-columns: 1fr;
    gap: 32px;
    padding: 0 20px;
  }

  .work-intro { padding: 140px 20px 80px; }

  .case { min-height: auto; }
  .case__preview, .case--reverse .case__preview { padding: 0; min-height: 60vh; }
  .case__preview > video, .case__preview > img, .case__preview > .placeholder { height: 60vh; }
  .case__panel, .case--reverse .case__panel {
    position: relative; width: 100%; left: auto; right: auto;
    padding: 60px 20px;
    border: none;
    border-bottom: 1px solid rgba(212, 217, 223, 0.06);
  }
  .case__panel-inner { max-width: none; }

  .footer { padding: 100px 20px 30px; }
  .footer__row--wide { flex-direction: column; align-items: flex-start; }
  .footer__fine { flex-direction: column; gap: 8px; }
}

/* ========= REDUCED MOTION =========
   Always last — these rules need highest specificity for accessibility. */

/* ===== MOBILE — phones & narrow tablets ≤ 768px =====
   This block targets phones and narrow tablets. The earlier
   @media (max-width: 900px) block above catches wider tablets but is
   mostly stale code; this block overrides it where the mocks specify
   mobile-specific layout. Desktop (above 768px) is unaffected.

   Breakpoint widened from 480px → 768px after Sid flagged mobile
   wasn't taking effect — the original 480px was too narrow to catch
   common devices in browser dev-tools emulation modes. iPhone 17 is
   402px so it was matching, but anything wider (Pixel 8 Pro at 412px
   IS still under, but iPad Mini portrait at 768px wasn't) wouldn't.

   Per Sid's mocks (iPhone 17 = 402×874pt) and explicit decisions:
     - Hide pill nav entirely on mobile.
     - Wordmark bigger top-left.
     - Stage Layer A: full-bleed video, title left-aligned mid-viewport,
       glass card pinned bottom-right (smaller footprint).
     - Stage Layer B: title + body left-aligned, single column, no figure.
     - Scroll hint: keep but smaller.
     - My Work: copy already matches mock; curves stay (no change needed).
     - Chapter cards: single-column (already in 900px block); CTA aside
       wraps below button at narrow widths via existing flex-wrap rule.
     - Contact: portrait stacks ABOVE the copy, everything centered,
       RESUME link hidden via .contact__link--desktop-only. */
@media (max-width: 768px) {

  /* Mobile inertial scroll: leaving the html-level proximity snap from
     the base rule ENABLED. Earlier v51 disabled it via
     `scroll-snap-type: none !important` for free OS-momentum scroll;
     v53 brings snap back per Sid — chapters and the work-intro panel
     have `scroll-snap-align: start`, so flicks now glide softly into
     section boundaries instead of stopping wherever inertia carries
     them. The 88dvh chapter height (set further down) keeps the
     "peek of next card" affordance intact even with snap on. */

  /* ----- Hide pill nav on mobile ----- */
  .pill-nav { display: none !important; }

  /* ----- Wordmark: keep full size on mobile -----
     Desktop has a JS-driven scrub that scales the wordmark from 1.0
     down to 0.42 across phase 4 of the stage timeline. On mobile per
     Sid's spec, the wordmark stays at full size throughout — the
     scrub doesn't shrink it. We use `!important` on transform here
     to beat the inline `style.setProperty('--wordmark-scale', X)`
     that JS writes; the inline style sets the variable but doesn't
     touch transform directly, so this rule wins. */
  .wordmark {
    left: 20px;
    top: 20px;
    transform: scale(1) !important;
  }
  .wordmark__svg {
    /* Mock 2 shows the wordmark at ~46% of viewport width on iPhone 17
       (375px on a 804px-wide mock = ~46.6%). At a 402px iPhone viewport
       that's ~187px — but mock 2 looks bigger than my prior 180-220px
       cap. Bumping target width to ~52% of viewport with a higher
       ceiling so it reads as a strong brand mark, not a small badge. */
    width: clamp(200px, 56vw, 260px);
    height: auto;
  }

  /* ----- Stage section: keep full-bleed video, no padding tweaks
           needed at the section level. The layers handle layout. ----- */

  /* ----- Layer A: "Human-first. AI-second." + glass card -----
     Mock 1: title left-aligned in lower-middle, glass card bottom-right
     as a compact tile.

     Force-hide Layer A in phases 5 and 6 — without this, GSAP's
     opacity tween on layer A occasionally fails to land at 0 on
     mobile, leaving "Human-first. AI-second." visible behind
     "Hello, I am Siddharth." Hooking into the body data-phase set
     by setPhase() in script.js. */
  body[data-stage-phase="5"] .stage__layer--a,
  body[data-stage-phase="6"] .stage__layer--a {
    opacity: 0 !important;
    visibility: hidden;
  }

  .stage__layer--a {
    /* Was grid 1fr auto bottom-aligned. Now: full-viewport relative
       container so we can absolutely position the title and card. */
    display: block;
    padding: 0;
  }
  .hero-a__title {
    position: absolute;
    left: 28px;
    /* Mock 1: top edge of "Human-first." sits at ~46% Y; with this
       transform-translate-50 anchor, top:52% positions it correctly.
       Tried 60% earlier — too low, the title sat near the silhouette's
       hip; 52% places it under the chin like the mock. */
    top: 52%;
    transform: translateY(-50%);
    /* Mock 1 title is large — comparable in scale to the wordmark.
       Pushing the clamp ceiling up to 3.6rem (the prior 3.4 cap looked
       too small in side-by-side) and raising the vw factor to 12vw so
       it tracks the mock's relative dominance on a 402px viewport. */
    font-size: clamp(2.6rem, 12vw, 3.6rem);
    line-height: 1.0;
    max-width: 86vw;
  }
  .glass-card {
    position: absolute;
    right: 28px;
    bottom: 32px;
    /* Chrome is now gone (no background, border, shadow) — so no
       padding either; just naked text pinned to lower-right. The
       max-width constrains how wide the title wraps so "LEAD UX /
       DESIGNER" still stacks to two lines like the mock. Bumped
       from 180→200 so the larger text below doesn't get force-wrapped
       past two lines. Right gutter bumped 20→28 to match left-side
       gutter on Layer A. */
    min-width: 0;
    padding: 0;
    max-width: 200px;
  }
  .glass-card__title {
    /* Mock 1 shows "LEAD UX / DESIGNER" reading much larger than my
       1.15rem — closer to 1.45rem with tight 1.0 line-height. Bumping
       so the brand statement carries weight in the lower-right anchor. */
    font-size: 1.45rem;
    line-height: 1.0;
  }
  .glass-card__sub {
    /* "AT GOOGLE" subtitle — mock reads at ~14-15px (0.85rem), with
       slightly looser tracking. Was 0.78rem — too quiet against the
       larger title above. */
    margin-top: 10px;
    font-size: 0.85rem;
  }

  /* ----- Layer B: "Hello, I am Siddharth." + bio -----
     Mock 2: title + body both left-aligned, vertically centered-ish,
     single column. The desktop figure column is empty here anyway. */
  .stage__layer--b {
    display: block;
    padding: 0;
  }
  .stage__layer--b .hero-b {
    position: absolute;
    /* Per mock 2: title block starts ~28px from left edge (slightly
       wider gutter than the 20px wordmark gutter — keeps the title
       optically aligned with the wordmark's letter-strokes, not its
       SVG bounding box). Title block extends close to the right edge
       so the body paragraph below has enough horizontal room to wrap
       at ~6 lines like the mock. */
    left: 28px;
    right: 28px;
    /* Override the base `.hero-b` desktop rule that caps width at
       min(34%, 480px) — that produced ~137px on a 402px viewport,
       making the body wrap at ~14 chars per line. We want the wrapper
       to fill the full gutter-bounded width on mobile. */
    width: auto;
    /* Mock 2: title block sits roughly mid-viewport (top edge of title
       at ~50% Y), with the body paragraph extending into the lower
       half. Was anchored at 42% which placed the title too high
       relative to the subject's face. 50% lands the "Hello, I am"
       line just under the subject's chin, matching the mock. */
    top: 50%;
    transform: none;
    max-width: none;
  }
  .hero-b__title {
    /* Mock 2 title looks ~58-62px / ~3.7rem. Bumping the clamp ceiling
       to land in that range on iPhone 17 (402px viewport) where 14vw
       = 56px. */
    font-size: clamp(2.8rem, 14vw, 3.8rem);
    line-height: 1.0;
    max-width: 86vw;
  }
  .hero-b__intro {
    /* Mock 2 body wraps at ~5 short lines: each line ~26-30 chars
       wide ("As the UX Lead for Google Ads / Billing, I collaborate
       deeply with / cross-functional partners to / translate ambitious
       design visions / into scalable, viable solutions."). With body
       at 100% of the .hero-b wrapper (full gutter to gutter) we got 4
       wider lines instead. Capping body max-width at ~74% of the
       wrapper narrows the column to match the mock's wrap pattern. */
    font-size: 0.95rem;
    line-height: 1.45;
    max-width: 74%;
    margin-top: 20px;
  }

  /* ----- Scroll hint: keep, smaller per Sid's spec ----- */
  .stage__scroll-hint {
    bottom: 28px;
    gap: 10px;
  }
  .stage__scroll-hint-mouse {
    width: 22px;
    height: 36px;
    border-width: 1.25px;
  }
  .stage__scroll-hint-dot {
    width: 3px;
    height: 3px;
    top: 7px;
  }
  .stage__scroll-hint-label {
    font-size: 9px;
    letter-spacing: 0.28em;
  }

  /* ----- Chapter cards: refine the existing 900px single-column
           layout for iPhone-narrow widths. The flex-wrap on
           .chapter__cta-group already handles the lock+microcopy
           wrapping below the button when there's no horizontal room. ----- */
  .chapter {
    /* More breathing room from the screen edges than before — was
       16px which made the card sit tight against the viewport edges.
       24px lets the card read as a deliberate framed unit on the
       deep-navy backdrop. Vertical padding stays modest because the
       card itself now provides its own internal generosity. */
    padding: 32px 24px;
    /* Shorter than full viewfold on mobile — at 88dvh, ~12% of the
       next chapter peeks into the viewport, signaling that there's
       more below. Now that snap is killed this peek pairs with the
       free-flowing inertial scroll: user sees what's coming next and
       can flick down naturally. Min-height floor dropped because the
       640px desktop floor would force chapters back to full-height
       on shorter mobile viewports, defeating the peek. */
    height: 88dvh;
    min-height: 0;
  }
  .chapter__card {
    /* Internal padding bumped 20→28 on the sides and 32→36 on the top
       so body copy and the title don't sit tight against the card's
       border. Bottom stays 0 — the media still bleeds flush to the
       card's bottom edge per the original spec. */
    padding: 36px 28px 0;
  }
  .chapter__head { gap: 20px; margin-bottom: 28px; }
  .chapter__title { font-size: clamp(2rem, 8vw, 2.6rem); }
  .chapter__body { font-size: 0.95rem; line-height: 1.5; }
  /* Media zone inset matches the new 28px card side padding so it
     bleeds edge-to-edge on the card — the negative margins cancel out
     the parent's horizontal padding. Was 20/40, bumped to 28/56 to
     stay in sync with the wider card padding above. */
  .chapter__media {
    margin: 0 -28px;
    width: calc(100% + 56px);
  }

  /* ----- Contact section -----
     Mock 5: portrait stacked ABOVE the copy, everything centered,
     wordmark bleeds off bottom edges. */
  .contact {
    padding: 40px 20px 0;
    /* Allow it to grow naturally with stacked content rather than
       fighting fixed 100vh on a phone. */
    height: auto;
    min-height: 100vh;
  }
  .contact__hero {
    /* Stack: portrait first, then copy. */
    grid-template-columns: 1fr;
    /* Mock 5: portrait → "Hit me up" gap is generous. 48px lands
       comfortably between the two; less feels cramped, more makes
       the portrait look detached. */
    gap: 48px;
    max-width: none;
    justify-content: stretch;
  }
  .contact__portrait {
    /* Mock 5: portrait reads at ~50% of viewport width (390/810 in the
       mock), with visible negative space on either side. Was 80% / 320
       max — too dominant, made the page feel portrait-first instead
       of copy-first. Trimming to 56% with a 240 ceiling drops it into
       the mock's proportions. */
    width: 56%;
    max-width: 240px;
    aspect-ratio: 3 / 4;
    justify-self: center;
  }
  .contact__copy {
    /* Mock shows everything centered on mobile.
       Override the desktop's uniform 24px column gap so we can space
       each element to mock 5's balance: tight title→bio, generous
       bio→links. */
    align-items: center;
    text-align: center;
    gap: 0;
  }
  .contact__title {
    font-size: clamp(2rem, 9vw, 2.6rem);
    text-align: center;
    /* Tight gap to bio paragraph below — mock shows ~14-18px. */
    margin-bottom: 18px;
  }
  .contact__paragraph {
    text-align: center;
    font-size: 0.95rem;
    /* Mock 5: body wraps at ~6 lines (wider column), not 8. Was 80%
       which over-narrowed the column and pushed the line count up.
       Bumping to 88% gives the wrap pattern in the mock: 5-6 wider
       lines with modest side margins, not a tight tall column. */
    max-width: 88%;
    /* Generous gap before the links row — mock 5 has roughly 50px
       between the last line of bio and the EMAIL/INSTAGRAM/LINKEDIN
       row. This breathing room is what makes the layout feel balanced. */
    margin-bottom: 48px;
  }
  .contact__links {
    /* Three links spread evenly with a wider gap on mobile so the row
       reads as full-bleed across the viewport (mock 5 has the links
       almost touching the section's left/right padding, with generous
       inter-link space). Was 5vw — too tight, links bunched centrally. */
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    /* Cap the row width so on very narrow phones the links still have
       comfortable padding from the section edges. */
    width: 100%;
    max-width: 360px;
    margin: 0 auto;
  }
  /* Hide RESUME link on mobile per Sid: only EMAIL · INSTAGRAM ·
     LINKEDIN show, no resume CTA. */
  .contact__link--desktop-only {
    display: none;
  }
  /* Mock 5 link order on mobile: EMAIL · INSTAGRAM · LINKEDIN.
     Markup keeps desktop order (EMAIL · LINKEDIN · RESUME · INSTAGRAM);
     using flex `order` here just shuffles Instagram to slot 2 on mobile
     without rearranging the HTML, so desktop stays untouched. */
  .contact__links { display: flex; }
  .contact__link--instagram { order: -1; }   /* before LinkedIn */
  /* Email needs to stay first — give it a smaller order than Instagram. */
  .contact__link[href^="mailto"] { order: -2; }

  /* ----- Wordmark at footer: per Sid's update — keep inside viewport
           horizontally (no left/right clipping). Only the BOTTOM is
           allowed to clip by sitting flush at the contact section's
           bottom edge. Width capped at viewport width with auto margins
           so it stays centered. ----- */
  .contact__wordmark img {
    width: 100%;
    max-width: 100%;
    margin-left: 0;
  }
}

/* ========= REDUCED MOTION ========= */
@media (prefers-reduced-motion: reduce) {
  .stage { height: auto; }
  .stage__sticky { position: relative; height: auto; min-height: 100vh; }
  .stage__video { filter: none !important; transform: none !important; }
  .stage__layer { position: relative; opacity: 1 !important; padding: 80px 40px; }
  [data-split="words"] .word-inner { transform: none !important; opacity: 1 !important; }
  .wordmark { position: relative; top: auto; left: auto; }
}
