/* performance.css — page-specific overrides for performance.html.
 *
 * Everything in here is gated behind a class only used on performance.html
 * (`.hero-splash--watermark-bl`, `.hero-splash__name--stacked`,
 * `.s-work--club`) so it can never leak into the homepage.
 *
 * Sections (extracted from the previously-shared files):
 *   1. Stacked hero title    — was css/hero-story.css §stacked
 *   2. Watermark cover + zoom — was css/hero-story.css §watermark
 *   3. Noir-red tunnel theme  — was css/work-orbit.css §club
 *
 * Page-specific stylesheets that are ALREADY separate files
 * (about-reveal.css, lets-talk.css, magnet-cursor.css, dock-nav.css)
 * stay where they are — performance.html links them individually.
 */


/* Page background: pure black (override the global #0c0e14). */
:root {
  --bg: #000;
}

body {
  background: #000;
  background-image: none;
}

/* About — bulletproof bounds:
   - transparent (no fill that could paint below)
   - overflow:clip so absolute children (spatchcock) cannot bleed past
     the section box into #finale / #work
   - z-index: 0 + isolation:auto so it never wins stacking over #work (z:10) */
.about-reveal[data-about-reveal] {
  background: transparent !important;
  background-image: none !important;
  /* z-index 3: above hero (z:1) so the rising headline isn't covered by
     the hero's bottom fade; below #work (z:10). overflow visible so
     headline can paint outside its section box while rising from below. */
  z-index: 3 !important;
  isolation: auto !important;
  overflow: visible !important;
}

/* Spatchcock below copy (text z:20) — also inside the clipped section box. */
.about-reveal__photo-wrap {
  z-index: 8;
}

/* Tagline — absolute, shifted down 120px to track the headline's new position. */
.about-reveal__tagline {
  position: absolute;
  top: 779px;
  left: 652px;
  will-change: transform, opacity;
  font-size: clamp(1.4rem, 2.2vw, 2.1rem) !important;
  color: rgba(255, 255, 255, 0.92) !important;
  right: auto;
  width: min(38vw, 540px);
  max-width: none;
  margin: 0;
  text-align: left;
}

/* Spatchcock — absolute; top/left/width set by JS (anchor 950,370 @ scroll 1164;
   width = 100vw − anchorX so right edge flush with window right). */
.about-reveal__photo-wrap {
  position: absolute !important;
  right: auto !important;
  z-index: 8;
}

.about-reveal__photo-wrap > .about-reveal__photo {
  width: 100% !important;
  max-width: none !important;
  border-radius: 0 !important;
}

/* Bio + Reels CTA hidden for now per user request. */
.about-reveal__btn {
  display: none !important;
}

/* Bio — absolute; top/left set by JS from anchor (322,568 @ scroll 1300). */
.about-reveal__bio {
  position: absolute !important;
  right: auto !important;
  width: min(38vw, 580px) !important;
  margin: 0 !important;
  z-index: 20;
  /* Initial hidden state (was provided by [data-about-reveal-block] in
     about-reveal.css — we dropped that attr to avoid two opacity tweens
     fighting; now my body-anchored blur tween in about-reveal.js owns it). */
  opacity: 0;
  filter: blur(12px);
  will-change: opacity, filter, transform;
}

/* "As a dancer…" statement: natural flow position is already right
   (tagline being absolute lifts statement up into the desired range). */

/* ============================================================
   Finale RED — fixed; expands from 0 width @ scroll 1434 to
   100vw @ scroll 1900; top edge slides 760px → 0; opacity 0 → 1;
   blur held high all the way (40px → 20px, never sharp).
   Initial transform set by GSAP (xPercent: -50, y: 760).
   ============================================================ */
.finale-red {
  position: fixed;
  top: 0;
  left: 50%;
  width: 0;
  height: auto;
  aspect-ratio: 1433 / 2144;
  opacity: 0;
  filter: blur(40px) brightness(0.7);
  pointer-events: none;
  z-index: 30;
  will-change: width, opacity, filter, transform;
}

/* Two-column credentials blocks (A / B) — fixed overlay, two flex
   columns. A sits high, B sits centred. */
.finale-pre {
  position: fixed;
  left: 0;
  right: 0;
  z-index: 34;
  pointer-events: none;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 4vw;
  padding: 0 8vw;
  color: #fff;
  font: 400 clamp(1rem, 1.6vw, 1.4rem)/1.45 "DM Sans", "Inter", sans-serif;
  /* Block-level visibility removed — each .finale-pre__col fades on its
     own scrub trigger (staggered in/out per spec). */
}
.finale-pre__col {
  visibility: hidden;
  opacity: 0;
  filter: blur(20px);
  will-change: opacity, filter, visibility;
}

.finale-pre--a { top: 22vh; }
.finale-pre--b { top: 30vh; }

.finale-pre__col { max-width: 38vw; }
.finale-pre__col--l {
  text-align: left;
  font-style: italic;
  font-weight: 600;
  font-size: clamp(1.2rem, 2.2vw, 1.9rem);
  letter-spacing: -0.005em;
  flex: 0 0 auto;
}
.finale-pre__col--r { text-align: left; }

