/* ============================================================
   dandia® — édition n°2 · quiet luxury
   warm noir / ivory / champagne · Cormorant + Albert Sans
   ============================================================ */

/* ---------- fonts: self-hosted ----------
   The same three families Google Fonts served, as latin + latin-ext woff2 in
   /assets/fonts. Self-hosting removes a render-blocking round trip to a third
   origin (~0.9s on a throttled phone) and, more to the point, stops every
   visitor's IP being sent to Google before the page paints — a real question
   for an EU audience with no consent banner. unicode-range is kept exactly as
   Google shipped it, so the browser still fetches only the faces and ranges a
   page actually uses; latin-ext exists for the Romanian names.
   Regenerate: _unused-assets/fonts-src/ holds the original CSS and the script
   that produced these rules. ---------- */
@font-face {
  font-family: "Albert Sans";
  font-style: italic;
  font-weight: 300;
  font-display: swap;
  src: url("assets/fonts/albert-sans-300-italic-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Albert Sans";
  font-style: italic;
  font-weight: 300;
  font-display: swap;
  src: url("assets/fonts/albert-sans-300-italic-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Albert Sans";
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("assets/fonts/albert-sans-300-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Albert Sans";
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("assets/fonts/albert-sans-300-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Albert Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("assets/fonts/albert-sans-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Albert Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("assets/fonts/albert-sans-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Albert Sans";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("assets/fonts/albert-sans-500-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Albert Sans";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("assets/fonts/albert-sans-500-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Cormorant Garamond";
  font-style: italic;
  font-weight: 300;
  font-display: swap;
  src: url("assets/fonts/cormorant-garamond-300-italic-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Cormorant Garamond";
  font-style: italic;
  font-weight: 300;
  font-display: swap;
  src: url("assets/fonts/cormorant-garamond-300-italic-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Cormorant Garamond";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("assets/fonts/cormorant-garamond-400-italic-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Cormorant Garamond";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("assets/fonts/cormorant-garamond-400-italic-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("assets/fonts/cormorant-garamond-300-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("assets/fonts/cormorant-garamond-300-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("assets/fonts/cormorant-garamond-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("assets/fonts/cormorant-garamond-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("assets/fonts/cormorant-garamond-500-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("assets/fonts/cormorant-garamond-500-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("assets/fonts/ibm-plex-mono-300-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("assets/fonts/ibm-plex-mono-300-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("assets/fonts/ibm-plex-mono-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("assets/fonts/ibm-plex-mono-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}


:root {
  --noir: #0e0c09;
  --noir-2: #15120e;
  --ivory: #ece4d6;
  --ivory-dim: rgba(236, 228, 214, 0.52);   /* 0.50 measured 4.46:1 on noir — a hair under the 4.5 small-text line; 0.52 is 4.73:1 and indistinguishable */
  --ivory-faint: rgba(236, 228, 214, 0.16);
  --champagne: #c6a15b;
  --hairline: rgba(236, 228, 214, 0.13);
  --font-display: "Cormorant Garamond", Georgia, serif;
  --font-body: "Albert Sans", "Helvetica Neue", sans-serif;
  --font-mono: "IBM Plex Mono", monospace;
  --pad: clamp(22px, 5vw, 84px);
}

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

/* reserve the scrollbar gutter at all times so pinned (position:fixed) sections
   don't shift horizontally by the scrollbar width when they lock */
html { scrollbar-gutter: stable; }

/* site-wide ember field: fixed, behind all content, above the page background */
#global-particles {
  position: fixed; inset: 0; width: 100%; height: 100%;
  z-index: -1; pointer-events: none; display: block;
}

body {
  background: var(--noir);
  color: var(--ivory);
  font-family: var(--font-body);
  font-weight: 300;
  font-size: 16.5px;
  line-height: 1.65;
  /* Contains the testimonial carousel's 100vw breakout. `hidden` computes to
     `overflow: hidden auto`, which makes body a scroll container and silently
     kills `position: sticky` in every descendant — that is what broke the film
     on mobile. `clip` clips identically without creating one. The `hidden` line
     stays first as a fallback for browsers that don't support clip. */
  overflow-x: hidden;
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
}
body.is-loading { overflow: hidden; }

/* ---------- smooth scroll (Lenis) ---------- */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }

/* ---------- back-to-top (SVG ring + arrow; stroke glows warm on hover) ---------- */
.back-top {
  position: fixed;
  bottom: clamp(20px, 3vw, 36px);
  right: clamp(20px, 3vw, 36px);
  z-index: 90;
  display: grid; place-items: center;
  width: 56px; height: 56px; padding: 0;
  border: 0; border-radius: 50%;
  background: rgba(14, 12, 9, 0.5);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  cursor: pointer;
  opacity: 0;
  transform: translateY(16px) scale(0.92);
  pointer-events: none;
  transition: opacity 0.45s ease, transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.back-top.is-visible { opacity: 1; transform: none; pointer-events: auto; }
.back-top.is-visible:hover { transform: scale(1.05); }
.back-top-svg { width: 100%; height: 100%; display: block; overflow: visible; transition: filter 0.35s ease; }
.back-top-svg .bt-ring, .back-top-svg .bt-arrow, .back-top-svg .bt-disc { transition: stroke 0.3s ease, fill 0.3s ease, opacity 0.3s ease; }
/* warm stroke glow on hover — the same amber bloom as the lines elsewhere */
.back-top:hover .back-top-svg { filter: drop-shadow(0 0 5px rgba(255, 178, 86, 0.6)) drop-shadow(0 0 14px rgba(255, 140, 60, 0.32)); }
.back-top:hover .bt-ring { stroke: #f2d9a3; }
.back-top:hover .bt-arrow { fill: #f6e7c4; }
.back-top:hover .bt-disc { opacity: 0.4; }

::selection { background: var(--champagne); color: var(--noir); }
a { color: inherit; text-decoration: none; }
em { font-style: italic; }
.gold { color: var(--champagne); }

/* ---------- grain ---------- */
.grain {
  position: fixed; inset: -50%;
  width: 200%; height: 200%;
  pointer-events: none; z-index: 999;
  opacity: 0.06;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- preloader ---------- */
.loader {
  position: fixed; inset: 0; z-index: 2000;
  background: var(--noir);
  display: flex; align-items: center; justify-content: center;
}
.loader-mark {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(3rem, 8vw, 6rem);
  letter-spacing: 0.04em;
  display: flex; align-items: baseline;
}
.loader-letter { display: inline-block; opacity: 0; transform: translateY(24px); }
.loader-reg { font-size: 0.35em; color: var(--champagne); opacity: 0; margin-left: 4px; }
.loader-count {
  position: absolute; bottom: 36px; right: var(--pad);
  font-family: var(--font-mono);
  font-weight: 300;
  font-size: 0.85rem;
  letter-spacing: 0.2em;
  color: var(--ivory-dim);
}
.loader-line {
  position: absolute; bottom: 28px; left: var(--pad);
  width: clamp(120px, 24vw, 320px); height: 1px;
  background: var(--hairline);
}
.loader-line-fill {
  display: block; width: 100%; height: 100%;
  background: var(--champagne);
  transform: scaleX(0); transform-origin: left;
}

/* ---------- cursor ---------- */
.cursor {
  position: fixed; top: 0; left: 0;
  width: 28px; height: 28px;
  border: 1px solid rgba(198, 161, 91, 0.7);
  border-radius: 50%;
  pointer-events: none; z-index: 1500;
  transform: translate(-50%, -50%);
  transition: width .3s ease, height .3s ease, background-color .3s ease, opacity .3s ease;
}
.cursor.is-active { width: 56px; height: 56px; background: rgba(198, 161, 91, 0.08); }
.cursor.is-hidden { opacity: 0; }
@media (hover: none), (pointer: coarse) { .cursor { display: none; } }

/* ---------- soft cursor spotlight ---------- */
.spotlight {
  position: fixed; top: 0; left: 0;
  width: 540px; height: 540px;
  border-radius: 50%;
  pointer-events: none;
  z-index: 7;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle,
    rgba(216, 180, 112, 0.06) 0%,
    rgba(198, 161, 91, 0.028) 38%,
    rgba(198, 161, 91, 0) 70%);
  opacity: 0;
  mix-blend-mode: screen;
  will-change: transform, opacity;
}
@media (hover: none), (pointer: coarse) { .spotlight { display: none; } }

/* warm glow confined to strokes/lines and interactable elements, revealed by
   cursor proximity (JS sets --gx/--gy/--gi per element). A warm streak rides
   along the line near the cursor and bleeds a little light to each side.
   --pulse animates a subtle breathing intensity, multiplied into the opacity. */
@property --pulse { syntax: "<number>"; inherits: false; initial-value: 1; }
@keyframes glow-pulse { 0%, 100% { --pulse: 1; } 50% { --pulse: 0.78; } }
.glow-line, .glow-line-top, .glow-line-bottom, .glow-box, .glow-border { position: relative; }
.glow-line::after, .glow-line-top::after, .glow-line-bottom::after {
  content: ""; position: absolute; left: 0; right: 0; height: 130px;
  pointer-events: none; z-index: 2;
  opacity: calc(var(--gi, 0) * var(--pulse, 1));
  background:
    /* 1. the stroke itself lights up — hot near-white core tapering along the line */
    radial-gradient(330px 3px at var(--gx, -999px) 50%,
      rgba(255, 246, 220, 1) 0%, rgba(255, 178, 86, 0.72) 38%, rgba(255, 120, 50, 0) 80%),
    /* 2. hot core bloom — the perpendicular bleed, concentrated at the bright point */
    radial-gradient(80px 30px at var(--gx, -999px) 50%,
      rgba(255, 206, 120, 0.55) 0%, rgba(255, 140, 60, 0.16) 46%, transparent 72%),
    /* 3. soft wide warm haze */
    radial-gradient(200px 56px at var(--gx, -999px) 50%,
      rgba(255, 132, 56, 0.13) 0%, transparent 74%);
  /* fade the glow toward the line's ends so it never cuts off at the edge */
  -webkit-mask-image: linear-gradient(to right, transparent, #000 42px, #000 calc(100% - 42px), transparent);
          mask-image: linear-gradient(to right, transparent, #000 42px, #000 calc(100% - 42px), transparent);
  mix-blend-mode: screen;
}

/* card outer border glow — the perimeter lights up near the cursor */
.glow-border::before {
  content: ""; position: absolute; inset: 0;
  border-radius: inherit;
  border: 1.5px solid rgba(255, 196, 110, 0.95);
  box-shadow: 0 0 7px rgba(255, 165, 80, 0.55), 0 0 17px rgba(255, 120, 50, 0.22);
  pointer-events: none; z-index: 2;
  opacity: calc(var(--gi, 0) * var(--pulse, 1));
  /* reveal only the border arc near the cursor, brightest at the closest point */
  -webkit-mask-image: radial-gradient(180px circle at var(--gx, -999px) var(--gy, -999px),
    #000 0%, rgba(0, 0, 0, 0.55) 34%, transparent 70%);
          mask-image: radial-gradient(180px circle at var(--gx, -999px) var(--gy, -999px),
    #000 0%, rgba(0, 0, 0, 0.55) 34%, transparent 70%);
  mix-blend-mode: screen;
}
.glow-line::after { top: 50%; transform: translateY(-50%); }   /* line at element centre */
.glow-line-top::after { top: -65px; }                          /* line at top border */
.glow-line-bottom::after { bottom: -65px; }                    /* line at bottom border */
.glow-box::after {
  content: ""; position: absolute; inset: 0;
  pointer-events: none; z-index: 2; border-radius: inherit;
  opacity: calc(var(--gi, 0) * var(--pulse, 1));
  background: radial-gradient(circle at var(--gx, 50%) var(--gy, 50%),
    rgba(255, 208, 135, 0.40) 0%,
    rgba(255, 150, 70, 0.16) 45%,
    transparent 72%);
  mix-blend-mode: screen;
}
/* subtle breathing pulse while a glow is lit */
.glow-line::after, .glow-line-top::after, .glow-line-bottom::after,
.glow-box::after, .glow-border::before {
  animation: glow-pulse 2.6s ease-in-out infinite;
}
@media (hover: none), (pointer: coarse) {
  .glow-line::after, .glow-line-top::after, .glow-line-bottom::after,
  .glow-box::after, .glow-border::before { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .glow-line::after, .glow-line-top::after, .glow-line-bottom::after,
  .glow-box::after, .glow-border::before { animation: none; }
}

/* ---------- nav ---------- */
.nav2 {
  position: fixed; top: 0; left: 0; right: 0; z-index: 100;
  display: flex; align-items: center; justify-content: space-between;
  padding: 26px var(--pad);
  /* the darkening lives on .topveil below, not here — one gradient, and it can
     run past the nav's own box instead of stopping on its edge */
  transition: transform .5s cubic-bezier(.4, 0, .2, 1);
}
.nav2.is-hidden { transform: translateY(-110%); }

/* ---------- cinematic top veil ----------
   Sits under the nav (z 99 against its 100) and above the page. Two jobs:
   darken behind the links, and ramp a blur that is heavy at the very top and
   gone by the bottom edge.

   backdrop-filter only takes ONE radius, so a true gradient of blur has to be
   built by stacking. Each span blurs by a fixed amount and is masked to a band;
   the largest radius is confined to the thinnest band at the very top and, being
   last in the DOM, paints over the gentler ones. The masks overlap so the steps
   cross-fade into each other rather than banding.

   The darkening is on ::after, painted ABOVE the spans on purpose: if it sat on
   the parent the blur layers would sample it as part of their backdrop and
   re-blur a gradient for nothing. */
.topveil {
  position: fixed; top: 0; left: 0; right: 0; z-index: 99;
  height: clamp(130px, 17vh, 210px);
  pointer-events: none;
  transition: opacity .5s cubic-bezier(.4, 0, .2, 1);
}
/* no JS: the veil is the header's next sibling, so it simply follows its state */
.nav2.is-hidden + .topveil { opacity: 0; }
.topveil > span {
  position: absolute; left: 0; right: 0; top: 0; height: 100%;
  display: block;
}
/* The bands span the whole veil rather than crowding the first few pixels: an
   earlier split put the heavy blur in the top 30px, which the nav bar itself
   covers, so the ramp was invisible. Each layer now hands over to the next
   around the point the one above has faded out. */
.topveil > span:nth-child(1) {
  -webkit-backdrop-filter: blur(1px); backdrop-filter: blur(1px);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 72%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 72%, transparent 100%);
}
.topveil > span:nth-child(2) {
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 52%, transparent 74%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 52%, transparent 74%);
}
.topveil > span:nth-child(3) {
  -webkit-backdrop-filter: blur(7px); backdrop-filter: blur(7px);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 34%, transparent 54%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 34%, transparent 54%);
}
.topveil > span:nth-child(4) {
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 17%, transparent 35%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 17%, transparent 35%);
}
.topveil::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(to bottom,
    rgba(14, 12, 9, 0.82) 0%,
    rgba(14, 12, 9, 0.52) 34%,
    rgba(14, 12, 9, 0.18) 66%,
    rgba(14, 12, 9, 0) 100%);
}
.nav2-logo {
  font-family: var(--font-display);
  font-size: 1.55rem; font-weight: 500;
  letter-spacing: 0.03em;
}
.nav2-logo span { color: var(--champagne); font-size: 0.55em; vertical-align: super; }
.nav2-links { display: flex; gap: clamp(18px, 2.6vw, 40px); }
.nav2-links a {
  font-size: 0.72rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ivory-dim);
  transition: color .3s ease;
  position: relative;
  padding: 4px 0;
}
.nav2-links a::after {
  content: "";
  position: absolute; left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: var(--champagne);
  transform: scaleX(0); transform-origin: right;
  transition: transform .4s cubic-bezier(.6, 0, .2, 1);
}
.nav2-links a:hover { color: var(--ivory); }
.nav2-links a:hover::after { transform: scaleX(1); transform-origin: left; }
.nav2-cta {
  font-size: 0.72rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--champagne);
  border: 1px solid rgba(198, 161, 91, 0.45);
  border-radius: 99px;
  padding: 11px 26px;
  transition: background-color .35s ease, color .35s ease, border-color .35s ease;
}
.nav2-cta:hover { background: var(--champagne); color: var(--noir); border-color: var(--champagne); }

/* ---------- hero ---------- */
.hero2 {
  position: relative;
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
  padding: 140px var(--pad) 48px;
  overflow: hidden;
}
/* ---------- hero eclipse video (scroll-scrubbed) ---------- */
/* selector outranks the `.hero2 > *` relative rule below so the wrapper
   stays absolutely positioned (the old #dust-canvas won on id specificity) */
.hero2 > .hero2-media { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.hero2-canvas {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
}
/* totality loop video — fades in over the canvas at the end of the scrub */
.hero2-loop {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center;
  opacity: 0;
  transition: opacity 0.5s ease;
  pointer-events: none;
}
.hero2-loop.is-playing { opacity: 1; }
/* golden ember particles — sits above the eclipse + scrim, below the text */
.hero2 > .hero2-particles {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
}
/* scrim keeps the headline legible over the bright early (sun) frames:
   darken the left column (title) and the bottom (foot), leave the
   upper-right — where the sun sits — clearer */
.hero2-media::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(90deg, rgba(14,12,9,.85) 0%, rgba(14,12,9,.5) 42%, rgba(14,12,9,.12) 100%),
    linear-gradient(0deg, rgba(14,12,9,.92) 0%, rgba(14,12,9,.25) 38%, rgba(14,12,9,0) 70%);
}
/* ---------- window portal — Dan & Diana seen through the ring; the camera
   dives through it into the eclipse on scroll (driven by heroStory). Sits
   above the media/particles, below the headline. ---------- */
.hero2 > .hero2-window {
  position: absolute; inset: 0; overflow: hidden; pointer-events: none;
  transform-origin: 79% 35%;         /* push the zoom into the gap between their heads */
  will-change: transform, filter, opacity;
}
.hero2-window-img {
  position: absolute; inset: 0;
  background: url("assets/dan-diana-window.webp?v=2") no-repeat 50% 42% / cover;
  will-change: transform;
  animation: winBreathe 15s ease-in-out infinite;   /* subtle living drift */
}
@keyframes winBreathe {
  0%, 100% { transform: scale(1.02); }
  50%      { transform: scale(1.05) translate3d(-0.5%, -0.45%, 0); }
}
@media (prefers-reduced-motion: reduce) { .hero2-window-img { animation: none; } }

.hero2 > * { position: relative; }

.hero2-eyebrow {
  display: flex; align-items: center; gap: 16px;
  font-size: 0.72rem;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--ivory-dim);
  margin-bottom: clamp(28px, 4vh, 52px);
  opacity: 0;
}
.rule { display: inline-block; width: 52px; height: 1px; background: var(--champagne); }

.hero2-title {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(3.2rem, 9.4vw, 9rem);
  line-height: 0.98;
  letter-spacing: -0.015em;
}
/* extra bottom padding inside the reveal mask so Cormorant's long
   descenders (y, g) aren't clipped; negative margin re-tightens leading */
.hero2-title .h-line { display: block; overflow: hidden; padding-bottom: 0.14em; margin-bottom: -0.14em; }
.hero2-title .char { display: inline-block; }
.hero2-title em { color: var(--champagne); font-weight: 400; }
.h-dot { color: var(--champagne); }

.hero2-foot {
  margin-top: clamp(40px, 7vh, 80px);
  padding-top: 26px;
  border-top: 1px solid var(--hairline);
  display: flex; justify-content: space-between; align-items: flex-end; gap: 30px;
  opacity: 0;
}
.hero2-sub { color: var(--ivory-dim); font-size: 1rem; line-height: 1.7; }
.hero2-meta {
  display: flex; flex-direction: column; align-items: flex-end; gap: 14px;
  font-size: 0.7rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ivory-dim);
  white-space: nowrap;
}
.hero2-scroll { display: inline-flex; align-items: center; gap: 10px; color: var(--champagne); }
.hero2-scroll i {
  display: inline-block; width: 1px; height: 38px;
  background: var(--champagne);
  transform-origin: top;
  animation: drip 2s ease-in-out infinite;
}
@keyframes drip { 0% { transform: scaleY(0); } 55% { transform: scaleY(1); } 100% { transform: scaleY(0); transform-origin: bottom; } }

/* ---------- hero scroll-story beats ---------- */
.hero2-beats {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  text-align: center;
  pointer-events: none;
}
.beat {
  position: absolute;
  opacity: 0;
  max-width: 86%;
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(2rem, 5.4vw, 4.8rem);
  line-height: 1.15;
  letter-spacing: -0.01em;
}
.beat em { color: var(--champagne); }
.beat-zero { display: flex; flex-direction: column; align-items: center; gap: 18px; }
.beat-big {
  font-size: clamp(5rem, 16vw, 13rem);
  line-height: 0.9;
  color: var(--champagne);
  letter-spacing: 0.02em;
}
.beat-list {
  font-family: var(--font-body);
  font-size: 0.74rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--ivory-dim);
}

/* ---------- chapters (shared) ---------- */
.chapter {
  padding: clamp(110px, 14vw, 200px) var(--pad);
  max-width: 1500px;
  margin: 0 auto;
}
.chapter-head {
  display: flex; align-items: baseline; gap: 22px;
  padding-bottom: 20px;
  margin-bottom: clamp(48px, 6vw, 96px);
  border-bottom: 1px solid var(--hairline);
}
.chapter-num {
  font-family: var(--font-mono);
  font-weight: 300;
  font-size: 0.8rem;
  color: var(--champagne);
  letter-spacing: 0.1em;
}
.chapter-label {
  /* an h2 (each chapter's real heading — it fixes the h1→h3 skip the phase
     cards had), styled to stay the quiet mono label it always was */
  font-family: inherit; font-weight: inherit; margin: 0;
  font-size: 0.72rem;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--ivory-dim);
}

/* ---------- 01 manifesto ---------- */
.manifesto-text {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(1.7rem, 3.6vw, 3.3rem);
  line-height: 1.32;
  letter-spacing: 0.002em;
  max-width: 21em;
}
/* faint only once armed by JS as you approach — see the manifesto block in
   main.js. Unarmed (at load, without JS, with reduced motion) the words are
   plain ivory and simply readable. */
.manifesto-text.is-armed .m-word { color: var(--ivory-faint); }

/* ---------- 02 the subtraction ---------- */
.chain-pin {
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
  text-align: center;
}
.chain-intro, .chain-outro {
  font-size: 0.78rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--ivory-dim);
  line-height: 2;
}
.chain-outro { margin-top: 34px; opacity: 0; }
.chain-outro em {
  display: block;
  font-family: var(--font-display);
  font-size: 1.7rem;
  text-transform: none;
  letter-spacing: 0.01em;
  color: var(--champagne);
  margin-top: 10px;
}
.chain { margin-top: 38px; }
.chain-row {
  overflow: hidden;
  display: flex; flex-direction: column; align-items: center;
}
.chain-word {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(1.9rem, 4.6vw, 4rem);
  line-height: 1.25;
  white-space: nowrap;
}
.chain-row.keep .chain-word { color: var(--ivory); }
.chain-row.cut .chain-word { color: var(--ivory-dim); }
.chain-arrow {
  font-family: var(--font-display);
  font-size: 1.4rem;
  color: var(--ivory-faint);
  line-height: 1.6;
}
.chain-arrow.gold { color: var(--champagne); }

/* ---------- 03 the quiet process ---------- */
.thread-wrap { position: relative; max-width: 880px; margin: 0 auto; }
.thread {
  position: absolute; top: 0; bottom: 0; left: 8px;
  width: 1px;
  background: var(--hairline);
}
.thread::after {
  content: "";
  position: absolute; inset: 0;
  background: var(--champagne);
  transform: scaleY(var(--thread-p, 0));
  transform-origin: top;
}
.phase {
  position: relative;
  display: flex; gap: clamp(28px, 5vw, 72px);
  padding: clamp(36px, 5vw, 64px) 0 clamp(36px, 5vw, 64px) clamp(40px, 6vw, 90px);
}
.phase + .phase { border-top: 1px solid var(--hairline); }
.phase::before {
  content: "";
  position: absolute; left: 4px; top: clamp(48px, 6vw, 78px);
  width: 9px; height: 9px;
  border-radius: 50%;
  border: 1px solid var(--champagne);
  background: var(--noir);
}
.phase-num {
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(1.6rem, 2.6vw, 2.4rem);
  color: var(--champagne);
  min-width: 2.2em;
}
.phase h3 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.5rem, 2.6vw, 2.2rem);
  margin-bottom: 12px;
}
.phase p { color: var(--ivory-dim); max-width: 30em; }
.phase-tag {
  display: inline-block;
  margin-top: 18px;
  font-size: 0.68rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--champagne);
  border: 1px solid rgba(198, 161, 91, 0.35);
  border-radius: 99px;
  padding: 6px 16px;
}

/* ---------- 04 the same brief, twice ---------- */
.vs-intro { max-width: 80%; margin: 0 auto clamp(34px, 5vh, 68px); text-align: center; }
.vs-title {
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(2rem, 4.4vw, 3.8rem); line-height: 1.06;
}
.vs-title em { color: var(--champagne); font-weight: 400; }

/* ---- the film — one continuous shot, scrubbed by scroll ----
   The canvas paints a pre-decoded WebP frame sequence (same technique as the
   hero eclipse); the type above it is real HTML, so it stays crisp on retina,
   selectable and translatable, and can be rewritten without re-rendering a
   single frame. Every frame was composed with its left third near-black, so
   the copy sits in genuine darkness rather than under a heavy scrim. */
.film {
  position: relative;
  width: 100vw; margin-left: calc(50% - 50vw);   /* full-bleed out of .chapter's padding */
  margin-top: clamp(40px, 6vw, 90px);
  /* the pin fills the viewport; the film band sits centred inside it, so the
     21:9 framing is never cropped to fit a 16:9 window — it letterboxes into
     the page ground instead, which is the same near-black as the footage */
  height: 100svh; display: grid; align-content: center;
}
/* the poster is the film's OPENING frame, so it never spoils the reveal. It
   shows immediately and the canvas paints straight over it once the sequence
   has loaded — and it is the whole picture if JS or gsap never arrives. */
/* the band matches the footage exactly, so nothing is cropped and no dead
   letterbox is left over; only a short, wide window trims a little top and
   bottom, which the draw handles. 16:9 since the origami film replaced the
   2.33:1 footage — the band is taller, so max-height keeps it inside the pin. */
.film-viewport {
  position: relative; width: 100%; overflow: hidden;
  aspect-ratio: 16 / 9; max-height: 100svh;
}
/* the masked layer: footage + scrim, feathered top and bottom so the band
   dissolves into the page instead of ending on a hard horizontal edge */
.film-media {
  position: absolute; inset: 0;
  /* ?v= must match FILM_V in main.js — /assets/* is served immutable, so a
     re-cut poster under the same name would never reach returning visitors */
  background: url("assets/film/poster.jpg?v=2") center / 100% auto no-repeat;
  -webkit-mask-image: linear-gradient(180deg,
    rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.35) 3%, #000 11%,
    #000 89%, rgba(0, 0, 0, 0.35) 97%, rgba(0, 0, 0, 0) 100%);
  mask-image: linear-gradient(180deg,
    rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.35) 3%, #000 11%,
    #000 89%, rgba(0, 0, 0, 0.35) 97%, rgba(0, 0, 0, 0) 100%);
}
.film-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* a left-weighted scrim — insurance for the brightest frames only, never a slab */
.film-scrim {
  position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(90deg, rgba(14, 12, 9, 0.8) 0%, rgba(14, 12, 9, 0.46) 32%, rgba(14, 12, 9, 0) 60%),
    linear-gradient(180deg, rgba(14, 12, 9, 0.55) 0%, rgba(14, 12, 9, 0) 20%, rgba(14, 12, 9, 0) 72%, rgba(14, 12, 9, 0.6) 100%);
}

/* The beats stack in one place and cross-fade; JS drives opacity + y.
   Set low in the frame rather than centred: the origami film pushes into a
   close-up around the middle of the sequence and fills the centre-left with lit
   paper, which dropped the copy to ~2.6:1 against its backdrop. The bottom of
   the frame stays in shadow throughout, and the scrim is heaviest there. */
.film-beats {
  position: absolute; inset: 0; z-index: 2;
  display: grid; align-content: end;
  padding: 0 var(--pad) clamp(72px, 12svh, 132px);
  max-width: 1500px; margin: 0 auto;
}
.film-beat {
  grid-area: 1 / 1;                              /* all beats share one cell */
  max-width: min(46vw, 640px); opacity: 0; will-change: transform, opacity;
}
.film-beat-label {
  display: block; font-family: var(--font-mono); font-size: 0.64rem;
  letter-spacing: 0.26em; text-transform: uppercase; color: var(--champagne);
  margin-bottom: clamp(14px, 1.8vh, 22px);
}
.film-beat-line {
  display: block; font-family: var(--font-display); font-weight: 300;
  font-size: clamp(2rem, 4.2vw, 3.6rem); line-height: 1.12; color: var(--ivory);
}
.film-beat-line em { font-style: italic; color: var(--champagne); }

/* progress hairline along the bottom of the frame — the only chrome */
.film-rail {
  position: absolute; left: var(--pad); right: var(--pad); bottom: clamp(26px, 4svh, 48px);
  height: 1px; background: var(--ivory-faint); z-index: 2; overflow: hidden;
}
.film-rail-fill {
  display: block; width: 100%; height: 100%; background: var(--champagne);
  transform: scaleX(0); transform-origin: 0 50%;
}

.film-fallback {
  padding: clamp(40px, 8vw, 90px) var(--pad);
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(1.5rem, 3vw, 2.4rem); line-height: 1.25; color: var(--ivory);
}

/* no-pin fallback: the film holds a single frame and the beats stack beneath
   it, in flow. Three ways in — JS adds .film-static on narrow screens; gsap
   failing to load sets .gsap-failed; and reduced motion returns out of main.js
   before the film ever initialises, so CSS has to carry that case alone. */
.film-static,
.gsap-failed .film { height: auto; display: block; }
/* the viewport stops being a clipped fixed-aspect box — otherwise the stacked
   beats below the frame get cut off by its overflow. The media layer takes the
   aspect ratio instead, and the beats simply follow it in flow. */
.film-static .film-viewport,
.gsap-failed .film-viewport {
  height: auto; aspect-ratio: auto; max-height: none; overflow: visible;
}
/* the frame sticks and the beats travel over it — the mobile answer to the
   desktop pin. gsap-failed keeps the old plain stacking, since without JS the
   frame never advances and a sticky still would just hang there. */
/* ---------- phones: four 9:16 stills travelling sideways ----------
   Built in JS (see filmSection in main.js). The section pins for one screen and
   the track slides one image per screen-height of scroll; each line sits in the
   dark bottom-left area the stills were composed to leave empty. */
/* Full-bleed in PX, overriding .film's `width: 100vw; margin-left: calc(50% - 50vw)`.
   That trick is fine in normal flow but not here: ScrollTrigger pins this section
   by switching it to position:fixed, and a percentage margin then resolves against
   the viewport instead of .chapter's content box — so `50% - 50vw` collapses to 0
   and the whole section jumps right by exactly one --pad, which is the strip that
   kept showing down the left edge. --pad resolves to px, so this cannot move. */
.film-m {
  height: 100svh; overflow: hidden;
  width: calc(100% + 2 * var(--pad));
  margin-left: calc(-1 * var(--pad));
}
/* Geometry is entirely proportional: the track is one screen wide, each slide is
   one full track, and the JS steps by -100% of the track per image. Nothing is
   measured, so there is no width to get wrong and no drift to accumulate — it is
   correct at any phone size, and stays correct through a rotation or a resize
   without recalculating anything. */
.film-m-track { display: flex; width: 100%; height: 100svh; will-change: transform; }
.film-m-slide {
  flex: 0 0 100%;      /* shrink must be off or four slides squeeze into one screen */
  position: relative; height: 100svh; overflow: hidden;
  background: var(--noir);
}
.film-m-slide img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* a soft floor so the copy holds even if a still runs bright at the bottom */
.film-m-slide::after {
  content: ""; position: absolute; inset: auto 0 0 0; height: 52%;
  background: linear-gradient(180deg, rgba(14,12,9,0) 0%, rgba(14,12,9,0.72) 62%, rgba(14,12,9,0.94) 100%);
}
.film-m-cap {
  position: absolute; z-index: 2;
  left: var(--pad); right: var(--pad); bottom: clamp(56px, 11svh, 96px);
  max-width: 78%;
}
.film-m-cap .film-beat {
  position: static; opacity: 1; transform: none; max-width: none;
  display: block; min-height: 0; padding: 0;
}
.film-m-cap .film-beat-label { margin-bottom: 12px; }
.film-m-cap .film-beat-line { font-size: clamp(1.6rem, 8vw, 2.3rem); line-height: 1.14; }

/* The frame holds as a cinematic band near the top and the beats travel
   underneath it, disappearing behind its edge — a 2.33:1 still can never fill a
   portrait screen, so it is framed as a deliberate letterbox rather than a
   picture the text has to fight for space with. */
.film-static .film-media {
  /* `inset: auto` must come BEFORE `top` — it is shorthand for all four sides,
     so setting it afterwards silently resets top to auto and sticky does nothing */
  inset: auto;
  position: sticky; top: 11vh; z-index: 2;
  width: 100%; aspect-ratio: 16 / 9;
  background: var(--noir);
  box-shadow: 0 26px 60px rgba(0, 0, 0, 0.55);
}
/* hairline top and bottom so the band reads as a frame, not a floating crop */
.film-static .film-media::before,
.film-static .film-media::after {
  content: ""; position: absolute; left: 0; right: 0; height: 1px;
  background: var(--hairline); z-index: 3;
}
.film-static .film-media::before { top: 0; }
.film-static .film-media::after { bottom: 0; }
.gsap-failed .film-media {
  position: relative; inset: auto; width: 100%; aspect-ratio: 16 / 9;
}
.film-static .film-beats {
  position: relative; inset: auto; z-index: 1;   /* below the band, so copy slides behind it */
  display: block; padding: 6vh var(--pad) 0;
}
.film-static .film-beat {
  max-width: none; min-height: 44vh;
  display: flex; flex-direction: column; justify-content: center;
}
.film-static .film-beat:last-child { min-height: 32vh; }
.gsap-failed .film-beats {
  position: relative; inset: auto; display: flex; flex-direction: column;
  gap: clamp(30px, 6vh, 56px); padding: clamp(38px, 8vh, 70px) var(--pad) 0;
}
.gsap-failed .film-beat { max-width: none; opacity: 1; transform: none; }
.film-static .film-rail,
.gsap-failed .film-rail { display: none; }
/* text now passes over the frame, so the scrim has to carry legibility: dark at
   the edges, and heavier at the foot where the next beat rides up over it */
.film-static .film-scrim {
  background:
    linear-gradient(180deg, rgba(14,12,9,0.72) 0%, rgba(14,12,9,0.18) 26%,
                            rgba(14,12,9,0.30) 62%, rgba(14,12,9,0.88) 100%);
}
.gsap-failed .film-scrim {
  background: linear-gradient(180deg, rgba(14, 12, 9, 0.4) 0%, rgba(14, 12, 9, 0) 30%, rgba(14, 12, 9, 0.55) 100%);
}

@media (prefers-reduced-motion: reduce) {
  .film { height: auto; display: block; }
  .film-viewport { height: auto; aspect-ratio: auto; max-height: none; overflow: visible; }
  .film-media { position: relative; inset: auto; width: 100%; aspect-ratio: 16 / 9; }
  .film-beats {
    position: relative; inset: auto; display: flex; flex-direction: column;
    gap: clamp(30px, 6vh, 56px); padding: clamp(38px, 8vh, 70px) var(--pad) 0;
  }
  .film-beat { max-width: none; opacity: 1; transform: none; }
  .film-rail { display: none; }
  .film-scrim {
    background: linear-gradient(180deg, rgba(14, 12, 9, 0.4) 0%, rgba(14, 12, 9, 0) 30%, rgba(14, 12, 9, 0.55) 100%);
  }
}

/* coda — the in-house objection, answered quietly */
/* ============================================================
   AREAS OF SPECIALIZATION — scroll-driven ticker + flip-card showcase
   (PIN 2 of section 04; replaces the old finished-work deck)
   ============================================================ */
.spec {
  position: relative; width: 100%; min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
  padding: 12svh 0 9svh; overflow: visible;   /* let the ticker bleed past the content column */
}
.spec-head { position: relative; z-index: 3; text-align: center; margin-bottom: calc(1.5vh + 40px); }
.spec-label {
  display: inline-block; font-family: var(--font-mono); font-size: 0.7rem;
  letter-spacing: 0.28em; text-transform: uppercase; color: var(--champagne);
  opacity: 0.82; margin-bottom: 16px;
}
.spec-title {
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(2.1rem, 5vw, 4.4rem); line-height: 1.02;
  letter-spacing: -0.015em; color: var(--ivory);
}
.spec-ell { color: var(--champagne); }

.spec-stage { position: relative; z-index: 1; flex: 1 1 auto; min-height: 50svh; perspective: 1600px; }
.spec-slide {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: clamp(18px, 3vh, 38px);
  /* opacity + vertical offset are driven by GSAP (directional handoff, see main.js);
     no CSS transition here or the two would fight over the same properties */
  opacity: 0; pointer-events: none; will-change: transform, opacity;
}
.spec-slide.is-active { pointer-events: auto; }

/* endless right-to-left marquee of the category name, full-bleed to the browser
   edges (100vw, centred on the viewport regardless of the content column) */
.spec-ticker {
  position: absolute; left: 50%; width: 100vw; top: 46%; transform: translate(-50%, -50%);
  display: flex; overflow: hidden;
  z-index: 2;                 /* rides in front of the image — the name is the message */
  pointer-events: none;
}
.spec-ticker-track { display: flex; flex: none; will-change: transform; animation: specMarquee 90s linear infinite; }
.spec-slide:not(.is-active) .spec-ticker-track { animation-play-state: paused; }
.spec-ticker-seg {
  flex: none; display: flex; align-items: center; white-space: nowrap;
  font-family: var(--font-display); font-weight: 300; font-style: italic;
  font-size: clamp(5.5rem, 15vw, 15rem); line-height: 1; letter-spacing: -0.01em;
  color: var(--ivory);
  /* soft warm-dark halo: invisible against the noir background, but keeps the
     letterforms intact where they cross the lit part of the image behind */
  text-shadow: 0 0 18px rgba(14, 12, 9, 0.62), 0 0 52px rgba(14, 12, 9, 0.45);
}
.spec-w { flex: none; }
.spec-sep-wrap { flex: none; display: inline-flex; align-items: center; }
.spec-sep { flex: none; width: 0.46em; height: 0.46em; margin: 0 0.42em; position: relative; top: 0.03em; }
@keyframes specMarquee { to { transform: translateX(-50%); } }

/* the fluted-glass frame holding the category image — enters with a 3D flip:
   it swings up from lying flat and settles level (the ticker + pills cross-fade
   around it via the slide's opacity) */
.spec-card {
  position: relative; z-index: 1; width: min(50vw, 560px); aspect-ratio: 4 / 3;
  border-radius: 16px; overflow: hidden; transform-origin: 50% 50%;
  transform: perspective(1600px) rotateX(84deg) translateY(6%); opacity: 0;
  transition: transform 1.0s cubic-bezier(0.2, 0.7, 0.2, 1), opacity 0.6s ease;
  border: 1px solid rgba(236, 228, 214, 0.14);
  box-shadow: 0 50px 130px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(236, 228, 214, 0.12);
}
.spec-slide.is-active .spec-card {
  transform: perspective(1600px) rotateX(0deg) translateY(0); opacity: 1;
  box-shadow: 0 50px 130px rgba(0, 0, 0, 0.6), 0 0 90px rgba(198, 161, 91, 0.10), inset 0 1px 0 rgba(236, 228, 214, 0.14);
}
.spec-card img { width: 100%; height: 100%; object-fit: cover; display: block; }
.spec-card::after {                          /* fluted-glass sheen */
  content: ""; position: absolute; inset: 0; pointer-events: none; border-radius: inherit;
  background: repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.05) 0 2px, rgba(255, 255, 255, 0) 2px 9px);
  mix-blend-mode: overlay; opacity: 0.5;
}

/* sub-service pills below the card */
.spec-pills {
  position: relative; z-index: 3; list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; max-width: 620px;
}
.spec-pills li {
  font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ivory-dim);
  border: 1px solid var(--hairline); border-radius: 99px;
  padding: 9px 17px; background: rgba(14, 12, 9, 0.55);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  transition: color 0.3s ease, border-color 0.3s ease;
}
.spec-pills li:hover { color: var(--ivory); border-color: rgba(198, 161, 91, 0.5); }
.spec-cap { display: none; }

/* the four disciplines: one row on desktop, a horizontal rail on narrow screens.
   No visible scrollbar in either case — on mobile the fourth card peeking past
   the edge is the affordance, plus the hint below. */
/* no overflow clip here — a tilted card lifts out of its cell and would get
   sheared off. The mobile rail re-adds it, where it's needed to stop the
   full-bleed track spilling into page-level horizontal scroll. */
.spec-static { min-height: auto; padding: 7svh 0; }
.spec-static .spec-stage {
  perspective: none; min-height: 0;
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: clamp(16px, 2vw, 34px); align-items: start;
}
.spec-static .spec-slide {
  position: relative; inset: auto; opacity: 1; pointer-events: auto;
  flex-direction: column; align-items: stretch; justify-content: flex-start;
  gap: clamp(12px, 1.5vh, 18px); margin-bottom: 0;
}
.spec-static .spec-ticker { display: none; }
.spec-static .spec-card {
  transform: none; opacity: 1; width: 100%; max-width: none; aspect-ratio: 9 / 16;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
}
.spec-static .spec-card img { -webkit-user-drag: none; user-select: none; }
.spec-static .spec-cap {
  display: block; text-align: center; font-family: var(--font-display);
  font-weight: 300; font-size: clamp(1.15rem, 1.6vw, 1.5rem); color: var(--ivory);
}
.spec-static .spec-pills { justify-content: center; gap: 7px; max-width: none; }
.spec-static .spec-pills li { font-size: 0.58rem; padding: 6px 11px; letter-spacing: 0.08em; }

/* hover: the card tilts toward the cursor and a specular sweep tracks it.
   ::after is already the fluted-glass sheen, so the glare lives on ::before,
   lifted above the image with z-index. Fine pointers only — a tilt that never
   resets is worse than no tilt at all on touch. */
@media (hover: hover) and (pointer: fine) {
  /* a short perspective makes the rotation read — at 900px a 7° tilt on a card
     this size is nearly invisible */
  .spec-static .spec-slide { perspective: 620px; }
  /* NOTE the selector weight: `.spec-slide.is-active .spec-card` above is three
     classes and pins the card to a flat `perspective(1600px) rotateX(0deg)`.
     Anything lighter than that here is silently ignored — the tilt applies but
     the computed matrix stays identity. */
  .spec-static .spec-slide.is-active .spec-card {
    --rx: 0deg; --ry: 0deg; --mx: 50%; --my: 50%; --lift: 0;
    transform: rotateX(var(--rx)) rotateY(var(--ry))
               translateZ(calc(var(--lift) * 34px))
               scale(calc(1 + var(--lift) * 0.015));
    transition: transform 0.55s cubic-bezier(0.2, 0.7, 0.2, 1),
                box-shadow 0.55s ease, border-color 0.55s ease;
  }
  /* while the cursor is actually over the card, track it almost instantly —
     the long ease is for settling back on the way out */
  .spec-static .spec-slide.is-active .spec-card.is-tilting {
    transition: transform 0.1s linear, box-shadow 0.3s ease, border-color 0.3s ease;
  }
  .spec-static .spec-slide.is-active .spec-card:hover {
    --lift: 1;
    border-color: rgba(198, 161, 91, 0.42);
    box-shadow: 0 44px 96px rgba(0, 0, 0, 0.62),
                0 0 70px rgba(198, 161, 91, 0.14),
                inset 0 1px 0 rgba(236, 228, 214, 0.16);
  }
  /* the tilted card has to be able to break its cell, and sit over its neighbours */
  .spec-static .spec-slide:hover { z-index: 6; }

  /* the glare is a real layer, not ::before — the stroke-light system owns
     ::before on anything registered as .glow-border. Overlay blend modulates
     the image's own mid-tones instead of washing grey over it; the mockups
     carry enough bright ivory and screen glow for that to read richly. */
  .spec-glare {
    position: absolute; inset: 0; z-index: 1; pointer-events: none;
    border-radius: inherit; mix-blend-mode: overlay;
    opacity: 0; transition: opacity 0.4s ease;
    background:
      radial-gradient(46% 38% at var(--mx) var(--my),
        rgba(255, 238, 200, 0.9), rgba(198, 161, 91, 0.38) 42%, transparent 72%),
      linear-gradient(100deg,
        transparent calc(var(--mx) - 26%),
        rgba(255, 232, 186, 0.5) var(--mx),
        transparent calc(var(--mx) + 26%));
  }
  .spec-static .spec-card:hover .spec-glare { opacity: 1; }

  /* a tight color-dodge core rides above the overlay. Overlay takes its result
     from the backdrop, so on near-black it barely lifts — dodge is what makes a
     specular read as light instead of a grey film, and it's exactly what the
     flat screen version was missing. Small and low-alpha so it blooms rather
     than flares. */
  .spec-glare-core {
    position: absolute; inset: 0; z-index: 1; pointer-events: none;
    border-radius: inherit; mix-blend-mode: color-dodge;
    opacity: 0; transition: opacity 0.4s ease;
    background: radial-gradient(28% 22% at var(--mx) var(--my),
      rgba(154, 119, 64, 0.6), rgba(98, 76, 41, 0.2) 44%, transparent 74%);
  }
  .spec-static .spec-card:hover .spec-glare-core { opacity: 1; }

  /* embers thrown off the hotspot, drifting outward and up. Spawned by JS while
     the cursor is over the card, clipped to it by the card's own overflow. */
  .spec-sparks {
    position: absolute; inset: 0; z-index: 3; pointer-events: none;
    border-radius: inherit; overflow: hidden;
  }
  .spec-spark {
    position: absolute; display: block; border-radius: 50%;
    background: radial-gradient(circle,
      rgba(255, 248, 232, 1), rgba(232, 196, 124, 0.85) 38%,
      rgba(198, 161, 91, 0.4) 62%, transparent 78%);
    box-shadow: 0 0 10px rgba(232, 196, 124, 0.8), 0 0 22px rgba(198, 161, 91, 0.4);
    mix-blend-mode: screen; will-change: transform, opacity;
  }
}

.spec-hint { display: none; }
.spec-hint i { font-style: normal; color: var(--champagne); margin-left: 8px; }

@media (max-width: 860px) {
  .spec-static { overflow: hidden; }
  .spec-static .spec-stage {
    display: flex; align-items: flex-start;
    width: 100vw; margin-left: calc(50% - 50vw);
    padding: 4px var(--pad) 6px;
    overflow-x: auto; overflow-y: hidden;
    scroll-snap-type: x proximity; overscroll-behavior-x: contain;
    cursor: grab;
    scrollbar-width: none; -ms-overflow-style: none;    /* the peeking card is the cue */
  }
  .spec-static .spec-stage::-webkit-scrollbar { display: none; width: 0; height: 0; }
  .spec-static .spec-stage.is-dragging { cursor: grabbing; scroll-snap-type: none; }
  .spec-static .spec-slide { flex: 0 0 min(70vw, 320px); scroll-snap-align: center; }
  .spec-hint {
    display: block; text-align: center; margin-top: clamp(14px, 2vh, 20px);
    font-family: var(--font-mono); font-size: 0.6rem; letter-spacing: 0.24em;
    text-transform: uppercase; color: var(--ivory-dim);
  }
}

.vs-coda {
  margin-top: clamp(56px, 9vw, 110px);
  padding-top: clamp(26px, 4vw, 44px);
  border-top: 1px solid var(--hairline);
  max-width: 72ch;
}
.vs-coda-label {
  font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.24em;
  text-transform: uppercase; color: var(--champagne);
}
.vs-coda-line {
  margin-top: 12px; font-family: var(--font-display); font-weight: 300;
  font-size: clamp(1.25rem, 2.2vw, 1.8rem); line-height: 1.35; color: var(--ivory);
}
.vs-coda-line em { font-style: italic; color: var(--champagne); }
.vs-coda-block + .vs-coda-block { margin-top: clamp(26px, 4vw, 42px); }

@media (max-width: 860px) {
  .film-beat-line { font-size: clamp(1.75rem, 7vw, 2.6rem); }
}

/* ---------- 05 studio ---------- */
/* ---------- studio video (ping-pong loop, blended into the page) ---------- */
/* tight gap above the video (overrides the chapter-head's larger margin) */
.studio .chapter-head { margin-bottom: 24px; }
.studio-media {
  margin: 0 0 24px;
  /* wider-than-16:9 frame so object-fit:cover crops the empty top/bottom
     bands and shows just the letter scene (≈ the middle 75% of the source) */
  aspect-ratio: 1280 / 550;
}
/* screen blend drops the footage's true-black background into the page bg
   (black → backdrop), so the lit scene melds in with no visible frame */
.studio-video {
  display: block; width: 100%; height: 100%;
  object-fit: cover;
  object-position: center;
  mix-blend-mode: screen;
  /* feather all four edges so the footage's light bleed fades into the
     page instead of stopping at a hard rectangular edge */
  --feather-x: 7%;
  --feather-y: 10%;
  -webkit-mask-image:
    linear-gradient(to right, transparent, #000 var(--feather-x), #000 calc(100% - var(--feather-x)), transparent),
    linear-gradient(to bottom, transparent, #000 var(--feather-y), #000 calc(100% - var(--feather-y)), transparent);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right, transparent, #000 var(--feather-x), #000 calc(100% - var(--feather-x)), transparent),
    linear-gradient(to bottom, transparent, #000 var(--feather-y), #000 calc(100% - var(--feather-y)), transparent);
  mask-composite: intersect;
}

.studio-grid {
  display: grid;
  grid-template-columns: 1.2fr 1fr;
  gap: clamp(32px, 6vw, 110px);
  align-items: start;
}
.studio-title {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(2.4rem, 5vw, 4.6rem);
  line-height: 1.08;
}
.studio-title em { color: var(--champagne); }
.studio-copy p { color: var(--ivory-dim); margin-bottom: 22px; }
.studio-copy em { color: var(--ivory); }
.studio-stats {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 24px;
  margin-top: clamp(60px, 8vw, 110px);
  padding-top: 34px;
  border-top: 1px solid var(--hairline);
}
.s-num, .s-suffix {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(2.6rem, 4.6vw, 4.4rem);
  line-height: 1;
}
.s-suffix, .s-infinity { color: var(--champagne); }
.s-label {
  display: block;
  margin-top: 12px;
  font-size: 0.68rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ivory-dim);
}

/* ---------- 06 testimonials (carousel) ---------- */
/* preface — frames the count as deliberate before the reader counts, and
   states the permission/discretion policy. Sits closer to the cards than to
   the chapter head so it reads as attached to them. */
.testi-note {
  max-width: 66ch;
  margin-bottom: clamp(38px, 4.6vw, 64px);
}
.testi-note-label {
  display: block;
  font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.24em;
  text-transform: uppercase; color: var(--champagne);
}
.testi-note-line {
  margin-top: 14px;
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(1.08rem, 1.85vw, 1.5rem); line-height: 1.5;
  color: var(--ivory-dim);
}
.testi-note-line em { font-style: italic; color: var(--champagne); }

.testi-carousel { position: relative; }
.testi-viewport {
  /* break out to full viewport width so the peeking side cards bleed to the
     screen edges; the active card (--card-w, set in JS) matches the content column */
  width: 100vw;
  margin-left: 50%;
  transform: translateX(-50%);
  overflow: hidden;
  /* fade the peeking side cards into the page, like the reference */
  -webkit-mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
          mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
}
.testi-track {
  display: flex;
  gap: clamp(20px, 3vw, 40px);
  will-change: transform;
}
/* pan-y hands vertical gestures to the page (Lenis still scrolls normally) but
   keeps horizontal ones for the drag handler. Without this the browser claims
   the whole gesture as a scroll and the swipe never engages on touch. */
.testi-viewport { touch-action: pan-y; }
.testi-card { cursor: grab; }
.testi-card:active { cursor: grabbing; }
.testi-card {
  flex: 0 0 var(--card-w, min(780px, 84vw));
  background: var(--noir-2);
  border: 1px solid var(--hairline);
  border-radius: 6px;
  padding: clamp(26px, 3.4vw, 50px);
  opacity: 0.3;
  transform: scale(0.955);
  transition: opacity 0.55s ease, transform 0.55s ease, border-color 0.55s ease;
}
.testi-card.is-active { opacity: 1; transform: scale(1); border-color: var(--ivory-faint); }
/* during the infinite-loop snap, kill card transitions so there's no fade dip */
.testi-track.testi-noanim .testi-card { transition: none; }
.testi-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 18px;
  padding-bottom: clamp(22px, 3vw, 36px);
  margin-bottom: clamp(22px, 3vw, 36px);
  border-bottom: 1px solid var(--hairline);
}
.testi-brand {
  display: inline-flex; align-items: center; gap: 11px;
  font-family: var(--font-display); font-weight: 400;
  font-size: 1.15rem; color: var(--ivory);
}
.testi-glyph { color: var(--champagne); font-size: 0.62em; }
.testi-cat {
  font-family: var(--font-mono); font-size: 0.6rem;
  letter-spacing: 0.22em; text-transform: uppercase; color: var(--ivory-dim);
  white-space: nowrap;
}
.testi-body {
  display: grid; grid-template-columns: 1.7fr 0.85fr;
  gap: clamp(26px, 4vw, 58px); align-items: center;
}
/* full-length card: the quote leads, and the work photo becomes a tall rail
   that matches the text height rather than a fixed-ratio thumbnail */
.testi-card--full .testi-body { grid-template-columns: 1.62fr 0.78fr; align-items: stretch; }
.testi-card--full .testi-text { display: flex; flex-direction: column; }
.testi-card--full .testi-author { margin-top: auto; }
.testi-quote-mark {
  display: block;
  font-family: var(--font-display); color: var(--champagne);
  font-size: 3.4rem; line-height: 0.4; height: 0.5em;
}
.testi-headline {
  font-family: var(--font-display); font-weight: 400; font-style: italic;
  font-size: clamp(1.5rem, 2.6vw, 2.3rem); line-height: 1.12;
  color: var(--ivory); margin: 14px 0 16px;
}
/* works for both shapes: a single <p class="testi-quote"> (short cards) and a
   <div class="testi-quote"> wrapping several <p> (full-length testimonials) */
.testi-quote, .testi-quote p {
  font-size: 1.02rem; line-height: 1.72; color: var(--ivory-dim);
  max-width: 46ch;
}
.testi-quote { margin-bottom: clamp(22px, 3vw, 32px); }
.testi-quote p + p { margin-top: 0.9em; }
/* the adjacent-sibling rule above cannot reach across the .testi-rest wrapper,
   so the first revealed paragraph would butt straight onto the last visible one.
   Safe to give it a margin: .testi-rest is overflow:hidden, so this cannot
   collapse out through the box and skew the height GSAP animates to. */
.testi-quote .testi-rest > p:first-child { margin-top: 0.9em; }
/* a full testimonial is a lot of type — ease the size and let the column widen */
.testi-card--full .testi-quote p { font-size: 0.97rem; line-height: 1.68; max-width: 52ch; }
.testi-card--full .testi-headline { font-size: clamp(1.35rem, 2.1vw, 1.85rem); margin-bottom: 20px; }
.testi-author {
  font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ivory);
  display: flex; flex-direction: column; gap: 5px;
}
.testi-author span { color: var(--ivory-dim); letter-spacing: 0.18em; }
.testi-emblem {
  aspect-ratio: 3 / 4;
  border: 1px solid var(--hairline); border-radius: 4px;
  background:
    radial-gradient(130% 110% at 50% 8%, rgba(198, 161, 91, 0.12), transparent 58%),
    var(--noir);
  display: grid; place-items: center;
}
.testi-initials {
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(3rem, 6vw, 5rem); color: var(--champagne); letter-spacing: 0.02em;
}
/* emblem carrying a real photograph of the work rather than initials —
   2:3 to match how the work images are shot */
.testi-emblem--shot {
  aspect-ratio: 2 / 3; overflow: hidden; background: var(--noir);
  border-color: rgba(236, 228, 214, 0.18);
}
.testi-emblem--shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* inside a full card it stretches to the quote's height instead of a fixed ratio */
.testi-card--full .testi-emblem--shot {
  aspect-ratio: auto; align-self: stretch; min-height: 360px;
}
.testi-quote em { font-style: italic; color: var(--ivory); }

/* ---------- "more" toggle for long quotes ----------
   Reusable: any .testi-quote containing a .testi-rest gets the control. The
   collapsed state is applied by JS, so without JS (or if gsap fails) the whole
   quote is simply visible — a client's words should never be hidden by a script
   that did not load. */
.testi-rest { overflow: hidden; }
.testi-card.is-collapsed .testi-rest { height: 0; }
.testi-more { display: none; }
.testi-card.has-more .testi-more {
  display: inline-flex; align-items: center; gap: 8px;
  /* 44px tap target. The padding does the work and the reduced margin cancels
     it, so the control sits exactly where it looks like it sits. */
  margin-top: 4px; padding: 16px 0; border: 0; background: none; cursor: pointer;
  font-family: var(--font-mono); font-weight: 300;
  font-size: 0.62rem; letter-spacing: 0.24em; text-transform: uppercase;
  color: var(--champagne);
  transition: opacity .3s ease;
}
.testi-card.has-more .testi-more:hover { opacity: 0.7; }
.testi-more-i {
  width: 11px; height: auto; display: block;
  transition: transform .4s cubic-bezier(.4, 0, .2, 1);
}
.testi-card:not(.is-collapsed) .testi-more-i { transform: rotate(180deg); }
/* No fade-out over the collapsed edge. The cut falls on a paragraph break, not
   mid-sentence, so there is nothing to fade into — a gradient there only dims
   text that is fully visible and readable. The chevron carries the affordance. */
.testi-trans {
  font-style: normal; font-size: 0.58rem; letter-spacing: 0.14em;
  color: rgba(236, 228, 214, 0.34);
}
.testi-nav {
  display: flex; align-items: center; justify-content: center; gap: 22px;
  margin-top: clamp(34px, 5vh, 58px);
}
.testi-arrow {
  width: 54px; height: 54px; border-radius: 50%;
  border: 1px solid var(--hairline); background: transparent;
  color: var(--ivory); font-family: var(--font-display); font-size: 1.5rem; line-height: 1;
  cursor: pointer; display: grid; place-items: center;
  transition: border-color 0.3s ease, color 0.3s ease, opacity 0.3s ease;
}
.testi-arrow:hover { border-color: var(--champagne); color: var(--champagne); }
.testi-arrow:disabled { opacity: 0.28; cursor: default; border-color: var(--hairline); color: var(--ivory); }
.testi-count {
  font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.24em;
  color: var(--ivory-dim); min-width: 70px; text-align: center;
}
.testi-i { color: var(--champagne); }
@media (max-width: 720px) {
  .testi-body { grid-template-columns: 1fr; gap: 24px; }
  .testi-emblem { display: none; }
  /* the decorative initials emblem goes, but a real shot of the work stays —
     it is the proof, and this is the screen most people will see it on */
  .testi-emblem--shot {
    display: block; width: 100%; max-width: none;
    margin-inline: 0; order: -1;
  }
  /* the full-card desktop rules are more specific than the plain .testi-body
     rule above, so they must be undone explicitly here or the card stays in
     two columns and the photo stretches to the full text height */
  .testi-card--full .testi-body { grid-template-columns: 1fr; align-items: stretch; }
  .testi-card--full .testi-emblem--shot {
    aspect-ratio: 2 / 3; align-self: center; min-height: 0;
  }
  .testi-card--full .testi-quote p { max-width: none; }
  /* the category is nowrap by default; on a narrow card a long label sets the
     card's min-content width and shoves it off-centre. Let it wrap here. */
  /* stack the header so neither the name nor the label has to squeeze, and give
     the client's name real presence — it is the credential doing the work */
  .testi-top { flex-direction: column; align-items: flex-start; gap: 10px; }
  .testi-brand { font-size: 1.5rem; white-space: nowrap; }
  .testi-cat { white-space: normal; text-align: left; }
  .testi-quote { max-width: none; }
}

/* ---------- 07 guardians ---------- */
.guardians-intro {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(1.5rem, 2.8vw, 2.4rem);
  line-height: 1.4;
  margin-bottom: clamp(48px, 6vw, 90px);
  color: var(--ivory-dim);
}
.guardian-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(28px, 5vw, 80px);
}
.guardian-frame {
  border: 1px solid var(--hairline);
  border-radius: 4px;
  padding: clamp(24px, 3vw, 44px);
  background: var(--noir-2);
  transition: border-color .4s ease;
}
.guardian:hover .guardian-frame { border-color: rgba(198, 161, 91, 0.5); }
.guardian-frame {
  cursor: none; /* the gold ring cursor leads */
  overflow: hidden; /* clips the bleeding photo to the frame border */
}
.g-photo { pointer-events: none; }
/* Touch has no hover, so without a hint the photograph behind the etching is
   undiscoverable — a phone reader scrolls past two line drawings. Shown only
   where hover is absent; hidden once the frame has been opened. */
.guardian-frame { position: relative; }
.guardian-hint {
  display: none; position: absolute; right: 14px; bottom: 12px; z-index: 3;
  font-family: var(--font-mono); font-weight: 300; font-size: 0.6rem; letter-spacing: 0.24em; text-transform: uppercase;
  color: rgba(198, 161, 91, 0.8); pointer-events: none;   /* 5.3:1 on noir-2; 0.7 fell just short */
  transition: opacity .4s ease;
}
@media (hover: none) { .guardian-hint { display: block; } }
.guardian.is-open .guardian-hint { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  /* no wobble engine: hovering simply shows the full photograph */
  .guardian-frame:hover .g-photo { mask: none; }
}
@media (hover: none), (pointer: coarse) {
  .guardian-frame { cursor: pointer; }
}
.etch { width: 100%; height: auto; overflow: visible; }
.etch .e, .etch .e-frame {
  fill: none;
  stroke: var(--champagne);
  stroke-width: 1.6;
  stroke-linecap: round;
}
.etch .e-frame { stroke: var(--hairline); stroke-width: 1; }
.guardian figcaption { margin-top: 26px; }
.guardian h3 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.9rem;
}
.guardian-role {
  margin: 6px 0 12px;
  font-size: 0.68rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--champagne);
}
.guardian-line { color: var(--ivory-dim); font-size: 0.95rem; max-width: 26em; }

/* ---------- 07 arrangement ---------- */
.arrangement-grid {
  display: grid;
  grid-template-columns: 1fr 1.1fr;
  gap: clamp(36px, 6vw, 110px);
  align-items: start;
}
.arrangement-lead h2 {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(2.6rem, 5.4vw, 5rem);
  line-height: 1.05;
}
.arrangement-lead em { color: var(--champagne); }
.arrangement-price {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(3.4rem, 6vw, 5.6rem);
  margin-top: 34px;
  line-height: 1;
}
.arrangement-price sup { font-size: 0.4em; color: var(--champagne); vertical-align: 40%; margin-right: 4px; }
.arrangement-price span {
  font-family: var(--font-body);
  font-size: 0.22em;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ivory-dim);
  margin-left: 10px;
}
.arrangement-availability {
  display: flex; align-items: center; gap: 12px;
  margin-top: 26px;
  font-size: 0.7rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ivory-dim);
}
.avail-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--champagne);
  animation: avail 2.2s ease-in-out infinite;
}
@keyframes avail {
  0%, 100% { box-shadow: 0 0 0 0 rgba(198, 161, 91, 0.5); }
  50% { box-shadow: 0 0 0 8px rgba(198, 161, 91, 0); }
}
.arrangement-terms { list-style: none; }
.arrangement-terms li {
  display: flex; justify-content: space-between; align-items: baseline; gap: 24px;
  padding: 22px 4px;
  border-bottom: 1px solid var(--hairline);
}
.arrangement-terms li:first-child { border-top: 1px solid var(--hairline); }
.arrangement-terms li span:first-child {
  font-family: var(--font-display);
  font-size: 1.35rem;
  white-space: nowrap;
}
.arrangement-terms li span:last-child {
  font-size: 0.8rem;
  color: var(--ivory-dim);
  text-align: right;
}
.arrangement-cta {
  margin-top: clamp(50px, 7vw, 90px);
  display: flex; align-items: center; gap: 34px; flex-wrap: wrap;
}

