/* ==========================================================================
   Ported from codebases/wordpress/hod-redesign-prototype/css/style.css
   (line 205 onward: resets, typography, and every component/layout rule).
   See tokens.css for the :root/[data-theme] variable definitions this file
   consumes. Do not edit the prototype file from this theme — port changes
   back here by hand.
   ========================================================================== */

/* ---------- WordPress-theme-only additions (not in the prototype) ----------
   .hod-section / .hod-section-content: the generic section wrapper used by
   template-parts/section-wrapper.php (see 13-phase-3-page-theme-
   implementation-plan.md Section 2.6). The prototype's static pages never
   needed a generic wrapper — every section there is bespoke markup — but a
   real WordPress theme reuses one wrapper across many page templates, so
   these two classes are new, not ported. .reveal / .reveal-group behavior
   itself IS ported (main.js + the .reveal/.reveal-group rules below). */
.hod-section {
  padding: var(--section-pad) 0;
}
.hod-section-content {
  width: 100%;
  max-width: 1240px;
  margin: 0 auto;
  padding: 0 var(--gutter);
}

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

/* Standard visually-hidden-but-screen-reader-visible utility (matches
   WordPress core's own .screen-reader-text pattern). Added 2026-07-26
   (staging QA crawl pass) for real section <h2>s inserted purely to fix
   axe-core "heading-order" skips (e.g. Services/Stories archive card
   grids, About's story section) where no visible section
   heading existed before the card/step grid's own <h3>s — inserting a
   genuine, accessible-tree-visible <h2> fixes the skip without adding
   visible copy the design never called for. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

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

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-weight: 400;
  line-height: 1.1;
  margin: 0 0 0.5em;
  letter-spacing: -0.005em;
  text-wrap: balance;
}

p {
  margin: 0 0 1em;
  line-height: 1.72;
  color: var(--ink-soft);
}

/* Emotive accent type — used sparingly for taglines, quotes, and
   moments that should feel hand-written rather than typeset. */
.accent {
  font-family: var(--font-accent);
  font-style: italic;
  font-weight: 500;
}


.container {
  width: 100%;
  max-width: 1240px;
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  font-family: var(--font-body);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  /* Lighthouse (2026-08-01): 4.43:1 against this section's background,
     a narrow miss of the 4.5:1 minimum — mixing 25% toward --ink provides
     a safe margin without changing hue. */
  color: color-mix(in srgb, var(--accent-terracotta) 75%, var(--ink) 25%);
  margin-bottom: 1.1em;
}

.eyebrow::before {
  content: "";
  width: 28px;
  height: 1px;
  background: currentColor;
  display: inline-block;
}

/* A softer, accent-type eyebrow variant for emotionally-led moments
   (quotes, atmosphere section) — script-adjacent, not shouty. */
.eyebrow-soft {
  display: block;
  font-family: var(--font-accent);
  font-style: italic;
  font-weight: 500;
  font-size: 1.2rem;
  color: var(--accent-candle);
  margin-bottom: 0.7em;
}

section {
  padding: var(--section-pad) 0;
}

.section-head {
  max-width: 640px;
  margin: 0 0 3rem;
}
.section-head.center {
  margin-left: auto;
  margin-right: auto;
  text-align: center;
}

.section-head h2 {
  font-size: clamp(2rem, 3.6vw, 3.1rem);
}

.section-head p {
  font-size: 1.05rem;
}

/* ---------- Buttons ---------- */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6em;
  padding: 0.95em 1.9em;
  font-family: var(--font-body);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  border-radius: var(--radius-lg);
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform 0.35s var(--ease), background-color 0.35s var(--ease), color 0.35s var(--ease), border-color 0.35s var(--ease), box-shadow 0.35s var(--ease);
  white-space: nowrap;
}

.btn-primary {
  background: var(--accent-terracotta);
  color: var(--offwhite);
  border-color: var(--accent-terracotta);
  box-shadow: 0 0 0 0 rgba(var(--ink-rgb), 0);
}

/* Subtle golden strobe — client feedback: give the main "Book a
   Consultation" CTA the same warm ambient-light language as the hero's
   firefly/bokeh canvas (--accent-candle), as a quiet attention cue rather
   than a loud effect. A soft sweeping highlight, not a flashing/blinking
   strobe — pauses most of each 5s cycle so it reads as occasional, not
   distracting. Re-themes automatically since --accent-candle is redefined
   per mood in tokens.css. */
.btn-primary::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(100deg, transparent 30%, rgba(var(--offwhite-rgb), 0.55) 45%, var(--accent-candle) 50%, rgba(var(--offwhite-rgb), 0.55) 55%, transparent 70%);
  background-size: 220% 100%;
  background-position: 150% 0;
  opacity: 0;
  mix-blend-mode: overlay;
  animation: btn-strobe 5s ease-in-out infinite;
}
@keyframes btn-strobe {
  0%, 55% { background-position: 150% 0; opacity: 0; }
  62% { opacity: 0.9; }
  75%, 100% { background-position: -50% 0; opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .btn-primary::after { animation: none; display: none; }
}
.btn-primary:hover {
  background: var(--chocolate);
  border-color: var(--chocolate);
  transform: translateY(-2px);
  /* Polished CTA moment — a soft glow lift on hover, tinted to the
     active theme's accent, instead of a plain flat color swap. */
  box-shadow: 0 10px 28px -8px rgba(var(--ink-deep-rgb), 0.45), 0 0 0 3px rgba(var(--offwhite-rgb), 0.06);
}

.btn-outline-light {
  background: transparent;
  color: var(--offwhite);
  border-color: var(--line-on-dark);
}
.btn-outline-light:hover {
  background: var(--offwhite);
  color: var(--chocolate);
  border-color: var(--offwhite);
  transform: translateY(-2px);
}

.btn-outline-dark {
  background: transparent;
  color: var(--chocolate);
  border-color: var(--line);
}
.btn-outline-dark:hover {
  background: var(--chocolate);
  color: var(--offwhite);
  border-color: var(--chocolate);
  transform: translateY(-2px);
}

.btn-block {
  width: 100%;
}

/* ---------- Placeholder photography ----------
   No real client media is used in this prototype (see 08-phase-1
   plan, Non-Goals). Every "photo" is a generated duotone panel with
   a caption describing the real photography it stands in for. */

.ph {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(38% 46% at 20% 18%, rgba(243, 201, 136, 0.5), transparent 60%),
    radial-gradient(120% 140% at 15% 10%, rgba(200, 134, 59, 0.55), transparent 55%),
    radial-gradient(130% 130% at 85% 90%, rgba(168, 90, 58, 0.5), transparent 55%),
    linear-gradient(155deg, var(--ph-grad-1) 0%, var(--ph-grad-2) 65%, var(--ph-grad-3) 100%);
  display: flex;
  align-items: flex-end;
  color: var(--offwhite);
  isolation: isolate;
}

.ph::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    repeating-linear-gradient(115deg, rgba(var(--offwhite-rgb), 0.05) 0px, rgba(var(--offwhite-rgb), 0.05) 1px, transparent 1px, transparent 26px);
  mix-blend-mode: overlay;
  opacity: 0.6;
}

.ph::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(0deg, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0) 45%);
}

/* Bokeh/candlelight glow orbs — soft floating light motif, layered
   above the base gradient, below caption text. Positions vary per
   instance via inline custom properties so repeated .ph blocks don't
   look identical. Purely decorative, aria-hidden. */
.ph-glow {
  position: absolute;
  z-index: 0;
  border-radius: 50%;
  background: radial-gradient(circle, var(--glow-color, var(--accent-candle)) 0%, transparent 70%);
  opacity: var(--glow-opacity, 0.55);
  filter: blur(1px);
  animation: glow-drift var(--glow-dur, 9s) ease-in-out infinite;
}
@keyframes glow-drift {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(6px, -14px) scale(1.08); }
}

/* Floral silhouette corner motif — simple original line-art sprig,
   drawn with SVG, tucked into a corner of hero/page-hero panels. */
.floral-corner {
  position: absolute;
  z-index: 1;
  width: clamp(90px, 12vw, 160px);
  height: auto;
  opacity: 0.55;
  pointer-events: none;
}
.floral-corner.bottom-left { left: 0; bottom: 0; transform: scaleX(1); }
.floral-corner.bottom-right { right: 0; bottom: 0; transform: scaleX(-1); }
.floral-corner.top-right { right: 0; top: 0; transform: rotate(180deg); }
.floral-corner svg { width: 100%; height: auto; display: block; }
.floral-corner path { fill: none; stroke: var(--accent-candle); stroke-width: 1.4; stroke-linecap: round; }
.floral-corner circle { fill: var(--accent-marigold); stroke: none; }

.ph-cap {
  position: relative;
  z-index: 1;
  padding: 1.1rem 1.3rem;
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.85;
  font-family: var(--font-body);
}

.ph-tone-2::before { filter: hue-rotate(-14deg); }
.ph-tone-3::before { filter: hue-rotate(20deg); }
.ph-tone-4 { filter: saturate(0.85) brightness(0.96); }

/* ---------- Header ---------- */

.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  min-height: var(--header-height);
  box-sizing: border-box;
  /* Padding trimmed slightly (2026-07-25, from 1.3rem) to make room for
     the larger .brand-logo-mark below without growing the header's
     overall rendered height past --header-height (89px) — logo got
     bigger, header did not. */
  padding: 1.1rem 0;
  display: flex;
  align-items: center;
  transition: background-color 0.4s var(--ease), padding 0.4s var(--ease), box-shadow 0.4s var(--ease);
}

.site-header .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}

.site-header .container > nav {
  flex: 1;
}

.site-header.is-solid {
  background: rgba(var(--ink-rgb), 0.96);
  backdrop-filter: blur(6px);
  /* Trimmed to match .site-header's own padding reduction above, same
     proportional shrink relative to the larger logo. */
  padding: 0.65rem 0;
  box-shadow: 0 12px 30px -18px rgba(0,0,0,0.6);
}

/* Non-home pages no longer sit on a dark full-bleed photo hero (see
   .page-hero above — shrunk to a light-background compact title band), so
   the fixed/transparent header needs a permanent dark backdrop there
   instead of only the scroll-triggered .is-solid state main.js applies.
   `body:not(.home)` — WordPress's own get_body_class() adds `home`
   whenever is_front_page() is true, static-page front page or not, so
   this targets every inner page without a template-specific check. */
body:not(.home) .site-header {
  background: rgba(var(--ink-rgb), 0.96);
  backdrop-filter: blur(6px);
}

.brand {
  display: flex;
  align-items: center;
}

/* Source logo artwork is black; both the header (transparent-over-hero,
   then dark on scroll) and the footer are dark contexts, so the logo is
   inverted to white rather than shipping separate white-ink asset files. */
.brand-logo {
  display: block;
  filter: brightness(0) invert(1);
}

/* Client feedback: header now shows just the mark, no wordmark text — the
   "House of Dipali" wordmark's job moved to the hero's own H1, so the
   header repeating it felt redundant. Applies at every width now, not
   just mobile. */
.brand-logo-full {
  display: none;
}

.brand-logo-mark {
  display: block;
  /* Bumped from 48px (2026-07-25 client feedback) — .site-header's own
     vertical padding was trimmed by the same amount so the header's
     overall rendered height stays put. */
  height: 54px;
  width: auto;
}

.nav-links {
  display: flex;
  align-items: center;
  justify-content: space-evenly;
  gap: 0;
  list-style: none;
  margin: 0;
  padding: 0;
}

.nav-links a,
.nav-links a {
  /* Syne — chosen after live comparison against 5 other candidates. Also
     applied to .mobile-nav a further down (kept with its own font-size
     rule there, since mobile nav is a much larger touch-target size). */
  font-family: 'Syne', sans-serif;
  font-weight: 600;
  font-size: 0.92rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--offwhite);
  opacity: 0.95;
  position: relative;
  padding-bottom: 4px;
}

.nav-links a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: var(--accent-marigold);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.35s var(--ease);
}
.nav-links a:hover,
.nav-links a.is-active {
  opacity: 1;
}
.nav-links a:hover::after,
.nav-links a.is-active::after {
  transform: scaleX(1);
}

.nav-cta {
  display: flex;
  align-items: center;
  gap: 1.4rem;
}

