/* ==========================================================================
   Rafael Styles — Design System
   Black #000000 / Gold #D4AF37, editorial red-carpet aesthetic
   ========================================================================== */

:root {
  --black: #000000;
  --black-soft: #0a0a0a;
  --panel: #111111;
  --gold: #D4AF37;
  --gold-bright: #f0cf6b;
  --gold-dim: #8a7226;
  --gold-trim: #B77924; /* sampled from gold_velvet_tray reference; matches the velvet textures */
  --hairline: rgba(183, 121, 36, 0.45);
  --text: #f3efe4;
  --text-muted: #c9c3b3;
  --font-display: "Cormorant Garamond", "Playfair Display", Georgia, serif;
  --font-script: "Tangerine", "Brush Script MT", cursive;
  --font-body: "Jost", "Helvetica Neue", Arial, sans-serif;
  --nav-height: 76px;
  --max-width: 1180px;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

body {
  margin: 0;
  background: var(--black);
  color: var(--text);
  font-family: var(--font-body);
  font-weight: 300;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; display: block; }
a { color: inherit; }
h1, h2, h3, h4 { font-family: var(--font-display); font-weight: 600; margin: 0 0 0.5em; letter-spacing: 0.02em; }
p { margin: 0 0 1.1em; }
ul { margin: 0; padding: 0; list-style: none; }
button { font-family: inherit; }

.container {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 clamp(1.25rem, 5vw, 3rem);
}

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap; border: 0;
}

.skip-link {
  position: absolute;
  left: 1rem;
  top: -60px;
  background: var(--gold);
  color: var(--black);
  padding: 0.65em 1.2em;
  z-index: 1000;
  font-family: var(--font-body);
  font-weight: 600;
  text-decoration: none;
  border-radius: 2px;
  transition: top 0.2s var(--ease);
}
.skip-link:focus { top: 1rem; }

.eyebrow {
  display: block;
  font-family: var(--font-body);
  font-size: 0.72rem;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 0.85em;
}

.ornament {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  margin: 1.75rem 0;
  color: var(--gold);
  opacity: 0.85;
}
.ornament::before,
.ornament::after {
  content: "";
  flex: 1 1 60px;
  max-width: 140px;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
}
.ornament .ornament-glyph { font-size: 1rem; line-height: 1; }

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.9em 2.1em;
  border: 1px solid var(--gold);
  border-radius: 999px;
  font-family: var(--font-body);
  font-size: 0.78rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--gold);
  background: transparent;
  cursor: pointer;
  transition: background 0.35s var(--ease), color 0.35s var(--ease), box-shadow 0.35s var(--ease);
  white-space: nowrap;
}
.btn:hover, .btn:focus-visible {
  background: var(--gold);
  color: var(--black);
  box-shadow: 0 0 24px rgba(212, 175, 55, 0.35);
}
.btn-solid {
  background: var(--gold);
  color: var(--black);
}
.btn-solid:hover, .btn-solid:focus-visible {
  background: var(--gold-bright);
  color: var(--black);
}
.btn-block { width: 100%; justify-content: center; }

:focus-visible {
  outline: 2px solid var(--gold-bright);
  outline-offset: 3px;
}

/* ---------- Nav ---------- */
.site-nav {
  position: sticky;
  top: 0;
  z-index: 500;
  height: var(--nav-height);
  display: flex;
  align-items: center;
  background: rgba(0, 0, 0, 0.88);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  border-bottom: 1px solid var(--hairline);
}
.site-nav .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  max-width: none;
  padding: 0 clamp(1.25rem, 4vw, 3rem);
}
.nav-brand {
  display: flex;
  flex-direction: column;
  line-height: 1.05;
  text-decoration: none;
  color: var(--text);
}
.nav-brand .brand-name {
  font-family: var(--font-display);
  font-size: 1.55rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  color: var(--gold);
}
.nav-brand .brand-sub {
  font-size: 0.58rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.nav-toggle {
  display: none;
  background: none;
  border: 1px solid var(--hairline);
  border-radius: 6px;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  color: var(--gold);
  cursor: pointer;
}
.nav-toggle svg { width: 22px; height: 22px; }

.nav-links {
  display: flex;
  align-items: center;
  gap: clamp(1.1rem, 2.4vw, 2.4rem);
}
.nav-links a {
  text-decoration: none;
  font-size: 0.76rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text);
  padding: 0.4em 0.1em;
  position: relative;
}
.nav-links a::after {
  content: "";
  position: absolute;
  left: 0; right: 100%;
  bottom: -2px;
  height: 1px;
  background: var(--gold);
  transition: right 0.3s var(--ease);
}
.nav-links a:hover::after,
.nav-links a:focus-visible::after,
.nav-links a[aria-current="page"]::after {
  right: 0;
}
.nav-links a[aria-current="page"] { color: var(--gold); }
.nav-links .btn { padding: 0.65em 1.5em; }

@media (max-width: 780px) {
  .nav-toggle { display: inline-flex; }
  .nav-links {
    position: absolute;
    top: var(--nav-height);
    left: 0; right: 0;
    /* Fully solid, not rgba(0,0,0,0.97) -- on the homepage this dropdown sits over the bright
       hero photo/heading (the nav is a transparent overlay there now), and even 3% transparency
       was enough to let the gold "Welcome to the Red Carpet" heading visibly ghost through
       behind the links, making the Reserve button look broken/crowded instead of cleanly
       covering it. */
    background: var(--black);
    border-bottom: 1px solid var(--hairline);
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    /* More top/bottom breathing room than before -- the Reserve button sat right at the edge of
       the dropdown with barely any space below it. */
    padding: 1rem clamp(1.25rem, 5vw, 3rem) 2.25rem;
    transform: translateY(-8px);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s var(--ease), transform 0.25s var(--ease);
  }
  .nav-links.is-open {
    opacity: 1;
    /* "none" rather than translateY(0) -- the identity transform is a visual no-op but was
       leaving the dropdown on its own GPU-composited layer after the open transition finished,
       which made the Reserve button's thin pill border rasterize with a broken/dashed look on
       mobile. Dropping the transform once open clears that layer and the border renders solid. */
    transform: none;
    pointer-events: auto;
  }
  .nav-links a {
    display: block;
    width: 100%;
    padding: 1.1em 0;
    border-bottom: 1px solid rgba(212,175,55,0.12);
  }
  .nav-links a::after { bottom: -1px; }
  .nav-links .btn {
    display: inline-flex;
    width: auto;
    margin-top: 1.4em;
    text-align: center;
    justify-content: center;
    border-bottom: none;
  }
}

/* ---------- Fullscreen intro ---------- */
.intro { display: none; }
/* When the inline head script (index.html) has already decided to skip the intro -- already
   seen this visit, or reduced-motion is on -- it never even renders, not even for a frame.
   This has to win over ".js .intro" below regardless of source order, so it repeats that
   selector's specificity exactly (html.no-intro.js beats html.js alone either way, but being
   explicit here keeps this rule's intent obvious rather than relying on cascade position). */