/* the quiet alternative — a single project, no seat. A coda under the CTA,
   deliberately lower-voiced than the retainer so it never competes with it */
.oneoff {
  margin-top: clamp(56px, 8vw, 110px);
  padding-top: clamp(30px, 4.4vw, 52px);
  border-top: 1px solid var(--hairline);
  display: grid; grid-template-columns: 1fr 1.1fr;
  gap: clamp(24px, 6vw, 110px);
  align-items: start;
}
.oneoff-label {
  font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.24em;
  text-transform: uppercase; color: var(--champagne);
}
.oneoff-line {
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(1.6rem, 2.8vw, 2.5rem); line-height: 1.12;
  margin-top: 10px;
}
.oneoff-line em { color: var(--champagne); font-style: italic; }
.oneoff-sub { font-size: 0.92rem; line-height: 1.7; color: var(--ivory-dim); max-width: 52ch; }
.oneoff-link {
  display: inline-flex; align-items: center; gap: 10px;
  margin-top: 18px; padding-bottom: 6px;
  /* ~44px tap target: pad the top and pull it back with a negative margin */
  padding-top: 12px; margin-top: 6px;
  font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--champagne); border-bottom: 1px solid rgba(198, 161, 91, 0.35);
  transition: border-color 0.3s ease, color 0.3s ease;
}
.oneoff-link:hover { color: #f2d9a3; border-color: var(--champagne); }
.oneoff-arrow { transition: transform 0.3s ease; }
.oneoff-link:hover .oneoff-arrow { transform: translateX(4px); }
.btn2 {
  display: inline-block;
  font-size: 0.74rem;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--noir);
  background: var(--champagne);
  border-radius: 99px;
  padding: 20px 44px;
  transition: background-color .35s ease;
}
.btn2:hover { background: var(--ivory); }
.arrangement-cta p { color: var(--ivory-dim); font-size: 0.9rem; max-width: 26em; }