/* Header "Contact Us" CTA. The background spotlight/halo (formerly
   `.nav-cta .btn::before`, a blurred radial-gradient glow) is removed —
   no glow behind this button. Font matched to the rest of the nav menu
   (.nav-links a, above) rather than .btn's own default font — this
   button reads as one of the header's nav items, not a generic site
   button, so it should share that exact type treatment (Syne, size,
   letter-spacing), not .btn's Inter/smaller/looser default. */
.nav-cta .btn {
  border-width: 1.5px;
  font-family: 'Syne', sans-serif;
  font-size: 0.92rem;
  letter-spacing: 0.1em;
}

/* Border shimmer animation. Deliberately its own standalone class with
   no dependency on `.nav-cta .btn` or any other existing selector —
   several earlier attempts at animating this same button broke
   repeatedly because they lived inside that shared selector chain and
   silently inherited properties (width, height, opacity, z-index) from
   an unrelated older rule; this class has no such entanglement. A real
   static border (1.5px, via `.nav-cta .btn` above) is sized slightly
   THINNER than the shimmer ring's own 2px padding below, so the
   animated ring reads as a deliberately bigger/brighter accent passing
   over a slightly finer resting line, not two identical-weight rings
   competing. */
.btn-shimmer {
  position: relative;
  overflow: hidden;
}
.btn-shimmer::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 2px;
  background: linear-gradient(115deg, transparent 30%, rgba(var(--offwhite-rgb), 0.9) 48%, transparent 66%);
  background-size: 250% 100%;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  animation: hod-btn-shimmer 3.2s ease-in-out infinite;
  pointer-events: none;
}
@keyframes hod-btn-shimmer {
  0% {
    background-position: 130% 0;
  }
  45%, 100% {
    background-position: -30% 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .btn-shimmer::before {
    animation: none;
  }
}

.nav-toggle {
  display: none;
  flex-direction: column;
  gap: 5px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 8px;
}
.nav-toggle span {
  width: 24px;
  height: 1.5px;
  background: var(--offwhite);
  transition: transform 0.3s var(--ease), opacity 0.3s var(--ease);
}

/* ---------- Hero ---------- */

.hero {
  position: relative;
  /* Verified with a real headless-Chrome render at multiple viewport
     sizes (not guessed): a FIXED `height` here is the actual root cause,
     confirmed the hard way across three prior attempts. The eyebrow/H1/
     lede/buttons stack scales with viewport WIDTH (clamp()'s vw units —
     H1 font-size, lede padding-bottom up to 6rem), while a fixed `height`
     here only scales with viewport HEIGHT (vh). On a wide-but-moderate
     browser window (1440x900 — an ordinary laptop, not an edge case),
     content genuinely grows taller than a vh-based box, and a
     bottom-anchored flex item (align-items: flex-end) has no ceiling — it
     gets pushed up past its own container's top edge and behind the
     header. `min-height` instead of `height` fixes this categorically:
     the section is still guaranteed to look like a proper full hero (at
     least 72vh/640px), but if content ever needs more room than that, the
     section itself grows to fit it instead of clipping/pushing content
     off-screen. */
  /* Client feedback (2026-07): make the hero the full height of the
     window, replicating 847389.us8.myftpupload.com's real hero. Still
     min-height, not height — same overflow bug this comment block
     documents applies regardless of the target value. */
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--offwhite);
  overflow: hidden;
  /* Reserves clearance for the fixed .site-header, exactly matching its
     real height (--header-height) — not an approximated vh-based gap.
     .hero-content is bottom-anchored (align-items: flex-end above) —
     padding-top on THAT child is a no-op (a bottom-anchored flex item's
     box grows upward to compensate, so the visible content ends up in the
     exact same place). Padding has to go on the flex CONTAINER instead: it
     shrinks the content box align-items actually operates within, which
     genuinely keeps the content out of the top reserved strip. */
  padding-top: var(--header-height);
}

.hero .ph {
  position: absolute;
  inset: -8% 0 0 0;
  height: 116%;
  will-change: transform;
  /* Base .ph's gradient is top-left-focused (built for bottom-left-anchored
     text). Now that the hero is centered, the warm light is recentered to
     match, with a soft vignette darkening the corners so the eye is pulled
     toward the middle (where the title + .hero-spotlight-glow now sit)
     instead of the old top-left highlight. Vignette is deliberately mild
     ("not too darker" per feedback) — 0.22 alpha at the very edge, fully
     transparent through the center ~55% of the frame.
     Real bug found on a real phone: the original width%/height% ellipse
     sizing (e.g. "46% 55%") computes independently against this box's
     width and height — fine on a wide desktop hero, but on a narrow tall
     mobile viewport the two percentages diverge enough to render as a
     lopsided "teardrop" instead of the round glow seen on desktop. `circle`
     with a fixed vmin radius forces an actual circle at any aspect ratio,
     which desktop already coincidentally looked like. */
  background:
    radial-gradient(circle 34vmin at 50% 42%, rgba(243, 201, 136, 0.34), transparent 100%),
    radial-gradient(circle 46vmin at 50% 38%, rgba(200, 134, 59, 0.26), transparent 100%),
    radial-gradient(circle 60vmin at 50% 50%, transparent 55%, rgba(0, 0, 0, 0.22) 100%),
    linear-gradient(155deg, var(--ph-grad-1) 0%, var(--ph-grad-2) 65%, var(--ph-grad-3) 100%);
}

/* Dark scrim over the real hero photo — replicates 847389's Elementor
   overlay recipe (a translucent near-black-brown layer over the
   background image) so the white title lockup stays readable against
   whatever the photo's own tones happen to be. Sits above .ph, below
   the atmosphere canvas/spotlight/content (DOM order below handles the
   stacking — all four are position:absolute with no z-index conflicts). */
.hero-scrim {
  position: absolute;
  inset: 0;
  background: rgba(20, 12, 10, 0.58);
}

/* Client feedback: the ambient gold particle glow reads a bit strong —
   lowered slightly via opacity on the canvas itself rather than touching
   the particle physics/brightness values in atmosphere.js, so the effect
   is easy to re-tune later without risking the shared scroll-wind logic
   other atmosphere canvases (page-hero, etc.) also depend on. */
.hero .atmosphere-canvas {
  opacity: 0.82;
}

/* Centered gold spotlight — client feedback: with the hero now centered
   (title moved from bottom-left to the middle), the ambient light should
   be centered behind it too, so the glow reads as a light source focused
   on "House of Dipali" rather than generic ambient texture. Sits above
   the photo/canvas background but below hero-content (z-index: 2). */
.hero-spotlight-glow {
  position: absolute;
  left: 50%;
  top: 50%;
  width: min(78vw, 900px);
  height: min(78vw, 900px);
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(circle, var(--accent-candle) 0%, transparent 65%);
  opacity: 0.15;
  filter: blur(50px);
  pointer-events: none;
  z-index: 1;
}

/* Bridges the hard seam between Home's hero and the next (Atmosphere)
   section: without this, two independently-computed diagonal gradients
   meet at a hard horizontal edge and don't line up in tone, reading as an
   abrupt cut. Fading the hero's own bottom edge down to the same
   --ph-grad-3 tone the Atmosphere section's gradient/scrim already settle
   into (see .atmosphere-hero, .atmosphere-scrim below) makes the two
   sections settle into one continuous dark passage instead. Sits above
   the photo/canvas layers (default stacking order, no z-index needed —
   see .hero-content's explicit z-index: 2 below) but doesn't need to be
   above hero-content itself; a light title over a slightly darker base
   only improves contrast. Re-themes automatically with the mood switcher
   since --ph-grad-3 is redefined per [data-theme] in tokens.css. */
.hero::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: clamp(120px, 20vh, 240px);
  background: linear-gradient(to bottom, transparent, var(--ph-grad-3));
  pointer-events: none;
}

/* Parallax layers move at different rates via JS-applied transform
   (data-parallax on the element). CSS just declares the will-change
   hint; the actual translateY is set in js/main.js, scroll-linked. */
[data-parallax] { will-change: transform; }

.hero-content {
  position: relative;
  z-index: 2;
  width: 100%;
  /* Hero recentered per client feedback — text/buttons centered so the
     whole block, and the spotlight glow behind it, focus on the title. */
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-bottom: clamp(1.5rem, 3vw, 2.5rem);
}

.hero-content .eyebrow {
  /* Was --accent-marigold — too close in tone to the warm gold hero
     background/atmosphere glow to read clearly. --accent-candle is a much
     lighter warm tone (still gold-family, not a plain white) that holds
     contrast against the dark hero backdrop. */
  color: var(--accent-candle);
}

/* Hero kicker lines — "Welcome To" above the title, "Wedding Decor &
   Design" below it. Replicates 847389.us8.myftpupload.com's actual
   hod-hero-subtitle recipe exactly (Montserrat, uppercase, 0.28em
   tracking, soft off-white — not gold, not italic) per client request to
   match fonts exactly rather than reuse this theme's existing .eyebrow
   (adds an unwanted dash + gold color) or .hero-title-secondary (italic
   serif, wrong font entirely) styles. */
.hero-kicker {
  display: block;
  font-family: "Montserrat", var(--font-body);
  font-weight: 500;
  /* Bumped slightly (2026-07-25, from 0.68/1.1vw/0.95rem) alongside the
     H1 bump below — same clamp() approach, so this still scales safely
     down to phone widths rather than a fixed px jump. */
  font-size: clamp(0.72rem, 1.15vw, 1rem);
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: rgba(var(--offwhite-rgb), 0.85);
  margin: 0 0 0.9em;
}

.hero-kicker-sub {
  margin: 0.6em 0 1.8em;
}

.hero h1 {
  /* Bumped (2026-07) to match 847389's actual hero title size (132px
     desktop / 88px tablet / 54px mobile) — the previous clamp rendered
     visibly smaller relative to the hero than the reference site's real
     hero, per client request to match sizes closely, still responsive
     via clamp() rather than a fixed px value. Nudged larger three more
     times since (2026-07-25/26) after an earlier round had reduced it —
     the first two of those nudges were real but small (~3-5% each),
     which turned out to be below the threshold of what's actually
     noticeable at typical desktop widths (most screens >=1310px wide
     were already hitting the clamp's max bound, so the intermediate vw
     term barely mattered) — that round used a deliberately bigger,
     unmistakable jump instead of another small one. If this needs
     tuning again, change this rule, not .hero-title-brand (see the note
     below it: .hero h1 wins on specificity and is the one that actually
     renders).

     2026-07-30 (third demo feedback, Item 3) — the middle clamp term
     switched from a pure vw value to min(vw, vh), driving the effective
     max bound off viewport HEIGHT as well as width. This replaced the
     old two-tier `@media (max-height: ...)` step-down (removed) with a
     single continuous curve, but the first coefficient chosen (10.2vh)
     was still too conservative: real headless-Chrome measurement
     against live staging (not a local guess) showed EVERY tested
     desktop-class viewport — laptop and large monitor alike — was
     already vh-bound (the vw term never won the min()), so the result
     was pure viewport-height scaling: ~5.3-5.7rem on real MacBook
     logical viewport heights (832-890px) vs. ~8.5-8.9rem on a real
     34"/27" monitor (1329-1396px logical height) — the exact "looks
     right on a monitor, too small on a laptop" complaint this round
     reported, just now smooth instead of stepped.

     2026-07-30, same day (second pass) — coefficient raised 10.2vh ->
     15vh after confirming, via real Puppeteer measurement against live
     staging at 7 viewports (1470x832 MacBook 14", 1366x700 small
     laptop, 2880x1396 large monitor, 1024x768 iPad landscape, and three
     short/extreme cases down to 736x360 phone landscape), that the
     bottom-anchored hero content has 150-450px of unused clearance
     above the fixed header at every realistic desktop/laptop viewport
     — the original header-overlap risk this vh-based approach exists
     to prevent was never actually close to triggering at any tested
     size, even at the old 10.2vh coefficient. 15vh brings a MacBook 14"
     to ~7.8rem (was ~5.3rem) while the monitor stays essentially
     unchanged (already near the 9.6rem max), meaningfully closing the
     gap without reintroducing overlap — confirmed zero overlap down to
     736x360 (36px buffer) and 844x390 (42px buffer); a genuinely
     short desktop window (1400x500) still keeps 69px clear. If this
     needs tuning again, re-run the same real-viewport measurement
     approach rather than guessing — a static docblock claim isn't a
     substitute for it, as this very entry demonstrates. */
  font-size: clamp(2.85rem, min(11.5vw, 15vh), 9.6rem);
  max-width: 20ch;
  color: var(--offwhite);
}

