/* ============================================================
   Alexandra Neville — APPARATUS-style restyle
   Orange poster bg, concrete forms, bold display type,
   monospace stamps, white handwritten overlay layer.
   ============================================================ */

/* Shared grain overlay (must come first — @import only valid at top). */
@import url("/css/_grain.css");

:root {
  /* Palette */
  --bg:        #e21500;   /* Bright red — page background */
  --bg-deep:   #a00b00;   /* Darker red — image placeholders, code blocks */
  --ink:       #0A0A0A;
  --ink-mute:  #1A1A1A;
  --paper:     #F1ECE0;
  --concrete:  #C8C0B0;
  --script:    #FFFFFF;
  --accent:    #FFFFFF;
  --rule:      rgba(10, 10, 10, 0.35);

  /* Type */
  --font-display: "Bricolage Grotesque", system-ui, sans-serif;
  --font-sans:    "Space Grotesk", system-ui, -apple-system, sans-serif;
  --font-mono:    "Space Mono", "Courier New", monospace;

  /* Grain control — tune these to taste */
  --grain-fine:   0.08;   /* fine paper noise opacity */
  --grain-coarse: 0.14;   /* coarse stain/blotch opacity */
}

/* Grain overlay lives in /css/_grain.css (imported above). */

*, *::before, *::after { box-sizing: border-box; }

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

/* Scroll-snap on the document root.
   - `mandatory` forces every scroll to land on a snap point.
   - `scroll-padding-top: 0` keeps the snap aligned to viewport top.
   - `scroll-behavior: smooth` makes JS-driven jumps animate.
   - `overscroll-behavior: none` blocks the rubber-band bounce that
     can throw the snap calculation off on mac trackpads.
   It needs to live on `html` (the scroll container) — putting it on
   `body` only is unreliable across browsers. */
html {
  scroll-snap-type: y mandatory;
  scroll-behavior: smooth;
  scroll-padding-top: 0;
  overscroll-behavior: none;
}
body {
  scroll-snap-type: y mandatory;   /* belt + suspenders */
  overscroll-behavior: none;
  /* V5.17 — kill global text selection. Drag-to-select on a touch device
     was triggering the iOS magnifying glass + selection toolbar mid-swipe,
     interrupting the 3D interaction. Site is presentation-first, not
     copy-paste; disable selection everywhere. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}
/* Form fields are the one exception — the contact page inputs must stay
   selectable so visitors can edit what they type. */
input, textarea, select {
  user-select: text;
  -webkit-user-select: text;
}

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

/* ---------- 3D stage ---------- */
#stage { position: fixed; inset: 0; z-index: 0; pointer-events: none; }
#canvas { width: 100%; height: 100%; display: block; pointer-events: auto; }
#canvas.is-interactive { cursor: pointer; }
/* V5.17 — pointer particle trail. Sits ABOVE the 3D stage (z-index 0)
   so the wake reads against the meshes, but BELOW the panel overlays
   (z-index 10–30) and the header (z-index 200) so text stays crisp
   on top of the particles. Never accepts pointer events. White specks
   with "overlay" blend so they brighten the red bg without printing
   solid white over text. */
.trail-canvas {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 5;
  pointer-events: none;
  mix-blend-mode: overlay;
}
/* Scroll-mode canvas is purely decorative — clicks, taps, hover, and
   drag-selection are all disabled. pointer-events: none kills it all
   in one go: the renderer keeps drawing frames (it doesn't need pointer
   events), but clicks pass through to whatever's underneath. Tap-highlight
   and long-press callouts are also suppressed for touch devices. Text
   content inside .panel__overlay stays selectable because the panel sits
   above the canvas in the stacking order. */
body[data-mode="scroll"] #canvas {
  cursor: default !important;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  touch-action: pan-y;
}
/* Coarse pointers (touch / mobile) — kill all canvas pointer events
   so taps can't fire raycaster clicks. Desktop with mouse keeps
   pointer-events: auto so hover rotation on the 3D models works. */
@media (pointer: coarse) {
  body[data-mode="scroll"] #canvas {
    pointer-events: none !important;
  }
}

/* ---------- Loading veil (V5.20) ----------
   Heading sits exactly where the s1 panel__overlay headline sits on
   the home page — bottom-left, same padding, same .display--huge
   type — so when the veil fades the wordmark stays put. The
   percentage lives bottom-right on desktop, dead-centre on mobile
   portrait. */
