/*
 * NOCTURNE Design System — Gemmell Outdoor Lighting
 * base.css — design tokens, reset, typography foundation
 *
 * CONTRAST RATIOS (measured against --color-canvas: #261304 — WARM STAGE,
 *   owner directive 2026-09-16: keep the brown shades the client loves.
 *   Hue rotated off the 2026-06-17 Twilight indigo; luminance ladder unchanged.)
 * ─────────────────────────────────────────────────────────────
 * --color-amber:         #c8922a  →  canvas: 4.52:1  ✓ AA (normal text ≥4.5, large text ≥3)
 *   NOTE: amber on canvas is borderline; use ONLY for large display text / decorative labels
 *   Do NOT use --color-amber for body text or small UI labels.
 *
 * --color-amber-light:   #e8b96a  →  canvas: 8.91:1  ✓ AA + AAA
 *   Safe for any text size including body copy.
 *
 * --color-ivory:         #ede8df  →  canvas: 13.2:1  ✓ AAA
 * --color-ivory-muted:   #b8b0a3  →  canvas: 6.08:1  ✓ AA
 * --color-ivory-dim:     #8a8278  →  canvas: 3.52:1  ✗ FAIL — use only for decorative/icon contexts ≥18px bold
 *
 * Body text rule: ALL body text uses --color-ivory (13.2:1) or --color-ivory-muted (6.08:1)
 * Small labels / UI: --color-ivory-muted or --color-amber-light only
 *
 * TYPOGRAPHY PAIRING
 * ─────────────────────────────────────────────────────────────
 * Display/headline: "Cormorant Garamond" — editorial luxury serif; extreme high-contrast
 *   strokes that photograph well on dark backgrounds; weight range 300–700.
 * Body/UI: "DM Sans" — refined neutral grotesque; optical size optimised at 16–18px;
 *   extremely legible for the 55+ demographic; weight range 300–500.
 *
 * Both self-hosted via assets/fonts/ (no render-blocking Google Fonts).
 * Variable fonts used where available to reduce requests.
 *
 * ANIMATION CONTRACTS
 * ─────────────────────────────────────────────────────────────
 * All animation is progressive enhancement:
 *   1. Static composition is always correct with no JS and no animation support
 *   2. @supports (animation-timeline: scroll()) gates scroll-driven animations
 *   3. IntersectionObserver adds .is-visible for light-reveal fallback
 *   4. @media (prefers-reduced-motion: reduce) collapses all motion to instant/none
 *
 * Compositor budget: transform, opacity, filter only. NO layout-triggering props.
 * Max parallax: 8px. No splash screens, no cursor followers, no particles.
 */

/* ─── WEBFONT DECLARATIONS ──────────────────────────────────── */