/* ---------- Hero title lockup — "House of Dipali" as the main heading ----------
   Finalized choice (text brand name) after live comparison against a
   logo-image and mark+text hybrid. */
.hero-title-brand {
  font-family: var(--font-display);
  font-size: clamp(2.8rem, 7vw, 5.6rem);
  color: var(--offwhite);
  line-height: 1.05;
  margin: 0.75rem 0 0.3em;
}

.hero-title-secondary {
  /* Back to --font-accent (Cormorant Garamond italic) — same treatment as
     .accent ("timeless, immersive, and unforgettable." in the lede below). */
  font-family: var(--font-accent);
  font-style: italic;
  font-weight: 500;
  font-size: clamp(1rem, 1.9vw, 1.5rem);
  color: rgba(var(--offwhite-rgb), 0.88);
  white-space: nowrap;
  margin: 0 0 0.7em;
}

.hero p.lede {
  font-size: clamp(1.05rem, 1.7vw, 1.32rem);
  max-width: 46ch;
  color: rgba(var(--offwhite-rgb), 0.82);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1rem;
  margin-top: 2rem;
}

/* ---------- Hero: header-safe height tiers ----------
   .hero-content is bottom-anchored (align-items: flex-end above) inside a
   fixed-height .hero, with .site-header floating on top (position: fixed,
   z-index: 100). On wide-but-short "laptop" viewports (1280x800, 1536x864,
   and similar — width alone doesn't predict this, height does) the
   .hero h1's width-driven clamp() still renders at or near its max size
   regardless of how little vertical room is actually available, so the
   bottom-anchored content block's top edge can render underneath the
   header instead of below it. Because the block is bottom-anchored,
   neither padding-top nor margin-top on .hero/.hero-content changes its
   final position (only its own height does) — the only real fix is
   reducing that height on short viewports specifically.

   2026-07-30 (third demo feedback, Item 3): the .hero h1 font-size
   override that used to live in each of these two tiers was REMOVED —
   .hero h1's own rule above now drives its max bound off vh directly
   (min(11.5vw, 10.2vh) inside one continuous clamp()), so it no longer
   needs a separate step-down here; keeping a second override here on
   top of that would just reintroduce the same kind of hard edge this
   round is fixing. The remaining rules below (.hero p.lede sizing,
   .hero-content bottom padding, .hero-actions margin) are unrelated to
   the reported heading bug and are unchanged — still real, still needed
   for short-viewport layout breathing room. */
@media (max-height: 880px) {
  .hero p.lede {
    font-size: clamp(1rem, 1.45vw, 1.15rem);
  }
  .hero-content {
    padding-bottom: clamp(2rem, 5vw, 4rem);
  }
  .hero-actions {
    margin-top: 1.4rem;
  }
}

@media (max-height: 780px) {
  .hero p.lede {
    font-size: clamp(0.95rem, 1.35vw, 1.05rem);
  }
  .hero-content {
    padding-bottom: clamp(1.5rem, 4vw, 2.75rem);
  }
}

.hero-scroll {
  position: absolute;
  right: var(--gutter);
  bottom: 2.4rem;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 0.7rem;
  color: rgba(var(--offwhite-rgb), 0.75);
  font-size: 0.7rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  writing-mode: vertical-rl;
}
.hero-scroll::after {
  content: "";
  width: 1px;
  height: 46px;
  background: currentColor;
  display: block;
}

/* ---------- Trust / Venue carousel ---------- */

.trust-strip {
  background: var(--chocolate);
  color: var(--offwhite);
  padding: 2.6rem 0;
  overflow: hidden;
}

.trust-strip .container {
  text-align: center;
}

.trust-strip .eyebrow {
  color: var(--accent-marigold);
  justify-content: center;
}

.trust-track-wrap {
  overflow: hidden;
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}

.trust-track {
  display: flex;
  gap: clamp(2.2rem, 5vw, 4.5rem);
  width: max-content;
  animation: scroll-left 34s linear infinite;
}

.trust-track:hover {
  animation-play-state: paused;
}

/* About-page instance of the shared trust-strip component
   (template-parts/trust-strip.php, page-about.php). Bigger, fully-opaque,
   whiter/lighter text than Home's own .trust-item default (rgba(offwhite,
   0.62) — deliberately muted against Home's own hardcoded --chocolate
   strip background). Scoped to .trust-strip-about only — Home's own
   .trust-item sizing/color is completely untouched. */
.trust-track-wrap.trust-strip-about .trust-item {
  font-size: 1.3rem;
  color: rgba(var(--offwhite-rgb), 0.92);
}

/* About's trust-strip instance isn't wrapped in .trust-strip (see
   template-parts/trust-strip.php's docblock — it deliberately renders
   only the inner markup so About can embed it in its own section
   instead), so it doesn't inherit .trust-strip .container's/.eyebrow's
   center-alignment rules above. Centers the "Trusted at Tri-State's
   Finest Venues" eyebrow to match Home's version, plus top spacing so
   it doesn't sit flush against the "Why Families Choose Us" cards
   right above it. */
.trust-strip-about-inner {
  margin-top: 2.5rem;
}

.trust-strip-about-inner .container {
  text-align: center;
}

.trust-strip-about-inner .eyebrow {
  justify-content: center;
}

/* Divider between the venue trust-strip and the footer (2026-07-25 client
   feedback) — .trust-strip and .site-footer both render on the same
   --chocolate background, so without this the venue list ran straight
   into the footer with no visual break at all. Reuses the .cta-divider
   hairline+diamond motif already established for the global CTA sections
   rather than inventing a new one. */
.section-divider {
  background: var(--chocolate);
  padding: clamp(1.5rem, 4vw, 2.5rem) 0 0;
}
.section-divider .cta-divider {
  margin-bottom: 0;
}

.trust-item {
  font-family: var(--font-display);
  font-size: 1.05rem;
  letter-spacing: 0.03em;
  color: rgba(var(--offwhite-rgb), 0.62);
  white-space: nowrap;
}

@keyframes scroll-left {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* Reverse-direction marquee variant — used as a textural, word-based
   divider elsewhere on the site (see .word-strip), so the "something
   keeps happening" feeling isn't only on Home. */
.trust-track.reverse {
  animation-direction: reverse;
}

.word-strip {
  background: var(--offwhite-warm);
  padding: 1.6rem 0;
  overflow: hidden;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.word-strip .trust-track { animation-duration: 46s; }
.word-strip .trust-item {
  font-family: var(--font-accent);
  font-style: italic;
  font-size: 1.3rem;
  color: var(--accent-terracotta);
  opacity: 0.55;
}
.word-strip .trust-item::after {
  content: "-";
  margin-left: clamp(2.2rem, 5vw, 4.5rem);
  opacity: 0.5;
}

/* ---------- Floral divider — animated vine/garland between sections ----------
   Original simple line-art (hand-authored path, not traced), draws
   itself in via stroke-dasharray when scrolled into view. */
.floral-divider {
  display: flex;
  justify-content: center;
  padding: 0 0 var(--section-pad);
  margin-top: calc(var(--section-pad) * -1 + 1px);
}
.floral-divider svg {
  width: min(420px, 70%);
  height: auto;
  overflow: visible;
}
.floral-divider path,
.floral-divider circle {
  stroke: var(--accent-terracotta);
  fill: none;
  stroke-width: 1.1;
  stroke-linecap: round;
}
.floral-divider .fd-dot { fill: var(--accent-marigold); stroke: none; }
.floral-divider .fd-line {
  stroke-dasharray: 340;
  stroke-dashoffset: 340;
  transition: stroke-dashoffset 1.6s var(--ease-slow);
}
.floral-divider.is-visible .fd-line {
  stroke-dashoffset: 0;
}
.section-dark + .section-dark .floral-divider path,
.section-dark .floral-divider path { stroke: var(--accent-candle); }
.section-dark .floral-divider .fd-dot { fill: var(--accent-candle); }

@media (prefers-reduced-motion: reduce) {
  .floral-divider .fd-line { stroke-dashoffset: 0; transition: none; }
}

/* ---------- Cards: services ---------- */

.grid {
  display: grid;
  gap: clamp(1.25rem, 3vw, 2.25rem);
}

.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-2 { grid-template-columns: repeat(2, 1fr); }

.card {
  display: block;
  background: #fff;
  border: 1px solid var(--line);
  /* Clips the hover push-in scale (.card:hover .ph) so it stays
     inside the card boundary instead of bleeding into the body copy
     below or neighboring cards. */
  overflow: hidden;
}

.card-photo {
  aspect-ratio: 4 / 5;
}

/* ---------- Decor artwork ----------
   Round 3: original, hand-authored line-art scenes standing in for
   real photography on service/gallery cards — replaces flat gradient
   panels with content-specific silhouettes (mandap, stage arch,
   floral wall, etc.). Not traced or copied from any source; simple
   geometric/organic shapes only. Themes automatically since it uses
   the same accent/candle CSS variables as everything else. */
.decor-art {
  position: absolute;
  z-index: 1;
  left: 50%;
  top: 42%;
  transform: translate(-50%, -50%);
  width: 62%;
  height: auto;
  opacity: 0.92;
}
.decor-art .da-line {
  fill: none;
  stroke: var(--accent-candle);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.decor-art .da-fill {
  fill: var(--accent-candle);
  opacity: 0.85;
}
.decor-art .da-accent {
  fill: var(--accent-marigold);
}
.decor-art .da-soft {
  fill: none;
  stroke: rgba(var(--offwhite-rgb), 0.4);
  stroke-width: 1;
}
/* Card-level transform on hover — subtle camera-like push-in, part of
   Round 3's "something keeps happening" scroll/interaction language. */
.card:hover .decor-art {
  transform: translate(-50%, -50%) scale(1.05);
}
.card .ph {
  transition: transform 0.6s var(--ease-slow);
}
.card:hover .ph {
  transform: scale(1.04);
}
@media (prefers-reduced-motion: reduce) {
  .card:hover .decor-art,
  .card:hover .ph { transform: none; }
}

.card-body {
  padding: 1.6rem 1.5rem 1.9rem;
}

.card-body .eyebrow {
  margin-bottom: 0.7em;
  font-size: 0.62rem;
}

.card-body h3 {
  font-size: 1.28rem;
  margin-bottom: 0.5em;
}

.card-body p {
  font-size: 0.92rem;
  margin-bottom: 1.2em;
}

.card-link {
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  /* Lighthouse (2026-08-01): 4.43:1 against the page-hero background, a
     narrow miss of 4.5:1 — same fix as .eyebrow. */
  color: color-mix(in srgb, var(--accent-terracotta) 75%, var(--ink) 25%);
  border-bottom: 1px solid currentColor;
  padding-bottom: 2px;
  transition: color 0.3s var(--ease);
}
.card-link:hover {
  color: var(--chocolate);
}

/* Single Event / Single Story internal-linking row. Reuses .card-link
   for each individual link; just needs a wrapping row with spacing
   between items. */
.event-related-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem 1.75rem;
  margin-top: 2rem;
}

/* ---------- Gallery ---------- */

.filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
  margin-bottom: 2.6rem;
}

.chip {
  font-size: 0.72rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  padding: 0.65em 1.15em;
  border: 1px solid var(--line);
  color: var(--ink-soft);
  background: transparent;
  cursor: pointer;
  transition: background-color 0.3s var(--ease), color 0.3s var(--ease), border-color 0.3s var(--ease);
}
.chip.is-active,
.chip:hover {
  background: var(--chocolate);
  color: var(--offwhite);
  border-color: var(--chocolate);
}

/* Modern pill filter bar (2026-07-29 client feedback: "modern list of
   filters", no "All" chip, default view is Wedding Ceremony). Same .chip
   markup/JS as before (assets/js/main.js's plain click-toggle handler,
   plus a real navigation for the filtered query) — this is a visual
   upgrade only: bigger radius/padding for easier tapping, a filled
   active state instead of the plain border look, on the same underlying
   element. */
.filter-row-pills {
  gap: 0.6rem;
}
.filter-row-pills .chip {
  border-radius: 999px;
  padding: 0.7em 1.4em;
  font-size: 0.7rem;
  border-color: rgba(var(--ink-rgb), 0.16);
}
.filter-row-pills .chip.is-active {
  box-shadow: 0 4px 14px rgba(var(--ink-rgb), 0.18);
}

/* ---------- Services preview carousel (Home) ----------
   2026-07-29 client feedback: "carousel or something modern... showing 3
   initially and then keep on moving." CSS scroll-snap track (no JS
   dependency for the core interaction — scroll-snap alone is fully
   usable by touch/trackpad); assets/js/services-carousel.js layers
   arrows/dots/autoplay on top. */
.services-carousel {
  position: relative;
}
.services-carousel-track {
  display: flex;
  gap: clamp(1rem, 2.4vw, 1.8rem);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  padding-bottom: 0.5rem;
  scrollbar-width: none;
}
.services-carousel-track::-webkit-scrollbar { display: none; }
.services-carousel-slide {
  flex: 0 0 calc(33.333% - clamp(0.7rem, 1.6vw, 1.2rem));
  scroll-snap-align: start;
  min-width: 0;
}
.services-carousel-slide .card { height: 100%; }
@media (max-width: 900px) {
  .services-carousel-slide { flex-basis: calc(66% - 0.6rem); }
}
@media (max-width: 600px) {
  .services-carousel-slide { flex-basis: 88%; }
}

.carousel-arrow {
  position: absolute;
  top: 40%;
  transform: translateY(-50%);
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  border: 1px solid var(--line-on-dark, rgba(var(--offwhite-rgb), 0.3));
  background: rgba(var(--ink-rgb), 0.55);
  color: var(--offwhite);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.3s var(--ease);
  z-index: 2;
}
.carousel-arrow:hover,
.carousel-arrow:focus-visible {
  background: var(--accent-marigold);
  border-color: var(--accent-marigold);
}
.carousel-arrow-prev { left: -1.2rem; }
.carousel-arrow-next { right: -1.2rem; }
@media (max-width: 700px) {
  .carousel-arrow { display: none; }
}

.carousel-dots {
  display: flex;
  justify-content: center;
  gap: 0.75rem;
  margin-top: 1.6rem;
}
.carousel-dot {
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  border: none;
  padding: 0;
  background: transparent;
  cursor: pointer;
  position: relative;
}
.carousel-dot::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: rgba(var(--offwhite-rgb), 0.35);
  transform: translate(-50%, -50%);
  transition: background-color 0.3s var(--ease), transform 0.3s var(--ease);
}
.carousel-dot.is-active::before {
  background: var(--accent-marigold);
  transform: translate(-50%, -50%) scale(1.3);
}
@media (prefers-reduced-motion: reduce) {
  .services-carousel-track { scroll-behavior: auto; }
}