/* ---------- footer ---------- */
.footer2 {
  border-top: 1px solid var(--hairline);
  margin-top: clamp(40px, 6vw, 80px);
  padding: clamp(100px, 13vw, 180px) var(--pad) 40px;
  text-align: center;
}
.footer2-eyebrow {
  font-size: 0.72rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--champagne);
  margin-bottom: 30px;
}
.footer2-cta { display: inline-block; }
.f-line {
  display: block;
  overflow: hidden;
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(3.4rem, 12vw, 11rem);
  line-height: 1.04;
  letter-spacing: -0.01em;
  padding-bottom: 0.12em;
  margin-bottom: -0.12em;
  transition: letter-spacing .6s cubic-bezier(.3, .9, .3, 1);
}
.f-line .char { display: inline-block; }
.footer2-cta:hover .f-line { letter-spacing: 0.02em; }
.footer2-sub {
  margin-top: 30px;
  color: var(--ivory-dim);
}
.footer2-bottom {
  margin-top: clamp(80px, 10vw, 140px);
  padding-top: 26px;
  border-top: 1px solid var(--hairline);
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: 14px;
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ivory-dim);
}
/* the hit area is padded out to ~44px without moving the text: negative
   margins cancel the padding visually, so only the finger notices */
.footer2-bottom a { transition: color .3s ease; padding: 13px 0; margin: -13px 0; }
/* colophon — quieter than the row above it, and set in the mono so it reads as
   a credit line rather than more copy */