.no-intro .intro { display: none !important; }

.js .intro {
  position: fixed;
  inset: 0;
  z-index: 900;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 2rem;
  background: #000000;
  text-align: center;
  cursor: pointer;
  opacity: 1;
  transition: opacity 1s cubic-bezier(0.4, 0, 0.2, 1), visibility 0s linear 0s;
}
.js .intro.is-dismissed {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 1s cubic-bezier(0.4, 0, 0.2, 1), visibility 0s linear 1s;
}
/* Crossfade: the intro's contents drift up and swell slightly as the whole screen fades */
.js .intro > * { transition: transform 1.2s var(--ease); }
.js .intro.is-dismissed > * { transform: translateY(-10px) scale(1.035); }
.js .intro.is-dismissed .intro-cue { animation: none; }
.intro-logo {
  /* Full lockup (crest + wordmark + slogan baked into the art), 1490x1674 — taller than the old crest-only mark.
     Bounded on BOTH axes so it scales down cleanly and never overflows a short mobile viewport. */
  display: block;
  width: auto;
  height: auto;
  max-width: min(80vw, 520px);
  max-height: min(58svh, 640px);
}
.intro-tagline { /* same treatment as the sticky nav's .brand-sub */
  font-size: 0.58rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-top: 0.5rem; /* a touch more than the original 0.25rem: the art's own baked-in slogan
    now sits right above it, so this keeps the two from reading as crowded together */
}
.intro-cue {
  margin-top: 2.25rem;
  font-size: 0.68rem;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: var(--gold-dim);
  animation: intro-cue-pulse 2.6s ease-in-out infinite;
}
.js body.intro-active { overflow: hidden; }
@keyframes intro-cue-pulse { 0%, 100% { opacity: 0.45; } 50% { opacity: 1; } }

/* The hero rises into place while the intro fades, so there's no hard cut */
.hero { transition: opacity 1.1s cubic-bezier(0.4, 0, 0.2, 1) 0.25s, transform 1.3s var(--ease) 0.25s; }
.js body.intro-active .hero { opacity: 0; transform: translateY(28px); }

@media (prefers-reduced-motion: reduce) {
  .js .intro { display: none; }
  .js body.intro-active { overflow: auto; }
}

/* ---------- Hero: one full-width living-room photo, nav + welcome text overlaid on top ----------
   Per spec: "The first layout layer revealed is a full-width section displaying my background
   graphic of luxury armchairs... Placed cleanly on top of this section is the brand header and
   its main navigation tabs" + "Sitting cleanly and right next to the living area armchair
   graphics (not stretched borderless across the screen) is my primary introductory text block."
   So: the photo fills the entire section edge-to-edge (no split columns), the nav floats
   transparently over it (see body.is-home .site-nav below), and the welcome text is a bounded
   panel (not full-width) overlaid on the photo itself, not a separate column beside it. */
.hero {
  position: relative;
  min-height: 100vh;
  overflow: hidden;
  background: var(--black);
}
.hero-photo { position: absolute; inset: 0; z-index: 0; }
.hero-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 42%;
}
.hero-copy {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  /* Pinning to the bottom (flex-end) of a full 100vh box left a large empty gap above the text,
     since the text block itself is much shorter than the viewport. Centering removes that. */
  justify-content: center;
  min-height: 100vh;
  max-width: 680px;
  /* Shifted right and widened from the original flush-left 560px box, so it reads as sitting
     "next to" the armchair graphics rather than pinned to the very left edge of the screen. */
  margin-left: 8%;
  padding: calc(var(--nav-height) + 3rem) clamp(1.5rem, 5vw, 4.5rem) clamp(2.5rem, 6vw, 4rem);
  /* A gradient scrim, not a flat/heavy overlay filter: it only darkens where the text actually
     sits, leaving the rest of the photo clear — the spec explicitly rules out "heavy dark
     overlay filters that wash out graphic details". An ellipse centered on the text, not a
     straight top-to-bottom gradient -- a vertical-only gradient paints at full box width, so on
     any viewport wide enough to show photo past the box's own right edge (tablets and up; the
     box is only 680px max-width but the viewport is much wider) that edge showed up as a hard
     dark rectangle seam slicing across the photo. The radial ellipse fades to fully transparent
     well inside the box's own bounds on every side, so there's no edge left to see. */
  background: radial-gradient(ellipse 85% 72% at 38% 50%, rgba(0,0,0,0.68) 0%, rgba(0,0,0,0.5) 42%, rgba(0,0,0,0.22) 70%, transparent 100%);
}
.hero-copy h1 {
  font-size: clamp(2.1rem, 4.2vw, 3.4rem);
  color: var(--gold);
  line-height: 1.1;
  margin-bottom: 0.6em;
}
.hero-copy p { color: var(--text-muted); font-size: 1.02rem; }
.hero-copy p:last-of-type { color: var(--text); margin-bottom: 0; }

/* Fine metallic gold hairline framing any two-block split against black (still used by
   .about-grid.split-frame on about.html; unrelated to the hero now). */
.split-frame {
  border-top: 1px solid var(--gold-trim);
  border-bottom: 1px solid var(--gold-trim);
}
.split-frame > * + * { border-left: 1px solid var(--gold-trim); }

@media (max-width: 900px) {
  .hero, .hero-copy { min-height: 100vh; }
  .hero-copy {
    /* Horizontally centered in the frame -- not shifted to one side -- with a cap on width so
       it reads as a centered column rather than stretching edge to edge. */
    max-width: 480px;
    margin-left: auto;
    margin-right: auto;
    padding-left: 1.25rem;
    padding-right: 1.25rem;
    text-align: center;
    /* Desktop pins the text to the bottom (flex-end), which works there because the box is
       much wider so the text block itself is short. On a narrow phone the same 3 paragraphs
       wrap into a much taller block, and flex-end was pushing most of it down past the bottom
       of the screen. Centering keeps it fully in view instead. */
    justify-content: center;
    /* The bottom-weighted scrim above is tuned for text pinned at the bottom (the desktop
       layout) -- with the text now vertically centered on mobile, that gradient left the
       heading sitting in its barely-darkened top band, right over the brightest part of the
       photo. This vignette instead darkens where the now-centered text actually is. Radial, not
       a straight vertical gradient, for the same reason as the desktop version above: on a
       tablet-portrait width (this box is capped at 480px but the viewport can be much wider
       here, up to 900px) a vertical-only gradient paints a hard-edged rectangle either side of
       the centered box. The ellipse fades to transparent before it reaches the box's own edges. */
    background: radial-gradient(ellipse 88% 75% at 50% 50%, rgba(0,0,0,0.68) 0%, rgba(0,0,0,0.52) 42%, rgba(0,0,0,0.24) 70%, transparent 100%);
  }
}