/* Gallery "View More" (2026-07-25 correction) — a real link/button, never
   an infinite-scroll observer, per explicit client instruction. */
.gallery-view-more {
  text-align: center;
  margin-top: 2.6rem;
  min-height: 2.8rem;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* "View More" loading spinner (2026-07-26, assets/js/load-more.js) — a
   simple marigold ring with a small rotated-diamond center, echoing the
   .cta-divider motif already used for section breaks elsewhere on the
   site, rather than a generic system spinner. */
.hod-spinner {
  display: inline-block;
  width: 2rem;
  height: 2rem;
  position: relative;
  border: 3px solid rgba(var(--ink-rgb), 0.12);
  border-top-color: var(--accent-marigold);
  border-radius: 50%;
  animation: hod-spinner-rotate 0.85s linear infinite;
}
.hod-spinner::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 5px;
  height: 5px;
  margin: -2.5px 0 0 -2.5px;
  background: var(--accent-marigold);
  transform: rotate(45deg);
}
@keyframes hod-spinner-rotate {
  to {
    transform: rotate(360deg);
  }
}
@media (prefers-reduced-motion: reduce) {
  .hod-spinner {
    animation: none;
    opacity: 0.7;
  }
}

/* Small inline variant for inside a button (e.g. Contact form's submit
   button while the relay POST is in flight, 2026-07-31) — same motif,
   scaled down, border color follows the button's own text color via
   currentColor so it reads correctly on both light and dark buttons. */
.hod-spinner-btn {
  width: 1.15rem;
  height: 1.15rem;
  border-width: 2px;
  border-color: rgba(255, 255, 255, 0.35);
  border-top-color: currentColor;
  vertical-align: middle;
}
.hod-spinner-btn::after {
  width: 3px;
  height: 3px;
  margin: -1.5px 0 0 -1.5px;
  background: currentColor;
}

/* Sub-service picture showcase teaser (2026-07-25) — sits under the
   showcase's .gallery-grid, template-parts/service-showcase.php. */
.service-showcase-teaser {
  text-align: center;
  max-width: 52ch;
  margin: 2.4rem auto 0;
}
.service-showcase-teaser p {
  color: var(--ink-soft);
  margin-bottom: 1.4rem;
}

/* ---------- Horizontal gallery reveal ----------
   A scroll-snapped strip: distinct interaction from the grid below,
   deliberately "sideways" so scrolling itself feels like flipping
   through a curated set before the full archive. */
.horiz-gallery-section {
  padding-bottom: clamp(2rem, 5vw, 3.5rem);
}
.horiz-gallery {
  display: flex;
  gap: 1.1rem;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding: 0.5rem var(--gutter) 1.5rem;
  scrollbar-width: thin;
  scrollbar-color: var(--accent-terracotta) transparent;
}
.horiz-gallery::-webkit-scrollbar { height: 6px; }
.horiz-gallery::-webkit-scrollbar-thumb { background: var(--accent-terracotta); border-radius: 999px; }
.horiz-gallery:focus-visible { outline: 2px solid var(--accent-terracotta); outline-offset: 4px; }

.horiz-item {
  flex: 0 0 clamp(220px, 32vw, 340px);
  aspect-ratio: 3 / 4;
  scroll-snap-align: start;
  transition: transform 0.5s var(--ease-slow);
}
.horiz-item:hover { transform: translateY(-6px); }

@media (prefers-reduced-motion: reduce) {
  .horiz-gallery { scroll-behavior: auto; }
  .horiz-item:hover { transform: none; }
}

/* ---------- Gallery grid — Instagram-post style ----------
   Client feedback: the old masonry-style grid (wide/tall variants, big
   4:5/16:8 images with a permanent caption block) felt too heavy. Replaced
   with a tight, uniform grid — small thumbnails with an always-visible
   event-name overlay. Used by
   both the Home Gallery Preview and the full /events/ archive (see
   template-parts/event-card.php), one shared component, not two. */
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(0.35rem, 1vw, 0.6rem);
}

.gallery-item {
  aspect-ratio: 3 / 4;
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--line);
}

/* Natural-aspect-ratio masonry is intentionally limited to Showcase Photos
   on single Service and compact single Event pages. The shared gallery grid
   above remains the uniform-crop treatment approved for archives and Home. */
.gallery-grid.showcase-masonry,
.gallery-grid.event-masonry {
  display: grid;
  /* Match the JavaScript layout's desktop column count so its first pass
     does not begin from a materially different fallback geometry. */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0;
  position: relative;
}