.footer2-colophon {
  margin: 22px auto 0; text-align: center;
  font-family: var(--font-mono); font-weight: 300;
  font-size: 0.68rem; line-height: 1.7; letter-spacing: 0.08em;
  color: rgba(236, 228, 214, 0.6);   /* no max-width: one line on desktop, wraps naturally on phones. 0.6, not --ivory-faint: small text needs 4.5:1, and this measures ~5.7:1 */
}
.footer2-bottom a:hover { color: var(--champagne); }

/* ---------- brand logo (svg) across nav, loader, footer ---------- */
/* doubled from clamp(24px, 3vw, 30px) — the mark now sets the bar's height,
   where the Begin button used to */
.nav2-logo img { height: clamp(48px, 6vw, 60px); width: auto; display: block; }
.loader-logo { width: clamp(220px, 34vw, 360px); height: auto; display: block; opacity: 0; }
.footer2-logo { height: 20px; width: auto; display: block; }

/* ---------- 08 how it works ---------- */
.how-intro { text-align: center; max-width: 760px; margin: 0 auto clamp(34px, 5vh, 60px); }
.how-title {
  font-family: var(--font-display); font-weight: 300; line-height: 1.04;
  font-size: clamp(2rem, 4.6vw, 3.4rem); color: var(--ivory);
}
.how-title em { color: var(--champagne); font-style: italic; }
.how-sub { margin-top: clamp(12px, 1.6vh, 18px); color: var(--ivory-dim); font-size: clamp(0.92rem, 1.1vw, 1.05rem); }
.how-video {
  position: relative; width: 100%; aspect-ratio: 16 / 9; margin: 0; overflow: hidden;
  border: 1px solid var(--ivory-faint); border-radius: 14px; background: var(--noir-2);
  box-shadow: 0 40px 110px rgba(0, 0, 0, 0.55);
}
.how-video-el { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; background: var(--noir); }