/* ---------- Every page: nav starts transparent (a soft dark gradient, not solid) at the very
   top, then becomes the normal solid bar once scrolled down a bit (toggled via .is-solid, see
   main.js). Applied site-wide, not just the homepage, so the nav behaves identically everywhere
   -- on the homepage that means it floats over the living-room photo as the client described;
   on the other pages, the top section is already solid black, so the "transparent" state just
   blends into that rather than revealing a photo, but the interaction stays consistent. ---------- */
.site-nav {
  position: absolute;
  top: 0; left: 0; right: 0;
  /* Fully transparent made the gold/white nav text unreadable against a busy photo behind it --
     a soft dark gradient (fading to nothing by the bottom of the nav) keeps whatever's behind it
     visible while still giving the text enough contrast to read. */
  background: linear-gradient(to bottom, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0.15) 100%);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom-color: transparent;
  transition: background 0.35s var(--ease), backdrop-filter 0.35s var(--ease), border-color 0.35s var(--ease);
}
.site-nav.is-solid {
  position: fixed;
  background: rgba(0, 0, 0, 0.88);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  border-bottom-color: var(--hairline);
}

.placeholder-slot {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  border: 1px dashed var(--hairline);
  border-radius: 4px;
  background:
    repeating-linear-gradient(135deg, rgba(212,175,55,0.05) 0 2px, transparent 2px 14px),
    linear-gradient(160deg, #121212, #050505);
  color: var(--gold-dim);
  overflow: hidden;
}
.placeholder-slot .placeholder-label {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  padding: 1.5rem;
  font-family: var(--font-body);
}
.placeholder-slot .placeholder-icon {
  width: 34px; height: 34px;
  opacity: 0.8;
}
.placeholder-slot .placeholder-title {
  font-family: var(--font-display);
  font-size: 1rem;
  letter-spacing: 0.05em;
  color: var(--gold);
}
.placeholder-slot .placeholder-dims {
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* ---------- Section shells ---------- */
.section {
  padding: clamp(3.5rem, 8vw, 6.5rem) 0;
  position: relative;
}
.section-black { background: var(--black); }
.section-panel { background: var(--black-soft); border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline); }
/* A section-black that's a direct child of .fabric-veil must not paint its own opaque black,
   or it hides the wrapper's texture behind it entirely (the two sections sit flush with no
   gap between them). This only fires when .fabric-veil is wrapping sections rather than being
   applied directly to a single element (the about.html bio, the lookbook.html header). */
.fabric-veil > .section-black { background: none; }
/* Fabric texture, applied via this standalone class so every plain section-black/other surface
   stays untouched. Painted once on the .fabric-veil element itself, so where it wraps multiple
   sections (the About Teaser + Lookbook Menu intro on index.html) the diagonal fold pattern
   runs continuously across their combined height instead of each section re-covering its own
   height independently and breaking the pattern at the seam. Also used directly on a single
   element with both classes: the bio section on about.html, and the lookbook.html page header. */
.fabric-veil {
  position: relative;
  background:
    linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)),
    url("../assets/fabric-velvet-drapery-gold-dust.jpg") center / cover no-repeat,
    var(--black);
}
/* Client spec: "my original metallic gold dust sprays [must be] clearly visible at their three
   specific spots". Previously the delivered fabric texture had no gold in it at all, so a CSS
   effect (a scattering of radial-gradient specks) simulated the sprays here as a stand-in. The
   client has since supplied the real photo (fabric-velvet-drapery-gold-dust.jpg, swapped in
   above), so the simulated specks are removed -- keeping both would have doubled up the effect. */
.section-head {
  max-width: 720px;
  margin: 0 auto 3rem;
  text-align: center;
}
.section-head h2 {
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  color: var(--gold);
}
.section-head .lede { color: var(--text-muted); font-size: 1.02rem; }

/* ---------- About ---------- */
.about-grid {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: start;
}
.about-grid.split-frame { gap: 0; border: 1px solid var(--gold-trim); align-items: stretch; }
.about-grid.split-frame > * + * { border-left: 1px solid var(--gold-trim); }
.about-grid.split-frame .about-body { padding: clamp(1.5rem, 4vw, 3rem); }
.about-portrait.placeholder-slot { aspect-ratio: 3 / 4; border: 0; border-radius: 0; }
.about-body h3 { color: var(--gold); font-size: clamp(1.4rem, 3vw, 1.9rem); }
.about-body p { color: var(--text-muted); }
.about-note {
  margin-top: 2rem;
  padding: 1.4rem 1.6rem;
  border-left: 2px solid var(--gold);
  background: rgba(212, 175, 55, 0.05);
}
.about-note strong { color: var(--gold); display: block; margin-bottom: 0.4em; font-family: var(--font-display); font-size: 1.05rem; }
.about-note a { color: var(--gold); text-decoration: underline; text-underline-offset: 3px; }

@media (max-width: 860px) {
  .about-grid { grid-template-columns: 1fr; }
  .about-portrait.placeholder-slot { aspect-ratio: 16 / 10; }
  .about-grid.split-frame > * + * { border-left: 0; border-top: 1px solid var(--gold-trim); }
}

/* ---------- Lookbook ---------- */
.lookbook-hero {
  position: relative;
  z-index: 1; /* same reason as .site-footer below: stay above the fixed curtain layer */
  padding: calc(var(--nav-height) + 3rem) 0 2.5rem;
  text-align: center;
  border-bottom: 1px solid var(--hairline);
}
.lookbook-hero h1 { color: var(--gold); font-size: clamp(2rem, 5vw, 3rem); }
.lookbook-hero .lede { max-width: 620px; margin: 0 auto; color: var(--text-muted); }

.parallax-stage {
  position: relative;
  overflow: hidden;
}
.parallax-layer {
  position: absolute;
  inset: -10% -5%;
  z-index: 0;
  pointer-events: none;
  will-change: transform;
}
/* The gold drape (.layer-far) must read as completely still behind the scrolling text/cards,
   on EVERY device including iPhone. `background-attachment: fixed` cannot be trusted for that:
   iOS Safari (and anything else running the iOS WebKit engine, so this includes Chrome/Firefox
   on iPhone too) has never reliably supported it — the background ends up scrolling along with
   the page instead of staying put, which is exactly the "curtain is moving" bug this fixes.
   The old mobile fallback (a slow JS-driven translate3d "drift") avoided that specific bug but
   wasn't actually still either — it drifted at 6% of scroll speed, which doesn't satisfy the
   client's explicit "must stand completely still" requirement.
   Fix: make .layer-far a real `position: fixed` element pinned to the viewport at every width,
   instead of an absolutely-positioned part of the (very tall) scrolling .parallax-stage. A fixed
   element simply never moves, on any browser — no background-attachment trick, no scroll-linked
   JS needed. It sits at z-index 0, behind the actual page content (.category-block is z-index 1),
   and outside this parallax-stage section it's naturally covered by the opaque black sections
   before/after it, so it only ever shows through here. */