@media (max-width: 900px) and (min-width: 641px) {
  .gallery-grid.showcase-masonry,
  .gallery-grid.event-masonry {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
.gallery-grid.showcase-masonry .masonry-gallery-item,
.gallery-grid.event-masonry .masonry-gallery-item {
  aspect-ratio: auto;
  overflow: hidden;
  margin: 0;
  background: var(--line);
}
.gallery-grid.showcase-masonry .masonry-gallery-item img,
.gallery-grid.event-masonry .masonry-gallery-item img {
  display: block;
  width: 100%;
  height: auto;
}
.showcase-masonry.is-masonry-ready,
.event-masonry.is-masonry-ready {
  display: block;
}

/* Single Event photo grid only (see .event-photo-grid, template-parts/
   single-event-compact.php) — taller crop per client feedback. Safe for
   sharpness: object-fit: cover just crops more of the source frame, it
   never upscales, and these photos are requested at 'large' (1024px)
   which comfortably covers this box size at 3-per-row. */
.event-photo-grid .gallery-item {
  aspect-ratio: 2 / 3;
}

.gallery-item img {
  transition: transform 0.5s var(--ease-slow);
}
.gallery-item:hover img {
  transform: scale(1.05);
}
@media (prefers-reduced-motion: reduce) {
  .gallery-item:hover img { transform: none; }
}

.gallery-meta {
  position: absolute;
  inset: auto 0 0 0;
  z-index: 1;
  padding: 0.9rem 0.9rem 0.8rem;
  background: linear-gradient(0deg, rgba(0,0,0,0.72) 0%, rgba(0,0,0,0.35) 55%, transparent 100%);
  opacity: 1;
  transform: none;
}
.gallery-meta .g-title {
  font-family: var(--font-display);
  font-size: 1.1rem;
  color: var(--offwhite);
}

/* ---------- Single event ---------- */

.event-hero {
  position: relative;
  height: 78vh;
  min-height: 480px;
  color: var(--offwhite);
  display: flex;
  align-items: flex-end;
}
.event-hero .ph { position: absolute; inset: 0; }
/* Oversized so the JS parallax translateY never reveals a gap at the
   top/bottom edge — same safety pattern as .hero .ph. */
.event-hero .ph[data-parallax] { inset: -6% 0 0 0; height: 112%; }
.event-hero-content { position: relative; z-index: 1; padding-bottom: 3.5rem; }

/* Single line (2026-07-25 client feedback, was wrapping to multiple rows)
   — nowrap + horizontal scroll on any viewport too narrow to fit every
   field, rather than letting it wrap and break the "one line" request.
   Kept as the mobile behavior (looked fine there per 2026-07-28 feedback);
   desktop gets its own rule below so it spreads across the full width
   instead of clustering left with a scrollbar despite empty space. */
.event-meta-row {
  display: flex;
  flex-wrap: nowrap;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  gap: 2.4rem;
    /* Top margin tightened, paired with .page-hero's own bottom-padding
     reduction above — was 2.6rem. */
  margin: 1.1rem 0 3.4rem;
  padding: 2rem 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.event-meta-item {
  flex-shrink: 0;
}

/* Desktop only: spread items across the full
   row width instead of bunching to the left with unused space and a
   scrollbar. flex-shrink stays allowed here so if content ever genuinely
   can't fit even at 901px+, it still scrolls within this box (overflow-x:
   auto above) rather than breaking the layout — same "scroll appears at
   the box level" behavior as mobile, just without a scrollbar for the
   common case where everything already fits. */
@media (min-width: 901px) {
  .event-meta-row {
    justify-content: space-between;
  }
  .event-meta-item {
    flex-shrink: 1;
    min-width: 0;
  }
}
.event-meta-item .label {
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  /* Lighthouse (2026-08-01): 4.43:1 against this row's background, a
     narrow miss of 4.5:1 — same fix as .eyebrow. */
  color: color-mix(in srgb, var(--accent-terracotta) 75%, var(--ink) 25%);
  margin-bottom: 0.5em;
  display: block;
}
.event-meta-item .value {
  font-family: var(--font-display);
  font-size: 1.05rem;
}
/* Story's Photography row (template-parts/story-meta.php) is the one
   .value that's a real link, not plain text — a visible underline is the
   only thing that distinguishes it from the other rows' plain values. */
.event-meta-item a.value {
  text-decoration: underline;
  text-underline-offset: 0.2em;
}
.event-meta-item a.value:hover {
  color: var(--accent-terracotta);
}

.event-story {
  max-width: 68ch;
  font-size: 1.08rem;
}
.event-story p {
  color: var(--ink);
}

/* Per-segment Story breakdown (2026-07-26) — one block per real event
   within a Story (Sangeet, Ceremony, Reception, etc.), each with its own
   heading, short blurb, and only its own photos. Replaces one merged
   narrative + one merged gallery per client feedback. */
.story-segment {
  margin: 3.4rem 0;
}
.story-segment:first-of-type {
  margin-top: 2.6rem;
}
.story-segment-heading {
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  margin-bottom: 0.75rem;
}
.story-segment-text {
  max-width: 68ch;
  font-size: 1.05rem;
  color: var(--ink);
  margin-bottom: 1.6rem;
}

.event-gallery {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 1rem;
  margin-top: 3rem;
}
.event-gallery .ph:nth-child(1) { grid-column: span 4; aspect-ratio: 4/5; }
.event-gallery .ph:nth-child(2) { grid-column: span 2; aspect-ratio: 3/5; }
.event-gallery .ph:nth-child(3) { grid-column: span 2; aspect-ratio: 3/5; }
.event-gallery .ph:nth-child(4) { grid-column: span 4; aspect-ratio: 4/5; }

.photographer-credit {
  margin-top: 1.5rem;
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--ink-soft);
  font-style: italic;
}

/* ---------- Dark section ---------- */

.section-dark {
  background: var(--chocolate);
  color: var(--offwhite);
}
.section-dark h2 { color: var(--offwhite); }
.section-dark p { color: rgba(var(--offwhite-rgb), 0.78); }
.section-dark .eyebrow { color: var(--accent-marigold); }

/* .card defaults to an opaque white background (see .card rule above) —
   correct on a light page, but inside .section-dark its off-white
   inherited text color lands on that white card background nearly
   invisible (real bug found via computed-style check on the new
   single-service.php sub-service grid, 2026-07). front-page.php's
   Services Preview cards worked around this per-instance with inline
   styles (transparent bg, explicit light text); this makes that the
   default for ANY .card inside .section-dark instead, so future reuse
   doesn't need to repeat the workaround. */
.section-dark .card {
  background: transparent;
  border-color: var(--line-on-dark);
}
.section-dark .card-body h3 {
  color: var(--offwhite);
}

/* ---------- Atmosphere / video-ready centerpiece ----------
   A full-bleed, motion-rich section. No real video asset exists in
   this local prototype (see README) — a canvas-based ambient light
   simulation (js/atmosphere.js) stands in for it, with the markup
   already shaped to accept a real <video> source later. */
.atmosphere-hero {
  position: relative;
  height: 52vh;
  min-height: 380px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: linear-gradient(155deg, var(--ph-grad-1) 0%, var(--ph-grad-2) 65%, var(--ph-grad-3) 100%);
  color: var(--offwhite);
}
.atmosphere-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.atmosphere-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.atmosphere-scrim {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(60% 60% at 50% 40%, rgba(var(--ink-rgb),0) 0%, rgba(var(--ink-rgb),0.55) 78%),
    linear-gradient(0deg, rgba(var(--ink-deep-rgb),0.75) 0%, rgba(var(--ink-deep-rgb),0.15) 40%, rgba(var(--ink-deep-rgb),0.35) 100%);
}

/* The other half of the hero/Atmosphere bridge (see .hero::after above):
   fades this section's own top edge down to the same --ph-grad-3 tone the
   hero's bottom now settles into, so the two sections meet at a matched
   tone instead of hero's now-dark bottom hitting this section's
   comparatively light top. Sits above .atmosphere-scrim (later in source
   order, same auto stacking) but below .atmosphere-content (z-index: 2). */
.atmosphere-hero::after {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: clamp(120px, 20vh, 240px);
  background: linear-gradient(to top, transparent, var(--ph-grad-3));
  pointer-events: none;
}

/* ---------- Atmosphere motif — static decorative floral overlay ----------
   Round 5 follow-up: the scene-cycling loop (four motif overlays cross-fading
   on a timer, plus a caption text swap) has been removed entirely —
   it was the source of ongoing lag/jank even after Round 5 removed
   the light-leak sweep that used to sit on top of it. This is now a
   single fixed decorative element with no transition, no timer, and
   no DOM content changing after page load. The firefly/bokeh canvas
   (js/atmosphere.js) is what keeps this section feeling alive. */
.atmosphere-motif {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.atmosphere-motif .decor-art {
  position: static;
  transform: none;
  width: min(50vw, 320px);
  opacity: 0.42;
}
.atmosphere-content {
  position: relative;
  z-index: 2;
  text-align: center;
  max-width: 720px;
  padding: 0 var(--gutter);
}
.atmosphere-content .eyebrow-soft { justify-content: center; }
.atmosphere-content h2 {
  font-size: clamp(2.1rem, 5vw, 3.6rem);
  color: var(--offwhite);
}
.atmosphere-content p {
  color: rgba(var(--offwhite-rgb), 0.82);
  font-size: clamp(0.98rem, 1.3vw, 1.12rem);
  max-width: 52ch;
  margin: 0 auto 1.8rem;
}
.atmosphere-note {
  position: absolute;
  z-index: 2;
  right: var(--gutter);
  bottom: 1.6rem;
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(var(--offwhite-rgb), 0.4);
}

/* ---------- Mandap-inspired arch frame ----------
   An abstract architectural arch (not literal religious iconography)
   used to frame key CTA moments, echoing mandap/ceremonial staging
   silhouettes with a garland accent along the crown. */
/* Static CTA top divider — see template-parts/cta-global.php for why this
   exists separately from .arch-frame's own (JS-scroll-triggered) top
   ornamentation. A centered hairline that fades in from both sides toward
   a small rotated diamond, echoing the .eyebrow dash motif used
   throughout the site without just repeating it outright. */
.cta-divider {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.9rem;
  max-width: 420px;
  margin: 0 auto 2rem;
}
.cta-divider span {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--accent-marigold));
}
.cta-divider span:last-child {
  background: linear-gradient(90deg, var(--accent-marigold), transparent);
}
.cta-divider i {
  display: block;
  width: 7px;
  height: 7px;
  background: var(--accent-marigold);
  transform: rotate(45deg);
  flex-shrink: 0;
}

.arch-frame {
  position: relative;
  max-width: 780px;
  margin: 0 auto;
  padding: clamp(3rem, 7vw, 5rem) clamp(1.5rem, 5vw, 3.5rem) clamp(2.5rem, 5vw, 3.5rem);
  border: 1px solid var(--line);
  border-top: none;
  border-radius: 220px 220px 4px 4px / 140px 140px 4px 4px;
}
/* On dark sections, the light-bg border color would be invisible —
   switch to the on-dark line color automatically. */
.section-dark .arch-frame {
  border-color: var(--line-on-dark);
}
.arch-frame::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 0;
  transform: translate(-50%, -1px);
  width: 74%;
  height: 3px;
  background: repeating-linear-gradient(90deg, var(--accent-marigold) 0 6px, transparent 6px 14px);
  border-radius: 999px;
  opacity: 0.85;
}
/* The build variant replaces this static dashed line with its own
   animated .build-arch garland — showing both would double up the
   ornamentation at the top edge. */
.arch-frame-build::before {
  display: none;
}
.arch-frame-inner { position: relative; z-index: 1; text-align: center; }
/* Slight extra clearance between the hanging garland lights and the
   heading — the .build-arch/.ba-thread lights sit just above this box and
   read as too close to the heading without it. */
.arch-frame-build .arch-frame-inner { padding-top: 1.4rem; }

/* ---------- "Build the event" — CTA stage build-in ----------
   Round 5: a lightweight, one-time scroll reveal on the final CTA
   arch-frame. As it scrolls into view, the scene assembles in stages
   so reaching the CTA feels like watching a small stage come
   together, not just seeing a static box. Deliberately NOT
   scroll-linked/continuous (no per-scroll-position math): a single
   IntersectionObserver trigger adds .is-built once, and every layer
   below is a plain CSS transition off that one class change — cheap,
   predictable, and easy to keep smooth.

   Round 6 refines the sequence into a real staging build order:
     1. Side poles rise from the ground (like posts going up first)
     2. Floral corners settle in at the base
     3. The arch garland draws itself across the top, connecting the posts
     4. Garland lights switch on one by one
     5. Small flowers drop from threads along the garland
     6. A soft glow activates behind the CTA — the stage is lit
   Every stage is still transform/opacity/stroke-dashoffset only. */
.arch-frame-build {
  overflow: visible;
}

/* Stage 1 — side posts rising from the ground. */
.build-pole {
  position: absolute;
  bottom: 4%;
  width: 2px;
  height: 84%;
  background: linear-gradient(180deg, var(--accent-candle) 0%, var(--accent-marigold) 65%, transparent 100%);
  opacity: 0;
  transform: scaleY(0);
  transform-origin: bottom;
  z-index: 0;
  pointer-events: none;
  transition: transform 1.05s ease-out, opacity 0.4s ease-out;
}
.build-pole.left { left: 4%; }
.build-pole.right { right: 4%; }
.arch-frame-build.is-built .build-pole {
  opacity: 0.6;
  transform: scaleY(1);
}

/* Stage 3-5 — garland: arch line, lights, hanging flower threads. */
.build-arch {
  position: absolute;
  left: 50%;
  top: -1px;
  transform: translateX(-50%);
  width: 78%;
  height: auto;
  z-index: 0;
  pointer-events: none;
  overflow: visible;
}
.build-arch .ba-line {
  fill: none;
  stroke: var(--accent-marigold);
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-dasharray: 460;
  stroke-dashoffset: 460;
  transition: stroke-dashoffset 1.3s ease-out 0.55s;
}
.arch-frame-build.is-built .build-arch .ba-line {
  stroke-dashoffset: 0;
}
.build-arch .ba-dot {
  fill: var(--accent-candle);
  opacity: 0;
  transition: opacity 0.45s ease-out;
}
.arch-frame-build.is-built .build-arch .ba-dot {
  opacity: 1;
}
/* Garland lights switch on one by one, after the arch has drawn in. */
.build-arch .ba-dot:nth-child(3) { transition-delay: 1.15s; }
.build-arch .ba-dot:nth-child(4) { transition-delay: 1.3s; }
.build-arch .ba-dot:nth-child(5) { transition-delay: 1.45s; }
.build-arch .ba-dot:nth-child(6) { transition-delay: 1.6s; }
.build-arch .ba-dot:nth-child(7) { transition-delay: 1.75s; }

/* Hanging thread + small flower — draws down then blooms, like a
   dropped festoon garland ornament. */
.build-arch .ba-thread {
  stroke: var(--accent-candle);
  stroke-width: 1.1;
  stroke-linecap: round;
  stroke-dasharray: 42;
  stroke-dashoffset: 42;
  transition: stroke-dashoffset 0.55s ease-out;
}
.arch-frame-build.is-built .build-arch .ba-thread {
  stroke-dashoffset: 0;
}
.build-arch .ba-thread-1 { transition-delay: 1.9s; }
.build-arch .ba-thread-2 { transition-delay: 2.1s; }
.build-arch .ba-thread-3 { transition-delay: 2.3s; }

.build-arch .ba-thread-flower {
  fill: var(--accent-rose);
  opacity: 0;
  transition: opacity 0.4s ease-out;
}
.arch-frame-build.is-built .build-arch .ba-thread-flower {
  opacity: 0.95;
}
.build-arch .ba-thread-flower-1 { transition-delay: 2.35s; }
.build-arch .ba-thread-flower-2 { transition-delay: 2.55s; }
.build-arch .ba-thread-flower-3 { transition-delay: 2.75s; }

/* Stage 2 — floral corners settle in at the base, alongside the
   rising posts. */
.build-floral {
  position: absolute;
  z-index: 0;
  width: clamp(56px, 10vw, 84px);
  height: auto;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.9s ease-out 0.3s, transform 0.9s ease-out 0.3s;
}
.build-floral.left {
  left: -14px;
  bottom: -12px;
  transform: translateY(10px) scale(0.9);
}
.build-floral.right {
  right: -14px;
  bottom: -12px;
  transform: scaleX(-1) translateY(10px) scale(0.9);
}
.arch-frame-build.is-built .build-floral {
  opacity: 0.7;
}
.arch-frame-build.is-built .build-floral.left {
  transform: translateY(0) scale(1);
}
.arch-frame-build.is-built .build-floral.right {
  transform: scaleX(-1) translateY(0) scale(1);
}
.build-floral path {
  fill: none;
  stroke: var(--accent-candle);
  stroke-width: 1.4;
  stroke-linecap: round;
}
.build-floral circle {
  fill: var(--accent-marigold);
  stroke: none;
}

/* Stage 6 — the stage is lit: soft glow activates last, once
   everything else has settled in. */
.build-glow {
  position: absolute;
  inset: -6% -4%;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(55% 65% at 50% 32%, var(--accent-candle), transparent 72%);
  opacity: 0;
  transition: opacity 1.1s ease-out 1.6s;
}
.arch-frame-build.is-built .build-glow {
  opacity: 0.24;
}

