/* ============================================================
   FONTS — self-hosted (latin subsets, woff2, ~128KB total).
   No third-party request, no render-blocking @import chain; the two
   critical files are <link rel="preload">ed from every page's head.
   ============================================================ */
@font-face{
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('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: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/cormorant-garamond-600-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: 'Jost';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/jost-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: 'Jost';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/jost-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;
}

  /* ============================================================
     DESIGN TOKENS
     One motion duration + one easing curve for the entire site.
     Fast enough to feel expensive; the long settle reads as WEIGHT,
     which is the right semantic for gold.
     ============================================================ */
  :root{
    color-scheme: light;

    --motion-duration: 220ms;
    --motion-ease: cubic-bezier(0.16, 1, 0.3, 1);
    --motion-slow: 900ms;

    /* Warm ivory — the brand's own ground (their logo, their FB, their product
       photography all sit on cream). Two warm tones do the separation; never pure
       white, which flattens and glares against the gold. */
    --ground:   #f4efe4;
    --panel:    #ece5d7;
    --hairline: rgba(40, 34, 24, .14);

    /* ONE accent. On a light ground the gold must be a RICHER, darker gold or it
       vanishes into the cream — a pale champagne only works on black. Used on
       hover, active states, and hairline marks; the button stays outlined. */
    --gold:        #856118;   /* AA-legible on cream (4.9:1) and still a rich antique gold */
    --gold-bright: #c39a34;   /* decorative marks/fills only — too light for text on cream */

    /* Warm charcoal — matches the near-black of the LOVE, BELLE logo. */
    --ink:   #241f18;
    --muted: #6f6556;         /* AA-legible secondary text (5:1) */

    --font-display: "Cormorant Garamond", "Bodoni MT", "Didot", Georgia, serif;
    --font-body: "Jost", "Century Gothic", "Segoe UI", system-ui, sans-serif;
    /* Geometric, letter-spaced — echoes the LOVE, BELLE wordmark in the logo. */
    --font-geo: "Jost", "Century Gothic", "Futura", system-ui, sans-serif;
    /* Mono is the SPEC voice: carat, mm, metal purity, ring size.
       Gives tabular figures for free. */
    --font-mono: "Jost", "Century Gothic", ui-monospace, monospace;

    --step: clamp(18px, 4vw, 32px);
  }

  *, *::before, *::after { box-sizing: border-box; }

  html{
    /* Chrome hardening — these are what make a dark site feel sealed
       rather than like a document. */
    overscroll-behavior-y: none;
    -webkit-tap-highlight-color: transparent;
    scrollbar-width: none;
  }
  html::-webkit-scrollbar{ display: none; width: 0; height: 0; }

  body{
    margin: 0;
    background: var(--ground);
    color: var(--ink);
    font-family: var(--font-body);
    -webkit-font-smoothing: antialiased;
    overflow-x: hidden;
  }

  ::selection{ background: rgba(166, 124, 34, .22); color: var(--ink); }

  :focus-visible{ outline: 2px solid var(--gold); outline-offset: 3px; border-radius: 2px; }

  /* ============================================================
     SITE HEADER — transparent over the hero, then a cream blurred
     bar with a hairline once you've scrolled past it. Fixed, so it
     never affects the scroll math the 3D stage depends on.
     ============================================================ */
  .site-header{
    position: fixed; inset: 0 0 auto 0; z-index: 50;
    display: flex; align-items: center; justify-content: space-between;
    gap: 24px; padding: 18px clamp(16px, 4vw, 40px);
    background: transparent;
    border-bottom: 1px solid transparent;
    transition: background var(--motion-duration) var(--motion-ease),
                border-color var(--motion-duration) var(--motion-ease),
                padding var(--motion-duration) var(--motion-ease),
                opacity var(--motion-slow) var(--motion-ease),
                transform var(--motion-slow) var(--motion-ease);
  }
  /* Hidden through the intro — the ring gets the stage alone. It arrives with the hero
     headline (html.hero-in), and only when JS is driving; without JS it stays visible. */
  html.home.js .site-header{ opacity: 0; transform: translateY(-14px); pointer-events: none; }
  html.home.js.hero-in .site-header{ opacity: 1; transform: none; pointer-events: auto; }
  .site-header.scrolled{
    background: color-mix(in srgb, var(--ground) 82%, transparent);
    -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
    border-bottom-color: var(--hairline);
    padding-top: 12px; padding-bottom: 12px;
  }
  /* Wordmark echoes the logo: LOVE, BELLE letter-spaced, with a little brilliant
     seated on the first O (the O reads as the ring, the diamond crowns it). */
  .wordmark{
    display: inline-block;
    font-family: var(--font-geo); font-size: 15px; font-weight: 500;
    letter-spacing: .34em; text-transform: uppercase; color: var(--ink);
    text-decoration: none; white-space: nowrap; padding-left: .34em;
  }
  .wordmark .wm-o{ position: relative; display: inline-block; letter-spacing: 0; margin-right: .34em; }
  .wordmark .wm-dia{
    position: absolute; left: 50%; top: -0.58em; transform: translateX(-50%);
    width: .52em; height: .58em; overflow: visible;
  }
  .wordmark .wm-dia .gem{ fill: var(--ink); }
  .wordmark .wm-dia .facet{ fill: none; stroke: var(--ground); stroke-width: 1.1; stroke-linejoin: round; }
  .site-nav{ display: flex; gap: clamp(18px, 2.4vw, 34px); }
  .site-nav a, .header-cta{
    font-family: var(--font-mono);
    font-size: 11px; letter-spacing: .2em; text-transform: uppercase;
    color: var(--ink); text-decoration: none;
    position: relative; padding: 6px 0;
  }
  /* the signature premium detail: a left-anchored underline that wipes in on hover */
  .site-nav a::after{
    content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
    background: var(--gold); transform: scaleX(0); transform-origin: left;
    transition: transform var(--motion-duration) var(--motion-ease);
  }
  .site-nav a:hover::after, .site-nav a:focus-visible::after{ transform: scaleX(1); }
  .header-cta{
    border: 1px solid var(--gold); padding: 9px 20px; color: var(--ink);
    transition: background var(--motion-duration) var(--motion-ease), color var(--motion-duration) var(--motion-ease);
  }
  .header-cta:hover{ background: var(--gold); color: var(--ground); }
  .menu-toggle{ display: none; }

  @media (max-width: 860px){
    .site-nav, .header-cta{ display: none; }
    .menu-toggle{
      display: inline-flex; flex-direction: column; justify-content: center; gap: 5px;
      width: 40px; height: 34px; padding: 0; border: 0; background: transparent; cursor: pointer;
    }
    .menu-toggle span{ display: block; height: 1.5px; width: 24px; background: var(--ink);
      transition: transform var(--motion-duration) var(--motion-ease), opacity var(--motion-duration) var(--motion-ease); }
    .menu-open .menu-toggle span:nth-child(1){ transform: translateY(6.5px) rotate(45deg); }
    .menu-open .menu-toggle span:nth-child(2){ transform: translateY(-6.5px) rotate(-45deg); }
  }

  /* Full-screen mobile menu */
  .mobile-menu{
    position: fixed; inset: 0; z-index: 45;
    display: flex; flex-direction: column; justify-content: center; gap: 6px;
    padding: 12vh clamp(24px, 8vw, 60px) 8vh;
    background: var(--ground);
    opacity: 0; visibility: hidden; transform: translateY(-8px);
    transition: opacity var(--motion-duration) var(--motion-ease),
                transform var(--motion-duration) var(--motion-ease),
                visibility 0s linear var(--motion-duration);
  }
  .menu-open .mobile-menu{ opacity: 1; visibility: visible; transform: none; transition-delay: 0s; }
  .mobile-menu a.m-link{
    font-family: var(--font-display); font-size: clamp(30px, 9vw, 52px); font-weight: 500;
    color: var(--ink); text-decoration: none; padding: 8px 0; line-height: 1.1;
    border-bottom: 1px solid var(--hairline);
  }
  .mobile-menu .btn{ align-self: start; }
  .mobile-menu .socials{ margin-top: auto; }

  /* ============================================================
     STAGE — the single persistent WebGL canvas.
     The intro and the hero are two camera states of THIS scene.
     It is never remounted; remounting at the boundary causes a
     one-frame flash that destroys the "one shot" illusion.
     ============================================================ */
  #stage{
    position: fixed; inset: 0; z-index: 1;
    pointer-events: none;
    /* opacity is driven by scroll: the ring fades out as it exits */
    will-change: opacity;
  }
  /* On cream this reads as a soft photographer's backdrop light behind the ring —
     a warm-white bloom, not a gold glow. */
  #stage-glow{
    position: absolute; left: 50%; top: 46%;
    width: min(135vw, 940px); aspect-ratio: 1;
    transform: translate(-50%, -50%);
    background: radial-gradient(circle at 50% 45%,
      rgba(255, 252, 244, .75) 0%,
      rgba(249, 240, 220, .35) 32%,
      transparent 68%);
    filter: blur(12px);
  }
  #scene{
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    display: block;
    opacity: 0;                                   /* revealed on first rendered frame */
    transition: opacity var(--motion-duration) var(--motion-ease);
  }
  #scene.is-live{ opacity: 1; }
  /* A whisper of warm edge-shade for depth — never the dark vignette of the old theme. */
  #stage-vignette{
    position: absolute; inset: 0;
    background: radial-gradient(ellipse at 50% 46%, transparent 60%, rgba(120, 96, 46, .07) 100%);
  }

  /* Skeleton → live handoff. No percentage counter: we reserve the box,
     run a whisper-faint shimmer, and cross-fade the canvas in once the
     FIRST FRAME has actually rendered. If the ring loads fast, this is
     never even seen. Gating on `load` instead would flash an empty canvas. */
  #skeleton{
    position: absolute; left: 50%; top: 50%;
    width: min(52vmin, 420px); aspect-ratio: 1;
    transform: translate(-50%, -50%);
    border-radius: 50%;
    background: var(--panel);
    overflow: hidden;
    opacity: 1;
    transition: opacity var(--motion-duration) var(--motion-ease);
  }
  /* No JS → no canvas is ever coming → never show a "loading" shimmer for it. */
  html:not(.js) #skeleton{ display: none; }
  #skeleton::after{
    content: "";
    position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent, rgba(0,0,0,.05) 50%, transparent);
    transform: translateX(-100%);
    animation: shimmer 1.5s ease-in-out infinite;
  }
  @keyframes shimmer{ to { transform: translateX(100%); } }
  #skeleton.is-done{ opacity: 0; }

  #stage-fallback{
    position: absolute; inset: 0;
    display: none; place-items: center;
    text-align: center; padding: 8vw;
    color: var(--muted); font-family: var(--font-display);
    font-size: clamp(15px, 2.4vw, 19px);
  }
  #stage-fallback b{ display: block; font-size: 24vmin; margin-bottom: .3em; }

  /* Scroll progress — a hairline, not a UI element */
  #progress{
    position: fixed; left: 0; top: 0; height: 1px; width: 0; z-index: 40;
    background: linear-gradient(90deg, var(--gold), var(--gold-bright));
  }

  /* ============================================================
     LAYOUT
     ============================================================ */
  main{ position: relative; z-index: 2; }
  .wrap{ width: min(1080px, 100% - var(--step) * 2); margin-inline: auto; }

  /* --- ACT 0 — INTRO: the ring alone. No text. Scroll drives it. --- */
  #intro{
    height: 150svh;          /* enough runway that a fast flick can't blow past it,
                                without the page reading as empty scroll */
    position: relative;
    pointer-events: none;
  }
  .scroll-cue{
    position: sticky; top: calc(100svh - 96px);
    display: flex; flex-direction: column; align-items: center; gap: 10px;
    font-family: var(--font-mono);
    font-size: 10px; letter-spacing: .38em; text-transform: uppercase;
    color: var(--muted);
    transition: opacity var(--motion-duration) var(--motion-ease);
  }
  .scroll-cue .rail{
    width: 1px; height: 44px;
    background: linear-gradient(180deg, transparent, var(--hairline) 30%, var(--gold));
    position: relative; overflow: hidden;
  }
  .scroll-cue .rail::after{
    content: ""; position: absolute; left: 0; top: -44px;
    width: 1px; height: 44px;
    background: linear-gradient(180deg, transparent, var(--gold-bright));
    animation: cue 2.2s cubic-bezier(.7,0,.3,1) infinite;
  }
  @keyframes cue{ to { transform: translateY(88px); } }
  html.past-intro .scroll-cue{ opacity: 0; }

  /* --- ACT 1 — HERO: the headline reveals as the ring settles ---
     The section is taller than the viewport and its contents are sticky, so the
     hero PINS and holds for ~100svh of scrolling — it parks. That dwell is what stops
     an over-scroll from throwing the user straight past the headline. The ring keeps
     turning slowly through the hold, so parked never reads as frozen. */
  #hero{
    height: 200svh;
    position: relative;
  }
  .hero-inner{
    position: sticky; top: 0;
    height: 100svh;
    display: grid; align-content: center;
  }

  /* STATIC HERO (html.static-hero) — phones, and any visit where the 3D can't
     load (boot.js decides). No canvas, no scroll runway: the page opens straight
     on the headline, and none of Three.js or the ring model is ever fetched. */
  html.static-hero #stage,
  html.static-hero .scroll-cue,
  html.static-hero .intro-caption{ display: none; }
  html.static-hero #intro{ height: 0; }
  html.static-hero #hero{ height: auto; }
  html.static-hero .hero-inner{ position: static; height: auto;
    padding: calc(96px + 6svh) 0 9svh; }
  /* Registration brackets — the cheapest editorial signal on a dark
     ground: 1px hairlines, no assets, no weight. */
  .brackets{ position: relative; padding: 26px 0 26px 26px; }
  .brackets::before, .brackets::after{
    content: ""; position: absolute; width: 16px; height: 16px;
    border-color: var(--hairline); border-style: solid;
  }
  .brackets::before{ top: 0; left: 0; border-width: 1px 0 0 1px; }
  .brackets::after{ bottom: 0; left: 0; border-width: 0 0 1px 1px; }

  .eyebrow{
    font-family: var(--font-mono);
    font-size: 10px; letter-spacing: .38em; text-transform: uppercase;
    color: var(--gold); margin: 0 0 var(--step);
  }

  /* The headline is ALWAYS in the DOM — it is masked, never withheld.
     That is the SEO + LCP contract: the intro hides the text visually,
     it does not remove it. Each line rides inside an overflow:hidden
     wrapper so the reveal is a pure transform (no layout shift). */
  h1{
    font-family: var(--font-display);
    font-weight: 600; font-size: clamp(56px, 13vw, 168px);
    line-height: .9; letter-spacing: -0.01em; margin: 0;
  }
  h1 .mask{ display: block; overflow: hidden; }
  h1 .mask > span{
    display: block;
    /* Charcoal, like the LOVE, BELLE logo — with the faintest warm shift so the huge
       serif has depth on cream without turning into gold foil. */
    background: linear-gradient(180deg, #2c2519, #17130d);
    -webkit-background-clip: text; background-clip: text; color: transparent;
  }
  /* masked only once we know JS is driving the reveal — otherwise the headline is
     simply visible, which is also the SEO/LCP contract */
  html.js h1 .mask > span{ transform: translateY(105%); transition: transform var(--motion-slow) var(--motion-ease); }
  html.js.hero-in h1 .mask > span{ transform: translateY(0); }
  html.js h1 .mask:nth-child(2) > span{ transition-delay: 90ms; }

  .lede{
    font-size: clamp(15px, 1.9vw, 19px); line-height: 1.65;
    color: var(--muted); max-width: 30ch; margin: var(--step) 0 0;
  }
  html.js #hero .lede{
    opacity: 0; transform: translateY(14px);
    transition: opacity var(--motion-slow) var(--motion-ease),
                transform var(--motion-slow) var(--motion-ease);
    transition-delay: 220ms;
  }
  html.js.hero-in #hero .lede{ opacity: 1; transform: none; }

  /* Honest labelling: the hero ring is brand atmosphere, not a SKU.
     No price, no product name, no "order this" — so it is a
     representative design, and it says so. */
  .disclaimer{
    font-family: var(--font-mono);
    font-size: 9.5px; letter-spacing: .24em; text-transform: uppercase;
    color: var(--muted); margin: calc(var(--step) * 1.4) 0 0;
  }
  html.js .disclaimer{ opacity: 0; transition: opacity var(--motion-slow) var(--motion-ease) 400ms; }
  html.js.hero-in .disclaimer{ opacity: 1; }

  /* --- ACT 2 — CONTENT: no ring. Clean, editorial, readable. --- */
  .chapter{
    position: relative;
    padding: clamp(56px, 9svh, 104px) 0;
    border-top: 1px solid var(--hairline);
    background: var(--ground);          /* opaque: the ring is gone by here */
    /* Below-fold chapters skip layout/paint until they approach the viewport.
       1400px estimate: over-estimating is cheap (shrinkage happens below the
       viewport), under-estimating makes Safari jump on upward first-renders and
       the progress hairline drift — the two-plate chapters run ~1800px. */
    content-visibility: auto;
    contain-intrinsic-size: auto 1400px;
  }
  .chapter-grid{
    display: grid; gap: clamp(24px, 3.5vw, 48px);
    grid-template-columns: 1fr;
  }
  @media (min-width: 900px){
    .chapter-grid{ grid-template-columns: 0.9fr 1.1fr; align-items: start; }
    /* The lead column PARKS beside the taller detail column as you scroll it —
       turning what was an empty gap into a deliberate editorial hold. */
    .chapter-lead{ position: sticky; top: 15svh; align-self: start; }
  }
  .chapter-lead .body{ max-width: 36ch; }

  .chapter-index{
    font-family: var(--font-mono);
    font-size: 10px; letter-spacing: .3em; text-transform: uppercase;
    color: var(--gold);
    display: flex; align-items: center; gap: 14px;
  }
  .chapter-index::after{ content: ""; flex: 1; height: 1px; background: var(--hairline); }

  h2{
    font-family: var(--font-display);
    font-weight: 500; font-size: clamp(30px, 5vw, 60px);
    line-height: 1.04; margin: var(--step) 0 0; color: var(--ink);
    text-wrap: balance;
  }
  .body{ color: var(--muted); line-height: 1.7; margin: var(--step) 0 0; max-width: 46ch; }

  /* The spec register — mono, tabular, hairline-ruled. */
  .spec{ list-style: none; margin: var(--step) 0 0; padding: 0; }
  .spec li{
    display: flex; justify-content: space-between; gap: 20px;
    padding: 14px 0; border-bottom: 1px solid var(--hairline);
    font-family: var(--font-mono); font-size: 12px; letter-spacing: .08em;
    font-variant-numeric: tabular-nums;
  }
  .spec dt, .spec .k{ color: var(--muted); text-transform: uppercase; letter-spacing: .2em; font-size: 10px; }
  .spec .v{ color: var(--ink); text-align: right; }
  /* Phones: a two-ended row with a long right-aligned value reads ragged —
     stack each row into label-over-value instead. */
  @media (max-width: 640px){
    .spec li{ flex-direction: column; align-items: flex-start; gap: 5px; }
    .spec .v{ text-align: left; }
  }

  .stats{ display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); }
  .stat{ background: var(--panel); padding: clamp(22px, 3vw, 34px); }
  .stat .n{ font-family: var(--font-display); font-size: clamp(34px, 5vw, 52px); line-height: 1; color: var(--gold); font-variant-numeric: tabular-nums; }
  .stat .k{ font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .24em; text-transform: uppercase; color: var(--muted); margin-top: 12px; }

  /* PHOTO SLOTS.
     The image's own width/height attributes reserve the space, so swapping in the real
     photograph causes ZERO layout shift. The box is ALWAYS visible — no clip-path, no
     JS-gated reveal. Hiding a photo and depending on JS to un-hide it is exactly how it
     went missing before; a picture must never be one failed observer away from blank.
     The only motion is a gentle one-way settle that can't hide anything: the image
     eases from a hair over-scale to rest, and does nothing at all without JS. */
  .plate{
    position: relative; aspect-ratio: 4 / 5; background: var(--panel);
    /* clip, NOT hidden: overflow:hidden makes the plate a scroll container, which
       captures any view() timeline inside it and freezes the parallax at 0 progress.
       clip clips identically but stays transparent to scroll timelines. */
    border: 1px solid var(--hairline); overflow: clip;
  }
  .plate.wide{ aspect-ratio: 4 / 3; }
  .plate img{ width: 100%; height: 100%; object-fit: cover; display: block; }
  html.js .plate img{ transform: scale(1.06); transition: transform 1.4s var(--motion-ease);
    transition-delay: calc(var(--i, 0) * 90ms); }
  /* delay 0 here: the entrance stagger must never lag the hover zoom (the delay in
     the base rule would otherwise apply to BOTH directions of the hover transition) */
  html.js .plate.in img{ transform: scale(1); transition-delay: 0s; }
  /* Curtain reveal: a ground-colour panel slides off the plate as it enters view,
     the image settling beneath it. In grids (ui.js sets --i) the curtains cascade. */
  html.js .plate::after{
    content: ""; position: absolute; inset: -1px; z-index: 2;
    background: var(--ground);
    transform-origin: bottom;
    transition: transform 900ms var(--motion-ease);
    transition-delay: calc(var(--i, 0) * 90ms);
  }
  html.js .plate.in::after{ transform: scaleY(0); }
  /* Hover: the photo leans in, gently. Hover-capable, motion-tolerant pointers only —
     under reduced motion even a 1ms hover snap is still a spatial jump. */
  @media (hover: hover) and (prefers-reduced-motion: no-preference){
    html.js .plate.in:hover img{ transform: scale(1.045); }
  }
  /* Scroll parallax: the photo drifts slowly inside its frame as it crosses the
     viewport. Native scroll timeline on `translate` (a separate property, so it never
     fights the transform-based settle/zoom). The img is top-anchored with all 10% of
     its oversize hanging BELOW the frame, so the travel must stay within [-9%, 0] —
     any positive translate would pull the top edge into view. */
  @supports (animation-timeline: view()){
    @media (prefers-reduced-motion: no-preference){
      html.js .plate img{
        height: 110%;
        animation: plate-parallax linear both;
        animation-timeline: view();
      }
      @keyframes plate-parallax{
        from{ translate: 0 -9%; }
        to{ translate: 0 0; }
      }
    }
  }
  .plate figcaption{
    position: absolute; left: 0; right: 0; bottom: 0;
    padding: 30px 16px 12px;
    font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .24em;
    text-transform: uppercase; color: var(--ink);
    background: linear-gradient(180deg, transparent, rgba(244,239,228,.45) 45%, rgba(244,239,228,.9));
  }
  figure{ margin: 0; }

  /* Lookbook — an editorial grid, deliberately off-rhythm rather than a neat row. */
  .lookbook{ display: grid; gap: 14px; grid-template-columns: 1fr; }
  @media (min-width: 760px){
    .lookbook{ grid-template-columns: repeat(6, 1fr); }
    .lookbook .l1{ grid-column: span 4; }
    .lookbook .l2{ grid-column: span 2; align-self: end; }
    .lookbook .l3{ grid-column: span 2; }
    .lookbook .l4{ grid-column: span 4; align-self: start; }
  }

  /* CTA — outlined, never filled with gold. Restraint is what keeps the
     metal in the hero the most saturated thing on the page. */
  .cta{ text-align: center; }
  .cta .body{ margin-inline: auto; }
  .btn{
    display: inline-block; margin-top: calc(var(--step) * 1.5);
    font-family: var(--font-mono); font-size: 11px;
    letter-spacing: .24em; text-transform: uppercase;
    color: var(--ink); text-decoration: none;
    padding: 18px 34px; border: 1px solid var(--gold);
    background: transparent;
    transition: background var(--motion-duration) var(--motion-ease),
                color var(--motion-duration) var(--motion-ease),
                transform var(--motion-duration) var(--motion-ease);
  }
  /* Outline → fill on hover, text inverts to the cream ground. */
  .btn:hover{ background: var(--gold); color: var(--ground); transform: translateY(-1px); }
  .btn:active{ transform: translateY(0) scale(.99); }

  footer{ padding: 44px 0 56px; border-top: 1px solid var(--hairline); }
  .colophon{ font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .22em; text-transform: uppercase; color: var(--muted); display: flex; flex-wrap: wrap; gap: 8px 22px; }
  .socials{ display: flex; flex-wrap: wrap; gap: 20px; margin-top: 22px; }
  .socials a{ font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .22em; text-transform: uppercase; color: var(--muted); text-decoration: none; transition: color var(--motion-duration) var(--motion-ease); }
  .socials a:hover{ color: var(--gold); }

  /* Anchored sections land below the fixed header, not under it. */
  section[id], #contact{ scroll-margin-top: 92px; }

  /* ============================================================
     INQUIRY — a client-side quote builder. No backend: the button
     assembles the picks into a pre-filled Messenger message and opens
     the exact inbox they already run the business from.
     ============================================================ */
  .inquiry{ text-align: center; }
  .inquiry .body{ margin-inline: auto; }
  .trust-strip{
    display: flex; flex-wrap: wrap; justify-content: center; gap: 10px 26px;
    margin: var(--step) auto 0; max-width: 780px;
    font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .12em;
    text-transform: uppercase; color: var(--muted);
  }
  .trust-strip span{ display: inline-flex; align-items: center; gap: 10px; }
  .trust-strip span::before{ content: ""; width: 4px; height: 4px; border-radius: 50%; background: var(--gold); }

  .builder{
    max-width: 720px; margin: calc(var(--step) * 1.6) auto 0;
    text-align: left; display: grid; gap: 22px;
  }
  .builder-group{ display: grid; gap: 10px; }
  .builder-group > .glabel{
    font-family: var(--font-mono); font-size: 10px; letter-spacing: .24em;
    text-transform: uppercase; color: var(--muted);
  }
  .chips{ display: flex; flex-wrap: wrap; gap: 8px; }
  .chip{
    font-family: var(--font-mono); font-size: 12px; letter-spacing: .04em;
    color: var(--ink); background: transparent;
    border: 1px solid var(--hairline); border-radius: 40px;
    padding: 9px 16px; cursor: pointer;
    transition: border-color var(--motion-duration) var(--motion-ease),
                background var(--motion-duration) var(--motion-ease),
                color var(--motion-duration) var(--motion-ease);
  }
  .chip:hover{ border-color: var(--gold); }
  .chip[aria-pressed="true"]{ background: var(--gold); border-color: var(--gold); color: var(--ground); }
  /* Touch: chips are the site's main form control — give thumbs a ≥44px target. */
  @media (pointer: coarse){
    .chip{ padding: 12px 18px; }
  }

  .builder-actions{ display: grid; gap: 16px; justify-items: center; margin-top: calc(var(--step) * 0.5); }
  .builder-note{ font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .1em; color: var(--muted); text-align: center; max-width: 46ch; }
  .msg-preview{ font-family: var(--font-mono); font-size: 11.5px; line-height: 1.65; color: var(--ink);
    background: var(--panel); border: 1px solid var(--hairline); border-radius: 10px;
    padding: 14px 18px; max-width: 54ch; text-align: left; }
  .channel-row{ display: flex; flex-wrap: wrap; justify-content: center; gap: 20px; }
  .channel-row a{
    font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .2em; text-transform: uppercase;
    color: var(--muted); text-decoration: none; position: relative; padding: 4px 0;
    transition: color var(--motion-duration) var(--motion-ease);
  }
  .channel-row a:hover{ color: var(--ink); }

  /* ============================================================
     INQUIRY FORM (inquire.html) — MVP is front-end only: ui.js swaps
     the form for the thank-you panel on submit. Nothing is posted or
     stored yet; wire a backend here later.
     ============================================================ */
  .inq-form{
    max-width: 640px; margin: calc(var(--step) * 1.6) auto 0;
    display: grid; gap: 22px; text-align: left;
  }
  .inq-form .field{ display: grid; gap: 10px; }
  .inq-form .glabel{
    font-family: var(--font-mono); font-size: 10px; letter-spacing: .24em;
    text-transform: uppercase; color: var(--muted);
  }
  .inq-form input,
  .inq-form select,
  .inq-form textarea{
    width: 100%;
    font-family: var(--font-body); font-size: 15px; color: var(--ink);
    background: rgba(255, 255, 255, .38);
    border: 1px solid var(--hairline); border-radius: 10px;
    padding: 14px 16px;
    transition: border-color var(--motion-duration) var(--motion-ease);
  }
  .inq-form textarea{ resize: vertical; }
  .inq-form select{ cursor: pointer; }
  .inq-form input:hover, .inq-form select:hover, .inq-form textarea:hover,
  .inq-form input:focus, .inq-form select:focus, .inq-form textarea:focus{ border-color: var(--gold); }
  .inq-form ::placeholder{ color: var(--muted); opacity: .7; }
  .inq-form .btn{ margin-top: calc(var(--step) * 0.5); justify-self: center; min-width: 260px; cursor: pointer; }
  .inq-form .builder-note{ justify-self: center; }

  .form-success{ display: grid; gap: 4px; justify-items: center; padding-top: calc(var(--step) * 1.2); }
  .form-success[hidden]{ display: none; }
  .form-success .body{ margin-inline: auto; }
  .form-success:focus{ outline: none; }

  /* ============================================================
     MOTION SYSTEM — reveal-on-view entrances (ui.js arms these).
     IntersectionObserver, per element — cheap, one-shot, and it never
     fights the WebGL loop the way a scrubbed timeline would. Every
     hidden state is scoped to html.js; without JS all content shows.
     ============================================================ */

  /* Body copy and blocks: a soft rise with a breath of focus pulling in.
     Blur is kept small and one-shot — it composites, it doesn't reflow. */
  html.js .rise{ opacity: 0; transform: translateY(18px); filter: blur(5px);
    transition: opacity var(--motion-slow) var(--motion-ease),
                transform var(--motion-slow) var(--motion-ease),
                filter var(--motion-slow) var(--motion-ease); }
  html.js .rise.in{ opacity: 1; transform: none; filter: none; }
  html.js .rise:nth-child(2){ transition-delay: 70ms; }
  html.js .rise:nth-child(3){ transition-delay: 140ms; }

  /* Functional UI is never animated: the quote builder renders instantly and is
     always clickable — a hidden-but-interactive form is worse than an unanimated one. */
  html.js .builder.rise{ opacity: 1; transform: none; filter: none; transition: none; }
  /* Large containers keep the rise but drop the blur — an animated Gaussian over a
     near-viewport-sized region re-rasterises every frame on a budget phone. */
  html.js .gallery-grid.rise, html.js .faq.rise, html.js .settings-grid.rise{ filter: none; }

  /* Word-masked headings (ui.js splits h2.rise + .page-banner h1 into
     .w masks). Each word rises out of its own clip with a small stagger —
     the block itself is never hidden, so the .rise fallback can't double-hide. */
  .split .w{ display: inline-block; overflow: hidden; vertical-align: bottom;
    /* 0.05em inline breathing room: Cormorant's word-initial 'y' paints ~0.04em left
       of its box and would shear flat against a tighter mask. */
    padding: 0.06em 0.05em 0.14em; margin: -0.06em -0.05em -0.14em; }
  .split .w > span{ display: inline-block; }
  html.js .split{ opacity: 1; transform: none; filter: none; }
  html.js .split .w > span{
    transform: translateY(115%);
    transition: transform 800ms var(--motion-ease);
    transition-delay: calc(var(--wi, 0) * 45ms);
  }
  html.js .split.in .w > span{ transform: none; }

  /* Staggered groups (ui.js sets --i on the children of .spec/.stats/.trust-strip
     and observes the container): rows fan in one after another. No blur here —
     these can be many elements, and budget phones pay for every filter. */
  html.js .stagger.rise{ opacity: 1; transform: none; filter: none; }
  html.js .stagger > *{
    opacity: 0; transform: translateY(14px);
    transition: opacity 700ms var(--motion-ease), transform 700ms var(--motion-ease);
    /* 120ms base keeps rows AFTER the sibling body paragraph (70ms nth-child slot),
       so a chapter always reveals top-to-bottom, never rows-before-prose. */
    transition-delay: calc(120ms + var(--i, 0) * 60ms);
  }
  html.js .stagger.in > *{ opacity: 1; transform: none; }

  /* ============================================================
     REDUCED MOTION — the intro is a courtesy, never a toll booth.
     ============================================================ */
  @media (prefers-reduced-motion: reduce){
    *, *::before, *::after{ animation: none !important;
      transition-duration: 1ms !important; transition-delay: 0s !important; }
    #intro{ height: 0; }                  /* skip the intro entirely */
    .scroll-cue{ display: none; }         /* a cue inside a 0-height intro would
                                             overflow-paint on top of the hero */
    /* Every neutralisation below repeats the hiding rule's own html.js-prefixed
       selector — a bare .rise (0,1,0) LOSES to html.js .rise (0,2,1) and becomes
       dead code; same specificity + later source order is what actually wins. */
    html.js h1 .mask > span{ transform: none; }
    html.js #hero .lede, html.js .disclaimer{ opacity: 1; transform: none; }
    html.js .rise{ opacity: 1; transform: none; filter: none; }
    html.js .plate img{ transform: none; }
    html.js .split .w > span{ transform: none; }
    html.js .stagger > *{ opacity: 1; transform: none; }
    html.js .plate::after{ content: none; }        /* no curtain at all */
    /* pullquote sweep + plate parallax live inside (prefers-reduced-motion:
       no-preference) blocks — they simply don't exist here. */
  }

  /* ============================================================
     SUB-PAGE building blocks (Material / Stone / Work / Lookbook / Contact)
     ============================================================ */
  .page-banner{ padding: clamp(104px, 17vh, 150px) 0 clamp(30px, 5vh, 60px); border-bottom: 1px solid var(--hairline); }
  .page-banner .eyebrow{ margin: 0 0 var(--step); }
  .page-banner h1{ font-family: var(--font-display); font-weight: 600; line-height: .96;
    font-size: clamp(40px, 8vw, 104px); letter-spacing: -0.01em; margin: 0; color: var(--ink);
    background: none; -webkit-text-fill-color: currentColor; text-wrap: balance; }
  .page-banner .lede{ max-width: 52ch; color: var(--muted); font-size: clamp(16px, 2.2vw, 21px);
    line-height: 1.6; margin: var(--step) 0 0; }

  /* The banner (and the home ticker) already draw the hairline; the first chapter
     adding its own border-top would stack into a 2px double rule at that seam. */
  .page-banner + .chapter, .ticker + .chapter{ border-top: 0; }

  .site-nav a[aria-current="page"]{ color: var(--gold); }
  .site-nav a[aria-current="page"]::after{ transform: scaleX(1); }
  .mobile-menu a.m-link[aria-current="page"]{ color: var(--gold); }

  /* Placeholder plate with a centred label (used where a real photo will go) */
  .plate .plate-label{ position: absolute; inset: 0; display: grid; place-content: center; justify-items: center;
    gap: 10px; text-align: center; padding: 22px; }
  .plate .plate-label .pl-title{ font-family: var(--font-display); font-size: clamp(18px, 2.4vw, 24px); color: var(--ink); }
  .plate .plate-label .pl-note{ font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .22em;
    text-transform: uppercase; color: var(--muted); max-width: 30ch; line-height: 1.6; }

  /* Gallery photo card — a real photo with its title + one-line note on a cream scrim
     anchored at the bottom, so the copy stays readable over the image (unlike the
     centred placeholder label). Sits under the curtain, so it reveals with the photo. */
  .plate .plate-cap{ position: absolute; left: 0; right: 0; bottom: 0;
    display: grid; gap: 5px; padding: 26px 16px 15px; text-align: left;
    background: linear-gradient(180deg, transparent, rgba(244,239,228,.5) 40%, rgba(244,239,228,.95) 82%); }
  .plate .plate-cap .pl-title{ font-family: var(--font-display); font-size: clamp(17px, 2.2vw, 23px);
    line-height: 1.06; color: var(--ink); }
  .plate .plate-cap .pl-note{ font-family: var(--font-mono); font-size: 10px; letter-spacing: .03em;
    line-height: 1.5; color: var(--muted); max-width: 34ch; }

  /* A free-standing plate spanning the whole .wrap would run 1350px tall at desktop
     (4:5 of 1080px). Cap it and centre it so it reads deliberate, never stranded
     against a blank right half; plates inside grids keep their own column sizing. */
  .wrap > .plate{ max-width: 480px; margin-top: var(--step); margin-inline: auto; }
  .wrap > .plate.wide{ max-width: 720px; }
  /* Inside a chapter grid, a plate that follows other content gets breathing room;
     a figure that IS the column starts flush with the lead. */
  .chapter-grid .plate:not(:first-child){ margin-top: var(--step); }

  /* Gallery grids. Base: fluid auto-fit so tiles always fill the row on phones.
     The count classes hold 4- and 5-tile grids to TWO columns below desktop — a
     3-column band would strand an orphan beside a blank cell. From 900px: rows of
     three; .g4 = 2×2, then one row of four once tiles clear their 230px floor
     (~1040px); .g5 = three up top + a CENTRED pair. No orphan, no blank track. */
  .gallery-grid{ display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); margin-top: var(--step); }
  .gallery-grid.g4, .gallery-grid.g5{ grid-template-columns: repeat(auto-fit, minmax(min(100%, 290px), 1fr)); }
  @media (min-width: 660px) and (max-width: 899.98px){
    /* two-column band: g5's lone fifth tile centres at tile width */
    .gallery-grid.g5 > :nth-child(5){ grid-column: 1 / -1; width: calc(50% - 8px); justify-self: center; }
  }
  @media (min-width: 900px){
    .gallery-grid, .gallery-grid.g4, .gallery-grid.g5{ grid-template-columns: repeat(6, 1fr); }
    .gallery-grid > *{ grid-column: span 2; }
    .gallery-grid.g4 > *{ grid-column: span 3; }   /* 2×2 until tiles fit four across */
    .gallery-grid.g5 > :nth-child(4){ grid-column: 2 / span 2; }
  }
  @media (min-width: 1040px){
    .gallery-grid.g4{ grid-template-columns: repeat(4, 1fr); }
    .gallery-grid.g4 > *{ grid-column: auto; }
  }

  /* Settings guide — the brand's own square explainer cards. Shown 2-up on desktop
     (~500px each) so the baked-in body copy stays readable; one per row on mobile.
     An odd last card is centred at the same width — never stranded half-row left. */
  .settings-grid{ display: grid; gap: 16px; margin-top: var(--step);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 360px), 1fr)); }
  .settings-grid img{ width: 100%; height: auto; display: block;
    border: 1px solid var(--hairline); border-radius: 6px; }
  /* 820px, not 760: the grid itself only fits two 360px tracks once the wrap
     clears ~736px (viewport ≈ 800px) — engaging earlier would shrink the last
     card to half width while the grid above it is still single-column. */
  @media (min-width: 820px){
    .settings-grid > :nth-child(odd):last-child{
      grid-column: 1 / -1; justify-self: center; width: calc(50% - 8px);
    }
  }
  /* Hover lift rides the `translate` property — the stagger entrance owns
     `transform` (html.js .stagger.in > * pins it to none at higher specificity),
     so a transform-based lift would be dead on the primary JS path. The html.js
     override re-declares the full transition list so border/shadow ease at the
     site duration while the entrance keeps its stagger delay. */
  @media (hover: hover) and (prefers-reduced-motion: no-preference){
    .settings-grid img{
      transition: translate var(--motion-duration) var(--motion-ease),
                  border-color var(--motion-duration) var(--motion-ease),
                  box-shadow var(--motion-duration) var(--motion-ease);
    }
    html.js .settings-grid > img{
      transition: opacity 700ms var(--motion-ease), transform 700ms var(--motion-ease),
                  translate var(--motion-duration) var(--motion-ease),
                  border-color var(--motion-duration) var(--motion-ease),
                  box-shadow var(--motion-duration) var(--motion-ease);
      transition-delay: calc(120ms + var(--i, 0) * 60ms), calc(120ms + var(--i, 0) * 60ms), 0s, 0s, 0s;
    }
    .settings-grid img:hover{
      translate: 0 -4px;
      border-color: color-mix(in srgb, var(--gold) 45%, var(--hairline));
      box-shadow: 0 16px 32px rgba(40, 34, 24, .09);
    }
  }

  /* Brand ticker — a slow editorial marquee bridging the hero into the chapters.
     Two identical halves; the loop shifts one half-width, so the seam never shows.
     Under reduced motion the global animation kill leaves a quiet static strip. */
  .ticker{ position: relative; z-index: 2; overflow: hidden;
    border-block: 1px solid var(--hairline); padding: 15px 0; background: var(--ground); }
  .ticker-track{ display: flex; width: max-content; animation: ticker 38s linear infinite; }
  .ticker-half{ display: flex; align-items: center; gap: 46px; padding-right: 46px; }
  .ticker-half span{ font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .3em;
    text-transform: uppercase; color: var(--muted); white-space: nowrap; }
  .ticker-half .tick-dia{ color: var(--gold-bright); font-size: 8px; }
  @keyframes ticker{ to{ transform: translateX(-50%); } }

  /* FAQ — native <details> disclosure, fully keyboard-accessible */
  .faq{ margin-top: var(--step); border-top: 1px solid var(--hairline); max-width: 780px; }
  .faq details{ border-bottom: 1px solid var(--hairline); }
  .faq summary{ cursor: pointer; list-style: none; padding: 20px 0; gap: 20px;
    display: flex; justify-content: space-between; align-items: center;
    font-family: var(--font-display); font-size: clamp(16px, 2.1vw, 21px); color: var(--ink); }
  .faq summary::-webkit-details-marker{ display: none; }
  .faq summary::after{ content: "+"; font-family: var(--font-mono); color: var(--gold); font-size: 18px; flex: 0 0 auto; }
  .faq details[open] summary::after{ content: "\2013"; }
  .faq .a{ color: var(--muted); line-height: 1.75; margin: 0; padding: 0 0 22px; max-width: 70ch; }

  .section-cta{ text-align: center; }
  .section-cta .body{ margin-inline: auto; }
  .pullquote{ font-family: var(--font-display); font-weight: 500; font-size: clamp(26px, 4.4vw, 46px);
    line-height: 1.15; color: var(--ink); max-width: 20ch; margin: 0 auto; text-align: center; text-wrap: balance; }
  /* Scroll-driven ink sweep: the quote starts faint and inks in left-to-right as it
     crosses the viewport — native scroll timeline, compositor-only. Browsers without
     animation-timeline (and no-JS) simply show the quote in full ink via .rise.
     The whole trick lives inside prefers-reduced-motion: no-preference — reduce-mode
     users get plain full-ink text with no cascade fight to lose. */
  @supports (animation-timeline: view()){
    @media (prefers-reduced-motion: no-preference){
      html.js .pullquote{
        background: linear-gradient(90deg,
          var(--ink) 45%,
          color-mix(in srgb, var(--ink) 22%, transparent) 55%);
        background-size: 210% 100%;
        background-position: 100% 0;
        -webkit-background-clip: text; background-clip: text;
        color: transparent;
        animation: quote-sweep linear both;
        animation-timeline: view();
        animation-range: entry 40% cover 70%;
      }
      /* the sweep IS the entrance — neutralise the generic rise so they don't stack */
      html.js .pullquote.rise{ opacity: 1; transform: none; filter: none; transition: none; }
      @keyframes quote-sweep{
        from{ background-position: 100% 0; }
        to{ background-position: 0% 0; }
      }
    }
  }
  /* The supporting line under a centred pullquote centres with it. */
  .pullquote + .body{ margin-inline: auto; text-align: center; }

  /* home: "read the full story" links on the overview chapters */
  .more-link{ display: inline-flex; align-items: center; gap: 10px; margin-top: var(--step);
    font-family: var(--font-mono); font-size: 11px; letter-spacing: .2em; text-transform: uppercase;
    color: var(--ink); text-decoration: none; }
  .more-link::after{ content: "\2192"; color: var(--gold); transition: transform var(--motion-duration) var(--motion-ease); }
  .more-link:hover::after{ transform: translateX(4px); }

  /* Intro caption — a quiet cover line on the left while the ring is alone. It fades
     as the hero headline arrives, so the intro's side is never bare. */
  .intro-caption{
    position: sticky; top: 38svh;
    width: min(1080px, 100% - var(--step) * 2); margin-inline: auto;
    pointer-events: none;
    transition: opacity var(--motion-slow) var(--motion-ease), transform var(--motion-slow) var(--motion-ease);
  }
  .intro-caption .eyebrow{ margin: 0; }
  .intro-caption .intro-line{
    font-family: var(--font-display); font-weight: 500;
    font-size: clamp(24px, 3.8vw, 46px); line-height: 1.08; color: var(--ink);
    max-width: 12ch; margin: 16px 0 0; text-wrap: balance;
  }
  html.hero-in .intro-caption{ opacity: 0; transform: translateY(-10px); }
  @media (max-width: 640px){ .intro-caption{ top: 20svh; } .intro-caption .intro-line{ max-width: 9ch; } }