.veil {
  position: fixed; inset: 0; z-index: 100;
  background: var(--bg);
  transition: opacity 600ms ease, visibility 600ms;
  color: var(--ink);
  /* Trail-canvas (z-index 9999) draws on top of the veil so the
     pointer trail still works during loading. */
}
.veil.is-hidden { opacity: 0; visibility: hidden; pointer-events: none; }

/* Heading wrapper mirrors .panel__overlay exactly: bottom-aligned,
   same fluid padding, same max-width. The inner h1 inherits all of
   .display + .display--huge (uppercase, font-display, size clamp). */
.veil__heading {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 0 clamp(1.5rem, 5vw, 4rem) clamp(4.5rem, 9vh, 6rem);
  max-width: 52rem;
  pointer-events: none;
}

/* Percentage — desktop default: bottom-right corner, aligned to the
   same baseline padding as the heading so the two read as a paired
   row across the bottom of the screen. */
.veil__percent {
  position: absolute;
  right: clamp(1.5rem, 5vw, 4rem);
  bottom: clamp(4.5rem, 9vh, 6rem);
  margin: 0;
  font-family: var(--font-mono);
  font-size: clamp(1rem, 1.4vw, 1.4rem);
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  text-transform: uppercase;
  line-height: 1;
}

/* Mobile portrait: heading stays in its s1 spot; percentage moves to
   dead centre as a hero-size number. Coarse pointer in portrait =
   phone-style narrow viewport — matches the orientation lock CSS
   media query used elsewhere. */
@media (max-width: 640px), (hover: none) and (pointer: coarse) and (orientation: portrait) {
  .veil__percent {
    right: auto; bottom: auto;
    left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    font-family: var(--font-display);
    font-weight: 800;
    font-size: clamp(3rem, 14vw, 5rem);
    letter-spacing: -0.025em;
  }
}

/* ---------- Wordmark (top-right) ----------
   Mono / uppercase / tracked — matches the eyebrow + scroll cue. */
.wordmark {
  position: fixed;
  top: 1.6rem;
  right: 3.5rem;
  z-index: 30;
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  line-height: 1;
  color: var(--ink);
}
.wordmark a { display: inline-block; padding: 0.2rem 0; }

/* ---------- Scroll dots (right edge) ---------- */
.scroll-dots {
  position: fixed;
  right: 1.4rem;
  top: 50%;
  transform: translateY(-50%);
  z-index: 30;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}
.scroll-dots a {
  width: 26px; height: 26px;
  display: grid; place-items: center;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  color: var(--ink);
}
.scroll-dots a span {
  width: 8px; height: 8px;
  border: 1.4px solid var(--ink);
  background: transparent;
  transition: background 220ms ease, transform 220ms ease;
}
.scroll-dots a.is-active span,
.scroll-dots a:hover span {
  background: var(--ink);
  transform: scale(1.25);
}

/* ---------- Scroll cue (bottom-right) ---------- */
.scroll-cue {
  position: fixed;
  right: 1.6rem;
  bottom: 4rem;             /* leaves room above the footer line */
  z-index: 30;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.5rem;
  transition: opacity 400ms ease;
  pointer-events: none;
  font-family: var(--font-mono);
}
.scroll-cue.is-hidden { opacity: 0; }
.scroll-cue__label {
  font-size: 0.65rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--ink);
}
.scroll-cue__line {
  width: 1.4px;
  height: 36px;
  background: var(--ink);
  animation: drift 2s ease-in-out infinite;
  transform-origin: top;
}
@keyframes drift {
  0% { transform: scaleY(0.3); opacity: 0.3; }
  50% { transform: scaleY(1); opacity: 1; }
  100% { transform: scaleY(0.3); opacity: 0.3; }
}