@media (prefers-reduced-motion: reduce) {
  .build-glow,
  .build-pole,
  .build-arch .ba-line,
  .build-arch .ba-dot,
  .build-arch .ba-thread,
  .build-arch .ba-thread-flower,
  .build-floral {
    transition: none;
  }
  .build-glow { opacity: 0.24; }
  .build-pole { opacity: 0.6; transform: scaleY(1); }
  .build-arch .ba-line { stroke-dashoffset: 0; }
  .build-arch .ba-dot { opacity: 1; }
  .build-arch .ba-thread { stroke-dashoffset: 0; }
  .build-arch .ba-thread-flower { opacity: 0.95; }
  .build-floral { opacity: 0.7; }
  .build-floral.left { transform: translateY(0) scale(1); }
  .build-floral.right { transform: scaleX(-1) translateY(0) scale(1); }
}

@media (max-width: 640px) {
  .build-floral { width: clamp(44px, 16vw, 60px); }
  .build-pole { display: none; }
}

/* ---------- About story rows ---------- */
.about-story-rows {
  display: flex;
  flex-direction: column;
  gap: clamp(5rem, 14vh, 9rem);
  padding: 1rem 0 2rem;
}
.about-story-row {
  display: grid;
  grid-template-columns: 2fr 3fr;
  gap: clamp(2rem, 6vw, 5rem);
  align-items: start;
}
.about-story-photo {
  position: relative;
  aspect-ratio: 4 / 5;
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.about-story-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.about-story-photo .ph { position: absolute; inset: 0; }
.about-story-copy h3 { font-size: clamp(1.5rem, 2.6vw, 2.1rem); }
.about-story-copy .step-index {
  font-family: var(--font-accent);
  font-style: italic;
  font-size: 1.1rem;
  /* WCAG AA fix retained from the prior story layout: this mix provides
     approximately 6:1 contrast while preserving the terracotta hue. */
  color: color-mix(in srgb, var(--accent-terracotta) 75%, var(--ink) 25%);
  display: block;
  margin-bottom: 0.6em;
}

/* ---------- Story split ---------- */

.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2rem, 6vw, 5rem);
  align-items: center;
}
.split .ph { aspect-ratio: 4/5; }
.split-copy h2 { font-size: clamp(1.9rem, 3.2vw, 2.8rem); }

/* Single Service featured image — the primary visual introduction above
   the service description, distinct from the optional showcase gallery. */
.service-featured-image {
  width: 100%;
  overflow: hidden;
  border-radius: var(--radius-lg);
  margin-bottom: 2.5rem;
}
.service-featured-image img {
  display: block;
  width: 100%;
  max-height: 680px;
  object-fit: cover;
}

/* Single Service body copy uses a comfortable reading measure. */
.service-copy {
  max-width: 68ch;
}
/* Real fix for Coming Soon centering (2026-07-26) — the previous attempt
   only centered .coming-soon *within* .service-copy, but .service-copy
   itself is a left-aligned 68ch column inside a much wider centered
   section, so that inner centering barely moved anything. When there's
   no real body copy (single-service.php adds this class specifically for
   that state), the whole column centers itself instead of staying
   left-anchored — body copy elsewhere is untouched since this class is
   opt-in, not a change to .service-copy's default behavior. */
.service-copy-empty {
  margin: 0 auto;
  text-align: center;
}

/* Reusable "Coming Soon" placeholder (template-parts/coming-soon.php).
   Sits inside .service-copy's 68ch column on single-service.php, so no
   separate max-width here — a centered block within whatever container
   already wraps it. */
/* Centered as its own island (2026-07-25 fix) — .service-copy (its usual
   parent) is a left-aligned 68ch reading column, not centered on the
   page; that's correct for body copy but left this block looking
   stranded on the left half of the section when it's the only content. */
.coming-soon {
  text-align: center;
  max-width: 60ch;
  margin: 0 auto;
  padding: 2.5rem 1rem;
}
.coming-soon h3 {
  margin-bottom: 0.75rem;
}
.coming-soon p {
  max-width: 52ch;
  margin: 0 auto 1.5rem;
  color: var(--ink-soft);
}
/* .section-dark places light text on a dark surface (see base.css's
   "invisible card titles" fix, single-service.php's children grid) — if
   this component is ever reused inside a .section-dark wrapper, match
   that convention instead of the light-background --ink-soft above. */
.section-dark .coming-soon p {
  color: rgba(var(--offwhite-rgb), 0.78);
}

/* Home "About Us" (Our Craft) — client pivot (2026-07): the old two-column
   .split (photo + copy) becomes a single centered column, no image. A
   modifier of its own rather than an edit to .split, which single-service.php
   and others still use in its original two-column form. */
.intro-about {
  max-width: 760px;
  margin: 0 auto;
  text-align: center;
}
.intro-about .eyebrow {
  justify-content: center;
}
.intro-about h2 {
  font-size: clamp(1.9rem, 3.2vw, 2.8rem);
}
.intro-about p {
  margin: 0 auto;
}
.intro-about .card-link {
  display: inline-block;
  margin-top: 0.5rem;
}

/* ---------- Values / stats ---------- */

.stats-row {
  /* Flex, not grid: 3 real stats now (was 4 — see page-about.php's
     $hod_stats). A fixed-column grid left an empty trailing column
     (previously 4 columns for 4 stats) reading left-shifted/unbalanced;
     flex + centered wrap keeps this centered as a block at any stat count
     or width, including the odd-item-out case on narrow screens, which a
     grid's per-row column tracks don't re-center on their own. */
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 2rem 3rem;
  margin: 3.5rem auto 0;
  padding-top: 3rem;
  border-top: 1px solid var(--line-on-dark);
  text-align: center;
}
.stat {
  flex: 0 1 160px;
}
.stat .num {
  font-family: var(--font-display);
  font-size: clamp(2rem, 3.4vw, 2.9rem);
  color: var(--accent-marigold);
}
.stat .label {
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.75;
  margin-top: 0.4em;
}

/* ---------- Testimonial-ish quote ---------- */

.quote-block {
  max-width: 760px;
  margin: 0 auto;
  text-align: center;
}
.quote-block blockquote {
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 2.6vw, 2.1rem);
  line-height: 1.4;
  margin: 0 0 1.3rem;
}
.quote-block cite {
  font-style: normal;
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent-marigold);
}

/* ---------- FAQ accordion ---------- */

.faq-list {
  max-width: 820px;
  margin: 0 auto;
  border-top: 1px solid var(--line);
}
.faq-item {
  border-bottom: 1px solid var(--line);
}
.faq-q {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  background: none;
  border: none;
  text-align: left;
  padding: 1.5rem 0;
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 1.1rem;
  color: var(--ink);
}
.faq-q .icon {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  position: relative;
}
.faq-q .icon::before,
.faq-q .icon::after {
  content: "";
  position: absolute;
  background: var(--accent-terracotta);
  transition: transform 0.3s var(--ease);
}
.faq-q .icon::before { top: 50%; left: 0; right: 0; height: 1px; }
.faq-q .icon::after { left: 50%; top: 0; bottom: 0; width: 1px; }
.faq-item.is-open .icon::after { transform: scaleY(0); }

.faq-a {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.4s var(--ease);
}
.faq-a-inner {
  padding-bottom: 1.6rem;
  max-width: 62ch;
}

/* ---------- Contact ---------- */

/* Tightened hero->card gap: the generic
   `section { padding: var(--section-pad) 0; }` rule gave this section its
   own full top padding on top of .page-hero's own bottom padding, leaving
   too much air between the "Let's design your celebration." H1 and the
   card/form below. Scoped to this page's section only — no change to the
   generic `section` rule used everywhere else. */
.contact-section {
  padding-top: clamp(1rem, 2.5vw, 1.75rem);
}

.contact-layout {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: clamp(2.5rem, 6vw, 5rem);
  align-items: start;
}

/* Sticky contact-info card: on desktop/
   tablet, the left info card stays in view while the form scrolls past it
   on the right — standard sticky-sidebar pattern. Scoped to the
   desktop/tablet breakpoint only; mobile (.contact-layout collapses to a
   single column at 900px, see the existing @media (max-width: 900px)
   rule) keeps normal document flow, no sticky behavior there. */
@media (min-width: 901px) {
  .contact-info-card {
    position: sticky;
    top: calc(var(--header-height) + 2rem);
  }
}

.contact-info-card {
  background: var(--chocolate);
  color: var(--offwhite);
  padding: clamp(2rem, 4vw, 3rem);
}
/* Real WCAG AA contrast failure (axe-core "serious", /contact/, staging QA
   crawl 2026-07-26): the shared .eyebrow rule's default
   var(--accent-terracotta) color doesn't meet 4.5:1 against this card's
   dark var(--chocolate) background. .section-dark already solves the exact
   same problem the same way (var(--accent-marigold) instead of
   --accent-terracotta) — reusing that already-verified-safe combination
   here rather than inventing a new color. CSS-only change (this file), no
   edit to page-contact.php itself, so it doesn't collide with the parallel
   contact-form-modernization work on that template. */
.contact-info-card .eyebrow {
  color: var(--accent-marigold);
}
/* Selector renamed h3 -> h2 alongside page-contact.php's heading-order fix
   (axe-core "heading-order", staging QA crawl 2026-07-26 pass) — the
   contact-info-card's own heading was relabeled to fix an h1->h3 skip; this
   is a same-rule rename only, no visual change. */
.contact-info-card h2 {
  color: var(--offwhite);
  font-size: 1.5rem;
}
.contact-info-list {
  list-style: none;
  padding: 0;
  margin: 2rem 0 0;
}
.contact-info-list li {
  padding: 1rem 0;
  border-top: 1px solid var(--line-on-dark);
  font-size: 0.94rem;
}
.contact-info-list li .label {
  display: block;
  font-size: 0.64rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-marigold);
  margin-bottom: 0.4em;
}

.form-card {
  background: #fff;
  border: 1px solid var(--line);
  padding: clamp(1.75rem, 4vw, 2.75rem);
}

.form-note {
  font-size: 0.76rem;
  color: var(--ink-soft);
  border: 1px dashed var(--line);
  padding: 0.85rem 1rem;
  margin-bottom: 1.8rem;
  background: var(--offwhite-warm);
}

.field-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.1rem;
}
.field {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 1.1rem;
}
.field.full { grid-column: 1 / -1; }

.field label {
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* Label + live character counter on one line: label left, counter right.
   Only used by fields that also carry a .field-count (see below). */
.field-label-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
}
.field-label-row label {
  flex: 1 1 auto;
}

.field label .req {
  color: var(--accent-terracotta);
}

.field input,
.field select,
.field textarea {
  font-family: var(--font-body);
  font-size: 0.96rem;
  padding: 0.85em 1em;
  border: 1px solid var(--line);
  background: var(--offwhite);
  color: var(--ink);
  border-radius: var(--radius-lg);
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease), background-color 0.2s var(--ease);
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--accent-terracotta);
  box-shadow: 0 0 0 3px rgba(var(--ink-rgb), 0.06);
}
.field textarea {
  resize: vertical;
  min-height: 120px;
}

/* ---------- Contact form validation — accessible error states ----------
   Errors are never conveyed by color alone: each invalid field gets
   aria-invalid="true" (set in JS), a visible border/background change,
   and real associated error text (aria-describedby -> #field-error),
   read by assistive tech the same as any other supporting copy. */

.field.has-error input,
.field.has-error select,
.field.has-error textarea {
  border-color: var(--accent-terracotta);
  background: rgba(var(--ink-rgb), 0.03);
}
.field.has-error input:focus,
.field.has-error select:focus,
.field.has-error textarea:focus {
  box-shadow: 0 0 0 3px rgba(176, 85, 95, 0.16);
}

.field-error {
  display: none;
  font-size: 0.76rem;
  color: var(--accent-terracotta);
  margin-top: -0.15rem;
}
.field.has-error .field-error {
  display: block;
}

.form-error-summary {
  display: none;
  font-size: 0.84rem;
  padding: 0.85rem 1rem;
  margin-bottom: 1.4rem;
  background: rgba(176, 85, 95, 0.08);
  border-left: 3px solid var(--accent-terracotta);
  color: var(--ink);
}
.form-error-summary.is-visible {
  display: block;
}

/* Honeypot — off-screen via absolute positioning, not display:none/
   visibility:hidden (some bots specifically detect and skip those before
   deciding what to fill). Real users never see or reach this field. */