/* The client's spec for this texture is explicit: "Absolutely no repeating tile grid patterns,
   blurry stretching, or dark filters." The old rule broke all three — it tiled a small image in
   a repeating grid, upscaled that same small image past its native resolution (blurry on mobile),
   and mixed a black linear-gradient straight into the background (a dark filter over the whole
   texture). Fixed by using ONE real high-resolution photo (gold-satin-drape.jpg, 1264x1264) at
   every screen width, shown with `cover` + `no-repeat` so it fills the screen as a single clean
   image with no tiling and no upscaling blur, and with no gradient/overlay mixed into it. */
.parallax-layer.layer-far {
  position: fixed;
  inset: 0;
  background: url("../assets/gold-satin-drape.jpg") center / cover no-repeat;
}
/* Legibility lives here, not in the backdrop overlay above: a soft scrim scoped to each
   category's own heading block, so it scrolls with the text and always sits behind it no
   matter which part of the (fixed-attachment) fabric happens to be showing through at the time.
   Was desktop-only (min-width: 901px) -- mobile had no scrim at all behind this text, leaving it
   nearly unreadable against the bright gold drape. Applied at every width now. */
.category-head {
  position: relative;
  z-index: 0; /* own stacking context, keeps the scrim scoped to just this block */
}
.category-head::before {
  content: "";
  position: absolute;
  inset: -1.75rem -3rem;
  z-index: -1;
  background: radial-gradient(ellipse 60% 78% at 50% 50%, rgba(0,0,0,0.62) 0%, rgba(0,0,0,0.62) 38%, transparent 78%);
  pointer-events: none;
}
.parallax-layer.layer-near {
  background:
    radial-gradient(ellipse 55% 40% at 20% 15%, rgba(240,207,107,0.10), transparent 65%),
    radial-gradient(ellipse 50% 40% at 80% 80%, rgba(240,207,107,0.08), transparent 65%);
}

.category-block {
  position: relative;
  z-index: 1;
  padding: clamp(2.5rem, 6vw, 4rem) 0;
}
.category-block + .category-block { border-top: 1px solid var(--hairline); }
.category-head { text-align: center; max-width: 680px; margin: 0 auto 2.5rem; }
.category-head .eyebrow { text-align: center; }
.category-head h2 { color: var(--gold); font-size: clamp(1.6rem, 3.6vw, 2.2rem); }
.category-head p { color: var(--text-muted); font-size: 0.95rem; }

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 1.75rem;
}

.lookbook-card {
  background: linear-gradient(180deg, rgba(212,175,55,0.04), transparent 30%), var(--panel);
  border: 1px solid var(--hairline);
  border-radius: 6px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: transform 0.4s var(--ease), box-shadow 0.4s var(--ease), border-color 0.4s var(--ease);
}
.lookbook-card:hover {
  transform: translateY(-6px);
  border-color: var(--gold);
  box-shadow: 0 18px 40px rgba(0,0,0,0.5);
}
.lookbook-card .card-photo.placeholder-slot { aspect-ratio: 4 / 5; border-radius: 0; border-width: 0 0 1px 0; }
.lookbook-card .card-photo:not(.placeholder-slot) { aspect-ratio: 4 / 5; overflow: hidden; border-bottom: 1px solid var(--hairline); background: #000; }
.lookbook-card .card-photo img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 50%; }

.lookbook-card .card-photo { position: relative; }

.lookbook-card .card-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  opacity: 0; /* off by default (mobile grid, and desktop before JS marks a card centered) —
    only the desktop carousel's non-centered cards switch this on, below */
  transition: opacity 0.4s var(--ease);
  pointer-events: none;
}
.lookbook-card .card-body { padding: 1.4rem 1.5rem 1.7rem; display: flex; flex-direction: column; flex: 1; }
.lookbook-card h3 {
  font-size: 1.2rem;
  color: var(--text);
  margin-bottom: 0.55em;
}
.lookbook-card .card-dek {
  font-size: 0.82rem;
  color: var(--gold-dim);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  margin-bottom: 0.9em;
}

/* Accordion */
.accordion-item { border-top: 1px solid rgba(212,175,55,0.18); margin-top: 1.4rem; }
.accordion-trigger {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  background: none;
  border: 0;
  color: var(--gold);
  padding: 0.9em 0 0.7em;
  font-family: var(--font-body);
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
  text-align: left;
}
.accordion-trigger .chevron {
  flex-shrink: 0;
  width: 11px; height: 11px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
  transition: transform 0.35s var(--ease);
}
.accordion-trigger[aria-expanded="true"] .chevron { transform: rotate(-135deg); }

/* Below 901px the lookbook cards are a plain stacked grid, not the desktop carousel — there's
   no "centered card" for the description to auto-open on, so instead of a collapsed-by-default
   accordion behind a click, the description is just always visible here, same "no click needed"
   principle as the flip-book pages. The desktop carousel (>=901px) keeps the click-to-expand
   accordion, since there every non-centered card collapsing is what makes the centered one's
   auto-open meaningful. */
@media (max-width: 900px) {
  .lookbook-card .accordion-trigger { display: none; }
  .lookbook-card .accordion-panel {
    max-height: none !important;
    overflow: visible;
  }
}

.accordion-panel {
  overflow: hidden;
  max-height: 0;
  transition: max-height 0.45s var(--ease);
}
.accordion-panel[data-open="true"] { }
.accordion-panel .accordion-panel-inner {
  padding: 0 0 1.2rem;
  color: var(--text-muted);
  font-size: 0.93rem;
}
.accordion-panel .badge {
  display: inline-block;
  margin-top: 0.7em;
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--black);
  background: var(--gold);
  padding: 0.35em 0.7em;
  border-radius: 999px;
}

.card-reserve {
  margin-top: 1.1rem;
  align-self: flex-start;
}

@media (prefers-reduced-motion: reduce) {
  .lookbook-card:hover { transform: none; }
}

/* ---------- Policies ---------- */
.policy-hero {
  padding: calc(var(--nav-height) + 3rem) 0 1rem;
  text-align: center;
}
.policy-hero h1 { color: var(--gold); font-size: clamp(2rem, 5vw, 2.8rem); }
.policy-hero .lede { max-width: 640px; margin: 0 auto; color: var(--text-muted); }

.policy-doc {
  max-width: 820px;
  margin: 0 auto;
  padding: 1rem 0 2rem;
}
.policy-doc h2 {
  color: var(--gold);
  font-size: 1.15rem;
  letter-spacing: 0.04em;
  margin-top: 2.4em;
  padding-bottom: 0.5em;
  border-bottom: 1px solid var(--hairline);
}
.policy-doc h2:first-child { margin-top: 0; }
.policy-doc p { color: var(--text-muted); font-size: 0.97rem; }
.policy-region {
  font-family: var(--font-display);
  font-size: 1.3rem;
  color: var(--text);
  text-align: center;
  margin-bottom: 0.3em;
}
.policy-region-sub { text-align: center; color: var(--text-muted); margin-bottom: 2.5rem; }