/* ---------- Sections ---------- */
main {
  position: relative;
  z-index: 10;
  pointer-events: none;   /* let mouse events fall through to the canvas */
}
.panel {
  scroll-snap-align: start;
  scroll-snap-stop: always;       /* skip-scrolling past sections is disallowed */
  height: 100vh;
  height: 100svh;                 /* `svh` = the small-viewport-height (stable
                                     regardless of mobile browser chrome show/hide).
                                     Falls back to vh in browsers that don't support it. */
  position: relative;
  display: grid;
  grid-template-rows: 1fr auto;
  pointer-events: none;
}
.panel__overlay {
  align-self: end;
  /* bottom padding lifts overlay text clear of the fixed .site-foot
     (which is ~2.4rem tall). The clamp keeps spacing fluid on tall
     viewports while guaranteeing footer-clearance on short ones. */
  padding: 0 clamp(1.5rem, 5vw, 4rem) clamp(4.5rem, 9vh, 6rem);
  max-width: 52rem;
  pointer-events: none;
}
.panel__overlay * { pointer-events: auto; }   /* but text inside is still selectable / clickable */

/* Stamp eyebrow with dots: "● 02 / VESSELS / @APPARATUS" */
.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink);
  margin: 0 0 1.5rem;
  display: flex;
  align-items: center;
  gap: 0.55rem;
  flex-wrap: wrap;
}
.eyebrow .dot { font-size: 0.55rem; }
.eyebrow .sep { opacity: 0.4; }

/* Chapter display titles — sized to let the 3D dominate */
.display {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1.75rem, 4.5vw, 3.25rem);
  line-height: 0.95;
  letter-spacing: -0.025em;
  margin: 0;
  color: var(--ink);
  text-transform: uppercase;
  font-variation-settings: "opsz" 64, "wght" 800;
}

.lede {
  font-family: var(--font-sans);
  font-size: clamp(0.95rem, 1.4vw, 1.125rem);
  line-height: 1.4;
  color: var(--ink);
  margin: 1.25rem 0 0;
  max-width: 32ch;
  font-weight: 400;
  letter-spacing: -0.01em;
}

/* ---------- Footer ----------
   Fixed to viewport bottom so it remains visible across all chapters —
   in normal flow it sat below the 5th 100vh panel and was unreachable
   because of `scroll-snap-stop: always` on .panel. */
.site-foot {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 30;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1.1rem clamp(1.5rem, 5vw, 4rem);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink);
  background: transparent;
  pointer-events: none;
}
.site-foot > * { pointer-events: auto; }
.site-foot .muted { opacity: 0.65; }

/* ---------- Noscript fallback ---------- */
.fallback {
  position: relative; z-index: 200;
  max-width: 30rem; margin: 4rem auto; padding: 2rem;
  background: var(--paper); color: var(--ink);
  text-align: center;
}
.fallback h1 { font-family: var(--font-display); font-weight: 800; }
.fallback ul { list-style: none; padding: 0; margin: 2rem 0 0; }
.fallback li { padding: 0.5rem 0; border-top: 1px solid var(--rule); }
.fallback li:last-child { border-bottom: 1px solid var(--rule); }

/* ---------- Mobile ---------- */
@media (max-width: 720px) {
  /* Scroll dots removed on mobile — they sat on top of the 3D model
     + text on narrow viewports. The scroll-cue at the bottom is
     plenty of "there's more below" affordance. */
  .scroll-dots { display: none; }
  .wordmark { top: 1rem; right: 2.5rem; font-size: 0.9rem; }
  /* Lift the scroll cue above iOS Safari's bottom URL bar (~5rem)
     so it stays visible on mobile. Add safe-area inset on top of
     that for devices with home indicators. */
  .scroll-cue {
    bottom: calc(env(safe-area-inset-bottom, 0px) + 6rem);
    right: 1rem;
  }
  .panel__overlay { padding-bottom: 5rem; }
  .display { font-size: clamp(1.5rem, 6vw, 2.5rem); }
}

@media (prefers-reduced-motion: reduce) {
  html, body { scroll-snap-type: none; }
  .scroll-cue__line { animation: none; }
}

/* HTML labels for 3D fingertips (positioned in JS) */
.label3d {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 20;
  pointer-events: none;
  font-family: var(--font-mono);
  font-size: 0.65rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--bg);
  background: var(--ink);
  padding: 0.4rem 0.75rem;
  white-space: nowrap;
  transform: translate(-50%, -50%);
  opacity: 0;
  transition: opacity 220ms ease;
}
.label3d.is-visible { opacity: 1; }
.label3d.is-hover { background: var(--script); color: var(--ink); }

/* Per-fingertip variant — ink box + paper text. A thin SVG line
   (.label-lines) draws the connector from the label to the sigil tip.
   Lifted above the grain (z-index 95) so the type stays crisp.
   Box stays solid ink in BOTH hover states (broad palm-hover showing
   all 5 labels, and the specific sigil-hover for a single label) so
   the labels read clearly against the red page. */