.finale-pre__list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.finale-pre__list li { margin: 0.15em 0; }

/* Bridge line — single centred italic word between Block B and finale-words. */
.finale-words__dive {
  color: #fff;
  text-decoration: none;
  pointer-events: auto;
  cursor: pointer;
  /* Hover/focus shifts to the unified claret accent so the dive CTA
     reads as the "hot" entry point, picking up the same red language
     as the contact pill and magnet-cursor blob. */
  transition: color 0.25s ease, text-shadow 0.25s ease;
}
.finale-words__dive:hover,
.finale-words__dive:focus-visible {
  color: #b3001b;
  text-shadow:
    0 0 12px rgba(179, 0, 27, 0.55),
    0 0 28px rgba(179, 0, 27, 0.25);
  outline: none;
}

.finale-bridge {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 34;
  pointer-events: none;
  color: #fff;
  font: italic 600 clamp(2rem, 4.4vw, 4.2rem)/1 "DM Sans", "Inter", sans-serif;
  visibility: hidden;
  opacity: 0;
  filter: blur(20px);
  will-change: opacity, filter, visibility;
}

/* "I'm here to…" intent menu — fixed centred overlay, same fade language as
   the finale-pre blocks (autoAlpha + blur, scrubbed in js/about-reveal.js).
   The container is click-through; only the options take pointer events. */
.intent-menu {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 34;
  pointer-events: none;
  text-align: center;
  color: #fff;
  visibility: hidden;
  opacity: 0;
  filter: blur(20px);
  will-change: opacity, filter, visibility;
}

.intent-menu__lead {
  margin: 0 0 1em;
  font: italic 600 clamp(2rem, 4.4vw, 4.2rem)/1 "DM Sans", "Inter", sans-serif;
}

.intent-menu__options {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.9em;
}

.intent-menu__item { position: relative; }

.intent-menu__option {
  position: relative;
  display: inline-block;
  pointer-events: auto;
  cursor: pointer;
  text-decoration: none;
  color: rgba(255, 255, 255, 0.9);
  font: italic 500 clamp(1.2rem, 2.6vw, 2.3rem)/1.2 "DM Sans", "Inter", sans-serif;
  transition: color 0.25s ease, text-shadow 0.25s ease;
}

/* "(online soon)" — small, dimmed, non-italic note trailing an option. */
.intent-menu__soon {
  margin-left: 0.5em;
  font-size: 0.5em;
  font-style: normal;
  font-weight: 400;
  letter-spacing: 0.04em;
  color: rgba(255, 255, 255, 0.5);
  vertical-align: middle;
}

.intent-menu__option:hover,
.intent-menu__option:focus-visible {
  color: #b3001b;
  text-shadow:
    0 0 12px rgba(179, 0, 27, 0.55),
    0 0 28px rgba(179, 0, 27, 0.25);
  outline: none;
}

/* Hover hint — revealed below each option from its data-tip. */
.intent-menu__option::after {
  content: attr(data-tip);
  position: absolute;
  left: 50%;
  top: calc(100% + 0.4em);
  transform: translateX(-50%) translateY(4px);
  white-space: nowrap;
  font: 400 clamp(0.72rem, 1.2vw, 0.95rem)/1 "DM Sans", "Inter", sans-serif;
  font-style: normal;
  letter-spacing: 0.04em;
  color: rgba(255, 255, 255, 0.68);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease, transform 0.25s ease;
}

.intent-menu__option:hover::after,
.intent-menu__option:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  .intent-menu__option.finale__float { animation: none; }
}

/* Finale words overlay. */
/* Breath wrapper: exhale + inhale OCCUPY THE SAME GRID CELL but each
   has its OWN float animation — they swap by scale (exhale 1→0, inhale
   0→1.25) for a breathing rhythm. */
.finale-words__breath {
  position: relative;
  display: inline-grid;
}
.finale-words__breath > .finale__float {
  grid-column: 1;
  grid-row: 1;
  transform-origin: center center;
}
.finale-words__breath > [data-finale-inhale] {
  scale: 0;
}

.finale-words {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 35;
  pointer-events: none;
  display: flex;
  gap: 0.4em;
  white-space: nowrap;
  font: italic 600 clamp(2.4rem, 5.2vw, 5rem)/1 "DM Sans", "Inter", sans-serif;
  color: #fff;
  opacity: 0;
  filter: blur(20px);
  will-change: opacity, filter;
}

/* Tunnel must paint above everything before it. */
.s-work--club {
  z-index: 10;
  background: #000 !important;
}
.s-work--club::before,
.s-work--club::after {
  display: none !important;
}

/* Performance: kill the amber mask wall so LUMIN sits on pure black
   from the very first frame. The tunnel "expansion" still reads from
   the scene scale + canvas particles + cards swirl. */
/* Hide ONLY the SVG visuals inside the mask. Don't display:none the
   .s__mask-outer wrapper itself — ScrollTrigger pins it. */