/* ---------- Footer ---------- */
.site-footer {
  position: relative;
  z-index: 1; /* Must paint above the lookbook page's fixed gold-curtain layer (z-index: 0),
    or the curtain — which is pinned to the viewport and never scrolls away — visually covers
    the footer once scrolled into view, since a positioned z-index:0 element otherwise paints
    above plain non-positioned content regardless of DOM order. */
  border-top: 1px solid var(--hairline);
  background: var(--black-soft);
  padding: 3.5rem 0 2.5rem;
}
.footer-grid {
  display: grid;
  grid-template-columns: 1.2fr 1fr 1fr;
  gap: 2.5rem;
  margin-bottom: 2.5rem;
}
.footer-brand .brand-name {
  font-family: var(--font-display);
  color: var(--gold);
  font-size: 1.5rem;
  letter-spacing: 0.06em;
}
.footer-brand p { color: var(--text-muted); font-size: 0.9rem; margin-top: 0.75em; }
.footer-col h4 {
  font-size: 0.72rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 1em;
}
.footer-col ul li { margin-bottom: 0.7em; }
.footer-col a {
  text-decoration: none;
  color: var(--text-muted);
  font-size: 0.92rem;
}
.footer-col a:hover, .footer-col a:focus-visible { color: var(--gold); }
/* Client asked for "clean, minimal icon links" for Instagram/Facebook/email; these were plain
   text with no icon at all. Small inline icons added (matching the site's own hand-drawn SVG
   style used elsewhere — the nav toggle and carousel arrows — rather than pulling in an icon
   font/library for just three glyphs). */
.footer-col a.social-link { display: inline-flex; align-items: center; gap: 0.6em; }
.footer-col a.social-link svg { width: 16px; height: 16px; flex-shrink: 0; color: var(--gold-dim); transition: color 0.25s var(--ease); }
.footer-col a.social-link:hover svg, .footer-col a.social-link:focus-visible svg { color: var(--gold); }
.footer-bottom {
  border-top: 1px solid var(--hairline);
  padding-top: 1.6rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;
  justify-content: space-between;
  font-size: 0.78rem;
  color: var(--text-muted);
}
.footer-bottom a { color: var(--text-muted); text-decoration: underline; text-underline-offset: 3px; }
.footer-bottom a:hover, .footer-bottom a:focus-visible { color: var(--gold); }

@media (max-width: 780px) {
  .footer-grid { grid-template-columns: 1fr; gap: 2rem; }
}


/* ---------- Lookbook carousel (desktop) ---------- */
.carousel-controls { display: none; }
@media (min-width: 901px) {
  .card-grid {
    display: flex;
    gap: 2rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    align-items: flex-start;
    /* Fixed (not min-height, which only sets a floor and still let real content grow past it)
       height for the row, reserving just enough for the tallest card with its description open
       (re-measured across all 15 service cards at this 380px card width after the accordion
       spacing changes below: tallest is "Red-Carpet Roller-Clip Retention" at ~929.5px), so
       switching which card is centered -- which opens/closes that card's accordion -- never
       changes the row's own height. Without this, the arrows (positioned at 50% of this row's
       height) and everything below on the page (other categories, the scheduling note, the
       footer) would visibly shift up/down every time a different card centers. Same fix
       philosophy as the flip-book's fixed --book-track-h frame above, for the same class of bug.
       Individual cards are no longer forced to this same height (see .lookbook-card below) --
       align-items: flex-start lets each one stay only as tall as its own real content, so a
       collapsed card's own border/background no longer wraps a big empty void underneath
       "Full Description"; the leftover room in a short card's row slot is just plain backdrop,
       not part of the card itself. */
    height: 950px;
    /* Horizontal edge padding (letting the first/last card reach dead-center) is set by
       main.js's setEdgePadding() instead of a fixed calc() here. This track intentionally
       bleeds past .container via the negative margin below, so its real rendered width isn't
       the same box a CSS percentage would resolve against -- a fixed `calc(50%-190px)` guess
       left both edge cards sitting ~48px permanently off-center (confirmed in real-browser
       testing). Measuring the actual track/card widths at runtime removes that guesswork. */
    padding: 0.5rem 0 2rem;
    margin: 0 calc(-1 * clamp(1.25rem, 5vw, 3rem));
    scrollbar-width: none;
    cursor: grab;
    -webkit-overflow-scrolling: touch;
  }
  .card-grid::-webkit-scrollbar { display: none; }
  .card-grid.is-dragging { cursor: grabbing; scroll-snap-type: none; scroll-behavior: auto; }
  .card-grid.is-dragging * { user-select: none; }
  .card-grid .lookbook-card {
    flex: 0 0 380px;
    /* Auto height, not a fixed 850px -- a fixed per-card height was leaving a large empty void
       inside a collapsed card's own border/background below "Full Description" (the card box
       stayed 850px tall even when its real content barely filled half of that). The row itself
       still can't be allowed to resize when a different card centers and its accordion opens/
       closes (that's what caused the arrows and everything below to visibly shift -- see
       .card-grid's own min-height above, which still pins the row to that same measured
       850px regardless of any individual card's height, via align-items: flex-start so cards
       no longer stretch to fill it). So the row height guarantee now lives on .card-grid alone;
       each card is free to be only as tall as its own actual content. */
    scroll-snap-align: center;
    transform: scale(0.94);
    transition: transform 0.4s var(--ease), border-color 0.4s var(--ease), box-shadow 0.4s var(--ease);
  }
  .card-grid .lookbook-card.is-centered { transform: none; border-color: var(--gold); }
  .card-grid .lookbook-card:not(.is-centered) .card-photo::after { opacity: 1; }
  .card-grid .lookbook-card:hover { transform: none; }
  .card-grid .lookbook-card:not(.is-centered):hover { transform: scale(0.94); }
  .card-grid .lookbook-card.is-centered:hover { box-shadow: 0 18px 40px rgba(0,0,0,0.5); }
  /* On page load the first card starts at the default scale(0.94) look, then main.js marks it
     centered -- without this, that triggered the normal 0.4s transition, visibly popping/
     zooming the card on its own right as the page loads (a "flicker" with no user interaction
     behind it). main.js adds this class only for that first, automatic centering and removes
     it right after, so every real user-driven card switch still gets the smooth animation. */
  .card-grid.is-settling .lookbook-card,
  .card-grid.is-settling .lookbook-card .accordion-panel {
    transition: none !important;
  }

  /* .card-grid itself (not .carousel-wrap, which doesn't exist until main.js creates it, so it
     can never be caught mid-layout) is hidden until main.js has applied the initial edge-padding
     and scrolled to center the first card. .card-grid DOES exist in the raw HTML and paints in
     its raw, uncentered default state the instant the page renders -- no padding yet, scrolled
     to 0, every card at its default (non-centered) scale -- which is the actual source of the
     "wrong card visible for a moment" flash, since this happens before main.js has even run,
     let alone finished positioning things. main.js adds .is-ready right after it finishes that
     first positioning, revealing the already-correct result instead of the raw default state. */
  .card-grid { visibility: hidden; }
  .card-grid.is-ready { visibility: visible; }
  .carousel-wrap { position: relative; }
  .carousel-controls { display: block; }
  .carousel-btn {
    position: absolute;
    z-index: 5;
    top: calc(50% - 0.75rem); /* track padding is 0.5rem top / 2rem bottom */
    margin-top: -23px;
    width: 46px; height: 46px;
    border-radius: 50%;
    border: 1px solid var(--gold);
    background: rgba(0,0,0,0.6);
    color: var(--gold);
    cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    transition: background 0.3s var(--ease), color 0.3s var(--ease);
  }
  .carousel-btn[data-dir="-1"] { left: calc(50% - 190px - 72px); }
  .carousel-btn[data-dir="1"] { right: calc(50% - 190px - 72px); }
  .carousel-btn:hover, .carousel-btn:focus-visible { background: var(--gold); color: var(--black); }
  .carousel-btn svg { width: 18px; height: 18px; }
}
@media (min-width: 901px) and (prefers-reduced-motion: reduce) {
  .card-grid { scroll-behavior: auto; }
  .card-grid .lookbook-card { transition: none; }
}