.label3d--finger {
  font-size: 0.62rem;
  padding: 0.4rem 0.75rem;
  letter-spacing: 0.22em;
  background: var(--ink);
  color: var(--paper);
  border: 0;
  box-shadow: none;
  z-index: 95;
  text-align: center;
}
/* Explicit overrides beat `.label3d.is-hover { background: var(--script); }`
   on specificity grounds — without these, the inherited white pill /
   ink text combo from the legacy single-label rule wins. */
.label3d--finger.is-hover {
  background: var(--ink);
  color: var(--paper);
}
/* Sub-line beneath a finger label (e.g. "(coming soon)" under Shop). */
.label3d__sub {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.82em;
  letter-spacing: 0.12em;
  text-transform: none;
  opacity: 0.7;
}

/* Touch devices: the hand labels are persistent navigation, not a
   hover-revealed hint. Make each finger label itself a tap target so
   visitors don't need to hit the tiny 3D sigil precisely. Desktop
   behaviour is untouched. */
@media (hover: none) and (pointer: coarse) {
  .label3d--finger {
    pointer-events: auto;
    cursor: pointer;
    /* Bigger comfortable tap target on touch */
    padding: 0.55rem 0.9rem;
    -webkit-tap-highlight-color: transparent;
  }
}

/* SVG layer that draws thin connector lines from each label to its
   sigil tip. Positioned in JS — viewport-sized, no pointer-events. */
.label-lines {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
  z-index: 94;            /* above grain (90), below labels (95) */
  overflow: visible;
}
.label-lines line {
  stroke: var(--ink);
  stroke-width: 1;
  stroke-linecap: round;
  opacity: 0;
  transition: opacity 220ms ease;
}
.label-lines line.is-visible { opacity: 0.85; }
/* No opacity override on .is-hover — otherwise the line keeps opacity:1
   even after .is-visible is removed, and lingers visibly while the text
   fades. Hover state now only matters for the (same) ink stroke. */
.label-lines line.is-hover   { stroke: var(--ink); }

/* ----------------------------------------------------------------
   Lite mode (production index.html)
   Hides everything except the Entry hand: no chrome, no scroll UI,
   no footer, no wordmark. Document is pinned to a single viewport.
   ---------------------------------------------------------------- */
body[data-mode="lite"] {
  overflow: hidden;
  height: 100vh;
  height: 100svh;
}
/* `html` is an ancestor of `body`, so this is matched two ways: via
   :has() in modern browsers, and via a `lite` class set on the html
   element from main.js for older browsers (Safari < 15.4 et al.). */
html.lite-mode,
html:has(body[data-mode="lite"]) {
  /* Disable scroll-snap entirely — there's only one panel. */
  scroll-snap-type: none;
  overflow: hidden;
}
body[data-mode="lite"] .wordmark,
body[data-mode="lite"] .scroll-dots,
body[data-mode="lite"] .scroll-cue,
body[data-mode="lite"] .site-foot {
  display: none !important;
}
/* Lite-mode overlay sits centred-bottom of viewport, no need to clear a
   footer that isn't there. */
body[data-mode="lite"] .panel__overlay {
  padding-bottom: clamp(2rem, 5vh, 3rem);
}
/* Eyebrow in lite mode is a single line of text, not the chapter
   breadcrumb — drop the dot separators by simplifying spacing. */
body[data-mode="lite"] .eyebrow {
  margin-bottom: 0.85rem;
  font-size: 0.72rem;
}
/* Lite home is a one-shot poster. Type is sized so the headline fills
   a narrow column on the left and the hand has room on the right.
   The 28rem overlay cap keeps the headline out of the hand's halo on
   landscape; narrow viewports get smaller type via the portrait
   override below. */
body[data-mode="lite"] .panel__overlay {
  max-width: 30rem;
}
body[data-mode="lite"] .display {
  font-size: clamp(1.75rem, 5vw, 4.5rem);
  line-height: 0.95;
  letter-spacing: -0.03em;
}
body[data-mode="lite"] .lede {
  font-size: clamp(0.9rem, 1.4vw, 1.1rem);
  max-width: 26ch;
  margin-top: 1rem;
}