/* ---------- reveal defaults ---------- */
.chapter-head, .manifesto-text, .guardians-intro { /* revealed via JS */ }
.r { opacity: 0; transform: translateY(30px); }
.gsap-failed .r, .gsap-failed .hero2-eyebrow, .gsap-failed .hero2-foot { opacity: 1; transform: none; }
.gsap-failed .loader { display: none; }

/* ---------- responsive ---------- */
@media (max-width: 1020px) {
  .studio-grid, .arrangement-grid, .oneoff { grid-template-columns: 1fr; }
  .guardian-grid { grid-template-columns: 1fr; }
  .studio-stats { grid-template-columns: repeat(2, 1fr); gap: 38px 24px; }
}
@media (max-width: 760px) {
  .nav2-links { display: none; }
  .hero2-foot { flex-direction: column; align-items: flex-start; }
  .hero2-meta { align-items: flex-start; }
  .arrangement-terms li { flex-direction: column; gap: 4px; }
  .arrangement-terms li span:last-child { text-align: left; }
  .footer2-bottom { flex-direction: column; align-items: center; gap: 10px; }
  /* the fixed back-to-top button (56px + its offset) sat over the last line of
     the colophon at the page's end — clear it, and centre the credit to match
     the stacked row above */
  .footer2 { padding-bottom: 104px; }
}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  .loader { display: none; }
  .hero2-scroll i, .avail-dot { animation: none; }
  .hero2-eyebrow, .hero2-foot, .chain-outro, .r { opacity: 1 !important; transform: none !important; }
  .manifesto-text .m-word { color: var(--ivory); }
  .thread::after { transform: scaleY(1); }
}