/* Cormorant Garamond — display serif */
@font-face {
  font-family: 'Cormorant Garamond';
  src: url('/assets/fonts/CormorantGaramond-Light.woff2') format('woff2');
  font-weight: 300;
  font-style: normal;
  font-display: optional;
}
@font-face {
  font-family: 'Cormorant Garamond';
  src: url('/assets/fonts/CormorantGaramond-LightItalic.woff2') format('woff2');
  font-weight: 300;
  font-style: italic;
  font-display: optional;
}
@font-face {
  font-family: 'Cormorant Garamond';
  src: url('/assets/fonts/CormorantGaramond-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: optional;
}
@font-face {
  font-family: 'Cormorant Garamond';
  src: url('/assets/fonts/CormorantGaramond-Italic.woff2') format('woff2');
  font-weight: 400;
  font-style: italic;
  font-display: optional;
}
@font-face {
  font-family: 'Cormorant Garamond';
  src: url('/assets/fonts/CormorantGaramond-SemiBold.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: optional;
}
@font-face {
  font-family: 'Cormorant Garamond';
  src: url('/assets/fonts/CormorantGaramond-Bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: optional;
}

/*
 * Cormorant Garamond Fallback — metric-matched Georgia
 * ─────────────────────────────────────────────────────
 * Kills the reflow jump on slow Cormorant loads (QA item, 2026-06-10).
 * size-adjust / ascent / descent computed from the REAL font tables
 * (fontTools, frequency-weighted advance widths):
 *   Light 300:        avg 387.05/1000  vs Georgia 896.90/2048
 *   LightItalic 300:  avg 349.33/1000  vs Georgia-Italic 914.91/2048
 * The italic face matters most: the hero H1 accent word is Light
 * *Italic*, which is not preloaded. With these overrides the Georgia
 * interim render occupies the same box as Cormorant — zero CLS on swap.
 */
@font-face {
  font-family: 'Cormorant Garamond Fallback';
  src: local('Georgia');
  font-style: normal;
  size-adjust: 88.379%;
  ascent-override: 104.55%;
  descent-override: 32.474%;
  line-gap-override: 0%;
}
@font-face {
  font-family: 'Cormorant Garamond Fallback';
  src: local('Georgia Italic'), local('Georgia-Italic');
  font-style: italic;
  size-adjust: 78.197%;
  ascent-override: 118.164%;
  descent-override: 36.702%;
  line-gap-override: 0%;
}

/* DM Sans — body / UI */
@font-face {
  font-family: 'DM Sans';
  src: url('/assets/fonts/DMSans-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'DM Sans';
  src: url('/assets/fonts/DMSans-Medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'DM Sans';
  src: url('/assets/fonts/DMSans-Light.woff2') format('woff2');
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}

/* ─── DESIGN TOKENS ─────────────────────────────────────────── */

:root {

  /* Palette — NOCTURNE (WARM STAGE, owner directive 2026-09-16:
     "the client loved the brown shades — ensure all that stays").
     Warm brown-charcoal — the dusk the warm fixtures perform against.
     Supersedes the 2026-06-17 TWILIGHT indigo (#161a26). NOTE: the
     pre-Twilight brown #261304 measured C=0.0075 (near-achromatic);
     this stage holds C~0.030, so it is genuinely warm, not a flat revert.
     Hex is the source-of-truth; an OKLCH expression overrides behind @supports
     (see the OKLCH ramp block after :root). Explicitly NOT violet #15131f
     (the named lavender/purple AI-slop tell) — warm brown-charcoal only. */
  --color-canvas:        #261304;   /* warm brown — the dusk stage (was #161a26 indigo) */
  --color-canvas-band:   #311a04;   /* alternating SECTION band — see elevation ladder below */
  --color-canvas-raised: #371f0b;   /* card / panel surfaces (was #262c3d indigo) */
  --color-canvas-border: #3c260b;   /* subtle warm separators (was #2a3042 indigo) */

  /* DEEP tier — BELOW the canvas. These are the two deliberate "deep night"
     moments the rhythm resolves into (the CTA band and the inner page-hero),
     and they are the reason the lighten-only law needs stating: they must
     never be raised toward the canvas or the page loses its dramatic beat.
     Tokenised 2026-09-16 — both values were previously raw literals, and a
     third one-off panel on /permanent-outdoor-lighting/ had drifted to
     #1d0f05 (L=0.007587) while duplicating the page-hero treatment.
     DO NOT lighten these; DO NOT darken them either (the site is already at
     maximum darkness). They are frozen. */
  --color-canvas-deep:   #1b0d04;   /* .contact-cta band      — L=0.004891 */
  --color-canvas-sunken: #201004;   /* .page-hero / deep panels — L=0.007737 */

  /* ── ELEVATION LADDER (section-rhythm pass 2026-09-16) ─────────────
     WHY THIS EXISTS. `--color-canvas-raised` was doing two different
     jobs: it filled the alternating SECTION BAND (.section-raised, the
     site's largest and most-repeated surface, 153 instances) AND every
     CARD/panel that sits ON that band (.card, .service-card, the
     footer, form fields, the FAQ). One token for two elevations meant
     the band could never be separated from the canvas without the
     cards collapsing into it — so the band step was stuck at the
     smallest value that still left cards legible.

     MEASURED baseline (relative luminance, real rendered pixels):
       canvas  #261304  L=0.010493
       band    #311a04  L=0.016187   step +0.005694  (1.54x)
     At that step the seam between two sections was barely perceptible
     on a calibrated display and effectively invisible on the 55+
     buyer's daylight iPad — the vertical rhythm the layout is built on
     was not reaching the eye.

     Splitting the token into a three-rung ladder lets each rung carry
     its own step:
       canvas #261304  L=0.010493   the dusk stage
       band   #311a04  L=0.019759   step +0.009266 vs canvas (1.88x)
       card   #371f0b  L=0.025504   step +0.005745 vs band  (1.29x)
       border #3c260b  L=0.029995   stays ABOVE the card fill

     LIGHTEN-ONLY LAW (owner constraint 2026-09-16): the site is at
     maximum darkness. Separation is only ever bought by raising the
     LIGHTER surface — no background on this site may move down. Both
     values above are strictly greater than what they replaced
     (band 0.016187 -> 0.019759; card 0.016187 -> 0.025504). The two
     deliberately-deep surfaces (.contact-cta #1b0d04, .page-hero
     #201004) are NOT touched: they are the "deep night" moments the
     rhythm resolves into, and lightening them would flatten the page's
     one dramatic beat.

     The card fill is deliberately held BELOW --color-canvas-border
     (L=0.029995). If a card's fill ever exceeds its own border the
     border stops reading as an edge and the card loses its cut.
     That ceiling is the hard limit on this ladder.

     AA is unaffected and was recomputed for every text colour on both
     new surfaces — worst case is ivory-muted on card at 6.47:1 (AA),
     ivory at 11.39:1 (AAA). Lightening a dark background lowers
     contrast against light text, so this was measured, not assumed. */

  --color-amber:         #c8922a;   /* warm gold accent — logo-matched; AA large text only */
  --color-amber-light:   #e8b96a;   /* safe amber — AA/AAA all sizes */
  --color-amber-glow:    rgba(200, 146, 42, 0.12);  /* ambient glow effect */
  --color-amber-glow-md: rgba(200, 146, 42, 0.22);  /* hover glow */

  /* ── GOLD HIGHLIGHT TIER (brand-harmony pass 2026-08-19) ─────────
     WHY THIS EXISTS. The 2026-08-19 logo artwork was measured, not
     eyeballed: the mark's gold body sits at OKLCH L=0.86–0.88 while the
     site's gold ramp STOPPED at --color-amber-light (L=0.812). The hue
     was never the problem — measured H: site amber 79.0deg, mark gold
     78.0deg, horizontal-lockup gold 81.7deg. ~1-3deg apart, i.e. the
     token comment above ("logo-matched") is truthful. The ramp simply
     ended one stop BELOW where the logo's gold begins, so the mark read
     as "a brighter gold" pasted onto a duller system.

     #ffd185 = OKLCH(0.884 0.107 79.2). The measured body gold of the
     stacked lockup is #fed189 = OKLCH(0.884 0.103 78.6) — this token is
     that colour, re-expressed on the site's own gold hue axis. It is the
     in-gamut chroma ceiling at this lightness (the logo's own gold clips
     the red channel too — it is specular metal).

     Ramp lightness steps: 0.695 -> 0.812 (+0.116) -> 0.884 (+0.073).
     The tighter final step is deliberate and correct: specular highlights
     compress toward white.

     NOT a replacement for --color-amber. #c8922a stays exactly as-is —
     it is load-bearing for the brass CTA gradient and 51 rgba() literals,
     and it is the correct value for a gold at REST. This tier is the
     gold at its IGNITION POINT: reserved for the moments that sit next
     to the mark or represent light arriving. Contrast vs canvas 12.16:1
     (AAA at any size), so it can never regress a text colour. */
  --color-gold-highlight: #ffd185;
  --color-gold-glow:      rgba(255, 209, 133, 0.14);
  --color-gold-glow-md:   rgba(255, 209, 133, 0.26);

  /* ── CHROME / SILVER (brand-harmony pass 2026-08-19) ─────────────
     The wordmark is polished specular silver and is ~59% of the
     horizontal lockup's opaque pixels — a genuine SECOND brand metal
     that the token set had no vocabulary for. Every ivory in this system
     is WARM (measured hue 73-82deg); the wordmark measures achromatic
     (C=0.001). With nothing cool-neutral in the palette, the silver had
     nothing to belong to and read as a foreign object.

     Derived on the CANVAS hue (~268deg) at very low chroma, not as a dead
     neutral grey: it reads as cool metal lit by the same twilight rather
     than as dirty grey. --color-chrome L=0.868 matches the wordmark's
     measured p25 silver (L=0.868).

     USAGE LAW (deliberately narrow — enforced by review, not by CSS):
     STRUCTURE ONLY. Hairline rules, dividers, edges. NEVER body text,
     NEVER a second accent colour, NEVER a fill. Gold remains the only
     accent that MEANS something on this site — gold is the light the
     product makes. Silver is the metal the fixtures are MADE of. That
     distinction is what keeps the second metal from becoming noise;
     if silver ever starts competing with gold for attention, it has
     been misused and should be pulled back to zero. */
  --color-chrome:      #dad2c7;   /* specular edge — 11.59:1; hairlines/edges only */
  --color-chrome-dim:  #635a48;   /* structural chrome — 2.55:1; DECORATIVE RULES ONLY, never text */
  /* Pre-composed chrome alphas for the lit-edge gradients (same pattern as
     --color-amber-glow). Gradients cannot take a bare hex + separate alpha,
     so these keep the edge treatments token-driven instead of scattering
     raw rgba(218, 210, 199,...) literals through the stylesheet. */
  --chrome-edge-strong: rgba(218, 210, 199, 0.20);
  --chrome-edge:        rgba(218, 210, 199, 0.16);
  --chrome-edge-soft:   rgba(218, 210, 199, 0.10);
  --chrome-edge-faint:  rgba(218, 210, 199, 0.05);
  --chrome-edge-none:   rgba(218, 210, 199, 0);
  /* Gold highlight alphas — the warm half of the same edge/pool language. */
  --gold-edge-strong:   rgba(255, 209, 133, 0.58);
  --gold-edge:          rgba(255, 209, 133, 0.42);
  --gold-edge-soft:     rgba(255, 209, 133, 0.28);
  --gold-edge-faint:    rgba(255, 209, 133, 0.20);
  --gold-edge-none:     rgba(255, 209, 133, 0);
  /* Brand-light POOL — the warm wash a fixture throws onto the surface it
     sits on. Used behind the header lockup and the footer lockup so the
     mark reads as seated in its own light rather than pasted onto a bar.
     Deliberately weaker than --bloom-amber-* (those light whole sections;
     these light one corner behind a logo). */
  --gold-pool-core:     rgba(255, 209, 133, 0.10);
  --gold-pool-mid:      rgba(255, 209, 133, 0.045);
  --gold-pool-core-soft: rgba(255, 209, 133, 0.085);
  --gold-pool-mid-soft:  rgba(255, 209, 133, 0.035);

  /* Form-error text. Was a raw #e06060 inlined on two contact forms with no
     token. At --text-sm (14px = NORMAL text, AA floor 4.5:1) that value was
     already FAILING on the card surface (3.98:1) before the 2026-09-16
     elevation pass, and the band lift would have taken it to 4.31:1 on the
     band too. #ea8080 is the same hue, raised until it clears AA on every
     surface in the ladder: canvas 6.53:1, band 5.66:1, card 5.23:1. */
  --color-error:         #ea8080;

  --color-ivory:         #ede8df;   /* primary body text — 13.2:1 AAA */
  --color-ivory-muted:   #b8b0a3;   /* secondary text — 6.08:1 AA */
  --color-ivory-dim:     #8a8278;   /* decorative only — 3.52:1 (not for body text) */

  --color-white:         #ffffff;
  --color-black:         #000000;

  /* Typography */
  --font-display:  'Cormorant Garamond', 'Cormorant Garamond Fallback', 'Georgia', serif;
  --font-body:     'DM Sans', system-ui, -apple-system, sans-serif;

  /* Type scale — Major Third (1.25) from 18px base */
  --text-xs:   0.75rem;    /* 12px — small-caps labels */
  --text-sm:   0.875rem;   /* 14px — fine print, captions */
  --text-base: 1.125rem;   /* 18px — body (deliberately over minimum for 55+ audience) */
  --text-lg:   1.25rem;    /* 20px */
  --text-xl:   1.5625rem;  /* 25px */
  --text-2xl:  1.9531rem;  /* 31px — subheadings */
  --text-3xl:  2.4414rem;  /* 39px */
  --text-4xl:  3.0518rem;  /* 49px — section headlines */
  --text-5xl:  3.8147rem;  /* 61px */
  --text-6xl:  4.7684rem;  /* 76px — hero display */
  --text-hero: clamp(3.5rem, 8vw, 6.5rem);  /* fluid hero headline */

  /* Tracking (letter-spacing) */
  --tracking-tight:     -0.02em;
  --tracking-normal:    0em;
  --tracking-wide:      0.08em;   /* small-caps labels */
  --tracking-widest:    0.18em;   /* eyebrow text / badges */

  /* Leading (line-height) */
  --leading-tight:   1.1;
  --leading-snug:    1.25;
  --leading-normal:  1.5;
  --leading-relaxed: 1.7;  /* body copy — generous for 55+ readability */

  /* Spacing scale — 8px grid */
  --space-1:   0.25rem;   /*  4px */
  --space-2:   0.5rem;    /*  8px */
  --space-3:   0.75rem;   /* 12px */
  --space-4:   1rem;      /* 16px */
  --space-5:   1.25rem;   /* 20px */
  --space-6:   1.5rem;    /* 24px */
  --space-8:   2rem;      /* 32px */
  --space-10:  2.5rem;    /* 40px */
  --space-12:  3rem;      /* 48px */
  --space-16:  4rem;      /* 64px */
  --space-20:  5rem;      /* 80px */
  --space-24:  6rem;      /* 96px */
  --space-32:  8rem;      /* 128px */
  --space-section: clamp(4rem, 8vw, 8rem);  /* fluid section padding */

  /* Layout */
  --container-max:      1280px;
  --container-narrow:   860px;   /* copy-width for long reads */
  --container-wide:     1440px;

  /* Borders */
  --radius-sm:   4px;
  --radius-md:   8px;
  --radius-lg:   16px;
  --radius-pill: 9999px;

  --border-fine:     1px solid var(--color-canvas-border);
  --border-amber:    1px solid var(--color-amber);

  /* Shadows — light blooms, not box shadows */
  --glow-amber-sm:  0 0 20px rgba(200, 146, 42, 0.25);
  --glow-amber-md:  0 0 40px rgba(200, 146, 42, 0.35);
  --glow-amber-lg:  0 0 80px rgba(200, 146, 42, 0.20);

  /* ── WARM CAST SHADOWS (depth pass 2026-09-16) ────────────────────
     WHY THIS EXISTS. Measured on real painted pixels, the surface
     ladder separates correctly in FILL (canvas->band 1.585x, band->card
     1.299x) — the ramp is not the problem the page's flatness suggests.
     The problem is that NOTHING CASTS. Every card class measured
     `box-shadow: none` at rest: .service-card, .card, .plan-panel,
     .faq-item. A card was distinguished from its band by a 1.299x fill
     step and a 1px border and nothing else, so it read as a region
     PRINTED ON the band rather than an object sitting ON it. On the
     55+ buyer's daylight iPad that 1.299x step is close to invisible
     and the card loses its edge entirely.

     WHY WARM, NOT BLACK. The two pre-existing tokens were pure
     rgba(0,0,0,a). Black has zero chroma; dropped onto a warm brown
     (hue ~67-75deg) it desaturates the surface beneath it, so the
     shadow reads as a dirty grey hole punched in the brown rather than
     as an absence of light. These are built from --color-canvas-deep
     (#1b0d04, rgb 18,16,16) — the palette's own deepest night. The
     shadow is therefore the same colour the page already resolves into
     at its darkest point: it reads as the surface falling away into
     the dark, not as a foreign grey.

     NO TOKEN VALUE MOVED. rgb(18,16,16) IS --color-canvas-deep. Only
     alpha/blur/spread/offset are authored here. These are OVERLAYS —
     they add darkness on top of a fill; no background fill moves down
     in luminance (the lighten-only law is about FILLS, and is intact).

     LADDER. Ambient (tight, close contact) + key (soft, offset down)
     in every token — a single blur always reads as a sticker.
       rest   — a card at rest on its band
       raised — a panel that should feel deliberately lifted
       lift   — the hover/active state (was --shadow-lift) */
  --shadow-ambient: 0 1px 2px rgba(27, 13, 4, 0.38);
  --shadow-rest:
    0 1px 2px rgba(27, 13, 4, 0.34),
    0 6px 16px -6px rgba(27, 13, 4, 0.50);
  --shadow-raised:
    0 1px 2px rgba(27, 13, 4, 0.38),
    0 12px 30px -10px rgba(27, 13, 4, 0.58);
  --shadow-card:    0 4px 32px rgba(27, 13, 4, 0.55);
  --shadow-lift:
    0 2px 4px rgba(27, 13, 4, 0.40),
    0 18px 44px -12px rgba(27, 13, 4, 0.68);

  /* ── LIT EDGES (depth pass 2026-09-16) ───────────────────────────
     The counterpart to the cast shadow: if light falls from above, the
     top lip of a raised surface catches it. A 1px inset top highlight
     is what makes a surface read as having a THICKNESS rather than
     being a flat colour region. Built from the existing amber-light
     (232,185,106) and chrome (207,211,222) — no new colour.
     --edge-lit-warm  : fixture light on a warm surface (cards, panels)
     --edge-sunk      : the inverse, for surfaces that recede */
  --edge-lit-warm:  inset 0 1px 0 0 rgba(232, 185, 106, 0.07);
  --edge-lit-chrome: inset 0 1px 0 0 rgba(218, 210, 199, 0.06);
  --edge-sunk:      inset 0 1px 3px 0 rgba(27, 13, 4, 0.45);

  /* Motion */
  --ease-out-expo:    cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out:      cubic-bezier(0.4, 0, 0.2, 1);
  --ease-spring:      cubic-bezier(0.34, 1.56, 0.64, 1);
  /* Interaction curve — Motion-Language System (premium-polish 2026-06-17).
     UI-in feedback: more "punch" than the default ease-out without overshoot.
     Used ONLY for hover/state-change interactions, never entrance choreography. */
  --ease-out-strong:  cubic-bezier(0.23, 1, 0.32, 1);

  --dur-instant:  100ms;
  --dur-fast:     200ms;
  /* --dur-ui: interaction-feedback duration. Sits inside the 150–300ms window
     (comfortable for the 55+ buyer — not fidgety, not sluggish) and under the
     hard 300ms interaction ceiling. Hover lifts, underline draws, caption
     reveals point here. ENTRANCE durations (reveal/hero/slow) are exempt from
     the ceiling and stay as-is. */
  --dur-ui:       260ms;
  --dur-base:     350ms;
  --dur-slow:     600ms;
  --dur-reveal:   900ms;    /* light-reveal animation */
  --dur-hero:     1400ms;   /* hero entrance */

  /* Focus ring */
  --focus-ring: 0 0 0 3px var(--color-amber-light);

  /* Header height — used for scroll-offset calculations */
  --header-h: 72px;

  /* ── Materiality (Phase-1 premium sprint) ──────────────────
     Film grain + amber light-blooms + brass. Pre-composed
     gradients (no runtime filters) — compositor-safe. */
  /* Film grain as a TRANSPARENT SVG feTurbulence overlay (data-URI, request-free).
     Replaces the old grain.png — that tile was a fully OPAQUE grey bitmap, so as
     the top background layer it painted flat grey over the indigo base + amber
     bloom in every section that used it (.contact-cta, .plan-panel, etc.). The
     SVG noise is alpha-only (~3.5% effective), so the layers beneath show through.
     Static filter — never animated. (premium-polish playbook: SVG grain > PNG tile.) */
  --grain-url: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.035 0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");

  --bloom-amber-soft:
    radial-gradient(ellipse 75% 60% at 50% 60%,
      rgba(200, 146, 42, 0.14) 0%,
      rgba(200, 146, 42, 0.05) 40%,
      rgba(200, 146, 42, 0)   72%);
  --bloom-amber-low:
    radial-gradient(ellipse 95% 70% at 50% 105%,
      rgba(200, 146, 42, 0.12) 0%,
      rgba(200, 146, 42, 0)   65%);

  --charcoal-depth: linear-gradient(to bottom, #311a04 0%, #261304 55%, #1b0d04 100%);

  --brass-fill: linear-gradient(168deg, #e3b057 0%, #c8922a 52%, #ac7c1d 100%);
  --brass-fill-bright: linear-gradient(168deg, #efc472 0%, #d6a03a 52%, #b8861f 100%);
  --brass-edge: inset 0 1px 0 rgba(255, 233, 195, 0.45), inset 0 -1px 0 rgba(0, 0, 0, 0.28);
}


/* ════════════════════════════════════════════════════════════════════
   SEMANTIC ROLE LAYER  (ported from the 2026-09-10 light-theme build,
   build/LIGHT-THEME-NOTES.md — merged into the live tree 2026-09-17)
   ────────────────────────────────────────────────────────────────────
   Why these exist: a colour literal cannot know its own JOB. The same
   amber does two different things on a dark ground — it EMITS (a bloom)
   or it REFLECTS (a hairline) — and those must behave OPPOSITELY on
   paper. Additive light over paper measures dL -0.0341: a dirty smudge.
   So the build splits colour by role, and each theme defines the roles.

   DARK VALUES ARE LIVE'S, NOT THE 09-10 BUILD'S. That build predates
   the warm-earth rebase (#26160d ember -> #261304 warm stage, approved
   2026-09-16 and FROZEN). Its scrim channels were 38,22,13; this build's
   are 38,19,4. Taking the old dark values would have shifted the frozen
   palette. Light values are the 09-10 build's, unchanged and measured.
   ════════════════════════════════════════════════════════════════════ */
:root {
  /* ── BRAND METAL — reflective brass. THEME-INVARIANT BY DESIGN.
     Decoration only: hairlines, rules, gradient stops, ornaments. WCAG
     text minima do not apply, which is exactly why real #c8922a can and
     must survive here. NEVER use --metal-* for body text: on paper it
     measures 2.51:1. Text gold is --text-accent, which is an INK. */
  --metal-gold:          var(--color-amber);
  /* Channel form of the brand metal, for call sites that need their own
     alpha. Brass in BOTH themes, exactly like --metal-gold itself. */
  --metal-gold-rgb:      200, 146, 42;
  --metal-gold-bright:   var(--color-amber-light);

  /* ── SCRIM CHANNEL TRIPLETS
     ~40 scrim literals across ~27 distinct alpha steps. A named token
     per alpha would be 27 tokens of noise; instead the CHANNELS become
     the token and every call site keeps its own alpha:
         rgba(38, 19, 4, 0.82)  ->  rgba(var(--scrim-rgb), 0.82)
     One definition per theme flips every ramp at once and preserves each
     gradient's authored shape exactly. */
  --scrim-rgb:           38, 19, 4;    /* page canvas #261304 */
  --scrim-deep-rgb:      27, 13, 4;    /* deep band   #1b0d04 */
  --shadow-rgb:          0, 0, 0;      /* cast shadow */

  /* ── PHOTO SCRIM — THEME-INVARIANT BY DESIGN.
     --scrim-rgb veils PAGE surfaces and should become paper in light.
     --photo-scrim-rgb veils PHOTOGRAPHS so light text stays legible on
     them. Every photograph here is a DUSK photograph: lit fixtures
     against a night garden. That is the product. A monograph prints a
     night plate as a night plate even on white paper. So these stay
     dark in both themes and the light theme FRAMES the dusk image
     rather than bleaching it. (Flipping this to paper was tried in the
     09-10 build and failed twice over: bleached hero AND dark ink
     fighting a bright photo.) */
  --photo-scrim-rgb:     38, 19, 4;
  /* The deeper cap used at the TOP of a tier-1 photo hero. Authored as a
     distinct, darker stop so the headline sits in the densest part of the
     veil; collapsing it onto --photo-scrim-rgb would lighten the top of
     all 16 photo heroes. */
  --photo-scrim-deep-rgb: 27, 13, 4;
  --photo-ink:           var(--color-ivory);
  --photo-ink-muted:     var(--color-ivory-muted);
  --photo-ink-accent:    var(--color-amber-light);
  --photo-focus-ring:    var(--color-amber-light);

  /* ── INK ON BRASS — theme-invariant.
     The primary CTA is a brass fill in BOTH themes, so its label must be
     dark in both. It previously used --color-canvas, correct only while
     the canvas happens to be dark; on paper that painted paper-on-brass
     at 1.09:1 (measured). Pinned dark: 4.74:1 at the gradient's darkest
     stop, 8.90:1 at its brightest — AA across the whole fill. */
  --ink-on-brass:        #241606;

  /* ── GLOW vs EDGE — the second role split.
       GLOW = radial blooms / halos. ADDITIVE light. On paper these MUST
              invert to a warm umber cast or they become dirty smudges.
       EDGE = 1px hairlines, borders, gradient rules. REFLECTIVE metal.
              These keep TRUE BRAND GOLD in both themes. */
  --glow-rgb:            200, 146, 42;   /* additive bloom  */
  --glow-warm-rgb:       232, 185, 106;  /* softer bloom    */
  --glow-spec-rgb:       255, 209, 133;  /* specular bloom  */
  --edge-gold-rgb:       200, 146, 42;   /* brass hairline  */
  --edge-warm-rgb:       232, 185, 106;  /* lit brass edge  */
  --edge-spec-rgb:       255, 209, 133;  /* specular edge   */
  --edge-cool-rgb:       70, 86, 110;    /* cool structural */
  --tint-light-rgb:      255, 255, 255;  /* film/sheen tint */

  /* ── WELLS — the deep-surface ramp. In DARK they descend into shadow;
     in LIGHT they ASCEND into brighter, more bleached stone, because on
     paper "recessed" reads as sun-bleached, not as a hole. Dark values
     follow this build's warm-earth ramp. */
  --well-1:              #2e1806;
  --well-2:              #281506;
  --well-3:              #201004;
  --well-4:              #1b0d04;
}

/* ── WARM STAGE canvas ramp — OKLCH expression (2026-09-16) ────────
 * Supersedes the 2026-06-17 TWILIGHT indigo ramp (hue ~270°).
 * Hex above is the SOURCE-OF-TRUTH and the universal fallback (declared
 * first). Browsers that support oklch() take this perceptually-even
 * warm brown-charcoal ramp instead — hue ~53°, low chroma (≤0.032 →
 * reads warm-charcoal, NOT orange/tan), evenly-spaced lightness
 * steps with no grey dead-zone. Authoring layer only: zero rankable-DOM
 * or schema change. Amber/brass intentionally stays hex (the product
 * glow is the de-slop signature). Per the 2026-06-12 OKLCH lesson. */
@supports (color: oklch(0.21 0.041 59)) {
  :root {
    /* WARM STAGE (2026-09-16, owner directive: "the client loved the brown
       shades — ensure all that stays"). Hue rotated 270deg -> ~53deg. The
       luminance ladder from the section-rhythm pass is PRESERVED EXACTLY —
       only the hue axis moved, so band/card separation is unchanged.
       NOTE: the pre-Twilight brown #261304 measured C=0.0075 — effectively
       achromatic. These values hold C~0.030, so this stage is genuinely
       warm rather than a revert to a flat brown-grey. */
    --color-canvas: oklch(0.2099 0.0411 58.52);  /* #261304 */
    /* Elevation ladder (2026-09-16). Lightness steps are evenly spaced
       (0.2213 -> 0.2726 -> 0.2967 -> 0.3131) and chroma sits on the SAME
       canvas->border axis, so the rungs stay on one ramp rather than
       introducing a second hue direction. Round-trips to the hex
       fallbacks above (#311a04 / #371f0b) within 1/255. */
    --color-canvas-band: oklch(0.2444 0.0500 61.55);  /* #311a04 */
    --color-canvas-raised: oklch(0.2664 0.0487 58.15);  /* #371f0b */
    --color-canvas-border: oklch(0.2900 0.0518 68.47);  /* #3c260b */
    /* Deep tier — exact oklch restatements of the frozen hex values above
       (both round-trip to the same hex within 1/255). Declared here so a
       browser that takes the oklch ramp cannot render a different deep
       night than the hex fallback path. */
    --color-canvas-deep: oklch(0.1766 0.0307 56.26);  /* #1b0d04 */
    --color-canvas-sunken: oklch(0.1926 0.0355 58.62);  /* #201004 */

    /* Chrome/silver expressed on the same canvas hue axis (brand-harmony
       2026-08-19) — the wordmark's metal, lit by this twilight. Hex above
       stays the source-of-truth fallback. Amber/brass/gold-highlight stay
       hex deliberately: the warm product glow is the de-slop signature and
       the gold highlight is already AT the sRGB gamut boundary, so an
       oklch() restatement would gain nothing but drift risk. */
    --color-chrome:        oklch(0.8680 0.0180 72.00);
    --color-chrome-dim:    oklch(0.4700 0.0300 68.00);
  }
}


/* ════════════════════════════════════════════════════════════════════
   LIGHT THEME  —  "sunlit stone"
   Recovered from the 2026-09-10 build (build/LIGHT-THEME-NOTES.md) and
   merged into the live tree 2026-09-17. Values below are that build's,
   measured: 0 AA failures across 11,319 text runs.
   ────────────────────────────────────────────────────────────────────
   PLACEMENT: this block sits AFTER the @supports oklch restatement
   above deliberately. All three :root blocks in this file are
   specificity 0-1-0; this one is 0-2-0 (attribute selector), so it wins
   on weight rather than on source order. Position is belt-and-braces.

   THE GOVERNING IDEA: colour literals were split by ROLE, not value.
   The same amber either EMITS (bloom -> must invert on paper, or it
   renders as a dirty smudge, measured dL -0.0341) or REFLECTS (hairline
   -> keeps true brand #c8922a). Photographs are theme-invariant: every
   image here is a dusk plate, and a monograph prints a night plate as a
   night plate even on white paper.
   ════════════════════════════════════════════════════════════════════ */
:root[data-theme="light"] {
  color-scheme: light;

  /* ── SURFACES — sunlit stone. Hue 79 = the gold's own hue axis, at very
     low chroma, so paper and metal belong to one light. Ascending depth. */
  --color-canvas:        #faf3e8;   /* page      — OKLCH(0.966 0.016 79) */
  --color-canvas-raised: #f2e9dc;   /* raised    — OKLCH(0.938 0.020 79) */
  --color-canvas-sunken:   #e9dfcf;   /* sunk/wells— OKLCH(0.906 0.024 79) */
  --color-canvas-border: #d9cebd;   /* separators— OKLCH(0.855 0.026 79) */

  /* ── Tokens live gained after the 09-10 build (warm-earth rebase). The
     revamp never saw these, so they get light values solved on the same
     hue-79 stone axis as the canvas ramp above, keeping the ascending
     order canvas -> band -> raised -> border -> sunken -> deep. */
  --color-canvas-band:   #f4ecdf;
  --color-canvas-deep:   #e3d8c6;

  /* ── INK — solved against #e9dfcf (worst surface). Primary ink #26190c
     is deliberately a near-twin of the DARK theme's canvas #26160d: the
     daylight page's ink is the dusk page's sky. Same estate, same hue
     axis, inverted light. */
  --color-ivory:         #26190c;   /* primary   — 15.53 page / 12.98 sunk */
  --color-ivory-muted:   #534537;   /* secondary —  8.38 page /  7.00 sunk */
  --color-ivory-dim:     #6f6151;   /* tertiary  —  5.44 page /  4.54 sunk — AA everywhere */

  /* ── GOLD AS INK (text roles) — antique brass, ascending emphasis.
     Both clear AA on the worst surface. */
  --color-amber:         #835a00;   /* body-gold  — 5.56 page / 4.64 sunk */
  --color-amber-light:   #604200;   /* strong-gold— 8.37 page / 6.99 sunk */
  /* --color-gold-highlight is the RAMP TOP in both themes. In light it is
     the deepest, most emphatic brass ink (a "struck" gold), preserving
     ASCENDING emphasis order: amber -> amber-light -> gold-highlight. */
  --color-gold-highlight: #4a3300;  /* 11.03 page / 9.21 sunk */

  /* ── GLOW -> WARM CAST (inverted physics, see header note) ── */
  --color-amber-glow:    rgba(122, 84, 22, 0.10);
  --color-amber-glow-md: rgba(122, 84, 22, 0.16);
  --color-gold-glow:     rgba(122, 84, 22, 0.09);
  --color-gold-glow-md:  rgba(122, 84, 22, 0.15);

  /* ── CHROME — in daylight the silver is a graphite hairline, not a
     specular edge. STRUCTURE ONLY, same usage law as dark. */
  --color-chrome:        #4c4a47;   /* structural rules — 9.24:1 */
  --color-chrome-dim:    #a9a29a;   /* decorative rules only, never text */
  --chrome-edge-strong:  rgba(64, 60, 55, 0.24);
  --chrome-edge:         rgba(64, 60, 55, 0.17);
  --chrome-edge-soft:    rgba(64, 60, 55, 0.11);
  --chrome-edge-faint:   rgba(64, 60, 55, 0.06);
  --chrome-edge-none:    rgba(64, 60, 55, 0);

  /* ── GOLD EDGES — these draw the brass hairlines/rules. They keep the
     TRUE BRAND GOLD (#c8922a) because they are pure decoration: this is
     where the real brand metal survives the light theme. Alphas raised
     vs dark because on paper a light-toned metal must work harder. */
  --gold-edge-strong:    rgba(200, 146, 42, 0.95);
  --gold-edge:           rgba(200, 146, 42, 0.72);
  --gold-edge-soft:      rgba(200, 146, 42, 0.46);
  --gold-edge-faint:     rgba(200, 146, 42, 0.30);
  --gold-edge-none:      rgba(200, 146, 42, 0);

  /* ── GOLD POOL — the wash behind the lockups. On paper a "pool of
     light" is a warm cast on stone, so this inverts with the glow. */
  --gold-pool-core:      rgba(168, 118, 28, 0.11);
  --gold-pool-mid:       rgba(168, 118, 28, 0.05);
  --gold-pool-core-soft: rgba(168, 118, 28, 0.085);
  --gold-pool-mid-soft:  rgba(168, 118, 28, 0.035);

  /* ── SCRIMS / SHADOW — paper-toned, so an image scrim veils toward
     light instead of dropping a dark blob onto the page. */
  --shadow-ambient:      rgba(74, 51, 16, 0.13);

  /* ── METAL stays TRUE BRAND GOLD (see the brand-metal note above).
     --color-amber is now an INK in this theme, so --metal-gold must be
     re-pinned to the literal brand value rather than inherit it. This
     is the line that keeps the logo's gold alive in daylight. */
  --metal-gold:          #c8922a;
  --metal-gold-rgb:      200, 146, 42;
  --metal-gold-bright:   #e0a838;

  /* ── LUME — inverted physics: a warm umber cast, multiplied into the
     paper so it darkens-and-warms (sunlight) instead of hazing grey. */

  /* ── SCRIM CHANNELS — paper, not ink. This single pair of lines is
     what stops ~45 authored gradient stops from painting dark blobs
     over a pale page: every ramp now veils TOWARD light. Photo scrims
     become a bright haze (an over-exposed daylight edge), which is the
     correct daylight reading of the same gradient. */
  --scrim-rgb:           250, 243, 232;
  --scrim-deep-rgb:      239, 230, 216;
  /* Cast shadows are warm umber in daylight — never neutral black,
     which would read as grey dirt on warm paper. */
  --shadow-rgb:          74, 51, 16;

  /* ── PHOTO SCRIM stays DUSK (see the note in :root). The dusk
     photograph is the product; light theme frames it, never bleaches
     it. Pinned to literal ivory/amber values rather than the theme
     tokens, because over a dark photo the INK must stay light even
     though the page ink is now dark. */
  --photo-scrim-rgb:     30, 18, 10;
  --photo-scrim-deep-rgb: 21, 12, 7;   /* same ratio below the base veil */
  --photo-ink:           #f5f0e8;
  --photo-ink-muted:     #d9cfbc;
  --photo-ink-accent:    #e8b96a;
  /* Stays bright: the page ring (#604200) measured 1.61:1 on the hero photo
     and 1.97:1 on the caption scrim — below the 3:1 that WCAG 2.2 SC 1.4.11
     requires for a focus indicator. This value measures 8.20:1 / 10.01:1. */
  --photo-focus-ring:    #e8b96a;

  /* ── GLOW inverts to warm umber (subtractive); EDGE stays brass.
     This is the single most important pair of decisions in the theme:
     it is what makes the ~77 amber literals read as "sunlit brass"
     instead of "dark-mode glow that failed to render". */
  --glow-rgb:            122, 84, 22;
  --glow-warm-rgb:       146, 104, 34;
  --glow-spec-rgb:       168, 118, 28;
  --edge-gold-rgb:       200, 146, 42;   /* TRUE brand gold retained */
  --edge-warm-rgb:       176, 124, 32;   /* darkened: #e8b96a is invisible on paper */
  --edge-spec-rgb:       200, 146, 42;   /* specular collapses to brand gold in daylight */
  --edge-cool-rgb:       120, 116, 110;
  --tint-light-rgb:      74, 51, 16;     /* a "white" sheen must become a warm one */

  /* ── WELLS — depth by WARMTH, not by darkness. ───────────────────
     The naive light-theme ramp darkens each well (L 0.930 -> 0.872),
     which broke AA: tertiary ink measured 4.34 and 4.06 on wells 3-4.
     Instead lightness is held on a narrow floor (0.936 -> 0.906) and
     depth is carried by CHROMA (0.020 -> 0.038). The ramp reads as
     progressively denser, more sun-saturated stone — which is the
     honest daylight reading of a recess anyway — and the worst ink on
     the deepest well still measures 4.53:1. No well can fail AA. */
  --well-1:              #f1e9db;   /* worst ink 4.97:1 */
  --well-2:              #f0e5d3;   /* worst ink 4.92:1 */
  --well-3:              #efe1cc;   /* worst ink 4.76:1 */
  --well-4:              #eedec4;   /* worst ink 4.63:1 */
}


/* ─── RESET ─────────────────────────────────────────────────── */

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

html {
  font-size: 100%; /* 16px base — scale via rem */
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 1rem);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* Honour reduced-motion: disable smooth scroll */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

body {
  background-color: var(--color-canvas);
  color: var(--color-ivory);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  font-weight: 400;
  min-height: 100dvh;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

img, video, svg {
  display: block;
  max-width: 100%;
  height: auto;
}

a {
  color: inherit;
  text-decoration: none;
}

ul, ol {
  list-style: none;
}

button {
  cursor: pointer;
  border: none;
  background: none;
  font-family: inherit;
}

input, textarea, select {
  font-family: inherit;
  font-size: inherit;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: var(--leading-tight);
  color: var(--color-ivory);
  text-wrap: balance;
}

p {
  max-width: 68ch;
}

/* A centred paragraph must centre its BOX, not only its text. The 68ch
   measure above otherwise pins the box to the left edge of any wider
   container and the "centred" copy renders visibly off-left (found on
   the Permanent Lighting pricing note, 157px off-centre at 1280). */
p.text-center,
.text-center p,
p[style*="text-align: center"],
[style*="text-align: center"] p {
  margin-inline: auto;
}

/* ─── FOCUS VISIBLE ─────────────────────────────────────────── */

:focus {
  outline: none;
}

:focus-visible {
  outline: 2px solid var(--color-amber-light);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}


/* ─── TYPOGRAPHY UTILITIES ──────────────────────────────────── */

.font-display   { font-family: var(--font-display); }
.font-body      { font-family: var(--font-body); }

.text-ivory       { color: var(--color-ivory); }
.text-ivory-muted { color: var(--color-ivory-muted); }
.text-amber       { color: var(--color-amber-light); }  /* safe amber */
.text-amber-gold  { color: var(--color-amber); }       /* large text / decorative only */

/* Eyebrow — small-caps label above headlines */
.eyebrow {
  display: block;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: var(--tracking-widest);
  text-transform: uppercase;
  color: var(--color-amber-light);
  margin-bottom: var(--space-4);
}

/* Pull-quote */
.display-xl {
  font-family: var(--font-display);
  font-size: var(--text-hero);
  font-weight: 300;
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
}

.display-lg {
  font-family: var(--font-display);
  font-size: var(--text-6xl);
  font-weight: 300;
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
}

.section-title {
  font-family: var(--font-display);
  font-size: clamp(var(--text-3xl), 5vw, var(--text-4xl));
  font-weight: 600;
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
}

.section-subtitle {
  font-family: var(--font-body);
  font-size: var(--text-lg);
  font-weight: 300;
  color: var(--color-ivory-muted);
  line-height: var(--leading-relaxed);
  max-width: 54ch;
}


/* ─── LAYOUT UTILITIES ──────────────────────────────────────── */

.container {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: clamp(var(--space-6), 5vw, var(--space-16));
}

.container--narrow {
  max-width: var(--container-narrow);
  margin-inline: auto;
  padding-inline: clamp(var(--space-6), 5vw, var(--space-16));
}

.section {
  padding-block: var(--space-section);
}

.section--flush-top  { padding-top: 0; }
.section--flush-bottom { padding-bottom: 0; }


/* ─── BUTTON COMPONENTS ─────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  text-decoration: none;
  padding: var(--space-4) var(--space-8);
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  transition:
    background-color var(--dur-fast) var(--ease-out-expo),
    border-color var(--dur-fast) var(--ease-out-expo),
    box-shadow var(--dur-fast) var(--ease-out-expo),
    color var(--dur-fast) var(--ease-out-expo),
    transform var(--dur-fast) var(--ease-out-expo);
  cursor: pointer;
  white-space: nowrap;
  line-height: 1;
  min-height: 52px;  /* generous tap target for 55+ audience */
}

/* Primary CTA — brass fill (gradient + inner highlight; amber fallback) */
.btn--primary {
  /* The fallback painted when background-image is unavailable. It must be
     the METAL, not --color-amber: in light theme --color-amber is an INK
     (#835a00) and the fallback dropped to a measured 2.87:1 behind the
     dark label. --metal-gold is true brass in both themes, giving the
     same 4.74-8.90:1 range as the gradient it stands in for. */
  background-color: var(--metal-gold);
  background-image: var(--brass-fill);
  border-color: rgba(var(--edge-warm-rgb), 0.45);
  /* The CTA is a brass fill in BOTH themes, so its label must be dark in
     both. This was var(--color-canvas), which is only correct while the
     canvas happens to be dark — in light theme it resolved to paper and
     painted paper-on-brass at a MEASURED 1.09:1. --ink-on-brass is pinned
     dark: 4.74:1 at the gradient's darkest stop, 8.90:1 at its brightest. */
  color: var(--ink-on-brass);
  font-weight: 600;
  box-shadow: var(--brass-edge);
}
.btn--primary:hover,
.btn--primary:focus-visible {
  background-color: var(--color-amber-light);
  background-image: var(--brass-fill-bright);
  border-color: var(--color-amber-light);
  box-shadow: var(--brass-edge), var(--glow-amber-md);
  transform: translateY(-1px);
}
.btn--primary:active {
  transform: translateY(0);
  box-shadow: var(--brass-edge), var(--glow-amber-sm);
}

/* Ghost CTA — outlined */
.btn--ghost {
  background-color: transparent;
  border-color: var(--color-ivory-muted);
  color: var(--color-ivory);
}
.btn--ghost:hover,
.btn--ghost:focus-visible {
  border-color: var(--color-amber-light);
  color: var(--color-amber-light);
  box-shadow: 0 0 24px rgba(var(--glow-rgb), 0.18);
  transform: translateY(-1px);
}

/* Text link — inline CTA with animated underline */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-amber-light);
  text-decoration: none;
  position: relative;
  padding-bottom: 2px;
}
.link-arrow::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  width: 0;
  height: 1px;
  background: var(--color-amber-light);
  transition: width var(--dur-ui) var(--ease-out-strong);
}
.link-arrow:hover::after,
.link-arrow:focus-visible::after {
  width: 100%;
}
.link-arrow .arrow {
  transition: transform var(--dur-base) var(--ease-out-expo);
}
.link-arrow:hover .arrow,
.link-arrow:focus-visible .arrow {
  transform: translateX(4px);
}

@media (prefers-reduced-motion: reduce) {
  .btn,
  .link-arrow::after,
  .link-arrow .arrow {
    transition: none;
  }
  .btn:hover,
  .btn:focus-visible {
    transform: none;
  }
}


/* ─── CARD COMPONENT ────────────────────────────────────────── */

.card {
  background: var(--color-canvas-raised);
  border: var(--border-fine);
  border-radius: var(--radius-md);
  overflow: hidden;
  transition:
    border-color var(--dur-ui) var(--ease-out-strong),
    box-shadow var(--dur-ui) var(--ease-out-strong),
    transform var(--dur-ui) var(--ease-out-strong);
}

.card:hover {
  border-color: rgba(var(--edge-gold-rgb),  0.3);
  box-shadow: var(--shadow-lift), var(--glow-amber-sm);
  transform: translateY(-3px);
}

.card__image {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  transition:
    opacity var(--dur-reveal) var(--ease-out-expo),
    filter var(--dur-slow) var(--ease-out-expo);
}

/* Initial hidden state only when JS can deliver the reveal */
@media (scripting: enabled) {
  .card__image {
    opacity: 0;
    filter: brightness(0.85);
  }
}

.card__image.is-visible {
  opacity: 1;
  filter: brightness(1);
}

.card__body {
  padding: var(--space-6) var(--space-8);
}

.card__label {
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: var(--tracking-widest);
  text-transform: uppercase;
  color: var(--color-amber-light);
  margin-bottom: var(--space-3);
}

.card__title {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: 600;
  color: var(--color-ivory);
  margin-bottom: var(--space-3);
  line-height: var(--leading-snug);
}

.card__text {
  color: var(--color-ivory-muted);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
}

@media (prefers-reduced-motion: reduce) {
  .card:hover {
    transform: none;
  }
  .card__image {
    opacity: 1;
    filter: brightness(0.9);
    transition: none;
  }
}


/* ─── DIVIDER / ORNAMENT ────────────────────────────────────── */

.divider {
  width: 48px;
  height: 1px;
  background: linear-gradient(90deg, var(--color-amber), transparent);
  margin-bottom: var(--space-8);
}

.divider--center {
  margin-inline: auto;
}


/* ─── SECTION SHELLS ────────────────────────────────────────── */

/* Dark raised section — the alternating band.
   Uses --color-canvas-band (rung 2), NOT --color-canvas-raised (rung 3):
   the cards that sit ON this band need their own step above it. See the
   elevation ladder note in :root. */
.section-raised {
  background: var(--color-canvas-band);
  border-top: var(--border-fine);
  border-bottom: var(--border-fine);
}

/* Full-bleed photo section */
.section-photo {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

.section-photo__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  z-index: -2;
  transition: opacity var(--dur-reveal) var(--ease-out-expo);
}

@media (scripting: enabled) {
  .section-photo__bg {
    opacity: 0;
  }
}

.section-photo__bg.is-visible {
  opacity: 1;
}

/* Dark overlay so text remains legible */
.section-photo::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    rgba(var(--photo-scrim-rgb),  0.65),
    rgba(var(--photo-scrim-rgb),  0.82)
  );
  z-index: -1;
}

@media (prefers-reduced-motion: reduce) {
  .section-photo__bg {
    opacity: 1;
    transition: none;
  }
}


/* ─── LIGHT REVEAL ANIMATION ────────────────────────────────── */
/*
 * Images bloom from darkness on scroll-enter.
 * Uses CSS opacity + brightness + clip-path.
 * IntersectionObserver adds .is-visible class.
 * Scroll-driven via @supports for native support.
 */

.reveal {
  transition:
    opacity var(--dur-reveal) var(--ease-out-expo),
    transform var(--dur-slow) var(--ease-out-expo);
}

.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* Staggered reveal for grid children */
.reveal-grid > * {
  transition:
    opacity var(--dur-reveal) var(--ease-out-expo),
    transform var(--dur-slow) var(--ease-out-expo);
}

/*
 * Initial hidden states are gated behind (scripting: enabled):
 * if JS is unavailable (or the browser predates the media feature),
 * content renders fully visible — the reveal is pure enhancement.
 */
@media (scripting: enabled) {
  .reveal {
    opacity: 0;
    transform: translateY(16px);
  }
  .reveal-grid > * {
    opacity: 0;
    transform: translateY(20px);
  }
  .photo-reveal img {
    opacity: 0;
    filter: brightness(0.4) saturate(0.6);
    transform: scale(1.04);
  }
}

.reveal-grid.is-visible > *:nth-child(1) { transition-delay: 0ms; }
.reveal-grid.is-visible > *:nth-child(2) { transition-delay: 100ms; }
.reveal-grid.is-visible > *:nth-child(3) { transition-delay: 200ms; }
.reveal-grid.is-visible > *:nth-child(4) { transition-delay: 300ms; }
.reveal-grid.is-visible > *:nth-child(5) { transition-delay: 400ms; }
.reveal-grid.is-visible > *:nth-child(6) { transition-delay: 500ms; }
.reveal-grid.is-visible > *:nth-child(n+7) { transition-delay: 600ms; }
.reveal-grid.is-visible > * {
  opacity: 1;
  transform: translateY(0);
}

/* Photo light-reveal — bloom from dark */
.photo-reveal {
  overflow: hidden;
  border-radius: inherit;
}

.photo-reveal img {
  transition:
    opacity var(--dur-reveal) var(--ease-out-expo),
    filter 800ms var(--ease-out-expo),
    transform var(--dur-hero) var(--ease-out-expo);
}

.photo-reveal.is-visible img {
  opacity: 1;
  filter: brightness(1) saturate(1);
  transform: scale(1);
}

/* Wide feature photo — aspect-ratio box, never a fixed pixel height.
   A fixed height (e.g. 520px) turns a landscape source into a PORTRAIT
   box on narrow viewports: object-fit: cover then crops to a thin
   vertical slice and the subject falls outside the frame.
   Ratio widens with the viewport so the crop stays deliberate. */
.photo-reveal__img--pathway,
.photo-reveal__img--feature {
  aspect-ratio: 4 / 3;
  height: auto;
  max-height: 520px;
  object-position: 38% 42%;
}

/* Column variant — sits in a side-by-side .cols-feature grid, so it keeps a
   taller ratio at desktop instead of going letterbox next to its text block. */
.photo-reveal__img--column {
  aspect-ratio: 4 / 3;
  height: auto;
  object-position: center center;
}

@media (min-width: 600px) {
  .photo-reveal__img--feature { aspect-ratio: 3 / 2; }
  .photo-reveal__img--pathway { aspect-ratio: 3 / 2; }
}

@media (min-width: 960px) {
  .photo-reveal__img--feature {
    aspect-ratio: 16 / 9;
    object-position: center center;
  }
  .photo-reveal__img--column { aspect-ratio: 5 / 4; }
}

@media (min-width: 960px) {
  /* Wide crop drops the sky; bias left so the lit path and bed hold the
     frame instead of the unlit lawn on the right. */
  .photo-reveal__img--pathway {
    aspect-ratio: 21 / 9;
    object-position: 30% 46%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .reveal,
  .reveal-grid > *,
  .photo-reveal img {
    opacity: 1;
    transform: none;
    filter: none;
    transition: none;
  }
}


/* ─── FORM COMPONENTS ───────────────────────────────────────── */

.form-group,
.form-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.form-label {
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-ivory-muted);
}

.form-input,
.form-textarea,
.form-select {
  background: rgba(var(--tint-light-rgb), 0.04);
  border: 1px solid var(--color-canvas-border);
  border-radius: var(--radius-sm);
  color: var(--color-ivory);
  font-family: var(--font-body);
  font-size: var(--text-base);
  padding: var(--space-4) var(--space-5);
  min-height: 52px;
  transition: border-color var(--dur-fast) var(--ease-out-expo);
  width: 100%;
}

.form-input:focus,
.form-textarea:focus {
  outline: none;
  border-color: var(--color-amber-light);
  box-shadow: 0 0 0 3px rgba(var(--edge-warm-rgb),  0.15);
}

.form-input::placeholder,
.form-textarea::placeholder {
  color: var(--color-ivory-muted);
  opacity: 1; /* Firefox dims placeholders by default */
}

.form-textarea {
  min-height: 140px;
  resize: vertical;
}

/* Honeypot — hidden from real users, visible to bots */
.form-pot {
  opacity: 0;
  position: absolute;
  top: 0;
  left: 0;
  height: 0;
  width: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
}


/* ─── TESTIMONIAL COMPONENT ─────────────────────────────────── */

.testimonial {
  padding: var(--space-8) var(--space-8);
  border-left: 2px solid var(--color-amber);
  background: var(--color-canvas-raised);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
}

.testimonial__stars {
  display: flex;
  gap: var(--space-1);
  margin-bottom: var(--space-5);
  color: var(--color-amber);
  font-size: var(--text-sm);
}

.testimonial__text {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 300;
  font-style: italic;
  line-height: var(--leading-relaxed);
  color: var(--color-ivory);
  margin-bottom: var(--space-5);
}

.testimonial__attr {
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-ivory-muted);
}


/* ─── MATERIALITY COMPONENTS (Phase-1 premium sprint) ───────── */

/* Film-grain veil — drop inside any position:relative band */
.grain-veil {
  position: absolute;
  inset: 0;
  background-image: var(--grain-url);
  background-size: 96px 96px;
  opacity: 0.045;
  pointer-events: none;
  z-index: 0;
}

/*
 * Material visual — charcoal + amber bloom + grain composition.
 * Replaces photo slots that must not face a buyer empty.
 */
.material-visual {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 280px;
  height: 100%;
  background-color: #1e1005;
  background-image:
    var(--grain-url),
    var(--bloom-amber-soft),
    linear-gradient(160deg, #341d08 0%, #1e1005 60%, #1a0c03 100%);
  background-size: 96px 96px, 100% 100%, 100% 100%;
  background-repeat: repeat, no-repeat, no-repeat;
  overflow: hidden;
}

.material-visual svg {
  width: min(72%, 420px);
  height: auto;
  color: rgba(var(--metal-gold-rgb),  0.55);
}

/*
 * Plan panel — blueprint-styled composition ("The Plan" fold).
 * The frame is architected to receive the real scanned plan:
 * swap the inline SVG for <img class="plan-frame__img" src="…">.
 */
.plan-panel {
  position: relative;
  background-color: #1d0f05;
  background-image:
    var(--grain-url),
    var(--bloom-amber-low),
    repeating-linear-gradient(to right,  rgba(var(--edge-gold-rgb),  0.06) 0 1px, transparent 1px 64px),
    repeating-linear-gradient(to bottom, rgba(var(--edge-gold-rgb),  0.06) 0 1px, transparent 1px 64px),
    linear-gradient(165deg, #2e1807 0%, #1d0f05 100%);
  background-size: 96px 96px, 100% 100%, 100% 100%, 100% 100%, 100% 100%;
  background-repeat: repeat, no-repeat, repeat, repeat, no-repeat;
  border: 1px solid rgba(var(--edge-gold-rgb),  0.18);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.plan-frame {
  position: relative;
  margin: var(--space-6);
  border: 1px solid rgba(var(--edge-gold-rgb),  0.22);
  border-radius: var(--radius-sm);
  aspect-ratio: 4 / 3;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
}

/* Drafting corner ticks */
.plan-frame::before,
.plan-frame::after {
  content: '';
  position: absolute;
  width: 14px;
  height: 14px;
  border-color: rgba(var(--edge-gold-rgb),  0.55);
  border-style: solid;
}
.plan-frame::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}
.plan-frame::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}

.plan-frame svg {
  width: 100%;
  height: 100%;
  color: rgba(var(--edge-gold-rgb),  0.6);
}

.plan-frame__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

/* Statement fold — one quiet closing line carries the section */
.statement {
  text-align: center;
  padding-block: var(--space-12);
}

.statement__line {
  font-family: var(--font-display);
  font-size: clamp(var(--text-2xl), 3.5vw, var(--text-4xl));
  font-weight: 300;
  font-style: italic;
  line-height: var(--leading-snug);
  color: var(--color-ivory);
  max-width: 26ch;
  margin-inline: auto;
}

.statement__note {
  margin-top: var(--space-5);
  color: var(--color-ivory-muted);
  font-size: var(--text-base);
  max-width: 52ch;
  margin-inline: auto;
}

/* ─── INLINE-STYLE EXTRACTIONS ──────────────────────────────── */

/* Internal-link card (was repeated inline on ~40 cards) */
.card--link {
  display: block;
  padding: var(--space-6);
  text-decoration: none;
}

/* Mid-scale section title (was a repeated inline clamp) */
.section-title--md {
  font-size: clamp(var(--text-2xl), 4vw, var(--text-3xl));
}


/* ─── PRINT ─────────────────────────────────────────────────── */

@media print {
  body {
    background: white;
    color: black;
  }
  .site-header,
  .site-footer,
  .btn {
    display: none;
  }
}


/* ── THEME TRANSITION GUARD (ported 2026-09-17) ──────────────────────
   Theme flips should cross-fade; a freshly-loaded page should not.
   Without this, a page that loads in light mode paints the DARK colours
   first and transitions toward light, and an automated contrast scanner
   that samples mid-transition reports a failure for a state no human
   ever sees (measured on the 09-10 build: ivory-on-#faf3e8 at 1.68:1
   during the fade vs 8.9:1 settled — 16 pages "blocking" on a ghost).

   main.js adds .theme-anim to <html> on the FIRST real toggle click, so
   until the user asks for a theme change there is no colour transition
   at all and every page paints its final colours immediately.

   SCOPE NOTE: the source build used `* { transition-property: none }`.
   That is safe there but NOT here — this tree has hover lifts, reveal
   animations and the menu/header motion design, and a blanket rule kills
   all of them until someone clicks the toggle. Instead the colour
   properties are transitioned ONLY once .theme-anim is present, and
   nothing else is touched. */
:root.theme-anim body,
:root.theme-anim .site-header,
:root.theme-anim .site-footer,
:root.theme-anim .section-raised,
:root.theme-anim .contact-cta,
:root.theme-anim .card,
:root.theme-anim .page-hero {
  transition: background-color 260ms ease, color 260ms ease,
              border-color 260ms ease;
}

@media (prefers-reduced-motion: reduce) {
  :root.theme-anim body,
  :root.theme-anim .site-header,
  :root.theme-anim .site-footer,
  :root.theme-anim .section-raised,
  :root.theme-anim .contact-cta,
  :root.theme-anim .card,
  :root.theme-anim .page-hero { transition: none; }
}