/* Portrait orientation — smaller type, the hand sits above, text below. */
@media (orientation: portrait) {
  body[data-mode="lite"] .eyebrow {
    font-size: 0.65rem;
    margin-bottom: 0.7rem;
  }
  body[data-mode="lite"] .display {
    font-size: clamp(1.5rem, 7vw, 2.6rem);
  }
  body[data-mode="lite"] .lede {
    font-size: 0.85rem;
    margin-top: 0.7rem;
  }
}

/* ----------------------------------------------------------------
   Scroll mode — portrait / mobile
   ----------------------------------------------------------------
   The JS side (applyScrollLayout) lifts the 3D model upward on
   portrait viewports so it sits in the upper half. CSS here shrinks
   the typography and tightens the panel overlay so text sits cleanly
   in the lower half without crowding the model. */
/* Apply on portrait OR narrow viewport, so phones in any rotation
   AND desktop devtools "responsive" mode at narrow widths both pick
   these up. Previously only `orientation: portrait` matched, which
   missed landscape phones and some devtools widths. */
/* Hidden by default — only flipped to display:block inside the
   mobile/portrait media query further down. Prevents the lede from
   double-rendering alongside the teaser grid on desktop. */
.lede--s2-mobile { display: none; }

@media (orientation: portrait), (max-width: 720px) {
  body[data-mode="scroll"] .panel__overlay {
    padding-bottom: clamp(2rem, 5vh, 3rem);
    max-width: none;
  }
  body[data-mode="scroll"] .eyebrow {
    font-size: 0.6rem;
    letter-spacing: 0.16em;
    margin-bottom: 0.6rem;
  }
  body[data-mode="scroll"] .display {
    font-size: clamp(2rem, 10vw, 3.2rem);
    line-height: 0.92;
    letter-spacing: -0.03em;
  }
  body[data-mode="scroll"] .display--huge {
    font-size: clamp(2.6rem, 13vw, 4.5rem);
    line-height: 0.9;
    letter-spacing: -0.035em;
  }

  /* Per-section eyebrows ("02 · Projects", "03 · About", "04 · Contact")
     removed on mobile per design — only the home eyebrow stays. The
     numbered breadcrumbs add clutter on a small screen when the 3D and
     the chapter heading already convey context. */
  body[data-mode="scroll"] #s2 .eyebrow,
  body[data-mode="scroll"] #s3 .eyebrow,
  body[data-mode="scroll"] #s4 .eyebrow {
    display: none;
  }

  /* Scroll cue removed on mobile — it was overlapping the bottom-anchored
     headings + CTAs. Touch users intuitively swipe to scroll; the cue is
     a desktop affordance. */
  body[data-mode="scroll"] .scroll-cue {
    display: none;
  }

  /* V5.8 — single source of truth for the mobile CTA. All three
     section CTAs (Projects "View all projects", About "View more",
     Contact "Get in touch") use the same full-width line so they
     visually read as the same component. Old duplicate rules that
     set --big to clamp(1rem,4vw,1.4rem) were making Contact bigger
     than the others; removed. */
  body[data-mode="scroll"] .panel__cta,
  body[data-mode="scroll"] .panel__cta--big {
    display: block;
    width: 100%;
    max-width: none;
    text-align: left;
    background: transparent;
    color: var(--ink);
    padding: 0.5rem 0;
    border: 0;
    border-bottom: 1px solid var(--ink);
    font-family: var(--font-mono);
    font-size: 0.78rem;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    margin-top: 1.4rem;
  }
  /* Hide the per-teaser caption on mobile — the design only shows the
     project title for each card so the grid reads at a glance. */
  body[data-mode="scroll"] .teaser__meta {
    display: none;
  }
  body[data-mode="scroll"] .lede {
    font-size: 0.85rem;
    margin-top: 0.7rem;
    max-width: none;
  }
  /* Teaser cards stay 2-up on mobile per design — small landscape
     thumbnails read like a contact-sheet pair rather than two big
     stacked posters. Overrides the base .teaser-grid → 1fr stack
     rule at @media (max-width: 720px). */
  body[data-mode="scroll"] .teaser-grid {
    grid-template-columns: 1fr 1fr;
    gap: 0.6rem;
  }
  body[data-mode="scroll"] .teaser__media {
    aspect-ratio: 16 / 10;
  }
  body[data-mode="scroll"] .teaser__title {
    font-size: 0.9rem;
  }
  body[data-mode="scroll"] .teaser__meta {
    font-size: 0.55rem;
    letter-spacing: 0.14em;
  }
  /* V5.10 — Practice mobile: hide the contact-sheet teaser cards and
     show a short lede instead. Matches the About panel pattern
     (heading → lede → CTA) so all four mobile sections feel cohesive. */
  body[data-mode="scroll"] #s2 .teaser-grid {
    display: none;
  }
  body[data-mode="scroll"] .lede--s2-mobile {
    display: block;
  }
  /* About panel — no portrait image on mobile per design. Just the
     eyebrow + heading + bio + CTA in the bottom overlay. Image stays
     in the DOM for desktop; only hidden on portrait/narrow. */
  body[data-mode="scroll"] .about-teaser {
    grid-template-columns: 1fr;
    gap: 0;
  }
  body[data-mode="scroll"] .about-teaser__media {
    display: none;
  }
}