/* ============================================================
   RESERVE A SEAT — modal (blurred backdrop, split layout)
   ============================================================ */
.rsv {
  position: fixed; inset: 0; z-index: 300;
  display: grid; place-items: center; padding: clamp(14px, 4vw, 48px);
  opacity: 0; visibility: hidden; transition: opacity .4s ease, visibility .4s;
}
.rsv.is-open { opacity: 1; visibility: visible; }
.rsv-backdrop {
  position: fixed; inset: 0; background: rgba(8, 7, 5, 0.55);
  -webkit-backdrop-filter: blur(13px); backdrop-filter: blur(13px);
}
.rsv-dialog {
  position: relative; z-index: 1;
  width: min(980px, 100%); max-height: 94vh;
  display: grid; grid-template-columns: 0.9fr 1.1fr;
  background: var(--noir-2); border: 1px solid var(--ivory-faint);
  border-radius: 16px; overflow: hidden;
  box-shadow: 0 50px 130px rgba(0, 0, 0, 0.7);
  transform: translateY(18px) scale(0.985);
  transition: transform .5s cubic-bezier(.16, 1, .3, 1);
}
.rsv.is-open .rsv-dialog { transform: none; }
.rsv-x {
  position: absolute; top: 14px; right: 16px; z-index: 4;
  width: 36px; height: 36px; border-radius: 50%;
  border: 1px solid var(--hairline); background: rgba(0, 0, 0, 0.25);
  color: var(--ivory); font-size: 1.3rem; line-height: 1; cursor: pointer;
  transition: border-color .3s ease, color .3s ease;
}
.rsv-x:hover { border-color: var(--champagne); color: var(--champagne); }