/* ---------- Scheduling note (standalone) ---------- */
.scheduling-section { position: relative; z-index: 1; padding: clamp(2.5rem, 6vw, 4rem) 0; }
.scheduling-section .about-note { max-width: 820px; margin: 0 auto; }
.scheduling-section .about-note p { color: var(--text-muted); margin-bottom: 0; }

/* ---------- Flip-book ---------- */
.flipbook { position: relative; display: flex; justify-content: center; min-height: 460px; }
.flipbook-cta { text-align: center; margin-top: clamp(1.5rem, 4vw, 2.5rem); }

/* Closed: dark cover, tilted, coloured back cover peeking out, resting shadow */
.book-cover {
  position: relative;
  display: block;
  width: min(78vw, 340px);
  aspect-ratio: 3 / 4;
  margin: 1.5rem 0 3.25rem;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  perspective: 900px;
  transition: transform 0.4s var(--ease);
}
.book-cover:hover { transform: translateY(-4px); }
.book-back, .book-front { position: absolute; inset: 0; border-radius: 4px 8px 8px 4px; }
.book-back {
  background: linear-gradient(135deg, #7a1420, #3d0810);
  transform: rotate(5deg) translate(16px, 6px);
  box-shadow: 0 2px 6px rgba(0,0,0,0.6);
}
.book-front {
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(ellipse at 30% 20%, rgba(255,255,255,0.07), transparent 55%),
    linear-gradient(145deg, #1b1512, #050403);
  border: 1px solid rgba(212,175,55,0.35);
  box-shadow: inset 0 0 0 7px #0a0807, inset 0 0 0 8px rgba(212,175,55,0.5), -6px 0 0 -1px #000, 0 20px 30px rgba(0,0,0,0.55);
  transform: rotate(-4deg);
}
.book-front { overflow: hidden; }
.book-front img { width: 122%; max-width: none; height: auto; mix-blend-mode: screen; /* PNG has an opaque black ground */ }
.book-corner {
  position: absolute; width: 22px; height: 22px;
  border: 0 solid var(--gold);
}
.book-corner.tl { top: 14px; left: 14px; border-top-width: 2px; border-left-width: 2px; }
.book-corner.tr { top: 14px; right: 14px; border-top-width: 2px; border-right-width: 2px; }
.book-corner.bl { bottom: 14px; left: 14px; border-bottom-width: 2px; border-left-width: 2px; }
.book-corner.br { bottom: 14px; right: 14px; border-bottom-width: 2px; border-right-width: 2px; }
.book-shadow {
  position: absolute;
  left: 6%; right: -4%; bottom: -26px; height: 32px;
  background: radial-gradient(ellipse at center, rgba(0,0,0,0.75), transparent 70%);
  filter: blur(6px);
}
.book-cue {
  position: absolute;
  left: 50%; bottom: -3.4rem;
  transform: translateX(-50%);
  white-space: nowrap;
  padding: 0.45em 1.1em;
  background: rgba(0,0,0,0.7);
  border: 1px solid var(--gold-trim);
  border-radius: 999px;
  text-align: center;
  font-size: 0.72rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--gold);
}

/* Open: magazine-style reading view */
.book-reader {
  width: min(100%, 560px);
  background: rgba(0,0,0,0.88);
  border: 1px solid var(--gold-trim);
  border-radius: 4px;
  padding: clamp(1.25rem, 4vw, 2rem);
  box-shadow: 0 24px 60px rgba(0,0,0,0.6);
}
.book-reader:not([hidden]) { animation: book-open 0.5s var(--ease) both; }
@keyframes book-open { from { opacity: 0; transform: scale(0.94) translateY(14px); } to { opacity: 1; transform: none; } }
.book-masthead { text-align: center; padding-bottom: 1.1rem; margin-bottom: 1.1rem; border-bottom: 1px solid var(--hairline); }
.masthead-name {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.9rem, 6vw, 2.7rem);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold);
  line-height: 1.05;
}
.masthead-sub {
  display: block;
  margin-top: 0.4em;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.05rem;
  color: var(--text-muted);
}
.page-counter {
  position: absolute;
  z-index: 5;
  font-family: var(--font-body);
  font-size: 0.72rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--gold-bright);
  background: rgba(0,0,0,0.7);
  padding: 0.35em 0.75em;
  border-radius: 999px;
  top: 0.75rem; left: 0.75rem;
}

/* ---------- Book pages: two templates, turning on a 3D hinge in a shared track ---------- */
.book-spread {
  position: relative;
  perspective: 1500px;
  -webkit-perspective: 1500px;
}
.book-track {
  /* Fixed per breakpoint (mobile override below) instead of auto/min-height, so the frame
     itself never resizes as pages turn or an accordion opens/closes — only the tallest
     realistic state (a content page with its longest description expanded) is what it needs
     to fit; every shorter page just sits centered inside it (see justify-content below).
     --book-track-h was measured empirically: true max natural page height (photo + copy +
     open accordion, including the three two-paragraph "note" pages — Tailored Bespoke
     Accents, Bespoke Tress Liberation, Extended Care) across 320/390/501/560/600/768/900px
     (mobile) and 901/1024/1280/1920px (desktop) topped out at 511px mobile / 517px desktop;
     these values give a ~60-80px comfortable margin above that measured max. */
  --book-track-h: 640px;
  --book-track-pad-top: 2.5rem; /* clears the page-counter pill floating above */
  position: relative;
  overflow: hidden;
  height: var(--book-track-h);
  padding-top: var(--book-track-pad-top);
  display: flex;
  flex-direction: column;
  /* flex-start (not center): centering recalculated per page based on that page's own natural
     height, so the photo/title visibly shifted up or down every time you moved Prev/Next
     between pages of different heights — the "section adjusts itself" complaint, even though
     the outer frame itself had already stopped resizing. Content-template pages now anchor to
     a constant top position (flush after the page-counter padding) and stay at their natural
     height, so any leftover frame space appears below them, never above, and the photo never
     moves. Divider-template pages opt back into a centered look via their own explicit height
     below (data-template="divider"), which fills the whole frame so their EXISTING internal
     centering (already flex/justify-content: center on that rule) has the full frame to center
     within — a deliberate, book-like title-page treatment, distinct from photo pages on
     purpose. align-items: stretch (the default) keeps .book-unit at its full block width,
     preserving each template's own internal layout untouched. */
  justify-content: flex-start;
}
.book-unit[hidden] { display: none; }
/* Safety net: if any page's content somehow exceeds the fixed frame (an unusually long future
   description, extreme browser zoom, etc.), it scrolls internally instead of clipping or
   growing the frame. calc() (not a % max-height) so it resolves identically for the normal-flow
   resting page and the position: absolute is-turning/is-under pages below. */