/* ----------------------------------------------------------------
   Lite home V2 — entrance + editorial frame
   ----------------------------------------------------------------
   Panel content fades up after the loading veil hides. The
   "currently showing" line is a small editorial pointer to the most
   recent project so the home isn't just navigation chrome. */
body[data-mode="lite"] .panel__overlay {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 900ms ease, transform 900ms cubic-bezier(0.22, 0.61, 0.36, 1);
  transition-delay: 350ms;
}
body[data-mode="lite"].is-loaded .panel__overlay {
  opacity: 1;
  transform: translateY(0);
}

/* "Currently showing" — small mono caps line under the lede.
   Underline appears on hover so the link reads cleanly at rest. */
.now-showing {
  margin: 1.6rem 0 0;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.55rem;
  max-width: 36ch;
  line-height: 1.5;
}
.now-showing__label {
  opacity: 0.55;
}
.now-showing a {
  border-bottom: 1px solid transparent;
  padding-bottom: 1px;
  transition: border-color 200ms ease;
}
.now-showing a:hover {
  border-bottom-color: var(--ink);
  opacity: 1;
}

@media (orientation: portrait) {
  .now-showing {
    font-size: 0.58rem;
    margin-top: 1rem;
    gap: 0.4rem;
  }
}

/* ================================================================
   Scroll mode (production home V3)
   ================================================================
   data-mode="scroll" — 5 scroll panels with copy + CTAs, 3D scene
   is decorative, top-right header holds the nav. */

/* Fixed top header — wordmark left, nav right on a single row.
   Wordmark keeps its mono-caps treatment from the base .wordmark rule
   but loses its own fixed positioning (the header is fixed now). */
.site-header {
  position: fixed;
  top: 1.6rem;
  right: 3rem;
  left: 3rem;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  pointer-events: none;     /* let mousemove fall through to canvas */
}
.site-header > * { pointer-events: auto; }
.site-header .wordmark {
  position: static;
  top: auto;
  right: auto;
  order: 0;
}
.site-header .page__menu {
  display: flex;
  align-items: center;
  order: 1;
}
.site-header .page__menu ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2vw, 1.8rem);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink);
}
.site-header .page__menu a {
  color: var(--ink);
  transition: opacity 180ms ease;
}
.site-header .page__menu a:hover { opacity: 0.6; }
.site-header .menu-toggle { display: none; }

/* Mobile — wordmark stays left, hamburger right, menu drops as a panel. */
@media (max-width: 780px) {
  .site-header {
    top: 1rem;
    right: 1rem;
    left: 1rem;
    gap: 1rem;
  }
  .site-header .menu-toggle {
    display: inline-flex;
    flex-direction: column;
    justify-content: center;
    background: transparent;
    border: 0;
    padding: 0.4rem 0;
    cursor: pointer;
    gap: 4px;
    width: 28px;
    height: 28px;
    margin: 0;
    order: 2;
    -webkit-tap-highlight-color: transparent;
  }
  .site-header .menu-toggle__bar {
    display: block;
    width: 100%;
    height: 1.6px;
    background: var(--ink);
    transition: transform 200ms ease, opacity 150ms ease;
    transform-origin: center;
  }
  .site-header .menu-toggle.is-open .menu-toggle__bar:nth-child(1) {
    transform: translateY(5.6px) rotate(45deg);
  }
  .site-header .menu-toggle.is-open .menu-toggle__bar:nth-child(2) { opacity: 0; }
  .site-header .menu-toggle.is-open .menu-toggle__bar:nth-child(3) {
    transform: translateY(-5.6px) rotate(-45deg);
  }
  .site-header .page__menu {
    display: none;
    order: 99;
    position: fixed;
    top: 3.5rem;
    left: 1rem;
    right: 1rem;
    /* Black panel, white type — high-contrast against the red page
       and matches the boxed Contact CTA elsewhere on the site. */
    background: var(--ink);
    color: var(--paper);
    padding: 1.5rem;
    z-index: 50;
  }
  .site-header .page__menu.is-open { display: flex; }
  .site-header .page__menu ul {
    flex-direction: column;
    align-items: flex-start;
    gap: 1rem;
    width: 100%;
    font-size: 0.85rem;
    color: var(--paper);
  }
  .site-header .page__menu a {
    color: var(--paper);
  }
  .site-header .page__menu .page__menu-disabled {
    color: var(--paper);
    opacity: 0.55;
  }
}