/* left — the studio photograph + a scrim + the three things you get */
.rsv-aside {
  position: relative; overflow: hidden;
  padding: clamp(24px, 3vw, 40px);
  display: flex; flex-direction: column; justify-content: space-between; gap: clamp(20px, 3vh, 36px);
  background: linear-gradient(158deg, #1b1611, #0e0c09 72%);   /* fallback if the image fails */
}
.rsv-aside-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% 45%; z-index: 0; }
/* No scrim. The old photograph was much brighter (mean luma 44 against this
   one's 30) and needed 0.74 alpha over the title to hold it; against this
   image the same scrim crushed the whole panel to near-black. The picture now
   plays at full strength — the eyebrow and title sit over unlit wall at the top
   of the frame, and .rsv-perks carries its own tinted, blurred background, so
   nothing here depends on an overlay for legibility. */
.rsv-aside-head, .rsv-perks { position: relative; z-index: 2; }
.rsv-eyebrow {
  font-family: var(--font-mono); font-size: 0.6rem; letter-spacing: 0.26em;
  text-transform: uppercase; color: var(--champagne);
}
.rsv-aside-title {
  font-family: var(--font-display); font-weight: 300; line-height: 1.06;
  font-size: clamp(1.7rem, 2.6vw, 2.3rem); margin: 14px 0 14px;
}
.rsv-aside-title em { color: var(--champagne); font-style: italic; }
.rsv-aside-sub { font-size: 0.9rem; line-height: 1.6; color: var(--ivory-dim); max-width: 34ch; }
/* one compact glass box, a checkmark row per perk */
.rsv-perks {
  list-style: none; position: relative; z-index: 2;
  border: 1px solid var(--hairline); border-radius: 12px;
  background: rgba(16, 13, 9, 0.55); -webkit-backdrop-filter: blur(7px); backdrop-filter: blur(7px);
  padding: 2px clamp(14px, 1.7vw, 18px);
}
.rsv-perk {
  display: flex; align-items: baseline; gap: 11px; line-height: 1.2;
  padding: clamp(7px, 1vh, 9px) 0;
}
.rsv-perk + .rsv-perk { border-top: 1px solid var(--hairline); }
.rsv-perk-check { flex: 0 0 auto; color: var(--champagne); font-size: 0.82rem; line-height: 1; transform: translateY(1px); }
.rsv-perk-t { font-family: var(--font-display); font-size: 1.06rem; color: var(--ivory); white-space: nowrap; }
.rsv-perk-d { font-size: 0.76rem; color: var(--ivory-dim); margin-left: auto; }