.s-work--club .s__mask__svg,
.s-work--club .s__mask {
  visibility: hidden !important;
}
.s-work--club .s__inner {
  /* Stays position:fixed (inherited from work-orbit.css) so LUMIN frame
     never moves — no slide-up entry. Hidden by default; the .lumin-stuck
     toggle flips opacity INSTANTLY (no transition) so the visibility
     change is on the SAME clock as the GSAP scrub. Any CSS transition
     here introduces a time-based motion that desyncs from the scroll-
     driven grow → reads as a frame skip at the boundary. Scene starts
     at scale 0 inside the timeline, so even with opaque .s__inner the
     LUMIN letters are invisible until grow runs. */
  background: #000 !important;
  transform: none !important;
  opacity: 0;
  pointer-events: none;
}
.s-work--club .s__inner.lumin-stuck {
  opacity: 1;
  pointer-events: auto;
}
.s-work--club .s__inner::before {
  display: none !important;
}

/* Hide non-anchor ghost letters during grow (state=0). Slow linear
   fade in over state 0→1 (was 0→0.25 with ×4 multiplier — too snappy,
   read as a "pop" especially during the reverse merge phase). Anchor
   ghost stays at full opacity so a single crisp letter per slot is
   always visible.
   will-change: transform — promotes each ghost to its own GPU layer so
   the per-frame swirl/scatter transforms compositor-only, no paint.
   ALSO override the scatter transform: the upstream 50vw spread
   makes letters fly across the viewport — way too much motion for
   our pacing. Keep them barely leaving their slot: 10vw horizontal,
   10% vertical, 2deg rotation. */
.s-work--club .s__scene__letter {
  will-change: transform;
}

/* Bigger tunnel cards on the club page: 40-65vw vs the shared default
   30vw. Pole reels are portrait stage shots that need real estate so the
   dancer reads at a glance during the (deliberately slow) fly-through.
   Concave wall feel dialled down: --tunnel-yaw 38° → 18° + depth 9rem
   → 4rem so cards stay closer to face-on and the perspective warp at
   the edges is gentler — less "fishbowl distortion" while still reading
   as a curved tunnel. */
.s-work--club a-work.s__scene__work {
  width: clamp(310px, 47vw, 640px);
  --tunnel-yaw: 8deg;
  --tunnel-depth: 0.5rem;
}
/* Portrait cards: scaled down so the rendered height roughly matches the
   landscape cards' height (landscape ~3:2 → height ≈ 31vw; portrait 4:5
   at 32vw → height ≈ 40vw, close enough that the row reads as uniform).
   Class added by js/perf-card-tooltip.js after the img reports its
   natural dimensions. */
.s-work--club a-work.s__scene__work.is-portrait {
  width: clamp(220px, 32vw, 440px);
}

/* Club: respect each image's native aspect ratio (no forced 4:5 crop).
   .s__work__media drops its fixed aspect-ratio so the inner <img> sizes
   itself by its intrinsic dimensions, and we use object-fit: contain so
   nothing is cropped. Card width stays clamp(310-640) above; height is
   whatever the image naturally needs. */
.s-work--club .s__work__media {
  aspect-ratio: auto;
  height: auto;
}
.s-work--club .s__work__media img,
.s-work--club .s__work__media video {
  width: 100%;
  height: auto;
  object-fit: contain;
}

/* Caption removed from card layout entirely — surfaces as a floating
   cursor-following label on hover instead (see js/perf-card-tooltip.js).
   Kept in DOM for a11y so screen readers still read the title/key. */