.hod-hp-field {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Live character counters — one per limited field. Purely informational
   (aria-hidden; the real accessible feedback is the field-error text and
   the native maxlength enforcement itself). Sits in .field-label-row,
   sharing a line with that field's label (label left, counter right). */
.field-count {
  font-size: 0.72rem;
  color: var(--ink-soft);
  white-space: nowrap;
  flex-shrink: 0;
}

/* Shake feedback when a user keeps typing after hitting a field's
   maxlength. Respects prefers-reduced-motion like every other animation
   in this theme. */
.field input.is-shaking,
.field textarea.is-shaking {
  animation: field-shake 0.32s var(--ease);
}
@keyframes field-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-4px); }
  40% { transform: translateX(4px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(3px); }
}
@media (prefers-reduced-motion: reduce) {
  .field input.is-shaking,
  .field textarea.is-shaking {
    animation: none;
  }
}

/* Consent checkbox — sits directly above the submit button. */
.field-consent {
  margin-top: 0.3rem;
}
.consent-label {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  cursor: pointer;
  font-size: 0.82rem;
  color: var(--ink-soft);
  line-height: 1.5;
}
.consent-label input[type="checkbox"] {
  margin-top: 0.2rem;
  flex-shrink: 0;
  width: 1rem;
  height: 1rem;
}

#hod-recaptcha-placeholder:empty {
  margin: 0;
}

/* Submit button, disabled until required fields + consent are valid. */
.btn.btn-primary:disabled,
.btn.btn-primary[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}
.btn.btn-primary:disabled::after,
.btn.btn-primary[disabled]::after {
  animation: none;
  display: none;
}

.form-submit-note {
  display: none;
  margin-top: 1.2rem;
  padding: 0.9rem 1rem;
  font-size: 0.82rem;
  background: var(--offwhite-warm);
  border-left: 3px solid var(--accent-terracotta);
  color: var(--ink);
}
.form-submit-note.is-visible {
  display: block;
}

/* ---------- Footer ---------- */

.site-footer {
  background: var(--chocolate);
  color: rgba(var(--offwhite-rgb), 0.75);
  padding: clamp(3.5rem, 6vw, 5.5rem) 0 2rem;
}

.footer-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1fr;
  gap: 2.5rem;
  padding-bottom: 3rem;
  border-bottom: 1px solid var(--line-on-dark);
}

.footer-brand .brand {
  color: var(--offwhite);
  margin-bottom: 1rem;
}
/* Footer keeps the full logo+wordmark (unlike the header, which is
   mark-only per client feedback — see .brand-logo-full above); the footer
   has no adjacent H1 doing the wordmark's job, so it stays legible here. */
.footer-brand .brand-logo-full {
  display: block;
  /* logo-full.png is a wide mark+wordmark lockup (~1.9:1) — needs real
     height to keep "HOUSE OF DIPALI" legible; 40px (tried first) rendered
     the text near-illegible at ~76px wide. */
  height: 68px;
  width: auto;
}
.footer-brand .brand-logo-mark {
  display: none;
}
.footer-brand-text {
  max-width: 32ch;
  font-size: 0.88rem;
  /* Client feedback: this description read noticeably dimmer than the
     Studio/Explore column text next to it — both were actually the same
     0.75 inherited opacity, but this one has no sibling text to visually
     calibrate against, so it read as duller. Bumped to match footer link
     text's perceived brightness (.footer-col a sits at 0.85). */
  color: rgba(var(--offwhite-rgb), 0.85);
}

.footer-col h3 {
  font-family: var(--font-body);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-marigold);
  margin-bottom: 1.2rem;
}

.footer-col ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.footer-col a {
  font-size: 0.88rem;
  opacity: 0.85;
}
.footer-col a:hover { opacity: 1; }

.footer-bottom {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
  padding-top: 1.6rem;
  font-size: 0.76rem;
}

/* Plain-text-styled trigger for Complianz's consent-preferences dialog —
   sits next to the copyright line instead of Complianz's own floating
   persistent icon (removed, see assets/css/cookie-banner.css). Reset to
   match the surrounding footer text rather than looking like a default
   <button>. */
.footer-consent-link {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  font-size: 0.76rem;
  color: rgba(var(--offwhite-rgb), 0.7);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  cursor: pointer;
}
.footer-consent-link:hover {
  color: var(--offwhite);
}

/* ---------- Reveal-on-scroll (subtle motion only) ---------- */

.reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.9s var(--ease) var(--reveal-delay, 0s), transform 0.9s var(--ease) var(--reveal-delay, 0s);
}
.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* Staggered choreography — direct children of .reveal-group get an
   incremental --reveal-delay applied via JS so a row of cards or
   gallery items arrives in sequence rather than all at once. */
.reveal-group > * { opacity: 0; transform: translateY(18px); transition: opacity 0.8s var(--ease) var(--reveal-delay, 0s), transform 0.8s var(--ease) var(--reveal-delay, 0s); }
.reveal-group.is-visible > * { opacity: 1; transform: translateY(0); }

/* ---------- Gallery story-reveal — cinematic clip-path wipe ----------
   Round 6: a second storytelling moment, applied only to the Gallery
   page's main grid (.gallery-grid.reveal-cinematic). Reuses the exact
   same stagger-delay/IntersectionObserver mechanism as .reveal-group
   (no new JS at all) but swaps the plain fade+rise for a soft
   top-down clip-path wipe plus a gentle scale settle — reads as a
   curtain/reveal moment being pulled back on each card, rather than
   the standard site-wide reveal. clip-path + transform + opacity are
   all compositor-only properties, so this costs nothing extra to
   animate smoothly. */
.reveal-cinematic > * {
  opacity: 0;
  clip-path: inset(0 0 100% 0);
  transform: scale(1.03);
  transition: opacity 0.7s var(--ease) var(--reveal-delay, 0s), clip-path 0.9s var(--ease-slow) var(--reveal-delay, 0s), transform 0.9s var(--ease-slow) var(--reveal-delay, 0s);
}
.reveal-cinematic.is-visible > * {
  opacity: 1;
  clip-path: inset(0 0 0% 0);
  transform: scale(1);
}

@media (prefers-reduced-motion: reduce) {
  .reveal, .reveal-group > * { opacity: 1; transform: none; transition: none; }
  .reveal-cinematic > * { opacity: 1; clip-path: none; transform: none; transition: none; }
  .trust-track, .ph-glow { animation: none; }
}

/* ---------- Page hero (interior pages) ---------- */

/* Client feedback: the old near-fullscreen photo-placeholder hero on every
   inner page pushed real content too far down — shrunk to a compact title
   band so content starts right away. The .ph/.atmosphere-canvas photo
   layer is hidden (not deleted from page-hero.php) rather than removed
   from markup, so a real hero photo can still be reinstated later without
   a template change. Heading/eyebrow text is untouched — this is a visual
   height change only, not a content removal, so it doesn't cost any
   on-page SEO copy. */
.page-hero {
  position: relative;
  height: auto;
  min-height: 0;
  color: var(--ink);
  display: block;
    /* Bottom padding tightened — too much air
     between the page heading and the Story/Service metadata box right
     below it — was 1.5rem. */
  padding: calc(var(--header-height) + 2.5rem) 0 0.75rem;
}
.page-hero .ph,
.page-hero .atmosphere-canvas { display: none; }
.page-hero-content { position: relative; z-index: 1; padding-bottom: 0; }
.page-hero h1 { font-size: clamp(2rem, 4vw, 3rem); }

/* Optional right-aligned link next to the H1 (e.g. "← Back to All
   Services" on single-service.php) — see page-hero.php's link_text/
   link_url args. */
.page-hero-heading-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
}
.page-hero-heading-row h1 { margin: 0; }

/* Thank-you page: fills a defined viewport-height block below the fixed
   header and centers its content both axes, instead of the heading
   sitting top-aligned with a large blank gap before the footer. */
.thank-you-page {
  margin-top: var(--header-height);
  min-height: calc(100vh - var(--header-height));
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}
.thank-you-page .page-hero {
  padding: 0 0 1rem;
  min-height: 0;
}

/* ---------- Responsive ---------- */

@media (max-width: 1080px) {
  .footer-grid { grid-template-columns: 1.2fr 1fr 1fr; }
  .footer-grid .footer-col:last-child { grid-column: span 3; }
}