/* right — the questionnaire (compact: fits one screen, never scrolls) */
.rsv-main { position: relative; padding: clamp(20px, 2.4vw, 34px); overflow: hidden; display: flex; flex-direction: column; justify-content: center; }
.rsv-form-head h2 { font-family: var(--font-display); font-weight: 400; font-size: clamp(1.5rem, 2.2vw, 1.95rem); line-height: 1.08; }
.rsv-form-head p { font-size: 0.82rem; color: var(--ivory-dim); margin-top: 4px; }
.rsv-field { border: 0; margin-top: clamp(11px, 1.6vh, 16px); }
.rsv-field legend {
  font-family: var(--font-mono); font-size: 0.6rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ivory-dim); margin-bottom: 7px;
}
.rsv-hint, .rsv-opt { color: rgba(236, 228, 214, 0.3); text-transform: none; letter-spacing: 0; }
.rsv-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.rsv-chip { position: relative; cursor: pointer; }
.rsv-chip input { position: absolute; opacity: 0; width: 0; height: 0; }
.rsv-chip span {
  display: inline-block; padding: 6px 12px; border: 1px solid var(--hairline);
  border-radius: 999px; font-size: 0.8rem; color: var(--ivory);
  transition: border-color .2s ease, background .2s ease, color .2s ease;
}
.rsv-chip:hover span { border-color: rgba(236, 228, 214, 0.34); }
.rsv-chip input:checked + span { border-color: var(--champagne); background: rgba(198, 161, 91, 0.15); color: #f3e9d4; }
.rsv-chip input:focus-visible + span { outline: 2px solid var(--champagne); outline-offset: 2px; }
.rsv-textarea, .rsv-input input {
  width: 100%; background: rgba(0, 0, 0, 0.26); border: 1px solid var(--hairline);
  border-radius: 8px; color: var(--ivory); font-family: var(--font-body);
  font-size: 0.88rem; padding: 9px 13px; transition: border-color .2s ease;
}
.rsv-textarea { resize: none; min-height: 74px; height: 74px; }
.rsv-textarea:focus, .rsv-input input:focus { outline: none; border-color: var(--champagne); }
.rsv-textarea::placeholder, .rsv-input input::placeholder { color: rgba(236, 228, 214, 0.28); }
.rsv-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: clamp(11px, 1.6vh, 16px); }
.rsv-input label {
  display: block; font-family: var(--font-mono); font-size: 0.6rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ivory-dim); margin-bottom: 6px;
}
.rsv-submit {
  width: 100%; margin-top: clamp(13px, 1.8vh, 18px); padding: 12px; border: 0; border-radius: 8px;
  background: var(--ivory); color: #14110c; font-family: var(--font-body); font-weight: 500;
  font-size: 0.9rem; letter-spacing: 0.02em; cursor: pointer;
  transition: background .25s ease;
}
.rsv-submit:disabled { opacity: 0.6; cursor: default; }
.rsv-submit:hover:not(:disabled) { background: var(--champagne); }
.rsv-fine {
  margin-top: 9px; text-align: center; font-family: var(--font-mono);
  font-size: 0.58rem; letter-spacing: 0.1em; color: rgba(236, 228, 214, 0.34);
}
/* shown only when the POST fails — champagne rather than red, which would be the
   one alarming colour on a page that never raises its voice */
.rsv-error {
  margin-top: 12px; text-align: center;
  font-size: 0.78rem; line-height: 1.5; color: var(--champagne);
}
.rsv-error[hidden] { display: none; }
.rsv-done {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; height: 100%; min-height: 320px; gap: 14px; padding: 24px;
}
.rsv-done[hidden] { display: none; }   /* the explicit display above would otherwise defeat [hidden] */
.rsv-done-mark { color: var(--champagne); font-size: 1.7rem; }
.rsv-done h2 { font-family: var(--font-display); font-weight: 400; font-size: clamp(1.7rem, 2.6vw, 2.3rem); }
.rsv-done p { color: var(--ivory-dim); font-size: 0.9rem; line-height: 1.6; max-width: 42ch; }
.rsv-done-close { max-width: 220px; }

@media (max-width: 760px) {
  .rsv-dialog { grid-template-columns: 1fr; max-height: 92vh; overflow-y: auto; }
  .rsv-main { overflow: visible; }
  .rsv-row { grid-template-columns: 1fr; }
  .rsv-aside { gap: 20px; }
  /* the panel is short on a phone and the perks box sat right across the faces —
     the photograph is worth more here than three lines the form itself covers */
  .rsv-perks { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .rsv, .rsv-dialog { transition: none; }
}