.s-work--club .s__work__caption {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Floating tooltip that follows the cursor while hovering a club card.
   JS toggles .is-visible + updates --tx/--ty per mousemove. */
.perf-card-tooltip {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9999;
  padding: 0.55rem 0.85rem 0.6rem;
  pointer-events: none;
  background: rgba(0, 0, 0, 0.88);
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 6px;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  font-family: "DM Sans", system-ui, sans-serif;
  white-space: nowrap;
  opacity: 0;
  transform: translate3d(var(--tx, -200px), var(--ty, -200px), 0)
             translate(18px, 22px) scale(0.92);
  transition: opacity 0.18s ease, transform 0.18s ease;
}
.perf-card-tooltip.is-visible {
  opacity: 1;
  transform: translate3d(var(--tx, 0), var(--ty, 0), 0)
             translate(18px, 22px) scale(1);
}
.perf-card-tooltip__title {
  display: block;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.perf-card-tooltip__key {
  display: block;
  margin-top: 0.18rem;
  font-family: ui-monospace, monospace;
  font-size: 0.62rem;
  color: rgba(255, 255, 255, 0.55);
}

.s-work--club .s__scene__letter:not(.is-anchor) {
  opacity: var(--state);
  /* Tiny per-ghost offset so the brief fade-in overlap reads as layered
     copies, not a single fattening letter. `translate` is the CSS
     individual property (composes BEFORE the scatter `transform`), so
     once scatter spreads ghosts by ~50vw this 2-3px offset is invisible.
     --p ∈ [-1, +1] across ghosts; --ap ∈ [0, 1] (absolute progress). */
  translate: calc(var(--p) * 2.5px) calc(var(--ap) * 1.5px);
}

/* Match the "have we met?" headline (about-reveal): DM Sans 700 with
   tight tracking. Tunnel default is 900 + neutral tracking which reads
   chunkier/wider; switching to 700 + -0.04em letter-spacing makes the
   LUMIN word feel like the same typographic voice.
   font-size reduced from the upstream default (25lvh — sized for 4
   letters WORK) to 20lvh so 5 letters LUMIN fit the same vertical
   bounds. line-height 0.85 × 5 letters × 20lvh ≈ 85lvh column —
   leaves headroom above + below so the dock at --close=1 doesn't
   crop. */
.s-work--club .s__scene,
.s-work--club .s__title {
  font-weight: 700;
  letter-spacing: -0.04em;
  font-size: min(15rem, 20lvh);
}

/* Spacer is just a scroll runway — guarantee it never paints anything
   so the fixed LUMIN frame (revealed via .lumin-stuck) shows through
   cleanly no matter how tall the spacer is. */
#finale-spacer {
  background: transparent;
  pointer-events: none;
  position: relative;
  z-index: 0;
}


/* ============================================================
 * Finale v2 — sticky underwater pan + breathing closer
 * ------------------------------------------------------------
 * No GSAP pin, no pin-spacer, no z-index war.
 *   .finale          — outer section, no special positioning.
 *   .finale__track   — tall scroll runway (controls dwell length).
 *   .finale__stage   — position:sticky inside the track; the
 *                      whole stage stays in viewport while the
 *                      track scrolls past underneath.
 *   .finale__photo   — pans up via translate driven by a single
 *                      scroll handler (one rAF, one transform).
 *   .finale__breath  — pure CSS @keyframe exhale ↔ inhale swap.
 * ============================================================ */
.finale {
  position: relative;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  background: #000;
  color: #f0f0f0;
  /* Below #work (z:10) so when user scrolls past, tunnel paints clean. */
  z-index: 4;
}

.finale__track {
  /* Total dwell scroll. 250vh = 1.5 viewports past the entry — enough for
     the pan + breath beat without dragging. Tune up for more dwell. */
  height: 250vh;
}

.finale__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
  background: #000;
  opacity: var(--finale-fade, 1);
  /* Below #work (z:10) — once stage fades to 0 the tunnel mask underneath
     paints clean during the last 30% of track scroll. */
  z-index: 1;
}

.finale__photo-wrap {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

.finale__photo {
  position: absolute;
  top: 0;
  left: 50%;
  height: 160vh;
  width: auto;
  min-width: 100%;
  object-fit: cover;
  transform: translate3d(-50%, calc(var(--finale-pan, 0) * -60vh), 0);
  will-change: transform;
}

.finale__overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 6vw;
  text-align: center;
  pointer-events: none;
}

.finale__line {
  margin: 0;
  font-family: "DM Sans", "Inter", sans-serif;
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.8rem, 4.8vw, 4rem);
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.94);
  text-shadow: 0 2px 24px rgba(0, 0, 0, 0.55);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 0 0.35em;
}

.finale__comma {
  margin-left: -0.25em;
}

/* Per-word floating drift — each <span class="finale__float"> declares its
   own --fx / --fy / --fd / --fdl via inline style, so every word swims on
   its own loop. yoyo-style via alternate. */
.finale__float {
  display: inline-block;
  animation: finale-float var(--fd, 5s) ease-in-out var(--fdl, 0s) infinite alternate;
  will-change: translate;
}

@keyframes finale-float {
  from { translate: 0 0; }
  to   { translate: var(--fx, 0) var(--fy, 0); }
}

/* Breath: two words stacked on top of each other, alternating visibility
   on a 5.6s loop. Container has explicit width + height so the absolute
   children actually overlap at the same baseline. */
.finale__breath {
  position: relative;
  display: inline-block;
  width: 5ch;
  height: 1.2em;
  vertical-align: baseline;
}

.finale__breath-word {
  position: absolute;
  inset: 0;
  text-align: center;
  /* Stacks float (parent inline transform) with breath fade (its own loop).
     We use opacity here so it doesn't clobber the float transform. */
  animation:
    finale-float var(--fd, 5s) ease-in-out var(--fdl, 0s) infinite alternate,
    finale-breath 5.6s ease-in-out infinite;
}

.finale__breath-word--out { animation-delay: var(--fdl, 0s), 0s; }
.finale__breath-word--in  { animation-delay: var(--fdl, 0s), 2.8s; }

@keyframes finale-breath {
  0%, 42%   { opacity: 1; }
  50%, 92%  { opacity: 0; }
  100%      { opacity: 1; }
}

.finale__dive {
  color: #fff;
  text-decoration: none;
  border-bottom: 2px solid rgba(255, 255, 255, 0.5);
  padding-bottom: 0.05em;
  pointer-events: auto;
  transition: color 0.25s ease, border-color 0.25s ease;
}

.finale__dive:hover,
.finale__dive:focus-visible {
  color: #ff5577;
  border-color: #ff5577;
}

@media (prefers-reduced-motion: reduce) {
  .finale__float,
  .finale__breath-word { animation: none; }
  .finale__breath-word--in { display: none; }
  .finale__photo { transform: translate3d(-50%, -30vh, 0); }
}