.book-unit {
  max-height: calc(var(--book-track-h) - var(--book-track-pad-top));
  overflow-y: auto;
}

/* The page underneath: sits flat, no rotation, revealed once the turning page's
   backface hides past 90°. */
.book-unit.is-under,
.book-unit.is-turning {
  background: #0a0705; /* opaque "paper" — without this the stacked layer beneath shows through
    in every gap between glyphs, since both sit at the exact same position during the turn */
}
/* is-under/is-turning are position: absolute, so they're removed from the flex flow above and
   would otherwise anchor to the track's padding-box top edge (i.e. under the counter pill,
   top-pinned) regardless of the resting page's position — a visible vertical snap right as a
   turn starts/ends. top: var(--book-track-pad-top) (instead of relying on the ancestor's
   padding, which absolute children don't get pushed by) puts them at the exact same top offset
   #book-track's flex-start already rests content-template pages at, and height is left auto —
   so a content page lands at the same Y turning as it does resting, no jump. Divider-template
   pages get their own explicit height (see data-template="divider" below) that applies
   regardless of is-under/is-turning, so they still fill the whole frame and center internally
   the same way turning as resting. */
.book-unit.is-under {
  position: absolute;
  top: var(--book-track-pad-top); left: 0; right: 0;
  z-index: 1;
}

/* The page actually turning. Forward hinges on its LEFT edge (spine), rotating away from
   0deg to -180deg; backward hinges on the RIGHT edge, unfolding from 180deg back to 0deg —
   a mirrored motion rather than the same animation reversed, so it reads as "undoing" a turn
   rather than a different effect. backface-visibility hides the mirrored/blank reverse of the
   page through the invisible 90deg-270deg arc, which is what reveals .is-under beneath it. */
.book-unit.is-turning {
  position: absolute;
  top: var(--book-track-pad-top); left: 0; right: 0;
  z-index: 2;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.55);
}
.book-unit.is-turning::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
}
.book-unit.is-turning.turn-fwd {
  transform-origin: left center;
  animation: book-turn-fwd 0.75s cubic-bezier(0.45, 0, 0.55, 1) forwards;
}
.book-unit.is-turning.turn-fwd::after {
  /* Darkest right at the spine, fading toward the free edge — a fold shadow, not a flat tint. */
  background:
    linear-gradient(to right, rgba(0,0,0,0.62) 0%, rgba(0,0,0,0.2) 30%, transparent 58%),
    linear-gradient(to right, rgba(212,175,55,0.2) 0%, transparent 7%);
  animation: book-turn-shadow 0.75s cubic-bezier(0.45, 0, 0.55, 1) forwards;
}
.book-unit.is-turning.turn-back {
  transform-origin: right center;
  animation: book-turn-back 0.75s cubic-bezier(0.45, 0, 0.55, 1) forwards;
}
.book-unit.is-turning.turn-back::after {
  background:
    linear-gradient(to left, rgba(0,0,0,0.62) 0%, rgba(0,0,0,0.2) 30%, transparent 58%),
    linear-gradient(to left, rgba(212,175,55,0.2) 0%, transparent 7%);
  animation: book-turn-shadow 0.75s cubic-bezier(0.45, 0, 0.55, 1) forwards;
}
@keyframes book-turn-fwd {
  from { transform: rotateY(0deg); }
  to   { transform: rotateY(-180deg); }
}
@keyframes book-turn-back {
  from { transform: rotateY(180deg); }
  to   { transform: rotateY(0deg); }
}
@keyframes book-turn-shadow {
  0%, 100% { opacity: 0; }
  45%, 55% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .book-unit.is-turning.turn-fwd,
  .book-unit.is-turning.turn-back {
    animation: none;
    transform: none;
    box-shadow: none;
  }
  .book-unit.is-turning.turn-fwd::after,
  .book-unit.is-turning.turn-back::after {
    animation: none;
    opacity: 0;
  }
}

/* Content template: real service photo + copy, same visual weight as the old single layout.
   Client spec: the 15 portrait photos "must scale natively without cutting off their faces" --
   object-fit: cover crops to fill this box, and none of the real photos (mostly near-square,
   some portrait) match the 3:2 landscape box exactly, so cover was slicing off the top/bottom
   of every face. contain shows the complete photo always, letterboxed (against the box's own
   black background, so the bars are nearly invisible) when a photo's ratio doesn't fill the box. */
.book-unit-photo { aspect-ratio: 3 / 2; overflow: hidden; border-radius: 2px; border: 1px solid var(--hairline); margin-bottom: 1.1rem; background: #000; }
.book-unit-photo img { width: 100%; height: 100%; object-fit: contain; object-position: 50% 50%; }
.book-unit-copy .unit-dek {
  display: block;
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-dim);
  margin-bottom: 0.5em;
}
.book-unit-copy h3 { color: var(--gold); font-size: clamp(1.3rem, 3.6vw, 1.6rem); margin-bottom: 0.4em; }
.book-unit-copy p { color: var(--text-muted); font-size: 0.92rem; margin-bottom: 0.85em; }
.book-unit-copy p:last-child { margin-bottom: 0; }

/* Client requirement: the description must be "unhidden on the bottom" and must appear as an
   "organic accordion-style sliding text drop... without requiring the user to click a rigid
   down-arrow link button." So .book-page-copy is just plain visible text (no click-to-expand,
   no collapsed state) — the "accordion-style sliding drop" is instead a one-shot entrance
   animation replayed automatically by main.js (playTextDrop) every time a page becomes current,
   not a toggle the visitor has to operate. */
