/* main.css — concatenated bundle (build order preserved). Source split kept in git history. */

/* ===== styles.css ===== */

/* ─── Accessibility / SEO utility ──────────────────────────────────────────── */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ─── Houdini property for art-direction conic blink animation ─────────────── */
@property --p {
  syntax: "<percentage>";
  initial-value: 0%;
  inherits: true;
}

/* ============================================================================
   CSS CUSTOM PROPERTIES (DESIGN TOKENS)
   Centralized values for colors, spacing, shadows, and transitions
   ============================================================================ */
:root {
  /* ===== COLORS ===== */
  --white: #fff;
  --black: #000;
  --dark: #111;
  --light: #fefefe;

  /* Primary palette */
  --cyan: #00ffff;
  --orange: #ff3c00;
  --orange-brown: #CA6702;
  --teal: #0A9396;

  /* Legacy aliases (maintain backward compatibility) */
  --color: var(--cyan);
  --hover-color: var(--orange);
  --glitch-final-color: var(--white);
  --bg: var(--black);

  /* Alpha colors for overlays */
  --cyan-alpha-10: rgba(0, 255, 255, 0.1);
  --magenta-alpha-10: rgba(255, 0, 255, 0.1);
  --yellow-alpha-10: rgba(255, 255, 0, 0.1);
  --white-alpha-01: rgba(255, 255, 255, 0.01);
  --black-alpha-60: rgba(0, 0, 0, 0.6);
  --header-bg: rgba(0, 18, 25, 0.85);

  /* Header backgrounds per section (15% transparency = 0.85 alpha) */
  --header-bg-intro: rgba(0, 18, 25, 0.85);
  --header-bg-about: rgba(0, 95, 115, 0.85);
  --header-bg-illustration: rgba(148, 210, 189, 0.85);
  --header-bg-contact: rgba(202, 103, 2, 0.85);

  /* Grid color palette */
  --grid-color-1: #001219;
  --grid-color-2: #005F73;
  --grid-color-3: #0A9396;
  --grid-color-4: #94D2BD;
  --grid-color-5: #E9D8A6;
  --grid-color-6: #EE9B00;
  --grid-color-7: #CA6702;
  --grid-color-8: #BB3E03;
  --grid-color-9: #AE2012;
  --grid-color-10: #9B2226;


  /* ===== SPACING — 8-point grid (multiples of 8px) ===== */
  --spacing-xs:  8px;    /*  1×8 */
  --spacing-sm:  16px;   /*  2×8 */
  --spacing-md:  24px;   /*  3×8 */
  --spacing-lg:  32px;   /*  4×8 */
  --spacing-xl:  48px;   /*  6×8 */
  --spacing-2xl: 64px;   /*  8×8 */
  --spacing-3xl: 96px;   /* 12×8 */
  --nav-height:  72px;   /*  9×8 — documentary; actual height is set by content */

  /* ===== SHADOWS & GLOWS ===== */
  --glow-sm: 0 0 10px;
  --glow-md: 0 0 20px;
  --glow-lg: 0 0 30px;
  --glow-xl: 0 0 40px;

  /* Optimized for Safari performance: reduced from 3 to 2 shadow layers */
  --glow-cyan: 0 0 15px var(--cyan), 0 0 30px var(--cyan);
  --glow-cyan-intense: 0 0 10px var(--cyan), 0 0 20px var(--cyan), 0 0 30px var(--cyan), 0 0 40px var(--cyan), 0 0 70px var(--cyan), 0 0 80px var(--cyan), 0 0 100px var(--cyan);
  --glow-orange: 0 0 15px var(--orange), 0 0 30px var(--orange);
  --glow-amber: 0 0 15px var(--grid-color-6), 0 0 30px var(--grid-color-6);

  /* Cyan-blue emitted-light halo — matches the about #id1svg badge's glow, whose
     internal radial gradients run #00d5df (bright cyan core) → #008adf (blue
     bloom). Shared so the DNA capsule, cert cube, and vtv decoration glow with
     the exact same light. */
  --id-glow: rgba(0, 213, 223, 0.70);
  --id-glow-soft: rgba(0, 138, 223, 0.45);
  --id-glow-filter:
    drop-shadow(0 0 6px var(--id-glow))
    drop-shadow(0 0 16px var(--id-glow-soft));

  /* ── Semantic interaction roles ────────────────────────────────────────────
     cyan  = system / machine state  (glitch fx, HUD panels, focus rings)
     amber = human  / content state  (link hovers, active CTAs, content chrome)
     ───────────────────────────────────────────────────────────────────────── */
  --accent-system: var(--cyan);
  --accent-human:  var(--grid-color-6);

  --text-shadow-dark: 0 0 10px rgba(0, 0, 0, 0.9), 0 0 20px rgba(0, 0, 0, 0.8), 0 0 30px rgba(0, 0, 0, 0.7), 0 4px 8px rgba(0, 0, 0, 0.6), 0 8px 16px rgba(0, 0, 0, 0.4);

  /* ===== TRANSITIONS — use only in `transition:` property ===== */
  --transition-fast: 0.2s;
  --transition-base: 0.3s;
  --transition-slow: 0.5s;
  --transition-slower: 0.6s;
  --transition-slowest: 1s;   /* deprecated: prefer --duration-slow in animation: */

  /* ===== DURATIONS — use only in `animation:` and `animation-duration:` ===== */
  --duration-quick:    0.2s;   /* glitch micro-frames, VHS static */
  --duration-base:     0.5s;   /* single-element entrance, line draw */
  --duration-moderate: 0.6s;   /* medium entrance, social icon sequences */
  --duration-rise:     0.8s;   /* content entrance: slideIn / slideDown */
  --duration-slow:     1s;     /* deliberate: icon transforms, lineGrow */
  --duration-scene:    1.5s;   /* scene-level: capsule, multi-element compositions */
  --duration-ambient:  2.4s;   /* ambient infinite: pulsing markers */

  --easing-ease: ease;
  --easing-ease-out: ease-out;
  --easing-ease-in: ease-in;
  --easing-ease-in-out: ease-in-out;

  /* Distinctive motion curves — the personality of this site's movement */
  --ease-expo-out: cubic-bezier(0.22, 1, 0.36, 1);   /* fast out, slow settle    */
  --ease-spring:   cubic-bezier(0.34, 1.56, 0.64, 1); /* overshoot / playful land */
  --ease-material: cubic-bezier(0.4, 0, 0.2, 1);      /* Material Design standard */

  /* ===== Z-INDEX LAYERS ===== */
  --z-background: 0;
  --z-layer-1: 1;
  --z-layer-2: 2;
  --z-layer-3: 3;
  --z-layer-4: 4;
  --z-layer-5: 5;
  --z-layer-10: 10;
  --z-layer-15: 15;
  --z-header: 1000;

  /* ===== SIZING ===== */
  --grid-size: 50px;
  --square-size: 40px;
  --icon-size: 60px;
  --border-thin: 1px;
  --border-medium: 2px;
  --border-thick: 4px;
}

/* ── Skip-to-content link (keyboard / screen reader) ────────────────────── */
.skip-link {
  position: absolute;
  top: -100%;
  left: 1rem;
  z-index: 9999;
  padding: 0.5rem 1.25rem;
  background: #001219;
  color: #94D2BD;
  font-family: 'Funnel Display', sans-serif;
  font-size: 0.875rem;
  letter-spacing: 0.05em;
  text-decoration: none;
  border: 1px solid #94D2BD;
  transition: top 0.2s;
}
.skip-link:focus { top: 1rem; }

/* ── Global focus indicator ─────────────────────────────────────────────────
   Single source of truth for keyboard focus across all interactive elements.
   Component-specific overrides below this rule intentionally shadow it.      */
*:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 4px;
  border-radius: 2px;
}

/* ── Coming-soon placeholder for unbuilt sections ───────────────────────── */
.coming-soon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  min-height: 100vh;
}

.coming-soon__label {
  font-family: 'Funnel Display', sans-serif;
  font-weight: 400;
  font-size: clamp(0.85rem, 1.6vw, 1.2rem);
  letter-spacing: 0.10em;
  white-space: nowrap;
}

/* ============================================================================
   GLOBAL RESET & BASE STYLES
   ============================================================================ */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* Apply color transition only to interactive elements to improve performance */
a,
button,
.nav-btn,
.logo {
  transition: color 0.25s var(--easing-ease);
}

html {
  scroll-behavior: auto;
}

body {
  font-family: 'Funnel Display', sans-serif;
  font-weight: 300;
  background: var(--bg);
  color: var(--color);
  min-height: 100vh;
  overflow-x: hidden;
}

/* ============================================================================
   REUSABLE COMPONENTS & EFFECTS
   Shared components, animations, and effects used across multiple sections
   ============================================================================ */

/* --- Glitch Text & Titles --- */
.glitch-text {
  position: relative;
  font-family: inherit;
  font-weight: inherit;
  text-decoration: none;
  white-space: pre;
}

.word {
  position: relative;
  display: inline-block;
}

/* Glitch Animation (Splitting.js) */
[data-char] {
  color: transparent;
  position: relative;
}

[data-char]:after {
  --txt: attr(data-char);
  content: var(--txt);
  color: var(--glitch-final-color);
  position: absolute;
  left: 0;
  top: 0;
  animation-duration: var(--duration-quick);
  animation-delay: var(--duration-base);
  animation-timing-function: steps(1);
  animation-fill-mode: backwards;
}

.glitch-text[data-splitting] [data-char]:after {
  animation-iteration-count: calc(var(--char-index) * 0.55);
  animation-name: glitch-switch;
}

@keyframes glitch-switch {
  0% { content: var(--char-0); }
  10% { content: var(--char-1); }
  20% { content: var(--char-2); }
  30% { content: var(--char-3); }
  40% { content: var(--char-4); }
  50% { content: var(--char-5); }
  60% { content: var(--char-6); }
  70% { content: var(--char-7); }
  80% { content: var(--char-8); }
  90% { content: var(--char-9); }
  100% { content: var(--char-0); }
}

/* --- VHS & Shader Effects (Reusable) --- */
/* Performance optimization: Pause animations when sections not visible */
#intro.paused-animations::before,
#intro.paused-animations::after,
#intro.paused-animations .scanlines,
#intro.paused-animations .vhs-static,
#intro.paused-animations .orb-wrap,
#about.paused-animations .scene .texts,
#about.paused-animations .scene .text,
#about.paused-animations::before,
#about.paused-animations::after,
#about.paused-animations .scanlines,
#about.paused-animations .vhs-static,
#art-direction.paused-animations .scanlines,
#art-direction.paused-animations .vhs-static,
#art-direction.paused-animations .art-cell,
#art-direction.paused-animations .glitch-r,
#art-direction.paused-animations .glitch-g,
#art-direction.paused-animations .glitch-b,
#photo.paused-animations .scanlines,
#photo.paused-animations .vhs-static {
  animation-play-state: paused !important;
}

/* Layer 1: Moving Grid - Shared between intro and about */
#intro::before,
#about::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-image:
    linear-gradient(var(--cyan-alpha-10) 1px, transparent 1px),
    linear-gradient(90deg, var(--magenta-alpha-10) 1px, transparent 1px);
  background-size: var(--grid-size) var(--grid-size);
  animation: gridMove 20s linear infinite;
  z-index: var(--z-layer-1);
}

@keyframes gridMove {
  0% { transform: translate(0, 0); }
  100% { transform: translate(var(--grid-size), var(--grid-size)); }
}

/* Layer 2: Color Shift - Intro section */
#intro::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: linear-gradient(45deg, var(--magenta-alpha-10), var(--cyan-alpha-10), var(--yellow-alpha-10));
  mix-blend-mode: screen;
  animation: colorShift 6s var(--easing-ease-in-out) infinite;
  z-index: var(--z-layer-2);
  pointer-events: none;
}

@keyframes colorShift {
  0%, 100% { filter: hue-rotate(0deg); }
  33% { filter: hue-rotate(120deg); }
  66% { filter: hue-rotate(240deg); }
}

/* Disable filter animation on Safari for better performance */
@supports (-webkit-touch-callout: none) {
  .color-shift {
    animation: none;
    filter: none;
  }
}

/* Layer 3: Scanlines - Uses performant transform instead of position */
.scanlines {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: repeating-linear-gradient(0deg, transparent, transparent 2px, var(--cyan-alpha-10) 2px, var(--cyan-alpha-10) 4px);
  pointer-events: none;
  animation: scanlineMove 0.3s linear infinite;  /* Reduced frequency from 0.1s for Safari performance */
  z-index: var(--z-layer-3);
}

@keyframes scanlineMove {
  0% { transform: translateY(0); }
  100% { transform: translateY(4px); }
}

/* Layer 4: VHS Static - Uses performant transform */
.vhs-static {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: repeating-linear-gradient(90deg, transparent, transparent 2px, var(--white-alpha-01) 2px, var(--white-alpha-01) 4px);
  opacity: 0.5;
  animation: vhsStatic var(--duration-quick) steps(2, end) infinite;
  z-index: var(--z-layer-4);
  pointer-events: none;
}

@keyframes vhsStatic {
  0% { transform: translateX(0); }
  100% { transform: translateX(4px); }
}

/* --- Common Keyframe Animations --- */
@keyframes fadeIn {
  0% { opacity: 0; }
  100% { opacity: 0.8; }
}

@keyframes slideDownFade {
  0% {
    opacity: 0;
    transform: translateY(-100px);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes slideInFromLeft {
  0% {
    opacity: 0;
    transform: translateX(-100px);
  }
  100% {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes slideInFromRight {
  0% {
    opacity: 0;
    transform: translateX(100px);
  }
  100% {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes slideOutToLeft {
  0% { opacity: 1; transform: translateX(0); }
  100% { opacity: 0; transform: translateX(-100px); }
}

@keyframes slideOutToRight {
  0% { opacity: 1; transform: translateX(0); }
  100% { opacity: 0; transform: translateX(100px); }
}

@keyframes slideUpFade {
  0% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-100px); }
}

@keyframes fadeOut {
  0% { opacity: 0.8; }
  100% { opacity: 0; }
}

@keyframes eraseLineVertical {
  0% { opacity: 1; scale: 1 1; }
  100% { opacity: 0; scale: 1 0; }
}

/* ============================================================================
   HEADER & NAVIGATION
   ============================================================================ */
header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  padding: var(--spacing-md) var(--spacing-xl);
  z-index: var(--z-header);
  background: var(--header-bg);
  backdrop-filter: blur(10px);
  transition: background 0.6s ease;
}

/* Safari fallback - disable backdrop-filter for better performance */
@supports (-webkit-touch-callout: none) {
  header {
    backdrop-filter: none;
    background: rgba(0, 18, 25, 0.95);
  }
}

/* Header color changes per section */
header.section-intro { background: var(--header-bg-intro); }
header.section-about { background: var(--header-bg-about); }
header.section-photo { background: transparent; }
header.section-illustration { background: var(--header-bg-illustration); }
header.section-contact { background: var(--header-bg-contact); }

nav {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

/* --- Logo & Navigation Text Elements --- */
.logo,
.nav-btn,
#main-title {
  font-weight: inherit;
  color: var(--white);
}

/* Logo with neon glow effect on hover */
.logo {
  transition: text-shadow var(--transition-base) var(--easing-ease), color var(--transition-base) var(--easing-ease);
}

.logo *,
.logo [data-char],
.logo [data-char]::after {
  pointer-events: none;
}

.logo:hover {
  color: var(--cyan);
  text-shadow: var(--glow-cyan-intense);
}

.logo:hover [data-char]::after {
  color: var(--cyan);
  text-shadow: var(--glow-cyan-intense);
}

/* ── Unified programmatic glitch restart ──────────────────────────────────
   .glitch-suppressed  — JS sets this to quiesce the base page-load animation
                         (animation-name → none so Chrome sees a name change on
                         the next trigger, which is the only cross-browser
                         reliable restart mechanism).
   .glitch-firing      — JS toggles this with a getBoundingClientRect() flush
                         between remove and add; Chrome sees none → glitch-switch
                         and always starts a fresh animation.
   Both classes live on the element that CONTAINS [data-char] children.
   Per-element tuning via CSS custom properties on the element itself:
     --glitch-count   (default 10)  — iteration count
     --glitch-stagger (default .05s) — per-char delay increment             */
.glitch-suppressed [data-char]::after {
  animation-name: none !important;
}
.glitch-suppressed.glitch-firing [data-char]::after {
  animation-name:            glitch-switch !important;
  animation-duration:        0.2s !important;
  animation-timing-function: steps(1) !important;
  animation-iteration-count: var(--glitch-count, 10) !important;
  animation-delay:           calc(var(--char-index) * var(--glitch-stagger, 0.05s)) !important;
  animation-fill-mode:       backwards !important;
}

/* --- Navigation Buttons --- */
.nav-btn {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 1rem;
  font-weight: inherit;
  /* Box hugs the glyphs horizontally; 3px top padding pushes the orange
     top-line away from the text. */
  padding: 3px 0 0;
  line-height: 1;
  color: var(--white);
  display: inline-flex;
  background-image: linear-gradient(var(--orange), var(--orange));
  background-size: 100% 0.5px;
  background-repeat: no-repeat;
  background-position: 100% 0%;
  transition: background-size 0.7s, background-position var(--transition-slow) var(--easing-ease-in-out), color var(--transition-base);
  animation: lineGrow var(--duration-slow) var(--easing-ease-out);
  animation-fill-mode: backwards;
}

@keyframes lineGrow {
  from { background-size: 0% 0.5px; }
  to { background-size: 100% 0.5px; }
}

/* Glitch jitter on hover — fires immediately from frame 0, plays once and resets.
   Targeting .word (Splitting.js inline-block wrappers) keeps lineGrow on the
   parent button completely untouched, so the underline never flickers on mouse-out. */
@keyframes nav-btn-glitch {
  0%        { transform: translateX(-6px) skewX(-2deg);  }
  14%       { transform: translateX(5px)  skewX(1deg);   }
  28%       { transform: translateX(-3px) skewX(0deg);   }
  42%       { transform: translateX(4px)  skewX(-1deg);  }
  56%       { transform: translateX(0px)  skewX(0.5deg); }
  70%       { transform: translateX(-2px) skewX(0deg);   }
  84%, 100% { transform: none;                           }
}

.main-nav .nav-btn:hover .word {
  animation: nav-btn-glitch 0.4s steps(1, end) 1;
}

/* Staggered animation delays for navigation buttons */
.main-nav .nav-btn:nth-child(1) { animation-delay: 0s; }
.main-nav .nav-btn:nth-child(2) { animation-delay: 0.15s; }
.main-nav .nav-btn:nth-child(3) { animation-delay: 0.3s; }
.main-nav .nav-btn:nth-child(4) { animation-delay: 0.45s; }
.main-nav .nav-btn:nth-child(5) { animation-delay: 0.6s; }
.main-nav .nav-btn:nth-child(6) { animation-delay: 0.75s; }

.nav-btn:hover {
  background-size: 100% 100%;
  background-position: 0% 100%;
  color: var(--bg);
  transition: background-position 0.7s, background-size var(--transition-slow) var(--easing-ease-in-out), color var(--transition-base);
}

.nav-btn.active {
  background-size: 100% 100%;
  background-position: 0% 100%;
  color: var(--bg);
}

.nav-btn.active [data-char]::after {
  color: var(--bg);
}

.nav-btn:hover [data-char]:after {
  color: var(--bg);
}

.main-nav {
  display: flex;
  gap: var(--spacing-xl);
  align-items: center;
}

.language-toggle {
  display: flex;
  gap: var(--spacing-sm);
  align-items: center;
}

/* ─── Sound toggle button ──────────────────────────────────────────────────── */
.sound-btn {
  padding: 5px 7px 5px 12px;
  line-height: 0;
  margin-left: 4px;
  border-left: 1px solid rgba(255, 255, 255, 0.15);
}

.sound-icon {
  width: 18px;
  height: 18px;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  display: block;
}

/* Waves: hidden when sound is off (default) */
.sound-icon .wave {
  opacity: 0;
  transition: opacity var(--transition-base) var(--easing-ease-in-out);
}

/* Mute slash: visible when sound is off */
.sound-icon .mute-slash {
  opacity: 1;
  transition: opacity var(--transition-base) var(--easing-ease-in-out);
}

/* Sound ON: full orange fill, icon turns dark (mirrors nav-btn hover state) */
.sound-btn.active {
  background-size: 100% 100%;
  background-position: 0% 100%;
  color: var(--bg);
  transition: background-position 0.7s, background-size var(--transition-slow) var(--easing-ease-in-out), color var(--transition-base);
}

.sound-btn.active .wave {
  opacity: 1;
}

.sound-btn.active .mute-slash {
  opacity: 0;
}

/* ============================================================================
   INTRO SECTION
   ============================================================================ */
#intro {
  position: sticky;
  top: 0;
  z-index: var(--z-layer-5);
  min-height: 100vh;
  background: transparent;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

/* --- Main Title --- */
#main-title {
  font-size: clamp(2rem, 6vw, 4.5rem);
  font-weight: inherit;
  text-align: center;
  z-index: var(--z-layer-5);
  color: var(--white);
  text-shadow: var(--text-shadow-dark);
}

.subtitle-glitch {
  font-size: 1.5rem;
  margin-top: var(--spacing-xs);
  position: relative;
  z-index: var(--z-layer-5);
  text-shadow: var(--text-shadow-dark);
}

/* --- Intro: Dual Affordance CTAs --- */
.intro-otw-link {
  text-decoration: none;
  display: block;
  text-align: center;
  position: relative;
  z-index: var(--z-layer-5);
}

.intro-otw-link .subtitle-glitch {
  color: var(--white);
  transition: color var(--transition-base), text-shadow var(--transition-base);
}

.intro-otw-link:hover .subtitle-glitch {
  color: var(--accent-human);
  text-shadow: var(--glow-amber);
}

.intro-otw-link:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 6px;
  border-radius: 2px;
}

.intro-work-cta {
  margin-top: var(--spacing-lg);
  position: relative;
  z-index: var(--z-layer-5);
  text-shadow: var(--text-shadow-dark);
}

.intro-work-cta:hover {
  text-shadow: none;
}

.intro-work-cta:focus-visible {
  outline: 2px solid var(--orange);
  outline-offset: 6px;
  border-radius: 2px;
}

/* --- Particle System Canvas --- */
#particleCanvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: var(--z-background);
  pointer-events: none;
  opacity: 0.8;
  will-change: transform;  /* GPU acceleration hint */
}

#barcodeCanvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: var(--z-layer-1);   /* above particle canvas (z:0), below text (z:5) */
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.8s ease;
  mix-blend-mode: screen;       /* enriches dark areas; white/bright content unaffected */
  will-change: opacity;
}

/* --- 3D Particle Orb --- */
.orb-wrap {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  transform-style: preserve-3d;
  perspective: 1000px;
  animation: orb-rotate 14s infinite linear 4s;
  z-index: var(--z-layer-1);
  pointer-events: none;
  will-change: transform;  /* GPU acceleration hint */
}

.orb-wrap .orb-particle {
  animation-play-state: inherit;
}

@keyframes orb-rotate {
  100% { transform: rotateY(360deg) rotateX(360deg); }
}

.orb-particle {
  position: absolute;
  width: 2px;
  height: 2px;
  border-radius: 50%;
  opacity: 0;
  will-change: transform, opacity;  /* GPU acceleration hint */
}


/* --- Cyberpunk Panel Animation --- */
.cyber-panel {
  position: absolute;
  top: 50%;
  left: var(--spacing-sm);
  transform: translateY(-50%);
  height: 200px;
  display: flex;
  align-items: stretch;
  z-index: var(--z-layer-10);
  pointer-events: none;
  opacity: 0;
}

.cyber-panel.active {
  opacity: 1;
}

/* Sidebar that slides in first */
.cyber-panel__sidebar {
  width: var(--border-thick);
  background: var(--orange);
  border: var(--border-thin) solid var(--orange);
  backdrop-filter: saturate(180%) blur(6px);
  transform: translateX(-100%);
  mask: linear-gradient(var(--white), hsl(0 0% 100% / 0.6) 15% 95%, var(--white));
  /* 0.6s — whole frame sequence runs at half speed (user request 2026-06-10) */
  transition: transform 0.6s var(--easing-ease-out);
  cursor: pointer;
  box-shadow: var(--glow-orange);
}

/* Safari fallback - disable backdrop-filter */
@supports (-webkit-touch-callout: none) {
  .cyber-panel__sidebar {
    backdrop-filter: none;
  }
}

.cyber-panel.active .cyber-panel__sidebar {
  transform: translateX(0);
}

/* Main panel body - slides in then stretches
   NOTE: Uses height animation (causes reflow) for deliberate stretching effect.
   Alternative transform: scaleY would scale content, altering visual appearance. */
.cyber-panel__body {
  width: 36px;
  position: relative;
  transform: translateX(calc(-100% - 4px));
  cursor: pointer;
}

.cyber-panel.active .cyber-panel__body {
  animation: panelSlideAndStretch 1.8s var(--easing-ease-out) 0.6s forwards;
}

@keyframes panelSlideAndStretch {
  0% {
    transform: translateX(calc(-100% - 4px)) translateY(0);
    height: 200px;
  }
  33% {
    transform: translateX(0) translateY(0);
    height: 200px;
  }
  100% {
    /* Viewport-relative stretch: pins the frame top at 110px (~30px below the
       fixed header — half the original gap) and the bottom 24px above the
       viewport edge — below the info-interface badge (bottom: 40px), so the
       bottom line never crosses it. */
    transform: translateX(0) translateY(calc(210px - 50vh));
    height: max(200px, calc(100vh - 134px));
  }
}

/* Panel backdrop with blur and clipped corners */
.cyber-panel__backdrop {
  position: relative;
  width: 100%;
  height: 100%;
  backdrop-filter: saturate(180%) blur(10px);
  background: var(--black-alpha-60);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 8px), calc(100% - 8px) 100%, 0% 100%);
  border: var(--border-medium) solid var(--orange);
  box-shadow: var(--glow-orange);
}

/* Safari fallback - disable backdrop-filter and use more opaque background */
@supports (-webkit-touch-callout: none) {
  .cyber-panel__backdrop {
    backdrop-filter: none;
    background: rgba(0, 18, 25, 0.9);
  }
}

/* Accent border effect */
.cyber-panel__backdrop::before {
  content: '';
  position: absolute;
  inset: -2px;
  background: linear-gradient(135deg, var(--orange) 0%, var(--orange-brown) 100%);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 8px), calc(100% - 8px) 100%, 0% 100%);
  z-index: -1;
  opacity: 0.3;
}

/* Accent tab on right side */
.cyber-panel__backdrop::after {
  content: '';
  position: absolute;
  left: 100%;
  top: 2rem;
  width: var(--border-thick);
  height: 40%;
  background: var(--orange);
  opacity: 0.8;
  clip-path: polygon(0 0, 0 100%, 100% calc(100% - 6px), 100% 6px);
}

/* Clipped corner decoration */
.cyber-panel__backdrop .corner {
  position: absolute;
  bottom: 0;
  right: 0;
  height: 8px;
  width: 8px;
}

.cyber-panel__backdrop .corner::after {
  content: '';
  height: var(--border-thick);
  width: 200%;
  position: absolute;
  top: 50%;
  left: 50%;
  translate: -50% -50%;
  transform: rotate(135deg);
  background: var(--orange);
}

/* Date in corner left bar */
.date {
  position: absolute;
  top: calc(50% - 35px); /* Moved 50px towards the top */
  left: 50%;
  transform: translate(-50%, -50%) rotate(-90deg);
  transform-origin: center;
  font-size: 12px;
  opacity: 0.8;
  font-family: 'Funnel Display', sans-serif;
  font-size: 10px;
  color: #9B2226;
  z-index: var(--z-layer-10);
  letter-spacing: 0.15em;
  white-space: nowrap;
}

/* Delay date glitch animation until after panel animations complete */
.date.glitch-text[data-splitting] [data-char]:after {
  animation-delay: 4.7s; /* Starts after panel body stretch (2s + 0.6s + 1.8s = 4.4s) + small buffer */
  color: #9B2226 !important; /* Override white glitch color with burgundy red */
}

/* --- Panel Frame Lines --- */
/* All lines use performant scale transform instead of width/height animation */
.panel-line {
  position: absolute;
  background: var(--orange);
  opacity: 0;
  pointer-events: auto;
  transition: box-shadow var(--transition-base) var(--easing-ease), filter var(--transition-base) var(--easing-ease);
  cursor: pointer;
}

/* Top horizontal line */
.panel-line--top {
  top: 0;
  left: 100%;
  height: var(--border-medium);
  width: calc(100vw - 100px);
  transform-origin: left;
  scale: 0 1;
  box-shadow: var(--glow-orange);
}

/* Bottom horizontal line */
.panel-line--bottom {
  bottom: 0;
  left: 100%;
  height: var(--border-medium);
  width: calc(100vw - 330px);
  transform-origin: left;
  scale: 0 1;
  box-shadow: var(--glow-orange);
}

/* Dotted line section at the end of bottom line */
.panel-line--bottom::after {
  content: '';
  position: absolute;
  right: -84px;
  bottom: 0;
  height: var(--border-medium);
  width: 75px;
  background: repeating-linear-gradient(to right, var(--orange) 0px, var(--orange) 8px, transparent 8px, transparent 16px);
  box-shadow: var(--glow-orange);
}

/* Right vertical line */
.panel-line--right {
  /* Stops 108px above the frame bottom (frame bottom is 24px above the
     viewport edge) so the line ends clear above the social icons. */
  bottom: 108px;
  /* The panel body sits 20px from the viewport's left edge (16px panel offset
     + 4px sidebar), so subtract it here to land 45px from the right edge —
     where the top line ends. */
  left: calc(100vw - 65px);
  height: calc(100% - 108px);
  width: var(--border-medium);
  transform-origin: bottom;
  scale: 1 0;
  box-shadow: var(--glow-orange);
}

/* Mini bar decoration on right line */
.panel-line--right::before {
  content: '';
  position: absolute;
  left: 100%;
  top: 2rem;
  width: var(--border-thick);
  height: 40%;
  background: var(--orange);
  opacity: 0.8;
  clip-path: polygon(0 0, 0 100%, 100% calc(100% - 6px), 100% 6px);
}

/* Animate lines after panel extends - Uses performant scale property */
.cyber-panel.active .panel-line--top {
  animation: drawLineHorizontal 0.8s var(--easing-ease-out) 2.4s forwards;
}

.cyber-panel.active .panel-line--bottom {
  animation: drawLineHorizontal 0.8s var(--easing-ease-out) 2.6s forwards;
}

.cyber-panel.active .panel-line--right {
  animation: drawLineVertical 1s var(--easing-ease-out) 2.8s forwards;
}

.cyber-panel.active .panel-line--right::before {
  animation: fadeIn 0.4s var(--easing-ease-out) 3.2s forwards;
}

@keyframes drawLineHorizontal {
  0% {
    opacity: 1;
    scale: 0 1;
  }
  100% {
    opacity: 1;
    scale: 1 1;
  }
}

@keyframes drawLineVertical {
  0% {
    opacity: 1;
    scale: 1 0;
  }
  100% {
    opacity: 1;
    scale: 1 1;
  }
}

/* --- Info Interface Badge --- */
.info-interface {
  position: absolute;
  bottom: 40px;
  left: 70px;
  width: 380px;
  z-index: var(--z-layer-10);
  opacity: 0;
  transform: translateY(-60px);
  pointer-events: none;
  /* Pre-promote to its own compositing layer so the entrance doesn't trigger repaints */
  will-change: transform, opacity;
}

.info-interface.active {
  /* Single keyframe owns both transform and opacity — no property-conflict jank */
  animation: infoEntrance 0.75s var(--ease-expo-out) forwards;
}

@keyframes infoEntrance {
  0%   { opacity: 0;   transform: translateY(-60px); }
  100% { opacity: 1;   transform: translateY(0);     }
}

/* Shimmer runs on the SVG child after entrance finishes (delay = entrance duration).
   Keeping it on a separate element avoids conflicting with the parent's transform. */
.info-interface.active .info-interface-svg {
  animation: id1Shimmer 3s ease-in-out 0.75s infinite;
}

/* ── Content lines: always start hidden ───────────────────────── */
.inf-lang .info-line {
  opacity: 0;
  transform: translateX(-14px);
}
.inf-lang .info-line--rule {
  transform: scaleX(0);
  transform-origin: left center;
  transform-box: fill-box;
}

/* ── Entrance: JS adds .inf-lang--entering to trigger stagger ──── */
/* delay var(--line-offset) is set by JS: 0.75s on first load, 0s on language switch */
.inf-lang--entering .info-line {
  animation: infoLineIn 0.4s var(--ease-expo-out)
    calc(var(--line-offset, 0s) + var(--i) * 0.07s) forwards;
}
.inf-lang--entering .info-line--rule {
  animation: infoRuleDraw 0.5s var(--ease-expo-out)
    calc(var(--line-offset, 0s) + var(--i) * 0.07s) forwards;
}

@keyframes infoLineIn {
  to { opacity: 1; transform: translateX(0); }
}
@keyframes infoRuleDraw {
  to { opacity: 0.35; transform: scaleX(1); }
}

/* ── Exit: JS adds .inf-lang--exiting to the outgoing group ────── */
.inf-lang--exiting {
  animation: infoLangFlickerOut 0.45s ease-in forwards;
}
@keyframes infoLangFlickerOut {
  0%   { opacity: 1;   transform: translateX(0);   }
  18%  { opacity: 0.1; transform: translateX(4px);  }
  35%  { opacity: 0.7; transform: translateX(-3px); }
  55%  { opacity: 0;   transform: translateX(2px);  }
  100% { opacity: 0;   transform: translateX(0);    }
}

.info-interface-svg {
  width: 100%;
  height: auto;
  display: block;
  /* Static glow — not animated, so no per-frame filter repaint during entrance */
  filter:
    drop-shadow(0 0 5px rgba(255, 60, 0, 0.5))
    drop-shadow(0 0 16px rgba(255, 60, 0, 0.2));
}

@media (max-height: 680px) {
  .info-interface { display: none; }
}

/* ── Info-interface interactive hotspots ─────────────────────────────── */
.info-interface.active #inf-hotspots { pointer-events: all; }

.inf-hotspot {
  cursor: pointer;
  fill: transparent;
  transition: fill 0.2s ease;
}
.inf-hotspot:hover {
  fill: rgba(255, 60, 0, 0.07);
}

/* Info-hint targets use a tighter stagger to feel snappier across many chars */
.main-nav .nav-btn,
.lang-btn,
.intro-work-cta {
  --glitch-stagger: 0.04s;
  --glitch-count: 8;
}

/* ─── Hamburger button ─────────────────────────────────────────────────────── */
.hamburger-btn {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 36px;
  height: 36px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
  color: var(--white);
  z-index: calc(var(--z-header) + 2);
  position: relative;
  flex-shrink: 0;
}

.hamburger-btn:focus-visible {
  outline: 1px solid var(--amber);
  outline-offset: 3px;
}

.hamburger-line {
  display: block;
  width: 22px;
  height: 1.5px;
  background: currentColor;
  border-radius: 1px;
  transform-origin: center;
  transition: transform 0.3s var(--ease-material),
              opacity   0.2s ease,
              width     0.3s ease;
}

/* X morph when open */
.hamburger-btn[aria-expanded="true"] .hamburger-line:nth-child(1) {
  transform: translateY(6.5px) rotate(45deg);
}
.hamburger-btn[aria-expanded="true"] .hamburger-line:nth-child(2) {
  opacity: 0;
  width: 0;
}
.hamburger-btn[aria-expanded="true"] .hamburger-line:nth-child(3) {
  transform: translateY(-6.5px) rotate(-45deg);
}

/* ─── Nav backdrop ─────────────────────────────────────────────────────────── */
.nav-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 10, 15, 0.65);
  backdrop-filter: blur(2px);
  z-index: calc(var(--z-header) - 1);
  opacity: 0;
  transition: opacity 0.3s ease;
}

.nav-backdrop.is-visible {
  opacity: 1;
}

/* ── Target glow applied by JS (.info-glow) — matches logo hover cyan ── */
.nav-btn.info-glow,
.lang-btn.info-glow,
.intro-work-cta.info-glow {
  color: var(--cyan) !important;
  text-shadow: var(--glow-cyan-intense) !important;
  transition: color 0.25s ease, text-shadow 0.25s ease !important;
}
.nav-btn.info-glow [data-char]::after,
.lang-btn.info-glow [data-char]::after,
.intro-work-cta.info-glow [data-char]::after {
  color: var(--cyan) !important;
}
/* Sound toggle: icon replaced by scrambling char when info hotspot is hovered */
.sound-glitch-char {
  display: none;
  width: 18px;
  height: 18px;
  line-height: 18px;
  text-align: center;
  font-family: 'Funnel Display', sans-serif;
  font-size: 14px;
  color: var(--cyan);
}
#sound-toggle.sound-glitching .sound-icon { display: none; }
#sound-toggle.sound-glitching .sound-glitch-char { display: block; }

#sound-toggle.info-glow {
  filter: drop-shadow(0 0 8px var(--cyan)) drop-shadow(0 0 20px var(--cyan)) !important;
  transition: filter 0.25s ease !important;
}
#sound-toggle.info-glow .sound-icon {
  stroke: var(--cyan);
  transition: stroke 0.25s ease;
}
/* --- Social Media Icons --- */
.social-media-icons {
  position: absolute;
  bottom: var(--spacing-lg);
  right: 35px;
  margin: 0;
  padding: 0;
  display: flex;
  gap: var(--spacing-lg);
  z-index: var(--z-layer-15);
  opacity: 0;
  transform: scale(0);
  transition: opacity var(--transition-slower) var(--easing-ease-out), transform var(--transition-slower) var(--easing-ease-out);
}

/* Animate icons when panel becomes active */
.cyber-panel.active ~ .social-media-icons {
  /* 3s — keeps "icons after bottom line passes" order at the half-speed timeline */
  animation: socialIconsGrow var(--duration-moderate) var(--easing-ease-out) 3s forwards;
}

@keyframes socialIconsGrow {
  0% {
    opacity: 0;
    transform: scale(0);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}

/* Auto-play hover animation on load */
.cyber-panel.active ~ .social-media-icons li a {
  animation: iconColorChange var(--duration-slow) var(--easing-ease-in-out) 3s;
}

@keyframes iconColorChange {
  0%, 100% {
    color: var(--orange);
    text-shadow: none;
  }
  50% {
    color: var(--cyan);
    text-shadow: var(--glow-cyan-intense);
  }
}

/* Horizontal spans - disappear then reappear on load */
.cyber-panel.active ~ .social-media-icons li a span:nth-child(1),
.cyber-panel.active ~ .social-media-icons li a span:nth-child(3) {
  animation: spanHorizontalDisappear var(--duration-slow) var(--easing-ease-in-out) 1.5s;
}

@keyframes spanHorizontalDisappear {
  0%, 100% {
    background: var(--orange);
    box-shadow: var(--glow-orange);
    transform: scaleX(1);
  }
  50% {
    background: var(--cyan);
    box-shadow: var(--glow-cyan);
    transform: scaleX(0);
  }
}

/* Vertical spans - appear then disappear on load */
.cyber-panel.active ~ .social-media-icons li a span:nth-child(2),
.cyber-panel.active ~ .social-media-icons li a span:nth-child(4) {
  animation: spanVerticalAppear var(--duration-slow) var(--easing-ease-in-out) 1.5s;
}

@keyframes spanVerticalAppear {
  0%, 100% {
    background: var(--orange);
    box-shadow: var(--glow-orange);
    transform: scale(0);
  }
  50% {
    background: var(--cyan);
    box-shadow: var(--glow-cyan);
    transform: scale(1);
  }
}

.social-media-icons li {
  list-style: none;
}

.social-media-icons li a {
  display: block;
  position: relative;
  width: var(--icon-size);
  height: var(--icon-size);
  line-height: var(--icon-size);
  font-size: 28px;
  text-align: center;
  text-decoration: none;
  color: var(--orange);
  transition: var(--transition-slow);
}

.social-media-icons li a span {
  position: absolute;
  transition: transform var(--transition-slow);
}

/* Horizontal spans (top and bottom) */
.social-media-icons li a span:nth-child(1),
.social-media-icons li a span:nth-child(3) {
  width: 100%;
  height: var(--border-medium);
  background: var(--orange);
  box-shadow: var(--glow-orange);
}

.social-media-icons li a span:nth-child(1) {
  top: 0;
  left: 0;
  transform-origin: right;
}

.social-media-icons li a:hover span:nth-child(1) {
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--transition-slow);
}

.social-media-icons li a span:nth-child(3) {
  bottom: 0;
  left: 0;
  transform-origin: left;
}

.social-media-icons li a:hover span:nth-child(3) {
  transform: scaleX(0);
  transform-origin: right;
  transition: transform var(--transition-slow);
}

/* Vertical spans (left and right) */
.social-media-icons li a span:nth-child(2),
.social-media-icons li a span:nth-child(4) {
  width: var(--border-medium);
  height: 100%;
  background: var(--orange);
  box-shadow: var(--glow-orange);
}

.social-media-icons li a span:nth-child(2) {
  top: 0;
  left: 0;
  transform: scale(0);
  transform-origin: bottom;
}

.social-media-icons li a:hover span:nth-child(2) {
  transform: scale(1);
  transform-origin: top;
  transition: transform var(--transition-slow);
}

.social-media-icons li a span:nth-child(4) {
  top: 0;
  right: 0;
  transform: scale(0);
  transform-origin: top;
}

.social-media-icons li a:hover span:nth-child(4) {
  transform: scale(1);
  transform-origin: bottom;
  transition: transform var(--transition-slow);
}

/* Icon hover — human social links use amber (content state, not machine state) */
.social-media-icons .instagram:hover,
.social-media-icons .linkedin:hover {
  color: var(--accent-human);
  text-shadow: var(--glow-amber);
}

.social-media-icons .instagram:hover span,
.social-media-icons .linkedin:hover span {
  background: var(--accent-human);
  box-shadow: var(--glow-amber);
}

/* Social media SVG icons */
.social-media-icons .social-icon {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 28px;
  height: 28px;
  transition: filter var(--transition-slow);
}

.social-media-icons .instagram:hover .social-icon,
.social-media-icons .linkedin:hover .social-icon {
  filter: drop-shadow(0 0 5px var(--accent-human)) drop-shadow(0 0 10px var(--accent-human)) drop-shadow(0 0 20px var(--accent-human));
}

/* ============================================================================
   ABOUT SECTION
   ============================================================================ */
.about-pin-wrapper {
  position: relative;
  z-index: var(--z-layer-10);
  /* height set by JS = section height + scroll distance for animation */
}

#about {
  position: sticky;
  top: var(--header-height, 60px);
  z-index: var(--z-layer-10);
  min-height: 100vh;
  display: flex;
  flex-direction: column;          /* top block, then the two columns, stacked */
  align-items: center;
  justify-content: flex-start;
  gap: var(--spacing-xl);
  overflow: clip;
  background: var(--black);
  padding: var(--spacing-3xl) var(--spacing-2xl) 100px var(--spacing-2xl);
}

/* Layer 2: About section - Background image */
#about::after {
  content: "";
  position: absolute;
  top: 0px;
  left: 50px;
  right: 50px;
  bottom: 0px;
  background-image: url('../images/sergio-ayala-vintage-tin-robot-amber-duotone-about.webp');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  mix-blend-mode: screen;
  z-index: var(--z-layer-2);
  pointer-events: none;
}

/* VHS effect layers for about section */
#about .scanlines,
#about .vhs-static {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

#about .scanlines {
  z-index: var(--z-layer-3);
}

#about .vhs-static {
  z-index: var(--z-layer-4);
}

/* --- About TOP — single full-width column: (title + id badge) · quote · bar1 --- */
.about-top {
  position: relative;
  z-index: var(--z-layer-5);
  /* Grow leftward and left-align so the container's left border meets the
     vertical bar2 svg (left:80px, ~117.5px right edge) without moving it.
     margin-left ≈ bar2 right edge − section padding-left (64px) = ~54px.
     This single value is the knob to nudge the border onto bar2. */
  align-self: stretch;
  width: auto;
  max-width: none;
  margin-left: 54px;
  margin-right: 0;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
  padding: var(--spacing-md);
}

/* Title on the left, id badge always pinned to the top-right (no wrap so the
   badge never drops to a second line; the intro shrinks to make room instead). */
.about-top-head {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-xl);
  flex-wrap: nowrap;
}

.about-top .decoration-bar1 { align-self: stretch; }

/* Title + paragraph share one height (align-items:stretch). Kept separate from
   the badge so the badge isn't vertically distorted. */
.about-intro {
  display: flex;
  align-items: stretch;
  gap: var(--spacing-xl);
  flex: 1 1 auto;
  min-width: 0;
  flex-wrap: wrap;
}

/* Title keeps its content width (never shrinks under the paragraph) */
.about-top-head #abouttitle { flex: 0 0 auto; }

/* Intro paragraph — to the right of the title, matched to its height */
.about-top-head #aboutp1 {
  flex: 1 1 320px;
  min-width: 260px;
  max-width: 60ch;
  align-self: stretch;     /* same height as the title */
  margin: 15px 0 0;        /* nudged 15px down */
  opacity: 1;
  transform: none;
}

/* --- About Container Layout (two columns) --- */
.about-container {
  position: relative;
  z-index: var(--z-layer-5);
  display: grid;
  grid-template-columns: 1fr 1fr;
  --about-col-gap: clamp(2rem, 5vw, var(--spacing-2xl));
  column-gap: var(--about-col-gap);
  row-gap: var(--spacing-lg);
  /* Match the top container's horizontal bounds: left column's left edge and
     right column's right edge line up with .about-top (same 54px left start,
     stretched to the same right edge). */
  align-self: stretch;
  width: auto;
  max-width: none;
  margin-left: 54px;
  margin-right: 0;
  align-items: start;
}

.about-left {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
  min-width: 0;            /* allow the grid track to shrink; no overflow */
  padding: var(--spacing-md);
}

.about-left > p {
  max-width: 60ch;         /* fluid reading measure — never overflows the column */
  width: 100%;
  text-align: inherit;
}

.about-right {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
  padding: clamp(1.5rem, 2.5vw, 40px);
  margin-top: 0;
  margin-left: 0;
  min-width: 0;
}

/* Animated vertical line — centered in the gap between the two columns */
.about-right::before {
  content: '';
  position: absolute;
  left: calc(var(--about-col-gap, 2rem) / -2 - var(--border-medium) / 2);
  top: 0;
  width: var(--border-medium);
  height: 100%;
  background: #ee9b00;
  transform-origin: top;
  scale: 1 0;
  opacity: 0;
  box-shadow: 0 0 15px #ee9b00, 0 0 30px #ee9b00;  /* Optimized: reduced from 3 to 2 layers */
  pointer-events: none;
}

/* Animate line when right column becomes visible in viewport */
#about .about-right.element-visible::before {
  animation: drawLineVertical var(--duration-base) var(--easing-ease-out) 0s forwards;
}

#about .about-right.element-exit::before {
  animation: eraseLineVertical 0.5s ease-in 0s forwards;
}

/* --- About Typography --- */
#about h2 {
  font-size: 5rem;
  font-weight: 800;
  color: var(--cyan);
  margin-bottom: var(--spacing-xs);
  text-shadow: none;
  line-height: 1.2;
}

#about p:not(.ending) {
  font-size: 1.1rem;
  line-height: 1.8;
  color: #001219;
}

/* Specific color for aboutp4 paragraph */
#about #aboutp4 {
  color: #ffffff !important;
  opacity: 0;
  transform: translateX(100px);
}

/* --- About Title (Left Column) --- */
#about #abouttitle {
  color: #001219;
  /* Collapse the trailing space the stacked title's negative line offsets
     reserve, so the badge/paragraphs below follow tightly at any resolution.
     The title is fixed-size, so this single value is stable across widths. */
  margin-bottom: -90px;
}

/* Prevent about section h2 glitch on page load */
#about h2.glitch-text[data-splitting] [data-char]:after {
  animation-name: none;
}

/* Enable glitch animation when element is visible in viewport */
#about h2.glitch-text[data-splitting].element-visible [data-char]:after {
  animation-name: glitch-switch;
}

/* Apply dark color and font-weight to glitch pseudo-elements */
#about #abouttitle[data-splitting] [data-char]:after {
  color: #001219 !important;
  font-weight: 800;
  animation-name: none;
}

/* Enable glitch animation for abouttitle when element is visible in viewport */
#about #abouttitle[data-splitting].element-visible [data-char]:after {
  animation-name: glitch-switch;
  animation-duration: var(--duration-quick);
  animation-delay: var(--duration-base);
  animation-timing-function: steps(1);
  animation-fill-mode: backwards;
  animation-iteration-count: calc(var(--char-index) * 0.55);
}

/* Individual line sizes for abouttitle - Edit these to control each line independently */
#about #abouttitle .title-line-1 {
  font-size: 1.20em; /* Hello! */
  position: relative;
  top: 0;
  left: 0;
  display: inline-block;
  width: max-content; /* size to the text so it can't overflow onto neighbours */
  opacity: 0;
  transform: translateY(-100px);
}

#about #abouttitle .title-line-2 {
  font-size: 2.2em;
  margin-top: -70px; /* I'm */
  position: relative;
  top: 0;
  left: 0;
  margin-left: -7px;
  display: inline-block;
  width: max-content; /* size to the text */
  opacity: 0;
  transform: translateY(-100px);
}

#about #abouttitle .title-line-3 {
  font-size: 1em; /* Sergio */
  position: relative;
  top: -64px; /* Moves "Sergio" up by 70px */
  left: 0;
  display: inline-block;
  width: max-content; /* size to the text */
  opacity: 0;
  transform: translateY(-100px);
}

#about #abouttitle .title-line-4 {
  font-size: 1.1em; /* Ayala */
  position: relative;
  top:-90px;
  left: 0;
  display: inline-block;
  width: max-content; /* size to the text */
  opacity: 0;
  transform: translateY(-100px);
}

/* Slide down animation when title becomes visible in viewport */
#about #abouttitle.element-visible .title-line-1 {
  animation: slideDownFade var(--duration-rise) ease-out 0s forwards;
}

#about #abouttitle.element-visible .title-line-2 {
  animation: slideDownFade var(--duration-rise) ease-out 0.15s forwards;
}

#about #abouttitle.element-visible .title-line-3 {
  animation: slideDownFade var(--duration-rise) ease-out 0.3s forwards;
}

#about #abouttitle.element-visible .title-line-4 {
  animation: slideDownFade var(--duration-rise) ease-out 0.45s forwards;
}

#about #abouttitle.element-exit .title-line-1 {
  animation: slideUpFade 0.5s ease-in 0s forwards;
}

#about #abouttitle.element-exit .title-line-2 {
  animation: slideUpFade 0.5s ease-in 0.05s forwards;
}

#about #abouttitle.element-exit .title-line-3 {
  animation: slideUpFade 0.5s ease-in 0.1s forwards;
}

#about #abouttitle.element-exit .title-line-4 {
  animation: slideUpFade 0.5s ease-in 0.15s forwards;
}

/* --- About Paragraphs (Left Column) --- */
.about-left > p {
  color: #001219;
  position: relative;     /* flows naturally now — px offsets removed */
  opacity: 0;
  transform: translateX(-100px);
}

/* Paragraph measures — fluid, capped for readability (was fixed 450/550px) */
#aboutp1 { max-width: 58ch; }

#aboutp2 { max-width: 58ch; }

#aboutp3 { max-width: 58ch; }

/* --- Availability Badge --- */
#about-availability {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: 'Funnel Display', sans-serif;
  font-size: 0.82rem;
  color: #001219;
  opacity: 0;
  transform: translateX(-30px);
}

.availability-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #52b788;
  flex-shrink: 0;
  animation: availabilityPulse 2.2s ease-in-out infinite;
}

@keyframes availabilityPulse {
  0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(82, 183, 136, 0.6); }
  50%       { opacity: 0.7; box-shadow: 0 0 0 5px rgba(82, 183, 136, 0); }
}

#about-availability.element-visible {
  animation: slideInFromLeft 0.6s ease-out 0.2s forwards;
}

#about-availability.element-exit {
  animation: slideOutToLeft 0.4s ease-in 0s forwards;
}

/* --- Metrics Strip --- */
#about-metrics {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  font-family: 'Funnel Display', sans-serif;
  font-size: 0.85rem;
  color: #001219;
  opacity: 0;
  transform: translateX(-30px);
}

.metrics-sep {
  color: #EE9B00;
  font-weight: 700;
}

#about-metrics.element-visible {
  animation: slideInFromLeft 0.6s ease-out 0.1s forwards;
}

#about-metrics.element-exit {
  animation: slideOutToLeft 0.4s ease-in 0s forwards;
}


/* ─────────────────────────────────────────────────────────────────────────────
   SEAL CTA — "Let's Work Together"
   Digital lacre / wax-seal aesthetic with cyberpunk glitch hover
───────────────────────────────────────────────────────────────────────────── */

.seal-cta-wrapper {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: var(--spacing-lg);
  padding: 70px 0 30px;
}

.about-social-icons {
  position: static;
  opacity: 1;
  transform: none;
  animation: none;
}

.about-social-icons li a {
  color: #001219;
}

.about-social-icons li a span:nth-child(1),
.about-social-icons li a span:nth-child(3),
.about-social-icons li a span:nth-child(2),
.about-social-icons li a span:nth-child(4) {
  background: #001219;
  box-shadow: none;
}

#seal-cta {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 220px;
  height: 220px;
  text-decoration: none;
  cursor: pointer;
  opacity: 0;
  transform: scale(0.75) rotate(-8deg);
  filter: drop-shadow(0 0 6px rgba(238, 155, 0, 0.2));
  will-change: transform, opacity, filter;
}

#seal-cta.element-visible {
  opacity: 1;
  animation: seal-entrance 0.85s var(--ease-spring) 0.1s forwards;
}

#seal-cta.element-exit {
  animation: fadeOut 0.4s ease-in 0s forwards;
}

.seal-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.seal-ring-group {
  transform-origin: 100px 100px;
  animation: seal-ring-spin 28s linear infinite;
}

.seal-ring-outer {
  fill: none;
  stroke: #EE9B00;
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
}

.seal-ticks line {
  stroke: #EE9B00;
  stroke-width: 1.5;
  stroke-linecap: round;
  transition: stroke 0.2s;
}

.seal-ring-inner {
  fill: none;
  stroke: #CA6702;
  stroke-width: 1;
}

.seal-body {
  fill: #001219;
  stroke: #005F73;
  stroke-width: 0.5;
}

.seal-circuit {
  stroke: #005F73;
  stroke-width: 1;
  stroke-linecap: round;
  transition: stroke 0.2s;
}

.seal-circuit-dot {
  fill: #005F73;
  transition: fill 0.2s;
}

.seal-monogram {
  font-family: 'Funnel Display', sans-serif;
  font-size: 11px;
  letter-spacing: 0.2em;
  fill: #94D2BD;
}

.seal-sep {
  stroke: #EE9B00;
  stroke-width: 0.8;
  stroke-dasharray: 3 2;
  opacity: 0.6;
}

.seal-text-layer {
  position: absolute;
  top: 57%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  pointer-events: none;
}

.seal-label {
  display: block;
  font-family: 'Funnel Display', sans-serif;
  font-size: 0.75rem;
  letter-spacing: 0.15em;
  color: #E9D8A6;
  line-height: 1.4;
  white-space: nowrap;
}

/* Suppress glitch on page load; re-enable when seal scrolls into view */
#seal-cta .seal-label[data-splitting] [data-char]:after {
  color: #E9D8A6;
  transition: color 0.2s;
  animation-name: none;
}

#seal-cta.element-visible .seal-label[data-splitting] [data-char]:after {
  animation-name: glitch-switch;
  animation-duration: var(--duration-quick);
  animation-delay: var(--duration-base);
  animation-timing-function: steps(1);
  animation-fill-mode: backwards;
  animation-iteration-count: calc(var(--char-index) * 0.55);
}

#seal-cta:hover .seal-ring-group {
  animation: seal-ring-spin 3.5s linear infinite;
}

#seal-cta:hover .seal-ring-outer {
  stroke: #ff3c00;
  filter: drop-shadow(0 0 4px #EE9B00);
}

#seal-cta:hover .seal-ticks line {
  stroke: #ff3c00;
}

#seal-cta:hover .seal-circuit {
  stroke: #0A9396;
}

#seal-cta:hover .seal-circuit-dot {
  fill: #0A9396;
}

#seal-cta:hover .seal-label[data-splitting] [data-char]:after {
  color: var(--accent-human);
}

#seal-cta:hover {
  filter: drop-shadow(0 0 14px rgba(238, 155, 0, 0.5)) drop-shadow(0 0 30px rgba(202, 103, 2, 0.25));
}

#seal-cta.seal-glitch-active {
  animation: seal-glitch-burst 0.55s ease-out 1 forwards;
}

@keyframes seal-ring-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

@keyframes seal-entrance {
  0%   { opacity: 0; transform: scale(0.75) rotate(-10deg); }
  60%  { opacity: 1; transform: scale(1.06) rotate(2deg); }
  80%  { transform: scale(0.97) rotate(-1deg); }
  100% { opacity: 1; transform: scale(1) rotate(0deg); }
}

@keyframes seal-glitch-burst {
  0%   { transform: translateX(0)    skewX(0deg);    filter: drop-shadow(0 0 14px rgba(238,155,0,0.5)) drop-shadow(0 0 30px rgba(202,103,2,0.25)); }
  8%   { transform: translateX(-6px) skewX(-2.5deg); filter: drop-shadow(7px  0 0 rgba(255,60,0,0.7))  drop-shadow(-7px 0 0 rgba(0,255,255,0.7)); }
  16%  { transform: translateX(5px)  skewX(2deg);    filter: drop-shadow(-5px 0 0 rgba(255,60,0,0.5))  drop-shadow(5px  0 0 rgba(0,255,255,0.5)); }
  24%  { transform: translateX(-3px) skewX(-1deg);   filter: drop-shadow(0 0 8px rgba(238,155,0,0.8)); }
  32%  { transform: translateX(4px)  skewX(1.5deg);  filter: drop-shadow(6px  0 0 rgba(255,60,0,0.7))  drop-shadow(-6px 0 0 rgba(0,255,255,0.7)); }
  42%  { transform: translateX(-1px) skewX(0deg);    filter: drop-shadow(0 0 14px rgba(238,155,0,0.5)) drop-shadow(0 0 30px rgba(202,103,2,0.25)); }
  100% { transform: translateX(0)    skewX(0deg);    filter: drop-shadow(0 0 14px rgba(238,155,0,0.5)) drop-shadow(0 0 30px rgba(202,103,2,0.25)); }
}

/* --- Paul Rand Quote --- */
#paulrand-quote {
  font-size: 1.7rem !important;      /* Change this to adjust quote size - Currently 2x author name size */
  font-weight: 300 !important;
  color: #001219;
  line-height: 1.4 !important;
  position: relative;
  top: -1000px;             /* Move up (more negative) or down (more positive) */
  left: 0px;              /* Move left (negative) or right (positive) */
  width: 200px;           /* Change this value to make the quote wider or narrower */
  opacity: 0;
  transform: translateX(-100px);
  margin-bottom: 5px !important;
}

/* Prevent quote glitch on page load */
#paulrand-quote[data-splitting] [data-char]:after {
  color: #001219 !important;
  font-weight: 300 !important;
  animation-name: none;
}

/* Enable glitch animation for quote when element is visible in viewport */
#about #paulrand-quote[data-splitting].element-visible [data-char]:after {
  animation-name: glitch-switch;
  animation-duration: var(--duration-quick);
  animation-delay: var(--duration-base);
  animation-timing-function: steps(1);
  animation-fill-mode: backwards;
  animation-iteration-count: calc(var(--char-index) * 0.55);
}

/* --- Paul Rand Quote --- */
.paul-rands-quote {
  position: relative;            /* flows inside .about-top now (was absolute) */
  top: -100px;                   /* nudge the quote up 100px inside .about-top */
  font-size: clamp(1.8rem, 4.5vw, 4rem);
  line-height: 1.2;
  width: fit-content;
  max-width: 100%;
  z-index: 1000;
}

.paul-rands-quote blockquote {
  display: flex;
  align-items: baseline;
  gap: 0.35em;
  margin: 0;
}

.paul-rands-quote blockquote p {
  margin: 0;
  display: inline-block;
  font-weight: 900;
  color: #000;
  white-space: nowrap;
}

/* #about p:not(.ending) sets 1.1rem with higher specificity — override it */
#about .paul-rands-quote blockquote p {
  font-size: inherit;
  position: relative;
  top: -15px;
}

/* Prevent quote glitch on page load */
.paul-rands-quote p[data-splitting] [data-char]:after {
  color: #000 !important;
  font-weight: 900;
  animation-name: none;
}

/* Enable glitch when about section is visible */
#about .paul-rands-quote p[data-splitting].element-visible [data-char]:after,
#about .paul-rands-quote p[data-splitting].glitch-active [data-char]:after {
  animation-name: glitch-switch;
  animation-duration: var(--duration-quick);
  animation-delay: var(--duration-base);
  animation-timing-function: steps(1);
  animation-fill-mode: backwards;
  animation-iteration-count: calc(var(--char-index) * 0.55);
}

.paul-rands-quote ul {
  font-weight: 600;
  padding-inline: 0;
  margin: 0;
  list-style-type: none;
  height: 1.2em;
  overflow: hidden;
}

.paul-rands-quote li {
  height: 1.2em;
  white-space: nowrap;
  color: #ee9b00;
  will-change: transform;
}

/* Gate li glitch — override .glitch-text default, only fire on .glitch-active */
.paul-rands-quote li.glitch-text[data-splitting] [data-char]:after {
  color: #ee9b00;
  animation-name: none;
  opacity: 0;
}
.paul-rands-quote li.glitch-text[data-splitting].glitch-active [data-char]:after {
  animation-name: glitch-switch;
  animation-duration: var(--duration-quick);
  animation-delay: var(--duration-base);
  animation-timing-function: steps(1);
  animation-fill-mode: backwards;
  animation-iteration-count: calc(var(--char-index) * 0.55);
  opacity: 1;
}

.paul-rands-quote .ending {
  margin: 0;
  font-weight: 600;
  font-size: clamp(1rem, 1.5vw + 0.4rem, 1.6rem);
  opacity: 0;
  position: relative;
  top: -25px;
  transition: opacity 0.8s ease;
}

/* Prevent .glitch-text's white-space:pre from breaking line wrapping */
.paul-rands-quote .ending.glitch-text {
  white-space: normal;
}

.paul-rands-quote .ending.visible {
  opacity: 1;
  color: #001219;
}

/* Gate ending glitch — override .glitch-text default, only fire on .glitch-active */
.paul-rands-quote .ending.glitch-text[data-splitting] [data-char]:after {
  color: #001219;
  animation-name: none;
  opacity: 0;
}
.paul-rands-quote .ending.glitch-text[data-splitting].glitch-active [data-char]:after {
  animation-name: glitch-switch;
  animation-duration: var(--duration-quick);
  animation-delay: var(--duration-base);
  animation-timing-function: steps(1);
  animation-fill-mode: backwards;
  animation-iteration-count: calc(var(--char-index) * 0.55);
  opacity: 1;
}

/* Static display — full quote frozen, shown after animation completes or on user scroll */
.paul-rands-quote.static-display li {
  transform: translateY(-2.4em) !important;
}
.paul-rands-quote.static-display li.glitch-text[data-splitting] [data-char]:after {
  opacity: 1;
  animation-name: none;
}
.paul-rands-quote.static-display .ending {
  opacity: 1;
}
.paul-rands-quote.static-display .ending.glitch-text[data-splitting] [data-char]:after {
  opacity: 1;
  animation-name: none;
}

/* Slide in animation for paragraphs when element becomes visible in viewport */
#about .about-left > p:nth-of-type(1).element-visible {
  animation: slideInFromLeft var(--duration-rise) ease-out 0s forwards;
}

#about .about-left > p:nth-of-type(2).element-visible {
  animation: slideInFromLeft var(--duration-rise) ease-out 0s forwards;
}

#about .about-left > p:nth-of-type(3).element-visible {
  animation: slideInFromLeft var(--duration-rise) ease-out 0s forwards;
}

#about .about-left > p:nth-of-type(1).element-exit,
#about .about-left > p:nth-of-type(2).element-exit,
#about .about-left > p:nth-of-type(3).element-exit {
  animation: slideOutToLeft 0.5s ease-in 0s forwards;
}

/* Slide in animation for Paul Rand quote when element becomes visible in viewport */
#about #paulrand-quote.element-visible {
  animation: slideInFromLeft var(--duration-rise) ease-out 0s forwards;
}

#about #paulrand-author.element-visible {
  animation: slideInFromLeft var(--duration-rise) ease-out 0.2s forwards;
}

#about #paulrand-quote.element-exit {
  animation: slideOutToLeft 0.5s ease-in 0s forwards;
}

#about #paulrand-author.element-exit {
  animation: slideOutToLeft 0.5s ease-in 0.1s forwards;
}

/* Slide in animation for DNA title when element becomes visible in viewport */
#about #dnatitle.element-visible {
  animation: slideInFromRight var(--duration-rise) ease-out 0s forwards;
}

#about #dnatitle.element-exit {
  animation: slideOutToRight 0.5s ease-in 0s forwards;
}

/* Slide in animation for aboutp4 paragraph when element becomes visible in viewport */
#about #aboutp4.element-visible {
  animation: slideInFromRight var(--duration-rise) ease-out 0s forwards;
}

#about #aboutp4.element-exit {
  animation: slideOutToRight 0.5s ease-in 0s forwards;
}

/* About section text highlights — soft teal sweep, triggered when parent slides in */
.about-highlight {
  background-image: linear-gradient(#94D2BD, #94D2BD);
  background-repeat: no-repeat;
  background-position: left center;
  background-size: 0% 88%;
  transition: background-size 0.8s ease-out 0.7s;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  color: #001219;
  padding: 0 3px;
}

#about .element-visible .about-highlight {
  background-size: 100% 88%;
}

.about-cta-marker {
  background-image: linear-gradient(#E9D8A6, #E9D8A6);
  background-repeat: no-repeat;
  background-position: left center;
  background-size: 0% 88%;
  transition: background-size 0.8s ease-out 0.4s;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  color: #001219;
  padding: 0 3px;
  text-decoration: none;
}

#aboutp4.element-visible .about-cta-marker {
  background-size: 100% 88%;
}

/* First letter of first paragraph - Large with same glitch effect as h2 titles */
.first-letter-glitch {
  font-size: 3.5em;
  font-weight: 800;
  float: left;
  line-height: 0.85;
  margin-right: 8px;
  margin-top: 8px;
}

/* Apply dark color to first letter glitch characters */
.first-letter-glitch[data-splitting] [data-char]:after {
  color: #001219 !important;
  font-weight: 800;
}

/* --- DNA Capsule and Title (Right Column) --- */
.dna-title-wrapper {
  display: flex;
  align-items: center;
  gap: var(--spacing-xl);
  width: 100%;
}

/* DNA title - smaller to match capsule height */
#about #dnatitle {
  font-size: 3rem;
  font-weight: 700;
  line-height: 1.3;
  flex: 1;
  opacity: 0;
  transform: translateX(100px);
}

/* --- DNA Capsule Container --- */
.dna-capsule {
  position: relative;
  width: 100%;
  max-width: 200px;
  height: 400px;
  display: flex;
  justify-content: center;
  align-items: center;
  margin: 0 auto;
}

/* --- DNA 3D Text Animation --- */
#about .scene {
  position: relative;
  width: 100%;
  height: 400px;
  perspective: 600px;
  transform-style: preserve-3d;
  display: flex !important;
  justify-content: center !important;
  align-items: center !important;
  z-index: 0; /* Behind VHS effects (z-index 3-4) */
}

.texts {
  position: absolute;
  font-size: 3.5vmin; /* Further reduced for better readability */
  font-family: 'Funnel Display', sans-serif;
  transform-style: preserve-3d;
  animation: none; /* Disabled by default */
  opacity: 0; /* Hidden until visible */
  transition: opacity 0.6s ease-out;
  will-change: transform;  /* GPU acceleration hint */
}

/* Enable animation when DNA capsule SVG is visible in viewport */
#about .dna-capsule-svg.element-visible ~ .scene .texts {
  animation: texts 12s infinite linear;
  opacity: 1;
}

.texts::after {
  content: '';
  position: absolute;
  inset: -6em -1.2em; /* Reduced by 20% from -7.5em -1.5em */
  background-image: none; /* Removed vignette shadow effect - DNA now fully visible */
}

.text {
  position: absolute;
  transform-style: preserve-3d;
  animation: none; /* Disabled by default */
  color: hsl(calc(var(--text) * 90) 75% 75%);
}

/* Enable animation when DNA capsule SVG is visible in viewport */
#about .dna-capsule-svg.element-visible ~ .scene .text {
  animation: text 8s calc(var(--text) * -2s) infinite linear;
}

.text span {
  position: absolute;
  inset: -0.8em; /* Reduced by 20% from -1em */
  display: grid;
  place-items: center;
  font-weight: 700;
  user-select: none;
  opacity: 0;
  transition: opacity var(--transition-base) var(--easing-ease-in-out);
  transform: translateY(calc((var(--i) - 6) * 0.48em)) rotateY(calc(var(--i) * 20deg)) translateZ(1.2em) rotateZ(-40deg); /* Reduced by 20%: 0.6em -> 0.48em, 1.5em -> 1.2em */
}

.text span.revealed {
  opacity: 1;
}

/* Specific color overrides for DNA text */
.text[style*="--text: 0"] {
  color: #94d2bd;
}

.text[style*="--text: 2"] {
  color: #9B2226;
}

@keyframes texts {
  from { transform: rotateZ(0deg) rotateX(20deg) rotateZ(360deg); }
  to { transform: rotateZ(360deg) rotateX(20deg) rotateZ(0deg); }
}

@keyframes text {
  to { rotate: y -1turn; }
}

/* --- DNA Capsule SVG Animation --- */
.dna-capsule-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: var(--z-layer-5); /* Above DNA text */
  pointer-events: none;
  transform: scale(0);
  transform-origin: bottom left;
  /* Teal emitted-light halo — same shared --id-glow tokens as #id1svg /
     vtv1.svg / the contact capsule, instead of the old dark drop-shadow. */
  filter: var(--id-glow-filter);
}

/* Scale animation - grows horizontally first, then vertically */
#about .dna-capsule-svg.element-visible {
  animation: capsuleGrow var(--duration-scene) ease-out 0s forwards;
}

#about .dna-capsule-svg.element-exit {
  animation: fadeOut 0.5s ease-in 0s forwards;
}

@keyframes capsuleGrow {
  0% {
    transform: scaleX(0) scaleY(0.025); /* Start: 0 width, ~10px height (10/400 = 0.025) */
  }
  40% {
    transform: scaleX(1) scaleY(0.025); /* Grow to full width, maintain ~10px height */
  }
  100% {
    transform: scaleX(1) scaleY(1); /* Grow to full height */
  }
}

/* Hide all capsule elements initially */
.capsule-outline,
.capsule-accent-left,
.capsule-accent-right,
.capsule-bar-top-1,
.capsule-bar-top-2,
.capsule-line-left,
.capsule-line-left-dashed,
.capsule-line-indicator-1,
.capsule-line-indicator-2,
.capsule-line-indicator-3,
.capsule-lines-right,
.capsule-corner-accent,
.capsule-text,
.capsule-diagonals rect,
.capsule-clip-border {
  opacity: 0;
}

/* Sequential animations when DNA capsule SVG becomes visible in viewport */
/* 1. Main outline appears first */
#about .dna-capsule-svg.element-visible .capsule-outline {
  animation: fadeIn 0.6s ease-out 0s forwards;
}

/* 2. Top bars appear */
#about .dna-capsule-svg.element-visible .capsule-bar-top-1 {
  animation: fadeIn 0.3s ease-out 0.3s forwards;
}

#about .dna-capsule-svg.element-visible .capsule-bar-top-2 {
  animation: fadeIn 0.3s ease-out 0.4s forwards;
}

/* 3. Accent polygons appear */
#about .dna-capsule-svg.element-visible .capsule-accent-left {
  animation: fadeIn 0.3s ease-out 0.5s forwards;
}

#about .dna-capsule-svg.element-visible .capsule-accent-right {
  animation: fadeIn 0.3s ease-out 0.6s forwards;
}

/* 4. Corner accent */
#about .dna-capsule-svg.element-visible .capsule-corner-accent {
  animation: fadeIn 0.2s ease-out 0.7s forwards;
}

/* 5. Vertical lines */
#about .dna-capsule-svg.element-visible .capsule-line-left {
  animation: fadeIn 0.3s ease-out 0.8s forwards;
}

#about .dna-capsule-svg.element-visible .capsule-line-left-dashed {
  animation: fadeIn 0.4s ease-out 0.9s forwards;
}

/* 6. Indicator lines */
#about .dna-capsule-svg.element-visible .capsule-line-indicator-1 {
  animation: fadeIn 0.2s ease-out 1.0s forwards;
}

#about .dna-capsule-svg.element-visible .capsule-line-indicator-2 {
  animation: fadeIn 0.2s ease-out 1.1s forwards;
}

#about .dna-capsule-svg.element-visible .capsule-line-indicator-3 {
  animation: fadeIn 0.2s ease-out 1.2s forwards;
}

/* 7. Right side lines */
#about .dna-capsule-svg.element-visible .capsule-lines-right {
  animation: fadeIn 0.3s ease-out 1.3s forwards;
}

/* 8. Binary text data */
#about .dna-capsule-svg.element-visible .capsule-text {
  animation: fadeIn 0.5s ease-out 1.4s forwards;
}

/* 9. Diagonal bars at bottom - animate one by one */
#about .dna-capsule-svg.element-visible .capsule-diagonals rect:nth-child(1) {
  animation: fadeIn 0.15s ease-out 1.6s forwards;
}

#about .dna-capsule-svg.element-visible .capsule-diagonals rect:nth-child(2) {
  animation: fadeIn 0.15s ease-out 1.7s forwards;
}

#about .dna-capsule-svg.element-visible .capsule-diagonals rect:nth-child(3) {
  animation: fadeIn 0.15s ease-out 1.8s forwards;
}

#about .dna-capsule-svg.element-visible .capsule-diagonals rect:nth-child(4) {
  animation: fadeIn 0.15s ease-out 1.9s forwards;
}

#about .dna-capsule-svg.element-visible .capsule-diagonals rect:nth-child(5) {
  animation: fadeIn 0.15s ease-out 2.0s forwards;
}

#about .dna-capsule-svg.element-visible .capsule-diagonals rect:nth-child(6) {
  animation: fadeIn 0.15s ease-out 2.1s forwards;
}

/* 10. Bottom clip border appears last */
#about .dna-capsule-svg.element-visible .capsule-clip-border {
  animation: fadeIn 0.3s ease-out 2.2s forwards;
}

/* --- ID badge stack: badge + HUD "descend" arrow --- */
/* Column that owns the badge's old top-right placement; the badge sits on top,
   the descend arrow centers beneath it. */
.about-id-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(10px, 1.4vw, 22px);
  width: min(59%, 561px);    /* badge −15% again — width now lives on the stack */
  margin-left: auto;         /* push to the right edge of the head row */
  align-self: flex-start;    /* sit in the top-right corner */
  flex-shrink: 0;            /* keep its size; wrap instead of squashing neighbours */
  /* Teal glow halo for the badge, scroll label, and arrow comes from the
     shared --id-glow / --id-glow-soft tokens in :root. The elements are dark
     (#001219), so the glow reads as emitted accent light. */
}

/* --- ID1 SVG --- */
#id1svg {
  position: relative;
  width: 100%;               /* fill the stack (stack carries the sizing now) */
  height: auto;
  opacity: 0;
  transform: translateY(-100px);
  transform-origin: bottom left;
  animation-fill-mode: forwards;
  filter: var(--id-glow-filter);
}

/* --- ID badge descend cue -------------------------------------------------
   Mirrors #art-direction's red intro-frame cue (adfrm-arrow): a bracketed
   scroll prompt on top (replaces the old dotted feed line), [ ] target
   brackets, and three down-chevrons that pulse top→bottom. Self-contained
   .aid-* classes — no coupling to the art-direction SVG's global <style>.
   Both fade in + the chevrons pulse when the badge reveals (siblings of
   #id1svg.element-visible). */
.about-id-scroll {
  font-family: 'Funnel Display', sans-serif;
  font-weight: 700;
  font-size: clamp(0.8rem, 1.3vw, 1.05rem);
  letter-spacing: 0.18em;
  color: #001219;
  white-space: nowrap;
  user-select: none;
  opacity: 0;
  transition: opacity 0.45s ease;
  text-shadow: 0 0 6px var(--id-glow), 0 0 14px var(--id-glow-soft);
}

.about-id-arrow {
  width: clamp(58px, 19%, 88px);
  height: auto;
  overflow: visible;
  opacity: 0;
  transition: opacity 0.45s ease;
  filter: drop-shadow(0 0 4px var(--id-glow))
          drop-shadow(0 0 11px var(--id-glow-soft));
}

.about-id-arrow .aid-bracket {
  fill: none;
  stroke: #001219;
  stroke-width: 3.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.about-id-arrow .aid-chev {
  fill: #001219;
  opacity: 0.15;             /* resting state before the pulse arms */
}

/* Reveal + perpetual descend pulse — armed off the badge's reveal class.
   svg-inline.js replaces <img#id1svg> in place, so the arrow stays its
   following sibling and this combinator keeps working post-swap. */
#about #id1svg.element-visible ~ .about-id-scroll,
#about #id1svg.element-visible ~ .about-id-arrow {
  opacity: 1;
}
#about #id1svg.element-visible ~ .about-id-arrow .aid-chev-0 {
  animation: aid-chev-seq 1.4s linear 0s infinite;
}
#about #id1svg.element-visible ~ .about-id-arrow .aid-chev-1 {
  animation: aid-chev-seq 1.4s linear 0.18s infinite;
}
#about #id1svg.element-visible ~ .about-id-arrow .aid-chev-2 {
  animation: aid-chev-seq 1.4s linear 0.36s infinite;
}

@keyframes aid-chev-seq {
  0%   { opacity: 0.15; }
  16%  { opacity: 1; }
  42%  { opacity: 1; }
  46%  { opacity: 0.3; }
  50%  { opacity: 0.9; }
  68%  { opacity: 0.15; }
  100% { opacity: 0.15; }
}

@media (prefers-reduced-motion: reduce) {
  .about-id-arrow { transition: none; }
  #about #id1svg.element-visible ~ .about-id-arrow .aid-chev {
    opacity: 0.8;
    animation: none !important;
  }
}

/* Pre-conversion <img> fallback only — once svg-inline.js swaps in the inline
   SVG, color comes from the per-class overrides below instead of a filter
   (a root filter would flatten the .id1-tag amber hover to #001219 too). */
img#id1svg {
  filter: brightness(0) saturate(100%) invert(5%) sepia(17%) saturate(4526%) hue-rotate(157deg) brightness(96%) contrast(103%);
}

/* Flat badge color #001219 — same result the old filter produced. Two #ids
   beat the SVG's own injected scoped stylesheet (#id1svg .stN). st6/st16/st17
   are fill:none outline classes, so they only get the stroke override. */
#about #id1svg .st18,
#about #id1svg :is(.st0, .st1, .st2, .st3, .st4, .st5, .st7, .st8, .st9, .st10, .st11, .st12, .st13, .st14, .st15) {
  fill: #001219;
}

#about #id1svg :is(.st0, .st1, .st2, .st3, .st4, .st5, .st6, .st7, .st8, .st9, .st10, .st11, .st12, .st13, .st14, .st15, .st16, .st17) {
  stroke: #001219;
}

/* --- ID1 role/hobby tag rows — amber marker hover --- */
#id1svg .id1-tag {
  cursor: pointer;
}

#id1svg .id1-tag-hit {
  fill: transparent;
  stroke: none;
  pointer-events: all;
}

#id1svg .id1-tag path {
  transition: fill 0.25s ease;
}

#about #id1svg .id1-tag:hover path {
  fill: #EE9B00;
}

/* --- ID1 alternate portraits — tag-row hover swaps the ID character ---
   svg-inline.js fits each alternate into the ID_character frame box and
   toggles id1-alt-on (root) + id1-alt-active (the matching group). */
#id1svg .id1-alt {
  fill: #001219;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}

#id1svg .id1-alt.id1-alt-active {
  opacity: 1;
}

/* Hide the robot portrait while an alternate is up — but keep the group's
   frame rect so the portrait window stays outlined. */
#id1svg.id1-alt-on .id1-face > :not(rect) {
  display: none;
}

/* Animate ID1 SVG when element becomes visible in viewport - Multi-stage animation */
#about #id1svg.element-exit {
  animation: slideUpFade 0.4s ease-in 0s forwards;
}

#about #id1svg.element-visible {
  animation: slideDownFade 0.4s ease-out 0s forwards,
             id1Pulse 0.4s ease-in-out 0s,
             id1Shimmer 1.5s ease-in-out 0s infinite;
}

/* ID1 SVG pulse animation - subtle scale pulse after entrance */
@keyframes id1Pulse {
  0%, 100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.02);
  }
}

/* ID1 SVG shimmer animation - creates scanning effect */
@keyframes id1Shimmer {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0.85;
  }
}


/* --- ID1 SVG Internal Element Animations --- */
/* Hide all internal elements initially */
#id1svg .id1-border,
#id1svg .id1-rect,
#id1svg .id1-hexagon,
#id1svg .id1-circle,
#id1svg .id1-face,
#id1svg .id1-star,
#id1svg .id1-side-circle {
  opacity: 0;
  transform: translateY(-100px);
}

/* Animate elements when ID1 SVG becomes visible in viewport */
/* 1. Border appears first */
#about #id1svg.element-visible .id1-border {
  animation: slideDownFade 0.4s ease-out 0s forwards;
}

/* 2. Rectangles/bars appear */
#about #id1svg.element-visible .id1-rect {
  animation: slideDownFade 0.3s ease-out calc(0.05s + var(--rect-index) * 0.05s) forwards;
}

/* 3. Hexagons appear one by one */
#about #id1svg.element-visible .id1-hexagon {
  animation: slideDownFade 0.4s ease-out calc(0.1s + var(--hex-index) * 0.08s) forwards;
}

/* 4. Circles appear */
#about #id1svg.element-visible .id1-circle {
  animation: slideDownFade 0.3s ease-out calc(0.2s + var(--circle-index) * 0.05s) forwards;
}

/* 5. Side circles appear sequentially */
#about #id1svg.element-visible .id1-side-circle {
  animation: slideDownFade 0.3s ease-out calc(0.3s + var(--side-circle-index) * 0.08s) forwards;
}

/* 6. Stars appear */
#about #id1svg.element-visible .id1-star {
  animation: slideDownFade 0.2s ease-out calc(0.4s + var(--star-index) * 0.05s) forwards;
}

/* 7. Face appears last */
#about #id1svg.element-visible .id1-face {
  animation: slideDownFade 0.4s ease-out 0.5s forwards;
}

/* --- SVG Decorations --- */
.svg-decorations {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 999;
}

.svg-decorations img {
  position: absolute;
  pointer-events: auto;
  opacity: 0;
}

/* Divider bar — flows at the bottom of .about-top, above the two columns. */
.decoration-bar1 {
  display: block;
  width: 100%;
  max-width: 981.6px;
  height: auto;            /* preserve the bar's aspect ratio */
  opacity: 0;              /* slideInFromLeft reveals it when in view */
  transform: translateX(-100px);
}

.decoration-bar2 {
  width: 37.5px;
  height: 997.6px;
  top: 30px;
  left: 80px;
  transform: translateY(-100px);
}

.decoration-certs1 {
  position: absolute;
  top: 0;
  left: 0;
  width: 30px;
  height: 100%;
  z-index: 1;
  object-fit: contain;
  object-position: left top;
  pointer-events: none;
  opacity: 0;
}

.decoration-vtv1 {
  position: absolute;
  top: 50px;
  left: 35px;
  width: 420px;
  height: 350px;
  z-index: 2;
  pointer-events: none;
  opacity: 0;
  /* Recolor the light-blue art to dark teal, then add the same teal emitted-light
     halo as the about #id1svg badge (shared --id-glow tokens). */
  filter: brightness(0) saturate(100%) invert(5%) sepia(18%) saturate(4098%) hue-rotate(167deg) brightness(95%) contrast(101%) var(--id-glow-filter);
}

.decoration-skills {
  position: absolute;
  opacity: 0;
  height: 600px;
  top: 1050px;
  left: 200px;
  transform: scale(0.8);
}

/* Skills block relocated into the left column — flows at the bottom.
   Svg + list are stacked in ONE grid cell so the list is in normal flow and
   the block grows to contain it (the old absolute list overflowed a fixed
   600px height and collided with the paragraph below). */
.about-left .decoration-skills {
  position: relative;
  top: auto;
  left: auto;
  display: grid;
  width: 100%;
  height: auto;
  align-self: stretch;
  margin-top: var(--spacing-lg);
  transform-origin: top left;
}
.about-left .decoration-skills > .skills-svg {
  grid-area: 1 / 1;
  align-self: start;
  justify-self: start;
  position: static;
  width: 100%;               /* full design — fills the left column */
  max-width: 460px;
  height: auto;
}

.decoration-skills .skills-svg {
  position: static;
  opacity: 1;
  filter: none;
  height: 100%;
  width: auto;
}

/* Animate decorations when element becomes visible in viewport */
#about .decoration-bar1.element-visible {
  animation: slideInFromLeft var(--duration-rise) ease-out 0s forwards;
}

#about .decoration-bar1.element-exit {
  animation: slideOutToLeft 0.5s ease-in 0s forwards;
}

#about .decoration-bar2.element-visible {
  animation: slideDownFade var(--duration-rise) ease-out 0s forwards;
}

#about .decoration-bar2.element-exit {
  animation: slideUpFade 0.5s ease-in 0s forwards;
}

#about .decoration-certs1.element-visible {
  animation: skillsFadeIn 1s ease-out 0s forwards;
}

#about .decoration-certs1.element-exit {
  animation: fadeOut 0.5s ease-in 0s forwards;
}

#about .decoration-vtv1.element-visible {
  animation: skillsFadeIn 1s ease-out 0s forwards;
}

#about .decoration-vtv1.element-exit {
  animation: fadeOut 0.5s ease-in 0s forwards;
}

#about .decoration-skills.element-visible {
  animation: skillsFadeIn 1s ease-out 0s forwards;
}

#about .decoration-skills.element-exit {
  animation: fadeOut 0.5s ease-in 0s forwards;
}

/* skills.svg per-element entrance (owner 2026-07-03): svg-inline.js converts
   the <img> to inline SVG on every viewport, wraps each text line in
   <g class="skills-row"> (--row-index) and tags the card bg / sidebar. When
   it succeeds (.skills-inline) the container paints immediately — its stock
   skillsFadeIn hands over — and the reveal is carried by the parts, keyed to
   the same element-visible the script.js observer toggles: card → sidebar →
   rows cascading down. Failure-safe: plain opacity:1 rides next to each
   animation (parts still paint if an RM tier strips animation-name); if the
   conversion fails the <img> keeps the stock container fade. The phone twin
   of this block lives in responsive.css (loads later, identical values). */
#about .decoration-skills.skills-inline {
  animation: none;
  opacity: 1;
  transform: none;
}
#about .decoration-skills .skills-anim :is(.skills-card, .skills-side, .skills-row) {
  opacity: 0;
}
#about .decoration-skills.element-visible .skills-anim .skills-card {
  opacity: 1;
  animation: skillsPartDrop 0.4s ease-out 0s backwards;
}
#about .decoration-skills.element-visible .skills-anim .skills-side {
  opacity: 1;
  animation: skillsPartDrop 0.35s ease-out 0.15s backwards;
}
#about .decoration-skills.element-visible .skills-anim .skills-row {
  opacity: 1;
  animation: skillsPartDrop 0.35s ease-out calc(0.25s + var(--row-index) * 0.04s) backwards;
}
@keyframes skillsPartDrop {
  from {
    opacity: 0;
    transform: translateY(-14px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* about copy glitch entrance — desktop leg (owner 2026-07-03): same
   Splitting.js suppress→fire reveal and tempo as the phone version
   (responsive.css ≤768px owns that twin; this block owns ≥769px so neither
   leg leaks into the other). The ::after glyphs paint in
   --glitch-final-color (default white) — inherit-scoped recolors match the
   text they replace: everything reads #001219 except #aboutp4's white base
   (keeps the default; its dark spans re-scope below) and the metrics' amber
   separators. Failure-safe: fill-mode stays `backwards`, so a collapsed/
   instant run (Reduce Motion) still lands on content:var(--txt). */
@media (min-width: 769px) {
  #about #aboutp1,
  #about #aboutp2,
  #about #aboutp3,
  #about #aboutp4,
  #about #about-availability,
  #about #about-metrics {
    --glitch-stagger: 0.004s;
    --glitch-count: 4;
  }
  #about #aboutp1,
  #about #aboutp2,
  #about #aboutp3,
  #about #about-availability,
  #about #about-metrics,
  #about #aboutp4 .about-highlight,
  #about #aboutp4 .about-cta-marker {
    --glitch-final-color: #001219;
  }
  #about #about-metrics .metrics-sep {
    --glitch-final-color: #EE9B00;
  }
  /* script.js adds .glitch-text to the bio for the reveal machinery — its
     white-space:pre would re-break the desktop bio's wrapping. */
  #about #aboutp1.glitch-text {
    white-space: normal;
  }
  /* Progressive reveal riding the wave — desktop leg of the phone rule in
     responsive.css: chars start invisible and pop in exactly when their
     glitch slot arrives. Hiding the [data-char] SPAN (not the ::after) also
     hides the ::after's `backwards` from-state — the random glitch glyph
     every char otherwise shows while waiting its turn. Delay formula matches
     the firing rule 1:1, so the opacity front and the glitch front travel
     together. Failure-safe: plain opacity:1 rides the firing state; i18n
     re-splits insert fresh chars into a still-.glitch-firing host. */
  #about #aboutp1 [data-char],
  #about #aboutp2 [data-char],
  #about #aboutp3 [data-char],
  #about #aboutp4 [data-char],
  #about #about-availability [data-char],
  #about #about-metrics [data-char] {
    opacity: 0;
  }
  #about #aboutp1.glitch-firing [data-char],
  #about #aboutp2.glitch-firing [data-char],
  #about #aboutp3.glitch-firing [data-char],
  #about #aboutp4.glitch-firing [data-char],
  #about #about-availability.glitch-firing [data-char],
  #about #about-metrics.glitch-firing [data-char] {
    opacity: 1;
    animation: glitchCharIn 0.01s linear
      calc(var(--char-index) * var(--glitch-stagger, 0.05s)) backwards;
  }
  @keyframes glitchCharIn {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
}

/* Skills decoration fade and scale animation */
@keyframes skillsFadeIn {
  0% {
    opacity: 0;
    transform: scale(0.8);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}

/* ============================================================================
   CONTENT SECTIONS (Web, Photo, Illustration, Experiments, Contact)
   ============================================================================ */
#art-direction,
#photo,
#illustration,
#contact {
  position: relative;
  z-index: var(--z-layer-10);
  min-height: 100vh;
  background: var(--black);
}

/* Pin wrapper — its height (section + 300vh spacer) is the distance the section
   stays frozen. No transform/filter/contain here: that would trap the fixed
   #photo curtain; it must stay viewport-fixed. bg matches the section so the
   clipped strip below the letters is seamless while pinned. */
.ad-pin-wrap {
  background-color: #001219;
}

#art-direction {
  background-color: #001219;
  overflow: hidden;
  scroll-margin-top: var(--header-height, 60px);
  /* Freeze the section at the top of the viewport while the #photo curtain
     reveals over it. Sticky pins within .ad-pin-wrap and reserves its flow
     space, so the spacer-driven photo reveal timing is unchanged. */
  position: sticky;
  top: 0;
  /* End the section exactly at the bottom edge of the TION letter row — no dead
     strip below the masthead. The grid is the only in-flow child and starts at
     the section top, so its height (4 × cell = min(100vh − header, 100vw)) is
     the section height. overflow:hidden then clips the robot bg to that edge. */
  height: min(calc(100vh - var(--header-height, 60px)), 100vw);
  min-height: 0;
}

/* ── Background image — animated entrance ───────────────────────────────── */
#art-direction .art-bg-image {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  /* Keep the original full-viewport height so `cover` resolves the robot to the
     exact same scale/position as before the section was shortened. The section's
     overflow:hidden then clips whatever falls below the (shorter) bottom edge. */
  height: 100vh;
  background-image: url('../images/sergio-ayala-robot-dark-teal-duotone-art-direction-section.webp');
  background-size: cover;
  background-position: calc(50% - 40px) calc(50% + 40px);
  background-repeat: no-repeat;
  z-index: 0;
  opacity: 0;
  clip-path: inset(0 0 0 100%);
  transform: translateX(-80px);
  /* will-change is scoped to the entrance class so the compositor layer is
     promoted only for the ~1.15s animation, not for the page's whole life. */
}

#art-direction .art-bg-image.art-bg-animate {
  will-change: transform, clip-path, opacity;
  animation: art-bg-entrance 1.15s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}

@keyframes art-bg-entrance {
  0%   { transform: translateX(-80px);              clip-path: inset(0 0 0 100%);      opacity: 0;    }
  10%  { transform: translateX(-68px);              clip-path: inset(0 0 0 74%);       opacity: 0.5;  }
  12%  { transform: translateX(-74px) skewX(-5deg); clip-path: inset(18% 0 22% 68%);  opacity: 0.25; }
  14%  { transform: translateX(-65px);              clip-path: inset(0 0 0 70%);       opacity: 0.6;  }
  35%  { transform: translateX(-44px);              clip-path: inset(0 0 0 40%);       opacity: 0.78; }
  56%  { transform: translateX(-20px);              clip-path: inset(0 0 0 18%);       opacity: 0.86; }
  58%  { transform: translateX(-25px) skewX(2.5deg);clip-path: inset(28% 0 6% 15%);   opacity: 0.6;  }
  60%  { transform: translateX(-18px);              clip-path: inset(0 0 0 12%);       opacity: 0.9;  }
  82%  { transform: translateX(-4px);               clip-path: inset(0 0 0 2%);        opacity: 0.97; }
  88%  { transform: translateX(5px) skewX(-1deg);   clip-path: inset(0 0 0 0);        opacity: 1;    }
  94%  { transform: translateX(-2px);               clip-path: inset(0 0 0 0);        opacity: 1;    }
  100% { transform: translateX(0);                  clip-path: inset(0 0 0 0);        opacity: 1;    }
}

#illustration,
#illustration * {
  cursor: none;
}

#illustration .coming-soon__label {
  --glitch-final-color: #94D2BD;
}

#ink-cursor {
  pointer-events: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  z-index: 9999;
}

#art-direction .background {
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* No z-index here — prevents stacking context so children can float above the grid */
}

#art-direction .scanlines,
#art-direction .vhs-static {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

#art-direction .scanlines {
  z-index: 6;
}

#art-direction .vhs-static {
  z-index: 7;
}


/* ── Art Direction — conic-gradient letter grid ─────────────────────────── */

@keyframes art-blink {
  from                        { --p: 0%;  }
  1%, 4%, 20%                 { --p: 90%; }
  2%, 5%, 19%, 21%, to        { --p: 0%;  }
}

.art-direction-grid {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  position: relative;
  z-index: var(--z-layer-5);
}

.art-row {
  display: flex;
}

.art-cell {
  --c1: color-mix(in srgb, #EE9B00, transparent var(--p));
  --c2: color-mix(in srgb, #0A9396, transparent var(--p));
  width: min(calc((100vh - var(--header-height, 60px)) / 4), calc(100vw / 4));
  height: min(calc((100vh - var(--header-height, 60px)) / 4), calc(100vw / 4));
  flex-shrink: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  position: relative;
  overflow: hidden;
  container-type: inline-size;
  animation: art-blink 10s linear infinite;
}

/* Staggered durations + delays for organic feel */
.art-cell:nth-child(1)  { animation-duration: 10s; animation-delay:  0.0s; }
.art-cell:nth-child(2)  { animation-duration: 14s; animation-delay:  2.0s; }
.art-cell:nth-child(3)  { animation-duration:  9s; animation-delay:  4.0s; }
.art-cell:nth-child(4)  { animation-duration: 12s; animation-delay:  1.0s; }
.art-cell:nth-child(5)  { animation-duration: 11s; animation-delay:  3.0s; }

.art-row .art-cell .left {
  transform: scaleX(-1);
  background:
    conic-gradient(from 270deg at 50% 75%, transparent, #444, black),
    conic-gradient(from 270deg at 50% 25%, black, #444, transparent),
    conic-gradient(from 314.3deg at 50% 75%,
      var(--c1), transparent 75%,
      var(--c2) 75%,
      color-mix(in srgb, #94D2BD, transparent var(--p))
    ),
    conic-gradient(var(--c1), var(--c1));
  background-repeat: no-repeat;
  background-position: 0, 0, 0, 0.2vmin 0;
}

.art-row .art-cell .right {
  background:
    conic-gradient(from 270deg at 50% 75%, transparent, #444, black),
    conic-gradient(from 270deg at 0% 75%, transparent, rgba(0, 0, 0, 0.3)),
    conic-gradient(from 225deg at 50% 25%,
      color-mix(in srgb, #CA6702, transparent var(--p)),
      color-mix(in srgb, #EE9B00, transparent var(--p)) 12.5%,
      #0d0d0d 12.5%, black 50%, transparent
    ),
    var(--c2);
}

.art-cell::after {
  content: attr(data-letter);
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'Funnel Display', sans-serif;
  font-weight: 800;
  font-size: 80cqi;
  color: rgba(148, 210, 189, 0.45);
  mix-blend-mode: screen;
  pointer-events: none;
  line-height: 1;
  user-select: none;
  z-index: 1;
  transition: opacity 0.1s;
}

.art-cell:hover::after {
  opacity: 0;
}

/* ── Left-edge skills bar — above all section layers (vignette z:8, list z:10) */
#art-direction .ad-skills-bar {
  position: absolute;
  left: 0;
  top: 0;
  width: 37.5px;
  height: auto;
  z-index: 11;
  pointer-events: none;
  display: block;
}

/* ── SVG filter container ──────────────────────────────────────────────── */
.art-glitch-filters {
  display: none;
}

/* ── RGB glitch layers — base ──────────────────────────────────────────── */
.glitch-r,
.glitch-g,
.glitch-b {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'Funnel Display', sans-serif;
  font-weight: 800;
  font-size: 80cqi;
  line-height: 1;
  color: #fff;
  mix-blend-mode: lighten;
  pointer-events: none;
  user-select: none;
  opacity: 0;
  z-index: 2;
  transition: opacity 0.1s;
}

.glitch-r::after,
.glitch-g::after,
.glitch-b::after {
  content: attr(data-letter);
}

.glitch-r { filter: url(#artGlitchRed);   }
.glitch-g { filter: url(#artGlitchGreen); }
.glitch-b { filter: url(#artGlitchBlue);  }

/* ── Hover state ─────────────────────────────────────────────────────────── */
.art-cell:hover .glitch-r,
.art-cell:hover .glitch-g,
.art-cell:hover .glitch-b {
  opacity: 1;
}

.art-cell:hover .glitch-r {
  transform: translate(3px, -2px);
  animation: glitch-clip-r 0.35s linear infinite;
}
.art-cell:hover .glitch-g {
  transform: translate(-2px, 3px);
  animation: glitch-clip-g 0.35s linear infinite;
}
.art-cell:hover .glitch-b {
  transform: translate(2px, 2px);
  animation: glitch-clip-b 0.35s linear infinite;
}

@keyframes glitch-clip-r {
  0%   { clip-path: polygon(0 0%,   100% 0%,   100% 30%,  0 30%);  }
  16%  { clip-path: polygon(0 52%,  100% 52%,  100% 73%,  0 73%);  }
  33%  { clip-path: polygon(0 8%,   100% 8%,   100% 42%,  0 42%);  }
  50%  { clip-path: polygon(0 68%,  100% 68%,  100% 92%,  0 92%);  }
  66%  { clip-path: polygon(0 20%,  100% 20%,  100% 55%,  0 55%);  }
  83%  { clip-path: polygon(0 75%,  100% 75%,  100% 100%, 0 100%); }
  100% { clip-path: polygon(0 0%,   100% 0%,   100% 30%,  0 30%);  }
}

@keyframes glitch-clip-g {
  0%   { clip-path: polygon(0 40%,  100% 40%,  100% 70%,  0 70%);  }
  16%  { clip-path: polygon(0 5%,   100% 5%,   100% 28%,  0 28%);  }
  33%  { clip-path: polygon(0 62%,  100% 62%,  100% 85%,  0 85%);  }
  50%  { clip-path: polygon(0 15%,  100% 15%,  100% 45%,  0 45%);  }
  66%  { clip-path: polygon(0 80%,  100% 80%,  100% 100%, 0 100%); }
  83%  { clip-path: polygon(0 35%,  100% 35%,  100% 60%,  0 60%);  }
  100% { clip-path: polygon(0 40%,  100% 40%,  100% 70%,  0 70%);  }
}

@keyframes glitch-clip-b {
  0%   { clip-path: polygon(0 20%,  100% 20%,  100% 55%,  0 55%);  }
  16%  { clip-path: polygon(0 70%,  100% 70%,  100% 90%,  0 90%);  }
  33%  { clip-path: polygon(0 0%,   100% 0%,   100% 20%,  0 20%);  }
  50%  { clip-path: polygon(0 48%,  100% 48%,  100% 78%,  0 78%);  }
  66%  { clip-path: polygon(0 10%,  100% 10%,  100% 38%,  0 38%);  }
  83%  { clip-path: polygon(0 62%,  100% 62%,  100% 88%,  0 88%);  }
  100% { clip-path: polygon(0 20%,  100% 20%,  100% 55%,  0 55%);  }
}

/* ============================================================================
   PHOTO SECTION
   ============================================================================ */
#photo {
  background: url('../images/sergio-ayala-robot-camera-head-yellow-teal-photo-section.webp') center/cover no-repeat;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
  z-index: var(--z-layer-15);
  visibility: hidden;
  clip-path: inset(0 0 100% 0);
  contain: layout;
}

/* VHS effect layers — drape over entire photo section (above camera z:4) */
#photo .scanlines,
#photo .vhs-static {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

#photo .scanlines {
  z-index: 5;
}

#photo .vhs-static {
  z-index: 6;
}

.photo-static-line {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 4px;
  z-index: var(--z-layer-15);
  opacity: 0;
  transition: opacity 0.15s ease-out;
  pointer-events: none;
}

.photo-static-line.active {
  opacity: 1;
  filter: drop-shadow(0 0 3px #0ef) drop-shadow(0 0 7px #0ef);
}

.about-bottom-static-line,
.about-top-static-line,
.art-direction-bottom-static-line {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 4px;
  z-index: var(--z-layer-15);
  opacity: 0;
  transition: opacity 0.15s ease-out;
  pointer-events: none;
}

.about-bottom-static-line.active,
.about-top-static-line.active,
.art-direction-bottom-static-line.active {
  opacity: 1;
}

/* Align art-direction glitch line to nav bottom, not viewport top */


.photo-scroll-spacer {
  position: relative;
  height: 300vh;
  z-index: var(--z-layer-1);
}

/* ── Overlap Collision Star ─────────────────────────────────────────────── */
#line-collision-star {
  position: fixed;
  left: 50%;
  height: 320px;
  transform: translateX(-50%) translateY(-50%);
  display: none;
  align-items: center;
  gap: 6px;
  pointer-events: none;
  z-index: 20;
}

#line-collision-star.firing {
  display: flex;
}

#line-collision-star span {
  display: block;
  width: 2px;
  background: #ee9b00;
  opacity: 0;
  transform: scaleY(0);
  transform-origin: center;
  animation: starFlash 0.7s ease-out forwards;
}

#line-collision-star span:nth-child(1) { height: 140px; animation-delay: 0.02s; }
#line-collision-star span:nth-child(2) { height: 240px; animation-delay: 0.01s; }
#line-collision-star span:nth-child(3) { height: 320px; animation-delay: 0s;    }
#line-collision-star span:nth-child(4) { height: 220px; animation-delay: 0.03s; }
#line-collision-star span:nth-child(5) { height: 160px; animation-delay: 0.04s; }

@keyframes starFlash {
  0%   { opacity: 0; transform: scaleY(0);    box-shadow: none; }
  8%   { opacity: 1; transform: scaleY(1);    box-shadow: 0 0 8px #ee9b00, 0 0 25px #ee9b00, 0 0 50px #ee9b00; }
  18%  { opacity: 0; transform: scaleY(0.9); }
  28%  { opacity: 1; transform: scaleY(1);    box-shadow: 0 0 6px #ee9b00, 0 0 18px #ee9b00; }
  38%  { opacity: 0; transform: scaleY(0.8); }
  52%  { opacity: 1; transform: scaleY(0.87); box-shadow: 0 0 10px #ee9b00, 0 0 30px #ee9b00, 0 0 60px #ee9b00; }
  62%  { opacity: 0; transform: scaleY(0.7); }
  72%  { opacity: 0.8; transform: scaleY(0.6); box-shadow: 0 0 4px #ee9b00, 0 0 12px #ee9b00; }
  82%  { opacity: 0; transform: scaleY(0.4); }
  90%  { opacity: 0.3; transform: scaleY(0.2); }
  100% { opacity: 0; transform: scaleY(0); }
}

/* ============================================================================
   PHOTO SECTION — PORTFOLIO OVERLAY
   ============================================================================ */

/* Background hover image — inside overlay stacking context at z:1.
   Sits above text-block (z:0) and below accordion (z:2). */
.photo-bg-image {
  position: fixed;
  left: 0;
  top: 0;
  width: 200px;
  height: 260px;
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  background-color: #001219;
  opacity: 0;
  z-index: 20;
  pointer-events: none;
  /* transform is the only property moved at runtime — promotes to compositor layer */
  will-change: transform, opacity;
  border-radius: 2px;
  transition: opacity 0.2s ease;
  box-shadow:
    0 0 0 1px rgba(238, 155, 0, 0.45),
    0 8px 32px rgba(0, 0, 0, 0.55);
}

.photo-bg-border {
  position: absolute;
  inset: -5px;
  border: 2px solid #94D2BD;
  box-shadow:
    0 0  8px rgba(148, 210, 189, 0.85),
    0 0 24px rgba(148, 210, 189, 0.40),
    0 0 52px rgba(238, 155,   0, 0.18);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s ease;
}

.photo-bg-image.photo-bg-elec-active .photo-bg-border {
  opacity: 1;
  transition: opacity 0s;
}

.photo-enlarge-label {
  position: fixed;
  left: 0;
  top: 0;
  width: 200px;
  padding: 4px 12px;
  background: rgba(255, 223, 0, 0.92);
  border-left: 3px solid #EE9B00;
  box-shadow: 0 2px 28px rgba(0, 0, 0, 0.45);
  pointer-events: none;
  opacity: 0;
  will-change: transform, opacity;
  transition: opacity 0.2s ease;
  z-index: 20;
}

.photo-enlarge-text {
  display: block;
  font-family: 'Funnel Display', sans-serif;
  font-size: 12px;
  font-weight: 300;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgb(10, 10, 10);
  white-space: nowrap;
  text-align: center;
  line-height: 1;
}

/* Content overlay — text + accordion, scroll together, z:2 */
.photo-portfolio-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 2;
  display: flex;
  flex-direction: column;
  padding: 180px var(--spacing-xl) var(--spacing-xl);
  opacity: 0;
  pointer-events: none;
  overflow: visible;
}

/* Section label above the 3-column grid */
.photo-section-label {
  font-family: 'Funnel Display', sans-serif;
  font-size: clamp(0.6rem, 0.85vw, 0.75rem);
  color: #0A9396;
  letter-spacing: 0.12em;
  opacity: 0.7;
  margin-bottom: var(--spacing-sm);
  flex-shrink: 0;
}

/* Top grid — no z-index so the grid doesn't create a stacking context.
   Active accordion items (z:2) participate in the overlay's context and
   appear above the bg-image (z:1) on hover. */
.photo-top-grid {
  position: relative;
  display: grid;
  grid-template-columns: 2.4fr 1fr;
  gap: clamp(1.5rem, 3vw, 3rem);
  align-items: stretch;
  flex: 1 0 auto;
}

/* Left column: accordion categories */
.photo-col-left {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: clamp(0.5rem, 1vw, 1rem) clamp(1rem, 1.5vw, 1.5rem);
}

.photo-section-bar-img {
  position: absolute;
  left: 0;
  top: 180px;
  width: 37.5px;
  height: auto;
  z-index: 3;
  pointer-events: none;
}

/* Intro paragraph + CTA */
.photo-intro {
  font-family: 'Funnel Display', sans-serif;
  font-weight: 300;
  font-size: clamp(0.7rem, 1.1vw, 0.95rem);
  color: #001219;
  line-height: 1.6;
  margin: 0;
  flex-shrink: 0;
}

/* Marker-draw highlight: amber background sweeps left → right via background-size animation.
   ::before defined here as a shared fallback but suppressed for .photo-intro and .photo-cta. */
.photo-ai-highlight {
  position: relative;
  color: #001219;
  padding: 0 4px;
}

.photo-ai-highlight::before {
  content: '';
  position: absolute;
  inset: -1px -3px;
  background-color: #E9D8A6;
  transform-origin: left center;
  z-index: -1;
}

/* First highlight (inside .photo-intro): background-size transition on the element
   itself — works across multiple wrapped lines, no z-index dependency, and reliably
   restarts when the JS toggle pattern (remove → offsetWidth → add) is used. */
.photo-intro .photo-ai-highlight {
  background-image: linear-gradient(#E9D8A6, #E9D8A6);
  background-repeat: no-repeat;
  background-position: left center;
  background-size: 0% 88%;
  transition: background-size 0.8s ease-out;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}
/* Suppress the shared ::before */
.photo-intro .photo-ai-highlight::before {
  display: none;
}
.photo-intro .photo-ai-highlight--animate {
  background-size: 100% 88%;
}

/* Second highlight (inside .photo-cta): same transition approach, block-level */
.photo-cta .photo-ai-highlight {
  display: block;
  background-image: linear-gradient(#E9D8A6, #E9D8A6);
  background-repeat: no-repeat;
  background-position: left center;
  background-size: 0% 88%;
  transition: background-size 0.8s ease-out;
}
/* Suppress the shared ::before */
.photo-cta .photo-ai-highlight::before {
  display: none;
}
.photo-cta .photo-ai-highlight--animate {
  background-size: 100% 88%;
}

/* Used by about/other section marker highlights */
@keyframes photo-marker-left {
  from { transform: skewX(-5deg) scaleX(0); }
  to   { transform: skewX(-5deg) scaleX(1); }
}

.photo-cta {
  font-family: 'Funnel Display', sans-serif;
  font-weight: 700;
  font-size: clamp(0.7rem, 1.1vw, 0.95rem);
  color: #9B2226;
  line-height: 1.5;
  margin: 0;
  flex-shrink: 0;
}

/* CTA sits above the accordion in the left column — add bottom spacing */
.photo-cta--left {
  margin-bottom: clamp(0.8rem, 1.5vw, 1.25rem);
}

/* In left col the highlight is inline (not block) */
.photo-cta--left .photo-ai-highlight {
  display: inline;
}

/* Centre column: Instagram */
.photo-col-instagram {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Static dim base line */
.photo-col-instagram::before {
  content: '';
  position: absolute;
  left: calc(-1 * clamp(0.75rem, 1.5vw, 1.5rem));
  top: 0;
  height: 100%;
  width: 1px;
  background: linear-gradient(
    to bottom,
    transparent 0%,
    #0A9396 12%,
    #0A9396 88%,
    transparent 100%
  );
  opacity: 0.3;
  pointer-events: none;
}

/* Animated scan highlight */
.photo-col-instagram::after {
  content: '';
  position: absolute;
  left: calc(-1 * clamp(0.75rem, 1.5vw, 1.5rem));
  top: 0;
  width: 1px;
  height: 35%;
  background: linear-gradient(
    to bottom,
    transparent 0%,
    #94D2BD 30%,
    #94D2BD 70%,
    transparent 100%
  );
  animation: photoLineScan 3.5s ease-in-out infinite;
  pointer-events: none;
}

@keyframes photoLineScan {
  0%   { top: -35%; opacity: 0; }
  8%   { opacity: 1; }
  75%  { opacity: 1; }
  100% { top: 100%; opacity: 0; }
}

.photo-ig-header {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 12px;
}

.photo-ig-label {
  font-family: 'Funnel Display', sans-serif;
  font-size: clamp(0.75rem, 1.2vw, 1rem);
  font-weight: 700;
  color: #9B2226;
  letter-spacing: 0.1em;
  transition: color 0.25s ease;
}

.photo-ig-label:hover { color: #001219; }

.photo-ig-handle {
  font-family: 'Funnel Display', sans-serif;
  font-size: clamp(0.75rem, 1.2vw, 1rem);
  color: #BB3E03;
  margin: 0;
  margin-top: clamp(1rem, 2vh, 1.75rem);
  letter-spacing: 0.04em;
  text-decoration: underline;
  display: inline-block;
  position: relative;
  transition: color 0.25s ease;
}

.photo-ig-handle::before {
  content: '';
  position: absolute;
  inset: -1px 0;
  background-color: #E9D8A6;
  transform-origin: left center;
  transform: skewX(-5deg) scaleX(0);
  transition: transform 0.5s ease-out;
  z-index: -1;
  pointer-events: none;
}

.photo-ig-handle:hover { color: #001219; font-weight: 700; }
.photo-ig-handle:hover::before { transform: skewX(-5deg) scaleX(1); }

.photo-ig-desc {
  font-family: 'Funnel Display', sans-serif;
  font-weight: 300;
  font-size: clamp(0.7rem, 1.1vw, 0.95rem);
  color: #001219;
  line-height: 1.6;
  margin: 0;
}

.photo-ig-link {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  text-decoration: none;
  color: #9B2226;
  font-family: 'Funnel Display', sans-serif;
  font-size: clamp(0.75rem, 1.2vw, 1rem);
  letter-spacing: 0.08em;
  margin-top: var(--spacing-xs);
  position: relative;
  transition: color 0.25s ease;
}

.photo-ig-link::before {
  content: '';
  position: absolute;
  inset: -1px 0;
  background-color: #E9D8A6;
  transform-origin: left center;
  transform: skewX(5deg) scaleX(0);
  transition: transform 0.5s ease-out;
  z-index: -1;
  pointer-events: none;
}

.photo-ig-link:hover { color: #001219; font-weight: 700; }
.photo-ig-link:hover::before { transform: skewX(5deg) scaleX(1); }

.photo-ig-icon {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
}

/* Right column: polaroid unit stacked above editorial text */
.photo-col-camera {
  position: sticky;
  top: 0;
  align-self: start;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(1.2rem, 2.5vw, 2rem);
  padding-top: 0.25rem;
}

/* Polaroid frame + hint side-by-side (same layout as before, now a sub-unit) */
.photo-polaroid-unit {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: clamp(0.5rem, 1vw, 1rem);
  width: 100%;
}

/* SVG info-card hint — replaces the old text marker */
.photo-polaroid-hint {
  flex: 1;
  min-width: 0;
  margin: 0;
  padding-top: 0.4rem;
  display: flex;
  align-items: flex-start;
}

.photo-hint-svg {
  width: 100%;
  height: auto;
  display: block;
  opacity: 0.45;
  transition: opacity 0.55s ease;
}

/* Triggered by JS class — SVG brightens and pulses a border glow */
.photo-polaroid-hint.reveal .photo-hint-svg {
  opacity: 1;
  animation: hint-svg-reveal 1.4s ease-out forwards;
}

@keyframes hint-svg-reveal {
  0%   { filter: none; }
  20%  { filter: drop-shadow(0 0 5px rgba(174,32,18,0.9)) drop-shadow(0 0 14px rgba(174,32,18,0.4)); }
  45%  { filter: none; }
  65%  { filter: drop-shadow(0 0 3px rgba(174,32,18,0.55)); }
  100% { filter: none; }
}

/* Static dim base line — between col 2 and col 3 */
.photo-col-camera::before {
  content: '';
  position: absolute;
  left: calc(-1 * clamp(0.75rem, 1.5vw, 1.5rem));
  top: 0;
  height: 100%;
  width: 1px;
  background: linear-gradient(
    to bottom,
    transparent 0%,
    #0A9396 12%,
    #0A9396 88%,
    transparent 100%
  );
  opacity: 0.3;
  pointer-events: none;
}

/* Animated scan highlight — offset by half cycle so the two lines don't sync */
.photo-col-camera::after {
  content: '';
  position: absolute;
  left: calc(-1 * clamp(0.75rem, 1.5vw, 1.5rem));
  top: 0;
  width: 1px;
  height: 35%;
  background: linear-gradient(
    to bottom,
    transparent 0%,
    #94D2BD 30%,
    #94D2BD 70%,
    transparent 100%
  );
  animation: photoLineScan 3.5s ease-in-out infinite;
  animation-delay: -1.75s;
  pointer-events: none;
}

/* ── Polaroid reveal ─────────────────────────────────────────────────────────
   True Polaroid 600 proportions (3.5" × 4.25"):
     • 5.7 % left/right margin   (0.2" of 3.5")
     • 4.7 % top margin          (0.2" of 4.25")
     • 22.4 % bottom caption strip (0.95" of 4.25")
   Layer stack inside .photo-polaroid-frame (bottom → top):
     1. .photo-polaroid-reveal  — photo + canvas scratch-off
     2. .photo-polaroid-caption — name strip
     3. .photo-polaroid-border  — outer glow frame (never erased)
   ─────────────────────────────────────────────────────────────────────────── */

/* Outer polaroid shell — dark background simulates the white border in dark mode */
.photo-polaroid-frame {
  position: relative;
  width: 50%;
  aspect-ratio: 35 / 43;
  border-radius: 3px;
}

/* Photo scratch-off area — inset to match real polaroid margins */
.photo-polaroid-reveal {
  position: absolute;
  top: 4.7%;
  left: 5.7%;
  right: 5.7%;
  bottom: 22.4%;
  overflow: hidden;
  border-radius: 1px;
}

.photo-polaroid-photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
  user-select: none;
}

.photo-polaroid-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  cursor: crosshair;
  touch-action: none;
}

/* Caption strip — the wide white bottom area of a real polaroid */
.photo-polaroid-caption {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 22.4%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 0 8%;
}

.photo-polaroid-section {
  font-family: 'Funnel Display', sans-serif;
  font-size: clamp(0.4rem, 0.6vw, 0.55rem);
  color: #001219;
  letter-spacing: 0.08em;
  text-align: center;
  position: relative;
}

.photo-polaroid-section::before {
  content: '';
  position: absolute;
  inset: -1px -3px;
  background-color: #E9D8A6;
  transform-origin: left center;
  transform: skewX(-3deg) scaleX(0);
  z-index: -1;
  pointer-events: none;
}

.photo-polaroid-section.reveal::before {
  animation: photo-marker-left 0.8s ease-out 0.4s forwards;
}

/* Photo name — amber marker highlight */
.photo-polaroid-name {
  position: relative;
  font-family: 'Funnel Display', sans-serif;
  font-size: clamp(0.5rem, 0.85vw, 0.78rem);
  color: #001219;
  letter-spacing: 0.04em;
  text-align: center;
  padding: 1px 4px;
  z-index: 1;
}

.photo-polaroid-name::before {
  content: '';
  position: absolute;
  inset: -1px -3px;
  background-color: #E9D8A6;
  transform-origin: left center;
  transform: skewX(-3deg) scaleX(0);
  z-index: -1;
}

.photo-polaroid-name.reveal::before {
  animation: photo-marker-left 0.8s ease-out 0.4s forwards;
}

/* Outer glow border on the frame — always above everything */
.photo-polaroid-border {
  position: absolute;
  inset: 0;
  border: 2px solid #EE9B00;
  border-radius: 3px;
  box-shadow:
    0 0 8px  rgba(238, 155, 0, 0.55),
    0 0 20px rgba(238, 155, 0, 0.20),
    inset 0 0 10px rgba(238, 155, 0, 0.08);
  pointer-events: none;
  z-index: 2;
}

.photo-camera-svg {
  width: 100%;
  max-width: 220px;
  height: auto;
}

/* ── Fuji X-T4 camera animations ── */

/* Continuous: focus grip ring rotates slowly */
@keyframes xt4FocusRotate {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* Camera body recoil on shot */
@keyframes xt4Recoil {
  0%, 77%, 85%, 100% { transform: translateY(0); }
  80%                { transform: translateY(-3px); }
  82%                { transform: translateY(1px); }
}

/* Iris closes then opens */
@keyframes xt4Iris {
  0%, 76%, 85%, 100% { transform: scale(1); }
  79%                { transform: scale(0.15); }
  82%                { transform: scale(1); }
}

/* Dark lens curtain flash */
@keyframes xt4Shutter {
  0%, 78%, 100% { opacity: 0; }
  80%           { opacity: 0.95; }
  81.5%         { opacity: 0.05; }
  82.5%         { opacity: 0.85; }
  84%           { opacity: 0; }
}

/* Full-frame white flash */
@keyframes xt4Flash {
  0%, 79.5%, 100% { opacity: 0; }
  81%             { opacity: 0.18; }
  82%             { opacity: 0; }
}

/* AF-confirm LED */
@keyframes xt4Led {
  0%, 79%, 100% { opacity: 0; }
  80%           { opacity: 1; }
  85%           { opacity: 1; }
  87%           { opacity: 0; }
}

/* EVF brightens on shot */
@keyframes xt4VfGlow {
  0%, 77%, 100% { opacity: 0.12; }
  80%           { opacity: 0.7; }
  82%           { opacity: 0.12; }
}

/* Hover: hand grips down then releases */
@keyframes xt4HandGrip {
  0%, 40%, 100% { transform: translateY(0); }
  60%, 75%      { transform: translateY(9px); }
}

/* Hover: robotic thumb presses down to click shutter */
@keyframes xt4ThumbClick {
  0%, 55%, 85%, 100% { transform: scaleX(1); }
  65%, 76%           { transform: scaleX(0.72); }
}

/* Hover: lens scan glow */
@keyframes xt4LensGlow {
  0%, 100% { opacity: 0; }
  50%      { opacity: 0.75; }
}

/* Hover: EVF focus pulse */
@keyframes xt4FocusGlow {
  0%, 100% { opacity: 0.4; }
  50%      { opacity: 0.95; }
}

.camera-focus-ring-anim {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: xt4FocusRotate 12s linear infinite;
}
.camera-body-group { animation: xt4Recoil 7s infinite; }
.camera-iris {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: xt4Iris 7s infinite;
}
.camera-shutter { animation: xt4Shutter 7s infinite; }
.camera-flash   { animation: xt4Flash   7s infinite; }
.camera-led     { animation: xt4Led     7s infinite; }
.camera-vf-glow { animation: xt4VfGlow  7s infinite; }

/* Hand layers: hidden by default, fade in on hover */
.camera-hand-back,
.camera-hand-front {
  opacity: 0;
  transition: opacity 0.45s ease;
}

/* ── Hover: hand fades in + grips; auto-fire pauses ── */
.photo-col-camera:hover .camera-shutter,
.photo-col-camera:hover .camera-flash,
.photo-col-camera:hover .camera-led,
.photo-col-camera:hover .camera-iris {
  animation-play-state: paused;
}
/* Camera + both hand layers all squeeze together */
.photo-col-camera:hover .camera-hand-back,
.photo-col-camera:hover .camera-hand-front,
.photo-col-camera:hover .camera-body-group {
  opacity: 1;
  animation: xt4HandGrip 2.2s ease-in-out infinite;
}
.photo-col-camera:hover .camera-focus-ring-anim {
  animation-duration: 2s;
}
.photo-col-camera:hover .camera-vf-glow {
  animation: xt4FocusGlow 0.8s ease-in-out infinite;
}
.photo-col-camera:hover .camera-lens-glow {
  animation: xt4LensGlow 1.2s ease-in-out infinite;
}
/* Thumb squishes down when hand grips (synced to same 2.2s cycle) */
.camera-thumb {
  transform-box: fill-box;
  transform-origin: 0% 50%;
}
.photo-col-camera:hover .camera-thumb {
  animation: xt4ThumbClick 2.2s ease-in-out infinite;
}

/* Category list — hidden by default, shown on button click */
.photo-project-list {
  position: relative;
  list-style: none;
  margin: 0 0 var(--spacing-sm, 1rem) 0;
  padding: 0;
  counter-reset: photo-project-counter 0;
  display: none;
  flex-direction: column;
  background-color: transparent;
  transition: background-color 0.3s ease;
}

.photo-accordion-item.list-animating,
.photo-accordion-item.list-animating .photo-project-list,
.photo-accordion-item.list-animating .photo-category-btn {
  background-color: rgba(0, 18, 25, 0.7);
}

.photo-accordion-item.list-animating .photo-category-btn,
.photo-accordion-item.list-animating .photo-project-data,
.photo-accordion-item.list-animating .photo-project-item::before {
  color: #94D2BD;
}

/* Each row */
.photo-project-item {
  cursor: pointer;
  width: 100%;
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 40px 1fr 2fr 1fr 1fr 80px;
  column-gap: 1rem;
  padding: 0;
  align-items: center;
  border-bottom: 1px solid rgba(10, 147, 150, 0.18);
  opacity: 0;
  position: relative;
  transition: border-color 0.3s ease;
}

/* Single scroll container — text + accordion flow as one column */
.photo-content-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: none;
  display: flex;
  flex-direction: column;
}

.photo-content-scroll::-webkit-scrollbar {
  display: none;
}

/* Accordion — no z-index so it does NOT form a stacking context.
   Children participate in the overlay's context (z:2) directly, which
   means bg-image (z:1) can appear above all accordion rows except the
   active one (see .photo-project-item.active below). */
.photo-accordion {
  position: relative;
  margin-top: 0;
  display: flex;
  flex-direction: column;
}

/* ── Accordion glow border — visible only while accordion is animating ──── */
/* box-shadow glow frame; the old electric displacement filter has been removed. */
.accordion-electric-border {
  position: absolute;
  inset: 0;
  border-radius: 2px;
  pointer-events: none;
  border: 2px solid #EE9B00;
  box-shadow:
    0 0 6px  rgba(238, 155, 0, 0.65),
    0 0 18px rgba(238, 155, 0, 0.22),
    0 0 32px rgba(10,  147, 150, 0.14);
  opacity: 0;
  transition: opacity 0.3s ease;
  z-index: 5;
}

.photo-accordion.accordion-animating .accordion-electric-border {
  opacity: 1;
}

/* Each accordion item: button + collapsible list */
.photo-accordion-item {
  display: flex;
  flex-direction: column;
  background-color: transparent;
  transition: background-color 0.3s ease;
}

/* Category button */
.photo-col-bar1 {
  display: block;
  width: 100%;
  height: auto;
  opacity: 0;
  margin-top: 2rem;
}

.photo-polaroids-label {
  font-family: 'Funnel Display', sans-serif;
  font-weight: 700;
  font-size: clamp(0.95rem, 1.6vw, 1.25rem);
  letter-spacing: 0.12em;
  line-height: 1;
  margin: 0.4rem 0 0;
  padding: 0;
  cursor: default;
  color: #EE9B00;
  opacity: 0;
}

.photo-polaroids-desc {
  font-family: 'Funnel Display', sans-serif;
  font-weight: 300;
  font-size: clamp(0.7rem, 1.05vw, 0.9rem);
  color: #001219;
  line-height: 1.6;
  margin: 0.6rem 0 0;
  padding: 0;
  opacity: 0;
}

.photo-polaroids-camera {
  display: block;
  width: 19%;
  margin: calc(10% + 7px) 0 -10%;
  transform: rotate(-90deg);
  transform-origin: left center;
  opacity: 0;
  position: relative;
  z-index: 1;
}

@keyframes photo-camera-bounce-left {
  0%   { opacity: 0; transform: rotate(-90deg) translateY(-80px); }
  100% { opacity: 1; transform: rotate(-90deg) translateY(0); }
}

.photo-polaroids-camera.photo-camera-reveal {
  animation: photo-camera-bounce-left 0.65s var(--ease-spring) forwards;
}

@keyframes camera-eject-bounce {
  0%   { transform: rotate(-90deg) translateY(0);    opacity: 1; }
  25%  { transform: rotate(-90deg) translateY(-5px); opacity: 1; }
  65%  { transform: rotate(-90deg) translateY(1px);  opacity: 1; }
  100% { transform: rotate(-90deg) translateY(0);    opacity: 1; }
}

.photo-polaroids-camera.camera-eject {
  animation: camera-eject-bounce 0.45s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}

/* ── Polaroid ghost stream ──────────────────────────────────────────────── */
/* Container positioned at the bottom of the left column, same vertical zone
   as the camera image. Cards travel left → right and are clipped by a
   gradient mask: invisible near the camera (left), visible through the middle,
   then fading out before the right column border. */
.photo-ghost-stream {
  position: absolute;
  /* Anchored to the camera image's vertical centre (measured in JS →
     --cam-center-y, relative to .photo-col-left), then pulled up half its own
     height so the stream's centre — and therefore the cards' centreline —
     always coincides with the camera's centre, independent of section
     size / resolution / locale reflow. Falls back to ~62% pre-measurement. */
  top: var(--cam-center-y, 62%);
  left: 0;
  right: 0;
  height: 300px;
  transform: translateY(-50%);
  pointer-events: none;
  -webkit-mask-image: linear-gradient(
    to right,
    transparent 0%,
    black 18%,
    black 78%,
    transparent 100%
  );
  mask-image: linear-gradient(
    to right,
    transparent 0%,
    black 18%,
    black 78%,
    transparent 100%
  );
  opacity: 0;
  transition: opacity 0.5s ease;
}

.photo-ghost-stream.stream-active {
  opacity: 1;
}

/* Each polaroid card — real scanned polaroids (frame + handwritten caption
   baked into the image), position/opacity driven by JS on every scroll tick */
.photo-ghost-card {
  position: absolute;
  /* 50% = the stream's geometric centre, which JS locks onto the camera centre.
     Combined with translateY(-50%), each card's centre rides that exact line. */
  top: 50%;
  left: 0;
  width: 156px;
  height: 189px;
  opacity: 0;
  transform: translateX(0) translateY(-50%) rotate(-90deg);
  background: #001219 center / cover no-repeat; /* fallback; JS sets url() */
  box-sizing: border-box;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.55);
  will-change: transform, opacity;
  --ghost-rotate: 0deg;
  overflow: hidden;
}

/* Per-card tilt cycles across all 30 cards */
.photo-ghost-card:nth-child(4n+1) { --ghost-rotate: -4deg; }
.photo-ghost-card:nth-child(4n+2) { --ghost-rotate:  6deg; }
.photo-ghost-card:nth-child(4n+3) { --ghost-rotate: -2deg; }
.photo-ghost-card:nth-child(4n)   { --ghost-rotate:  5deg; }

/* Cards become individually clickable when the stream is active */
.photo-ghost-stream.stream-active .photo-ghost-card {
  pointer-events: auto;
  cursor: pointer;
}

/* ── Polaroid stream info strip ─────────────────────────────────────────── */
.pgallery-info {
  position: relative;
  width: 100%;
  height: 100px;
  opacity: 0;
  margin: 0.75rem 0 0;
  box-sizing: border-box;
}

/* Down cue inside the info panel (near its bottom edge) — nudges the eye toward
   the polaroid carousel. Child of .pgallery-info (position:relative) so it rides
   the panel's reveal; anchored within the SVG border (bottom at y99 of 100). */
.pgallery-down-cue {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 16px;
  display: flex;
  justify-content: center;
  pointer-events: none;
}

.pgallery-down-cue svg {
  width: clamp(14px, 1.5vw, 19px);
  height: auto;
  color: #BB3E03;
  filter: drop-shadow(0 0 5px rgba(187, 62, 3, 0.55));
  animation: pgallery-down-bob 1.5s ease-in-out infinite;
}

@keyframes pgallery-down-bob {
  0%, 100% { transform: translateY(0);   opacity: 0.7; }
  50%      { transform: translateY(3px); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .pgallery-down-cue svg { animation: none; opacity: 0.9; }
}

.pgallery-info-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  filter:
    drop-shadow(0 0 5px rgba(187, 62, 3, 0.85))
    drop-shadow(0 0 14px rgba(187, 62, 3, 0.45))
    drop-shadow(0 0 28px rgba(187, 62, 3, 0.20));
}

.pgallery-info-content {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  padding: 0 1rem;
  overflow: hidden;
}

.pgallery-info-left,
.pgallery-info-right {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  user-select: none;
  padding: 0 0.25rem;
  flex-shrink: 0;
}

/* Horizontal bracketed-chevron nav arrows — about / art-direction style.
   One right-pointing SVG; the left button mirrors it to point left. */
.pgnav-arrow {
  width: clamp(56px, 6vw, 80px);
  height: auto;
  display: block;
  transition: filter 0.2s;
}

.pgallery-info-left .pgnav-arrow { transform: scaleX(-1); }   /* → [ <<< ] */

.pgnav-bracket {
  fill: none;
  stroke: #001219;
  stroke-width: 4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.pgnav-chev {
  fill: none;
  stroke: #001219;
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.2;
}

.pgnav-chev-0 { animation: pgnav-chev-seq 1.4s linear 0s    infinite; }
.pgnav-chev-1 { animation: pgnav-chev-seq 1.4s linear 0.18s infinite; }
.pgnav-chev-2 { animation: pgnav-chev-seq 1.4s linear 0.36s infinite; }

@keyframes pgnav-chev-seq {
  0%   { opacity: 0.2; }
  16%  { opacity: 1; }
  42%  { opacity: 1; }
  46%  { opacity: 0.35; }
  50%  { opacity: 0.95; }
  68%  { opacity: 0.2; }
  100% { opacity: 0.2; }
}

.pgallery-info-left:hover .pgnav-bracket,
.pgallery-info-right:hover .pgnav-bracket,
.pgallery-info-left:hover .pgnav-chev,
.pgallery-info-right:hover .pgnav-chev {
  stroke: #BB3E03;
}

.pgallery-info-left:hover .pgnav-arrow,
.pgallery-info-right:hover .pgnav-arrow {
  filter: drop-shadow(0 0 10px rgba(187, 62, 3, 0.85));
}

@media (prefers-reduced-motion: reduce) {
  .pgnav-chev { opacity: 0.85; animation: none !important; }
}

.pgallery-info-text {
  flex: 1;
  align-self: flex-start;     /* pin text to the top border; frees the lower
                                 half of the panel for the down-arrow cue */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 0.25rem;
  padding: 7px 0.5rem 0;
  user-select: none;
}

.pgallery-info-title {
  font-family: 'Funnel Display', sans-serif;
  font-size: clamp(0.85rem, 1.4vw, 1.1rem);
  font-weight: 700;
  color: #001219;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-align: center;
}

.pgallery-info-sub {
  font-family: 'Funnel Display', sans-serif;
  font-size: clamp(0.8rem, 1.2vw, 1rem);
  font-weight: 600;
  color: #001219;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-align: center;
  opacity: 0.75;
}

/* ── Photo stream lightbox ──────────────────────────────────────────────── */
.photo-stream-lightbox {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 18, 25, 0.96);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.35s ease;
}

.photo-stream-lightbox.open {
  opacity: 1;
  pointer-events: auto;
}

.photo-stream-lb-frame {
  position: relative;
  display: inline-flex;
  transform: scale(0.2);
  transition: transform 0.5s var(--ease-spring);
}

.photo-stream-lightbox.open .photo-stream-lb-frame {
  transform: scale(1);
}

.photo-stream-lb-img {
  display: block;
  max-width: 88vw;
  max-height: 85vh;
  width: auto;
  height: auto;
}

.photo-stream-lb-border {
  position: absolute;
  inset: -6px;
  border: 2px solid #EE9B00;
  box-shadow:
    0 0  8px rgba(238, 155,   0, 0.85),
    0 0 24px rgba(238, 155,   0, 0.40),
    0 0 48px rgba(148, 210, 189, 0.18);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.45s ease;
}

.photo-stream-lightbox.lb-elec-active .photo-stream-lb-border {
  opacity: 1;
  transition: opacity 0s;
}

@keyframes photo-lb-close-pulse {
  0%, 100% {
    color: #EE9B00;
    text-shadow: 0 0 6px rgba(238, 155, 0, 0.35);
  }
  50% {
    color: #94D2BD;
    text-shadow:
      0 0 10px rgba(148, 210, 189, 0.80),
      0 0 24px rgba(148, 210, 189, 0.35);
  }
}

.photo-stream-lb-close {
  position: absolute;
  top: -2.75rem;
  right: 0;
  background: transparent;
  border: none;
  color: #EE9B00;
  font-family: 'Funnel Display', sans-serif;
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.22em;
  cursor: pointer;
  padding: 0.25rem 0.5rem;
  transition: color 0.2s, text-shadow 0.2s;
}

.photo-stream-lightbox.open .photo-stream-lb-close {
  animation: photo-lb-close-pulse 2s ease-in-out 0.9s infinite;
}

.photo-stream-lb-close:hover {
  color: #94D2BD;
  text-shadow:
    0 0 14px rgba(148, 210, 189, 0.85),
    0 0 28px rgba(148, 210, 189, 0.40);
  animation-play-state: paused;
}

/* ── Item lightbox — expands from hover-preview thumbnail to fullscreen ── */
.photo-item-lightbox {
  position: fixed;
  display: none;
  z-index: 9100;
  overflow: hidden;
  cursor: pointer;
  background: #001219;
  border-radius: 2px;
  transition:
    left   0.55s cubic-bezier(0.22, 1, 0.36, 1),
    top    0.55s cubic-bezier(0.22, 1, 0.36, 1),
    width  0.55s cubic-bezier(0.22, 1, 0.36, 1),
    height 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}

.photo-item-lb-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  pointer-events: none;
}

.photo-item-lb-border {
  position: absolute;
  inset: 0;
  border: 2px solid #EE9B00;
  box-shadow:
    0 0  8px rgba(238, 155,   0, 0.85),
    0 0 24px rgba(238, 155,   0, 0.40),
    0 0 52px rgba(148, 210, 189, 0.18);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.photo-item-lightbox.lb-expanding .photo-item-lb-border {
  opacity: 1;
  transition: opacity 0s;
}

.photo-category-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  min-height: 44px;
  background: none;
  border: none;
  border-top: 1px solid rgba(10, 147, 150, 0.18);
  cursor: pointer;
  text-align: left;
  font-family: 'Funnel Display', sans-serif;
  font-weight: 400;
  font-size: clamp(0.8rem, 1.3vw, 1.05rem);
  color: #9B2226;
  letter-spacing: 0.12em;
  line-height: 1;
  padding: 0.75rem 0;
  transition: color 0.2s ease, background-color 0.3s ease;
}

.photo-category-btn:hover {
  color: #001219;
  font-weight: 700;
}

.photo-category-btn.active {
  color: #9b2226;
}


/* [ + ] / [ - ] indicator */
.photo-btn-indicator::before {
  content: '[ + ]';
  font-family: 'Funnel Display', sans-serif;
  font-size: 0.75em;
  letter-spacing: 0.1em;
  opacity: 0.5;
  transition: opacity 0.2s ease;
}

.photo-category-btn:hover .photo-btn-indicator::before,
.photo-category-btn.active .photo-btn-indicator::before {
  opacity: 1;
}

.photo-category-btn.active .photo-btn-indicator::before {
  content: '[ - ]';
}

/* CSS counter — the 01–20 numbers */
.photo-project-item::before {
  content: counter(photo-project-counter, decimal-leading-zero);
  counter-increment: photo-project-counter;
  font-family: 'Funnel Display', sans-serif;
  font-size: 12px;
  font-weight: 300;
  color: rgba(0, 18, 25, 0.45);
  letter-spacing: 0.05em;
  justify-self: start;
  position: relative;
  z-index: 10;
  transition: opacity 0.1s ease;
  line-height: 1;
}

/* Active row highlight — dim all, brighten hovered */
.photo-project-list.has-active .photo-project-item {
  opacity: 0.25;
}

.photo-project-list.has-active .photo-project-item.active {
  opacity: 1;
  /* Rise above bg-image (z:1) so the yellow-highlight row stays visible.
     Works because .photo-accordion has no z-index (no stacking context),
     so this z-index is evaluated directly in the overlay's context. */
  z-index: 2;
}

/* Column text */
.photo-project-data {
  font-family: 'Funnel Display', sans-serif;
  font-size: 12px;
  font-weight: 300;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #001219;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1;
  padding: 2px;
  position: relative;
  z-index: 1;
  transition: color 0.15s ease;
}

.photo-title    { justify-self: start; }
.photo-location { justify-self: start; }
.photo-camera   { justify-self: start; }
.photo-lens     { justify-self: start; }
.photo-year     { justify-self: end; text-align: right; }

/* Yellow highlight sweep on hover */
.photo-project-data::after {
  content: '';
  position: absolute;
  top: 0;
  left: -8px;
  width: calc(100% + 16px);
  height: calc(100% - 2px);
  background: rgba(255, 223, 0, 0.92);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.25s ease;
  z-index: -1;
}

.photo-project-item:hover .photo-project-data::after {
  transform: scaleX(1);
}

.photo-project-item:hover .photo-project-data {
  color: rgb(10, 10, 10);
}

/* Responsive — hide some columns on narrower screens */
@media (max-width: 1200px) {
  .photo-project-item {
    grid-template-columns: 32px 1fr 1.5fr 0.8fr 0.8fr 60px;
    column-gap: 1.2rem;
  }
}

@media (max-width: 900px) {
  /* Already 2-column at desktop; stack to single column at tablet */
  .photo-top-grid {
    grid-template-columns: 1fr;
  }
  .photo-col-camera {
    grid-column: auto;
    justify-content: flex-start;
  }
  .photo-camera-svg {
    max-width: 180px;
  }
}

/* Instagram left-border lines clash with the column divider inside the right col */
.photo-col-camera .photo-col-instagram::before,
.photo-col-camera .photo-col-instagram::after {
  display: none;
}

@media (max-width: 768px) {
  .photo-portfolio-overlay {
    padding: 100px var(--spacing-md) var(--spacing-md);
  }

  .photo-top-grid {
    grid-template-columns: 1fr;
  }

  .photo-col-camera {
    grid-column: auto;
  }

  .photo-col-instagram::before,
  .photo-col-instagram::after,
  .photo-col-camera::before,
  .photo-col-camera::after {
    display: none;
  }

  .photo-project-item {
    grid-template-columns: 28px 1fr 1fr;
    column-gap: 0.8rem;
  }

  .photo-project-data:not(.photo-title):not(.photo-location) {
    display: none;
  }
}

.pgallery-title {
  font-family: 'Funnel Display', sans-serif;
  font-weight: 700;
  font-size: clamp(1rem, 1.6vw, 1.3rem);
  color: #001219;
  text-align: center;
  letter-spacing: 0.06em;
  margin: clamp(1rem, 2vh, 1.5rem) 0 0;
  position: relative;
  z-index: 0;
}

.pgallery-desc {
  font-family: 'Funnel Display', sans-serif;
  font-weight: 300;
  font-size: clamp(0.7rem, 1.1vw, 0.95rem);
  color: #001219;
  --glitch-final-color: #001219;
  text-align: left;
  line-height: 1.6;
  margin: clamp(0.4rem, 0.8vh, 0.6rem) 0 0;
  position: relative;
  z-index: 0;
  white-space: normal; /* override .glitch-text pre */
}

/* Gate glitch-switch animation — runs only when chain reveals the element */
.pgallery-desc[data-splitting] [data-char]:after {
  animation-name: none;
}
.pgallery-desc[data-splitting].glitch-ready [data-char]:after {
  animation-name: glitch-switch;
}

.pgallery-hint {
  font-family: 'Funnel Display', sans-serif;
  font-size: clamp(0.7rem, 1vw, 0.85rem);
  color: #001219;
  text-align: center;
  letter-spacing: 0.1em;
  margin: clamp(0.6rem, 1.2vh, 1rem) auto 0;
  position: relative;
  z-index: 0;
  display: inline-block;
  padding: 0 4px;
}

.pgallery-hint::before {
  content: '';
  position: absolute;
  inset: -1px -3px;
  background-color: #E9D8A6;
  transform-origin: left center;
  transform: skewX(-5deg) scaleX(0);
  z-index: -1;
}

.pgallery-hint--animate::before {
  animation: photo-marker-left 0.8s ease-out forwards;
}

/* ── Glitch-load overlay on cascade reveal ─────────────────────────────────
   Fires once per element when it enters the chain. Steps timing gives each
   frame a discrete hold (CRT / signal-lock aesthetic). Heavy at the start,
   settles into light noise by the end.
   ───────────────────────────────────────────────────────────────────────── */
@keyframes photo-glitch-in {
  /* Phase 1: heavy interference (0–44%) — overlaps the opacity flicker */
  0%   { transform: translateX(0)     skewX(0deg);    filter: brightness(4)   saturate(0); }
  8%   { transform: translateX(-9px)  skewX(-2.2deg); filter: brightness(0.1) saturate(2); }
  16%  { transform: translateX(7px)   skewX(1.8deg);  filter: brightness(3)   hue-rotate(110deg) saturate(3); }
  24%  { transform: translateX(-5px)  skewX(0deg);    filter: brightness(0.3); }
  32%  { transform: translateX(6px)   skewX(-1.2deg); filter: brightness(2.2) hue-rotate(-55deg); }
  40%  { transform: translateX(0)     skewX(0.6deg);  filter: brightness(0.5); }
  /* Phase 2: settling noise (44–100%) — element is visible, still twitching */
  48%  { transform: translateX(-3px)  skewX(0deg);    filter: brightness(1.4) saturate(1.5); }
  56%  { transform: translateX(2px)   skewX(0deg);    filter: brightness(0.8); }
  64%  { transform: translateX(0)     skewX(-0.2deg); filter: brightness(1.15); }
  72%  { transform: translateX(-1px)  skewX(0deg);    filter: brightness(0.92); }
  80%  { transform: translateX(1px)   skewX(0deg);    filter: brightness(1.05); }
  88%  { transform: translateX(0)     skewX(0deg);    filter: brightness(1); }
  100% { transform: translateX(0)     skewX(0deg);    filter: brightness(1) saturate(1); }
}

.photo-glitch-load {
  animation: photo-glitch-in 0.5s steps(1, end) both;
}

/* Flying caption clone — cloned row that flies to top-center of the active photo */
.photo-caption-fly {
  position: fixed;
  z-index: 9200;
  pointer-events: none;
  list-style: none;
  margin: 0;
  display: grid;
  grid-template-columns: 40px 1fr 2fr 1fr 1fr 80px;
  column-gap: 1rem;
  padding: 4px 12px;
  align-items: center;
  border-bottom: none;
  border-left: 3px solid #EE9B00;
  box-shadow: 0 2px 28px rgba(0, 0, 0, 0.45);
  opacity: 0;
}

.photo-caption-fly::before {
  display: none;
}

.photo-caption-fly .photo-project-data {
  color: rgb(10, 10, 10);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.photo-caption-fly .photo-project-data::after {
  transform: scaleX(1);
}

.photo-fly-num {
  font-family: 'Funnel Display', sans-serif;
  font-size: 12px;
  font-weight: 300;
  color: rgba(0, 18, 25, 0.45);
  letter-spacing: 0.05em;
  justify-self: start;
  line-height: 1;
}

/* ============================================================================
   RESPONSIVE MEDIA QUERIES
   ============================================================================ */

/* Tablet and smaller screens - Stack DNA capsule and title vertically */
@media (max-width: 1024px) {
  /* About section - adjust grid to single column on smaller screens */
  .about-container {
    grid-template-columns: 1fr;
    gap: var(--spacing-xl);
  }

  /* DNA capsule and title wrapper - stack vertically on tablet */
  .dna-title-wrapper {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--spacing-md);
  }

  /* Reduce title size on smaller screens */
  #about #dnatitle {
    font-size: 2.5rem;
  }

  #about h2 {
    font-size: 6rem;
  }
}

/* Mobile screens - further adjustments */
@media (max-width: 768px) {
  #about {
    padding: var(--spacing-2xl) var(--spacing-lg);
  }

  .about-right {
    padding: var(--spacing-lg);
    margin-top: var(--spacing-2xl);
    margin-left: 0;
  }

  /* Uncap the hardcoded 550px paragraph width */
  .about-left > p {
    width: 100%;
  }

  #about #dnatitle {
    font-size: 2rem;
  }

  #about h2 {
    font-size: 4rem;
  }

  #about p {
    font-size: 1rem;
  }

  /* Make DNA capsule smaller on mobile */
  .dna-capsule {
    max-width: 150px;
    height: 300px;
  }
}

/* ─── Mobile navigation ────────────────────────────────────────────────────── */
@media (max-width: 768px) {
  .hamburger-btn {
    display: flex;
    order: 3;
  }

  .language-toggle {
    order: 4;
  }

  /* Off-canvas drawer */
  .main-nav {
    position: fixed;
    top: 0;
    right: 0;
    width: min(280px, 85vw);
    height: 100dvh;
    background: rgba(0, 12, 18, 0.97);
    border-left: 1px solid rgba(238, 155, 0, 0.2);
    flex-direction: column;
    align-items: flex-start;
    gap: var(--spacing-sm);
    padding: 80px var(--spacing-xl) var(--spacing-xl);
    z-index: calc(var(--z-header) + 1);
    transform: translateX(100%);
    transition: transform 0.35s var(--ease-material);
    overflow-y: auto;
    overscroll-behavior: contain;
    /* Scanline texture matching site aesthetic */
    background-image:
      repeating-linear-gradient(
        0deg,
        transparent,
        transparent 3px,
        rgba(238, 155, 0, 0.02) 3px,
        rgba(238, 155, 0, 0.02) 4px
      );
  }

  .main-nav.is-open {
    transform: translateX(0);
  }

  .main-nav .nav-btn {
    font-size: 0.85rem;
    padding: var(--spacing-sm) 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    width: 100%;
    text-align: left;
  }

  .main-nav .nav-btn:last-child {
    border-bottom: none;
  }

  .main-nav .nav-btn:focus-visible {
    outline: 1px solid var(--amber);
    outline-offset: 3px;
  }

  .nav-backdrop {
    display: block;
    pointer-events: none;
  }

  .nav-backdrop.is-visible {
    pointer-events: auto;
  }

  /* Prevent body scroll when drawer is open */
  body.nav-open {
    overflow: hidden;
  }
}

/* ─── Header — tighten horizontal padding on mobile ─────────────────────── */
@media (max-width: 768px) {
  header {
    padding-left: var(--spacing-md);
    padding-right: var(--spacing-md);
  }
}

/* ─── Intro — fluid title + subtitle on narrow viewports ────────────────── */
@media (max-width: 768px) {
  #main-title {
    font-size: clamp(2rem, 8vw, 3.5rem);
  }

  .subtitle-glitch {
    font-size: clamp(0.9rem, 3.8vw, 1.5rem);
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   REDUCED MOTION
   Respects the OS "reduce motion" accessibility preference.
   WCAG 2.3.3 (Level AAA) — Vestibular disorders, photosensitive epilepsy.

   Two-tier strategy
   ─────────────────
   Tier 1 · Global collapse
     All animation/transition durations shrink to 0.001 ms.  One-shot entrance
     animations jump instantly to their final keyframe (content visible, layout
     correct) without any spatial movement — content is never hidden.

   Tier 2 · Explicit kill for infinite loops
     animation-iteration-count: 1 alone isn't enough when paired with
     animation: <name> on an element that re-applies the rule every frame.
     animation: none !important is the only reliable gate.  Each selector
     below also serves as a human-readable audit of every looping effect.

   Note: this block covers all partials loaded in the same document because
   @media queries are document-scoped.  Files with their own reduced-motion
   blocks (contact.css) are intentionally left in place — duplication is
   harmless and easier to audit per-file.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {

  /* ── Tier 1: global duration collapse ──────────────────────────────────── */
  *,
  *::before,
  *::after {
    animation-duration:        0.001ms !important;
    animation-iteration-count: 1       !important;
    animation-delay:           0s      !important;
    transition-duration:       0.001ms !important;
    transition-delay:          0s      !important;
    scroll-behavior:           auto    !important;
  }

  /* ── Tier 2: explicit animation:none for every infinite loop ────────────
     Ordered by hazard severity: flash/flicker first, continuous motion next.  */

  /* Intro: scanlines (0.3 s loop) and VHS static (steps, fast) are the
     highest photosensitivity risk in the codebase — kill first.             */
  .scanlines,
  .vhs-static { animation: none !important; }

  /* Intro: animated perspective grid and hue-rotate color layer            */
  #intro::before,
  #intro::after { animation: none !important; }

  /* Intro/About: 3-D particle orb (continuous rotation → vestibular risk)  */
  .orb-wrap,
  .orb-wrap .orb-particle { animation: none !important; }

  /* Art-direction: letter-grid blink (10–14 s; organic, but infinite)      */
  .art-cell { animation: none !important; }

  /* Art-direction: RGB glitch layers on hover — 0.35 s ≈ 2.9 Hz,
     right at the WCAG photosensitivity threshold of 3 Hz                   */
  .art-cell:hover .glitch-r,
  .art-cell:hover .glitch-g,
  .art-cell:hover .glitch-b { animation: none !important; }

  /* About: ID-card SVG shimmer                                              */
  .info-interface.active .info-interface-svg { animation: none !important; }

  /* Photo: vertical scan lines on instagram and camera columns              */
  .photo-col-instagram::after,
  .photo-col-camera::after { animation: none !important; }

  /* Photo: lightbox close-button attention pulse                            */
  .photo-stream-lightbox.open .photo-stream-lb-close { animation: none !important; }

  /* Photo: Fuji X-T4 camera SVG — all infinite mechanical loops            */
  .camera-focus-ring-anim,
  .camera-body-group,
  .camera-iris,
  .camera-shutter,
  .camera-flash,
  .camera-led,
  .camera-vf-glow { animation: none !important; }

  /* ── Tier 3: visibility repair ─────────────────────────────────────────
     Elements that start at opacity: 0 via a non-animated CSS property and
     only become visible when JS adds a trigger class.  The 0.001 ms jump
     lands at the final keyframe when the class is present, but if JS runs
     late the user sees nothing.  Show them unconditionally instead.        */

  /* About: info-panel language lines; JS adds .inf-lang--entering          */
  .inf-lang .info-line {
    opacity:   1    !important;
    transform: none !important;
  }
  .inf-lang .info-line--rule {
    opacity:   0.35 !important;
    transform: none !important;
  }
}


/* ===== cert-gallery.css ===== */
/* ============================================================================
   CERTIFICATE GALLERY
   Rotating 3D cube gallery — small copy of the illustration section cube
   ============================================================================ */

/* ── Component tokens — alias the global palette so a single source-of-truth change
      in --grid-color-6 propagates here automatically ──────────────────────── */
:root {
  --cert-amber:    var(--grid-color-6);
  --cert-amber-08: color-mix(in srgb, var(--cert-amber), transparent 92%);
  --cert-amber-10: color-mix(in srgb, var(--cert-amber), transparent 90%);
  --cert-amber-18: color-mix(in srgb, var(--cert-amber), transparent 82%);
  --cert-amber-22: color-mix(in srgb, var(--cert-amber), transparent 78%);
  --cert-amber-45: color-mix(in srgb, var(--cert-amber), transparent 55%);
  --cert-amber-65: color-mix(in srgb, var(--cert-amber), transparent 35%);
}

/* ── Layer wrapper (scroll-revealed) ─────────────────────────────────────── */
.cert-gallery-layer {
  position: relative;
  width: 100%;
  height: 500px;
  z-index: 1000;
  pointer-events: auto;
  opacity: 0;
  transform: translateY(50px);
  transition: opacity 0.8s ease-out, transform 0.8s ease-out;
}

.cert-gallery-layer.visible {
  opacity: 1;
  transform: translateY(0);
}

/* ── Cert cube wrapper — takes the same position as the old embedded gallery */
.cert-cube-wrapper {
  --cs: 240px;
  position: absolute;
  top: 95px;
  left: 120px;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 18px;
  overflow: visible;
}

/* ── 3D scene container ───────────────────────────────────────────────────── */
.cert-cube-scene {
  position: relative;
  width: var(--cs);
  height: var(--cs);
  perspective: 600px;
  cursor: pointer;
  /* Same teal emitted-light halo as the about #id1svg badge (shared :root
     tokens). Applied to the scene so it wraps the rendered cube silhouette
     without flattening the preserve-3d faces inside. */
  filter: var(--id-glow-filter);
}

/* ── The cube itself ─────────────────────────────────────────────────────── */
.cert-cube {
  width: var(--cs);
  height: var(--cs);
  position: relative;
  transform-style: preserve-3d;
  transform: rotateX(90deg) rotateY(0deg);
  transition: transform 0.65s var(--ease-material);
  will-change: transform;
}

/* ── Cube faces ───────────────────────────────────────────────────────────── */
.cert-face {
  position: absolute;
  inset: 0;
  overflow: hidden;
  backface-visibility: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Emit blue from within — the same recipe as dnacapsule1.svg's internal
     radial gradient (#1ee9f2 core → #00aef0 → dark), so the cube glows like the
     capsule rather than just carrying an edge shadow. The cert image sits on top. */
  background:
    radial-gradient(ellipse 78% 78% at 50% 50%,
      rgba(30, 233, 242, 0.30) 0%,
      rgba(0, 138, 223, 0.15) 55%,
      rgba(0, 18, 25, 0.92) 100%);
  border: 1px solid rgba(0, 213, 223, 0.55);
  box-sizing: border-box;
  /* Blue emission + bloom halo (matches the capsule's --id-glow light). */
  box-shadow:
    inset 0 0 22px rgba(30, 233, 242, 0.30),
    0 0 16px 2px rgba(0, 213, 223, 0.40),
    0 0 40px 8px rgba(0, 138, 223, 0.24);
}

.cert-face[data-face="top"]    { transform: rotateX(-90deg) translateZ(calc(var(--cs) / 2)); }
.cert-face[data-face="front"]  { transform: translateZ(calc(var(--cs) / 2)); }
.cert-face[data-face="right"]  { transform: rotateY(90deg)  translateZ(calc(var(--cs) / 2)); }
.cert-face[data-face="back"]   { transform: rotateY(180deg) translateZ(calc(var(--cs) / 2)); }
.cert-face[data-face="left"]   { transform: rotateY(-90deg) translateZ(calc(var(--cs) / 2)); }
.cert-face[data-face="bottom"] { transform: rotateX(90deg)  translateZ(calc(var(--cs) / 2)); }

/* Certificate image within a face */
.cert-face img {
  display: block;
  max-width: 90%;
  max-height: 90%;
  width: auto;
  height: auto;
  opacity: 0;
  transition: opacity 0.4s ease;
  border-radius: 3px;
}

.cert-face img.cert-img-loaded {
  opacity: 1;
}

/* Glow intensifies when scene is hovered (hover state) */
.cert-cube-scene:hover .cert-cube {
  box-shadow: 0 0 48px 10px rgba(0, 213, 223, 0.45);
}

/* ── Navigation bar ──────────────────────────────────────────────────────── */
.cert-cube-nav {
  display: flex;
  align-items: center;
  gap: 1.5rem;
}

/* Prev / Next — match the about section link style */
.cert-cube-nav .cert-prev,
.cert-cube-nav .cert-next {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 1rem;
  font-family: 'Funnel Display', sans-serif;
  font-weight: 300;
  padding: 5px 0;
  color: #e9d8a6;
  display: inline-flex;
  background-image: linear-gradient(var(--orange, #ff3c00), var(--orange, #ff3c00));
  background-size: 100% 0.5px;
  background-repeat: no-repeat;
  background-position: 100% 0%;
  transition: background-size 0.7s, background-position 0.5s ease-in-out, color 0.3s;
}

.cert-cube-nav .cert-prev [data-char]:after,
.cert-cube-nav .cert-next [data-char]:after {
  color: #e9d8a6 !important;
}

.cert-cube-nav .cert-prev:hover,
.cert-cube-nav .cert-next:hover {
  background-size: 100% 100%;
  background-position: 0% 100%;
  color: var(--bg, #000);
  transition: background-position 0.7s, background-size 0.5s ease-in-out, color 0.3s;
}

.cert-cube-nav .cert-prev:hover [data-char]:after,
.cert-cube-nav .cert-next:hover [data-char]:after {
  color: var(--bg, #000) !important;
}

.cert-cube-nav .cert-prev:active,
.cert-cube-nav .cert-next:active {
  transform: scale(0.98);
}

/* Counter between Prev / Next */
.cert-counter {
  font-family: 'Funnel Display', sans-serif;
  font-size: 0.7rem;
  color: var(--cert-amber-65);
  letter-spacing: 0.12em;
  user-select: none;
  min-width: 3.5em;
  text-align: center;
}

/* ============================================================================
   CERTIFICATE MODAL/LIGHTBOX
   Click-to-enlarge functionality with 1200px max height
   ============================================================================ */

.cert-modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0.3s ease;
}

.cert-modal:not(.hidden) {
  opacity: 1;
  visibility: visible;
}

/* Semi-transparent backdrop */
.cert-modal-backdrop {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.9);
  backdrop-filter: blur(8px);
  cursor: pointer;
}

/* Modal content container */
.cert-modal-content {
  position: relative;
  max-width: 90vw;
  max-height: 90vh;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10001;
  animation: modalZoomIn 0.4s var(--ease-material);
}

@keyframes modalZoomIn {
  from {
    opacity: 0;
    transform: scale(0.8);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

/* The enlarged certificate image */
.cert-modal-content img {
  max-width: 100%;
  max-height: 1200px;
  height: auto;
  width: auto;
  border-radius: 8px;

  box-shadow: 0 0 20px rgba(238, 155, 0, 0.15),
              0 0 40px rgba(238, 155, 0, 0.08);

  border: 1px solid rgba(238, 155, 0, 0.3);

  /* Smooth transition */
  transition: all 0.3s ease;
}

/* Close button */
.cert-modal-close {
  position: absolute;
  top: 10px;
  right: 10px;
  background: none;
  border: none;
  cursor: pointer;
  font-size: 1rem;
  font-family: 'Funnel Display', sans-serif;
  font-weight: 300;
  color: var(--cert-amber);
  padding: 8px 16px;
  z-index: 10002;

  /* Text glow effect */
  text-shadow: 0 0 10px var(--cert-amber),
               0 0 20px var(--cert-amber),
               0 0 30px var(--cert-amber);

  transition: all 0.3s ease;
}

.cert-modal-close:hover {
  color: var(--cert-amber);
  text-shadow: 0 0 15px var(--cert-amber),
               0 0 30px var(--cert-amber),
               0 0 45px var(--cert-amber),
               0 0 60px var(--cert-amber);
  transform: scale(1.1);
}

.cert-modal-close [data-char]:after {
  color: var(--cert-amber);
}

.cert-modal-close:hover [data-char]:after {
  color: var(--cert-amber);
}

/* Responsive adjustments for modal */
@media (max-width: 768px) {
  .cert-modal-content {
    max-width: 95vw;
    max-height: 95vh;
  }

  .cert-modal-content img {
    max-height: 90vh;
  }

  .cert-modal-close {
    top: 5px;
    right: 5px;
    font-size: 0.9rem;
    padding: 6px 12px;
  }
}

@media (max-height: 1300px) {
  .cert-modal-content img {
    max-height: 85vh;
  }
}



/* ===== scroll-hint.css ===== */
/* ═══════════════════════════════════════════════════════════════════════════
   SCROLL HINT — cyberpunk idle overlay (about section)
   Appears fixed, centred in the viewport after the user is idle.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Panel ────────────────────────────────────────────────────────────────── */
.sc-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}

/* ── Corners and scan line hidden ────────────────────────────────────────── */
.sc-corner,
.sc-panel::after {
  display: none;
}

/* ── SVG scroll icon ─────────────────────────────────────────────────────── */
.sc-icon {
  width: 64px;
  height: 112px;
  color: #ee9b00;
  filter: drop-shadow(0 0 6px rgba(238, 155, 0, 0.85));
  flex-shrink: 0;
}

/* Scroll wheel drops and fades out, then snaps back */
.sc-wheel {
  animation: sc-wheel-drop 2s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}

@keyframes sc-wheel-drop {
  0%   { transform: translateY(0);    opacity: 1;   }
  55%  { transform: translateY(8px);  opacity: 0.1; }
  56%  { transform: translateY(0);    opacity: 0;   }
  70%  { opacity: 1;                               }
  100% { transform: translateY(0);    opacity: 1;   }
}

/* Chevrons cascade in */
.sc-chevron-1 {
  animation: sc-chevron-fade 2s ease-in-out infinite;
}
.sc-chevron-2 {
  animation: sc-chevron-fade 2s ease-in-out infinite 0.28s;
}

@keyframes sc-chevron-fade {
  0%, 25% { opacity: 0.1; }
  65%     { opacity: 1;   }
  100%    { opacity: 0.1; }
}

/* ── Scroll text (bottom) ────────────────────────────────────────────────── */
.sc-text {
  font-family: 'Funnel Display', sans-serif;
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.28em;
  color: #ee9b00;
  text-shadow: 0 0 12px rgba(238, 155, 0, 0.9), 0 0 24px rgba(238, 155, 0, 0.4);
  margin: 0;
  white-space: nowrap;
  animation: sc-text-breathe 3.5s ease-in-out infinite;
}

@keyframes sc-text-breathe {
  0%, 100% { opacity: 1;   }
  50%      { opacity: 0.55; }
}

/* ── Intro "02 SCROLL" how-to cue ─────────────────────────────────────────────
   Hovering the SCROLL row in the HOW TO NAVIGATE panel reveals the same
   bracketed [ ] + three pulsing down-chevrons + "[ · Scroll to explore · ]"
   message used by the illustration section. The info panel is docked at
   bottom:40px / left:70px / width:380px (≈274px tall); the cue sits just to its
   right and spans the panel height. Reuses the global aid-chev-seq pulse.
─────────────────────────────────────────────────────────────────────────────── */
.intro-scroll-cue {
  position: absolute;
  bottom: 130px;                       /* lifted so it clears the panel's bottom border line */
  left: calc(70px + 380px + 0.9rem);   /* just right of the info panel */
  height: 230px;                       /* spans most of the panel's height */
  z-index: var(--z-layer-5);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.35s ease;
  display: flex;
  flex-direction: column;              /* arrow at top, rotated label reaching bottom */
  align-items: center;
  justify-content: space-between;
}

.intro-scroll-cue .iscue-arrow {
  display: block;
  width: clamp(50px, 4.5vw, 68px);
  height: auto;
  overflow: visible;
  filter: drop-shadow(0 0 4px rgba(238, 155, 0, 0.5))
          drop-shadow(0 0 11px rgba(238, 155, 0, 0.3));
}
.intro-scroll-cue .iscue-bracket {
  fill: none;
  stroke: #EE9B00;
  stroke-width: 3.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.intro-scroll-cue .iscue-chev { fill: #EE9B00; opacity: 0.15; }

/* Label below the arrow, rotated 90° → reads top-to-bottom, centred on the arrow */
.intro-scroll-cue .iscue-text {
  writing-mode: vertical-rl;
  text-orientation: mixed;
  transform: rotate(180deg);           /* flip so "[ · Scroll" sits at the bottom, reading up */
  white-space: nowrap;
  font-family: 'Funnel Display', sans-serif;
  font-weight: 700;
  font-size: clamp(0.82rem, 1.2vw, 1.05rem);
  letter-spacing: 0.2em;
  color: #EE9B00;
  text-shadow: 0 0 6px rgba(238, 155, 0, 0.5), 0 0 14px rgba(238, 155, 0, 0.3);
}

/* Reveal + pulse the chevrons while the SCROLL hotspot is hovered */
#intro:has(.inf-hotspot[data-hint="scroll"]:hover) .intro-scroll-cue { opacity: 1; }
#intro:has(.inf-hotspot[data-hint="scroll"]:hover) .intro-scroll-cue .iscue-chev-0 { animation: aid-chev-seq 1.4s linear 0s    infinite; }
#intro:has(.inf-hotspot[data-hint="scroll"]:hover) .intro-scroll-cue .iscue-chev-1 { animation: aid-chev-seq 1.4s linear 0.18s infinite; }
#intro:has(.inf-hotspot[data-hint="scroll"]:hover) .intro-scroll-cue .iscue-chev-2 { animation: aid-chev-seq 1.4s linear 0.36s infinite; }

@media (prefers-reduced-motion: reduce) {
  .intro-scroll-cue { transition: none; }
  .intro-scroll-cue .iscue-chev { opacity: 0.8; animation: none !important; }
}

/* ── Photo section scroll hint — same layout, crimson palette ─────────────── */
#photo-scroll-hint {
  position: fixed;
  inset: 0;
  z-index: 979;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding-bottom: 2.5rem;
  pointer-events: none;

  opacity: 0;
  visibility: hidden;
  transition:
    opacity    0.7s var(--ease-expo-out),
    visibility 0.7s var(--ease-expo-out);
}

#photo-scroll-hint.visible {
  opacity: 1;
  visibility: visible;
}

#photo-scroll-hint .sc-icon {
  color: #9b2226;
  filter: drop-shadow(0 0 6px rgba(155, 34, 38, 0.85));
}

#photo-scroll-hint .sc-text {
  color: #9b2226;
  text-shadow: 0 0 12px rgba(155, 34, 38, 0.9), 0 0 24px rgba(155, 34, 38, 0.4);
}

#photo-scroll-hint .sc-sub-text {
  font-family: 'Funnel Display', sans-serif;
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.15em;
  color: #9b2226;
  text-shadow: 0 0 10px rgba(155, 34, 38, 0.7);
  margin: 0;
  white-space: nowrap;
  opacity: 0.8;
  animation: sc-text-breathe 3.5s ease-in-out infinite;
}


/* ===== preloader.css ===== */
/* ═══ PRELOADER — label · electric progress line · glitch % counter ═══════════
   No external dependencies. Layout (top → bottom): "[ · L O A D I N G · ]"
   label building letter by letter, the electric line growing from center
   (50px corridor at viewport center), the % counter. Multicolor particle
   canvas behind everything (injected by particle-system.js).
   ══════════════════════════════════════════════════════════════════════════ */

body.preloading {
  overflow: hidden;
}

/* Pause all intro + header animations while loading.
   Must include ::before/::after explicitly — the * selector does NOT
   match pseudo-elements, so glitch animations on [data-char]::after
   would otherwise run (and finish) behind the preloader overlay. */
body.preloading #intro,
body.preloading #intro::before,
body.preloading #intro::after,
body.preloading #intro *,
body.preloading #intro *::before,
body.preloading #intro *::after,
body.preloading header,
body.preloading header *,
body.preloading header *::after {
  animation-play-state: paused !important;
}

/* ── Overlay ─────────────────────────────────────────────────────────────── */
#preloader {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: #001219;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity 0.8s ease-in-out;
}

#preloader.exit {
  opacity: 0;
  pointer-events: none;
}

/* ── Stage ───────────────────────────────────────────────────────────────── */
/* Centered column: label above, % counter below. The 90px gap straddles the
   electric line's 50px corridor (the line runs through the viewport center,
   which is also the stage's center). */
.preloader-stage {
  position: relative;
  z-index: 2; /* above the scan-lines (z 1) and the #particleCanvas (z auto) */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 90px;
}

/* ── Electric scan-lines ──────────────────────────────────────────────────
   A few "electric static" lines (reuse js/static-line.js) sweep the viewport
   at random — some up, some down — for the life of the preloader. They sit
   ABOVE the particle canvas but BELOW the loading text, so [ LOADING ] and the
   % stay crisp. Travel + fade run on the compositor (transform + opacity). */
.preloader-lines {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  overflow: hidden;       /* clip the glow as a line exits past an edge */
}

.preloader-line {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 4px;
  opacity: 0;             /* keyframes fade each pass in/out at the ends */
  will-change: transform, opacity;
  transform: translate3d(0, 100vh, 0);   /* parked off-screen at rest */
  filter: drop-shadow(0 0 3px #0ef) drop-shadow(0 0 7px #0ef);
}

@keyframes plLineUp {
  0%   { transform: translate3d(0, 100vh, 0); opacity: 0; }
  10%  { opacity: 1; }
  90%  { opacity: 1; }
  100% { transform: translate3d(0, -8px, 0);  opacity: 0; }
}

@keyframes plLineDown {
  0%   { transform: translate3d(0, -8px, 0);  opacity: 0; }
  10%  { opacity: 1; }
  90%  { opacity: 1; }
  100% { transform: translate3d(0, 100vh, 0); opacity: 0; }
}

/* ── iOS / WebKit fix ────────────────────────────────────────────────────────
   On iPhone (Safari & Chrome, both WebKit) the scan-lines never appeared. A
   <canvas> carrying BOTH `will-change` and a `filter: drop-shadow` is promoted
   to a compositor layer that WebKit then fails to paint (works on desktop &
   Android/Blink). Fix: on iOS drop the will-change hint and the CSS filter, and
   render the cyan glow INSIDE the canvas instead (preloader.js passes
   shadow:true on iOS). @supports(-webkit-touch-callout) is iOS-WebKit only, so
   desktop + Android are byte-for-byte unchanged. */
@supports (-webkit-touch-callout: none) {
  .preloader-line {
    will-change: auto;
    filter: none;
  }
}

/* ── Reduced motion ──────────────────────────────────────────────────────────
   The sweeps are pure decorative MOTION — omit them under reduce. This is not
   a visual change on any platform: the global RM tier-1 collapse (styles.css)
   already forced every 0.001ms pass to complete instantly ON its opacity:0 end
   keyframe, i.e. the lines were permanently invisible while still burning rAF
   on the canvas jitter. preloader.js skips creating them under RM too. */
@media (prefers-reduced-motion: reduce) {
  .preloader-lines { display: none; }
}

/* ── Label (above the line) + percentage counter (below it) ─────────────── */
.preloader-label,
.preloader-pct {
  font-family: 'Funnel Display', sans-serif;
  font-weight: 600;
  letter-spacing: 0.08em;
  display: flex;
  align-items: center;
  white-space: nowrap;
  text-shadow: 0 0 12px rgba(0, 18, 25, 0.9), 0 2px 8px rgba(0, 0, 0, 0.85);
}

.preloader-label {
  font-size: 1.15rem;
  color: #94D2BD;
  /* Reserve the final label's height so layout never shifts as chars land */
  min-height: 1.4em;
}

.preloader-pct {
  font-size: 1.5rem;
  color: #EE9B00;
}

/* ── Glitch chars ────────────────────────────────────────────────────────── */
/* Each span has data-char=actual, --char-0=actual, --char-1..9=random glitch */

.preloader-pct [data-char],
.preloader-label [data-char] {
  color: transparent;
  position: relative;
  display: inline-block;
  /* Fixed slot per glyph. Funnel Display is proportional and the glitch swaps
     glyphs of wildly different widths — without a constant slot the whole row
     re-flows on every counter tick and the brackets dance around. */
  width: 0.68em;
  text-align: center;
}

/* Brief glitch per character change then settles on real char.
   glitch-switch keyframes are defined in styles.css. */
.preloader-pct [data-char]::after,
.preloader-label [data-char]::after {
  content: var(--char-0);
  color: #EE9B00;
  position: absolute;
  inset: 0 auto auto 0;
  width: 100%; /* span the slot so text-align centers whichever glyph shows */
  text-shadow: 0 0 12px rgba(0, 18, 25, 0.9), 0 2px 8px rgba(0, 0, 0, 0.85);
  animation-name: glitch-switch;
  animation-duration: 0.2s;
  animation-timing-function: steps(1);
  animation-delay: var(--pl-delay, 0s);
  animation-iteration-count: 3;
  animation-fill-mode: both;
}

.preloader-label [data-char]::after {
  color: #94D2BD;
}

/* Already-revealed label chars: frozen on their real glyph — only the
   newest character of each build step plays the glitch (see renderLabel) */
.preloader-label [data-char].pl-settled::after {
  animation: none;
}


/* ===== contact.css ===== */
/* ─────────────────────────────────────────────────────────────────────────────
   Contact Section — Synthwave Scene
   Requires styles.css loaded first (reuses font stacks, z-index tokens)
   ───────────────────────────────────────────────────────────────────────────── */

/* Houdini property for animated sun stripe mask */
@property --ct-shift {
  syntax: "<number>";
  inherits: false;
  initial-value: 1;
}

/* Animatable color stops for sun gradient cycle */
@property --ct-sun-c1 {
  syntax: "<color>";
  inherits: false;
  initial-value: #EE9B00;
}
@property --ct-sun-c2 {
  syntax: "<color>";
  inherits: false;
  initial-value: #BB3E03;
}
@property --ct-sun-glow-anim {
  syntax: "<color>";
  inherits: false;
  initial-value: #CA6702;
}

/* ── Section reset & palette ──────────────────────────────────────────────── */
/* Cross-dissolve handoff from #illustration: during the handoff band #contact is
   held as a fixed, fully-opaque backdrop directly BEHIND #illustration, which
   fades its own opacity out to reveal it. No transform pinning — fixed is driven
   by the compositor, so it stays rock-steady (no scroll jitter); opaque + behind,
   so the fade never exposes any other section. z below #illustration's raised
   handoff layer; the nav header (z 1000) still sits above. */
#contact.ct-handoff-in {
  position: fixed;
  top: var(--header-height, 60px);
  left: 0;
  width: 100%;
  height: calc(100vh - var(--header-height, 60px));
  z-index: var(--z-layer-10, 10);
  will-change: opacity;
}

#contact {
  background: transparent;
  overflow: hidden;

  /* Same pattern as #illustration's .illus-tunnel: the section fills the
     viewport minus the fixed menu bar, so at full scroll the cockpit's top
     edge sits flush against the bottom of the header. */
  min-height: calc(100vh - var(--header-height, 60px));
  scroll-margin-top: var(--header-height, 60px);

  /* Palette — mapped 1-to-1 from the project's design tokens */
  --ct-sky-dark:   #001219;   /* --grid-color-1  darkest background */
  --ct-sky-mid:    #003040;   /* between grid-1 and grid-2           */
  --ct-sun-warm:   #EE9B00;   /* --grid-color-6  site amber (keep)   */
  --ct-sun-pink:   #BB3E03;   /* --grid-color-8  burnt orange        */
  --ct-sun-glow:   #CA6702;   /* --grid-color-7  contact header hue  */
  --ct-grid-line:  #94D2BD;   /* --grid-color-4  light teal          */
  --ct-grid-glow:  #0A9396;   /* --grid-color-3  teal                */
  --ct-accent:     #00ffff;   /* --cyan           site primary accent */
  --ct-star-color: #E9D8A6;   /* --grid-color-5  warm sand           */
  --ct-mountain:   #001219;   /* --grid-color-1                      */
  --ct-speed:      4s;

}

/* ═══════════════════════════════════════════════════════════════════════════
   SCENE CONTAINER
   ═══════════════════════════════════════════════════════════════════════════ */
.ct-scene {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--ct-sky-dark);
}

/* ═══════════════════════════════════════════════════════════════════════════
   STARS — 3-layer parallax + individual twinkle + shooting stars
   ═══════════════════════════════════════════════════════════════════════════ */
#ct-stars {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* ── Box-shadow star field — box-shadows injected by JS ─────────────────── */
.ct-s1,
.ct-s2,
.ct-s3 {
  position: absolute;
  top: 0;
  left: 0;
  background: transparent;
}

.ct-s1 {
  width: 1px; height: 1px;
  animation: ct-anim-star  50s linear infinite;
}
.ct-s1::after {
  content: ""; position: absolute; top: 2000px; left: 0;
  width: 1px; height: 1px; background: transparent;
}

.ct-s2 {
  width: 2px; height: 2px;
  filter: blur(0.4px);
  animation: ct-anim-star 100s linear infinite;
}
.ct-s2::after {
  content: ""; position: absolute; top: 2000px; left: 0;
  width: 2px; height: 2px; background: transparent;
}

.ct-s3 {
  width: 3px; height: 3px;
  filter: blur(0.8px);
  animation: ct-anim-star 150s linear infinite;
}
.ct-s3::after {
  content: ""; position: absolute; top: 2000px; left: 0;
  width: 3px; height: 3px; background: transparent;
}

@keyframes ct-anim-star {
  from { transform: translateY(0); }
  to   { transform: translateY(-2000px); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   SKY LAYER
   ═══════════════════════════════════════════════════════════════════════════ */
.ct-sky {
  flex: 1;
  position: relative;
  display: flex;
  justify-content: center;
  align-items: flex-end;
  overflow: hidden;
  background: linear-gradient(to bottom, var(--ct-sky-dark) 0%, var(--ct-sky-mid) 100%);
  padding-bottom: 3vmin;
  z-index: 1;
}

/* ═══════════════════════════════════════════════════════════════════════════
   MOUNTAINS — 3-layer atmospheric depth system
   z-index: horizon-glow(1) → far(2) → mid(3) → near(4)
   All real divs so GSAP can animate them in the boot sequence
   ═══════════════════════════════════════════════════════════════════════════ */

/* Warm horizon glow — amber/teal gradient strip at the skyline */
.ct-horizon-glow {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 14vmin;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(
    to top,
    rgba(238, 155, 0, 0.18) 0%,
    rgba(10, 147, 150, 0.08) 45%,
    transparent 100%
  );
}

/* Far range — hazy, receding; lighter value for atmospheric depth */
.ct-mountains--far {
  position: absolute;
  bottom: -0.5vmin;
  left: 0;
  right: 0;
  height: 16vmin;
  z-index: 2;
  pointer-events: none;
  background: #0a1d2e;
  clip-path: polygon(
    0% 55%, 4% 48%, 8% 38%, 14% 50%, 19% 32%, 24% 45%, 28% 25%,
    33% 40%, 38% 18%, 43% 34%, 47% 22%, 52% 38%, 57% 20%, 62% 35%,
    67% 28%, 71% 42%, 76% 15%, 81% 30%, 85% 42%, 89% 28%, 93% 40%,
    96% 48%, 98% 42%, 100% 45%, 100% 100%, 0% 100%
  );
}

/* Mid range — dramatic jagged hero peaks with subtle teal peak glow */
.ct-mountains--mid {
  position: absolute;
  bottom: -0.5vmin;
  left: 0;
  right: 0;
  height: 11vmin;
  z-index: 3;
  pointer-events: none;
  background: #001523;
  clip-path: polygon(
    0% 48%, 3% 55%, 6% 32%, 10% 62%, 14% 72%, 18% 22%,
    21% 40%, 26% 8%, 31% 50%, 35% 65%, 40% 78%, 44% 58%,
    48% 70%, 53% 42%, 57% 72%, 62% 48%, 67% 12%, 71% 30%,
    75% 58%, 79% 15%, 84% 35%, 88% 52%, 92% 30%, 96% 48%,
    100% 38%, 100% 100%, 0% 100%
  );
}
.ct-mountains--mid::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(
    ellipse 100% 38% at 50% 0%,
    rgba(10, 147, 150, 0.28) 0%,
    transparent 100%
  );
  pointer-events: none;
}

/* Near range — dark foreground foothills */
.ct-mountains--near {
  position: absolute;
  bottom: -0.5vmin;
  left: 0;
  right: 0;
  height: 6vmin;
  z-index: 4;
  pointer-events: none;
  background: #000912;
  clip-path: polygon(
    0% 65%, 6% 52%, 12% 40%, 20% 58%, 28% 36%, 35% 55%,
    43% 32%, 50% 55%, 58% 42%, 65% 62%, 72% 44%, 80% 58%,
    87% 40%, 93% 55%, 97% 62%, 100% 58%, 100% 100%, 0% 100%
  );
}

/* Horizontal scanline film on sky */
.ct-skylines {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  mix-blend-mode: overlay;
  opacity: 0.05;
  background: repeating-linear-gradient(
    to bottom,
    var(--ct-sun-pink) 0,
    var(--ct-sun-pink) 0.15vmin,
    transparent 0.15vmin,
    transparent 0.35vmin
  );
}

/* ── Sun ──────────────────────────────────────────────────────────────────── */
.ct-sun {
  position: relative;
  width: clamp(100px, 28vmin, 260px);
  aspect-ratio: 1;
  border-radius: 50%;
  z-index: 1;
  margin-bottom: 1vmin;
  flex-shrink: 0;
  filter: drop-shadow(0 0 4rem var(--ct-sun-glow-anim));
  animation: ct-sun-glow-cycle 16s ease-in-out infinite;
}

/* Animated stripe mask — recreates the SCSS @mixin sun-lines from the reference */
.ct-sun::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(to bottom, var(--ct-sun-c1), var(--ct-sun-c2) 60%);
  animation: ct-sun-stripes calc(var(--ct-speed) / 4) linear infinite,
             ct-sun-color-cycle 16s ease-in-out infinite;
  mask: linear-gradient(to top,
    #000  calc(1%  + 3.5% * var(--ct-shift)),
    #0000 calc(8%  + 2.8% * var(--ct-shift)),
    #000  calc(10% + 3.5% * var(--ct-shift)),
    #0000 calc(16% + 2.8% * var(--ct-shift)),
    #000  calc(19% + 3.5% * var(--ct-shift)),
    #0000 calc(24% + 2.8% * var(--ct-shift)),
    #000  calc(28% + 3.5% * var(--ct-shift)),
    #0000 calc(32% + 2.8% * var(--ct-shift)),
    #000  calc(37% + 3.5% * var(--ct-shift)),
    #0000 calc(40% + 2.8% * var(--ct-shift)),
    #000  calc(46% + 3.5% * var(--ct-shift)),
    #0000 calc(48% + 2.8% * var(--ct-shift)),
    #000  calc(55% + 3.5% * var(--ct-shift)),
    #0000 calc(56% + 2.8% * var(--ct-shift)),
    #000  calc(56% + 2.5% * var(--ct-shift))
  );
  mask-size: 100% 120%;
}

/* Glow reflection below sun — follows the same color cycle */
.ct-sun::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(
    to top,
    var(--ct-sun-c1),
    var(--ct-sun-c2) 55%,
    var(--ct-accent)  65%,
    var(--ct-sun-c2)  69%
  );
  transform: translateY(100%) rotateX(40deg) scaleY(1.2);
  filter: blur(18px);
  opacity: 0.35;
  animation: ct-sun-color-cycle 16s ease-in-out infinite;
}

@keyframes ct-sun-stripes {
  from { --ct-shift: 1; }
  to   { --ct-shift: 3.8; }
}

/* Palette cycle: amber → teal → sand/gold → back                         */
/* Colors drawn from the project's design token set                        */
@keyframes ct-sun-color-cycle {
  0%   { --ct-sun-c1: #EE9B00; --ct-sun-c2: #BB3E03; }  /* amber  → burnt orange  */
  25%  { --ct-sun-c1: #CA6702; --ct-sun-c2: #9B2226; }  /* orange → deep red      */
  50%  { --ct-sun-c1: #94D2BD; --ct-sun-c2: #0A9396; }  /* sand teal → teal       */
  75%  { --ct-sun-c1: #E9D8A6; --ct-sun-c2: #EE9B00; }  /* warm sand → amber      */
  100% { --ct-sun-c1: #EE9B00; --ct-sun-c2: #BB3E03; }  /* back to start          */
}

@keyframes ct-sun-glow-cycle {
  0%   { --ct-sun-glow-anim: #CA6702; }
  25%  { --ct-sun-glow-anim: #9B2226; }
  50%  { --ct-sun-glow-anim: #0A9396; }
  75%  { --ct-sun-glow-anim: #EE9B00; }
  100% { --ct-sun-glow-anim: #CA6702; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   GROUND / PERSPECTIVE GRID
   ═══════════════════════════════════════════════════════════════════════════ */
.ct-ground {
  flex: 0 0 35%;
  position: relative;
  overflow: hidden;
  background: var(--ct-sky-dark);
  perspective: 14.5rem;
  z-index: 2;
}

/* Animated grid lines */
.ct-ground::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to right,  var(--ct-grid-line) 0.06rem, transparent 0.06rem),
    linear-gradient(to bottom, var(--ct-grid-line) 0.06rem, transparent 0.06rem);
  background-size: 2rem 2rem;
  transform: rotateX(53deg) scale(1.8) translateZ(43px);
  animation: ct-grid var(--ct-speed) linear infinite;
  border-top: 1px solid var(--ct-grid-line);
  filter: drop-shadow(0 0 2px var(--ct-grid-glow));
}

@keyframes ct-grid {
  from { background-position-y: -30rem; }
  to   { background-position-y: 0%; }
}

/* Vignette — softens grid edges and hides perspective seams */
.ct-ground::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 10;
  pointer-events: none;
  background: radial-gradient(
    ellipse at center bottom,
    transparent 40%,
    rgba(10, 0, 24, 0.9) 80%
  );
}

/* ═══════════════════════════════════════════════════════════════════════════
   SHIP COCKPIT  (inline SVG injected by contact.js into .ct-cockpit)
   Transparent windshield reveals the scene; sits above landscape layers,
   below .ct-content (z:50, separate stacking context).
   ═══════════════════════════════════════════════════════════════════════════ */
.ct-cockpit {
  position: absolute;
  inset: 0;
  z-index: 20;
  pointer-events: none;
}

.ct-cockpit svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* Glow lights named in Illustrator ("glow lights top, left blink").
   NOTE: group no longer exists in the current SVG export — kept in case the
   blink group returns in a future export. */
#glow_ligths_top_x2C__left_blink {
  animation: ct-cockpit-blink 2.6s ease-in-out infinite;
}

@keyframes ct-cockpit-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.2; }
}

/* ── Cockpit entrance states (classes flipped by the boot timeline) ────────
   1. .ct-hide-lines + .ct-no-colors  → only the #holo windshield beam shows
   2. remove .ct-hide-lines           → teal glowing wireframe fades in
   3. remove .ct-no-colors            → color art fades in underneath
   4. remove .ct-lines-teal           → lines transition back to source colors */
.ct-cockpit svg #lines path {
  transition: fill 1s ease;
}

.ct-cockpit.ct-hide-lines svg #lines {
  opacity: 0;
}

.ct-cockpit.ct-lines-teal svg #lines path {
  fill: #94D2BD;                       /* --ct-grid-line light teal */
}

/* Zero-size base shadows so the glow interpolates smoothly to "off" */
.ct-cockpit svg #lines {
  filter:
    drop-shadow(0 0 0 rgba(148, 210, 189, 0))
    drop-shadow(0 0 0 rgba(148, 210, 189, 0));
  transition: filter 1s ease, opacity 0.8s ease;
}

.ct-cockpit.ct-lines-teal svg #lines {
  filter:
    drop-shadow(0 0 6px rgba(148, 210, 189, 0.85))
    drop-shadow(0 0 16px rgba(148, 210, 189, 0.45));
}

.ct-cockpit svg #colors {
  transition: opacity 1.1s ease;
}

.ct-cockpit.ct-no-colors svg #colors {
  opacity: 0;
}

/* ── Holo windshield beam (blue-gradient #holo polygon) ────────────────────
   Entrance: revealed LAST, after the line art has settled to its source
   colors (.ct-hide-holo is dropped at the end of the boot timeline).
   Random flicker: contact.js adds .ct-holo-blink every 8/10/15 s at random;
   the class is removed on animationend so it can re-trigger. */
.ct-cockpit svg #holo {
  transition: opacity 1s ease;
}

.ct-cockpit.ct-hide-holo svg #holo {
  opacity: 0;
}

#holo.ct-holo-blink {
  animation: ct-holo-flicker 0.7s steps(1) both;
}

@keyframes ct-holo-flicker {
  0%   { opacity: 0.15; }
  12%  { opacity: 1; }
  22%  { opacity: 0.3; }
  38%  { opacity: 1; }
  52%  { opacity: 0.1; }
  64%  { opacity: 0.85; }
  78%  { opacity: 0.4; }
  100% { opacity: 1; }
}

/* ── Dashboard hotspot glow — lights the line art under the pointing finger ─
   The #hover rects stay invisible. contact.js keeps a recolored clone of the
   #lines group soft-masked to the pointed rect (.ct-lit-layer), so the line
   art inside that area glows and dissolves at the edges — independent of how
   paths are grouped.
   Left button column → amber, right screen cluster → cyan. */
.ct-lit-layer {
  display: none;
  pointer-events: none;
}

.ct-lit-layer path,
.ct-lit-layer polygon,
.ct-lit-layer rect,
.ct-lit-layer circle,
.ct-lit-layer polyline,
.ct-lit-layer line {
  fill: var(--ct-lit-c, var(--ct-sun-warm));
}

.ct-lit-layer.ct-lit-on {
  display: block;
  /* light teal — same system color as the wireframe entrance */
  --ct-lit-c: var(--ct-grid-line);
  filter: drop-shadow(0 0 4px rgba(148, 210, 189, 0.9))
          drop-shadow(0 0 12px rgba(148, 210, 189, 0.5));
  /* ignite as the fingertip lands (hand travel ≈ 0.55s), then pulse */
  animation: ct-lit-ignite 0.3s ease 0.35s both,
             ct-lit-pulse 1.6s ease-in-out 0.9s infinite;
}

@keyframes ct-lit-ignite {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ── Flux capacitor FX — pulses run the Y-arms into the core ──────────────── */
.ct-flux {
  pointer-events: none;
}

/* Energy pulses on the Y-arms — warm sand, matching the artwork's tubes */
.ct-flux-dot {
  fill: var(--ct-star-color);
  filter: drop-shadow(0 0 3px rgba(233, 216, 166, 0.9))
          drop-shadow(0 0 8px rgba(238, 155, 0, 0.6));
  animation: ct-flux-travel 0.9s ease-in infinite;
}

@keyframes ct-flux-travel {
  0%   { transform: translate(0, 0); opacity: 0; }
  12%  { opacity: 1; }
  82%  { opacity: 1; }
  100% { transform: translate(var(--fx, 0), var(--fy, 0)); opacity: 0; }
}

/* Core flashes as the pulses arrive */
.ct-flux-core {
  fill: var(--ct-star-color);
  filter: drop-shadow(0 0 4px rgba(233, 216, 166, 1))
          drop-shadow(0 0 12px rgba(238, 155, 0, 0.7));
  transform-box: fill-box;
  transform-origin: center;
  animation: ct-flux-core-pulse 0.9s ease-in infinite;
}

@keyframes ct-flux-core-pulse {
  0%, 70% { opacity: 0.35; transform: scale(0.8); }
  92%     { opacity: 1;    transform: scale(1.3); }
  100%    { opacity: 0.5;  transform: scale(0.9); }
}

/* ── Breathing glow in the artwork's own colors (flux device + 1,000,000) ──
   A blurred clone of the art, screen-blended over itself and soft-masked to
   the region, giving the device a soft breathing halo. */
.ct-glow-layer {
  pointer-events: none;
  mix-blend-mode: screen;
  filter: blur(7px) saturate(1.5) brightness(1.7);
  animation: ct-glow-breathe 3.4s ease-in-out infinite;
}

/* Digit glow (1,000,000 readout) — the layer holds only the number glyphs:
   a crisp lit copy over a blurred bloom copy. No region blur, so nothing
   rectangular glows; the panel around the digits stays dark. */
.ct-glow-layer--digits {
  filter: saturate(1.4) brightness(1.5);
}

.ct-glow-layer--digits .ct-glow-bloom {
  filter: blur(6px);
}

@keyframes ct-glow-breathe {
  0%, 100% { opacity: 0.45; }
  50%      { opacity: 1; }
}

/* Readout bars above the EVA screen — slow intermittent glow: a gentle swell
   that holds, fades, then rests dim before the next pulse (calm, not a steady
   breathe). Higher specificity than .ct-glow-layer so it wins the override. */
.ct-glow-layer.ct-eva-bars-glow {
  animation: ct-eva-bars-pulse 7s ease-in-out infinite;
}

@keyframes ct-eva-bars-pulse {
  0%   { opacity: 0.12; }
  35%  { opacity: 0.9; }
  55%  { opacity: 0.9; }
  82%  { opacity: 0.12; }
  100% { opacity: 0.12; }
}

.ct-cockpit.ct-no-colors svg .ct-glow-layer,
.ct-cockpit.ct-no-colors svg .ct-flux {
  opacity: 0;
}

@keyframes ct-lit-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

/* ── EVA-02 dashboard screen — 9-frame scan flipbook @ 8 fps ────────────────
   Frames (nested <svg> viewports built by contact.js, each cropping the mech
   out of images/contact/N.svg) are stacked at the screen slot and shown one at
   a time. Same step-end negative-stagger flipbook as the DNA capsule: each
   frame is opaque for one 1/9 slice, hard-cut to the next. 9 × 0.125s = 8 fps.
   The static #glow_and_blink mech is hidden — frame 1 is its pose. */
.ct-eva-static { display: none; }

.ct-eva { --ct-eva-step: 0.125s; }   /* 1000ms / 8fps */

/* The panel starts FROZEN on frame 1 (blinking); the scan flipbook only runs
   once activated by a click (.ct-eva--playing, toggled in contact.js). */
.ct-eva-frame { opacity: 0; }
.ct-eva-first { opacity: 1; }        /* the frozen / idle pose (also RM fallback) */

/* Idle: frame 1 blinks like a HUD panel on standby, inviting a click. */
.ct-eva:not(.ct-eva--playing) .ct-eva-first {
  animation: ct-eva-blink 1.4s ease-in-out infinite;
}

/* Activated: hard-cut 8 fps scan flipbook across all 9 frames, recoloured to
   #AE2012 with a red glow — both baked into the single SVG filter (tint + bloom)
   so it survives WebKit, which drops a url() filter chained with drop-shadow(). */
.ct-eva--playing .ct-eva-frame {
  animation: ct-eva-scan calc(var(--ct-eva-count, 9) * var(--ct-eva-step))
             step-end infinite;
  animation-delay: calc((var(--ct-eva-i, 0) - var(--ct-eva-count, 9)) * var(--ct-eva-step));
  filter: url(#ct-eva-tint);
}

/* Transparent click target covering the whole EVA slot. */
.ct-eva-hit { fill: transparent; pointer-events: all; cursor: pointer; }

@keyframes ct-eva-scan {
  0%      { opacity: 1; }
  11.111% { opacity: 0; }   /* 1/9 of the loop */
  100%    { opacity: 0; }
}

/* Standby flicker for the frozen frame 1 — mostly lit, quick double dip. */
@keyframes ct-eva-blink {
  0%, 55%   { opacity: 1;    }
  57%       { opacity: 0.12; }
  60%       { opacity: 1;    }
  63%       { opacity: 0.4;  }
  66%, 100% { opacity: 1;    }
}

/* ── Second mech screen — 12-frame rotation, 4 fps, right of the EVA ────────
   Same hard-cut flipbook as the EVA scan; 12 × 0.25s = 3s loop. */
.ct-mech { --ct-mech-step: 0.25s; }   /* 4 fps */

.ct-mech-frame {
  opacity: 0;
  animation: ct-mech-spin calc(var(--ct-mech-count, 12) * var(--ct-mech-step))
             step-end infinite;
  animation-delay: calc((var(--ct-mech-i, 0) - var(--ct-mech-count, 12)) * var(--ct-mech-step));
}

.ct-mech-first { opacity: 1; }        /* static fallback (reduced motion) */

@keyframes ct-mech-spin {
  0%      { opacity: 1; }
  8.333%  { opacity: 0; }   /* 1/12 of the loop */
  100%    { opacity: 0; }
}

/* ── Side art — one random static illustration, recoloured teal + glow ──────
   Source files are black line art (no fill), fetched inline by contact.js so
   the shapes can be recoloured here. Static (no flipbook), soft teal bloom. */
.ct-sideart :is(path, polygon, rect, circle, polyline, ellipse, line) {
  fill: #94d2bd;
}

.ct-sideart {
  filter: drop-shadow(0 0 2px rgba(148, 210, 189, 0.9))
          drop-shadow(0 0 5px rgba(148, 210, 189, 0.45));
}

/* Auto-looping "time traveller" flipbook — a hard-cut opacity cycle across the
   4 frames (same mechanism as the EVA/mech screens), running on its own. */
.ct-sideart-anim { --ct-sideart-step: 0.16s; }   /* ~6 fps, calm loop */
.ct-sideart-frame {
  opacity: 0;
  animation: ct-sideart-cycle calc(var(--ct-sideart-count, 4) * var(--ct-sideart-step))
             step-end infinite;
  animation-delay: calc((var(--i, 0) - var(--ct-sideart-count, 4)) * var(--ct-sideart-step));
}
.ct-sideart-first { opacity: 1; }   /* static fallback (reduced motion / paused) */
@keyframes ct-sideart-cycle {
  0%   { opacity: 1; }
  25%  { opacity: 0; }   /* 1/4 of the loop */
  100% { opacity: 0; }
}

/* ── Separator — 3 vertical lines between the EVA animations and the side art ── */
.ct-sep-bar {
  fill: #94d2bd;
  filter: drop-shadow(0 0 2px rgba(148, 210, 189, 0.85));
}

/* ── Interfaz "SALTO TEMPORAL" screen — 8-frame cross-fade, 1 frame / 2 s ──
   Slower & softer than the EVA scan: each frame fades in, holds, fades out,
   overlapping the next (cross-dissolve, no black flash). Whole stack glows. */
.ct-iz-static { display: none; }

.ct-iz {
  /* Teal bloom on the showing frame */
  filter: drop-shadow(0 0 2px rgba(148, 210, 189, 0.9))
          drop-shadow(0 0 6px rgba(148, 210, 189, 0.45));
}

/* Each interfaz frame is inlined (one in the DOM at a time). Its potrace paths
   are grouped into horizontal bands that cascade in — top→bottom — every time
   the frame changes (contact.js toggles .ct-iz-in), so the panel's ships +
   readouts "type in" like the About skills SVG rather than a flat cross-fade. */
.ct-iz-band { opacity: 0; }
.ct-iz-in .ct-iz-band {
  opacity: 1;
  animation: ct-iz-band-in 0.38s ease-out calc(var(--band-index, 0) * 0.035s) backwards;
}
@keyframes ct-iz-band-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ── #glow_and_blink indicator lights — own-colour glow + random blink ──────
   contact.js (initGlowBlink) tags each element and sets its glow colour
   (--bk) plus a randomised duration (--bk-dur) and negative delay (--bk-delay)
   so the lights blink out of sync. */
.ct-blink-el {
  filter: drop-shadow(0 0 1.5px var(--bk, #EE9B00))
          drop-shadow(0 0 4px var(--bk, #EE9B00));
  animation: ct-blink var(--bk-dur, 3.4s) ease-in-out var(--bk-delay, 0s) infinite;
}

@keyframes ct-blink {
  0%, 36% { opacity: 1; }
  39%     { opacity: 0.12; }   /* quick blink */
  42%     { opacity: 1; }
  68%     { opacity: 1; }
  71%     { opacity: 0.22; }   /* second, lighter blink */
  73%     { opacity: 1; }
  100%    { opacity: 1; }
}

/* ── Pilot's left hand — bottom-left, on top of the cockpit (z:20) ──────────
   contact.js translates it so the fingertip (#hand-pointer_invisible) lands
   on the dashboard hover rects while form elements are hovered/focused. */
.ct-hand {
  position: absolute;
  left: 0;
  bottom: 0;
  z-index: 21;
  width: clamp(260px, 38vmin, 520px);
  /* Both poses are absolutely stacked, so the box keeps its height from the hand
     SVG's own ratio (viewBox 945.1 × 814.2) even while a pose is swapped out. */
  aspect-ratio: 945.1 / 814.2;
  pointer-events: none;
  /* Parked out of the viewport (down-left); contact.js slides it in by
     setting an inline transform when a form element is hovered/focused */
  transform: translate(-35%, 110%);
  transition: transform 0.55s cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: transform;
}

.ct-hand svg {
  display: block;
  width: 100%;
  height: auto;
}

/* Right hand — mirrored copy, parked off-screen bottom-right */
.ct-hand--right {
  left: auto;
  right: 0;
  transform: translate(35%, 110%);
}

/* Resting + click poses share the same box (identical viewBox). Exactly ONE is
   ever rendered — a clean 1-for-1 swap, never both, so no doubled fingers. The
   container's aspect-ratio holds the height while a pose is display:none'd. */
.ct-hand > svg.ct-hand-pose {
  position: absolute;
  top: 0;
  left: 0;
}
.ct-hand > svg.ct-hand-pose--click { display: none; }                              /* resting: click hidden  */
.ct-hand.is-clicking > svg.ct-hand-pose:not(.ct-hand-pose--click) { display: none; } /* tapping: resting hidden */
.ct-hand.is-clicking > svg.ct-hand-pose--click { display: block; }                 /* tapping: click shown   */

/* ── CSS scanline grain (replaces external noise.svg) ─────────────────────── */
.ct-grain {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  mix-blend-mode: overlay;
  opacity: 0.04;
  background: repeating-linear-gradient(
    to bottom,
    transparent 0px,
    transparent 2px,
    rgba(255, 255, 255, 0.15) 2px,
    rgba(255, 255, 255, 0.15) 3px
  );
}

/* ── VHS / CRT overlays (reuse .scanlines / .vhs-static from styles.css) ─── */
/* Raise z-index above all scene elements (grain=5, palms=4, mountains=3).
   .ct-scene is its own stacking context (z:1) so these never obscure .ct-content. */
#contact .scanlines {
  z-index: 8;
  opacity: 0.6;
}
#contact .vhs-static {
  z-index: 9;
  opacity: 0.8;
}

/* ═══════════════════════════════════════════════════════════════════════════
   SCENE BOOT SEQUENCE
   .ct-scene--boot hides everything on init.
   GSAP timeline in contact.js drives all the reveals.
   ═══════════════════════════════════════════════════════════════════════════ */
.ct-scene--boot #ct-stars,
.ct-scene--boot .ct-horizon-glow,
.ct-scene--boot .ct-mountains--far,
.ct-scene--boot .ct-mountains--mid,
.ct-scene--boot .ct-mountains--near,
.ct-scene--boot .ct-sun,
.ct-scene--boot .ct-ground,
.ct-scene--boot .ct-cockpit,
.ct-scene--boot .ct-content { opacity: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   CONTACT CONTENT  (floats in sky area above the scene)
   ═══════════════════════════════════════════════════════════════════════════ */
.ct-content {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 35%;          /* confined to sky portion */
  z-index: 50;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: clamp(1rem, 4vmin, 3rem);
  pointer-events: none;
  /* Centre darkening for text legibility */
  background: radial-gradient(
    ellipse 60% 50% at 50% 48%,
    rgba(0, 0, 0, 0.45) 0%,
    transparent 100%
  );
}

/* ── Stacked rotated title (left of the form) ──────────────────────────────
   Reference: rotated lines + skewed letters + layered hard shadows; letters
   are split into .ct-stack-char spans and staggered in by contact.js. */
.ct-title {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.6rem;
  pointer-events: all;
}

.ct-stack {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  font-family: 'Funnel Display', sans-serif;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.ct-stack-line {
  display: block;
  transform: rotate(-10deg);
  font-size: clamp(3.4rem, 8.6vmin, 5.7rem);
  line-height: 1.12;
  white-space: nowrap;
  color: var(--ct-sun-warm);            /* line 1 — site amber   */
}

.ct-stack-line:nth-child(2) {
  color: var(--ct-grid-line);           /* line 2 — light teal   */
}

.ct-stack-line--tag {
  font-size: clamp(1.6rem, 3.8vmin, 2.55rem);
  letter-spacing: 0.1em;
  color: var(--ct-accent);              /* CTA lines — site cyan */
}

.ct-stack-line--tag:nth-child(3) {
  margin-top: 0.35em;
}

.ct-stack-line--tag:nth-child(4) {
  color: var(--ct-sun-warm);            /* "in touch!" — site amber */
}

.ct-stack-char {
  display: inline-block;
  transform: skew(-10deg);
  white-space: pre;
  text-shadow:
    1px 1px #001219, 2px 2px #001219, 3px 3px #001219,
    4px 4px #001219, 5px 5px #001219, 6px 6px #001219;
}

/* Replay button — revealed by the timeline after the letters land */
.ct-stack-btn {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: 'Funnel Display', sans-serif;
  font-weight: 600;
  font-size: clamp(1.1rem, 1.8vw, 1.45rem);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ct-accent);
  transform: rotate(-10deg);
  opacity: 0;
  visibility: hidden;
  transition: color 0.25s ease;
}

.ct-stack-btn .ct-stack-char {
  text-shadow: 1px 1px #001219, 2px 2px #001219, 3px 3px #001219;
}

.ct-stack-btn:hover,
.ct-stack-btn:focus-visible {
  color: #fff;
  outline: none;
}

/* ── Comms-console form ───────────────────────────────────────────────────── */
.ct-form-row {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 3vmin, 2.5rem);
  margin-top: clamp(0.6rem, 1.6vmin, 1.2rem);
  /* Shared capsule width — the vertical banner derives its height from it
     (capsule art is 207.55 × 410.66 → height = width × 1.9787) */
  --ct-capsule-w: clamp(176px, 28vmin, 300px);
  /* The flag banner / capsule height — the form column matches it too */
  --ct-bar-h: calc(var(--ct-capsule-w) * 1.9787);
}

/* Form + social links share one column so their widths always match:
   the links row defines the column's intrinsic width, the form stretches. */
.ct-form-col {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  flex-shrink: 0;
  /* Match the flag banner / capsule height */
  height: var(--ct-bar-h);
}

/* Vertical section banner — same rendered height as the capsule.
   Negative right margin cancels most of the row gap so it nearly
   touches the form to its right. */
.ct-contact-bar {
  flex-shrink: 0;
  height: var(--ct-bar-h);
  width: auto;
  margin-right: calc(3px - clamp(1rem, 3vmin, 2.5rem));
  pointer-events: none;
}

/* DNA capsule — decorative line art beside the form. Glows with the same teal
   emitted-light halo as the about #id1svg badge (shared --id-glow tokens).
   The glow filter sits on the container so shell + DNA glow as one piece. */
.ct-capsule {
  position: relative;
  width: var(--ct-capsule-w, clamp(176px, 28vmin, 300px));
  flex-shrink: 0;
  pointer-events: none;
  filter: var(--id-glow-filter);
  /* Hidden until the section boots — grows in like #about's capsule */
  transform: scale(0);
  transform-origin: bottom left;
}

/* Entrance: reuse #about's capsuleGrow (widens to a thin line, then rises to
   full height). Gated to the .ct-glitch-in boot class added by contact.js so
   it plays as the form fades in. */
#contact.ct-glitch-in .ct-capsule {
  animation: capsuleGrow var(--duration-scene) ease-out 0s forwards;
}

/* Reduced motion never adds .ct-glitch-in — keep the capsule simply visible */
@media (prefers-reduced-motion: reduce) {
  .ct-capsule { transform: none; }
  .ct-capsule-dna { opacity: 1; }
}

.ct-capsule-shell {
  display: block;
  width: 100%;
  height: auto;
}

/* DNA art inlaid in the capsule body (below the cap bars, inside the walls);
   SVG is injected inline by contact.js and contain-fitted to this box */
.ct-capsule-dna {
  position: absolute;
  inset: 13% 11% 6%;
  /* Hidden while the shell grows in — the character enters afterwards */
  opacity: 0;
}

/* Character entrance: fades in only once the shell's capsuleGrow has finished
   (delay = --duration-scene), so the empty capsule appears alone first. */
#contact.ct-glitch-in .ct-capsule-dna {
  animation: fadeIn 0.7s ease-out var(--duration-scene) both;
}

.ct-capsule-dna svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* Match the shell's line-art color (#cdedf9) — source art is black */
.ct-capsule-dna path,
.ct-capsule-dna polygon,
.ct-capsule-dna rect,
.ct-capsule-dna circle {
  fill: #cdedf9;
}

/* ── DNA rotation flipbook ──────────────────────────────────────────────────
   The injected SVG's numbered groups are rotation frames. Each frame is
   visible for one slice (1/9) of the cycle; negative delays stagger them so
   they play in numbered order with no startup wait. step-end = hard cuts. */
.ct-capsule-dna {
  --ct-dna-step: 0.1s;   /* per-frame hold; 9 frames ≈ 0.9s per rotation */
}

.ct-dna-frame {
  opacity: 0;
  animation: ct-dna-spin calc(var(--ct-dna-count, 9) * var(--ct-dna-step))
             step-end infinite;
  animation-delay: calc((var(--dna-frame, 0) - var(--ct-dna-count, 9)) * var(--ct-dna-step));
}

/* Static fallback frame (reduced motion / animation off) */
.ct-dna-first { opacity: 1; }

@keyframes ct-dna-spin {
  0%      { opacity: 1; }
  11.111% { opacity: 0; }
  100%    { opacity: 0; }
}


.ct-form {
  position: relative;
  width: 100%;
  min-width: min(700px, 90vw);
  /* Grow to fill the column so the form + buttons match the flag height;
     the inner grid (and its textarea) absorbs the extra vertical space. */
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: clamp(1.2rem, 3vmin, 2.1rem) clamp(1.3rem, 3.5vmin, 2.4rem);
  pointer-events: all;
  text-align: left;
  background: linear-gradient(to bottom, rgba(0, 18, 25, 0.78), rgba(0, 9, 18, 0.68));
  border: 1px solid rgba(148, 210, 189, 0.22);
  box-shadow:
    0 0 30px rgba(0, 0, 0, 0.45),
    inset 0 0 24px rgba(10, 147, 150, 0.06);
}

/* Amber corner brackets (tl + br) */
.ct-form::before,
.ct-form::after {
  content: "";
  position: absolute;
  width: 14px;
  height: 14px;
  pointer-events: none;
}
.ct-form::before {
  top: -1px; left: -1px;
  border-top: 2px solid var(--ct-sun-warm);
  border-left: 2px solid var(--ct-sun-warm);
}
.ct-form::after {
  bottom: -1px; right: -1px;
  border-bottom: 2px solid var(--ct-sun-warm);
  border-right: 2px solid var(--ct-sun-warm);
}

.ct-form-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  font-family: 'Funnel Display', sans-serif;
  font-size: clamp(0.7rem, 1vw, 0.85rem);
  letter-spacing: 0.08em;
  color: rgba(148, 210, 189, 0.6);
  border-bottom: 1px solid rgba(148, 210, 189, 0.14);
  padding-bottom: 0.5em;
}

.ct-form-feed { white-space: nowrap; }

/* ── Glitch intro on the comms-form text ─────────────────────────────────
   Same Splitting.js per-letter scramble used across the site. Each element
   keeps its own colour via --glitch-final-color, and the scramble is gated
   to .ct-glitch-in (added by contact.js as the form fades in) so it fires on
   entrance instead of being wasted while the section is off-screen. */
.ct-form-title.glitch-text,
.ct-field label.glitch-text { --glitch-final-color: rgba(148, 210, 189, 0.85); }
.ct-send.glitch-text        { --glitch-final-color: var(--ct-sun-warm); }

#contact .ct-form .glitch-text[data-splitting] [data-char]:after {
  animation-name: none;
}
#contact.ct-glitch-in .ct-form .glitch-text[data-splitting] [data-char]:after {
  animation-name: glitch-switch;
}

.ct-form-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-left: 0.6em;
  border-radius: 50%;
  background: var(--ct-sun-warm);
  box-shadow: 0 0 6px var(--ct-sun-warm);
  animation: ct-form-dot-blink 2.2s ease-in-out infinite;
}

@keyframes ct-form-dot-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.15; }
}

.ct-form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: auto 1fr;
  gap: 0.7rem 1.1rem;
  margin-top: 1rem;
  /* Absorb the form's extra height so the buttons stay pinned at the bottom */
  flex: 1 1 auto;
  min-height: 0;
}

.ct-field--msg {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.ct-field label {
  display: block;
  font-family: 'Funnel Display', sans-serif;
  font-weight: 600;
  font-size: clamp(0.8rem, 1.1vw, 0.95rem);
  letter-spacing: 0.08em;
  color: rgba(148, 210, 189, 0.75);
  margin-bottom: 0.35em;
}

.ct-field input,
.ct-field textarea {
  display: block;
  width: 100%;
  font-family: 'Funnel Display', sans-serif;
  font-weight: 300;
  font-size: clamp(1.05rem, 1.5vw, 1.25rem);
  color: var(--ct-star-color);
  caret-color: var(--ct-sun-warm);
  background: rgba(0, 0, 0, 0.38);
  border: 1px solid rgba(148, 210, 189, 0.22);
  border-left: 2px solid rgba(148, 210, 189, 0.4);
  border-radius: 0;
  -webkit-appearance: none;
  appearance: none;
  padding: 0.45em 0.65em;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
}

.ct-field textarea {
  /* Grow to fill the message field so the form reaches the flag height */
  flex: 1 1 auto;
  height: auto;
  min-height: clamp(96px, 16vmin, 160px);
  resize: none;
}

.ct-field input::placeholder,
.ct-field textarea::placeholder {
  color: rgba(148, 210, 189, 0.3);
}

.ct-field input:focus,
.ct-field textarea:focus {
  outline: none;
  border-color: var(--ct-sun-warm);
  background: rgba(0, 0, 0, 0.55);
  box-shadow: 0 0 12px rgba(238, 155, 0, 0.25), inset 0 0 8px rgba(238, 155, 0, 0.06);
}

/* Validation errors — terminal style */
.ct-field-err {
  min-height: 1.1em;
  margin: 0.25em 0 0;
  font-family: 'Funnel Display', sans-serif;
  font-size: 0.58rem;
  letter-spacing: 0.06em;
  color: #ff7d6b;
}

.ct-field.is-invalid input,
.ct-field.is-invalid textarea {
  border-color: rgba(174, 32, 18, 0.85);
  box-shadow: 0 0 10px rgba(174, 32, 18, 0.3);
}

/* Footer row: transmit button + typed log */
.ct-form-foot {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 0.35rem;
  flex-wrap: wrap;
}

/* Turnstile mount — collapses to nothing until the widget is rendered (so the
   layout is unchanged if the Cloudflare script is blocked or not yet live). */
.ct-turnstile { margin: 0.75rem 0 0.25rem; }
.ct-turnstile:empty { display: none; }

/* Direct-email fallback link — only revealed by contact.js when a send fails. */
.ct-mail-fallback { flex-shrink: 0; }
.ct-mail-fallback[hidden] { display: none; }

.ct-send {
  flex-shrink: 0;
  position: relative;
  overflow: hidden;
  cursor: pointer;
  font-family: 'Funnel Display', sans-serif;
  font-weight: 600;
  font-size: clamp(1rem, 1.5vw, 1.2rem);
  letter-spacing: 0.06em;
  color: var(--ct-sun-warm);
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid rgba(238, 155, 0, 0.45);
  padding: 0.6em 1.6em;
  transition: color 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
}

.ct-send:hover:not(:disabled),
.ct-send:focus-visible {
  color: #fff;
  border-color: var(--ct-sun-warm);
  box-shadow: 0 0 15px rgba(238, 155, 0, 0.4), inset 0 0 12px rgba(238, 155, 0, 0.08);
  outline: none;
}

.ct-send:disabled { cursor: default; }

/* Scanning sweep while transmitting */
.ct-form.is-sending .ct-send::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(238, 155, 0, 0.35), transparent);
  transform: translateX(-100%);
  animation: ct-send-scan 0.9s linear infinite;
}

@keyframes ct-send-scan {
  to { transform: translateX(100%); }
}

.ct-form-log {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-family: 'Funnel Display', sans-serif;
  font-size: clamp(0.55rem, 0.85vw, 0.68rem);
  letter-spacing: 0.05em;
  color: rgba(148, 210, 189, 0.65);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ct-form-log::after {
  content: "_";
  color: var(--ct-sun-warm);
  animation: ct-log-caret 1s steps(1) infinite;
}

@keyframes ct-log-caret {
  50% { opacity: 0; }
}

/* Success overlay — replaces the panel contents after a mock send */
.ct-form-success {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  text-align: center;
  padding: 1rem;
  background: rgba(0, 14, 21, 0.94);
}

.ct-form-success[hidden] { display: none; }

.ct-form.is-sent .ct-form-grid,
.ct-form.is-sent .ct-form-foot { visibility: hidden; }

.ct-success-title {
  font-family: 'Funnel Display', sans-serif;
  font-weight: 800;
  font-size: clamp(1.1rem, 2.6vmin, 1.6rem);
  letter-spacing: 0.08em;
  color: var(--ct-sun-warm);
  text-shadow: 0 0 20px rgba(238, 155, 0, 0.45);
  margin: 0;
  outline: none;
}

.ct-success-sub {
  font-family: 'Funnel Display', sans-serif;
  font-size: clamp(0.6rem, 0.9vw, 0.75rem);
  color: rgba(233, 216, 166, 0.7);
  letter-spacing: 0.06em;
  margin: 0 0 0.4rem;
}

.ct-success-reset { cursor: pointer; }

/* Honeypot + screen-reader-only helpers */
.ct-hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.ct-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.ct-links {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(0.5rem, 2vmin, 1rem);
  justify-content: center;
  pointer-events: all;
}

.ct-link {
  font-family: 'Funnel Display', sans-serif;
  font-weight: 600;
  font-size: clamp(0.7rem, 1.2vw, 0.9rem);
  color: var(--ct-sun-warm);
  text-decoration: none;
  letter-spacing: 0.06em;
  padding: 0.5em 1.1em;
  border: 1px solid rgba(238, 155, 0, 0.35);
  background: rgba(0, 0, 0, 0.3);
  transition: color 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
}

.ct-link:hover,
.ct-link:focus-visible {
  color: #fff;
  border-color: var(--ct-sun-warm);
  box-shadow: 0 0 15px rgba(238, 155, 0, 0.4), inset 0 0 12px rgba(238, 155, 0, 0.08);
  outline: none;
}

/* Compact variant — direct-contact row under the form.
   Stays on one line: its width defines the form's width (.ct-form-col). */
.ct-links--mini {
  margin-top: clamp(0.6rem, 1.6vmin, 1rem);
  flex-wrap: nowrap;
  justify-content: space-between;
}

.ct-links--mini .ct-link {
  font-size: clamp(0.85rem, 1.2vw, 1.05rem);
  padding: 0.6em 1.3em;
  border-color: rgba(238, 155, 0, 0.3);
  background: rgba(0, 0, 0, 0.28);
  white-space: nowrap;
}

/* ── Site footer — overlaid on the cockpit dashboard bottom edge ─────────── */
.ct-footer {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 60;
  padding: 0.5em 1em;
  text-align: center;
  pointer-events: none;
}

.ct-footer p {
  font-family: 'Funnel Display', sans-serif;
  font-size: clamp(0.55rem, 0.8vw, 0.7rem);
  color: rgba(233, 216, 166, 0.45);
  letter-spacing: 0.05em;
  margin: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
   PERFORMANCE — pause all animations when section is off-screen
   Entry/departure are exempt so they can complete through the scroll boundary.
   ═══════════════════════════════════════════════════════════════════════════ */
#contact.ct-paused * {
  animation-play-state: paused !important;
}

/* ═══════════════════════════════════════════════════════════════════════════
   REDUCED MOTION
   ═══════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  .ct-sun,
  .ct-sun::after,
  .ct-sun::before,
  .ct-ground::before,
  .ct-s1, .ct-s2, .ct-s3,
  #glow_ligths_top_x2C__left_blink,
  .ct-form-dot,
  .ct-form-log::after,
  .ct-form.is-sending .ct-send::after,
  .ct-capsule,
  .ct-dna-frame,
  #contact .scanlines,
  #contact .vhs-static {
    animation: none;
  }

  /* Cockpit entrance states snap instead of cross-fading */
  .ct-cockpit svg #lines path,
  .ct-cockpit svg #lines,
  .ct-cockpit svg #colors,
  .ct-hand {
    transition: none;
  }

  /* Flux capacitor / glow regions: no pulses or breathing, calm static glow */
  .ct-flux-dot { display: none; }
  .ct-flux-core,
  .ct-glow-layer { animation: none; }

  /* EVA-02 screen: hold frame 1 static — no standby blink, no scan flipbook.
     Selectors mirror the base rules so they win on specificity under RM. */
  .ct-eva:not(.ct-eva--playing) .ct-eva-first,
  .ct-eva--playing .ct-eva-frame,
  .ct-eva-frame { animation: none; }

  /* Interfaz screen: show the frame's bands statically, no cascade */
  .ct-iz-band { opacity: 1; animation: none; }

  /* Second mech screen: hold frame 1 (.ct-mech-first), no spin */
  .ct-mech-frame { animation: none; }

  /* Side-art flipbook: hold frame 1 (.ct-sideart-first), no loop */
  .ct-sideart-frame { animation: none; }

  /* Indicator lights: stay lit + glowing, no blink */
  .ct-blink-el { animation: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   MOBILE
   ═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 640px) {
  .ct-ground { flex: 0 0 28%; }

  /* Keep content inside the windshield: the cockpit dashboard fills the
     bottom ~45% and the frame pillars eat into the sides. */
  .ct-content { bottom: 42%; }

  /* No room beside the form on phones: capsule hidden, title stacks on top */
  .ct-capsule { display: none; }
  .ct-contact-bar { display: none; }
  .ct-hand { display: none; }
  .ct-form-row {
    margin-top: 0.5rem;
    flex-direction: column;
    align-items: center;
    gap: 0.6rem;
  }

  .ct-title { gap: 0.6rem; }
  .ct-stack-line { font-size: clamp(1.3rem, 6.5vw, 1.8rem); }
  .ct-stack-line--tag { font-size: clamp(0.75rem, 3.5vw, 1rem); }

  /* Flag is hidden here — drop the height-match so content sizes naturally */
  .ct-form-col { width: min(420px, 86vw); height: auto; }

  .ct-form {
    width: 100%;
    min-width: 0;
    margin-top: 0;
    padding: 0.7rem 0.8rem;
    flex: 0 0 auto;
  }

  /* Single column; the location feed is sacrificed for width */
  .ct-form-grid { grid-template-columns: 1fr; grid-template-rows: none; flex: 0 0 auto; gap: 0.35rem; margin-top: 0.55rem; }
  .ct-form-feed { display: none; }
  .ct-field label { margin-bottom: 0.25em; }
  .ct-field input, .ct-field textarea { padding: 0.4em 0.6em; }
  .ct-field textarea { flex: 0 0 auto; height: 48px; min-height: 0; }
  .ct-field-err { min-height: 0; }
  .ct-field-err:empty { display: none; }

  .ct-form-foot {
    flex-direction: column;
    align-items: stretch;
    gap: 0.3rem;
    margin-top: 0.5rem;
  }
  .ct-send { width: 100%; text-align: center; }
  .ct-form-log { text-align: center; }

  .ct-links {
    max-width: 86vw;
    margin-inline: auto;
  }

  .ct-links--mini {
    margin-top: 0.5rem;
    gap: 0.35rem;
  }
  .ct-links--mini .ct-link {
    font-size: 0.65rem;
    padding: 0.4em 0.7em;
    white-space: nowrap;
  }
}

/* Short viewports — tighten vertical rhythm so the panel clears the dashboard */
@media (max-height: 720px) {
  .ct-stack-line { font-size: clamp(1.6rem, 4vmin, 2.4rem); }
  .ct-title { gap: 1rem; }
  .ct-field textarea { min-height: 52px; }
  .ct-field-err { min-height: 0; }
  .ct-field-err:empty { display: none; }
}


/* ===== illus-cube.css ===== */
/* ============================================================================
   ILLUSTRATION — Interdimensional Gallery on Timeless Art
   Cube CSS3D gallery, scoped to #illustration with .illus-* namespace
   ============================================================================ */

/* ── Section height override ─────────────────────────────────────────────── */
#illustration {
    height: 2800vh; /* JS overrides to N*100vh — this is the fallback */
    min-height: unset;
    background: #001219;
}

/* ── Design tokens ────────────────────────────────────────────────────────── */
.illus-tunnel {
    --illus-bg:           #001219;
    --illus-fg:           #E9D8A6;
    --illus-muted:        #94D2BD;
    --illus-accent:       #EE9B00;
    --illus-card-bg:      rgba(10, 147, 150, 0.5);
    --illus-card-border:  rgba(238, 155, 0, 0.35);
    --illus-hairline:     0.0625rem;
    --illus-ui-inset:     2rem;
    --illus-face-color:   #94D2BD;
    --illus-face-rgb:     148, 210, 189;
    /* Matches .illus-cube --s — used by hintHud to stay outside cube bounds */
    --cube-s:             min(74vw, 74vh, 560px);
}

/* ── Sticky viewport wrapper ─────────────────────────────────────────────── */
.illus-tunnel {
    position: sticky;
    top: var(--header-height, 60px);
    height: calc(100vh - var(--header-height, 60px));
    overflow: hidden;
    background: var(--illus-bg);
}

/* ── Handoff to #contact — frozen cross-dissolve ───────────────────────────
   During the HANDOFF band (section still pinned) illus-cube.js adds
   .illus-handoff: the section is raised above #contact and its opacity scrubbed
   1→0, revealing the fixed, opaque #contact backdrop sitting behind it (see
   #contact.ct-handoff-in). The tunnel stays compositor-pinned (sticky) — nothing
   moves, only opacity, so there's no scroll jitter and no translucent gap. */
#illustration.illus-handoff {
    z-index: var(--z-layer-15, 15);
    /* The section is faded to opacity 0 here but opacity does NOT disable
       hit-testing — left interactive it would sit above #contact (z 10) and
       swallow every click (form unusable) while the centred .illus-cube-clickzone
       kept opening the lightbox. Once #contact is the revealed layer it must own
       all input, so the spent gallery becomes click-through. */
    pointer-events: none;
}

/* ── VHS / CRT overlay ────────────────────────────────────────────────────── */
#illustration .scanlines,
#illustration .vhs-static {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

#illustration .scanlines  { z-index: 6; }
#illustration .vhs-static { z-index: 7; }

#illustration.paused-animations .scanlines,
#illustration.paused-animations .vhs-static {
    animation-play-state: paused !important;
}

/* ── Particle background (canvas injected by particle-system.js) ─────────── */
#illus-particle-canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 1;
    pointer-events: none;
    display: block;
}

/* ── 3D scene container ───────────────────────────────────────────────────── */
.illus-scene {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    perspective: 1100px;
    pointer-events: none;
    z-index: 2;
}

.illus-cube {
    --s: min(74vw, 74vh, 560px);
    width: var(--s);
    height: var(--s);
    position: relative;
    transform-style: preserve-3d;
    transform: rotateX(90deg) rotateY(0deg);
    will-change: transform;
}

.illus-face {
    position: absolute;
    inset: 0;
    overflow: hidden;
    backface-visibility: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 50px;
    box-sizing: border-box;
    background: rgba(var(--illus-face-rgb), 0.22);
    transition: background 0.6s ease;
}

.illus-face img {
    display: block;
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    opacity: 0;
}

/* Meninas process thumbnails — a column of same-width thumbs overlaid on the
   left of the piece's cube face. Rides the face's 3D transform. Gated visible
   by JS (.is-visible) only while the face shows the Meninas image. Clicks are
   routed from the cube clickzone (pointer-events off here). */
.illus-face-thumbs {
    position: absolute;
    left: 12px;
    top: 50%;
    translate: 0 -50%;
    display: flex;
    flex-direction: column;
    gap: 8px;
    z-index: 3;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.45s ease 0.35s;
}
.illus-face-thumbs.is-visible {
    opacity: 1;
    pointer-events: auto; /* thumbs sit outside the cube clickzone, so click them directly */
}
/* Right-side column (used when the text card is on the left) */
.illus-face-thumbs--right {
    left: auto;
    right: 12px;
}
/* Beats the generic `.illus-face img` (which forces opacity:0 + max-* sizing) */
.illus-face-thumbs .illus-face-thumb {
    display: block;
    width: 58px;        /* uniform width for every thumb */
    height: auto;       /* natural height → full process image, no crop */
    max-width: none;
    max-height: none;
    opacity: 1;
    background: #001219;
    border: 1.5px solid var(--illus-accent, #EE9B00);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.55);
    cursor: zoom-in;
    transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.illus-face-thumbs .illus-face-thumb:hover {
    transform: scale(1.06);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.7);
}

/* Inset main image — a piece with a tall thumb column (e.g. Draconic Love, 4
   thumbs) shrinks its artwork and hugs the opposite side so the two never
   overlap. The face reserves a gutter on the column side and pins the image
   to the far edge; capping max-width keeps the (square) art clear of the
   column both horizontally and vertically. */
.illus-face:has(.illus-main-img--inset-left) {
    justify-content: flex-end;
    padding-left: 96px;
}
.illus-face:has(.illus-main-img--inset-right) {
    justify-content: flex-start;
    padding-right: 96px;
}
.illus-face img.illus-main-img--inset-left,
.illus-face img.illus-main-img--inset-right {
    max-width: calc(100% - 48px);
    max-height: 82%;
}

/* ── Gallery title face — intro robot background image ───────────────────── */
.illus-face[data-face="top"] {
    background: url('../images/sergio-ayala-robot-red-teal-duotone-illustration-section.webp') center / cover no-repeat;
}

/* ── Image entry animation — scan reveal ─────────────────────────────────── */
/* Scan line sweeps top→bottom (GPU transform, no layout cost).               */
/* Image clip-path is locked to the same linear 1.1 s so they stay in sync:  */
/* at T% the line is T% down the face, the top T% of the image is revealed.  */

@keyframes illus-img-scan {
    0%  { clip-path: inset(0 0 100% 0); opacity: 1; }
    88% { clip-path: inset(0 0 0% 0); }
    to  { clip-path: inset(0 0 0% 0); opacity: 1; }
}

@keyframes illus-scan-sweep {
    from { transform: translateY(-2px);     opacity: 1; }
    88%  { transform: translateY(var(--s)); opacity: 1; }
    to   { transform: translateY(var(--s)); opacity: 0; }
}

/* ── Scan line element (injected into each face by JS) ───────────────────── */
.illus-scan-line {
    position: absolute;
    left: 0;
    right: 0;
    height: 2px;
    top: 0;
    transform: translateY(-2px);
    background: linear-gradient(90deg,
        transparent                        0%,
        rgba(var(--illus-face-rgb), .30)  12%,
        var(--illus-face-color)           50%,
        rgba(var(--illus-face-rgb), .30)  88%,
        transparent                        100%
    );
    box-shadow:
        0 0  4px  2px rgba(var(--illus-face-rgb), 1.00),
        0 0 12px  4px rgba(var(--illus-face-rgb), .60),
        0 0 32px  8px rgba(var(--illus-face-rgb), .28),
        0 0 72px 14px rgba(var(--illus-face-rgb), .10);
    opacity: 0;
    pointer-events: none;
    z-index: 2;
    will-change: transform, opacity;
}

.illus-scan-line.illus-scan-active {
    animation: illus-scan-sweep 1.1s linear 1 forwards;
}

.illus-face img.illus-img-enter {
    animation: illus-img-scan 1.1s linear 1 forwards;
    will-change: clip-path;
}

.illus-face-label {
    position: absolute;
    bottom: 15px;
    left: 50px;
    right: 50px;
    padding: 8px 14px;
    text-align: center;
    font-family: 'Funnel Display', sans-serif;
    font-size: clamp(0.72rem, 1.9vmin, 0.95rem);
    font-weight: 700;
    letter-spacing: 0.18em;
    color: #EE9B00;
    background: rgba(0, 18, 25, 0.88);
    pointer-events: none;
    user-select: none;
    white-space: nowrap;
}

/* Gallery title variant — stacked word-art layout, matching .illus-word-art */
.illus-face-label--gallery {
    bottom: auto;
    top: 50%;
    left: 32px;
    right: 32px;
    padding: 22px 24px;
    transform: translateY(-50%);
    display: flex;
    flex-direction: column;
    text-align: left;
    white-space: nowrap;
    background: rgba(0, 18, 25, 0.88);
    font-size: clamp(0.72rem, 1.9vmin, 1rem);
    font-weight: 700;
    line-height: 4;
    letter-spacing: 0.2em;
    text-shadow:
        0 0 14px rgba(238, 155, 0, 0.60),
        0 0 32px rgba(238, 155, 0, 0.28),
        0 0 56px rgba(238, 155, 0, 0.10);
}

/* ── Click-to-expand overlay — lives outside the 3D cube so it is always flat ─
   Positioned at the visual bottom of the cube using the same --s variable.
   Inherits --s from .illus-cube via the cascade; duplicated here as a fallback calc
   so the value stays in sync: min(74vw, 74vh, 560px) / 2 = min(37vw, 37vh, 280px). */
.illus-expand-hint {
    position: absolute;
    bottom: calc(50% - min(37vw, 37vh, 280px) + 12px);
    left: 50%;
    transform: translateX(-50%);
    padding: 8px 20px;
    text-align: center;
    font-family: 'Funnel Display', sans-serif;
    font-size: clamp(0.72rem, 1.9vmin, 0.95rem);
    font-weight: 700;
    letter-spacing: 0.18em;
    color: #EE9B00;
    background: rgba(0, 18, 25, 0.88);
    white-space: nowrap;
    pointer-events: none;
    user-select: none;
    z-index: 22;
    opacity: 1;
    transition: opacity 0.3s ease;
}

/* Hidden at stop 0 (gallery title — no photo to expand) */
.illus-tunnel.illus-stop-zero .illus-expand-hint {
    opacity: 0;
}

/* Animation restart helper — toggled in JS to re-fire the glitch-switch animation */
.illus-expand-hint.illus-glitch-reset [data-char]:after {
    animation-name: none !important;
}

/* Cancel global subtitle-glitch/glitch-text overrides that would break sizing */
.illus-expand-hint.subtitle-glitch {
    font-size: clamp(0.72rem, 1.9vmin, 0.95rem);
    margin-top: 0;
    text-shadow: none;
}
.illus-expand-hint.glitch-text {
    white-space: nowrap;
}

/* ── Floating ILLUSTRATION title — behind cube (z:1), synced with hint HUD ── */
@keyframes illus-title-float-enter {
    from { opacity: 0; transform: scale(4.5) translateY(2.5rem); filter: blur(3px); }
    to   { opacity: 0.72; transform: none; filter: blur(0); }
}

.illus-title-float {
    position: absolute;
    bottom: calc(50% + 11.5rem); /* raised in step with .illus-strip (sits just above it) */
    z-index: 1;
    display: flex;
    flex-direction: column;
    font-family: 'Funnel Display', sans-serif;
    font-weight: 800;
    font-size: clamp(1.3rem, 2.8vw, 1.76rem);
    letter-spacing: 0.14em;
    line-height: 1.85;
    color: var(--illus-fg);
    pointer-events: none;
    user-select: none;
    opacity: 0;
    transition: opacity 0.35s ease;
}

.illus-title-float--left  { left: 3.5rem;  text-align: left; }
.illus-title-float--right { right: 2rem; text-align: right; }

.illus-title-float--visible {
    animation: illus-title-float-enter 0.7s var(--ease-expo-out) 1 both;
}

.illus-title-float-in {
    padding-inline-start: 1.5ch;
}

.illus-title-float-lust {
    color: var(--illus-lust-accent, var(--illus-fg));
    transition: color 0.55s ease;
    padding-inline-start: 1.5ch;
}

/* Glitch-switch animation — enabled on [data-char]:after once splitting is done */
.illus-title-float [data-char]:after {
    color: var(--illus-fg);
    font-weight: 800;
    animation-name: none;
}

.illus-title-float--visible [data-char]:after {
    animation-name: glitch-switch;
}

.illus-title-float-lust [data-char]:after {
    color: var(--illus-lust-accent, var(--illus-fg));
    transition: color 0.55s ease;
}

/* Reset helper — suppress animation-name then remove to re-fire */
.illus-title-float.illus-glitch-reset [data-char]:after {
    animation-name: none !important;
}

@media (width <= 56.25em) {
    .illus-title-float { display: none; }
}

/* Indent all lines after the first to align under the content start.
   "[ · " = 4 chars × (1ch each) + 4 × 0.2em letter-spacing = calc(4ch + 0.8em) */
.illus-gallery-indent {
    padding-inline-start: calc(4ch + 0.8em);
}

/* Scroll hint in HUD — stacked word-art below the progress line */
.illus-hud-scroll {
    display: flex;
    flex-direction: column;
    text-align: left;
    margin-block-start: 1.4rem;
    font-size: 0.76rem;
    font-weight: 800;
    letter-spacing: 0.15em;
    line-height: 1.85;
    color: var(--illus-muted);
    text-shadow:
        0 0 4px  rgba(148, 210, 189, 0.90),
        0 0 12px rgba(148, 210, 189, 0.55),
        0 0 28px rgba(148, 210, 189, 0.25);
    animation: illus-neon-flicker 22s linear 2.5s infinite;
}

@keyframes illus-neon-flicker {
    /* Long stable open */
    0%, 8.8%   { opacity: 1;    }
    /* Quick double blink */
    9%         { opacity: 0.1;  }
    9.3%       { opacity: 1;    }
    9.6%       { opacity: 0;    }
    9.9%       { opacity: 1;    }
    /* Long stable */
    10.5%, 34.8% { opacity: 1; }
    /* Single dim */
    35%        { opacity: 0.35; }
    35.5%      { opacity: 1;    }
    /* Long stable */
    36%, 54.8% { opacity: 1;    }
    /* Triple rapid flicker */
    55%        { opacity: 0;    }
    55.2%      { opacity: 1;    }
    55.5%      { opacity: 0.15; }
    55.8%      { opacity: 1;    }
    56.1%      { opacity: 0;    }
    56.4%      { opacity: 1;    }
    /* Long stable */
    57%, 74.8% { opacity: 1;    }
    /* Single off */
    75%        { opacity: 0;    }
    75.4%      { opacity: 1;    }
    /* Long stable */
    76%, 89.8% { opacity: 1;    }
    /* Dim then stutter */
    90%        { opacity: 0.4;  }
    90.4%      { opacity: 0;    }
    90.8%      { opacity: 1;    }
    /* Stable to end */
    91.5%, 100% { opacity: 1;   }
}

/* Cancel .glitch-text white-space:pre so the stacked flex layout still works */
.illus-hud-scroll.glitch-text {
    white-space: normal;
}

/* Cancel .subtitle-glitch overrides — keep only the glitch-switch animation */
.illus-hud-scroll.subtitle-glitch {
    font-size: 0.76rem;
    margin-top: 0;
    text-shadow: none;
}

/* Indent lines after the first — same calc as gallery title */
.illus-hud-scroll-in {
    padding-inline-start: calc(4ch + 0.8em);
}

/* ── Click zone — captures clicks on the front-facing cube face ──────────── */
.illus-cube-clickzone {
    position: absolute;
    top: 50%;
    left: 50%;
    width: min(74vw, 74vh, 560px);
    height: min(74vw, 74vh, 560px);
    translate: -50% -50%;
    z-index: 4;
    cursor: zoom-in;
}

/* ── Fullscreen lightbox ─────────────────────────────────────────────────── */
.illus-lightbox {
    position: fixed;
    inset: 0;
    z-index: 9000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 18, 25, 0.96);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.35s ease;
}

.illus-lightbox.open {
    opacity: 1;
    pointer-events: auto;
}

.illus-lightbox-frame {
    position: relative;
    display: inline-flex;
    transform: scale(0.2);
    transition: transform 0.5s var(--ease-spring);
}

.illus-lightbox.open .illus-lightbox-frame {
    transform: scale(1);
}

.illus-lightbox-img {
    display: block;
    max-width: 88vw;
    max-height: 85vh;
    width: auto;
    height: auto;
}

.illus-lightbox-border {
    position: absolute;
    inset: -6px;
    border: 2px solid #94D2BD;
    box-shadow:
        0 0  8px rgba(148, 210, 189, 0.85),
        0 0 24px rgba(148, 210, 189, 0.40),
        0 0 48px rgba(238, 155,   0, 0.18);
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.45s ease;
}

/* Snap border on the moment the frame starts growing; remove class after settle */
.illus-lightbox.lb-elec-active .illus-lightbox-border {
    opacity: 1;
    transition: opacity 0s;
}

@keyframes illus-lb-close-pulse {
    0%, 100% {
        color: #94D2BD;
        text-shadow: 0 0 6px rgba(148, 210, 189, 0.35);
    }
    50% {
        color: #EE9B00;
        text-shadow:
            0 0 10px rgba(238, 155, 0, 0.80),
            0 0 24px rgba(238, 155, 0, 0.35);
    }
}

.illus-lightbox-close {
    position: absolute;
    top: -2.75rem;
    right: 0;
    background: transparent;
    border: none;
    color: #94D2BD;
    font-family: 'Funnel Display', sans-serif;
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: 0.22em;
    cursor: pointer;
    padding: 0.25rem 0.5rem;
    transition: color 0.2s, text-shadow 0.2s;
}

/* Pulse starts at ~900ms — the electric border begins fading at 620ms over 450ms,
   so this handoff visually passes attention from border to close button. */
.illus-lightbox.open .illus-lightbox-close {
    animation: illus-lb-close-pulse 2s ease-in-out 0.9s infinite;
}

.illus-lightbox-close:hover {
    color: #EE9B00;
    text-shadow:
        0 0 14px rgba(238, 155, 0, 0.85),
        0 0 28px rgba(238, 155, 0, 0.40);
    animation-play-state: paused;
}


.illus-face[data-face="front"]  { transform: translateZ(calc(var(--s) / 2)); }
.illus-face[data-face="back"]   { transform: rotateY(180deg)  translateZ(calc(var(--s) / 2)); }
.illus-face[data-face="right"]  { transform: rotateY(90deg)   translateZ(calc(var(--s) / 2)); }
.illus-face[data-face="left"]   { transform: rotateY(-90deg)  translateZ(calc(var(--s) / 2)); }
.illus-face[data-face="top"]    { transform: rotateX(-90deg)  translateZ(calc(var(--s) / 2)); }
.illus-face[data-face="bottom"] { transform: rotateX(90deg)   translateZ(calc(var(--s) / 2)); }

/* ── Electric border faces — rotate with the cube in 3D ─────────────────── */
/* Sibling elements to the image faces, using identical transforms so they   */
/* sit exactly on each cube face and turn with the cube. backface-visibility  */
/* hidden ensures only the front-facing face's border is visible at a time.  */
.illus-face-elec {
    position: absolute;
    inset: 0;
    backface-visibility: hidden;
    background: transparent;
    pointer-events: none;
    border: 2px solid var(--illus-face-color);
    box-shadow:
        0 0  6px rgba(var(--illus-face-rgb), 0.70),
        0 0 18px rgba(var(--illus-face-rgb), 0.25),
        0 0 32px rgba(238, 155,   0, 0.12);
    opacity: 0;
    transition: opacity 0.4s ease;
}

.illus-tunnel.illus-electric-active .illus-face-elec {
    opacity: 1;
}

/* Mirror the image-face transforms exactly */
.illus-face-elec[data-face="front"]  { transform: translateZ(calc(var(--s) / 2)); }
.illus-face-elec[data-face="back"]   { transform: rotateY(180deg)  translateZ(calc(var(--s) / 2)); }
.illus-face-elec[data-face="right"]  { transform: rotateY(90deg)   translateZ(calc(var(--s) / 2)); }
.illus-face-elec[data-face="left"]   { transform: rotateY(-90deg)  translateZ(calc(var(--s) / 2)); }
.illus-face-elec[data-face="top"]    { transform: rotateX(-90deg)  translateZ(calc(var(--s) / 2)); }
.illus-face-elec[data-face="bottom"] { transform: rotateX(90deg)   translateZ(calc(var(--s) / 2)); }

/* ── HUD (top-right) ─────────────────────────────────────────────────────── */
.illus-hud {
    position: absolute;
    top: var(--illus-ui-inset);
    right: var(--illus-ui-inset);
    text-align: right;
    font-family: 'Funnel Display', sans-serif;
    font-size: 0.65rem;
    letter-spacing: 0.15em;
    color: var(--illus-muted);
    text-transform: uppercase;
    pointer-events: none;
    user-select: none;
    z-index: 20;
}

.illus-progress-bar {
    width: 7.5rem;
    height: var(--illus-hairline);
    background: color-mix(in srgb, var(--illus-muted) 40%, transparent);
    margin-block-start: 0.5rem;
    margin-inline-start: auto;
    position: relative;
    overflow: hidden;
}

.illus-progress-fill {
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    width: 0%;
    background: var(--illus-accent);
    transition: width 0.1s linear;
}

.illus-scene-label {
    font-size: 0.6rem;
    color: var(--illus-accent);
    margin-block-start: 0.4rem;
}

/* ── Left bar SVG decoration ─────────────────────────────────────────────── */
.illus-svg-bar {
    position: absolute;
    left: 0;
    top: 0;
    height: 100%;
    width: auto;
    display: block;
    pointer-events: none;
    user-select: none;
    z-index: 3;
}

/* ── Navigation dots — mirrors titleFloat anchor just below it ────────────── */
.illus-strip {
    position: absolute;
    /* Raised so the (now longer) title list clears the how-to-explore SVG below.
       Anchored to 50% with the same top on both sides — the L/R flip only swaps
       left/right + translate, so the vertical layout is identical either side. */
    top: calc(50% - 10.75rem); /* just below titleFloat bottom edge */
    left: 3.5rem;
    translate: 0 0;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    z-index: 20;
    transition: left 0.45s var(--ease-expo-out, cubic-bezier(0.16,1,0.3,1)),
                translate 0.45s var(--ease-expo-out, cubic-bezier(0.16,1,0.3,1));
}

/* Right side: right edge at 2rem from right — matches titleFloat--right */
.illus-strip--right {
    left: calc(100% - 2rem);
    translate: -100% 0;
}


.illus-dot {
    position: relative;
    display: block;
    width: 0.25rem;
    height: 0.25rem;
    border-radius: 50%;
    border: none;
    padding: 0;
    background: color-mix(in srgb, var(--illus-muted) 55%, transparent);
    cursor: pointer;
    transition: background 0.3s, scale 0.3s;
}

.illus-dot::before {
    content: "";
    position: absolute;
    inset: -0.4rem;
}

.illus-dot::after {
    content: attr(data-label);
    position: absolute;
    top: 50%;
    left: 50%;
    white-space: nowrap;
    font-family: 'Funnel Display', sans-serif;
    font-weight: 300;
    font-size: 0.7rem;
    letter-spacing: 0.04em;
    color: var(--illus-muted);
    opacity: 0.6;
    /* The name label is the primary click target of each strip entry (the dot
       itself is tiny): make it clickable so a click anywhere on the name routes
       through the [data-goto] delegate → gotoSlide. cursor inherits pointer. */
    pointer-events: auto;
    /* translateX: dot half-width (0.125rem) + gap — places label to the right */
    transform: translateY(-50%) translateX(calc(0.125rem + 0.65rem));
    transition: color 0.3s, opacity 0.3s,
                transform 0.45s var(--ease-expo-out, cubic-bezier(0.16,1,0.3,1));
}

/* When strip is on the right, flip labels to the left */
.illus-strip--right .illus-dot::after {
    transform: translateY(-50%) translateX(calc(-100% - 0.125rem - 0.65rem));
}



/* Hover affordance — the whole name lights up so it reads as clickable. */
.illus-dot:hover::after,
.illus-dot:focus-visible::after {
    color: var(--illus-accent);
    opacity: 1;
}

.illus-dot.active::after {
    color: var(--illus-accent);
    opacity: 1;
}

.illus-dot.active {
    background: var(--illus-accent);
    scale: 1.8;
}


/* ── Face caption (bottom-center) ───────────────────────────────────────── */
.illus-caption {
    position: absolute;
    bottom: var(--illus-ui-inset);
    left: 50%;
    translate: -50% 0;
    text-align: center;
    pointer-events: none;
    user-select: none;
    z-index: 20;
    transition: opacity 0.3s ease;
}

/* Hide caption on the gallery-title face (stop 0 — no image, no image title) */
.illus-tunnel.illus-stop-zero .illus-caption {
    opacity: 0;
}

.illus-caption-num {
    font-family: 'Funnel Display', sans-serif;
    font-size: 0.58rem;
    letter-spacing: 0.28em;
    color: var(--illus-accent);
    text-transform: uppercase;
    margin-block-end: 0.15rem;
}

@keyframes illus-name-glitch {
    0%        { translate: 0 0;    filter: none; opacity: 0; }
    8%        { translate: -7px 0; opacity: 1;
                filter: drop-shadow( 5px 0 0 rgba(238,155,  0,.95))
                        drop-shadow(-5px 0 0 rgba(148,210,189,.95)); }
    20%       { translate:  5px 0;
                filter: drop-shadow(-4px 0 0 rgba(238,155,  0,.70))
                        drop-shadow( 4px 0 0 rgba(148,210,189,.70)); }
    33%       { translate: -3px 0; filter: none; }
    47%       { translate:  2px 0;
                filter: drop-shadow( 3px 0 0 rgba(238,155,0,.50)); }
    62%       { translate: -1px 0; filter: none; }
    78%, 100% { translate: 0 0;    filter: none; opacity: 1; }
}

.illus-caption-name {
    font-family: 'Funnel Display', sans-serif;
    font-size: clamp(0.62rem, 1.45vw, 0.88rem);
    letter-spacing: 0.16em;
    color: #E9D8A6;
    text-shadow:
        0 0 12px rgba(233, 216, 166, 0.60),
        0 0 30px rgba(233, 216, 166, 0.22);
    opacity: 1;
    line-height: 1.6;
    white-space: nowrap;
}

.illus-caption-name.illus-name-glitch {
    animation: illus-name-glitch 0.42s steps(1, end) 1 both;
}

/* ── Sections layer ──────────────────────────────────────────────────────── */
.illus-sections {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 15;
}

.illus-section {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    padding: 6rem calc(5rem + var(--illus-ui-inset)) 6rem 5rem;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.45s ease;
}

.illus-section.active {
    opacity: 1;
    pointer-events: none; /* wrapper stays transparent — only .illus-card captures events */
}

.illus-section.active .illus-card {
    pointer-events: auto;
}

/* ── Cards ────────────────────────────────────────────────────────────────── */
.illus-card {
    max-width: 23.75rem;
    padding: 2.25rem 2rem;
    background: var(--illus-card-bg);
    border-left: var(--illus-hairline) solid var(--illus-card-border);
}

.illus-card--right {
    margin-inline-start: auto;
    border-left: none;
    border-right: var(--illus-hairline) solid var(--illus-card-border);
    text-align: right;
}

.illus-card--right .illus-h-line     { transform-origin: right; margin-inline-start: auto; }
.illus-card--right .illus-cta-row    { justify-content: flex-end; }
.illus-card--right .illus-stat-row   { justify-content: flex-end; }

.illus-tag {
    font-family: 'Funnel Display', sans-serif;
    font-size: 0.6rem;
    letter-spacing: 0.25em;
    text-transform: uppercase;
    color: #E9D8A6;
    margin-block-end: 1.1rem;
}

.illus-tag--hero {
    font-family: 'Funnel Display', sans-serif;
    font-weight: 800;
    font-size: 1rem;
    text-transform: none;
    letter-spacing: 0.18em;
}

.illus-h1 {
    font-family: 'Funnel Display', sans-serif;
    font-weight: 800;
    font-size: clamp(3rem, 8vw, 6.5rem);
    letter-spacing: 0.02em;
    line-height: 0.92;
    color: var(--illus-fg);
}

.illus-word-art {
    font-family: 'Funnel Display', sans-serif;
    font-weight: 800;
    font-size: clamp(1.7rem, 4.2vw, 3rem);
    letter-spacing: 0.14em;
    line-height: 1.35;
    display: flex;
    flex-direction: column;
}

.illus-word-in {
    padding-inline-start: 2.2ch;
}

/* Gate glitch off until slide 0 is active; override white to cream */
#illustration .illus-word-art [data-char]:after {
    animation-name: none;
    color: var(--illus-fg);
    font-weight: 800;
}

#illustration .illus-section.active .illus-word-art [data-char]:after {
    animation-name: glitch-switch;
}

/* Body glitch — display helper for line spans */
.illus-body .glitch-text {
    display: block;
}

/* Restore correct text colors when glitch-text class is added to card elements
   (global glitch-text may override color; these rules win by specificity) */
#illustration .illus-tag.glitch-text,
#illustration .illus-tag .glitch-text {
    color: #E9D8A6;
}

#illustration .illus-h2.glitch-text {
    color: var(--illus-fg);
    font-weight: 800;
    white-space: normal;
}

#illustration .illus-h2--category.glitch-text {
    color: #CA6702;
}

#illustration .illus-h2 [data-word] {
    display: inline;
    white-space: normal;
}

#illustration .illus-body.glitch-text,
#illustration .illus-body .glitch-text {
    color: var(--illus-fg);
    font-weight: 300;
    white-space: normal;
}

#illustration .illus-body [data-word] {
    display: inline;
    white-space: normal;
}

#illustration .illus-body [data-char] {
    display: inline;
}

/* Gate all card text glitch off by default inside #illustration */
#illustration .illus-tag [data-char]:after,
#illustration .illus-h2 [data-char]:after,
#illustration .illus-body [data-char]:after {
    animation-name: none;
    color: var(--illus-fg);
}

/* Category label keeps its own colour — must come after the gate rule above */
#illustration .illus-h2--category [data-char]:after {
    color: #CA6702;
}

/* Fire each element's glitch AFTER its own slide-in transition finishes.
   Delays match: transition-delay + transition-duration per element.
   Per-char stagger via --count keeps characters from firing all at once. */
#illustration .illus-section.active .illus-tag [data-char]:after {
    animation-name: glitch-switch;
    animation-delay: calc(0.5s + var(--count, 1) * 0.06s);
}

#illustration .illus-section.active .illus-h2 [data-char]:after {
    animation-name: glitch-switch;
    animation-delay: calc(0.6s + var(--count, 1) * 0.06s);
}

#illustration .illus-section.active .illus-body [data-char]:after {
    animation-name: glitch-switch;
    animation-delay: calc(0.75s + var(--count, 1) * 0.05s);
}

/* Speed up every glitch text in the section to the mobile tempo. The global
   rule (styles.css) scales iterations per character with
   animation-iteration-count: calc(var(--char-index) * 0.55), so long bodies
   crawl (last char flickers ~110×0.2s ≈ 22s). Mobile uses a fixed short count
   (--glitch-count: 4); match it here so each char settles in 4×0.2s = 0.8s
   regardless of position. Covers tag, word-art, title-float, category, body. */
#illustration [data-char]::after {
    animation-iteration-count: 4;
}

/* "LUST" accent — color driven by --illus-lust-accent, updated by JS on every stop change.
   Specificity (1,5,1) beats the base word-art rule (1,4,1) to override color only here.
   The transition fires from --illus-fg to the new color each time the section becomes .active,
   giving a smooth chromatic reveal that coincides with the card fade-in. */
#illustration .illus-section.active .illus-word-art .illus-lust-accent [data-char]:after {
    color: var(--illus-lust-accent, var(--illus-fg));
    transition: color 0.55s ease;
}

.illus-piece-title {
    font-family: 'Funnel Display', sans-serif;
    font-weight: 800;
    font-size: clamp(1.1rem, 2.2vw, 1.6rem);
    line-height: 1.25;
    color: var(--illus-fg);
    margin-block-end: 0.5rem;
}

.illus-h2 {
    font-family: 'Funnel Display', sans-serif;
    font-weight: 800;
    font-size: clamp(2.2rem, 5vw, 4rem);
    letter-spacing: 0.02em;
    line-height: 0.92;
    color: var(--illus-fg);
    word-break: break-word;
}

.illus-h2--category {
    font-size: clamp(0.65rem, 1.2vw, 0.85rem);
    letter-spacing: 0.28em;
    color: #CA6702;
    margin-block-end: 1rem;
}

.illus-h2 [data-char] {
    display: inline;
}

.illus-body {
    font-family: 'Funnel Display', sans-serif;
    font-weight: 300;
    font-size: 1rem;
    line-height: 1.8;
    color: var(--illus-fg);
    margin-block-start: 1.25rem;
}

.illus-technique {
    display: block;
    font-family: 'Funnel Display', sans-serif;
    font-size: 0.58rem;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--illus-muted);
    opacity: 0.85;
    margin-block-start: 0.8rem;
}

.illus-h-line {
    width: 3.125rem;
    height: var(--illus-hairline);
    background: var(--illus-accent);
    margin-block-end: 1.2rem;
    transform-origin: left;
}

.illus-meta-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin-block-start: 1.2rem;
    flex-wrap: wrap;
}

.illus-meta-item {
    font-family: 'Funnel Display', sans-serif;
    font-size: 0.7rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--illus-muted);
    opacity: 0.8;
}

/* Artist-reference link inside the meta row — reads as an attribution credit */
a.illus-meta-link {
    pointer-events: auto;
    text-decoration: none;
    border-bottom: 1px solid color-mix(in srgb, var(--illus-accent) 55%, transparent);
    transition: color 0.25s ease, opacity 0.25s ease, border-color 0.25s ease;
}
a.illus-meta-link:hover,
a.illus-meta-link:focus-visible {
    color: var(--illus-accent);
    opacity: 1;
    border-color: var(--illus-accent);
}

.illus-meta-sep {
    color: var(--illus-accent);
    font-size: 0.65rem;
    opacity: 0.5;
}

.illus-card--right .illus-meta-row { justify-content: flex-end; }

.illus-ig-link {
    list-style: none;
    padding: 0;
    margin: 0.8rem 0 0;
    --icon-size: 36px;
    /* override global .social-media-icons defaults */
    position: static;
    bottom: auto;
    right: auto;
    transform: none;
    gap: 0;
    z-index: auto;
}

.illus-card--right .illus-ig-link { justify-content: flex-end; display: flex; }

.illus-stat-row {
    display: flex;
    gap: 2.5rem;
    margin-block-start: 2rem;
    flex-wrap: wrap;
}

.illus-stat {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}

.illus-stat-num {
    font-family: 'Funnel Display', sans-serif;
    font-weight: 800;
    font-size: 2.2rem;
    color: var(--illus-accent);
    line-height: 1;
}

.illus-stat-label {
    font-family: 'Funnel Display', sans-serif;
    font-size: 0.58rem;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--illus-muted);
}

/* ── Button glitch — fires once when section enters .active ──────────────── */
@keyframes illus-btn-glitch {
    0%        { transform: translate(0)      skewX(0);       filter: none; }
    12%       { transform: translateX(-5px)  skewX(-2deg);
                filter: drop-shadow( 4px 0 0 rgba(148,210,189,.85))
                        drop-shadow(-4px 0 0 rgba(238,155,  0,.85)); }
    26%       { transform: translateX( 4px)  skewX( 1.5deg);
                filter: drop-shadow(-3px 0 0 rgba(148,210,189,.65))
                        drop-shadow( 3px 0 0 rgba(238,155,  0,.65)); }
    40%       { transform: translateX(-2px)  skewX(-1deg);   filter: none; }
    55%       { transform: translateX( 3px)  skewX( 1deg);
                filter: drop-shadow( 5px 0 0 rgba(148,210,189,.75))
                        drop-shadow(-5px 0 0 rgba(238,155,  0,.75)); }
    70%       { transform: translateX(-1px);                 filter: none; }
    85%, 100% { transform: none;                             filter: none; }
}

/* ── CTA buttons ─────────────────────────────────────────────────────────── */
.illus-cta-row {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-block-start: 1.75rem;
}

.illus-cta {
    display: inline-flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.85rem 1.75rem;
    border: 2px solid var(--illus-accent);
    background: var(--illus-accent);
    color: #001219;
    font-family: 'Funnel Display', sans-serif;
    font-size: 0.82rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s;
}

.illus-cta:hover {
    background: transparent;
    color: var(--illus-accent);
    box-shadow: 0 0 18px rgba(238,155,0,.45), inset 0 0 0 0 transparent;
}

.illus-cta svg { width: 0.875rem; height: 0.875rem; }

.illus-cta-back {
    display: inline-flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.85rem 1.75rem;
    border: 2px solid rgba(148, 210, 189, 0.45);
    background: transparent;
    color: var(--illus-muted);
    font-family: 'Funnel Display', sans-serif;
    font-size: 0.82rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background 0.2s, color 0.2s, border-color 0.2s;
}

.illus-cta-back:hover {
    background: rgba(148, 210, 189, 0.12);
    color: var(--illus-fg);
    border-color: var(--illus-muted);
}

.illus-cta-back svg { width: 0.875rem; height: 0.875rem; }

/* ── Card inner staggered reveal ─────────────────────────────────────────── */
.illus-tag,
.illus-h1,
.illus-h2,
.illus-piece-title,
.illus-body,
.illus-technique,
.illus-stat-row,
.illus-meta-row,
.illus-ig-link,
.illus-cta,
.illus-cta-back {
    opacity: 0;
    translate: 0 0.625rem;
}

.illus-h1,
.illus-h2 {
    translate: 0 1.125rem;
    transition: opacity 0.5s ease 0.08s, translate 0.5s ease 0.08s;
}

.illus-tag          { transition: opacity 0.5s ease 0s,    translate 0.5s ease 0s; }
.illus-piece-title  { transition: opacity 0.5s ease 0.08s, translate 0.5s ease 0.08s; }
.illus-body         { transition: opacity 0.5s ease 0.2s,  translate 0.5s ease 0.2s; }
.illus-technique    { transition: opacity 0.5s ease 0.28s, translate 0.5s ease 0.28s; }
.illus-meta-row     { transition: opacity 0.5s ease 0.28s, translate 0.5s ease 0.28s; }
.illus-ig-link      { transition: opacity 0.5s ease 0.32s, translate 0.5s ease 0.32s; }
.illus-stat-row     { transition: opacity 0.5s ease 0.3s,  translate 0.5s ease 0.3s; }

.illus-cta,
.illus-cta-back {
    transition:
        opacity 0.5s ease 0.35s,
        translate 0.5s ease 0.35s,
        background 0.2s,
        color 0.2s,
        border-color 0.2s;
    /* glitch fires after the slide-up finishes (0.35s delay + 0.5s transition) */
    animation: illus-btn-glitch 0.55s steps(1, end) 1;
    animation-play-state: paused;
}

.illus-h-line {
    opacity: 0;
    scale: 0 1;
    transition: opacity 0.4s ease 0.04s, scale 0.4s ease 0.04s;
}

.illus-section.active .illus-tag,
.illus-section.active .illus-h1,
.illus-section.active .illus-h2,
.illus-section.active .illus-piece-title,
.illus-section.active .illus-body,
.illus-section.active .illus-technique,
.illus-section.active .illus-stat-row,
.illus-section.active .illus-meta-row,
.illus-section.active .illus-ig-link,
.illus-section.active .illus-cta,
.illus-section.active .illus-cta-back {
    opacity: 1;
    translate: 0 0;
}

.illus-section.active .illus-cta {
    animation-play-state: running;
    animation-delay: 0.9s;
}

.illus-section.active .illus-cta-back {
    animation-play-state: running;
    animation-delay: 1.05s;
}

.illus-section.active .illus-h-line {
    opacity: 1;
    scale: 1 1;
}

/* ── Button idle pulse — triggers alongside scroll hint after 3.5 s idle ──── */
@keyframes illus-cta-idle-pulse {
    0%, 100% { box-shadow: 0 0 0 0   rgba(238,155,  0, 0); }
    50%       { box-shadow: 0 0 0 3px rgba(238,155,  0, 0.30),
                            0 0 20px  rgba(238,155,  0, 0.40); }
}

@keyframes illus-cta-back-idle-pulse {
    0%, 100% { box-shadow: 0 0 0 0   rgba(148,210,189, 0); }
    50%       { box-shadow: 0 0 0 2px rgba(148,210,189, 0.22),
                            0 0 16px  rgba(148,210,189, 0.30); }
}


/* ── Responsive ───────────────────────────────────────────────────────────── */
@media (width <= 56.25em) {
    .illus-hud {
        top: 1rem;
        right: 1rem;
    }

    .illus-svg-bar {
        display: none;
    }

    .illus-strip {
        display: none;
    }

    .illus-info-svg {
        display: none;
    }

    .illus-caption {
        bottom: 1rem;
    }

    .illus-section {
        padding: 0 1.5rem 3.5rem;
        align-items: flex-end;
    }

    .illus-section[data-idx="0"] {
        align-items: center;
        padding: 4rem 1.5rem;
    }

    .illus-card,
    .illus-card--right {
        max-width: 100%;
        padding: 1.5rem 1.25rem;
    }

    .illus-body {
        line-height: 1.55;
    }

    .illus-stat-row { gap: 1.5rem; margin-block-start: 1.25rem; }
    .illus-cta-row  { margin-block-start: 1.25rem; }
}

/* ── Section intro animation ─────────────────────────────────────────────── */

/* ── Intro: suppress shell elements until the animation fires ───────────────
   Sections and card children start at opacity:0 by their own default CSS,
   so only the elements that are naturally visible need suppressing here.
──────────────────────────────────────────────────────────────────────────── */
#illustration.illus-intro-active #illus-particle-canvas,
#illustration.illus-intro-active .illus-scene,
#illustration.illus-intro-active .illus-hud,
#illustration.illus-intro-active .illus-strip,
#illustration.illus-intro-active .illus-caption,
#illustration.illus-intro-active .illus-info-svg,
#illustration.illus-intro-active .illus-sections,
#illustration.illus-intro-active .illus-svg-bar {
    opacity: 0;
    transition: none;
    animation: none;
}

/* ── Slide-in keyframes ─────────────────────────────────────────────────── */
@keyframes illus-enter-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
}

@keyframes illus-enter-scale {
    from { opacity: 0; scale: 0.88; }
    to   { opacity: 1; scale: 1; }
}

@keyframes illus-enter-left {
    from { opacity: 0; translate: -28px 0; }
    to   { opacity: 1; translate: 0 0; }
}

@keyframes illus-enter-up {
    from { opacity: 0; translate: 0 18px; }
    to   { opacity: 1; translate: 0 0; }
}

/* Same slide-up, but on `transform` instead of the `translate` property — for
   elements (.illus-strip, .illus-info-svg) whose left/right side-flip is driven
   by `translate: -100%`. Animating `translate` here would clobber that and shove
   the right-side variant off-screen; `transform` composes with it instead. */
@keyframes illus-enter-up-tf {
    from { opacity: 0; transform: translateY(18px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ── Staged scroll-driven entrance ─────────────────────────────────────────
   Driven by illus-cube.js from the section's own bottom-up viewport coverage
   over the photo curtain:
     • .illus-stage-content — text + SVG, latched at 40% coverage
     • .illus-stage-cube    — the 3D cube, latched at 90% coverage
   The .illus-intro-active suppressor above stays applied the whole time; these
   animations win on source order and (with fill:both) hold their end state, so
   no fragile class-removal timing is needed. animation-fill-mode: both → the
   element holds the from-state during the delay and the to-state afterwards.
──────────────────────────────────────────────────────────────────────────── */

/* Stage A — text + SVG slide into position (everything except the cube) */
#illustration.illus-stage-content #illus-particle-canvas {
    animation: illus-enter-fade 600ms ease 0ms both;
}

#illustration.illus-stage-content .illus-svg-bar {
    animation: illus-svgbar-enter 900ms var(--ease-expo-out, cubic-bezier(0.16,1,0.3,1)) 60ms both;
}

#illustration.illus-stage-content .illus-hud {
    animation: illus-enter-left 400ms ease 160ms both;
}

#illustration.illus-stage-content .illus-strip,
#illustration.illus-stage-content .illus-caption {
    animation: illus-enter-up-tf 380ms ease 300ms both;
}

#illustration.illus-stage-content .illus-info-svg {
    animation: illus-enter-up-tf 400ms ease 380ms both;
}

/* Intro card slides up as a single panel — its inner elements have already
   settled (invisibly) via their .active transitions while held at Stage 0. */
#illustration.illus-stage-content .illus-sections {
    animation: illus-enter-up 460ms var(--ease-expo-out, cubic-bezier(0.16,1,0.3,1)) 220ms both;
}

@keyframes illus-svgbar-enter {
    from { opacity: 0; translate: -100% 0; }
    to   { opacity: 1; translate: 0 0; }
}

/* Stage B — the 3D cube "powers on" (fires at 90% coverage).
   Dramatic but cheap: only transform + opacity on .illus-scene (the perspective
   wrapper), so it composites on the GPU with no layout/paint. Scaling the scene
   acts as a dolly toward the viewer because the perspective lives on it; the
   cube child keeps its JS-driven rotation untouched. Per-keyframe timing builds
   the rhythm — fast rush-in, back-out overshoot, two hard digital glitch hitches
   (steps), then a smooth settle. will-change is safe here: the cube subtree is
   already a live compositor layer (it rotates every frame). */
#illustration.illus-stage-cube .illus-scene {
    will-change: transform, opacity;
    animation: illus-cube-warp-in 1100ms linear 0ms both;
}

@keyframes illus-cube-warp-in {
    0% {
        opacity: 0;
        transform: translate3d(0, 0, 0) scale(0.16) rotate(-14deg);
        animation-timing-function: steps(1, end);                     /* hold dark a beat */
    }
    12% {
        opacity: 0.2;
        transform: translate3d(0, 0, 0) scale(0.16) rotate(-14deg);
        animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);     /* grow smoothly to size */
    }
    100% {
        opacity: 1;
        transform: translate3d(0, 0, 0) scale(1) rotate(0deg);
    }
}

/* ── Frozen exit — the entrance played backwards ───────────────────────────
   While scrolling the exit band (illus-cube.js adds .illus-exiting), the tunnel
   is pinned and the background stays put; the cube shrinks away first, then the
   text/SVG slide back out — the opposite direction to how they entered. Placed
   after the entrance rules so it wins the `animation` property; the strip and
   info-SVG use a transform-based slide so the left/right `translate` positioning
   is preserved (see illus-enter-up-tf). Particles + bg are untouched and remain.
──────────────────────────────────────────────────────────────────────────── */
#illustration.illus-exiting .illus-scene {
    animation: illus-cube-warp-out 600ms cubic-bezier(0.7, 0, 0.84, 0) 0ms both;
}
#illustration.illus-exiting .illus-svg-bar {
    animation: illus-svgbar-exit 560ms var(--ease-expo-out, cubic-bezier(0.16,1,0.3,1)) 240ms both;
}
#illustration.illus-exiting .illus-hud {
    animation: illus-exit-left 340ms ease-in 240ms both;
}
#illustration.illus-exiting .illus-strip,
#illustration.illus-exiting .illus-caption,
#illustration.illus-exiting .illus-info-svg {
    animation: illus-exit-up-tf 340ms ease-in 280ms both;
}
#illustration.illus-exiting .illus-sections {
    animation: illus-exit-up 340ms ease-in 280ms both;
}
/* "Click to expand" hint — translate-based so its translateX(-50%) centering is
   preserved (it uses `transform` for that). */
#illustration.illus-exiting .illus-expand-hint {
    animation: illus-exit-up 340ms ease-in 280ms both;
}
/* Floating title — rests at opacity 0.72 with its own --visible animation; the
   higher-specificity .illus-exiting rule overrides it. */
#illustration.illus-exiting .illus-title-float {
    animation: illus-exit-title 340ms ease-in 240ms both;
}

@keyframes illus-cube-warp-out {
    from { opacity: 1; transform: translate3d(0, 0, 0) scale(1) rotate(0deg); }
    to   { opacity: 0; transform: translate3d(0, 0, 0) scale(0.16) rotate(-14deg); }
}
@keyframes illus-svgbar-exit {
    from { opacity: 1; translate: 0 0; }
    to   { opacity: 0; translate: -100% 0; }
}
@keyframes illus-exit-left {
    from { opacity: 1; translate: 0 0; }
    to   { opacity: 0; translate: -28px 0; }
}
@keyframes illus-exit-up-tf {
    from { opacity: 1; transform: translateY(0); }
    to   { opacity: 0; transform: translateY(18px); }
}
@keyframes illus-exit-up {
    from { opacity: 1; translate: 0 0; }
    to   { opacity: 0; translate: 0 18px; }
}
@keyframes illus-exit-title {
    from { opacity: 0.72; transform: none; }
    to   { opacity: 0; transform: translateY(18px); }
}

/* Reduced motion — no slide/scale; everything simply fades in at its stage. */
@media (prefers-reduced-motion: reduce) {
    #illustration.illus-stage-content #illus-particle-canvas,
    #illustration.illus-stage-content .illus-svg-bar,
    #illustration.illus-stage-content .illus-hud,
    #illustration.illus-stage-content .illus-strip,
    #illustration.illus-stage-content .illus-caption,
    #illustration.illus-stage-content .illus-info-svg,
    #illustration.illus-stage-content .illus-sections,
    #illustration.illus-stage-cube .illus-scene {
        animation: illus-enter-fade 220ms ease both;
    }

    #illustration.illus-exiting .illus-svg-bar,
    #illustration.illus-exiting .illus-hud,
    #illustration.illus-exiting .illus-strip,
    #illustration.illus-exiting .illus-caption,
    #illustration.illus-exiting .illus-info-svg,
    #illustration.illus-exiting .illus-sections,
    #illustration.illus-exiting .illus-expand-hint,
    #illustration.illus-exiting .illus-title-float,
    #illustration.illus-exiting .illus-scene {
        animation: illus-exit-fade 220ms ease both;
    }
}

@keyframes illus-exit-fade {
    from { opacity: 1; }
    to   { opacity: 0; }
}


/* ── Illustration how-to info panel (vertical SVG) ──────────────────────── */
.illus-info-svg {
    position: absolute;
    top: calc(50% + 4rem); /* lowered to clear the longer title list above */
    left: 3.5rem;
    width: 16.5rem;
    height: auto;
    translate: 0 0;
    display: block;
    pointer-events: none;
    user-select: none;
    z-index: 4;
    opacity: 0;
    transition: left  0.45s var(--ease-expo-out, cubic-bezier(0.16,1,0.3,1)),
                translate 0.45s var(--ease-expo-out, cubic-bezier(0.16,1,0.3,1));
}
.illus-info-svg--right {
    left: calc(100% - 2rem);
    translate: -100% 0;
}

/* Internal elements — hidden until animated in */
#ilsv-vline,
#ilsv-hdr-group,
#ilsv-row-01,
#ilsv-row-02,
#ilsv-row-03,
#ilsv-row-04,
#ilsv-footer { opacity: 0; }

/* Reveal: triggered once by JS adding .illus-info-revealed to .illus-tunnel */
.illus-tunnel.illus-info-revealed .illus-info-svg {
    animation: ilsv-frame-enter 0.7s var(--ease-expo-out, cubic-bezier(0.16,1,0.3,1)) 0.3s forwards;
}

/* Dashed line — draws top→bottom, then pulses like a signal */
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-vline {
    animation:
        ilsv-line-draw  1.4s ease-out   1.5s forwards,
        ilsv-line-pulse 13s  ease-in-out 2.9s infinite;
}

/* Header + rows + footer — wipe in left→right, then scan */
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-hdr-group {
    animation:
        ilsv-hatch-wipe 0.5s ease-out   1.6s forwards,
        ilsv-hatch-scan 17s  ease-in-out 2.1s infinite;
}
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-01 {
    animation:
        ilsv-hatch-wipe 0.5s ease-out   1.8s forwards,
        ilsv-hatch-scan 17s  ease-in-out 2.3s infinite;
}
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-02 {
    animation:
        ilsv-hatch-wipe 0.5s ease-out   2.0s forwards,
        ilsv-hatch-scan 17s  ease-in-out 2.5s infinite;
}
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-03 {
    animation:
        ilsv-hatch-wipe 0.5s ease-out   2.2s forwards,
        ilsv-hatch-scan 17s  ease-in-out 2.7s infinite;
}
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-04 {
    animation:
        ilsv-hatch-wipe 0.5s ease-out   2.4s forwards,
        ilsv-hatch-scan 17s  ease-in-out 2.9s infinite;
}

/* Footer — wipe in then scan */
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-footer {
    animation:
        ilsv-hatch-wipe 0.5s ease-out   2.6s forwards,
        ilsv-hatch-scan 17s  ease-in-out 3.1s infinite;
}

/* ── Keyframes ── */

@keyframes ilsv-frame-enter {
    0%   { opacity: 0;   clip-path: inset(0 100% 0 0); }
    14%  { opacity: 0.5; clip-path: inset(0 62% 0 0); }
    16%  { opacity: 0.2; clip-path: inset(12% 60% 8% 0); }
    55%  { opacity: 0.9; clip-path: inset(0 10% 0 0); }
    100% { opacity: 1;   clip-path: inset(0 0% 0 0); }
}

@keyframes ilsv-line-draw {
    0%   { opacity: 1; clip-path: inset(0 0 100% 0); }
    100% { opacity: 1; clip-path: inset(0 0 0%   0); }
}
@keyframes ilsv-line-pulse {
    0%,100% { opacity: 1; }
    18%     { opacity: 0.3; }
    22%     { opacity: 1; }
    55%     { opacity: 0.6; }
    58%     { opacity: 0.1; }
    62%     { opacity: 0.9; }
}

@keyframes ilsv-hatch-wipe {
    0%   { opacity: 1; clip-path: inset(0 0 0 100%); }
    100% { opacity: 1; clip-path: inset(0 0 0 0%  ); }
}
@keyframes ilsv-hatch-scan {
    0%,100% { opacity: 1; }
    30%     { opacity: 0.4; }
    35%     { opacity: 1; }
    70%     { opacity: 0.6; }
    73%     { opacity: 0.15; }
    77%     { opacity: 1; }
}

/* ── Row highlight cycle — sequential scanner effect ──────────────────────
   Each row has the same 8s period but staggered 2s apart, creating a
   continuous top→bottom sweep after the panel finishes its reveal (~3.5s).
   Background rect uses var(--illus-lust-accent) so it shifts with the cube.
   Text flips to #001219 (dark) for contrast against the bright rect fill.
─────────────────────────────────────────────────────────────────────────── */

/* Row highlight is HOVER-ONLY (was an automatic staggered 8s cycle). Hovering a
   line lights its accent background + flips its text — one line at a time.
   Keyframes below (ilsv-row-*-cycle) are kept for reference but no longer run. */
.illus-info-svg .ilsv-hl-rect { transition: fill-opacity 0.25s ease; }
.illus-info-svg .ilsv-act,
.illus-info-svg .ilsv-num,
.illus-info-svg .ilsv-sub { transition: fill 0.25s ease, fill-opacity 0.25s ease; }

/* Background rect → bright accent (orange/red per piece) */
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-01:hover .ilsv-hl-rect,
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-02:hover .ilsv-hl-rect,
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-03:hover .ilsv-hl-rect,
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-04:hover .ilsv-hl-rect {
    fill-opacity: 0.88;
}

/* Action text + step number → light, for contrast against the bright rect */
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-01:hover .ilsv-act,
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-02:hover .ilsv-act,
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-03:hover .ilsv-act,
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-04:hover .ilsv-act,
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-01:hover .ilsv-num,
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-02:hover .ilsv-num,
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-03:hover .ilsv-num,
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-04:hover .ilsv-num {
    fill: #E9D8A6;
}

/* Sub-text → light + more opaque */
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-01:hover .ilsv-sub,
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-02:hover .ilsv-sub,
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-03:hover .ilsv-sub,
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-04:hover .ilsv-sub {
    fill: #E9D8A6;
    fill-opacity: 0.85;
}

/* ── Keyframes ── */

@keyframes ilsv-row-bg-cycle {
    0%    { fill-opacity: 0; }
    4%    { fill-opacity: 0.88; }
    21%   { fill-opacity: 0.88; }
    25%   { fill-opacity: 0; }
    100%  { fill-opacity: 0; }
}

@keyframes ilsv-row-act-cycle {
    0%    { fill: #EE9B00; }
    4%    { fill: #E9D8A6; }
    21%   { fill: #E9D8A6; }
    25%   { fill: #EE9B00; }
    100%  { fill: #EE9B00; }
}

@keyframes ilsv-row-num-cycle {
    0%    { fill: var(--illus-lust-accent, #EE9B00); }
    4%    { fill: #E9D8A6; }
    21%   { fill: #E9D8A6; }
    25%   { fill: var(--illus-lust-accent, #EE9B00); }
    100%  { fill: var(--illus-lust-accent, #EE9B00); }
}

@keyframes ilsv-row-sub-cycle {
    0%    { fill: #EE9B00; fill-opacity: 0.5; }
    4%    { fill: #E9D8A6; fill-opacity: 0.85; }
    21%   { fill: #E9D8A6; fill-opacity: 0.85; }
    25%   { fill: #EE9B00; fill-opacity: 0.5; }
    100%  { fill: #EE9B00; fill-opacity: 0.5; }
}


/* ===== art-direction-panel.css ===== */
/* ── Art Direction — Works List ─────────────────────────────────────────────
   Right-zone project list. z:5 — sits below scanlines/vhs/vignette (z:6-8)
   so the section's VHS texture and vignette drape over it naturally.
   Palette: amber #EE9B00 · teal #0A9396 · cream #E9D8A6 · bg #001219
   ─────────────────────────────────────────────────────────────────────────── */

/* ── Intro text — shown on first load, dismissed on discipline select ── */
#art-direction .ad-intro-text {
  position: absolute;
  left: calc(min(75vh, 75vw) + 2vw + 80px);
  width: min(calc(100% - min(75vh, 75vw) - 4vw - 80px), 480px);
  top: calc(50% - 150px);
  transform: translateY(-50%);
  z-index: 5;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease;
  display: flex;
  flex-direction: column;
  gap: 22px;
  padding: 18px 20px;
}

#art-direction .ad-projects-bar {
  position: absolute;
  right: calc(100% + 12px);
  top: 50%;
  transform: translateY(-50%);
  width: 20px;
  height: auto;
  pointer-events: none;
  display: block;
}

/* Visible only after category list has finished scrambling in */
#art-direction.ad-intro-active.ad-intro-animate .ad-intro-text {
  opacity: 1;
  pointer-events: auto;
  animation: ad-intro-entrance 0.7s var(--ease-expo-out) forwards;
}

/* Works zone suppressed while intro is active */
#art-direction.ad-intro-active .ad-works-zone {
  opacity: 0 !important;
  pointer-events: none;
}

@keyframes ad-intro-entrance {
  0%   { opacity: 0; transform: translateY(calc(-50% + 18px)); clip-path: inset(0 100% 0 0); }
  12%  { opacity: 0.4; transform: translateY(calc(-50% + 16px)); clip-path: inset(0 72% 0 0); }
  14%  { opacity: 0.2; transform: translateY(calc(-50% + 17px)) skewX(-2deg); clip-path: inset(20% 68% 15% 0); }
  16%  { opacity: 0.5; transform: translateY(calc(-50% + 15px)); clip-path: inset(0 68% 0 0); }
  55%  { opacity: 0.85; transform: translateY(calc(-50% + 4px)); clip-path: inset(0 12% 0 0); }
  58%  { opacity: 0.7; transform: translateY(calc(-50% + 5px)) skewX(1.5deg); clip-path: inset(25% 8% 5% 0); }
  60%  { opacity: 0.88; transform: translateY(calc(-50% + 3px)); clip-path: inset(0 6% 0 0); }
  85%  { opacity: 0.97; transform: translateY(calc(-50% + 1px)); clip-path: inset(0 0 0 0); }
  100% { opacity: 1;    transform: translateY(-50%);             clip-path: inset(0 0 0 0); }
}

/* Frame wrapper owns the absolute box geometry; the SVG fills it and an HTML
   label can be overlaid at the chevron arrow without SVG-text distortion. */
#art-direction .ad-intro-frame {
  position: absolute;
  left: calc(min(75vh, 75vw) + 2vw);
  right: 0;
  top: 50%;
  transform: translateY(calc(-50% - 150px));
  height: 56vh;
  pointer-events: none;
  z-index: 9;
}

#art-direction .ad-intro-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  filter: drop-shadow(0 0 6px #BB3E03) drop-shadow(0 0 18px rgba(155, 34, 38, 0.7));
  opacity: 0;
  clip-path: inset(0 100% 0 0);
}

/* HUD prompt overlaying the chevron arrow — sits where the arrow's dashed dots
   used to be (viewBox 674 / 950.9 ≈ 70.9% across, ~72% down). Crisp HTML text
   instead of SVG <text> (the frame stretches via preserveAspectRatio="none").
   Matches the red frame + glows; fades in with the chevrons, intro state only. */
#art-direction .ad-intro-arrow-label {
  position: absolute;
  left: 66.7%;
  top: 72%;
  transform: translate(-50%, -50%);
  margin: 0;
  white-space: nowrap;
  font-family: 'Funnel Display', sans-serif;
  font-weight: 700;
  font-size: clamp(11px, 0.95vw, 15px);
  letter-spacing: 0.12em;
  color: #CA6702;
  text-shadow: 0 0 6px rgba(202, 103, 2, 0.6), 0 0 16px rgba(202, 103, 2, 0.5);
  pointer-events: none;
  opacity: 0;
}

body.ad-section-live #art-direction.ad-intro-active .ad-intro-arrow-label {
  animation: adfrm-arrow-in 0.6s ease-out 1.2s forwards;
}

body.ad-section-live #art-direction .ad-intro-svg {
  animation: ad-frame-enter 0.7s var(--ease-expo-out) 0s forwards;
}

@keyframes ad-frame-enter {
  0%   { opacity: 0;   clip-path: inset(0 100% 0 0); }
  14%  { opacity: 0.5; clip-path: inset(0 62% 0 0); }
  16%  { opacity: 0.2; clip-path: inset(12% 60% 8% 0); }
  55%  { opacity: 0.9; clip-path: inset(0 10% 0 0); }
  100% { opacity: 1;   clip-path: inset(0 0% 0 0); }
}

/* ── SVG internal elements — hidden until SVG frame finishes entering (~0.7s) ── */
#art-direction .ad-intro-svg #animate_3 { opacity: 0; }
#art-direction .ad-intro-svg #animate_2 { opacity: 0; }
#art-direction .ad-intro-svg #animate1  { opacity: 0; }

/* animate_3 — dashed vertical line: draws top→bottom, then pulses like a signal */
body.ad-section-live #art-direction .ad-intro-svg #animate_3 {
  animation:
    adfrm-line-draw  1.4s ease-out  0.8s forwards,
    adfrm-line-pulse 13s ease-in-out 2.2s infinite;
}
@keyframes adfrm-line-draw {
  0%   { opacity: 1; clip-path: inset(0 0 100% 0); }
  100% { opacity: 1; clip-path: inset(0 0 0% 0); }
}
@keyframes adfrm-line-pulse {
  0%,100% { opacity: 1; }
  18%     { opacity: 0.3; }
  22%     { opacity: 1; }
  55%     { opacity: 0.6; }
  58%     { opacity: 0.1; }
  62%     { opacity: 0.9; }
}

/* animate_2 — diagonal hatching: wipes in, then scans like a radar sweep */
body.ad-section-live #art-direction .ad-intro-svg #animate_2 {
  animation:
    adfrm-hatch-wipe 0.5s ease-out   0.9s forwards,
    adfrm-hatch-scan 17s ease-in-out 1.4s infinite;
}
@keyframes adfrm-hatch-wipe {
  0%   { opacity: 1; clip-path: inset(0 0 0 100%); }
  100% { opacity: 1; clip-path: inset(0 0 0 0%); }
}
@keyframes adfrm-hatch-scan {
  0%,100% { opacity: 1; }
  30%     { opacity: 0.4; }
  35%     { opacity: 1; }
  70%     { opacity: 0.6; }
  73%     { opacity: 0.15; }
  77%     { opacity: 1; }
}

/* animate1 — binary text: flickers on, then loops like live data streaming */
body.ad-section-live #art-direction .ad-intro-svg #animate1 {
  animation:
    adfrm-binary-flicker 1s  ease-out  1.0s forwards,
    adfrm-binary-stream  11s ease-in-out 2.0s infinite;
}
@keyframes adfrm-binary-flicker {
  0%   { opacity: 0; }
  15%  { opacity: 0.9; }
  20%  { opacity: 0.05; }
  35%  { opacity: 1; }
  40%  { opacity: 0.1; }
  55%  { opacity: 1; }
  60%  { opacity: 0.4; }
  80%  { opacity: 1; }
  100% { opacity: 1; }
}
/* adfrm-arrow — HUD-style down indicator pointing at the DISCIPLINES nav card.
   Visible only while the intro is active (ad-intro-active drops on first
   discipline selection); enters with the other frame internals (~1.2s), then
   the three chevrons pulse top-to-bottom — a data-stream "descend" cue. */
#art-direction .ad-intro-svg #adfrm-arrow {
  opacity: 0;
  transition: opacity 0.3s ease;
}

/* Arrow recoloured (#CA6702) independently of the red frame (#BB3E03) — these
   id-scoped rules outrank the SVG's inline .adfrm-2/.adfrm-6 base styles. */
#art-direction .ad-intro-svg #adfrm-arrow .adfrm-2 { stroke: #CA6702; }
#art-direction .ad-intro-svg #adfrm-arrow .adfrm-6 { fill: #CA6702; }

body.ad-section-live #art-direction.ad-intro-active .ad-intro-svg #adfrm-arrow {
  animation: adfrm-arrow-in 0.6s ease-out 1.2s forwards;
}

@keyframes adfrm-arrow-in {
  0%   { opacity: 0; }
  35%  { opacity: 0.9; }
  45%  { opacity: 0.1; }
  60%  { opacity: 1; }
  70%  { opacity: 0.4; }
  100% { opacity: 1; }
}

#art-direction .ad-intro-svg .adfrm-chev {
  opacity: 0.15;
}

body.ad-section-live #art-direction.ad-intro-active .ad-intro-svg .adfrm-chev-0 {
  animation: adfrm-chev-seq 1.4s linear 1.80s infinite;
}
body.ad-section-live #art-direction.ad-intro-active .ad-intro-svg .adfrm-chev-1 {
  animation: adfrm-chev-seq 1.4s linear 1.98s infinite;
}
body.ad-section-live #art-direction.ad-intro-active .ad-intro-svg .adfrm-chev-2 {
  animation: adfrm-chev-seq 1.4s linear 2.16s infinite;
}

@keyframes adfrm-chev-seq {
  0%   { opacity: 0.15; }
  16%  { opacity: 1; }
  42%  { opacity: 1; }
  46%  { opacity: 0.3; }
  50%  { opacity: 0.9; }
  68%  { opacity: 0.15; }
  100% { opacity: 0.15; }
}

@media (prefers-reduced-motion: reduce) {
  body.ad-section-live #art-direction.ad-intro-active .ad-intro-svg #adfrm-arrow {
    animation: none;
    opacity: 1;
  }
  #art-direction .ad-intro-svg .adfrm-chev {
    opacity: 0.8;
    animation: none !important;
  }
  body.ad-section-live #art-direction.ad-intro-active .ad-intro-arrow-label {
    animation: none;
    opacity: 1;
  }
}

/* ── DNA capsule (reused from #about) — band right of the intro copy ──────
   Recolored to the frame red (#BB3E03) with the frame's glow. The ring text
   rides the GLOBAL .texts/.text/.text span 3D geometry from styles.css
   (whose animation gates are #about-scoped), so this block re-creates the
   gates under body.ad-section-live. adk-* classes dodge styles.css's global
   .capsule-* opacity:0 staging. Anchored right so it clears the intro text
   (narrowed to 480px below) and sits above the arrow label (top: 72%). */
#art-direction .ad-dna {
  position: absolute;
  right: 4.5%;
  top: 14%;
  height: 55%;
  aspect-ratio: 207.55 / 410.66;
  pointer-events: none;
}

#art-direction .ad-dna-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 2;
  transform: scale(0);
  transform-origin: bottom left;
  filter: drop-shadow(0 0 6px #BB3E03) drop-shadow(0 0 18px rgba(155, 34, 38, 0.7));
}

#art-direction .ad-dna-svg .adk-s {
  fill: none;
  stroke: #BB3E03;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 2px;
}
#art-direction .ad-dna-svg .adk-dash1 { stroke-dasharray: 1.02 5.1 1.02 5.1; }
#art-direction .ad-dna-svg .adk-dash2 { stroke-dasharray: 1.02 5.1 1.02 5.1 1.02 5.1; }
#art-direction .ad-dna-svg .adk-f { fill: #BB3E03; }
#art-direction .ad-dna-svg .adk-t {
  fill: #BB3E03;
  font-family: 'Funnel Display', sans-serif;
  font-size: 5.66px;
  font-weight: 700;
}

/* Enters after the frame wipe (0.7s) — same capsuleGrow as #about. */
body.ad-section-live #art-direction.ad-intro-active .ad-dna-svg {
  animation: capsuleGrow 1.2s ease-out 0.9s forwards;
}

#art-direction .ad-dna .scene {
  position: absolute;
  inset: 0;
  perspective: 600px;
  transform-style: preserve-3d;
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1;
}

/* Capsule height is 55% of the 56vh frame → font tracks it like #about's
   3.5vmin does its fixed 400px capsule (≈ height / 12.7). */
#art-direction .ad-dna .texts { font-size: 2.4vh; }

body.ad-section-live #art-direction.ad-intro-active .ad-dna .texts {
  animation: texts 12s infinite linear, ad-dna-fade 0.6s ease-out 1.6s forwards;
}
body.ad-section-live #art-direction.ad-intro-active .ad-dna .text {
  animation: text 8s calc(var(--text) * -2s) infinite linear;
}

@keyframes ad-dna-fade { to { opacity: 1; } }

/* Ring colors — section palette (cream + amber) over the global hsl() */
#art-direction .ad-dna .text[style*="--text: 0"] { color: #E9D8A6; }
#art-direction .ad-dna .text[style*="--text: 2"] { color: #EE9B00; }

@media (prefers-reduced-motion: reduce) {
  #art-direction .ad-dna-svg {
    animation: none !important;
    transform: none;
  }
  #art-direction .ad-dna .texts {
    animation: none !important;
    opacity: 1;
  }
  #art-direction .ad-dna .text { animation: none !important; }
  #art-direction .ad-dna .text span { opacity: 1; }
}

/* Tighter band on smaller desktops; below 1220px the works panel/nav are
   already hidden and the text reclaims the frame width — drop the capsule. */
@media (max-width: 1366px) {
  #art-direction .ad-intro-text {
    width: min(calc(100% - min(75vh, 75vw) - 4vw - 80px), 440px);
  }
  #art-direction .ad-dna {
    height: 48%;
    top: 16%;
    right: 3.5%;
  }
  #art-direction .ad-dna .texts { font-size: 2.1vh; }
}

@media (max-width: 1219px) {
  #art-direction .ad-dna { display: none; }
}

/* ── Section copy — about-style Splitting glitch entrance ─────────────────
   (owner 2026-07-03: same effect and tempo as #about's paragraphs.)
   Hosts: .iad-body (fired with the intro reveal, art-direction.js) and
   .ad-pm-desc (fired on each modal open / re-render, art-direction-panel.js
   _glitchCopy). The ::after char-cycle comes from the global
   .glitch-suppressed.glitch-firing rule in styles.css; this block owns the
   tempo, the glyph colors (--glitch-final-color defaults to white) and the
   progressive wave: chars pop in exactly when their glitch slot arrives.
   Hiding the [data-char] SPAN (not the ::after) also hides the ::after's
   `backwards` from-state garbage glyph. Failure-safe: plain opacity:1 rides
   the firing state, and fill-mode backwards still lands content:var(--txt)
   on a collapsed/instant (Reduce Motion) run. */
#art-direction .iad-body,
#art-direction .ad-pm-desc {
  --glitch-stagger: 0.004s;
  --glitch-count: 4;
}
#art-direction .iad-body    { --glitch-final-color: #E9D8A6; }
#art-direction .ad-pm-desc  { --glitch-final-color: rgba(233, 216, 166, 0.78); }
#art-direction .iad-body [data-char],
#art-direction .ad-pm-desc [data-char] {
  opacity: 0;
}
#art-direction .iad-body.glitch-firing [data-char],
#art-direction .ad-pm-desc.glitch-firing [data-char] {
  opacity: 1;
  animation: ad-glitch-char-in 0.01s linear
    calc(var(--char-index) * var(--glitch-stagger, 0.05s)) backwards;
}
@keyframes ad-glitch-char-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes adfrm-binary-stream {
  0%,100% { opacity: 1; }
  8%      { opacity: 0.5; }
  10%     { opacity: 1; }
  11%     { opacity: 0.05; }
  13%     { opacity: 0.9; }
  40%     { opacity: 0.7; }
  42%     { opacity: 0.1; }
  44%     { opacity: 1; }
  78%     { opacity: 0.6; }
  80%     { opacity: 0.15; }
  82%     { opacity: 1; }
}

/* ── Skills bar entrance ── */
#art-direction .ad-skills-bar {
  opacity: 0;
  transform: translateY(-100px);
}

body.ad-section-live #art-direction .ad-skills-bar {
  animation: slideDownFade 0.5s ease-out 0.1s forwards,
             ad-skills-shimmer 2.5s ease-in-out 0.6s infinite;
}

@keyframes ad-skills-shimmer {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0.7; }
}

/* ── Type styles — text groups always visible inside their container ── */
#art-direction .ad-intro-text .iad-header,
#art-direction .ad-intro-text .iad-body {
  margin: 0;
}

#art-direction .ad-intro-text .iad-header {
  font-family: 'Funnel Display', sans-serif;
  font-size: clamp(13px, 1.3vw, 18px);
  color: #EE9B00;
  letter-spacing: 0.1em;
  padding-bottom: 14px;
  border-bottom: 1px solid rgba(238, 155, 0, 0.35);
}

#art-direction .ad-intro-text .iad-body {
  font-family: 'Funnel Display', sans-serif;
  font-weight: 300;
  font-size: clamp(15px, 1.7vw, 22px);
  color: #E9D8A6;
  line-height: 1.55;
}

/* ── LinkedIn icon — inline below body text, in flex flow ── */
#art-direction .ad-social-icons {
  position: static;
  opacity: 1;
  transform: none;
  pointer-events: auto;
  padding: 0;
  margin: 0;
}

#art-direction .ad-social-icons li a {
  color: #EE9B00;
}


/* ── Works zone — wrapper that positions label + panel together ── */
#art-direction .ad-works-zone {
  position: absolute;
  left: calc(min(75vh, 75vw) + 2vw + 50px);
  top: calc(50% - 100px);
  transform: translateY(-50%);
  width: min(calc(100% - min(75vh, 75vw) - 4vw), 780px);
  max-height: clamp(380px, 82vh, 780px);
  z-index: 5;
  display: flex;
  align-items: flex-start;
  gap: 16px;
}


/* ── Works list panel ── */
#art-direction .ad-works-panel {
  flex: 1;
  min-width: 0;
  max-height: clamp(380px, 82vh, 780px);
  display: flex;
  flex-direction: column;
  opacity: 0;
  transition: opacity 0.4s ease;
  transform: scale(0.9);
  transform-origin: top left;
}

#art-direction .ad-works-panel.ad-works-ready {
  opacity: 1;
}

/* ── Header row ── */
#art-direction .ad-works-header {
  display: flex;
  align-items: center;
  gap: 9px;
  height: 30px;
  flex-shrink: 0;
  margin-bottom: 16px;
}

#art-direction .ad-works-pulse {
  width: 6px;
  height: 6px;
  background: #EE9B00;
  border-radius: 50%;
  flex-shrink: 0;
  animation: ad-works-pulse 2.4s ease-in-out infinite;
}

@keyframes ad-works-pulse {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0.35; }
}

#art-direction .ad-works-label {
  color: #EE9B00;
  font-family: 'Funnel Display', sans-serif;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.18em;
}

#art-direction .ad-works-disc-name {
  margin-left: auto;
  color: rgba(233, 216, 166, 0.35);
  font-family: 'Funnel Display', sans-serif;
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  min-width: 60px;
  text-align: right;
}

/* ── Column label row ── */
#art-direction .ad-works-cols {
  display: grid;
  grid-template-columns: 32px 1.3fr 1.1fr 1fr 64px;
  column-gap: 1.2rem;
  padding-bottom: 10px;
  border-bottom: 1px solid rgba(10, 147, 150, 0.3);
  flex-shrink: 0;
  margin-bottom: 2px;
}

#art-direction .ad-works-col-label {
  font-family: 'Funnel Display', sans-serif;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.6);
  white-space: nowrap;
}

#art-direction .ad-col-right {
  text-align: right;
}

/* ── Scrollable table container ── */
#art-direction .ad-works-table {
  flex: 1;
  overflow-y: auto;
  scrollbar-width: none;
  counter-reset: ad-work-counter 0;
  transition: opacity 0.15s ease-out, transform 0.15s ease-out;
}

#art-direction .ad-works-table::-webkit-scrollbar {
  display: none;
}

/* Leaving state — fires before DOM swap on discipline change */
#art-direction .ad-works-table.is-leaving {
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.15s ease-in, transform 0.15s ease-in;
}

/* ── Empty discipline state — shown while a category has no works yet ── */
#art-direction .ad-works-empty {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 28px 0;
  border-bottom: 1px solid rgba(10, 147, 150, 0.1);
}

#art-direction .ad-works-empty-text {
  font-family: 'Funnel Display', sans-serif;
  font-size: 12px;
  letter-spacing: 0.08em;
  color: rgba(233, 216, 166, 0.4);
  animation: ad-works-pulse 2.4s ease-in-out infinite;
}

/* ── DISCIPLINES nav card — bottom-right zone, below project list, next to N.
   Inline SVG; the category rows are the section's navigation (old left-rail
   .ad-list removed). Container stays pointer-events:none so only the painted
   .adnav-cat rows take clicks — everything around the card stays click-through. ── */
#art-direction .ad-explore-card {
  position: absolute;
  /* Widened (smaller left offset) so the SVG is no longer width-constrained and
     its text can render larger; grows downward (smaller bottom buffer + taller)
     into the empty viewport bottom, keeping the top edge ~fixed so it never
     pushes up into the red intro frame or the works list above it. */
  left: calc(min(75vh, 75vw) + 2vw + 300px);
  right: clamp(12px, 2vw, 24px);
  bottom: calc(clamp(16px, 3vh, 32px) + 24px);
  height: min(31.5vh, 285px);
  z-index: 5;
  pointer-events: none;
  opacity: 0;
  overflow: hidden;
  clip-path: inset(0 0 100% 0);
}

body.ad-section-live #art-direction .ad-explore-card {
  animation: ad-explore-enter 0.7s var(--ease-expo-out) forwards;
}

@keyframes ad-explore-enter {
  0%   { opacity: 0;   clip-path: inset(0 0 100% 0); transform: translateY(8px); }
  14%  { opacity: 0.5; clip-path: inset(0 0 55% 0);  transform: translateY(6px); }
  16%  { opacity: 0.2; clip-path: inset(5% 0 52% 0);  transform: translateY(7px); }
  60%  { opacity: 0.9; clip-path: inset(0 0 8% 0);   transform: translateY(2px); }
  100% { opacity: 1;   clip-path: inset(0 0 0% 0);   transform: translateY(0); }
}

#art-direction .ad-explore-card .ad-nav-svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* ── Nav rows ── */
.ad-nav-svg .adnav-cat {
  pointer-events: auto;
  cursor: pointer;
  outline: none;
}

.ad-nav-svg .adnav-hl {
  opacity: 0;
}

/* Attract cycle — 5 slots × 3s; runs only until the first discipline is chosen
   (.has-active set by ArtWorksPanel), then the highlight becomes pure state. */
.ad-nav-svg:not(.has-active) .adnav-hl-0 { animation: adnav-cycle 15s ease-in-out 0s  infinite; }
.ad-nav-svg:not(.has-active) .adnav-hl-1 { animation: adnav-cycle 15s ease-in-out 3s  infinite; }
.ad-nav-svg:not(.has-active) .adnav-hl-2 { animation: adnav-cycle 15s ease-in-out 6s  infinite; }
.ad-nav-svg:not(.has-active) .adnav-hl-3 { animation: adnav-cycle 15s ease-in-out 9s  infinite; }
.ad-nav-svg:not(.has-active) .adnav-hl-4 { animation: adnav-cycle 15s ease-in-out 12s infinite; }

.ad-nav-svg:not(.has-active) .adnav-block-0 text { animation: adnav-txt 15s ease-in-out 0s  infinite; }
.ad-nav-svg:not(.has-active) .adnav-block-1 text { animation: adnav-txt 15s ease-in-out 3s  infinite; }
.ad-nav-svg:not(.has-active) .adnav-block-2 text { animation: adnav-txt 15s ease-in-out 6s  infinite; }
.ad-nav-svg:not(.has-active) .adnav-block-3 text { animation: adnav-txt 15s ease-in-out 9s  infinite; }
.ad-nav-svg:not(.has-active) .adnav-block-4 text { animation: adnav-txt 15s ease-in-out 12s infinite; }

@keyframes adnav-cycle {
  0%   { opacity: 0; }
  4%   { opacity: 1; }
  16%  { opacity: 1; }
  20%  { opacity: 0; }
  100% { opacity: 0; }
}

@keyframes adnav-txt {
  0%   { fill: #EE9B00; }
  4%   { fill: #001219; }
  16%  { fill: #001219; }
  20%  { fill: #EE9B00; }
  100% { fill: #EE9B00; }
}

/* Hover / keyboard focus — solid amber band so the row reads as selected,
   dark text on top for contrast; cycle suppressed */
.ad-nav-svg .adnav-cat:hover .adnav-hl,
.ad-nav-svg .adnav-cat:focus-visible .adnav-hl {
  animation: none;
  opacity: 1;
}

.ad-nav-svg .adnav-cat:hover text,
.ad-nav-svg .adnav-cat:focus-visible text {
  animation: none;
  fill: #001219;
}

.ad-nav-svg .adnav-cat:focus-visible .adnav-hl {
  stroke: #EE9B00;
  stroke-width: 1.5;
}

/* Active discipline — solid amber band, dark text (after hover/focus so it
   wins when the active row is hovered) */
.ad-nav-svg .adnav-cat.is-active .adnav-hl {
  animation: none;
  opacity: 1;
}

.ad-nav-svg .adnav-cat.is-active text {
  animation: none;
  fill: #001219;
}

@media (prefers-reduced-motion: reduce) {
  .ad-nav-svg .adnav-hl,
  .ad-nav-svg .adnav-block text {
    animation: none !important;
  }
}

/* ── Individual project row ── */
#art-direction .ad-work-item {
  display: grid;
  grid-template-columns: 32px 1.3fr 1.1fr 1fr 64px;
  column-gap: 1.2rem;
  align-items: center;
  padding: 15px 0;
  border-bottom: 1px solid rgba(10, 147, 150, 0.1);
  cursor: pointer;
  position: relative;
  counter-increment: ad-work-counter;
  transition: border-bottom-color 0.2s ease;
}

/* ── Row entrance animation — toggled by _animateRowsIn() ── */
#art-direction .ad-work-item.ad-row-entering {
  opacity: 0;
  transform: translateY(10px);
  transition: none;
}

#art-direction .ad-work-item.ad-row-visible {
  opacity: 1;
  transform: translateY(0);
  transition:
    opacity   var(--duration-quick) var(--ease-expo-out),
    transform var(--duration-quick) var(--ease-expo-out);
  transition-delay: calc(20ms + var(--row-index, 0) * 60ms);
}

#art-direction .ad-work-item:hover {
  border-bottom-color: rgba(238, 155, 0, 0.3);
}

/* Row number via CSS counter */
#art-direction .ad-work-item::before {
  content: counter(ad-work-counter, decimal-leading-zero);
  font-family: 'Funnel Display', sans-serif;
  font-size: 12px;
  font-weight: 300;
  color: rgba(233, 216, 166, 0.28);
  line-height: 1;
  transition: color 0.15s ease;
  position: relative;
  z-index: 1;
}

#art-direction .ad-work-item:hover::before {
  color: rgba(0, 18, 25, 0.45);
}

/* ── Data cells (shared) ── */
#art-direction .ad-work-data {
  font-family: 'Funnel Display', sans-serif;
  font-size: 12px;
  font-weight: 300;
  color: rgba(233, 216, 166, 0.65);
  letter-spacing: 0.04em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  position: relative;
  z-index: 1;
  line-height: 1;
  transition: color 0.15s ease;
  padding: 2px 0;
}

/* Yellow sweep per cell — same pattern as photo accordion */
#art-direction .ad-work-data::after {
  content: '';
  position: absolute;
  top: 0;
  left: -6px;
  width: calc(100% + 12px);
  height: 100%;
  background: rgba(238, 155, 0, 0.92);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.25s ease;
  z-index: -1;
}

#art-direction .ad-work-item:hover .ad-work-data::after {
  transform: scaleX(1);
}

#art-direction .ad-work-item:hover .ad-work-data {
  color: rgb(10, 10, 10);
}

/* ── Title — primary cell, heavier weight ── */
#art-direction .ad-work-title {
  font-weight: 600;
  font-size: 14px;
  color: #E9D8A6;
  letter-spacing: 0.02em;
}

/* ── Year — right-aligned, dimmed ── */
#art-direction .ad-work-year {
  text-align: right;
  color: rgba(233, 216, 166, 0.35);
  letter-spacing: 0.06em;
}

/* ── Hide works panel + nav card below 1220px — grid leaves no usable right
   space; with the panel gone the nav card would point at nothing ── */
@media (max-width: 1219px) {
  #art-direction .ad-works-panel,
  #art-direction .ad-explore-card {
    display: none;
  }
}

/* ── Mobile ≤ 900px — recentre all right-rail overlays ─────────────────── */
@media (max-width: 900px) {
  /* Intro text: centre over the letter grid */
  #art-direction .ad-intro-text {
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: min(440px, 88vw);
  }

  /* Works zone: centre; panel + label are hidden at 1219px so zone is invisible here */
  #art-direction .ad-works-zone {
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: min(600px, 92vw);
  }

}



/* ══════════════════════════════════════════════════════════════════════════════
   PROJECT DETAIL MODAL
   Fixed overlay, position: fixed escapes section overflow:hidden.
   z:1000 — above all section layers, below photo stream lightbox (z:9000).
   ══════════════════════════════════════════════════════════════════════════════ */

.ad-project-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.22s ease;
}

.ad-project-modal.is-open {
  pointer-events: auto;
  opacity: 1;
}

/* Semi-transparent dark backdrop */
.ad-pm-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 18, 25, 0.88);
  cursor: pointer;
}

/* Inner frame — inset from viewport edges, clips the entrance animation */
.ad-pm-frame {
  position: absolute;
  inset: clamp(24px, 6vh, 72px) clamp(24px, 6vw, 96px);
  overflow: hidden;
  clip-path: inset(50% 0);
  transition: clip-path 0.48s cubic-bezier(0.16, 1, 0.3, 1);
}

.ad-project-modal.is-open .ad-pm-frame {
  clip-path: inset(0);
}

/* Background photo — full bleed */
.ad-pm-bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  filter: grayscale(0.25);
  transform: scale(1.04);
  transition: transform 0.55s ease, filter 0.55s ease;
}

.ad-project-modal.is-open .ad-pm-bg {
  transform: scale(1);
  filter: grayscale(0);
}

/* Bottom-heavy dark gradient — same as old active slide */
.ad-pm-gradient {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(0, 18, 25, 0.08) 0%,
    rgba(0, 18, 25, 0.55) 50%,
    rgba(0, 18, 25, 0.95) 100%
  );
  pointer-events: none;
}

/* Scanlines texture — reuses section's scanlines pattern */
.ad-pm-scanlines {
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(
    0deg,
    transparent,
    transparent 2px,
    rgba(0, 0, 0, 0.09) 2px,
    rgba(0, 0, 0, 0.09) 4px
  );
  pointer-events: none;
  z-index: 1;
}

/* ── Decorative cyber frame — inlined red variant of the intro frame.
   z:3 — in front of the content text and thumbnail gallery (z:2), below the
   close button (z:4). pointer-events:none lets clicks pass through to both. ── */
.ad-pm-deco {
  position: absolute;
  top: 100px;
  right: 100px;
  width: 65%;
  height: 65%;
  z-index: 3;
  pointer-events: none;
  filter: drop-shadow(0 0 6px rgba(155, 34, 38, 0.6));
  opacity: 0;
  transition: opacity 0.4s ease 0.3s;
}

.ad-project-modal.is-open .ad-pm-deco {
  opacity: 1;
}

/* Internal elements hidden until the frame has faded in.
   Keyframes (adfrm-*) are shared with the intro frame, defined above in this file. */
.ad-pm-deco #adpmf-line,
.ad-pm-deco #adpmf-hatch,
.ad-pm-deco #adpmf-binary {
  opacity: 0;
}

.ad-project-modal.is-open .ad-pm-deco #adpmf-line {
  animation:
    adfrm-line-draw  1.2s ease-out     0.9s forwards,
    adfrm-line-pulse 13s  ease-in-out  2.2s infinite;
}

.ad-project-modal.is-open .ad-pm-deco #adpmf-hatch {
  animation:
    adfrm-hatch-wipe 0.5s ease-out     1.0s forwards,
    adfrm-hatch-scan 17s  ease-in-out  1.5s infinite;
}

.ad-project-modal.is-open .ad-pm-deco #adpmf-binary {
  animation:
    adfrm-binary-flicker 1s   ease-out    1.1s forwards,
    adfrm-binary-stream  11s  ease-in-out 2.1s infinite;
}

/* ── Image stage — active gallery image, uncropped, inside the deco frame.
   Geometry mirrors .ad-pm-deco; padding keeps the image within the drawn
   panel. z:4 — above the frame so artwork shows true-colour, untinted by
   the panel's red wash. ── */
.ad-pm-stage {
  position: absolute;
  top: 100px;
  right: 100px;
  width: 65%;
  height: 65%;
  z-index: 4;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4% 5%;
  pointer-events: none;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.4s ease 0.45s, transform 0.4s ease 0.45s;
}

.ad-project-modal.is-open .ad-pm-stage {
  opacity: 1;
  transform: translateY(0);
}

.ad-pm-stage-img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  filter: drop-shadow(0 10px 32px rgba(0, 0, 0, 0.55));
}

/* Hide the stage entirely until JS sets a source */
.ad-pm-stage-img:not([src]),
.ad-pm-stage-img[src=""] {
  display: none;
}

/* ── Missing-image void — cyberpunk black hole ───────────────────────────────
   Shown when a stage image 404s (JS adds .has-void to .ad-pm-stage). The broken
   <img> is hidden and this animated placeholder takes the surface. ── */
.ad-pm-void {
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(28px, 5vh, 54px);
  width: 100%;
  height: 100%;
  user-select: none;
}
.ad-pm-stage.has-void {
  pointer-events: none;
}
.ad-pm-stage.has-void .ad-pm-stage-img {
  display: none;
}
.ad-pm-stage.has-void .ad-pm-void {
  display: flex;
}

.ad-pm-void-hole {
  position: relative;
  width: clamp(120px, 15vw, 200px);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
}

/* Accretion disks — two conic rings spinning opposite ways, masked to a ring. */
.ad-pm-void-disk {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: conic-gradient(
    from 0deg,
    rgba(238, 155, 0, 0)   0%,
    rgba(238, 155, 0, 0.9) 12%,
    rgba(0, 213, 223, 0.7) 30%,
    rgba(238, 155, 0, 0)   46%,
    rgba(214, 40, 57, 0.7) 66%,
    rgba(238, 155, 0, 0.9) 84%,
    rgba(238, 155, 0, 0)   100%
  );
  -webkit-mask: radial-gradient(closest-side, transparent 55%, #000 60%, #000 92%, transparent 100%);
          mask: radial-gradient(closest-side, transparent 55%, #000 60%, #000 92%, transparent 100%);
  filter: blur(0.4px) saturate(1.2);
  animation: ad-void-spin 4.5s linear infinite;
}
.ad-pm-void-disk--b {
  inset: 12%;
  filter: blur(1.4px) saturate(1.3);
  opacity: 0.75;
  animation-duration: 7s;
  animation-direction: reverse;
}

/* Event horizon — black core with a razor amber rim + inward glow. */
.ad-pm-void-core {
  position: relative;
  width: 46%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, #000 58%, rgba(238, 155, 0, 0.18) 82%, rgba(238, 155, 0, 0) 100%);
  box-shadow:
    0 0 0 1.5px rgba(238, 155, 0, 0.85),
    0 0 22px 4px rgba(238, 155, 0, 0.45),
    inset 0 0 24px 6px rgba(0, 0, 0, 0.95);
  animation: ad-void-pulse 3.4s ease-in-out infinite;
}

/* Gravitational-lensing flicker sweep across the whole hole. */
.ad-pm-void-lens {
  position: absolute;
  inset: -10%;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, rgba(0, 213, 223, 0.12), transparent 62%);
  mix-blend-mode: screen;
  animation: ad-void-flicker 5.5s steps(1, end) infinite;
}

.ad-pm-void-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  text-align: center;
  max-width: 88%;
}
.ad-pm-void-glitch {
  font-family: 'Funnel Display', sans-serif;
  font-weight: 400;
  font-size: clamp(0.72rem, 1.5vw, 1.02rem);
  letter-spacing: 2.5px;
  text-transform: uppercase;
  color: #EE9B00;
  line-height: 1.5;
  text-shadow: -1px 0 rgba(0, 213, 223, 0.9), 1px 0 rgba(214, 40, 57, 0.9);
  animation: ad-void-glitch 2.6s steps(1, end) infinite;
}
.ad-pm-void-code {
  font-family: 'Funnel Display', sans-serif;
  font-size: clamp(0.58rem, 1vw, 0.72rem);
  letter-spacing: 3px;
  text-transform: uppercase;
  color: rgba(238, 155, 0, 0.42);
  animation: ad-void-blink 1.4s steps(1, end) infinite;
}

@keyframes ad-void-spin { to { transform: rotate(360deg); } }
@keyframes ad-void-pulse {
  0%, 100% { box-shadow: 0 0 0 1.5px rgba(238,155,0,0.85), 0 0 22px 4px rgba(238,155,0,0.45), inset 0 0 24px 6px rgba(0,0,0,0.95); }
  50%      { box-shadow: 0 0 0 1.5px rgba(238,155,0,1),    0 0 34px 7px rgba(238,155,0,0.6),  inset 0 0 20px 5px rgba(0,0,0,0.9); }
}
@keyframes ad-void-flicker {
  0%, 100% { opacity: 0.5; }
  8%       { opacity: 0.9; }
  9%       { opacity: 0.2; }
  40%      { opacity: 0.7; }
  41%      { opacity: 0.3; }
}
@keyframes ad-void-glitch {
  0%, 100% { text-shadow: -1px 0 rgba(0,213,223,0.9), 1px 0 rgba(214,40,57,0.9); transform: translateX(0); }
  46%      { text-shadow: -1px 0 rgba(0,213,223,0.9), 1px 0 rgba(214,40,57,0.9); transform: translateX(0); }
  48%      { text-shadow: -2.5px 0 rgba(0,213,223,0.95), 2.5px 0 rgba(214,40,57,0.95); transform: translateX(1.5px); }
  50%      { text-shadow: 2px 0 rgba(0,213,223,0.95), -2px 0 rgba(214,40,57,0.95); transform: translateX(-1.5px); }
  52%      { text-shadow: -1px 0 rgba(0,213,223,0.9), 1px 0 rgba(214,40,57,0.9); transform: translateX(0); }
}
@keyframes ad-void-blink { 0%, 60% { opacity: 0.42; } 70%, 100% { opacity: 0.12; } }

/* Failure-safe: static void for reduced-motion — hole + text stay legible. */
@media (prefers-reduced-motion: reduce) {
  .ad-pm-void-disk,
  .ad-pm-void-disk--b,
  .ad-pm-void-core,
  .ad-pm-void-lens,
  .ad-pm-void-glitch,
  .ad-pm-void-code {
    animation: none;
  }
  .ad-pm-void-glitch { text-shadow: -1px 0 rgba(0,213,223,0.7), 1px 0 rgba(214,40,57,0.7); }
}

/* ── 3D model stage — interactive <model-viewer> fills the stage panel.
   The stage is pointer-events:none for images; 3D needs orbit/zoom gestures. ── */
.ad-pm-stage.has-model {
  pointer-events: auto;
}

.ad-pm-model {
  width: 100%;
  height: 100%;
  background: transparent;
  --poster-color: transparent;
  --progress-bar-color: #EE9B00;
  --progress-bar-height: 2px;
  /* Hidden until the GLB is ready — JS adds .is-loaded after the red deco
     frame has drawn, so the model always enters on top of the frame. */
  opacity: 0;
  transform: translateY(14px) scale(0.97);
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.ad-pm-model.is-loaded {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .ad-pm-model {
    transform: none;
    transition: opacity 0.6s ease;
  }
}

/* ── White media surface — projects flagged whiteBg in WORKS_DATA.
   Transparent artwork (logos, stationery) reads against white instead of
   vanishing into the dark backdrop. ── */
.ad-project-modal.is-white-media .ad-pm-stage-img {
  background: #fff;
  padding: clamp(10px, 1.5vw, 22px);
}

.ad-project-modal.is-white-media .ad-pm-thumb {
  background-color: #fff;
}

.ad-row-preview.is-white {
  background-color: #fff;
}

/* ── Content block — bottom-left ── */
.ad-pm-content {
  position: absolute;
  bottom: 48px;
  left: 52px;
  right: 52px;
  z-index: 2;
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.42s ease 0.38s, transform 0.42s ease 0.38s;
  display: flex;
  align-items: flex-end;
  gap: 40px;
}

/* Text block — left side. The image/SVG stage occupies the right 65% of the
   frame (anchored right:100px), so its left edge lands near the 35% mark. The
   text column is pinned to the open left third and capped so the larger type
   always clears the red SVG horizontally — it wraps/grows downward, never under
   the stage. */
.ad-pm-text {
  position: relative;
  flex: 0 0 auto;
  width: min(40ch, 30%);
  max-width: 440px;
  margin-right: auto;
}

.ad-project-modal.is-open .ad-pm-content {
  opacity: 1;
  transform: translateY(0);
}

/* ── Glitch entrance on the left text block — mirrors the section's intro-text
   reveal (.ad-intro-text / ad-intro-entrance): a left→right clip-path wipe with
   skew + jitter, layered under the per-char scramble. fill-mode:both hides it
   during the delay and holds the open state; on close the rule drops so the
   block just fades out with .ad-pm-content. ── */
.ad-project-modal.is-open .ad-pm-text {
  animation: ad-pm-text-glitch-in 0.72s var(--ease-expo-out) 0.4s both;
}

@keyframes ad-pm-text-glitch-in {
  0%   { clip-path: inset(0 100% 0 0);       transform: translateY(8px); }
  12%  { clip-path: inset(0 72% 0 0);        transform: translateY(7px); }
  14%  { clip-path: inset(20% 68% 15% 0);    transform: translateY(7px) skewX(-2deg); }
  16%  { clip-path: inset(0 68% 0 0);        transform: translateY(6px); }
  55%  { clip-path: inset(0 12% 0 0);        transform: translateY(2px); }
  58%  { clip-path: inset(25% 8% 5% 0);      transform: translateY(2px) skewX(1.5deg); }
  60%  { clip-path: inset(0 6% 0 0);         transform: translateY(1px); }
  85%  { clip-path: inset(0 0 0 0);          transform: translateY(0); }
  100% { clip-path: inset(0 0 0 0);          transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .ad-project-modal.is-open .ad-pm-text {
    animation: none;
  }
}

/* Large faint project number — top-right of text block */
.ad-pm-num {
  position: absolute;
  bottom: calc(100% + 8px);
  right: 0;
  font-family: 'Funnel Display', sans-serif;
  font-size: clamp(56px, 8vw, 96px);
  font-weight: 300;
  color: rgba(233, 216, 166, 0.14);
  line-height: 1;
  pointer-events: none;
  user-select: none;
}

/* ── Glitch scramble — category / title / subtitle ───────────────────────────
   _glitchSplit() (art-direction-panel.js) runs Splitting.js on these on each
   open, stamping the [data-char] glyph vars. The global [data-char]:after rules
   (styles.css) supply duration/delay/timing; here we switch the animation on and
   recolour each char to its element's hue (the global default is white). ── */
.ad-pm-cat[data-splitting] [data-char]:after,
.ad-pm-title[data-splitting] [data-char]:after,
.ad-pm-sub[data-splitting] [data-char]:after {
  animation-name: glitch-switch;
  animation-iteration-count: calc(var(--char-index) * 0.55);
}
.ad-pm-cat[data-splitting]   [data-char]:after { color: #EE9B00; }
.ad-pm-title[data-splitting] [data-char]:after { color: var(--ad-pm-title-color, #E9D8A6); }
.ad-pm-sub[data-splitting]   [data-char]:after { color: rgba(233, 216, 166, 0.55); }

@media (prefers-reduced-motion: reduce) {
  .ad-pm-cat[data-splitting] [data-char]:after,
  .ad-pm-title[data-splitting] [data-char]:after,
  .ad-pm-sub[data-splitting] [data-char]:after {
    animation-name: none;
  }
}

/* Category chip */
.ad-pm-cat {
  display: block;
  font-family: 'Funnel Display', sans-serif;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.26em;
  color: #EE9B00;
  text-transform: uppercase;
  margin-bottom: 12px;
}

/* Project title */
.ad-pm-title {
  font-family: 'Funnel Display', sans-serif;
  font-size: clamp(32px, 4vw, 52px);
  font-weight: 700;
  /* Rotated per open by _nextTitleColor() (art-direction-panel.js) — the
     base color matters for selection highlights/fallbacks; the visible
     glyphs are the [data-char]::after copies above, same var. */
  color: var(--ad-pm-title-color, #E9D8A6);
  line-height: 1.1;
  margin: 0 0 8px 0;
}

/* Subtitle */
.ad-pm-sub {
  font-family: 'Funnel Display', sans-serif;
  font-size: 18px;
  font-weight: 300;
  color: rgba(233, 216, 166, 0.55);
  margin: 0 0 14px 0;
  letter-spacing: 0.04em;
}

/* Project description — short paragraph with inline links, between subtitle and specs */
.ad-pm-desc {
  font-family: 'Funnel Display', sans-serif;
  font-size: 18px;
  font-weight: 300;
  line-height: 1.6;
  color: rgba(233, 216, 166, 0.78);
  margin: 0 0 22px 0;
  letter-spacing: 0.02em;
}

.ad-pm-desc a {
  color: #EE9B00;
  text-decoration: underline;
  text-decoration-color: rgba(238, 155, 0, 0.45);
  text-underline-offset: 3px;
  transition: color 0.2s ease, text-decoration-color 0.2s ease;
}

.ad-pm-desc a:hover {
  color: #E9D8A6;
  text-decoration-color: #E9D8A6;
}

.ad-pm-desc a:focus-visible {
  outline: 1px solid #EE9B00;
  outline-offset: 2px;
}

/* Specs grid */
.ad-pm-specs {
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin-bottom: 20px;
  border-top: 1px solid rgba(10, 147, 150, 0.28);
  padding-top: 16px;
}

.ad-pm-spec-row {
  display: flex;
  gap: 1.5rem;
  font-family: 'Funnel Display', sans-serif;
  font-size: 15px;
  align-items: baseline;
}

.ad-pm-spec-key {
  color: rgba(233, 216, 166, 0.45);
  font-weight: 400;
  letter-spacing: 0.06em;
  min-width: 54px;
  flex-shrink: 0;
}

.ad-pm-spec-val {
  color: #E9D8A6;
  font-weight: 600;
  letter-spacing: 0.03em;
}

/* Tags */
.ad-pm-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.ad-pm-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  background: rgba(238, 155, 0, 0.08);
  border: 1px solid rgba(238, 155, 0, 0.22);
  border-radius: 10px;
  font-family: 'Funnel Display', sans-serif;
  font-size: 14px;
  font-weight: 500;
  color: #E9D8A6;
  letter-spacing: 0.05em;
}

.ad-pm-dot {
  width: 5px;
  height: 5px;
  background: #EE9B00;
  border-radius: 50%;
  flex-shrink: 0;
}

/* ── Thumbnail strip — right side of content block ── */
.ad-pm-thumbs {
  flex: 1;
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 8px;
  padding-bottom: 2px;
  min-width: 0;
}

.ad-pm-thumbs:empty {
  display: none;
}

.ad-pm-thumb {
  width: 80px;
  height: 56px;
  flex-shrink: 0;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  background-color: rgba(0, 18, 25, 0.8);
  cursor: pointer;
  opacity: 0.45;
  border: 1px solid rgba(238, 155, 0, 0.22);
  border-radius: 2px;
  transition: opacity 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
  list-style: none;
}

.ad-pm-thumb:hover {
  opacity: 0.85;
  border-color: rgba(238, 155, 0, 0.6);
  transform: scale(1.06);
}

.ad-pm-thumb.is-active {
  opacity: 1;
  border-color: #EE9B00;
  box-shadow: 0 0 0 1px rgba(238, 155, 0, 0.35), 0 0 10px rgba(238, 155, 0, 0.3);
  transform: none;
}

/* ── Close button — top-right of frame ── */
.ad-pm-close {
  position: absolute;
  top: 20px;
  right: 24px;
  background: rgba(0, 18, 25, 0.55);
  border: 1px solid rgba(238, 155, 0, 0.45);
  color: #EE9B00;
  font-family: 'Funnel Display', sans-serif;
  font-size: 20px;
  font-weight: 400;
  letter-spacing: 0.18em;
  cursor: pointer;
  padding: 10px 18px;
  z-index: 5; /* above the decorative frame (z:3) and image stage (z:4) */
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity 0.3s ease 0.55s, background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.ad-project-modal.is-open .ad-pm-close {
  opacity: 1;
  animation: ad-pm-close-blink 2.4s ease-in-out 0.85s infinite;
}

@keyframes ad-pm-close-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.3; }
}

.ad-pm-close:hover {
  background: rgba(238, 155, 0, 0.12);
  border-color: #EE9B00;
  color: #E9D8A6;
}

/* ── Floating row preview — cursor-following thumbnail ────────────────────── */
.ad-row-preview {
  position: fixed;
  left: 0;
  top: 0;
  width: 200px;
  height: 260px;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  background-color: #001219;
  opacity: 0;
  z-index: 1050;
  pointer-events: none;
  will-change: transform, opacity;
  border-radius: 2px;
  transition: opacity 0.2s ease;
  box-shadow:
    0 0 0 1px rgba(238, 155, 0, 0.45),
    0 8px 32px rgba(0, 0, 0, 0.55);
}

.ad-row-preview-border {
  position: absolute;
  inset: -5px;
  border: 2px solid #EE9B00;
  box-shadow:
    0 0  8px rgba(238, 155,   0, 0.85),
    0 0 24px rgba(238, 155,   0, 0.40),
    0 0 52px rgba(10,  147, 150, 0.18);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s ease;
  border-radius: 2px;
}

.ad-row-preview.is-visible .ad-row-preview-border {
  opacity: 1;
  transition: opacity 0s;
}

/* ── Flipbook — editorial catalog page-turn (js/ad-flipbook.js) ───────────────
   A CSS-3D book mounted into the project-modal stage for multi-page editorial
   works. Leaves rotate around a central spine; turned leaves pile left, unturned
   right. Geometry (--leaf-w/--leaf-h) is sized to the page aspect ratio by JS.
   Palette: amber #EE9B00 · cream #E9D8A6 · bg #001219
   ─────────────────────────────────────────────────────────────────────────── */

/* The image stage is normally pointer-events:none — the book needs clicks. */
.ad-pm-stage.has-book { pointer-events: auto; }
.ad-project-modal.is-flipbook .ad-pm-stage-img { display: none; }

.ad-book {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  perspective: 2600px;
  perspective-origin: 50% 44%;
  cursor: pointer;
  user-select: none;
}

.ad-book-stack {
  position: relative;
  width: calc(var(--leaf-w, 38%) * 2);   /* spread = two pages wide */
  height: var(--leaf-h, 78%);
  transform-style: preserve-3d;
  /* Page-silhouette shadow — cheap, follows the closed/open outline. */
  filter: drop-shadow(0 16px 34px rgba(0, 0, 0, 0.55));
}
.ad-book--single .ad-book-stack { width: var(--leaf-w, 60%); }

/* ── Leaves ── */
.ad-book-leaf {
  position: absolute;
  top: 0;
  height: 100%;
  transform-origin: left center;       /* the spine */
  transform-style: preserve-3d;
  transition: transform 0.86s cubic-bezier(0.22, 1, 0.36, 1);
}
.ad-book--spread .ad-book-leaf { left: 50%; width: 50%; }  /* right half */
.ad-book--single .ad-book-leaf { left: 0;   width: 100%; }
.ad-book-leaf.is-flipped { transform: rotateY(-180deg); }

.ad-book-face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  background-color: #0b1a22;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  box-shadow: inset 0 0 0 1px rgba(233, 216, 166, 0.10);
  overflow: hidden;
}
.ad-book-face--back { transform: rotateY(180deg); }
.ad-book-face.is-blank { background-color: #07141b; }

/* Binding shadow along the spine edge (each leaf's left edge). */
.ad-book--spread .ad-book-face::before {
  content: "";
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 15%;
  background: linear-gradient(90deg, rgba(0, 0, 0, 0.30), transparent);
  pointer-events: none;
}

/* Subtle dark core at the gutter for depth (behind the pages). */
.ad-book-gutter {
  position: absolute;
  top: 2%; bottom: 2%; left: 50%;
  width: 26px;
  transform: translateX(-50%);
  background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.5), transparent 72%);
  z-index: 0;
  pointer-events: none;
}
.ad-book--single .ad-book-gutter { display: none; }

/* ── Sheen — light sweeping across the turning page ── */
.ad-book-gloss {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(105deg,
    rgba(255, 255, 255, 0) 32%,
    rgba(255, 255, 255, 0.22) 50%,
    rgba(255, 255, 255, 0) 68%);
}
.ad-book-leaf.is-turning .ad-book-face--front .ad-book-gloss {
  animation: ad-gloss 0.86s ease forwards;
}
.ad-book-leaf.is-turning .ad-book-face--back .ad-book-gloss {
  animation: ad-gloss-soft 0.86s ease forwards;
}
@keyframes ad-gloss      { 0% { opacity: 0; } 50% { opacity: 0.85; } 100% { opacity: 0; } }
@keyframes ad-gloss-soft { 0% { opacity: 0; } 50% { opacity: 0.45; } 100% { opacity: 0; } }

/* ── Corner-peel affordance — only the top right-hand page ── */
.ad-book-corner {
  position: absolute;
  right: 0; bottom: 0;
  width: 46px; height: 46px;
  pointer-events: none;
  opacity: 0;
  clip-path: polygon(100% 48%, 100% 100%, 48% 100%);
  background:
    linear-gradient(315deg, rgba(0, 0, 0, 0.35), rgba(0, 0, 0, 0) 55%),
    linear-gradient(135deg, transparent 46%, #E9D8A6 47%, #EE9B00 100%);
  transition: width 0.28s ease, height 0.28s ease, opacity 0.28s ease;
}
.ad-book-leaf.is-top .ad-book-corner { opacity: 0.92; }
.ad-book-leaf.is-top:hover .ad-book-corner { width: 70px; height: 70px; opacity: 1; }

/* ── Controls — cyberpunk HUD layer ──────────────────────────────────────────
   Full-stage overlay so the prev/next triggers flank the book (vertically
   centred) and the counter rides the top edge — clear of the bottom-left
   project content they used to overlap. Layer is click-through; only the
   controls themselves take pointer events. ─────────────────────────────────── */
.ad-book-controls {
  position: absolute;
  inset: 0;
  z-index: 60;
  pointer-events: none;
}

/* Styled to match the red-glow HUD arrow that points to the disciplines card
   (#adfrm-arrow in .ad-intro-svg): a #BB3E03 chevron framed by rounded [ ]
   brackets with a red drop-glow. Sits well over the modal's red deco frame. */
.ad-book-btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: auto;
  appearance: none;
  width: 64px; height: 58px;
  display: grid;
  place-items: center;
  cursor: pointer;
  color: #BB3E03;
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1;
  /* Faint dark halo only — keeps the red chevron legible over bright catalog
     pages without reading as a panel (the bracket frame stays the focus). */
  background: radial-gradient(ellipse at center, rgba(0, 12, 18, 0.55), rgba(0, 12, 18, 0) 72%);
  border: none;
  backdrop-filter: blur(1px);
  text-shadow: 0 0 6px rgba(187, 62, 3, 0.9), 0 0 16px rgba(155, 34, 38, 0.6);
  filter: drop-shadow(0 0 5px rgba(187, 62, 3, 0.55))
          drop-shadow(0 0 14px rgba(155, 34, 38, 0.45));
  transition: color 0.2s ease, filter 0.2s ease,
              transform 0.2s var(--ease-expo-out);
}
.ad-book-prev { left: 4px; }
.ad-book-next { right: 4px; }

/* [ … ] bracket frame — the round-jointed HUD brackets from the SVG arrow */
.ad-book-btn::before,
.ad-book-btn::after {
  content: "";
  position: absolute;
  top: 7px;
  bottom: 7px;
  width: 9px;
  border: 2px solid #BB3E03;
  border-radius: 2px;
}
.ad-book-btn::before { left: 4px;  border-right: none; }
.ad-book-btn::after  { right: 4px; border-left: none; }

/* triple-chevron cluster — [ ‹‹‹ ] / [ ››› ], tightened like the SVG arrow */
.ad-book-chevs {
  display: block;
  letter-spacing: -0.12em;
  padding: 0 0.08em;
}
.ad-book-chevs i {
  display: inline-block;
  font-style: normal;
  opacity: 0.3;
  animation: ad-book-chev-glow 1.6s ease-in-out infinite;
}

/* Sequenced glow — each chevron lights in turn, flowing toward the pointing
   direction (echoes the #adfrm-arrow chevron waterfall). */
.ad-book-next .ad-book-chevs i:nth-child(1) { animation-delay: 0s;    }
.ad-book-next .ad-book-chevs i:nth-child(2) { animation-delay: 0.18s; }
.ad-book-next .ad-book-chevs i:nth-child(3) { animation-delay: 0.36s; }
.ad-book-prev .ad-book-chevs i:nth-child(1) { animation-delay: 0.36s; }
.ad-book-prev .ad-book-chevs i:nth-child(2) { animation-delay: 0.18s; }
.ad-book-prev .ad-book-chevs i:nth-child(3) { animation-delay: 0s;    }

@keyframes ad-book-chev-glow {
  0%, 100% { opacity: 0.3; text-shadow: 0 0 4px rgba(187, 62, 3, 0.35); }
  18%      { opacity: 1;   text-shadow: 0 0 8px rgba(187, 62, 3, 0.95), 0 0 18px rgba(155, 34, 38, 0.7); }
  45%      { opacity: 0.3; text-shadow: 0 0 4px rgba(187, 62, 3, 0.35); }
}

/* ── nav-below variant — landscape catalogs (WORKS_DATA `navBelow`) ──────────
   Wide pages fill the stage, so the flanking arrows would sit on the artwork.
   Drop the whole control row under the book: arrows flank the counter in a
   centred bottom strip. */
.ad-pm-stage.nav-below .ad-book-btn {
  top: auto;
  bottom: -16px;
  transform: none;
}
.ad-pm-stage.nav-below .ad-book-prev { left: calc(50% - 150px); }
.ad-pm-stage.nav-below .ad-book-next { right: calc(50% - 150px); }
.ad-pm-stage.nav-below .ad-book-prev:hover:not(:disabled) { transform: translateX(-3px); }
.ad-pm-stage.nav-below .ad-book-next:hover:not(:disabled) { transform: translateX(3px); }
.ad-pm-stage.nav-below .ad-book-counter {
  top: auto;
  bottom: 0;
}

@media (prefers-reduced-motion: reduce) {
  .ad-book-chevs i { animation: none; opacity: 1; }
}

.ad-book-btn:hover:not(:disabled) {
  color: #d8531a;
  filter: drop-shadow(0 0 7px rgba(187, 62, 3, 0.85))
          drop-shadow(0 0 22px rgba(155, 34, 38, 0.6));
}
.ad-book-prev:hover:not(:disabled) { transform: translate(-3px, -50%); }
.ad-book-next:hover:not(:disabled) { transform: translate(3px, -50%); }
.ad-book-btn:focus-visible { outline: 2px solid #BB3E03; outline-offset: 3px; }
.ad-book-btn:disabled { opacity: 0.22; cursor: default; }

/* Counter — top-edge HUD readout */
.ad-book-counter {
  position: absolute;
  top: 6px;
  left: 50%;
  transform: translateX(-50%);
  padding: 4px 12px;
  font-family: 'Funnel Display', sans-serif;
  font-size: 0.7rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.18em;
  color: #EE9B00;
  background: rgba(0, 18, 25, 0.72);
  border: 1px solid rgba(238, 155, 0, 0.35);
  clip-path: polygon(8px 0, 100% 0, calc(100% - 8px) 100%, 0 100%);
  text-shadow: 0 0 8px rgba(238, 155, 0, 0.5);
  backdrop-filter: blur(2px);
  white-space: nowrap;
}
.ad-book-counter::before { content: "[ "; color: #0A9396; }
.ad-book-counter::after  { content: " ]"; color: #0A9396; }

@media (prefers-reduced-motion: reduce) {
  .ad-book-leaf { transition: none; }
  .ad-book-gloss { display: none; }
  .ad-book-corner { transition: opacity 0.2s ease; }
  .ad-book-btn { transition: color 0.2s ease, box-shadow 0.2s ease; }
}


/* ===== inf-bracket.css ===== */
/* ═══════════════════════════════════════════════════════════════════════════
   INFO BRACKET — contextual { indicator tied to info-interface hotspots
   Position: fixed, above header (z:1001). JS measures targets via
   getBoundingClientRect() and sets left/top/width at runtime.
   ═══════════════════════════════════════════════════════════════════════════ */

#inf-bracket {
  position: fixed;
  pointer-events: none;
  z-index: 1001;
  opacity: 0;
  transition: opacity 0.3s var(--ease-expo-out);
}

#inf-bracket.visible {
  opacity: 1;
}

/* Glow applied to the whole SVG so chevrons share it */
#inf-bracket-svg {
  filter:
    drop-shadow(0 0 4px rgba(238, 155, 0, 0.7))
    drop-shadow(0 0 10px rgba(238, 155, 0, 0.3));
  overflow: visible;
}

#inf-bracket-path {
  stroke: #EE9B00;
  stroke-width: 2.5;
  fill: none;
}

/* Chevrons at nub tip — same cascade animation as scroll hint */
.inf-bracket-chevron {
  stroke: #EE9B00;
  stroke-width: 2.5;
  fill: none;
  animation: inf-chev-fade 2s ease-in-out infinite;
}

.inf-bracket-chevron--2 {
  animation-delay: 0.28s;
}

@keyframes inf-chev-fade {
  0%, 25% { opacity: 0.1; }
  65%     { opacity: 1;   }
  100%    { opacity: 0.1; }
}

/* Label below chevrons — individual letter spans */
#inf-bracket-label {
  fill: #EE9B00;
}

/* Each letter hidden by default */
.ibl {
  opacity: 0;
}

/* Sequential reveal when bracket becomes visible */
#inf-bracket.visible .ibl {
  animation: ibl-pop 0.18s ease-out forwards;
  animation-delay: calc(var(--li) * 70ms);
}

@keyframes ibl-pop {
  0%   { opacity: 0; }
  100% { opacity: var(--lo); }
}

/* Lang switch: flicker the whole label group, then letters re-sequence */
#inf-bracket-label.lang-flicker .ibl {
  animation: ibl-flicker 0.45s ease forwards;
  animation-delay: calc(var(--li) * 70ms);
}

@keyframes ibl-flicker {
  0%   { opacity: var(--lo); }
  20%  { opacity: 0.05; }
  50%  { opacity: 0;    }
  70%  { opacity: 0.3;  }
  100% { opacity: var(--lo); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   LEADER LINE — glowing dotted connector tying an info-card line to its bracket.
   Full-viewport fixed overlay (no viewBox → 1 user unit = 1 CSS px = viewport
   px). JS rebuilds the cubic path on hotspot enter; sits one layer below the
   bracket so the { reads as the head of the line.
   ═══════════════════════════════════════════════════════════════════════════ */
#inf-leader {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 1000;
  overflow: visible;
}

#inf-leader-track,
#inf-leader-snake {
  fill: none;
  stroke: #EE9B00;
  opacity: 0;
  transition: opacity 0.3s var(--ease-expo-out);
}

/* Faint guide wire — the route the snake threads through the content */
#inf-leader-track {
  stroke-width: 1;
  stroke-linecap: butt;
  stroke-linejoin: miter;
}

/* The snake — a short chain of square LCD pixels, like the classic game.
   butt caps + miter joins + crispEdges → hard, un-antialiased blocks.
   The dash pattern (a 4-square body + a route-length gap) and the travel
   animation are set per-route in JS, since both depend on the measured
   path length so the body crawls exactly from A to the bracket. */
#inf-leader-snake {
  stroke-width: 5;
  stroke-linecap: butt;
  stroke-linejoin: miter;
  shape-rendering: crispEdges;     /* pixel-sharp squares, no blur */
  filter: drop-shadow(0 0 2px rgba(238, 155, 0, 0.45));   /* faint dot-matrix glow */
}

/* Pixel head — position/rotation/opacity driven per-frame in JS */
#inf-leader-head {
  opacity: 0;
  filter: drop-shadow(0 0 2px rgba(238, 155, 0, 0.45));
}

#inf-leader.visible #inf-leader-track { opacity: 0.15; }
#inf-leader.visible #inf-leader-snake { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  #inf-leader-track,
  #inf-leader-snake { transition: none; }
}


/* ===== illus-snake.css ===== */
/* ═══════════════════════════════════════════════════════════════════════════
   ILLUSTRATION SNAKE-LEADER — amber vintage-Snake connector that runs from each
   "how to explore" info-SVG row to the control it describes (HUD, Enter, strip,
   cube). Mirrors the intro snake (see js/inf-bracket.js + css/inf-bracket.css)
   but tuned to the illustration palette (#EE9B00) and gallery targets.
   Full-viewport fixed overlay (no viewBox → 1 user unit = 1 CSS px).
   ═══════════════════════════════════════════════════════════════════════════ */
#illus-snake {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 8000;          /* above gallery (≤22), below lightbox (9000) */
  overflow: visible;
}

#illus-snake-track,
#illus-snake-body {
  fill: none;
  stroke: #EE9B00;
  opacity: 0;
  transition: opacity 0.3s ease;
}

/* Faint guide wire — the route the snake threads to the control */
#illus-snake-track {
  stroke-width: 1;
  stroke-linecap: butt;
  stroke-linejoin: miter;
}

/* The snake body — a chain of square LCD pixels (dash pattern + travel set in JS) */
#illus-snake-body {
  stroke-width: 5;
  stroke-linecap: butt;
  stroke-linejoin: miter;
  shape-rendering: crispEdges;
  filter: drop-shadow(0 0 2px rgba(238, 155, 0, 0.45));
}

/* Pixel head — position/rotation/opacity driven per-frame in JS */
#illus-snake-head {
  opacity: 0;
  filter: drop-shadow(0 0 2px rgba(238, 155, 0, 0.45));
}

#illus-snake.visible #illus-snake-track { opacity: 0.15; }
#illus-snake.visible #illus-snake-body  { opacity: 1; }

/* The info SVG is pointer-events:none; re-enable just the how-to rows so each
   line (01–04) is its own hover element — but only once the panel is revealed. */
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-01,
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-02,
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-03,
.illus-tunnel.illus-info-revealed .illus-info-svg #ilsv-row-04 {
  pointer-events: all;
  cursor: pointer;
}
/* Per-line hover highlight (accent bg + text flip) lives in illus-cube.css,
   replacing the old automatic row-highlight cycle. */

/* Intro / stop-0 layout (default): only SCROLL (01) + ENTER (02). */
#illustration:not(.illus-gallery-mode) .illus-info-svg #ilsv-row-03,
#illustration:not(.illus-gallery-mode) .illus-info-svg #ilsv-row-04 {
  display: none;
}

/* Gallery layout (an image is showing): drop the ENTER line. Rows 03/04 are
   shifted up + renumbered by illus-snake.js. */
#illustration.illus-gallery-mode .illus-info-svg #ilsv-row-02 {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  #illus-snake-track,
  #illus-snake-body { transition: none; }
}

/* ── SCROLL cue ───────────────────────────────────────────────────────────────
   Hovering the "01 · SCROLL" row reveals the about-section scroll hint at the
   top of the gallery (bracketed [ ] + three pulsing down-chevrons) instead of a
   snake. Amber to read on the dark gallery; reuses the global aid-chev-seq pulse.
─────────────────────────────────────────────────────────────────────────────── */
.illus-scroll-cue {
  position: absolute;
  top: calc(50% + 2rem);                 /* top of the info panel (.illus-info-svg) */
  height: calc(16.5rem * 257 / 200);     /* span down to the panel's bottom edge */
  z-index: 25;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.35s ease;
  display: flex;
  flex-direction: column;                /* arrow at top, rotated label reaching bottom */
  align-items: center;
  justify-content: space-between;
}

/* Sit just outside the info panel, on whichever side it's docked. The panel is
   16.5rem wide with a 2rem screen margin (see .illus-info-svg). */
/* --right panel docks at right:2rem; --left panel docks at left:3.5rem */
.illus-tunnel:has(.illus-info-svg--right) .illus-scroll-cue { right: calc(2rem   + 16.5rem + 0.75rem); }
.illus-tunnel:has(.illus-info-svg--left)  .illus-scroll-cue { left:  calc(3.5rem + 16.5rem + 0.75rem); }

.illus-scroll-cue-arrow {
  display: block;
  width: clamp(54px, 5.5vw, 76px);
  height: auto;
  overflow: visible;
  filter: drop-shadow(0 0 4px rgba(238, 155, 0, 0.5))
          drop-shadow(0 0 11px rgba(238, 155, 0, 0.3));
}
.illus-scroll-cue-arrow .iscue-bracket {
  fill: none;
  stroke: #EE9B00;
  stroke-width: 3.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.illus-scroll-cue-arrow .iscue-chev { fill: #EE9B00; opacity: 0.15; }

/* Label below the arrow, rotated 90° → reads top-to-bottom, centred on the arrow */
.illus-scroll-cue-text {
  writing-mode: vertical-rl;
  text-orientation: mixed;
  transform: rotate(180deg);   /* flip so "[ · Scroll" sits at the bottom, reading up */
  white-space: nowrap;
  font-family: 'Funnel Display', sans-serif;
  font-weight: 700;
  font-size: clamp(0.82rem, 1.2vw, 1.05rem);
  letter-spacing: 0.2em;
  color: #EE9B00;
  text-shadow: 0 0 6px rgba(238, 155, 0, 0.5), 0 0 14px rgba(238, 155, 0, 0.3);
}

.illus-tunnel.illus-info-revealed:has(#ilsv-row-01:hover) .illus-scroll-cue { opacity: 1; }
.illus-tunnel.illus-info-revealed:has(#ilsv-row-01:hover) .illus-scroll-cue .iscue-chev-0 { animation: aid-chev-seq 1.4s linear 0s    infinite; }
.illus-tunnel.illus-info-revealed:has(#ilsv-row-01:hover) .illus-scroll-cue .iscue-chev-1 { animation: aid-chev-seq 1.4s linear 0.18s infinite; }
.illus-tunnel.illus-info-revealed:has(#ilsv-row-01:hover) .illus-scroll-cue .iscue-chev-2 { animation: aid-chev-seq 1.4s linear 0.36s infinite; }

@media (prefers-reduced-motion: reduce) {
  .illus-scroll-cue { transition: none; }
  .illus-scroll-cue .iscue-chev { opacity: 0.8; animation: none !important; }
}


/* ===== section-transition.css ===== */
/* ═══ SECTION TRANSITION — interstitial particle wipe (desktop only) ════════
   Full-viewport overlay that masks the instant scroll jump between sections.
   Reuses the ParticleSystem class — the same multicolor #001219 swarm as the
   #preloader — driven by js/section-transition.js and orchestrated from
   NavigationManager.goToSection.

   Fades are COMPOSITOR-DRIVEN: js sets `transition: opacity …` inline per phase,
   so the cover keeps gliding off the main thread even while the main thread is
   stalled doing the navigation reflow (the source of the old stutter). Fully
   inert (visibility:hidden, pointer-events:none) when no transition is running.
   ══════════════════════════════════════════════════════════════════════════ */

#section-transition {
  position: fixed;
  inset: 0;
  z-index: 9990;            /* below #preloader (9999), above all page content */
  background: #001219;      /* the dark the viewport turns into */
  opacity: 0;               /* CSS-transitioned 0→1 (cover) then 1→0 (reveal) */
  pointer-events: none;     /* never traps input; re-entry guarded in JS */
  visibility: hidden;       /* inert at rest — JS adds .is-running for a cycle */
  contain: layout paint;    /* isolate the per-frame canvas repaint from the page */
}

#section-transition.is-running {
  visibility: visible;
  will-change: opacity;     /* promote to its own compositor layer for the fade */
}

/* The ParticleSystem injects its <canvas> as firstChild (inlinePosition:true →
   position:absolute; inset:0). Its opacity is faded separately, lagging the
   #001219 base so the dark establishes first and the particles bloom a beat
   later ("luego a la par"). */
#section-transition canvas {
  opacity: 0;
}

#section-transition.is-running canvas {
  will-change: opacity;
}

/* ── Time-traveler figure (centre of the vortex) ──────────────────────────
   A 4-frame flipbook centred in the swarm's vortex. Each frame is an inlined
   SVG (solid-black art) stacked in the same box; JS toggles .is-active so one
   frame shows at a time, looping the walk cycle. The source art is recoloured
   to a luminous cyan-white; a single container-level drop-shadow gives the glow
   (one blur pass over the box, not per-path). The figure fades in only AFTER
   the dark cover + particle swarm establish (delay ≈ cover duration), then
   rides the overlay's opacity for the fade-out on reveal. */
#section-transition .st-traveler {
  position: absolute;
  top: 50%;
  left: 50%;
  height: clamp(150px, 24vh, 210px);
  aspect-ratio: 1.11;                /* ~ the frames' viewBox ratio (2490 / 2247) */
  opacity: 0;                        /* the delayed fade-in drives this while running */
  transform: translate(-50%, -50%);
  pointer-events: none;
  filter: drop-shadow(0 0 8px rgba(0, 213, 223, 0.65));
}

/* Wait out the cover (~460ms) so the bg + particles are visible first, then
   fade the figure to 50% and hold it (forwards) until the reveal fades it out. */
#section-transition.is-running .st-traveler {
  animation: stTravelerFade 240ms ease-out 460ms both;
}

@keyframes stTravelerFade {
  from { opacity: 0; }
  to   { opacity: 0.5; }
}

/* Stacked frames — only .is-active is visible (hard cut, no crossfade). */
#section-transition .st-traveler .st-frame {
  position: absolute;
  inset: 0;
  opacity: 0;
}
#section-transition .st-traveler .st-frame.is-active {
  opacity: 1;
}

#section-transition .st-traveler .st-frame svg {
  display: block;
  width: 100%;
  height: 100%;
}

#section-transition .st-traveler .st-frame svg path {
  fill: #c8eef0;
}

/* ── Traveling scan-line ──────────────────────────────────────────────────
   Horizontal "electric static" line (reuses js/static-line.js for the look)
   that sweeps from the bottom of the viewport up and out the top, ON TOP of
   the cover + particle swarm, once per transition. A SIBLING of the overlay
   (z-index 9991, not a child) so it stays crisp through the reveal fade
   instead of dimming with it. Travel + fade run as a compositor CSS animation
   (transform + opacity only) — smooth even while the main thread does the
   navigation reflow. The cyan glow is a CSS drop-shadow (makeStaticLine is
   called with shadow:false, so no per-frame canvas shadowBlur). */
#section-transition-scanline {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 4px;
  z-index: 9991;            /* above the cover/particles (9990), below #preloader (9999) */
  opacity: 0;               /* the keyframes fade it in at the bottom, out at the top */
  pointer-events: none;
  will-change: transform, opacity;
  transform: translate3d(0, 100vh, 0);   /* parked just below the viewport at rest */
  filter: drop-shadow(0 0 3px #0ef) drop-shadow(0 0 7px #0ef);
}

/* Bottom→top sweep. JS picks this or stScanTravelDown at random per transition. */
@keyframes stScanTravel {
  0%   { transform: translate3d(0, 100vh, 0); opacity: 0; }
  10%  { opacity: 1; }
  90%  { opacity: 1; }
  100% { transform: translate3d(0, -8px, 0);  opacity: 0; }
}

/* Top→bottom sweep (the reverse). */
@keyframes stScanTravelDown {
  0%   { transform: translate3d(0, -8px, 0);  opacity: 0; }
  10%  { opacity: 1; }
  90%  { opacity: 1; }
  100% { transform: translate3d(0, 100vh, 0); opacity: 0; }
}


/* ===== responsive.css ===== */
/* ============================================================================
   responsive.css — MOBILE / TABLET LAYER
   ----------------------------------------------------------------------------
   Loaded LAST so it wins the cascade. Desktop output must stay byte-for-byte
   unchanged: every rule here lives inside a width / capability media query and
   mirrors the specificity of the desktop rule it overrides. Nothing in this
   file applies above 1024px or to fine-pointer (mouse) devices.

   Primary target: modern phones, portrait-first (~390–430px). Tablets get an
   in-between pass at ≤1024px. 360–380px is guard-railed (type/padding only).

   BREAKPOINTS
     ≤1024px  tablet / in-between
     ≤768px   primary phone layer (most work)
     ≤380px   small-phone guard rails
   CAPABILITY
     (hover:none) and (pointer:coarse)  → neutralize hover-only FX, cursor FX
     (orientation:landscape) and (max-height:480px) → phone-landscape sanity

   Existing per-section media queries (nav drawer, #about stacking, #photo
   grid in styles.css; reduced-motion blocks) are intentionally left in place
   and not duplicated here — this file only adds what is missing.

   SECTION INDEX
     1.  Foundation / global   2. Header & nav   3. #intro   4. #about
     5.  #art-direction        6. #photo         7. #illustration   8. #contact
   ============================================================================ */


/* ════════════════════════════════════════════════════════════════════════════
   1 · FOUNDATION / GLOBAL
   ════════════════════════════════════════════════════════════════════════════ */

@media (max-width: 1024px) {
  /* Horizontal-overflow guard. body already has overflow-x:hidden; clip on the
     root is cheaper (creates no scroll container) and catches transformed
     decorative elements that bleed past the viewport edge on small screens. */
  html {
    overflow-x: clip;
    /* iOS/WebKit hardening (inert on desktop engines):
       · text-size-adjust — stop Safari inflating type on orientation change,
         which would blow up the width-fitted #about title.
       · tap-highlight — remove the gray flash when tapping nav/links. */
    -webkit-text-size-adjust: 100%;
            text-size-adjust: 100%;
    -webkit-tap-highlight-color: transparent;
  }

  /* Momentum scrolling for any inner scroll areas on iOS. */
  body {
    -webkit-overflow-scrolling: touch;
  }
}

@media (max-width: 768px) {
  /* Honor the notch / dynamic-island and home-indicator safe areas. The header
     is a fixed bar; pad its horizontal edges past the insets in landscape. */
  header {
    padding-left: max(var(--spacing-md), env(safe-area-inset-left));
    padding-right: max(var(--spacing-md), env(safe-area-inset-right));
  }
}

/* Touch-friendly hit targets + no sticky hover on coarse pointers. */
@media (hover: none) and (pointer: coarse) {
  .main-nav .nav-btn,
  .language-toggle .nav-btn,
  #sound-toggle {
    min-height: 44px;
    display: flex;
    align-items: center;
  }
}

/* ── Mobile menu: replay the load-in on every open ───────────────────────────
   The drawer slides in via transform (it's never display:none), so the page-load
   lineGrow on .nav-btn (styles.css) plays off-screen once and never repeats.
   Cancel it on phones and re-bind a fresh lineGrow to the .is-open state, so every
   hamburger open grows each row's orange top-line back in, staggered down the
   list. The CHARACTER glitch on the words is fired in JS via
   GlitchSystem.triggerGlitchBatch() in openDrawer() — the same glitch-switch the
   logo/name uses — so it's the real intro-text glitch, not a positional shake. */
@media (max-width: 768px) {
  .main-nav .nav-btn {
    animation: none;                       /* drop the unseen page-load run */
    --glitch-count: 4;                     /* halve the open-menu char glitch (8→4)
                                              so each row settles/reads sooner */
  }
  .main-nav.is-open .nav-btn {
    animation: lineGrow var(--duration-slow) var(--easing-ease-out) backwards;
    animation-delay: calc(var(--nav-i, 0) * 0.12s);
  }
  /* per-row stagger index for the line-grow */
  .main-nav .nav-btn:nth-child(1) { --nav-i: 0; }
  .main-nav .nav-btn:nth-child(2) { --nav-i: 1; }
  .main-nav .nav-btn:nth-child(3) { --nav-i: 2; }
  .main-nav .nav-btn:nth-child(4) { --nav-i: 3; }
  .main-nav .nav-btn:nth-child(5) { --nav-i: 4; }
}

/* Reduced motion: open the menu instantly (no line-grow). */
@media (prefers-reduced-motion: reduce) {
  .main-nav.is-open .nav-btn { animation: none; }
}

/* ── Drawer panel: more transparent + frosted ────────────────────────────────
   Drop the near-opaque rgba(0,12,18,.97) (styles.css) so the hero / orange frame
   shows through; backdrop-filter keeps the nav text legible over the busy hero. */
@media (max-width: 768px) {
  .main-nav {
    /* Darkened 0.6 → 0.82 (owner 2026-07-10): the hero showed through too
       loud behind the section names. Still translucent + frosted. */
    background-color: rgba(0, 12, 18, 0.82);
    -webkit-backdrop-filter: blur(10px) saturate(140%);
            backdrop-filter: blur(10px) saturate(140%);
  }

  /* Close control redesign (owner 2026-07-10): while the drawer is open the
     three morphing lines are replaced by a bracketed [X], matching the
     [EN]/[ES] + [ · s e c t i o n · ] bracket language of the nav. The
     button/JS/aria flow is untouched — lines are hidden and the glyph is
     drawn by ::after only in the expanded state; closed state keeps the
     stock hamburger lines. */
  .hamburger-btn[aria-expanded="true"] .hamburger-line {
    display: none;
  }
  .hamburger-btn[aria-expanded="true"]::after {
    content: "[X]";
    font-family: 'Funnel Display', sans-serif;
    /* Owner 2026-07-10: bigger, bold, site orange/red. */
    font-size: 1.25rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    line-height: 1;
    color: var(--orange, #ff3c00);
  }

  /* Drawer rows (owner 2026-07-10): larger + semibold; animations (lineGrow
     replay, char glitch) and colors untouched. */
  .main-nav .nav-btn {
    font-size: 1.15rem;
    font-weight: 600;
  }

  /* Vertical electric-static line on the drawer's left edge — the desktop
     section-edge static line (cyan #0ef jitter canvas), rotated. The canvas is
     sized 4×innerHeight by makeStaticLine({vertical:true}); MobileNav toggles
     .active on open/close. Sits in the 48px left padding, clear of the text. */
  .main-nav .nav-static-line {
    position: absolute;
    top: 0;
    left: 0;
    width: 4px;
    height: 100%;
    z-index: 2;
    opacity: 0;
    transition: opacity 0.15s ease-out;
    pointer-events: none;
  }
  .main-nav .nav-static-line.active {
    opacity: 1;
    filter: drop-shadow(0 0 3px #0ef) drop-shadow(0 0 7px #0ef);
  }
}

/* Desktop: the drawer canvas is a no-op there (nav is horizontal) — hide it so it
   never renders at its default 300×150 size in the row. */
@media (min-width: 769px) {
  .nav-static-line { display: none; }
}


/* ════════════════════════════════════════════════════════════════════════════
   2 · PRELOADER (loading screen)
   The label, % counter and 90px stage gap are tuned for desktop; on a phone the
   type reads oversized. Shrink type + gap fluidly. clamp() upper bounds equal
   the desktop values, so nothing here can ever exceed the desktop render (and
   the whole block only applies ≤768px anyway). The colored particle swarm is
   thinned separately in particle-system.js (isMobile-gated, preloader only).
   ════════════════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {
  /* Tighter column so the smaller type still straddles the centre line cleanly
     (the electric line corridor is 50px; ~30px each side stays clear). */
  .preloader-stage {
    gap: clamp(54px, 16vw, 90px);
  }
  .preloader-label {
    font-size: clamp(0.82rem, 3.4vw, 1.15rem);
  }
  .preloader-pct {
    font-size: clamp(1.05rem, 4.6vw, 1.5rem);
  }
}


/* ════════════════════════════════════════════════════════════════════════════
   3 · #INTRO
   ════════════════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {
  /* Sticky hero stage: svh fills the screen without the mobile URL-bar resize
     reflowing the sticky layer mid-scroll (dvh would jitter here). */
  #intro {
    min-height: 100svh;
    padding-left: var(--spacing-md);
    padding-right: var(--spacing-md);
  }

  /* Desktop decorations that crowd a narrow hero: the info-interface badge is
     380px wide at left:70px (overflows a 390px viewport) and the cyber-panel is
     vertically centered at the far left, colliding with the centered title/CTA.
     The navigation guidance they provide is already reachable via the hamburger
     drawer + visible CTA, so drop them on phones. */
  .info-interface,
  .intro-scroll-cue,
  .cyber-panel {
    display: none;
  }

  /* The title is a letter-spaced single line (.glitch-text is white-space:pre,
     so it can't wrap). styles.css's ≤768 rule (clamp(2rem,8vw,3.5rem)) renders
     it ~655px wide → clipped both sides on a phone. Shrink to fit one line:
     ~4.3vw keeps the full string inside 360–430px viewports. */
  #main-title {
    font-size: clamp(0.8rem, 4.3vw, 1.9rem);
  }

  /* Social icons: center them along the bottom of the hero instead of the
     desktop bottom-right corner. A full-bleed ul (left:0/right:0) + flex
     justify-content:center centers the icons WITHOUT a translateX transform —
     the socialIconsGrow scale() entrance animation owns `transform`, so any
     positioning transform here would be clobbered the moment it plays.
     bottom uses the safe-area inset so the row clears the home indicator. */
  #intro .social-media-icons {
    left: 0;
    right: 0;
    justify-content: center;
    gap: var(--spacing-xl);
    bottom: max(var(--spacing-lg), env(safe-area-inset-bottom));
  }
  /* The 3s entrance delay (styles.css) waits for the desktop panel-line sweep
     to pass the bottom edge — those lines are display:none here, so the icons
     just sat invisible for 3 seconds (owner 2026-07-03: too slow). 0.6s reads
     as "right after the frame lands". The li a rule is the load-time hover
     autoplay, synced to the same start. */
  #intro .cyber-panel.active ~ .social-media-icons {
    animation-delay: 0.6s;
  }
  #intro .cyber-panel.active ~ .social-media-icons li a {
    animation-delay: 0.6s;
  }

  /* ── Mobile neon frame ────────────────────────────────────────────────────
     Desktop draws an orange (#ff3c00) glowing bracket around the hero via the
     .cyber-panel sidebar + .panel-line--top/--bottom/--right (all hidden above:
     their viewport-tuned geometry — calc(100vw-100px), translateY(210px-50vh),
     a 36px left body — collapses on a ~390px screen). Instead of bending that
     fragile geometry we re-express the SAME visual idea — orange edge +
     --glow-orange bloom + an accent tab + a clipped corner — on the inert,
     empty <div class="intro-frame-mobile">. It carries zero desktop CSS, so the
     desktop render is byte-for-byte unchanged. Decorative + non-focusable. */
  #intro .intro-frame-mobile {
    position: absolute;
    /* clear the fixed header up top; hug the safe-area edges elsewhere so the
       capsule shell never collides with the notch / home indicator. */
    top: calc(var(--header-height, 60px) + var(--spacing-xs));
    /* left/right flush to the viewport edges (full-bleed) — the capsule's side
       walls touch the screen. top/bottom keep their header / safe-area clearance. */
    left: 0;
    right: 0;
    bottom: max(var(--spacing-sm), env(safe-area-inset-bottom));
    z-index: var(--z-layer-10);
    pointer-events: none;
    /* NOTE: the orange neon glow filter used to live HERE, on the wrapper. It was
       moved down onto .ifm-svg because iOS/WebKit fails to paint compositing
       descendants (the ticker overlays' animated transforms) that sit inside a
       filtered ancestor → the tickers rendered blank on iPhone. The glow only
       ever traced the SVG strokes anyway, so the visual is unchanged. */
  }

  /* Inline capsule (was a background image) so each line can scale-grow on entry.
     preserveAspectRatio="none" stretches it to fill the frame, exactly as the old
     background did, so the bars still map to the ticker top: %s above.
     The neon glow drop-shadow lives here (not on the wrapper) so the tickers are
     NOT inside a filtered ancestor — see the iOS note above. */
  #intro .intro-frame-mobile .ifm-svg {
    display: block;
    width: 100%;
    height: 100%;
    filter: drop-shadow(0 0 6px var(--orange))
            drop-shadow(0 0 16px rgba(255, 60, 0, 0.5));
  }
  #intro .intro-frame-mobile .ifm-svg path {
    fill: none;
    stroke: var(--orange);
    stroke-width: 2px;
    stroke-linecap: round;
    stroke-linejoin: round;
  }
  #intro .intro-frame-mobile .ifm-svg rect,
  #intro .intro-frame-mobile .ifm-svg polygon {
    fill: var(--orange);
  }
  /* Per-line grow — mirrors the desktop .panel-line draw (scale 0→1 along the
     line). transform-box: fill-box puts the origin on each element's own bbox, so
     ifm-gx grows from its left edge and ifm-gy from its centre.
     FAILURE-SAFE: the collapsed state (scale 0) lives in the grow keyframes'
     `from` (fill-mode:both holds it through the stagger delay), NOT here — so if
     the animation is ever dropped (reduced-motion tier-1 collapse, a WebKit
     compositing quirk), the frame renders fully drawn instead of invisible.
     During body.preloading nothing animates, but the opaque #preloader overlay
     covers the hero, so the momentary full-scale state is never seen. */
  #intro .intro-frame-mobile .ifm-gx,
  #intro .intro-frame-mobile .ifm-gy {
    transform-box: fill-box;
  }
  #intro .intro-frame-mobile .ifm-gx {
    transform-origin: left center;
  }
  #intro .intro-frame-mobile .ifm-gy {
    transform-origin: center;
  }

  /* ── Tickers on the two top bars ───────────────────────────────────────────
     Looping marquees that ride the capsule's two top bars (rects y=15.32 and
     y=33.09, both h=8.75, in the SVG's 410.66-tall viewBox). The bars fill the
     div via background-size 100% 100%, so they map linearly: top bar at
     15.32/410.66 = 3.73%, bottom bar at 33.09/410.66 = 8.06%, height 8.75/410.66
     = 2.13% → each overlay lands pixel-on-bar at any phone size. Text is a
     separate HTML layer (NOT inside the stretched SVG) so it never distorts;
     translateX is GPU-cheap and constant-speed. */
  /* FAILURE-SAFE: no opacity:0 base state here — the hidden state lives in the
     entrance keyframes' 0% frame (fill-mode:both holds it through the delay).
     If the entrance is ever dropped (reduced-motion, WebKit quirk), the ticker
     shows statically instead of being stranded invisible. */
  #intro .intro-frame-mobile .ifm-ticker {
    position: absolute;
    height: 2.13%;
    left: 0;
    right: 0;
    display: flex;
    align-items: center;
    overflow: hidden;
    /* feather the text in/out at the bar ends instead of a hard clip */
    -webkit-mask: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
            mask: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  }
  #intro .intro-frame-mobile .ifm-ticker--top    { top: 3.73%; }   /* 1st bar (y=15.32) */
  #intro .intro-frame-mobile .ifm-ticker--bottom { top: 10.23%; }  /* 2nd bar (y=42) */
  #intro .intro-frame-mobile .ifm-ticker-track {
    display: flex;
    width: max-content;        /* two seqs wide → -50% == exactly one seq */
    will-change: transform;
    animation: ifmTicker 30s linear infinite;
  }
  /* counter-scroll the top ticker (dual-HUD feel), slightly desynced speed */
  #intro .intro-frame-mobile .ifm-ticker--top .ifm-ticker-track {
    animation-duration: 24s;
    animation-direction: reverse;
  }
  #intro .intro-frame-mobile .ifm-ticker-seq {
    flex: none;
    white-space: nowrap;
    font-family: 'Funnel Display', sans-serif;
    font-weight: 800;                       /* bold */
    text-transform: uppercase;
    font-size: clamp(9px, 2.9vw, 12px);
    letter-spacing: 0.16em;
    line-height: 1;
    color: #001219;                         /* dark on the orange bar — LED-tape look */
  }

  /* Pause the loop while the hero is off-screen (battery). The intro observer
     toggles .paused-animations on #intro. */
  #intro.paused-animations .intro-frame-mobile .ifm-ticker-track {
    animation-play-state: paused;
  }

  /* ── Intro entrance (plays after the preloader clears, in view) ──────────────
     body.preloading is removed on preloaderDone (js/preloader.js); applying the
     animations only then starts each one fresh from 0, so they're seen instead of
     running behind the loading overlay. (1) the frame grows to size, then (2) the
     two text bars glitch in, staggered. */
  body:not(.preloading) #intro .intro-frame-mobile .ifm-gx {
    animation: ifmGrowX 0.6s var(--easing-ease-out) var(--d, 0s) both;
  }
  body:not(.preloading) #intro .intro-frame-mobile .ifm-gy {
    animation: ifmGrowY 0.45s var(--easing-ease-out) var(--d, 0s) both;
  }
  body:not(.preloading) #intro .intro-frame-mobile .ifm-ticker--top {
    animation: ifmTickerGlitchIn 0.55s linear 0.75s both;
  }
  body:not(.preloading) #intro .intro-frame-mobile .ifm-ticker--bottom {
    animation: ifmTickerGlitchIn 0.55s linear 0.95s both;
  }
}

/* Continuous one-direction marquee; -50% of the 2-seq track recycles seamlessly. */
@keyframes ifmTicker {
  to { transform: translateX(-50%); }
}

/* ── iOS / WebKit ticker hardening ──────────────────────────────────────────
   iPhone Safari & Chrome (both WebKit) painted the tickers blank. Two WebKit
   quirks: (1) content inside a filtered ancestor + will-change/clip-path fails
   to composite (the wrapper filter was already moved onto .ifm-svg above), and
   (2) animating clip-path with fill-mode:both can pin the element on frame-0
   (opacity 0). Here we drop the layer-promotion hint and swap the clip-path
   glitch-in for a plain opacity fade — WebKit-bulletproof, keeps the marquee.
   @supports(-webkit-touch-callout) matches ONLY iOS WebKit (inert on desktop
   Safari AND Android/Blink), so desktop + Android stay byte-for-byte. */
@supports (-webkit-touch-callout: none) {
  @media (max-width: 768px) {
    #intro .intro-frame-mobile .ifm-ticker-track { will-change: auto; }
    body:not(.preloading) #intro .intro-frame-mobile .ifm-ticker--top {
      animation: ifmTickerFadeIn 0.5s linear 0.75s both;
    }
    body:not(.preloading) #intro .intro-frame-mobile .ifm-ticker--bottom {
      animation: ifmTickerFadeIn 0.5s linear 0.95s both;
    }
  }
}
@keyframes ifmTickerFadeIn { from { opacity: 0; } to { opacity: 1; } }

/* Desktop: never render the mobile frame (and its ticker). It was an empty,
   unstyled div above 768px before, so display:none keeps desktop byte-for-byte. */
@media (min-width: 769px) {
  #intro .intro-frame-mobile { display: none; }
}

/* Per-line scale-grow — same idea as the desktop .panel-line draw (scale 0→1).
   The `from` carries the collapsed base state (see the failure-safe note above). */
@keyframes ifmGrowX { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes ifmGrowY { from { transform: scaleY(0); } to { transform: scaleY(1); } }

/* Text-bar glitch entrance: clip-path slices + horizontal jitter + flicker that
   settles to a clean, fully-revealed band. This glitches the ticker CONTAINER,
   not the track, so it never fights the marquee's translateX (which keeps
   scrolling inside throughout). */
@keyframes ifmTickerGlitchIn {
  0%   { opacity: 0; clip-path: inset(40% 0 55% 0); transform: translate3d(-8px, 0, 0); }
  15%  { opacity: 1; clip-path: inset(0 0 78% 0);   transform: translate3d(7px, 0, 0); }
  30%  {             clip-path: inset(68% 0 8% 0);   transform: translate3d(-5px, 0, 0); }
  45%  {             clip-path: inset(22% 0 46% 0);  transform: translate3d(4px, 0, 0); }
  60%  {             clip-path: inset(6% 0 60% 0);   transform: translate3d(-3px, 0, 0); }
  78%  {             clip-path: inset(0 0 0 0);      transform: translate3d(2px, 0, 0); }
  100% { opacity: 1; clip-path: inset(0 0 0 0);      transform: translate3d(0, 0, 0); }
}

/* Reduced motion: skip grow + glitch, freeze the marquee (static text).
   !important beats the more-specific body:not(.preloading) entrance rules.
   This is the mobile layer's "tier-3 visibility repair" (see styles.css RM
   block): under RM the frame + tickers must render fully, statically —
   reduce the MOTION, never the content. */
@media (prefers-reduced-motion: reduce) {
  #intro .intro-frame-mobile .ifm-gx,
  #intro .intro-frame-mobile .ifm-gy {
    transform: none !important;
    animation: none !important;
  }
  #intro .intro-frame-mobile .ifm-ticker {
    opacity: 1 !important;
    clip-path: none !important;
    transform: none !important;
    animation: none !important;
  }
  #intro .intro-frame-mobile .ifm-ticker-track { animation: none !important; }
}


/* ════════════════════════════════════════════════════════════════════════════
   4 · #ABOUT
   (container/title/capsule stacking already handled at ≤1024 / ≤768 in styles.css)
   ════════════════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {
  /* Certificate gallery — the DESKTOP composition, scaled to the viewport
     (owner 2026-07-02; replaces the older centered-cube-only treatment).
     On phones script.js reparents the layer into the .about-top stack right
     under the #aboutp4 tail (order 9). Desktop lays the art in a 455px-wide
     box: certs1 strip x:0 w:30, vtv frame x:35,y:50 420×350, cube x:120,y:95
     240px — all absolute px. Mapping that box to 100vw keeps the identical
     layout edge-to-edge: every desktop px becomes calc(100vw * N / 455).
     The --cs var carries the scale into the cube faces' translateZ on its
     own; perspective scales along so the 3D foreshortening matches desktop.
     Nav row inherits cube-left alignment like desktop; fonts unscaled. */
  #about .cert-gallery-layer {
    align-self: center;
    order: 9;
    width: 100vw;
    max-width: none;
    height: calc(100vw * 500 / 455);
  }
  #about .cert-gallery-layer .decoration-certs1 {
    width: calc(100vw * 30 / 455);
  }
  #about .cert-gallery-layer .decoration-vtv1 {
    top: calc(100vw * 50 / 455);
    left: calc(100vw * 35 / 455);
    width: calc(100vw * 420 / 455);
    height: calc(100vw * 350 / 455);
  }
  #about .cert-gallery-layer .cert-cube-wrapper {
    --cs: calc(100vw * 240 / 455);
    top: calc(100vw * 95 / 455);
    left: calc(100vw * 120 / 455);
    gap: calc(100vw * 18 / 455);
  }
  #about .cert-gallery-layer .cert-cube-scene {
    perspective: calc(100vw * 600 / 455);
  }
  /* After the reparents .about-right holds only the hidden DNA wrapper —
     collapse its padding/margins so no dead band sits between the gallery
     and the seal (the amber ::before line gets no height to draw on). */
  #about .about-right {
    padding: 0;
    margin-top: 0;
  }
  /* Seal CTA + social icons centered under the cert gallery (owner request
     2026-07-03): script.js reparents the wrapper into the stack after the
     gallery — in the markup it's an .about-container child and trailed the
     section ~28px off-center. It's already a centering flex column (seal
     over icons, styles.css), so full width + order is all it needs; the
     desktop 70px top pad shrinks to close ranks with the gallery above. */
  #about .about-top .seal-cta-wrapper {
    order: 10;
    width: 100%;
    align-self: center;
    /* snug against the cube (owner 2026-07-03): no own padding — the gap to
       the gallery is just the layer's trimmed tail below the vtv frame. */
    padding: 0;
  }
  /* Section ends at the icons (owner 2026-07-03). The gallery layer's 500-unit
     design height carried ~87px of dead band (only the certs1 side strip
     reached it); trim to the vtv frame's 400-unit bottom — the absolutely-
     positioned art isn't clipped, the strip now ends with the frame. Then
     drop the paddings that stacked up after the icons: wrapper (above),
     .about-top's 24, #about's 64 → one spacing-md breather, and hide the
     emptied .about-container (both its columns are fully moved/hidden on
     phones; its observers just idle — same as the DNA group's). */
  #about .cert-gallery-layer {
    height: calc(100vw * 400 / 455);
  }
  #about .about-top {
    padding-bottom: 0;
  }
  #about .about-container {
    display: none;
  }
  #about {
    padding-bottom: var(--spacing-md);
  }

  /* The .decoration-bar2 timeline bar is a tall vertical flag ("[·About·]" star
     label reading bottom-to-top, viewBox 37.5×997.6) sitting at left:80px — it
     reads as an awkward sliver on a phone. Rotate it 90° clockwise so the label
     reads left-to-right and lay it along the section's TOP edge: a thin
     full-width horizontal rule with the star/label flag at the left and the
     dashed line trailing right.
       height:100vw → the SVG's long axis maps to the screen width after rotate;
       width:auto keeps its aspect (≈3.8vw thickness). The compound transform
       rotate(90deg) translateY(-100%) (origin top-left) pivots it down-right so
       it spans x:[0→100vw], y:[0→thickness] off the top border. */
  #about .decoration-bar2 {
    /* sit exactly on the top-edge static line: bindSectionEdge() draws the
       cyan static at the section's rect.top, and svg-decorations top:0 == that
       same edge, so the bar rides right on the line. */
    top: 0;
    left: 0;
    width: auto;
    /* +50% bigger at natural proportions: 150vw long axis → the bar is 1.5×.
       width:auto keeps aspect, so no distortion. */
    height: 150vw;
    /* The "[·About·]" flag is at the SVG's far end → after rotate it lands at
       x≈150vw, off the right edge. Shift the whole bar left by the 50vw overhang
       (plus a small inset) so the flag reads complete against the right viewport
       edge; the dotted tail runs off the LEFT instead (body overflow-x:hidden). */
    transform: translateX(calc(-50vw - 8px)) rotate(90deg) translateY(-100%);
    transform-origin: top left;
  }
  /* The desktop reveal (slideDownFade) animates transform and would clobber the
     rotation. Slide the bar in instead: the keyframes carry the FULL transform
     (rotation preserved) and add a leading screen-space translateX that starts
     the bar off the left edge and settles it into place. */
  #about .decoration-bar2.element-visible {
    animation: aboutBar2SlideIn 0.8s cubic-bezier(.22, 1, .36, 1) 0s forwards;
  }
  @media (prefers-reduced-motion: reduce) {
    #about .decoration-bar2.element-visible {
      animation: aboutBar2FadeIn var(--duration-rise) ease-out 0s forwards;
    }
  }
  #about .decoration-bar2.element-exit {
    animation: aboutBar2FadeOut 0.5s ease-in 0s forwards;
  }

  /* The amber robot backdrop (#about::after) is inset left:50px right:50px on
     desktop, leaving dark bands down both sides on a phone. Pull it full-bleed
     so the image runs edge to edge. */
  #about::after {
    left: 0;
    right: 0;
  }

  /* ── "·Hello! I'm Sergio Ayala·" hero title ──────────────────────────────────
     Desktop puts the title + bio + id badge in one left-aligned row. On a phone
     stack the head as a single column and center the big title in the orange
     (amber-duotone) backdrop, just below the bar2 svg; the bio, badge, quote and
     two-column block then flow below it. */
  /* .about-top carries a desktop-only margin-left:54px (aligns its border to the
     vertical bar2 svg) + spacing-md side padding — both just steal width on a
     phone, where bar2 is now a horizontal top bar. Drop them so .about-top spans
     the full section content width and the title can fill it. */
  /* Pull the whole stack up under the bar2 svg. The section's 64px top padding
     leaves a big gap below the bar; shrink it to just clear the rotated bar's
     thickness (height:150vw × 37.5/997.6 ≈ 5.64vw) plus a hair. */
  #about {
    /* clear the rotated bar2 (~5.64vw thick) + extra room: the title's tight
       line-height lets line-1 glyphs overshoot their box top, so give it a bit
       more so "·Hello!" never rides up into the bar. */
    padding-top: calc(5.64vw + var(--spacing-lg));
  }
  #about .about-top {
    margin-left: 0;
    padding-left: 0;
    padding-right: 0;
    padding-top: 0;
    /* .about-top is already display:flex/column (base). Own the vertical stack's
       centering + spacing here since .about-top-head is dissolved below. */
    align-items: center;
    gap: var(--spacing-lg);
  }
  /* Dissolve .about-top-head so title/badge/bio become flex items of .about-top
     directly — alongside the bar1 divider — so bar1 can be ordered to sit right
     under the id badge (title 0 → badge 1 → bar1 2 → bio 3). about-title-fit.js
     now measures .about-top for width (this node has no box). */
  #about .about-top-head {
    display: contents;
  }
  /* Dissolve .about-intro's box so its children (title + bio) become flex items
     of .about-top-head alongside .about-id-stack — lets us order them as a clean
     vertical stack: title → id badge → bio. (about-title-fit.js measures
     .about-top-head for width, since .about-intro now has no box.) */
  #about .about-intro {
    display: contents;
  }
  #about #abouttitle {
    text-align: center;
    align-self: center;
    order: 0;
    /* Tight line-height packs the 4 lines so they barely touch — and unlike the
       old `top:-Xem` shifts (visual only → left phantom flow space that needed a
       -1.4em margin hack and overlapped the badge), line-height changes the real
       line boxes, so the title's flow height matches what's drawn and the badge
       sits cleanly below (about-top-head gap). */
    margin: 0;
    line-height: 0.82;
  }
  /* full-width ID badge, directly below the title message */
  #about .about-id-stack {
    order: 1;
    width: 100%;
    max-width: none;
    margin: 0;
    align-self: stretch;
  }
  /* nudge the id1.svg badge up 30px (owner request 2026-07-01) */
  #about #id1svg {
    margin-top: -30px;
  }
  /* ── Scroll hint (text + chevrons): own 50%-visibility reveal on phones ────
     Desktop chains the hint's reveal to the BADGE's class
     (#id1svg.element-visible ~ …, styles.css), so it vanished the moment the
     badge exited. On phones svg-inline.js observes the hint itself (threshold
     0.5) and toggles element-visible on it directly. Neutralize the
     badge-chained reveal, then re-key it to the hint's OWN class — the
     opacity:1 rules win the opacity:0 ones by file order at equal specificity
     (2 ids, 2 classes). */
  #about #id1svg.element-visible ~ .about-id-scroll,
  #about #id1svg.element-visible ~ .about-id-arrow {
    opacity: 0;
  }
  #about #id1svg ~ .about-id-scroll.element-visible,
  #about #id1svg ~ .about-id-arrow.element-visible {
    opacity: 1;
  }
  /* chevron pulse rides the hint's own class too (desktop keys it off the badge) */
  #about #id1svg ~ .about-id-arrow.element-visible .aid-chev-0 {
    animation: aid-chev-seq 1.4s linear 0s infinite;
  }
  #about #id1svg ~ .about-id-arrow.element-visible .aid-chev-1 {
    animation: aid-chev-seq 1.4s linear 0.18s infinite;
  }
  #about #id1svg ~ .about-id-arrow.element-visible .aid-chev-2 {
    animation: aid-chev-seq 1.4s linear 0.36s infinite;
  }
  /* bar1 divider sits directly below the id badge (order 1), above the bio (3) */
  #about .decoration-bar1 {
    order: 2;
  }
  /* bio: below the badge, fully justified so it reads as a clean rectangular
     block (owner request 2026-07-01). Hyphenation disabled site-wide at owner
     request 2026-07-05: no soft-hyphens at line-ends in either language, words
     stay whole (-webkit-hyphens: none also covers iOS WebKit). */
  #about .about-top-head #aboutp1 {
    order: 3;
    text-align: justify;
    -webkit-hyphens: none;
            hyphens: none;
    /* .glitch-text (added by the script.js mobile split) is white-space:pre,
       which forbids soft wrapping — a 500-char paragraph renders as one line
       off the right edge of the phone (owner report 2026-07-03). Same escape
       hatch styles.css already uses for the Paul Rand ending. */
    white-space: normal;
    /* viewport-wide block, left-to-right (owner 2026-07-03): same 100vw +
       gutter recipe as #aboutp3 further down the stack. */
    align-self: center;
    width: 100vw;
    max-width: none;
    box-sizing: border-box;
    padding: 0 var(--spacing-md);
    margin-top: 0;
  }
  /* Bio glitch entrance (owner 2026-07-02; split + trigger live in the
     script.js mobile branch). The reveal runs through the suppress→fire path
     (styles.css), which reads two vars per element — defaults are sized for
     ~40-char titles/buttons, so re-tempo them for a ~500-char paragraph:
     --glitch-stagger 0.05s→0.007s (last char starts ≈3.5s in, not 25s) and
     --glitch-count 10→5 flickers (1s per char, wave settles ≈4.5s total).
     COLOR — [data-char] glyphs paint via ::after with --glitch-final-color
     (white at this scope); the bio reads #001219 (highlight span included),
     so recolor the painted glyphs to match.
     Failure-safe: fill-mode stays `backwards`, so a collapsed/instant run
     (Reduce Motion) still lands on content:var(--txt) — the real text. */
  /* Tempo shared by every glitch host in the section (owner 2026-07-03:
     "acelerado un poco" vs the bio's original 0.007s/5): last bio char now
     starts ≈2s in, each char settles in 0.8s. Short hosts (pill, metrics,
     p3/p4 spans) ride the same wave speed. */
  #about .about-top-head #aboutp1,
  #about .about-top #about-availability,
  #about .about-top #about-metrics,
  #about .about-top #aboutp3,
  #about .about-top #aboutp4 {
    --glitch-stagger: 0.004s;
    --glitch-count: 4;
  }
  /* dark-text hosts: the ::after glyphs default to --glitch-final-color
     (white) — recolor to the text they replace. #aboutp4 joined the dark
     family 2026-07-03 (its tail switched white → #001219, owner request). */
  #about .about-top-head #aboutp1[data-splitting] [data-char]::after,
  #about .about-top #about-availability [data-char]::after,
  #about .about-top #about-metrics [data-char]::after,
  #about .about-top #aboutp3 [data-char]::after,
  #about .about-top #aboutp4 [data-char]::after {
    color: #001219;
  }
  /* Progressive reveal riding the wave (owner 2026-07-03, like the quote's
     yellow ending on desktop): chars start invisible and pop in exactly when
     their glitch slot arrives. Hiding the [data-char] SPAN (not the ::after)
     also hides the ::after's `backwards` from-state — the random glitch
     glyph every char otherwise shows while waiting its turn. Delay formula
     matches the styles.css firing rule 1:1, so the opacity front and the
     glitch front travel together. Failure-safe: plain opacity:1 rides the
     firing state (backwards fill carries the hidden phase); i18n re-splits
     insert fresh chars into a still-.glitch-firing host, so they re-reveal. */
  #about .about-top-head #aboutp1 [data-char],
  #about .about-top #about-availability [data-char],
  #about .about-top #about-metrics [data-char],
  #about .about-top #aboutp3 [data-char],
  #about .about-top #aboutp4 [data-char] {
    opacity: 0;
  }
  #about .about-top-head #aboutp1.glitch-firing [data-char],
  #about .about-top #about-availability.glitch-firing [data-char],
  #about .about-top #about-metrics.glitch-firing [data-char],
  #about .about-top #aboutp3.glitch-firing [data-char],
  #about .about-top #aboutp4.glitch-firing [data-char] {
    opacity: 1;
    animation: glitchCharIn 0.01s linear
      calc(var(--char-index) * var(--glitch-stagger, 0.05s)) backwards;
  }
  @keyframes glitchCharIn {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
  /* The teal .about-highlight sweep keys off an `.element-visible` ancestor
     (styles.css), but #aboutp1 sits in .about-top-head — outside the
     .about-left observer list — so the new highlighted sentence never sweeps
     on phones. Ride the bio's own entrance instead: .glitch-firing is added
     when the reveal fires and persists, and the span's stock 0.8s/0.7s-delay
     transition still animates the fill. Failure-safe: if the entrance never
     fires the text stays readable, just unhighlighted (same as before). */
  #about .about-top-head #aboutp1.glitch-firing .about-highlight {
    background-size: 100% 88%;
  }
  /* availability pill ("Available for freelance — 2026" + pulsing dot):
     script.js reparents it under the bio on phones; slot it right after the
     bio in the stack (order 4) and pull it snug against the block it belongs
     to, left-aligned with the justified text. Selector only matches after the
     reparent (it sits in .about-left on desktop). */
  #about .about-top #about-availability {
    order: 4;
    align-self: flex-start;
    margin-top: calc(var(--spacing-sm) - var(--spacing-lg));
    /* bigger read on phones (owner request): 0.82rem → 1.05rem, dot scaled to
       match so the pair keeps its proportion */
    font-size: 1.05rem;
  }
  #about .about-top #about-availability .availability-dot {
    width: 10px;
    height: 10px;
  }
  /* metrics strip ("10+ years · 50+ projects · Bogotá → Worldwide"): script.js
     reparents it right after the pill on phones; slot it under the pill
     (order 5) and pull it into the same visual group (8px from the pill). */
  #about .about-top #about-metrics {
    order: 5;
    align-self: flex-start;
    margin-top: calc(var(--spacing-xs) - var(--spacing-lg));
  }
  /* skills.svg graphic below the metrics strip, full-bleed edge to edge
     (owner request 2026-07-01). script.js reparents it out of .about-left, so
     the .about-left overrides stop applying and the base .decoration-skills
     (absolute @ top:1050px/left:200px, h:600px, scale .8) would take over —
     re-ground it fully for the .about-top stack. width:100vw + align-self:
     center bleeds it past #about's side padding to the viewport edges
     (html overflow-x:clip absorbs any rounding overflow). The skillsFadeIn
     reveal (element-visible, fill:forwards) lands on scale(1)/opacity(1). */
  #about .about-top .decoration-skills {
    order: 6;
    position: relative;
    top: auto;
    left: auto;
    height: auto;
    transform: none;
    display: block;
    width: 100vw;
    max-width: 100vw;
    align-self: center;
    margin-top: calc(var(--spacing-sm) - var(--spacing-lg));
  }
  #about .about-top .decoration-skills .skills-svg {
    position: static;
    display: block;
    width: 100%;
    max-width: none;
    height: auto;
  }
  /* skills.svg per-element entrance (owner 2026-07-03, id1-style): svg-inline.js
     converts the <img> to inline SVG on phones, wraps each text line in
     <g class="skills-row"> (global top-down --row-index across both columns)
     and tags the card bg / sidebar. The container then paints immediately
     (its stock skillsFadeIn handed over — .skills-inline) and the reveal is
     carried by the parts, keyed to the same element-visible the script.js
     observer already toggles: card → sidebar → rows cascading down.
     Failure-safe: plain opacity:1 rides next to each animation (if a future
     RM tier strips animation-name, the parts still paint); if the inline
     conversion itself fails the <img> keeps the stock container fade. */
  #about .about-top .decoration-skills.skills-inline {
    animation: none;
    opacity: 1;
    transform: none;
  }
  #about .decoration-skills .skills-anim :is(.skills-card, .skills-side, .skills-row) {
    opacity: 0;
  }
  #about .decoration-skills.element-visible .skills-anim .skills-card {
    opacity: 1;
    animation: skillsPartDrop 0.4s ease-out 0s backwards;
  }
  #about .decoration-skills.element-visible .skills-anim .skills-side {
    opacity: 1;
    animation: skillsPartDrop 0.35s ease-out 0.15s backwards;
  }
  #about .decoration-skills.element-visible .skills-anim .skills-row {
    opacity: 1;
    animation: skillsPartDrop 0.35s ease-out calc(0.25s + var(--row-index) * 0.04s) backwards;
  }
  @keyframes skillsPartDrop {
    from {
      opacity: 0;
      transform: translateY(-14px);
    }
    to {
      opacity: 1;
      transform: none;
    }
  }
  /* #aboutp3 below the graphic (owner request 2026-07-02): only the amber
     .about-highlight sentence ("I strongly believe…") survives on phones.
     CSS can't select bare text nodes, so the lifestyle text collapses via
     font-size:0/line-height:0 and the span restores its own size — this also
     survives the i18n innerHTML re-render on language switch (both locales
     keep the span). After the reparent the .about-left p base/reveal rules
     stop matching, so visibility is re-keyed here: base hidden → fade on its
     own .element-visible, with the plain opacity:1 as the failure-safe. */
  #about .about-top #aboutp3 {
    order: 7;
    font-size: 0;
    line-height: 0;
    opacity: 0;
    transform: none;
    /* viewport-wide justified block (owner 2026-07-02): 100vw like the svg
       above it — bleeds past #about's side padding; a small gutter keeps the
       justified glyphs off the physical screen edge. */
    align-self: center;
    width: 100vw;
    max-width: none;
    box-sizing: border-box;
    padding: 0 var(--spacing-md);
    text-align: justify;
    -webkit-hyphens: none;
            hyphens: none;
    margin-top: calc(var(--spacing-sm) - var(--spacing-lg));
  }
  #about .about-top #aboutp3 .about-highlight {
    font-size: 1.05rem;
    line-height: 1.55;
  }
  #about .about-top #aboutp3.element-visible {
    opacity: 1;
    animation: aboutP3FadeIn 0.8s ease-out 0s forwards;
  }
  /* DNA capsule + "·Let's discover…DNA·" title removed on phones (owner
     2026-07-02). display:none on the wrapper takes out #dnacapsule1 and
     #dnatitle together; the rest of .about-right (#aboutp4, cert gallery,
     seal) and its amber ::before line stay. The DNA reveal observer still
     watches .about-right — it just toggles classes on unpainted nodes. */
  #about .about-right .dna-title-wrapper {
    display: none;
  }
  /* #aboutp4 under the highlight line (owner request 2026-07-02): on phones
     only the tail from the CTA onward survives ("Let's work together…" +
     the certificates paragraph) — same font-size:0 collapse as #aboutp3, with
     .about-p4-keep (span kept in index.html AND both locales) restoring
     itself. Unlike p3, the base #about #aboutp4 rules (styles.css) are
     ID-keyed and still match after the reparent, so the desktop
     translateX(100px) offset and slide/exit animations must be overridden
     here — a 100vw block shifted 100px would force horizontal overflow.
     The mid-paragraph .about-highlight span sits in the collapsed region and
     inherits font-size:0, so it stays invisible. */
  #about .about-top #aboutp4 {
    order: 8;
    font-size: 0;
    line-height: 0;
    opacity: 0;
    transform: none;
    align-self: center;
    width: 100vw;
    max-width: none;
    box-sizing: border-box;
    padding: 0 var(--spacing-md);
    text-align: justify;
    -webkit-hyphens: none;
            hyphens: none;
  }
  #about .about-top #aboutp4 .about-p4-keep {
    font-size: 1.05rem;
    line-height: 1.55;
    /* owner 2026-07-03: the tail read white on the orange bg — same dark ink
       as the rest of the phone stack. */
    color: #001219;
  }
  /* owner 2026-07-03: single highlight family — the cream CTA marker joins
     the teal (#94D2BD) the p1/p3 highlights already use. */
  #about .about-top #aboutp4 .about-cta-marker {
    background-image: linear-gradient(#94D2BD, #94D2BD);
  }
  /* Same plain fades as p3 (reusing its keyframes); the opacity:1 doubles as
     the failure-safe (see the block below — p4 left .about-left, so the
     .about-left p guard no longer reaches it). */
  #about .about-top #aboutp4.element-visible {
    opacity: 1;
    animation: aboutP3FadeIn 0.8s ease-out 0s forwards;
  }
  #about .about-top #aboutp4.element-exit {
    animation: aboutBar2FadeOut 0.5s ease-in 0s forwards;
  }

  /* ── FAILURE-SAFE reveals (mobile) ──────────────────────────────────────────
     Same principle as the #intro frame fix (2026-07-01): once JS marks an
     element revealed (.element-visible), a dropped entrance animation (RM
     tier-1 collapse, a WebKit quirk) must never strand it invisible. These are
     plain declarations, so ANY running or fill-forwards animation overrides
     them — the happy path is pixel-identical; they only matter when the
     animation never plays. transform:none is safe here because every listed
     base transform is an entrance offset (translateX/Y, scale), not
     positioning. Excluded on purpose: bar2 (base transform IS positioning —
     its rotate — and its base opacity is already 1) and the DNA capsule's
     internal parts (staged per-part reveal; root gets the opacity guard only). */
  #about #id1svg.element-visible,
  #about .about-left p.element-visible,
  #about .decoration-bar1.element-visible,
  #about #about-availability.element-visible,
  #about #about-metrics.element-visible,
  #about .decoration-skills.element-visible,
  #about #seal-cta.element-visible,
  #about #dnatitle.element-visible {
    opacity: 1;
    transform: none;
  }
  #about #dnacapsule1.element-visible {
    opacity: 1;
  }

  /* Remove the animated Paul Rand quote on phones (owner request 2026-07-01).
     Desktop keeps it (this is inside the ≤768px block). about-pin.js still runs
     its visibility-gated loop on the now-hidden node — harmless (no layout/paint). */
  #about .paul-rands-quote {
    display: none;
  }

  /* Remove the UX/UI + vibe-coding paragraph on phones (owner request
     2026-07-01) — the justified bio already carries the story on mobile.
     Desktop keeps it. NOTE: #aboutp2 is the DNA group's entry trigger in
     script.js; on phones that observer is re-anchored to #aboutp3
     (a display:none node never intersects). */
  #about #aboutp2 {
    display: none;
  }

  /* The stacked title's line offsets are hard-coded px tuned for 4rem. Express
     them in em (relative to each line's own size) so the whole composition
     scales when about-title-fit.js sizes the title to fill the viewport width. */
  /* I'm/Sergio/Ayala move as one block (same -40px delta on both) — keeps their
     internal spacing (no re-overlap) while pulling the group up closer to ·Hello!. */
  #about #abouttitle .title-line-2 {
    margin-top: 0;
    margin-left: -0.05em;   /* was -7px */
    top: -8px;              /* "I'm" (block pulled up 40px toward ·Hello!) */
  }
  #about #abouttitle .title-line-3,
  #about #abouttitle .title-line-4 {
    top: -39px;             /* "Sergio"+"Ayala·" group (dropped 28px from -67px, which
                               overlapped "I'm" too much) */
    margin-top: 0;
  }
}

/* Reduced motion: static chevrons for the phone-side hint reveal (mirrors the
   badge-keyed RM rule in styles.css:2757). The selector only matches classes
   the ≤768px JS adds, so desktop is untouched. */
@media (prefers-reduced-motion: reduce) {
  #about #id1svg ~ .about-id-arrow.element-visible .aid-chev {
    animation: none !important;
    opacity: 0.8;
  }
}

@keyframes aboutP3FadeIn { from { opacity: 0; } to { opacity: 1; } }

@keyframes aboutBar2FadeIn  { from { opacity: 0; } to { opacity: 1; } }
/* Slide the rotated bar2 in from off the left edge. The leading translateX is a
   screen-space (pre-rotate) shift that adds to the base translateX; the rest of
   the transform matches the base rule so `forwards` fill lands with no jump. */
@keyframes aboutBar2SlideIn {
  from { opacity: 0; transform: translateX(-100vw) translateX(calc(-50vw - 8px)) rotate(90deg) translateY(-100%); }
  to   { opacity: 1; transform: translateX(0)      translateX(calc(-50vw - 8px)) rotate(90deg) translateY(-100%); }
}
@keyframes aboutBar2FadeOut { from { opacity: 1; } to { opacity: 0; } }


/* ════════════════════════════════════════════════════════════════════════════
   5 · #ART-DIRECTION
   Desktop is a spatial composition: a 75vw letter grid on the left with content
   panels keyed to the narrow strip beside it (left: min(75vh,75vw)+…). On a
   phone that strip is ~80px wide — unusable. The section is NOT scroll-pinned
   (IntersectionObserver-gated), so it's safe to restack it as a vertical flow:
   compact grid masthead on top, then the active content panel, then the nav.
   ════════════════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {
  /* body's global overflow-x:hidden computes overflow-y to `auto`, making the
     body a scroll container — which breaks position:sticky (the #art-direction
     bottom-pin below sticks relative to the body span and never engages).
     `clip` prevents the same horizontal overflow WITHOUT creating a scroll
     container, so native sticky works again (composited → no scroll jitter).
     html already carries overflow-x:clip (≤1024px) as the root guard. */
  body {
    overflow-x: clip;
  }
  /* overflow:visible lets the now-in-flow panels define the section height
     (absolute children create none). The bg-image/scanlines stay full-bleed. */
  #art-direction {
    overflow: visible;
    height: auto;            /* cancel the desktop grid-height lock */
    min-height: 100svh;
    /* Trim the dead space under the accordion by half (owner 2026-07-04):
       was spacing-2xl(64) here + spacing-xl(48) accordion margin = 112px;
       now spacing-lg(32) + spacing-md(24) = 56px. */
    padding-bottom: var(--spacing-lg);
    /* Native sticky pin, offset so the section's BOTTOM lands at the viewport
       bottom (owner 2026-07-04). The section is taller than the viewport, so a
       top:0 pin (desktop) would hide the accordion; instead we pin at a
       negative top equal to the overflow (section height − viewport), computed
       into --ad-pin-top by js/ad-accordion.js on resize / accordion resize.
       Sticky is composited, so the pin is jitter-free (unlike a per-frame JS
       transform), and it engages exactly when the bottom reaches the viewport
       bottom — the same moment the #photo curtain starts — so no empty gap. */
    position: sticky;
    top: var(--ad-pin-top, 0px);
  }
  /* Restore full-bleed: the mobile section is a tall vertical flow, so the bg
     must stretch the whole height, not the fixed 100vh used on desktop. */
  #art-direction .art-bg-image {
    inset: 0;
    height: auto;
  }

  /* Letter masthead re-broken as ART / DIR / ECT / ION (owner 2026-07-03 v2):
     four rows of 3 squares, each spanning the viewport edge to edge. The
     markup rows (ART/DI/REC/TION, shared with desktop) can't change, so the
     phone re-break is pure CSS: the grid becomes 3 equal columns and the
     .art-row boxes dissolve (display:contents) — the 12 cells flow in DOM
     order and regroup as ART/DIR/ECT/ION on their own. Cells go fluid
     (100%/aspect-ratio) so each row fills the width exactly, no vw rounding
     gap; the letters keep scaling via their container queries. */
  #art-direction .art-direction-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    margin: calc(var(--header-height, 60px) + var(--spacing-md)) 0 var(--spacing-lg);
  }
  #art-direction .art-row {
    display: contents;
  }
  #art-direction .art-cell {
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
  }

  /* Decorative vertical bars that have no room on a phone. */
  #art-direction .ad-skills-bar,
  #art-direction .ad-projects-bar {
    display: none;
  }
  /* Red intro frame back on phones (owner 2026-07-03: "solo el svg rojo",
     empty for now — content slots in later). It overlays the ART/DIR/ECT/ION
     masthead (owner v2): same top offset as the grid's margin, and its box IS
     the grid's box — 100vw wide, 4 rows of 100vw/3 squares tall. The SVG is
     preserveAspectRatio=none by design, so it stretches to frame the letters
     exactly like desktop stretches it to its own box. Reveal + internal loops
     (dashed line, chevrons, binary) key off body.ad-section-live and run
     as-is; the chevron HUD label is deferred "content", still off. */
  #art-direction .ad-intro-frame {
    left: 0;
    right: auto;
    /* the grid's margin-top COLLAPSES through the section (it pushes the whole
       sticky section down), so the grid's border box sits at the section's own
       top — the frame overlays from 0, not from header+margin.
       Owner 2026-07-03 v3: the SVG art is inset in its viewBox (topmost red
       stroke = notch peak at y 20.6 − 1 half-stroke = 19.6 of 632.7), so at
       top:0 the red line floated ~16px below the letters' top. Pull the box up
       by that scaled inset so the red peak lands exactly on the ART row top
       (mobile section is overflow:visible, nothing clips). */
    top: calc(100vw * 4 / 3 * -19.6 / 632.7);
    transform: none;
    width: 100vw;
    height: calc(100vw * 4 / 3);
    margin: 0;
  }
  #art-direction .ad-intro-arrow-label {
    display: none;
  }
  /* Owner 2026-07-04: no animated down-chevron cascade on phones — on desktop
     it points at the DISCIPLINES card below the frame; on the mobile overlay
     it just sits on top of the letters. Brackets + 3 pulsing chevrons. */
  #art-direction .ad-intro-svg #adfrm-arrow {
    display: none;
  }

  /* Content panels → full-width vertical flow. transform:none neutralizes the
     translateY(-50%) centering the desktop absolute layout depends on. */
  #art-direction .ad-intro-text,
  #art-direction .ad-works-zone,
  #art-direction .ad-explore-card {
    position: static;
    inset: auto;
    width: auto;
    max-width: 100%;
    transform: none;
    margin: 0 var(--spacing-md) var(--spacing-xl);
  }

  /* intro-text entrance keyframes animate to translateY(-50%) (centering-only);
     drop the animation in flow but keep the class-driven visible state. */
  #art-direction.ad-intro-active.ad-intro-animate .ad-intro-text {
    animation: none;
  }

  /* Owner 2026-07-04: the intro text lives INSIDE the red frame, centered
     over the letter masthead (this is the frame's deferred "content"). Same
     box as the frame (grid box pulled up by the art's viewBox inset); the
     gutters keep the copy off the red border, the corner hatching and the
     top-right rects. z:10 floats it over frame lines (z:9) and cells (z:5).
     Body copy matches the #about phone paragraphs: 1.05rem / 1.55. */
  #art-direction .ad-intro-text {
    position: absolute;
    top: calc(100vw * 4 / 3 * -19.6 / 632.7);
    left: 0;
    width: 100%;
    height: calc(100vw * 4 / 3);
    box-sizing: border-box;
    margin: 0;
    padding: 16vw 9vw 10vw;
    justify-content: center;
    gap: 16px;
    z-index: 10;
  }
  #art-direction .ad-intro-text .iad-body {
    font-size: 1.05rem;
    line-height: 1.55;
    /* Edge-to-edge justification like the #about paragraphs (owner 2026-07-05). */
    text-align: justify;
    -webkit-hyphens: none;
            hyphens: none;
  }
  /* Owner 2026-07-04: dim the letter masthead under the intro text so the
     copy reads. Keyed to .ad-intro-animate (the exact class that starts the
     text reveal) and eased over 2.2s = the text's full resolve time (header
     scramble ≈ 300ms delay + 21 chars × 90ms; body char wave ≈ 0.9s), so the
     darkening is a visible effect that ends with the last decoded glyph.
     Filter on the grid container: cells (z:5) stay under frame (z:9) and
     text (z:10). Lifts symmetrically when the intro state exits. */
  #art-direction .art-direction-grid {
    transition: filter 2.2s ease;
  }
  #art-direction.ad-intro-active.ad-intro-animate .art-direction-grid {
    filter: brightness(0.32) saturate(0.65);
  }
  @media (prefers-reduced-motion: reduce) {
    #art-direction .art-direction-grid {
      transition: none;
    }
  }

  /* intro-text and works-zone are mutually exclusive states. opacity alone
     would leave the inactive one occupying space — collapse it with display. */
  #art-direction:not(.ad-intro-active) .ad-intro-text {
    display: none;
  }
  #art-direction.ad-intro-active .ad-works-zone {
    display: none !important;
  }

  /* styles' art-direction-panel.css hides the works panel + discipline nav
     below 1220px (no room beside the desktop grid, "nav would point at
     nothing"). The mobile restack gives them their own full-width space below
     the grid, so bring them back. */
  #art-direction .ad-works-panel {
    display: flex;
  }
  /* Owner 2026-07-04: the yellow DISCIPLINES SVG card is replaced on phones
     by the ad-accordion (js/ad-accordion.js builds it, photo-style attract
     cascade + click-to-expand project lists, works modal on tap). */
  #art-direction .ad-explore-card {
    display: none;
  }

  /* ── Art-direction accordion — photo's desktop list, recolored for the dark
     section and with TRANSPARENT backgrounds throughout (owner: no photo-style
     dark plate). Buttons are visible by default (failure-safe: no JS/animation
     needed to see them); only the project rows start hidden inside their
     display:none lists. ── */
  #art-direction .ad-accordion {
    position: relative;
    display: flex;
    flex-direction: column;
    margin: var(--spacing-lg) var(--spacing-md) var(--spacing-md);
    background: transparent;
  }
  #art-direction .ad-accordion-item {
    display: flex;
    flex-direction: column;
    background: transparent;
  }
  #art-direction .adacc-btn {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    min-height: 44px;
    background: none;
    border: none;
    border-top: 1px solid rgba(238, 155, 0, 0.28);
    cursor: pointer;
    text-align: left;
    font-family: 'Funnel Display', sans-serif;
    font-weight: 400;
    font-size: 0.95rem;
    color: #EE9B00;
    letter-spacing: 0.12em;
    line-height: 1.15;
    padding: 0.85rem 0;
    transition: color 0.2s ease;
    gap: 0.5rem;
  }
  /* Owner 2026-07-04: bigger, bold, wide-tracked category labels
     (· Identity · / · web · / · editorial · / · 3d & motion ·). The spacing
     between letters is letter-spacing, not baked spaces. text-transform:none
     keeps the authored mixed casing. */
  #art-direction .adacc-btn-label {
    font-weight: 700;
    font-size: 1.2rem;
    letter-spacing: 0.32em;
    text-transform: none;
  }
  #art-direction .ad-accordion-item:last-child {
    border-bottom: 1px solid rgba(238, 155, 0, 0.28);
  }
  #art-direction .adacc-btn.active {
    color: #E9D8A6;
  }
  #art-direction .adacc-btn-indicator::before {
    content: '[ + ]';
    font-family: 'Funnel Display', sans-serif;
    font-size: 0.75em;
    letter-spacing: 0.1em;
    opacity: 0.5;
    transition: opacity 0.2s ease;
  }
  #art-direction .adacc-btn.active .adacc-btn-indicator::before {
    content: '[ - ]';
    opacity: 1;
  }
  #art-direction .adacc-list {
    list-style: none;
    margin: 0 0 var(--spacing-sm) 0;
    padding: 0;
    display: none;
    flex-direction: column;
    background: transparent;
  }
  #art-direction .adacc-item {
    cursor: pointer;
    display: grid;
    /* 4th column = per-row cover thumbnail (js-injected, owner 2026-07-05). */
    grid-template-columns: 34px minmax(0, 1fr) 44px 64px;
    column-gap: 0.75rem;
    align-items: center;
    padding: 0.55rem 0;
    border-bottom: 1px solid rgba(10, 147, 150, 0.25);
    opacity: 0;
  }
  #art-direction .adacc-thumb {
    grid-column: 4;
    width: 64px;
    height: 44px;
    object-fit: cover;
    border-radius: 2px;
    border: 1px solid rgba(10, 147, 150, 0.35);
    display: block;
    background: rgba(0, 18, 25, 0.05);
    justify-self: end;
    align-self: center;
  }
  #art-direction .adacc-item:active {
    background: rgba(238, 155, 0, 0.12);
  }
  #art-direction .adacc-num {
    font-family: 'Funnel Display', sans-serif;
    font-size: 12px;
    font-weight: 300;
    color: #0A9396;
    letter-spacing: 0.05em;
    line-height: 1;
  }
  #art-direction .adacc-titles {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
  }
  #art-direction .adacc-title {
    font-family: 'Funnel Display', sans-serif;
    font-size: 0.95rem;
    /* Semibold for legibility (owner 2026-07-05). 600 is a real loaded weight
       (Funnel Display 300/600/800); 400 wasn't loaded → rendered thin. */
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #E9D8A6;
    line-height: 1.15;
  }
  #art-direction .adacc-sub {
    font-family: 'Funnel Display', sans-serif;
    font-size: 0.72rem;
    /* Semibold too (owner 2026-07-05) — the 300 light + 0.55 opacity read
       faint; hierarchy stays via smaller size and dimmer colour. */
    font-weight: 600;
    color: rgba(233, 216, 166, 0.7);
    line-height: 1.25;
  }
  #art-direction .adacc-year {
    font-family: 'Funnel Display', sans-serif;
    font-size: 12px;
    font-weight: 300;
    color: #0A9396;
    text-align: right;
    line-height: 1;
  }

  /* works panel: drop the desktop scale()/max-height so it reads full-width. */
  #art-direction .ad-works-zone {
    max-height: none;
  }
  #art-direction .ad-works-panel {
    transform: none;
    max-height: none;
  }
  /* The table is flex:1/flex-basis:0 — inside a now-auto-height column it
     collapses to 0. Make it content-height and let the page scroll instead. */
  #art-direction .ad-works-table {
    flex: none;
    max-height: none;
    overflow-y: visible;
  }

  /* 5 columns don't fit a phone: drop the column-label header and the
     scope/tools cells, leaving "NN · Title … Year" (mirrors the photo list). */
  #art-direction .ad-works-cols {
    display: none;
  }
  #art-direction .ad-work-item {
    grid-template-columns: 22px 1fr auto;
    column-gap: var(--spacing-sm);
  }
  #art-direction .ad-work-scope,
  #art-direction .ad-work-tools {
    display: none;
  }

  /* explore-card (discipline nav): the ad-explore-enter clip-path/translateY
     reveal works fine in flow; just let it size by the SVG aspect ratio. */
  #art-direction .ad-explore-card {
    height: auto;
    aspect-ratio: 564.09 / 406.8;
    pointer-events: auto;
  }
  #art-direction .ad-explore-card .ad-nav-svg {
    height: auto;
  }

  /* ── Project modal → clean vertical stack (owner 2026-07-04): the desktop
     layout absolutely-positions the image stage (right 65%) over the text/
     gallery row, which reads as overlapping junk on a phone. Rebuild the frame
     as a flex COLUMN: the opening image pinned on top, then a scrollable detail
     area holding title + info and, below it, the image gallery. The bg/deco
     layers stay absolute inset:0 behind the flow (frame doesn't scroll). ── */
  .ad-pm-frame {
    inset: max(calc(var(--header-height, 60px) + var(--spacing-sm)), env(safe-area-inset-top))
           var(--spacing-sm)
           max(var(--spacing-sm), env(safe-area-inset-bottom));
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }
  /* 1 · opening image — in-flow at the top, fixed share of the frame. Flipbook
     mid-flip pages ride 3D transforms out of the box, so clip to it. */
  .ad-pm-stage {
    position: relative;
    top: auto;
    left: auto;
    right: auto;
    bottom: auto;
    width: 100%;
    height: auto;
    flex: 0 0 auto;
    aspect-ratio: 4 / 3;
    max-height: 40vh;
    padding: var(--spacing-sm);
    z-index: 4;
    overflow: clip;
  }
  /* The red cyber frame (desktop: top:100 right:100 65%×65%, i.e. the desktop
     stage box) otherwise floats over the stacked text. Re-anchor it to the
     mobile stage box so it becomes the FRAME of the top image — identical
     top/width/aspect/max-height as .ad-pm-stage, so the two boxes coincide. */
  .ad-pm-deco {
    top: 0;
    left: 0;
    right: auto;
    bottom: auto;
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 3;
    max-height: 40vh;
  }
  /* No visible close button on phones (owner 2026-07-04): tapping the image,
     backdrop or text dismisses the modal (art-direction-panel.js). */
  .ad-pm-close {
    display: none;
  }
  /* 2 · detail area — everything below the image, scrolls as one column:
     gallery first, then title + info (owner 2026-07-04). */
  .ad-pm-content {
    position: relative;
    top: auto;
    left: auto;
    right: auto;
    bottom: auto;
    flex: 1 1 auto;
    min-height: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--spacing-md);
    padding: 0 var(--spacing-md) var(--spacing-md);
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }
  /* Full-width text column (desktop width:min(40ch,30%) → ~94px, clipping the
     title mid-word); title/num a touch smaller than desktop. */
  .ad-pm-text {
    order: 2;
    width: 100%;
    max-width: none;
    flex: 0 0 auto;
    margin: 0;
  }
  .ad-pm-num {
    font-size: clamp(36px, 10vw, 52px);
  }
  .ad-pm-title {
    font-size: clamp(24px, 7vw, 32px);
  }
  .ad-pm-desc {
    max-width: none;
  }
  /* 3 · gallery — a wrapping grid of thumbs directly under the image, above
     the title/info (owner 2026-07-04), 3 per row and big enough to tap. */
  .ad-pm-thumbs {
    order: 1;
    flex: 0 0 auto;
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 8px;
    padding-bottom: 0;
  }
  .ad-pm-thumb {
    width: calc((100% - 16px) / 3);
    height: auto;
    aspect-ratio: 4 / 3;
    opacity: 0.85;
  }
}


/* ════════════════════════════════════════════════════════════════════════════
   6 · #PHOTO
   The top-grid (camera + project list) already stacks at ≤900/≤768 and the
   project table already drops columns (styles.css). Single polaroid widget is
   viewport-fluid. Only small polish needed here.
   ════════════════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {
  /* The hover-reveal polaroid mockup (.photo-polaroid-frame) and its
     "HOVER TO REVEAL THE PHOTO" hint SVG (.photo-polaroid-hint) make no sense
     on touch — remove the whole unit (owner 2026-07-04). The item lightbox is
     a separate body-level element, unaffected. */
  #photo .photo-polaroid-unit {
    display: none;
  }

  /* Invert the stacked columns (owner 2026-07-04): show the right column
     (.photo-col-camera — polaroid unit + instagram) first in the viewport,
     then the left column (cta + category list). DOM order stays left→right;
     flip it with order on the grid items. */
  #photo .photo-col-camera {
    order: -1;
    /* Desktop makes this column position:sticky (top:0) for the 2-col layout.
       On the single mobile column that pinned the intro + Instagram so the
       category accordion (and its "Click a category…" message) scrolled up and
       overlapped them. Unstick it → intro, message and the photo list all
       scroll together as one column, no overlap. (owner 2026-07-05) */
    position: static;
  }
  #photo .photo-col-left {
    order: 0;
  }

  /* Location column was truncating; let long place names ellipsize cleanly
     instead of clipping mid-word against the viewport edge. */
  #photo .photo-project-item .photo-location {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* The scroll-driven `[ · photography · ]` title (photo-vfx canvas) parks at
     the top (~navHeight+fontSize band, ends ~123px on a 393px phone), but the
     content started at padding-top:100px → the intro paragraph overlapped it.
     Push the content down so it begins below the title band. (owner 2026-07-04) */
  #photo .photo-portfolio-overlay {
    padding-top: 145px;
  }

  /* Left "· Portfolio ·" bar: 37.5px wide at the screen edge (styles.css),
     but the phone content gutter is only var(--spacing-md) = 24px, so the
     full-size bar overlapped the intro text (starts at left:24px). Shrink it
     to fit the free gutter with a small clearance (18px → ~6px gap; height
     follows the SVG aspect ratio). gsap animates translateX/opacity on this
     element, not width, so this override is safe (owner 2026-07-04).
     Moved to the RIGHT gutter with its top aligned to the intro paragraph
     (owner 2026-07-09): intro top = overlay padding-top 145px + 4px column
     inset = 149px — keep in sync if that padding changes. gsap still slides
     x:-40→0, which now reads as a short slide toward the right edge. */
  #photo .photo-section-bar-img {
    width: 18px;
    left: auto;
    right: 0;
    top: 149px;
  }

  /* Remove the whole polaroids region on phones (owner 2026-07-04): the
     "· polaroids ·" subtitle + its description, the stream control panel,
     the camera image, the divider above it, and the image carousel (ghost
     stream). Mobile #photo ends with the category accordion. The ghost-stream
     canvas is decorative (aria-hidden); hiding its container is enough. */
  #photo .photo-col-bar1,
  #photo .photo-polaroids-label,
  #photo .photo-polaroids-desc,
  #photo .pgallery-info,
  #photo .photo-polaroids-camera,
  #photo .photo-ghost-stream {
    display: none;
  }

  /* Remove the amber glow frame that flashes around the accordion while it
     loads / opens (owner 2026-07-05). It's the shared .accordion-electric-
     border box-shadow frame (styles.css), shown via .accordion-animating —
     drop it on phones only; desktop keeps it. */
  #photo .accordion-electric-border {
    display: none;
  }

  /* Photo rows adopt the art-direction accordion layout: number on the left,
     then the title on top with the location (description) stacked below it.
     The old side-by-side "num | title | location" grid let long location names
     (e.g. "CAJÓN DEL MAIPO, CHILE") widen the 1fr track past the viewport →
     horizontal page scroll. Here the single content column is min-width:0 and
     each line ellipsizes, so nothing overflows. (owner 2026-07-05) */
  /* The accordion sized to its content (~280px) inside the 345px column,
     wasting the right edge. Stretch it full-width so rows span the column and
     the thumbnail can use that space (owner 2026-07-05). */
  #photo .photo-accordion,
  #photo .photo-project-list,
  #photo .photo-accordion-item {
    width: 100%;
  }
  #photo .photo-project-item {
    /* 3rd column = per-row photo thumbnail (js-injected, owner 2026-07-05),
       filling the empty space right of the short title/location. */
    grid-template-columns: 26px minmax(0, 1fr) auto;
    grid-template-rows: auto auto;
    column-gap: 0.7rem;
    row-gap: 3px;
    align-items: start;
    padding: 0.5rem 0;
  }
  #photo .photo-project-item::before {
    grid-column: 1;
    grid-row: 1 / span 2;
    align-self: start;
  }
  /* Per-row thumbnail — right column, spans both text rows, centered. */
  #photo .photo-project-item .photo-thumb {
    grid-column: 3;
    grid-row: 1 / span 2;
    align-self: center;
    justify-self: end;
    width: 96px;
    height: 64px;
    object-fit: cover;
    border-radius: 2px;
    border: 1px solid rgba(10, 147, 150, 0.35);
    display: block;
    background: rgba(0, 18, 25, 0.05);
  }
  /* Selected row: amber border on its thumbnail, matching the yellow marker. */
  #photo .photo-project-item.photo-item-selected .photo-thumb {
    border-color: rgba(238, 155, 0, 0.9);
  }
  #photo .photo-project-item .photo-title {
    grid-column: 2;
    grid-row: 1;
    min-width: 0;
    max-width: 100%;
    font-size: 0.9rem;
    font-weight: 400;
    line-height: 1.15;
  }
  #photo .photo-project-item .photo-location {
    grid-column: 2;
    grid-row: 2;
    min-width: 0;
    max-width: 100%;
    /* Subtitle (location) enlarged on phones at owner request 2026-07-05
       (0.72 → 0.85rem); still a step under the 0.9rem title so the
       title/subtitle hierarchy holds. */
    font-size: 0.85rem;
    font-weight: 300;
    text-transform: none;
    letter-spacing: 0.02em;
    color: rgba(0, 18, 25, 0.6);
    line-height: 1.25;
  }

  /* SELECTED-PHOTO STATE (owner bug 2026-07-05).
     The row dim/highlight is hover-driven; touch never fires a reliable
     mouseleave, so after the item lightbox closes the desktop `has-active`
     dim (siblings → opacity .25) and the tapped row's inline opacity stay
     stuck → the photo name + location read as invisible. On phones:
     1) neutralize the dim so every row stays fully readable, and
     2) mark the last-opened photo (js adds .photo-item-selected on tap) with
        a persistent yellow highlight behind its name + location, so you can
        tell which one is selected — same palette as the desktop hover sweep,
        but it stays after the lightbox closes. */
  #photo .photo-project-list.has-active .photo-project-item,
  #photo .photo-project-item.active,
  #photo .photo-project-item.photo-item-selected {
    opacity: 1 !important;
  }
  #photo .photo-project-item.photo-item-selected .photo-project-data {
    color: rgb(10, 10, 10);
  }
  #photo .photo-project-item.photo-item-selected .photo-project-data::after {
    transform: scaleX(1);
  }

  /* FLY CAPTION on phones (owner 2026-07-10): the lightbox caption clone kept
     the desktop 6-column row grid (40px 1fr 2fr 1fr 1fr 80px, styles.css) —
     inside the 345px mobile caption every column crushed to ~24px and the
     info read cut off. Re-lay it as 3 lines, same yellow-sweep + glitch
     effects (both live on the base .photo-caption-fly rules):
       01  TITLE
           location
           camera · lens · year
     NOTE: the clone is appended to <body>, NOT inside #photo — selectors here
     must stay un-prefixed or they miss it. */
  .photo-caption-fly {
    grid-template-columns: 26px max-content minmax(0, 1fr) max-content;
    grid-template-rows: auto auto auto;
    column-gap: 0.6rem;
    row-gap: 3px;
    align-items: baseline;
    padding: 6px 10px;
  }
  /* Spans keep justify-self:start (styles.css) so they take their natural
     width and can spill past their track — clamp them so the base
     nowrap+ellipsis actually engages at the cell edge. */
  .photo-caption-fly .photo-project-data { max-width: 100%; }
  /* The counter is dark ink for desktop's light photo backdrop; on the phone
     lightbox it floats over #001219 — lighten it so it reads. */
  .photo-caption-fly .photo-fly-num  { grid-column: 1; grid-row: 1; color: rgba(255, 255, 255, 0.55); }
  .photo-caption-fly .photo-title    { grid-column: 2 / -1; grid-row: 1; font-size: 0.9rem; }
  .photo-caption-fly .photo-location { grid-column: 2 / -1; grid-row: 2; }
  /* camera/lens/year are display:none'd on mobile ROWS by styles.css
     (.photo-project-data:not(.photo-title):not(.photo-location)) — restore
     them inside the caption only. Selector carries .photo-project-data to
     match that rule's (0,3,0) specificity; this file loads last, so it wins. */
  .photo-caption-fly .photo-project-data.photo-camera { display: block; grid-column: 2; grid-row: 3; }
  .photo-caption-fly .photo-project-data.photo-lens   { display: block; grid-column: 3; grid-row: 3; }
  .photo-caption-fly .photo-project-data.photo-year   { display: block; grid-column: 4; grid-row: 3; justify-self: end; }
  /* Tech line (camera · lens · year) compacted so the three specs share one
     345px line; the lens (longest, least critical) ellipsizes if it still
     can't fit its 1fr leftover. */
  .photo-caption-fly .photo-project-data.photo-camera,
  .photo-caption-fly .photo-project-data.photo-lens,
  .photo-caption-fly .photo-project-data.photo-year {
    font-size: 0.68rem;
    letter-spacing: 0.03em;
  }

  /* Match the #about body paragraph size on phones (owner 2026-07-05): about is
     1.1rem / lh 1.8; the photo paragraphs were clamped down to 0.7rem (~11px). */
  #photo .photo-intro,
  #photo .photo-ig-desc,
  #photo .photo-cta {
    font-size: 1.1rem;
    line-height: 1.8;
  }

  /* Justify body copy edge-to-edge like the #about paragraphs (owner 2026-07-05).
     Hyphenation disabled at owner request 2026-07-05: no soft-hyphens at
     line-ends in either language. Words stay whole and wrap at spaces. */
  #photo .photo-intro,
  #photo .photo-ig-desc {
    text-align: justify;
    -webkit-hyphens: none;
            hyphens: none;
  }

  /* Speed up the photo reveal glitch on phones to match the about / art-direction
     glitch feel. The per-element flicker (photo-glitch-in, 0.5s in styles.css)
     is halved; the per-item cascade stagger is cut in js/photo-portfolio.js
     (isMobile-gated). (owner 2026-07-05) */
  #photo .photo-glitch-load {
    animation-duration: 0.25s;
  }
}

/* Safety: the per-row thumbnails are js-injected only on phones, but if a
   session resizes mobile → desktop the nodes persist — keep them out of the
   frozen desktop grid. (owner 2026-07-05) */
@media (min-width: 769px) {
  #photo .photo-project-item .photo-thumb {
    display: none;
  }
}


/* ════════════════════════════════════════════════════════════════════════════
   7 · #ILLUSTRATION
   Cube/lightbox are viewport-fluid; per-stop scroll height is reduced on phones
   in illus-cube.js. The mouse-follow ink cursor is skipped on touch
   (illustration-ink-cursor.js) — restore the real cursor styles.css hides.
   ════════════════════════════════════════════════════════════════════════════ */

@media (hover: none) and (pointer: coarse) {
  #illustration,
  #illustration * {
    cursor: auto;
  }
}

@media (max-width: 768px) {
  /* Sticky cube stage: svh keeps it from reflowing every time the mobile URL
     bar shows/hides across the long cube scroll (matches the JS height units). */
  .illus-tunnel {
    height: calc(100svh - var(--header-height, 60px));
  }

  /* Owner directive (2026-07-10): on phones #illustration shows ONLY the
     particle background, the cube with its images, and the Enter / Back-Next
     buttons. Everything else is desktop UI chrome — hide it.
     Cards keep only their .illus-cta-row (that's where the buttons live).
     The "[ · click to expand · ]" hint STAYS, same as desktop (owner
     2026-07-10); the CRT overlays moved to the perf block below. */
  #illustration .illus-hud,
  #illus-snake,
  .illus-scroll-cue,
  #illustration .illus-svg-bar,
  #illustration .illus-strip,
  #illustration .illus-info-svg,
  #illustration .illus-title-float,
  #illustration .illus-card > :not(.illus-cta-row) {
    display: none;
  }

  /* With only the buttons left, strip the card chrome (box, border, side
     placement) and pin the CTA row bottom-center, clear of the centered cube. */
  .illus-section {
    align-items: flex-end;
    justify-content: center;
    padding: 0 1rem max(2.5rem, env(safe-area-inset-bottom)) 1rem;
  }
  #illustration .illus-card {
    background: none;
    border: none;
    padding: 0;
    max-width: none;
    margin: 0;
  }
  #illustration .illus-card .illus-cta-row {
    margin-block-start: 0;
    justify-content: center;
  }

  /* Intro face word-art ("INTERDIMENSIONAL CUBE GALLERY…"): the desktop
     metrics (line-height 4, 32px insets, 0.2em tracking) overflow the ~290px
     phone face. Tighten box + type so the whole title sits inside the face.
     transform is NOT touched here — illus-cube.js counter-rotates it inline. */
  #illustration .illus-face-label--gallery {
    left: 12px;
    right: 12px;
    padding: 12px 14px;
    font-size: 0.62rem;
    line-height: 2.6;
    letter-spacing: 0.12em;
  }

  /* The intro ENTER button sat ON TOP of the centered cube: illus-cube.css's
     own mobile block (≤900px) centers .illus-section[data-idx="0"] vertically
     (specificity 0,2,0). Re-pin it to the bottom like the Back/Next rows
     (owner 2026-07-10). */
  #illustration .illus-section[data-idx="0"] {
    align-items: flex-end;
    padding: 0 1rem max(2.5rem, env(safe-area-inset-bottom)) 1rem;
  }

  /* PERF (owner 2026-07-10, cube laggy on phone) — cut GPU fill-rate cost.
     .scanlines/.vhs-static are two full-viewport layers with infinite
     transform animations compositing over the particle canvas + 3D scene
     every frame. illus-cube.js also skips their JS drivers on isMobile.
     Desktop keeps them. */
  #illustration .scanlines,
  #illustration .vhs-static {
    display: none;
  }

  /* Electric face borders back on phones (owner 2026-07-10) in CHEAP mode:
     same per-stop random color as desktop (--illus-face-color/-rgb, set by
     applyFaceColor in illus-cube.js), but always-on instead of scroll-velocity
     driven and with a single soft glow instead of the desktop triple
     box-shadow stack — the lag came from the glow layers + per-scroll opacity
     churn, not from the border itself. The JS velocity driver stays skipped
     on isMobile. */
  #illustration .illus-face-elec {
    opacity: 1;
    transition: none;
    box-shadow: 0 0 10px rgba(var(--illus-face-rgb), 0.45);
  }

  /* Lightbox (owner 2026-07-10): scrollable column — large image on top,
     the piece's title / description / links below. The .illus-lightbox-info
     node is injected and filled by illus-cube.js only on phones (cloned from
     the stop's hidden card); desktop lightbox never has it. The backdrop blur
     is dropped: the backdrop is already 96% opaque and full-screen
     backdrop-filter is a fill-rate hog on phone GPUs. */
  .illus-lightbox {
    align-items: flex-start;
    justify-content: flex-start;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .illus-lightbox-frame {
    flex-direction: column;
    width: 100%;
    min-height: 100%;
  }
  .illus-lightbox-img {
    max-width: 100%;
    max-height: 68svh;
    margin: max(3.25rem, env(safe-area-inset-top)) auto 0;
  }
  .illus-lightbox-border {
    display: none;   /* frame glow doesn't fit a scrolling column */
  }
  .illus-lightbox-info {
    margin-top: 1.1rem;
    padding: 1.15rem 1.25rem calc(2.25rem + env(safe-area-inset-bottom));
    border-top: 1px solid rgba(148, 210, 189, 0.25);
    font-family: 'Funnel Display', sans-serif;
    color: #94D2BD;
    text-align: left;
  }
  /* The card originals sit at opacity 0 until their .illus-section goes
     .active (entry animations). The clones live outside that context — force
     everything visible and static inside the info panel. */
  .illus-lightbox-info,
  .illus-lightbox-info * {
    opacity: 1 !important;
    transform: none !important;
    translate: none !important;
    animation: none !important;
    transition: none !important;
    white-space: normal !important;   /* glitch system sets pre — no wrap */
    max-width: 100% !important;
  }
  /* The IG button's decorative hover-border spans paint as a huge box outside
     the card context — the icon svg alone is the tap target here. */
  .illus-lightbox-info .social-media-icons a > span {
    display: none !important;
  }
  .illus-lightbox-info .illus-tag {
    color: #EE9B00;
    font-size: 0.7rem;
    letter-spacing: 0.22em;
  }
  .illus-lightbox-info .illus-piece-title {
    color: #E9D8A6;
    font-size: 1.05rem;
    letter-spacing: 0.06em;
    line-height: 1.35;
    margin: 0.4rem 0 0.35rem;
  }
  .illus-lightbox-info .illus-h2--category {
    font-size: 0.72rem;
    letter-spacing: 0.3em;
    opacity: 0.8;
    margin: 0 0 0.7rem;
  }
  .illus-lightbox-info .illus-body {
    font-size: 0.85rem;
    line-height: 1.6;
    color: #CFE8DF;
    margin: 0 0 0.9rem;
  }
  .illus-lightbox-info .illus-meta-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.3rem 0.5rem;
    font-size: 0.72rem;
    letter-spacing: 0.08em;
  }
  .illus-lightbox-info .illus-meta-link {
    color: #EE9B00;
    text-decoration: underline;
    padding: 0.35rem 0;   /* tap target */
  }
  .illus-lightbox-info .illus-ig-link {
    margin-top: 0.85rem;
  }

  /* Tappable close button, clear of the notch. Pinned to the viewport while
     the column scrolls — JS moves it out of the scale-transformed frame on
     phones so position:fixed anchors to the viewport, not the frame. */
  .illus-lightbox-close {
    position: fixed;
    z-index: 2;
    min-width: 44px;
    min-height: 44px;
    top: max(var(--spacing-sm), env(safe-area-inset-top));
    right: max(var(--spacing-sm), env(safe-area-inset-right));
    background: rgba(0, 18, 25, 0.75);
  }
}


/* ════════════════════════════════════════════════════════════════════════════
   8 · #CONTACT
   The form row already fully stacks at ≤640px in contact.css (capsule/bar
   hidden, single-column grid, full-width send). Only touch-target + safe-area
   polish needed.
   ════════════════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {
  /* Owner directive (2026-07-10): phones show ONLY the landscape background
     (stars/sky, horizon glow, mountains, sun, virtual-floor grid), the
     animated "Sergio Ayala / Let's get in touch!" title at full viewport
     width, the form below it, and the social buttons below that. The ship
     cockpit, hands and the CRT/grain overlays are desktop-only. */
  #contact .ct-cockpit,
  #contact .ct-hand,
  #contact .ct-grain,
  #contact .scanlines,
  #contact .vhs-static,
  #contact .ct-capsule,
  #contact .ct-contact-bar {
    display: none;
  }

  /* Content leaves the windshield: back to normal flow so the section grows
     with it; .ct-scene (absolute, inset 0) keeps painting the landscape
     behind at whatever height results. */
  #contact .ct-content {
    position: relative;
    inset: auto;
    min-height: calc(100svh - var(--header-height, 60px));
    justify-content: flex-start;
    padding: 1.5rem 0 4.5rem;   /* bottom clears the absolute footer */
  }

  /* Title at full viewport width — keeps the -10° rotation and the GSAP
     per-letter entrance from contact.js. Sizes tuned e2e so SERGIO/AYALA
     span edge to edge without horizontal overflow. */
  #contact .ct-form-row,
  #contact .ct-title,
  #contact .ct-stack {
    width: 100%;
  }
  #contact .ct-stack-line {
    font-size: 16.5vw;
  }
  #contact .ct-stack-line--tag {
    font-size: 7vw;
  }

  /* Form below the title, comfortably wide. */
  #contact .ct-form-col {
    width: min(480px, 92vw);
  }

  /* Social buttons: organized as full-width stacked tap targets. */
  #contact .ct-links--mini {
    flex-direction: column;
    align-items: stretch;
    width: 100%;
    gap: 0.5rem;
    margin-top: 0.75rem;
  }
  #contact .ct-links--mini .ct-link {
    text-align: center;
    font-size: 0.78rem;
    padding: 0.8em 1em;
  }

  /* The illustration handoff leaves #contact as a PERMANENT fixed backdrop
     (viewport-high, overflow hidden). Desktop content fits; the phone column
     (title + form + links) is taller than the viewport, so the fixed section
     becomes its own scroller — once the page scroll bottoms out, swipes keep
     scrolling the contact content. */
  #contact,
  #contact.ct-handoff-in {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    /* Opaque sky: the base section is background:transparent, and past the
       scene's one-viewport height the page BEHIND the fixed backdrop (intro
       chrome) bled through at the end of the inner scroll (owner 2026-07-10). */
    background: var(--ct-sky-dark, #001219);
  }

  /* Landscape pinned to the scroller viewport: as an absolute (out of flow)
     layer it scrolled away with the content, ending the backdrop after one
     viewport. Sticky keeps it covering the screen for the whole inner scroll;
     the negative margin cancels its in-flow height so the content column
     starts on top of it, unchanged. Content (z:50) paints above scene (z:1). */
  #contact .ct-scene {
    position: sticky;
    top: 0;
    inset-inline: auto;
    height: calc(100svh - var(--header-height, 60px));
    margin-bottom: calc(-100svh + var(--header-height, 60px));
  }

  /* In-flow footer: its absolute bottom:0 anchors to the (viewport-high)
     padding box, which lands mid-content inside the scroller. In flow it
     simply ends the column. Clears the home-indicator on notched phones. */
  .ct-footer {
    position: static;
    padding-bottom: max(var(--spacing-sm), env(safe-area-inset-bottom));
  }
}

@media (hover: none) and (pointer: coarse) {
  /* Comfortable tap targets for the send button and direct-contact links. */
  .ct-send,
  .ct-links--mini .ct-link {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
}