/* ============================================================
 * 1 · Stacked hero title ("LUMIN" / "AFTER DARK")
 * ------------------------------------------------------------
 * The base .hero-splash__name (in hero-story.css) sets
 * `white-space: nowrap` and one big font-size — fine for a
 * single-word title, but blocks two stacked lines. The stacked
 * variant overrides those: column flex container, lets each
 * .hero-splash__line declare its own size + tracking so LUMIN
 * stays headline-big and AFTER DARK reads as a subtitle.
 * Blur / opacity / text-shadow from .hero-splash__name still
 * cascade to both lines.
 * ============================================================ */
.hero-splash__name--stacked {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;              /* negative-feeling tight stack — "after dark" pulled up */
  white-space: normal; /* override base nowrap so the two lines stack */
  font-size: 0;        /* container is just a layout box; line sizes win */
  line-height: 1;
}

.hero-splash__line {
  display: block;
  white-space: nowrap; /* each line stays on one row even at narrow viewports */
  line-height: 1;
}

/* LUMIN — bold, horizontally stretched + vertically compressed.
   Mostly opaque so the title reads clearly; the dancer behind still
   peeks through the kerned gaps. */
.hero-splash__line--lg {
  font-family: "DM Sans", system-ui, sans-serif;
  font-size: clamp(5.5rem, 24vw, 16rem);
  font-weight: 900;
  letter-spacing: 0.18em;
  transform: scale(1.22, 0.78);
  transform-origin: 50% 50%;
  opacity: 0.88;
}

/* "after dark" — lowercase editorial line under LUMIN. Negative margin
   pulls it up so it nests INTO the LUMIN baseline gap instead of
   sitting comfortably below — gives the title a tighter "kerned word
   pair" read rather than two stacked rows. */
.hero-splash__line--sm {
  font-family: "DM Sans", system-ui, sans-serif;
  font-size: clamp(1.2rem, 6.5vw, 3.8rem);
  font-weight: 400;
  letter-spacing: 0.32em;
  text-transform: lowercase;
  opacity: 0.82;
  margin-top: -0.55em;
}

/* Pole dancer · ... role line — slightly dimmer than base 0.82. */
.hero-splash--watermark-bl .hero-splash__role {
  color: rgba(232, 236, 239, 0.58);
  margin-top: 1rem;
}

/* Two-line role: each <span> on its own row. Keeps the existing
   colour/typography from .hero-splash__role and just stacks them. */
.hero-splash__role-line {
  display: block;
}

/* Hero bottom fade — 40% of hero height, opaque page-bg at bottom edge
   → fully transparent at the top of the band (overrides the 50vh
   default from hero-story.css). */
.hero-splash--watermark-bl::after {
  height: 40vh;
  background: linear-gradient(to top,
    #000 0%,
    rgba(0, 0, 0, 0.85) 35%,
    rgba(0, 0, 0, 0.45) 70%,
    transparent 100%);
}


/* ============================================================
 * 2 · Watermark cover + subject zoom
 * ------------------------------------------------------------
 * 1) Subject zoom — the source image (2048×1363) is wide
 *    relative to a portrait viewport, so object-fit: cover
 *    already crops some sides but the dancer ends up small.
 *    We scale the photo container so the subject fills more
 *    of the hero. Done on .hero-splash__media (which
 *    hero-parallax.js does NOT touch) instead of
 *    .hero-splash__img (which the parallax script overwrites
 *    with `transform: scale(...)` every frame); the two
 *    transforms compose, so the parallax range sits on top of
 *    our zoom and we don't fight the script. transform-origin
 *    pulled toward the dancer (centre + slightly down) so the
 *    zoom doesn't push their head off the top of the viewport.
 * 2) Watermark cover — the photo carries a "NELLIE & the
 *    Sirens" mark in its bottom-left corner. Stacked-gradient
 *    on .hero-splash__shade adds a corner radial darken that's
 *    opaque at the watermark zone and fades to transparent
 *    ~42% out, on top of the original gentle vertical falloff
 *    so the rest of the hero still feels like the homepage's
 *    light/dark rhythm.
 * ============================================================ */
/* Static hero on performance.html — no parallax JS, no filter pipeline.
   Subject zoom via transform on .__media (one-shot, no animation = no flicker). */
.hero-splash--watermark-bl,
.hero-splash--watermark-bl .hero-splash__img,
.hero-splash--watermark-bl .hero-splash__name,
.hero-splash--watermark-bl .hero-splash__char {
  filter: none !important;
  will-change: auto !important;
  backface-visibility: visible !important;
  contain: none !important;
}

.hero-splash--watermark-bl .hero-splash__media {
  transform: scale(1.55);
  transform-origin: 50% 48%;
}

.hero-splash--watermark-bl .hero-splash__img {
  object-position: center 38%;
}

.hero-splash--watermark-bl .hero-splash__content {
  --hero-p: 0 !important;
  --hero-spread: 0vw !important;
}