/* Per-section CTA — unified "chapter bar" treatment across Projects,
   About, and Contact. Block-level, capped at 30rem so the underline
   reads as a deliberate line (not a screen-spanning rule), text +
   arrow + 1px ink underline. .panel__cta--big used to render a black
   box on s4 Contact; that variant now resolves to the same line so
   the three CTAs visually match. */
.panel__cta,
.panel__cta--big {
  display: block;
  width: 100%;
  max-width: 30rem;
  margin-top: 1.75rem;
  padding: 0.5rem 0;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--ink);
  text-align: left;
  transition: opacity 180ms ease;
}
.panel__cta:hover,
.panel__cta--big:hover {
  opacity: 0.6;
  background: transparent;
  color: var(--ink);
}
.panel__cta--soon {
  border-bottom: 0;
  opacity: 0.55;
  pointer-events: none;
  padding-bottom: 0;
}

/* Wider overlay variant for sections that contain teaser cards or
   side-by-side content (projects + about teasers). */
.panel__overlay--wide {
  max-width: 64rem;
}
/* Centred overlay for the contact CTA section. */
.panel__overlay--center {
  align-self: center;
  text-align: center;
  max-width: 40rem;
  margin: 0 auto;
  padding-top: clamp(6rem, 14vh, 10rem);
}
.panel__overlay--center .lede {
  max-width: none;
  margin-left: auto;
  margin-right: auto;
}
.display--huge {
  font-size: clamp(3rem, 9vw, 7rem);
  line-height: 0.9;
}

/* Projects teaser — two cards, side-by-side on desktop, stacked on
   narrow screens. Each card is a single anchor wrapping image + meta.
   Capped at 38rem so the two thumbnails sit as a tidy pair in the
   bottom-left rather than ballooning to fill the whole .panel__overlay
   --wide container (the older layout felt crowded against the 3D vase). */
.teaser-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(0.8rem, 2vw, 1.4rem);
  margin: 1.5rem 0 0;
  max-width: 38rem;
}
.teaser {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  color: var(--ink);
  transition: opacity 200ms ease;
}
/* No global dim on hover — the image-zoom + colour-reveal IS the
   hover affordance. Dimming would let the red --bg-deep behind the
   image bleed through as an overlay. */
.teaser:hover { opacity: 1; }
.teaser__media {
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--bg-deep);
}
.teaser__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 600ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.teaser:hover .teaser__media img { transform: scale(1.04); }
.teaser__meta {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin: 0;
  opacity: 0.75;
}
.teaser__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.25rem, 2vw, 1.6rem);
  letter-spacing: -0.015em;
  text-transform: uppercase;
  margin: 0;
  line-height: 1.05;
}
@media (max-width: 720px) {
  .teaser-grid { grid-template-columns: 1fr; }
}

/* About teaser — portrait left, copy right. Stacks on narrow screens. */
.about-teaser {
  display: grid;
  grid-template-columns: 1fr 1.4fr;
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: center;
  margin-top: 1rem;
}
.about-teaser__media {
  margin: 0;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--bg-deep);
}
.about-teaser__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.about-teaser__body { display: flex; flex-direction: column; }
@media (max-width: 720px) {
  .about-teaser { grid-template-columns: 1fr; gap: 1.25rem; }
}

/* Hide the original fixed wordmark on home-full (the standalone <header>
   markup still works there). Only scroll-mode uses the .site-header
   wrapper, so this rule has no effect on home-full. */