@media (max-width: 900px) {
  .nav-links,
  .nav-cta .btn { display: none; }
  .nav-toggle { display: flex; }

  .grid-3 { grid-template-columns: repeat(2, 1fr); }
  .split { grid-template-columns: 1fr; }
  .contact-layout { grid-template-columns: 1fr; }
  .footer-grid { grid-template-columns: 1fr 1fr; }
  .footer-grid .footer-col:last-child { grid-column: span 2; }

  .about-story-row { grid-template-columns: 1fr; }

  .floral-corner { width: clamp(64px, 22vw, 100px); opacity: 0.4; }

  /* Single Event photo grid — client wants to preview 3-per-row (not the
     site-wide 2-per-row .gallery-grid default below) on tablet, scoped to
     just this page via .event-photo-grid so Home/Archive galleries are
     untouched. */
  .event-photo-grid { grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 640px) {
  .grid-3, .grid-2 { grid-template-columns: 1fr; }
  .gallery-grid { grid-template-columns: repeat(2, 1fr); }
  .gallery-grid.showcase-masonry,
  .gallery-grid.event-masonry { grid-template-columns: 1fr; }
  .gallery-item { grid-column: span 2; }
  .gallery-item.wide { grid-column: span 2; aspect-ratio: 4/5; }
  .event-gallery { grid-template-columns: repeat(2, 1fr); }
  /* Same 3-per-row preview on mobile — overrides the span:2 rule above
     (which was written for the old 2-per-row masonry grid) so these cards
     actually sit 3 to a row instead of each spanning the full width. */
  .event-photo-grid { grid-template-columns: repeat(3, 1fr); }
  .event-photo-grid .gallery-item { grid-column: auto; }
  .event-gallery .ph:nth-child(1),
  .event-gallery .ph:nth-child(2),
  .event-gallery .ph:nth-child(3),
  .event-gallery .ph:nth-child(4) { grid-column: span 2; aspect-ratio: 4/5; }
  .field-grid { grid-template-columns: 1fr; }

  /* Footer, mobile — 2026-07-26 client ask: logo+description centered,
     Explore/Studio side by side (not stacked full-width), Start Here +
     its button centered, copyright centered. Was a single stacked
     column (grid-template-columns: 1fr) before this change. */
  .footer-grid { grid-template-columns: 1fr 1fr; }
  .footer-brand {
    grid-column: 1 / -1;
    text-align: center;
  }
  .footer-brand .brand {
    justify-content: center;
  }
  .footer-brand-text {
    margin-left: auto;
    margin-right: auto;
  }
  .footer-brand .social-links {
    justify-content: center;
  }
  /* Explore + Studio (the two non-last .footer-col items) fall into the
     2-column grid automatically, side by side, immediately below
     .footer-brand's full-width row. */
  .footer-grid .footer-col:last-child {
    grid-column: 1 / -1;
    text-align: center;
  }
  .footer-grid .footer-col:last-child ul {
    align-items: center;
  }
  .footer-bottom {
    justify-content: center;
    text-align: center;
  }
  .event-meta-row { gap: 1.4rem; }
  .hero-scroll { display: none; }

  .arch-frame { border-radius: 90px 90px 4px 4px / 60px 60px 4px 4px; padding: 2.5rem 1.4rem 2rem; }
  .word-strip .trust-item { font-size: 1.05rem; }

  /* About stats — mobile QA, 2026-07-31: "the stats can be reduced in
     size and be on the same line". Default .stats-row is a wrapping
     flex row with 160px-wide items, which stacks all 3 stats into
     separate lines on a phone-width viewport. Forcing nowrap + equal
     flex-basis + smaller type keeps all 3 on one row instead. */
  .stats-row {
    flex-wrap: nowrap;
    justify-content: space-between;
    gap: 0.75rem;
    margin-top: 2.5rem;
    padding-top: 2rem;
  }
  .stat { flex: 1 1 0; min-width: 0; }
  .stat .num { font-size: clamp(1.3rem, 6vw, 1.8rem); }
  .stat .label { font-size: 0.58rem; letter-spacing: 0.05em; }

  /* Home Gallery/Stories preview — mobile QA, 2026-07-31: "can be
     carousels in mobile view." CSS-only scroll-snap (no JS needed —
     the Services Preview carousel above already proves swipe-only
     scroll-snap is fully usable without assets/js/services-carousel.js
     for the core interaction; skipping JS here since these two sections
     don't need arrows/dots/autoplay, just swipeable horizontal scroll).
     Scoped to the `.home-gallery-preview`/`.home-stories-preview`
     wrapper so the full Gallery archive's own `.gallery-grid` and any
     other `.grid-3` usage elsewhere in the theme are untouched. */
  .home-gallery-preview .gallery-grid,
  .home-stories-preview .grid-3 {
    display: flex;
    grid-template-columns: none;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    gap: 1rem;
    padding-bottom: 0.5rem;
    scrollbar-width: none;
    margin: 0 -1.25rem;
    padding-left: 1.25rem;
    padding-right: 1.25rem;
  }
  .home-gallery-preview .gallery-grid::-webkit-scrollbar,
  .home-stories-preview .grid-3::-webkit-scrollbar { display: none; }
  .home-gallery-preview .gallery-grid > .gallery-item {
    flex: 0 0 70%;
    grid-column: auto;
    scroll-snap-align: start;
    min-width: 0;
  }
  .home-stories-preview .grid-3 > .gallery-item {
    flex: 0 0 80%;
    grid-column: auto;
    scroll-snap-align: start;
    min-width: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .home-gallery-preview .gallery-grid,
  .home-stories-preview .grid-3 { scroll-behavior: auto; }
}

/* Mobile nav drawer */
.mobile-nav {
  position: fixed;
  inset: 0;
  z-index: 99;
  background: var(--chocolate);
  color: var(--offwhite);
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Real bug found on a real phone: no align-items here meant flex's
     default (stretch) — each <a> stretched to the drawer's full width,
     and since none of them had their own text-align, the text rendered
     hugging the left edge instead of appearing centered in the drawer. */
  align-items: center;
  text-align: center;
  gap: 1.6rem;
  padding: 2rem var(--gutter);
  transform: translateX(100%);
  transition: transform 0.45s var(--ease);
}
.mobile-nav.is-open {
  transform: translateX(0);
}
.mobile-nav a {
  font-family: 'Syne', sans-serif;
  font-weight: 600;
  /* Was 1.9rem — real bug found on a real phone: at that size, this rule's
     specificity (class+element) beats the base .btn rule's font-size
     (single class), so the "Book a Consultation" link (which also matches
     .btn) inherited the huge nav font-size — and since .btn's padding is
     in em units, its padding ballooned right along with it, making the
     button comically oversized. Reduced here to a normal mobile-menu size,
     plus an explicit override below so .mobile-nav .btn can never inherit
     a nav-link font-size again regardless of what this value becomes later. */
  font-size: 1.25rem;
  /* letter-spacing/text-transform added 2026-08-04 (CEO feedback: mobile
     nav "didn't match" desktop) — the font-family was already identical,
     but .nav-links a (desktop) sets these two and .mobile-nav a never did,
     so mobile rendered in plain sentence-case with no tracking instead of
     Syne's tracked-uppercase look. Desktop's values, copied as-is. */
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.mobile-nav .btn {
  font-size: 0.82rem;
  margin-top: 1rem;
}

/* Facebook/Instagram icon pair — see template-parts/social-links.php.
   Sized comfortably for touch in the mobile drawer; the footer usage
   below tightens size/opacity to match that context instead. */
.social-links {
  display: flex;
  align-items: center;
  gap: 1.1rem;
}
.social-links a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 1px solid var(--line-on-dark);
  border-radius: 50%;
  color: var(--offwhite);
  opacity: 0.85;
  transition: opacity 0.25s var(--ease), border-color 0.25s var(--ease);
}
.social-links a:hover {
  opacity: 1;
  border-color: var(--offwhite);
}
.mobile-nav .social-links {
  margin-top: 1.4rem;
}
.footer-brand .social-links {
  margin-top: 1.4rem;
  gap: 0.8rem;
}
.footer-brand .social-links a {
  width: 34px;
  height: 34px;
}

/* ==========================================================================
   Round 3 — Theme switcher (demo control)
   ========================================================================== */

.theme-switcher {
  position: fixed;
  right: clamp(1rem, 3vw, 1.75rem);
  bottom: clamp(1rem, 3vw, 1.75rem);
  z-index: 95;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.75rem;
  font-family: var(--font-body);
  /* Real bug found via elementFromPoint hit-testing at mobile viewports:
     this wrapper sizes (via flex) to fit BOTH children — the visible
     toggle pill AND the collapsed .theme-panel dropdown above it, which
     stays in normal flow (opacity:0, not display:none) even when closed.
     .theme-panel already sets its own pointer-events:none/auto per
     open-state, but this wrapper div had no pointer-events rule of its
     own, so taps landing in the panel's collapsed-but-still-occupying-
     space area (well above the visible pill) were captured by this
     transparent wrapper instead of passing through to whatever page
     content sits underneath — confirmed this is exactly what silently
     swallowed taps on the footer's "Book a Consultation" (Start Here)
     button on mobile, since that button's on-screen position at natural
     scroll-to-bottom coincides with this wrapper's full (panel+pill)
     footprint, not just the visible pill. */
  pointer-events: none;
}
.theme-toggle-btn {
  pointer-events: auto;
}

.theme-toggle-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.7em;
  padding: 0.6em 1.15em 0.6em 0.65em;
  background: var(--chocolate);
  color: var(--offwhite);
  border: 1px solid rgba(var(--offwhite-rgb), 0.22);
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 14px 34px -14px rgba(var(--ink-deep-rgb), 0.55);
  transition: var(--theme-transition), transform 0.3s var(--ease), box-shadow 0.3s var(--ease);
}
.theme-toggle-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 18px 40px -14px rgba(var(--ink-deep-rgb), 0.65);
}
.theme-toggle-btn .toggle-text {
  display: flex;
  flex-direction: column;
  line-height: 1.2;
  text-align: left;
}
.theme-toggle-btn .toggle-eyebrow {
  font-size: 0.6rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-candle);
  opacity: 0.85;
}
.theme-toggle-btn .toggle-label {
  font-family: var(--font-display);
  font-size: 0.86rem;
  letter-spacing: 0.01em;
}
.theme-toggle-btn .swatch-dot {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  flex-shrink: 0;
  border: 1px solid rgba(var(--offwhite-rgb), 0.4);
  box-shadow: 0 0 0 0 rgba(var(--offwhite-rgb), 0.35);
  animation: mood-pulse 2.8s ease-in-out infinite;
}
@keyframes mood-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(var(--offwhite-rgb), 0.28); }
  50% { box-shadow: 0 0 0 5px rgba(var(--offwhite-rgb), 0); }
}
@media (prefers-reduced-motion: reduce) {
  .theme-toggle-btn .swatch-dot { animation: none; }
}

.theme-panel {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.9rem;
  background: var(--offwhite);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 24px 50px -20px rgba(var(--ink-deep-rgb), 0.4);
  min-width: 210px;
  transform-origin: bottom right;
  transform: scale(0.92) translateY(6px);
  opacity: 0;
  pointer-events: none;
  transition: transform 0.28s var(--ease), opacity 0.28s var(--ease);
}
.theme-switcher.is-open .theme-panel {
  transform: scale(1) translateY(0);
  opacity: 1;
  pointer-events: auto;
}

.theme-panel-label {
  font-size: 0.64rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
  padding: 0.3rem 0.5rem 0.5rem;
}

.theme-option {
  display: flex;
  align-items: center;
  gap: 0.75em;
  width: 100%;
  padding: 0.55em 0.5em;
  border: none;
  background: transparent;
  border-radius: 6px;
  cursor: pointer;
  text-align: left;
  color: var(--ink);
  font-size: 0.86rem;
  transition: background-color 0.2s var(--ease);
}
.theme-option:hover {
  background: var(--offwhite-warm);
}
.theme-option.is-selected {
  background: var(--offwhite-warm);
  font-weight: 600;
}
.theme-option .opt-swatch {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  flex-shrink: 0;
  border: 1px solid var(--line);
  background: linear-gradient(135deg, var(--sw-a) 0%, var(--sw-b) 55%, var(--sw-c) 100%);
}
.theme-option .opt-check {
  margin-left: auto;
  opacity: 0;
  color: var(--accent-terracotta);
  font-size: 0.9rem;
}
.theme-option.is-selected .opt-check {
  opacity: 1;
}

@media (max-width: 640px) {
  /* Round 4 mobile polish: keep the theme name (the "Design Mood"
     indicator) visible even on small screens — only drop the
     redundant eyebrow caption above it, so the control stays
     compact without losing its indicator purpose. */
  .theme-toggle-btn .toggle-eyebrow { display: none; }
  .theme-toggle-btn { padding: 0.55em 1em 0.55em 0.6em; }
  .theme-panel { min-width: 190px; }
  .theme-switcher { right: 0.85rem; bottom: 0.85rem; }
}

/* ---------- Single Event gallery lightbox ---------- */
.hod-lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(10, 6, 8, 0.94);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s var(--ease);
  padding: 4vh 4vw;
}
.hod-lightbox.is-open {
  opacity: 1;
  visibility: visible;
}
.hod-lightbox-img {
  max-width: 100%;
  max-height: 92vh;
  width: auto;
  height: auto;
  object-fit: contain;
  box-shadow: 0 30px 80px -20px rgba(0, 0, 0, 0.6);
}
.hod-lightbox-close {
  position: absolute;
  top: 1.2rem;
  right: 1.5rem;
  background: none;
  border: none;
  color: var(--offwhite);
  font-size: 2.2rem;
  line-height: 1;
  cursor: pointer;
  padding: 0.4rem 0.7rem;
}
.hod-lightbox-close:hover {
  color: var(--accent-candle);
}

.hod-lightbox-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: rgba(var(--ink-rgb), 0.4);
  border: 1px solid rgba(var(--offwhite-rgb), 0.25);
  border-radius: 50%;
  color: var(--offwhite);
  font-size: 1.8rem;
  line-height: 1;
  width: 3rem;
  height: 3rem;
  cursor: pointer;
  transition: background-color 0.2s ease, border-color 0.2s ease;
}
.hod-lightbox-nav:hover {
  background: rgba(var(--ink-rgb), 0.7);
  border-color: var(--accent-candle);
  color: var(--accent-candle);
}
.hod-lightbox-prev { left: 1rem; }
.hod-lightbox-next { right: 1rem; }

.hod-lightbox-count {
  position: absolute;
  bottom: 1.2rem;
  left: 50%;
  transform: translateX(-50%);
  color: rgba(var(--offwhite-rgb), 0.75);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
}

@media (max-width: 640px) {
  /* Buttons stay tappable (a touch user isn't required to know swipe is
     available) but shrink and pull to the very edge so they don't sit on
     top of a narrow phone-width photo. */
  .hod-lightbox-nav {
    width: 2.4rem;
    height: 2.4rem;
    font-size: 1.4rem;
  }
  .hod-lightbox-prev { left: 0.3rem; }
  .hod-lightbox-next { right: 0.3rem; }
}

@media (prefers-reduced-motion: reduce) {
  .hod-lightbox { transition: none; }
}

.hod-gallery-toast {
  position: fixed;
  right: clamp(1rem, 3vw, 2rem);
  bottom: clamp(1rem, 3vw, 2rem);
  z-index: 1001;
  width: min(24rem, calc(100vw - 2rem));
  padding: 1.2rem 3rem 1.2rem 1.25rem;
  border: 1px solid rgba(var(--offwhite-rgb), 0.2);
  background: var(--ink);
  color: var(--offwhite);
  box-shadow: 0 18px 50px rgba(var(--ink-rgb), 0.3);
  opacity: 0;
  transform: translateY(1rem);
  transition: opacity 0.25s var(--ease), transform 0.25s var(--ease);
}
.hod-gallery-toast.is-visible {
  opacity: 1;
  transform: translateY(0);
}
.hod-gallery-toast p { margin: 0; }
.hod-gallery-toast a {
  color: var(--accent-candle);
  text-decoration: underline;
  text-underline-offset: 0.18em;
}
.hod-gallery-toast-close {
  position: absolute;
  top: 0.45rem;
  right: 0.55rem;
  border: 0;
  background: transparent;
  color: inherit;
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
}

@media (prefers-reduced-motion: reduce) {
  .hod-gallery-toast { transition: none; }
}