.hero-splash--watermark-bl .hero-splash__shade {
  background:
    /* Corner darken — bottom-left, sized large enough to still catch
       the watermark after the 1.28 zoom shifts it slightly outward. */
    radial-gradient(
      ellipse 50% 48% at 0% 100%,
      rgba(0, 0, 0, 0.96) 0%,
      rgba(0, 0, 0, 0.86) 40%,
      rgba(0, 0, 0, 0.0) 100%
    ),
    /* Vertical page-blend (kept from homepage rhythm). */
    linear-gradient(
      180deg,
      rgba(8, 10, 15, 0.2) 0%,
      rgba(8, 10, 15, 0.08) 40%,
      rgba(8, 10, 15, 0.08) 60%,
      rgba(8, 10, 15, 0.35) 100%
    );
}


/* ============================================================
 * Floating hero pills — text-only nav (signature / faq / gallery /
 * let's talk). No corners: each pill positions via --px/--py inline
 * custom props (vw/vh anchors). Each also drifts slowly with its own
 * --ix/--iy amplitude + --id duration + --idl delay. Magnet-cursor
 * (js/magnet-cursor.js) auto-snaps to <a>/<button>, wrapping each pill
 * with a round-rect blob on hover — no per-pill hover state needed for
 * that visual; here we just style the resting pill clean and minimal. */
.hero-pills {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
}

.hero-pill {
  position: absolute;
  left: var(--px, 50vw);
  top:  var(--py, 50vh);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.85rem 1.7rem 0.95rem;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.32);
  border-radius: 999px;
  color: #fff;
  font-family: "DM Sans", system-ui, sans-serif;
  font-size: clamp(0.95rem, 1.25vw, 1.15rem);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: lowercase;
  text-decoration: none;
  cursor: pointer;
  pointer-events: auto;
  white-space: nowrap;
  backdrop-filter: blur(6px) saturate(140%);
  -webkit-backdrop-filter: blur(6px) saturate(140%);
  background-color: rgba(0, 0, 0, 0.28);
  /* Drift on its own animation; transform is fully owned by the
     keyframe so the cursor-driven hover is purely the magnet blob
     wrapping the pill, not a per-pill transform. */
  animation: hero-pill-float var(--id, 8s) ease-in-out var(--idl, 0s) infinite alternate;
  transition: background-color 0.25s ease, border-color 0.25s ease, color 0.25s ease;
}

.hero-pill:hover,
.hero-pill:focus-visible {
  outline: none;
  border-color: rgba(255, 255, 255, 0.6);
  background-color: rgba(255, 255, 255, 0.08);
}

@keyframes hero-pill-float {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(var(--ix, 0), var(--iy, 0), 0); }
}

@media (prefers-reduced-motion: reduce) {
  .hero-pill { animation: none; }
}

/* Mobile: shrink padding + relax inline positions so they don't crowd
   the hero title at narrow viewports. */
@media only screen and (max-width: 640px) {
  .hero-pill {
    padding: 0.65rem 1.2rem 0.7rem;
    font-size: 0.88rem;
  }
}

/* ============================================================
 * 3 · Berlin techno tunnel theme (.s-work--club)
 * ------------------------------------------------------------
 * Same engine + geometry as index.html (css/work-orbit.css +
 * js/work-orbit.js). Only the palette changes: crushed black,
 * single saturated blood-red accent — Berghain / basement /
 * safelight. No second hue, no RGB split.
 * ============================================================ */
.s-work--club {
  --tunnel-warm: #a80018;            /* blood red — mask + fill */
  --tunnel-warm-rgb: 168, 0, 24;
  --tunnel-fade: 18vh;               /* tighter edge fades → harder frame */
}