body[data-mode="scroll"] > header.wordmark { display: none; }

/* On the scroll home, the s1 panel already shows "ALEXANDRA NEVILLE"
   as a display headline — having it AGAIN in the header reads as a
   duplicate. Hide the header wordmark while on s1; fade it in once
   the visitor scrolls past the hero (body.past-hero class is set in
   updateScrollProgress in main.js). */
body[data-mode="scroll"] .site-header .wordmark {
  opacity: 0;
  pointer-events: none;
  transition: opacity 400ms ease;
}
body[data-mode="scroll"].past-hero .site-header .wordmark {
  opacity: 1;
  pointer-events: auto;
}

/* In scroll-mode the fixed bottom .site-foot is replaced by an
   in-section copyright on s5 — hide the legacy footer entirely. */
body[data-mode="scroll"] .site-foot { display: none; }

/* Gradient bar anchored to the very top of the page. Solid red at
   the top fading to fully transparent at the bottom — gives the
   header a soft red "halo" that dissolves into the background.
   Both colour stops use the same red so the fade doesn't pass
   through grey (which `transparent` shorthand would do). */
.header-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 38px;
  z-index: 35;
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    rgba(226, 21, 0, 0.85) 0%,
    rgba(226, 21, 0, 0) 100%
  );
}
@media (max-width: 780px) {
  .header-bar {
    height: 28px;
  }
}

/* Copyright line tucked into the bottom of the last panel. Sits in
   the .panel grid's auto row (panel__overlay claims the 1fr row),
   so it only appears once the visitor has scrolled to the end. */
.panel__copyright {
  align-self: end;
  padding: 0 clamp(1.5rem, 5vw, 4rem) clamp(1.5rem, 4vh, 2rem);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0.55;
  pointer-events: auto;
}

/* Disabled Shop link in nav — same typography as the live links but
   not clickable (rendered as a <span>). Slight opacity hints it's
   inactive without yelling. */
.page__menu-disabled {
  display: inline-block;
  color: var(--ink);
  opacity: 0.55;
  cursor: default;
  pointer-events: none;
}

/* Scroll-mode panel text stays ink (black) — the mix-blend-mode
   experiment was reverted because the cyan-over-red side effect
   wasn't worth the legibility win. 3D models are positioned to the
   right of each section so they don't overlap the typography. */

/* B&W images until hover — applies to project teaser images on the
   home and any image marked .desat. Greyscale fades cleanly to full
   colour on hover (~400ms) so the interaction is gentle, not jumpy. */
.teaser__media img,
.about-teaser__media img,
img.desat {
  filter: grayscale(1);
  transition: filter 600ms ease, transform 600ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.teaser:hover .teaser__media img,
.about-teaser:hover .about-teaser__media img,
.about-teaser__media:hover img,
img.desat:hover {
  filter: grayscale(0);
}

/* ================================================================
   Landscape lockout overlay
   ================================================================
   Touch-device + landscape only. The 3D scroll layout was designed
   around a portrait canvas; landscape was breaking the composition.
   Pure CSS — no JS, no permissions, no listeners. Sits above the
   loading veil (z-index 100) so it wins immediately. */
.orientation-lock {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: var(--ink);
  color: var(--paper);
  display: none;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 2rem;
  pointer-events: auto;
}
.orientation-lock__inner {
  max-width: 22rem;
  display: grid;
  gap: 1.25rem;
  justify-items: center;
}
.orientation-lock__icon {
  font-size: 3rem;
  line-height: 1;
  animation: orientation-spin 2.4s ease-in-out infinite;
}
.orientation-lock__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.6rem;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  margin: 0;
  line-height: 1;
}
.orientation-lock__body {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  margin: 0;
  opacity: 0.7;
}
@keyframes orientation-spin {
  0%, 100% { transform: rotate(-12deg); }
  50%      { transform: rotate(78deg); }
}
/* V5.15 — landscape lockout restored. The mobile landscape composition
   wasn't holding even with the tuned layout; reverting to "please rotate
   your phone" until a proper landscape design exists. */
@media (hover: none) and (pointer: coarse) and (orientation: landscape) {
  .orientation-lock { display: flex; }
  body[data-mode="scroll"] {
    overflow: hidden;
    height: 100vh;
    height: 100svh;
  }
}