.book-page-copy.is-revealing { animation: book-text-drop 0.6s var(--ease); }
@keyframes book-text-drop {
  from { opacity: 0; transform: translateY(-14px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Divider template: intro / category dividers / closing page — section-head styling, no photo.
   :not([hidden]) keeps this from fighting .book-unit[hidden]{display:none} above — same
   specificity, so without it, a hidden divider page would still render as a flex box. */
.book-unit[data-template="divider"]:not([hidden]) {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
  min-height: 13rem;
  /* #book-track is flex-start now (see above), so without an explicit height a divider page
     would just sit at its own short natural height like a content page — this is what still
     gives divider pages the full frame to center within, keeping their existing "title page
     centered in the middle" look. Applies to this selector regardless of is-turning/is-under
     state (unaffected by :not([hidden]) — that class combo is never hidden while turning). */
  height: calc(var(--book-track-h) - var(--book-track-pad-top));
}
.book-unit[data-template="divider"] h3 { color: var(--gold); font-size: clamp(1.5rem, 4vw, 2rem); margin-bottom: 0; }
.book-unit[data-template="divider"] .ornament { margin: 1.1rem 0; }
.book-unit[data-template="divider"] .book-page-lede { max-width: 30rem; }
.book-unit[data-template="divider"] .book-page-lede + p { color: var(--text-muted); margin-bottom: 0; } /* the closing page's second line only — single-paragraph dividers are unaffected */
.book-unit[data-template="divider"].closing .book-page-lede { margin-bottom: 0.5em; }
.book-unit[data-template="divider"].closing .btn { margin-top: 1.4rem; }

.gold-rule { display: block; width: 64px; height: 1px; background: var(--gold); margin: 0 0 1rem; }
.book-page-lede { font-family: var(--font-display); font-size: 1.15rem; font-style: italic; color: var(--text); }

/* Footer nav row: explicit Prev/Next + a "PAGE 01 / 20" counter, the primary visible navigation
   alongside the existing tap-to-turn cue. The book wraps at both ends (see main.js), so neither
   button is ever disabled. */
.book-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  margin-top: 1.25rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--hairline);
}
.book-nav-btn {
  flex: 0 0 auto;
  background: none;
  border: 1px solid var(--gold-trim);
  border-radius: 999px;
  color: var(--gold);
  font-family: var(--font-body);
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  padding: 0.55em 1.05em;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.3s var(--ease), color 0.3s var(--ease);
}
.book-nav-btn:hover, .book-nav-btn:focus-visible { background: var(--gold); color: var(--black); }
.book-nav-counter {
  flex: 1 1 auto;
  text-align: center;
  font-family: var(--font-body);
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-muted);
  white-space: nowrap;
}
@media (max-width: 360px) {
  .book-nav { gap: 0.4rem; }
  .book-nav-btn { padding: 0.5em 0.7em; font-size: 0.64rem; letter-spacing: 0.1em; }
  .book-nav-counter { font-size: 0.62rem; letter-spacing: 0.1em; }
}
.book-close {
  display: block;
  margin: 1.4rem auto 0;
  background: none;
  border: 0;
  color: var(--gold-dim);
  font-size: 0.7rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  cursor: pointer;
  padding: 0.6em 1em;
}
.book-close:hover, .book-close:focus-visible { color: var(--gold); }
.flipbook[data-state="open"] .book-cover { display: none; }


/* ---------- Flip-book surface: the client's velvet + gold tray photo ---------- */
/* Plain black shell — the section itself (title/ornament/description live directly in it,
   via .container.section-head) must show no tray texture. Only .flipbook-tray below is textured. */
.flipbook-surface {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--black);
  padding-block: clamp(3rem, 7vw, 5rem);
}
/* Scoped to just the button + book block, not the whole section, so the texture doesn't bleed
   up behind the title/description above it. Its own padding keeps the tray from looking cropped
   tight against the book. */
.flipbook-tray {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding-block: clamp(2rem, 5vw, 3.5rem);
}
.flipbook-tray::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: url("../assets/flipbook-velvet-tray.jpg") center 55% / cover no-repeat;
  background-attachment: fixed; /* ties the `cover` crop to the viewport instead of .flipbook-tray's
    own box, which resizes on every book/page/accordion state change and otherwise makes the
    backdrop visibly rescale and shift along with it */
  /* feather every edge into the surrounding black so it reads as an extension of the photo */
  -webkit-mask-image: radial-gradient(ellipse 72% 78% at 50% 50%, #000 42%, transparent 100%);
  mask-image: radial-gradient(ellipse 72% 78% at 50% 50%, #000 42%, transparent 100%);
}
.flipbook-tray::after { /* gentle vignette keeps the reading view legible */
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: radial-gradient(ellipse at 50% 50%, transparent 35%, rgba(0,0,0,0.35) 100%);
}


/* ---------- Flip-book surface: mobile only (desktop rules above are untouched) ---------- */
@media (max-width: 900px) {
  /* Own fixed frame height for this breakpoint: measured max natural page height at mobile
     widths (320/390/501/560/600/768/900px) topped out at 511px (.book-reader's width caps at
     560px, so text reflow — and thus page height — barely differs from desktop above ~560px;
     this value is intentionally close to the desktop one for that reason), plus a comfortable
     margin. See the --book-track-h comment on .book-track above for the full measurement note. */
  .book-track { --book-track-h: 620px; }
  /* Brighter, more vibrant velvet + gold, closer to the hero photo's tone */
  .flipbook-tray::before {
    background-attachment: scroll; /* `fixed` has known rendering quirks on some mobile browsers
      (iOS Safari especially) — this codebase already avoids it on mobile elsewhere (see the
      layer-far handling in main.js). Desktop keeps `fixed` (set in the base rule above); mobile
      falls back to a stable min-height on .flipbook-tray below instead, so the backdrop still
      doesn't visibly rescale across the common open-book states at this breakpoint. */
    background-position: center 42%;
    filter: brightness(1.3) saturate(1.05) contrast(1.05);
    -webkit-mask-image: radial-gradient(ellipse 120% 105% at 50% 50%, #000 62%, transparent 100%);
    mask-image: radial-gradient(ellipse 120% 105% at 50% 50%, #000 62%, transparent 100%);
  }
  /* Floor sized to the tallest realistic mobile content state (an open page with its longest
     description expanded, measured at ~972px on a 390px-wide viewport) so the box stops
     shrinking below that point and the backdrop stays visually stable for the common cases. */
  /* Scoped to the OPEN book specifically: the closed cover doesn't vary in height, and the
     shifting complaint was about page-to-page/accordion changes once open, not the one-time
     closed-to-open transition. Applying the floor unconditionally left a lot of empty gold
     plate below the closed cover. .flipbook-tray is the ANCESTOR of #book-reader (not a
     descendant of .flipbook's data-state), so :has() is what actually lets this be scoped
     correctly without moving the floor onto the reader panel itself. */
  .flipbook-tray:has(#book-reader:not([hidden])) { min-height: 980px; }
  .flipbook-tray::after { background: none; }
  /* The peeking back cover: a deep, muted bronze-brown leather instead of saturated crimson */
  .book-back {
    background: linear-gradient(135deg, #4a2a14, #26130a);
    box-shadow: 0 2px 8px rgba(0,0,0,0.55), inset 0 0 0 1px rgba(183,121,36,0.35);
    transform: rotate(4deg) translate(10px, 5px);
  }
}