/* Edge fades blend into pure black, not page blue-black */
.s-work--club::before,
.s-work--club::after {
  background: linear-gradient(to bottom, #000 0%, transparent 100%);
}
.s-work--club::after {
  background: linear-gradient(to top, #000 0%, transparent 100%);
}

.s-work--club .s__inner {
  background:
    radial-gradient(
      ellipse 55% 45% at 50% 42%,
      #120004 0%,
      #060001 48%,
      #000 100%
    );
}

/* LUMIN letters: blood red + tight outer halo */
.s-work--club .s__scene__letter {
  color: var(--tunnel-warm);
  text-shadow:
    0 0 0.12em rgba(var(--tunnel-warm-rgb), 0.55),
    0 0 0.35em rgba(var(--tunnel-warm-rgb), 0.22),
    0 0 0.8em rgba(var(--tunnel-warm-rgb), 0.08);
}

/* Mask frame — same red as section fill */
.s-work--club .s__mask__path-outer,
.s-work--club .s__mask__path-inner,
.s-work--club .s__mask__path-lines {
  fill: var(--tunnel-warm);
}

.s-work--club .s__work__link {
  border-color: rgba(var(--tunnel-warm-rgb), 0.42);
  background: linear-gradient(135deg, rgba(var(--tunnel-warm-rgb), 0.35), #000 60%);
}

.s-work--club .s__work__link:hover {
  border-color: rgba(var(--tunnel-warm-rgb), 1);
  box-shadow:
    0 0 18px rgba(var(--tunnel-warm-rgb), 0.55),
    0 0 36px rgba(var(--tunnel-warm-rgb), 0.22);
}

.s-work--club .s__tagline {
  color: rgba(var(--tunnel-warm-rgb), 0.9);
  letter-spacing: 0.22em;
  /* The shared work-orbit tagline is uppercase for the homepage; on
     the performance close-out we want the "congratulations…" copy to
     stay quiet and conversational — lowercase across the board. */
  text-transform: none;
}

.s-work--club .s__icon__link {
  background: rgba(var(--tunnel-warm-rgb), 0.14);
  border-color: rgba(var(--tunnel-warm-rgb), 0.5);
}

.s-work--club .s__icon__link:hover {
  background: rgba(var(--tunnel-warm-rgb), 0.32);
  border-color: rgba(var(--tunnel-warm-rgb), 0.95);
  box-shadow:
    0 0 0 1px rgba(var(--tunnel-warm-rgb), 0.35),
    0 0 28px rgba(var(--tunnel-warm-rgb), 0.28);
}

/* ──────────────────────────────────────────────────────────────────
   .s__scene covers the full .s__inner area at z-index 2, intercepting
   clicks across the whole viewport during and after the tunnel. The
   close-out pills (.s__icons, z-index 1) sit beneath it and would be
   visually visible but unclickable. Turn the scene transparent to
   pointer events and put it back on the actual cards (the only thing
   inside the scene that NEEDS clicks). This frees the close-out pills
   AND the corner social icons to receive events normally.
   ────────────────────────────────────────────────────────────────── */
.s-work--club .s__scene { pointer-events: none; }
.s-work--club .s__scene .s__scene__work,
.s-work--club .s__scene a-work { pointer-events: auto; }

/* ──────────────────────────────────────────────────────────────────
   Tunnel close-out pills (signature / faq / gallery / let's talk)
   Re-skins the existing .s__icon burst items as text pills so the
   close-out CTAs mirror the hero pills (same wording, same shape).
   Geometry (--icon-x / --icon-y * --close burst) is inherited from
   work-orbit.css; we only override sizing, shape and content layout.
   ────────────────────────────────────────────────────────────────── */
.s-work--club .s__icon--pill {
  /* The base .s__icon uses square width/height to keep its centre at
     the (top:0,left:0) anchor. For pills we want a wider hit area —
     a 200×60 footprint balances "fits one CTA word" with "doesn't
     dominate the close-out". The negative-margin trick scales with
     these new dimensions. */
  --pill-w: clamp(132px, 12vw, 188px);
  --pill-h: clamp(40px, 4.4vw, 52px);
  width: var(--pill-w);
  height: var(--pill-h);
  margin: calc(var(--pill-h) * -0.5) 0 0 calc(var(--pill-w) * -0.5);
}

.s-work--club .s__icon__link--pill {
  /* Match the hero pill chrome: translucent dark fill, 1px claret-warm
     border, blurred. Uses the tunnel warm palette so it stays in the
     LUMIN colour world rather than the page-level black/white. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  padding: 0 1rem;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.42);
  border: 1px solid rgba(var(--tunnel-warm-rgb), 0.55);
  box-shadow: none;
  color: #fff;
  font-family: "DM Sans", system-ui, sans-serif;
  font-size: clamp(0.78rem, 0.95vw, 0.95rem);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: lowercase;
  text-decoration: none;
  white-space: nowrap;
  backdrop-filter: blur(6px) saturate(140%);
  -webkit-backdrop-filter: blur(6px) saturate(140%);
}

.s-work--club .s__icon__link--pill:hover {
  background: rgba(var(--tunnel-warm-rgb), 0.32);
  border-color: rgba(var(--tunnel-warm-rgb), 0.95);
  transform: scale(1.04);
  box-shadow: 0 0 22px rgba(var(--tunnel-warm-rgb), 0.28);
}

/* Mobile: pill widths shrink so all four still fit inside their
   --icon-x corners without colliding with each other. */
@media only screen and (max-width: 640px) {
  .s-work--club .s__icon--pill {
    --pill-w: clamp(108px, 32vw, 148px);
    --pill-h: clamp(34px, 8vw, 44px);
  }
  .s-work--club .s__icon__link--pill {
    font-size: 0.78rem;
    letter-spacing: 0.06em;
    padding: 0 0.75rem;
  }
}

/* ──────────────────────────────────────────────────────────────────
   .scroll-pct
   Live scroll-percentage label. Position mirrors its value: at 15%
   scroll the tag sits at 15vh from the top of the viewport. JS sets
   --pct (0..100); CSS computes top: calc(var(--pct) * 1vh). Hidden
   always visible; swaps to "go deeper" as soon as scrolling stops
   (scroll-pct.js).
   ────────────────────────────────────────────────────────────────── */
.scroll-pct {
  position: fixed;
  right: 1.2vw;
  top: calc(var(--pct, 0) * 1vh);
  transform: translateY(-50%);
  font-family: "DM Sans", system-ui, sans-serif;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  color: #ff5577;                                  /* matches #scroll-hud b */
  pointer-events: none;                            /* enabled on .is-rest */
  z-index: 9998;
  white-space: nowrap;
  /* The wrapper is a layout box; the visible pill is .scroll-pct__label.
     This lets the top / bottom pop-out buttons sit relative to the
     wrapper without disturbing the pill's chrome. */
}

.scroll-pct__label {
  display: inline-block;
  padding: 4px 9px;
  background: rgba(0, 0, 0, 0.72);
  border: 1px solid rgba(255, 85, 119, 0.35);
  border-radius: 6px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  /* top transitions for smooth vertical glide as the user scrubs the
     page; padding/letter-spacing eases when state swaps. */
  transition: padding 0.25s ease, letter-spacing 0.25s ease,
              background 0.25s ease, border-color 0.25s ease;
}
.scroll-pct {
  transition: top 0.18s linear;
}

/* Resting state: "go deeper" prompt — airier, lighter chrome so it
   reads as an invitation rather than a metric. Hover is also enabled
   here (the percentage state stays inert). */
.scroll-pct.is-rest {
  pointer-events: auto;
  cursor: default;
}
.scroll-pct.is-rest .scroll-pct__label {
  padding: 4px 12px;
  letter-spacing: 0.16em;
  background: rgba(0, 0, 0, 0.45);
  border-color: rgba(255, 85, 119, 0.22);
  text-transform: lowercase;
}

/* ── Top / bottom jump buttons ──────────────────────────────────────
   Hidden by default; the rest state surfaces them on hover. They
   slide out of the label vertically (top above, bottom below). */
.scroll-pct__jump {
  position: absolute;
  right: 0;
  left: auto;
  margin: 0;
  padding: 4px 12px;
  font: inherit;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.16em;
  color: #ff5577;
  background: rgba(0, 0, 0, 0.6);
  border: 1px solid rgba(255, 85, 119, 0.28);
  border-radius: 6px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  cursor: pointer;
  pointer-events: auto;
  white-space: nowrap;
  text-transform: lowercase;
  /* Hidden + slightly recessed into the label, ready to fly out. */
  opacity: 0;
  transform: translateY(0);
  transition: opacity 0.22s ease, transform 0.32s cubic-bezier(0.22, 1, 0.36, 1),
              background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.scroll-pct__jump--top    { bottom: 100%; margin-bottom: 6px; }
.scroll-pct__jump--bottom { top: 100%;    margin-top:    6px; }

/* Reveal: pill flies further away from the label on hover. Only the
   .is-rest state honours hover so the percentage doesn't bait clicks
   while it's still tracking. */
.scroll-pct.is-rest:hover .scroll-pct__jump,
.scroll-pct.is-rest:focus-within .scroll-pct__jump {
  opacity: 1;
}
.scroll-pct.is-rest:hover .scroll-pct__jump--top,
.scroll-pct.is-rest:focus-within .scroll-pct__jump--top {
  transform: translateY(-2px);
}
.scroll-pct.is-rest:hover .scroll-pct__jump--bottom,
.scroll-pct.is-rest:focus-within .scroll-pct__jump--bottom {
  transform: translateY(2px);
}

.scroll-pct__jump:hover,
.scroll-pct__jump:focus-visible {
  color: #fff;
  background: rgba(179, 0, 27, 0.55);             /* unified claret */
  border-color: rgba(179, 0, 27, 0.85);
  outline: none;
}

/* ──────────────────────────────────────────────────────────────────
   Magnet cursor — cyan in the free state, claret-red on snap.
   - The base vars (--cursor-blue / --cursor-gradient) drive the
     free-state blob → cyan.
   - .is-snapped overrides the gradient + halo to claret so the
     magnet "lock-in" reads as a colour change, not just a shape morph.
   - --cursor-red (ripple rim) stays red so click bursts match the
     snapped accent.
   - The tiny white tracker dot is hidden — blob alone is the cursor.
   ────────────────────────────────────────────────────────────────── */
:root {
  --cursor-blue: #0fd9d9;
  --cursor-blue-rgb: 15, 217, 217;
  --cursor-red: #b3001b;
  --cursor-red-rgb: 179, 0, 27;
  /* Lower-alpha fills so the blob reads as a translucent halo rather
     than a solid puck. The JS-driven opacity spring (free 1 / pressed
     0.8 / snapped 0.35) still applies on top, multiplying the alpha
     here — gives an even softer "ghost" look without us fighting the
     spring. */
  --cursor-gradient: linear-gradient(
    135deg,
    rgba(0, 184, 196, 0.55) 0%,
    rgba(42, 223, 223, 0.55) 55%,
    rgba(141, 245, 245, 0.55) 100%
  );
  /* Claret gradient kept as a separate var so the snapped state can
     re-use the iridescent feel without us hard-coding it twice. */
  --cursor-gradient-snapped: linear-gradient(
    135deg,
    rgba(138, 0, 20, 0.55) 0%,
    rgba(179, 0, 27, 0.55) 55%,
    rgba(210, 16, 46, 0.55) 100%
  );
}
/* Snapped → claret blob (re-uses the snapped gradient above). */
.magnet-cursor__blob.is-snapped {
  background: var(--cursor-gradient-snapped);
}
/* Hide the tiny tracker dot. */
.magnet-cursor__dot { display: none !important; }
