/*
Theme Name: Ixiun Onyx
Author: Ixiun
Description: Block theme (Full Site Editing). Colors & fonts are editable in the block editor's Global Styles; nav and footer are dynamic blocks backed by PHP, page content is built in Gutenberg.
Version: 1.6
Requires at least: 6.6
Tested up to: 7.0
Requires PHP: 7.4
*/

/* =============================================
   COLOR PALETTE & FONTS
   ============================================= */
:root {
  --color-primary:     var(--wp--preset--color--primary, #1a1b1a);
  --color-secondary:   var(--wp--preset--color--secondary, #f4f1e9);
  --color-accent:      var(--wp--preset--color--accent, #ddbd7d);
  /* The accent ramp — three steps, soft to saturated. Accent alone was carrying
     two incompatible jobs: accent-colored TEXT (eyebrows, nav hover, quote marks),
     which wants a soft tone, and accent-colored DECORATION (glows, arrows, markers,
     rules), which wants a bold one. A color that reads well as a hairline reads as
     a warning label at 1.1rem, so the two are split:

       accent       soft   → text: eyebrows, nav/footer link hover, quote marks
       accent-bold  bolder → decoration: glow core, list markers, accent rules
       accent-deep  boldest→ direction & emphasis: arrows, glow falloff

     Each fallback reproduces the exact color its rules used before the split, so a
     site that never sets these two slots renders identically to 1.3 — accent-bold
     lands back on accent, accent-deep back on btn (where the arrow-link color always was).
     Set them in Global Styles to take the ramp apart. */
  --color-accent-bold: var(--wp--preset--color--accent-bold, var(--color-accent));
  --color-accent-deep: var(--wp--preset--color--accent-deep, var(--color-btn));
  --color-btn:         var(--wp--preset--color--btn, #D6A84B);
  --color-btn-secondary: var(--wp--preset--color--btn-secondary, #e7dcdc);
  /* Primary button (is-style-primary) — each slot is its own preset, so the
     button recolors without dragging the nav/text palette with it. Fallbacks
     are the tokens these slots used before, so an unset preset keeps the old look. */
  --color-btn-primary-bg:         var(--wp--preset--color--btn-primary-bg, var(--color-btn));
  --color-btn-primary-text:       var(--wp--preset--color--btn-primary-text, var(--color-primary));
  --color-btn-primary-hover-bg:   var(--wp--preset--color--btn-primary-hover-bg, var(--color-primary));
  --color-btn-primary-hover-text: var(--wp--preset--color--btn-primary-hover-text, var(--color-text-inverse));
  /* Nav CTA button — its own four color slots so the header call-to-action can be
     recolored on its own, without dragging every editorial Primary button with it.
     Driven from Customizer ▸ Nav CTA (Button), which writes a :root override.
     "unpressed" is the resting look; "active" is the engaged look shared by the
     pressed (:active), hover and current-page states. Each slot falls back to the
     matching btn-primary slot, so a CTA whose colors are left unset renders exactly
     as it did before these slots existed. */
  --color-nav-cta-bg:          var(--color-btn-primary-bg);
  --color-nav-cta-text:        var(--color-btn-primary-text);
  --color-nav-cta-active-bg:   var(--color-btn-primary-hover-bg);
  --color-nav-cta-active-text: var(--color-btn-primary-hover-text);
  /* Secondary button (is-style-secondary) — same four slots as Primary. */
  --color-btn-secondary-bg:         var(--wp--preset--color--btn-secondary-bg, var(--color-btn-secondary));
  --color-btn-secondary-text:       var(--wp--preset--color--btn-secondary-text, var(--color-primary));
  --color-btn-secondary-hover-bg:   var(--wp--preset--color--btn-secondary-hover-bg, var(--color-btn));
  --color-btn-secondary-hover-text: var(--wp--preset--color--btn-secondary-hover-text, var(--color-primary));
  /* Ghost buttons (is-style-outline / is-style-outline-inverse) — a matched pair,
     one for dark backgrounds and one for light, each with its own slots so a ghost
     is no longer locked to white. ONE slot (…-color) drives both the label and the
     border, so the two can never drift apart: recolor the ghost and its outline
     follows. The border draws that same color at --btn-outline-border-alpha, which
     is what gives a ghost its soft edge — set it to 100% for a hard outline.
     Fallbacks reproduce the pre-1.4 white ghost exactly. */
  --color-btn-outline-color:              var(--wp--preset--color--btn-outline-color, var(--color-text-inverse));
  --color-btn-outline-hover-bg:           var(--wp--preset--color--btn-outline-hover-bg, var(--color-text-inverse));
  --color-btn-outline-hover-text:         var(--wp--preset--color--btn-outline-hover-text, var(--color-primary));
  --color-btn-outline-inverse-color:      var(--wp--preset--color--btn-outline-inverse-color, var(--color-primary));
  --color-btn-outline-inverse-hover-bg:   var(--wp--preset--color--btn-outline-inverse-hover-bg, var(--color-primary));
  --color-btn-outline-inverse-hover-text: var(--wp--preset--color--btn-outline-inverse-hover-text, var(--color-text-inverse));
  --btn-outline-border-alpha: 55%;
  --color-text:        var(--wp--preset--color--text, #1a1a2e);
  --color-text-inverse:  var(--wp--preset--color--text-inverse, #ffffff);
  --color-muted:       var(--wp--preset--color--muted, #6b7280);
  --color-bg:          var(--wp--preset--color--bg, #ffffff);
  --color-surface:     var(--wp--preset--color--surface, #ffffff);
  /* A second light surface, meant to sit a step off --color-bg so cards / panels
     lift off the page. Defaults to white (identical to bg) so it changes nothing
     until a site darkens it in Editor > Styles > Colors > "Background Inverse". */
  --color-bg-inverse:  var(--wp--preset--color--bg-inverse, #ffffff);
  --color-footer-bg:   var(--wp--preset--color--footer-bg, #1f211d);
  --color-footer-text: var(--wp--preset--color--footer-text, #EFE7D2);
  --color-drawer-bg:         var(--wp--preset--color--drawer-bg, #1f211d);
  --color-drawer-link:       var(--wp--preset--color--drawer-link, #ffffff);
  --color-drawer-link-hover: var(--wp--preset--color--drawer-link-hover, #ddbd7d);
  /* Divider lines for the reusable stat-grid — a faint tint of the primary color */
  --stat-divider:      color-mix(in srgb, var(--color-primary) 20%, transparent);
  /* overriding bootstrap background */
  --bs-body-bg:        var(--color-bg); 
  --max-width:         1440px;
  --nav-height:        115px;
  --nav-height-shrunk: 70px;
  --topbar-height:     44px;
  /* starting fonts, can be changed in the block theme editor for specific site needs */
  --font-heading:      var(--wp--preset--font-family--georgia, Georgia, serif);
  --font-body:    var(--wp--preset--font-family--system-sans, 'Helvetica Neue', Arial, sans-serif);
  --font-logo:         'Montserrat', sans-serif;
  /* Accent font for the cabin-intro heading + photo caption. Repointed to
     Playfair Display (Amatic SC was removed). Swap the preset to restyle. */
  --font-script:       var(--wp--preset--font-family--playfair-display, 'Playfair Display', serif);
  /* Functional monospace (code / placeholder boxes) — not a brand font, so it
     stays a plain fallback rather than a Global Styles preset. */
  --font-mono:         'Courier New', monospace;

  --transition:        0.3s ease;
  --border-radius:     6px;
}

/* =============================================
   RESET & BASE
   ============================================= */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  scroll-behavior: smooth;
  overflow-x: clip;
}

body {
  font-family: var(--font-body);
  color: var(--color-text);
  overflow-x: clip;
}

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

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

ul {
  list-style: none;
}

/* WordPress's inline "Text color" / "Highlight" formats wrap the selected text
   in a <mark>, and Bootstrap's Reboot gives every <mark> padding: .1875em and a
   pale highlight background. So any inline-colored word (e.g. a gold accent word
   in a heading) silently picks up space on all four sides — enough to read as a
   double-space before the word — plus a stray highlighter box.

   Reset <mark> back to plain inline text. A color or background deliberately
   chosen in the editor still wins: it rides on .has-*-color / .has-*-background-
   color classes or an inline style, all of which outrank this bare-element rule.
   Only Bootstrap's unasked-for defaults are removed. */
mark {
  padding: 0;
  background-color: transparent;
  color: inherit;
}

/* =============================================
   SKIP LINK  (WCAG 2.4.1 — Bypass Blocks)
   ---------------------------------------------
   First focusable element on every page (emitted at the top of the header in
   functions.php). Off-screen until it receives keyboard focus, then it drops
   into the top-left corner. Its target, #site-main, is stamped onto the <main>
   element by onyx_mark_skip_target(). */
.skip-link {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 2000;              /* above the fixed header (1050) and drawer overlay */
  transform: translateY(-120%);
  padding: 0.75rem 1.25rem;
  background: var(--color-primary, #1a1b1a);
  color: #fff;
  font-weight: 600;
  text-decoration: none;
  border-radius: 0 0 6px 0;
}

.skip-link:focus {
  transform: translateY(0);
  outline: 3px solid var(--color-accent, #ddbd7d);
  outline-offset: 2px;
}

/* The skip target receives focus programmatically; it's a large layout element,
   so suppress the focus ring on it (the visible cue is the link the user just
   activated, and the page jump itself). */
#site-main:focus {
  outline: none;
}

/* =============================================
   NAV
   ============================================= */

.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  transition: transform var(--transition);
}

.nav-anim-hide.nav-hidden .site-header {
  transform: translateY(-100%);
}

.nav-anim-load .site-header {
  animation: onyx-nav-in 0.5s ease backwards;
}

@keyframes onyx-nav-in {
  from { opacity: 0; transform: translateY(-100%); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .nav-anim-load .site-header {
    animation: none;
  }
  .nav-anim-hide.nav-hidden .site-header {
    transform: none;
  }
}

.nav-anim-overlay.has-hero .site-main,
.nav-anim-overlay.has-hero.is-scrolled .site-main {
  padding-top: 0;
}

.nav-anim-overlay.has-hero .site-nav,
.nav-anim-overlay.has-hero .nav-topbar {
  background: transparent;
}

/* Soft top-down scrim keeps white nav text legible over bright hero areas. */
.nav-anim-overlay.has-hero .site-header::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 220%;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.45), transparent);
  pointer-events: none;
  z-index: -1;
  transition: opacity var(--transition);
}


.nav-anim-overlay.has-hero.is-scrolled .site-nav {
  background: var(--color-primary);
}

/* toolbar at the top is always black */
.nav-anim-overlay.has-hero.is-scrolled .nav-topbar {
  background: var(--color-primary);
}

.nav-anim-overlay.has-hero.is-scrolled .site-header::before {
  opacity: 0;
}

.site-nav {
  position: relative;
  height: var(--nav-height);
  background: var(--color-primary);
  transition: height var(--transition), background var(--transition);
}

body.nav-anim-shrink.is-scrolled .site-nav {
  height: var(--nav-height-shrunk);
}

/* Top bar — a three-slot strip above the nav. Each slot (left / center / right)
   holds contact info, custom text, a social icon, or nothing; see Customizer >
   Top Bar. --topbar-bg / --topbar-color are set inline by the header when the
   Customizer colors are chosen, and fall back to the primary color otherwise. */
.nav-topbar {
  height: var(--topbar-height);
  background: var(--topbar-bg, var(--color-primary));
  color: var(--topbar-color, var(--color-text-inverse));
  overflow: hidden;
  transition: height var(--transition), opacity var(--transition), background var(--transition);
}

body.nav-anim-shrink.is-scrolled .nav-topbar {
  height: 0;
  opacity: 0;
}

/* Grid, not flex: the 1fr side columns keep the center slot optically centered
   on the page no matter how wide or lopsided the outer two are. */
.topbar-inner {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 1.5rem;
  height: var(--topbar-height);
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 2rem 0 0;
}

.topbar-slot {
  display: flex;
  align-items: center;
  min-width: 0;
}

.topbar-slot--center {
  justify-content: center;
}

.topbar-slot--right {
  justify-content: flex-end;
}

/* Custom text slot — a brokerage, franchise, or association line. */
.topbar-text {
  color: inherit;
  font-family: var(--font-body);
  font-size: 0.8rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: opacity var(--transition);
}

.topbar-text--caps {
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-weight: 600;
}

a.topbar-text:hover {
  opacity: 0.75;
}

.topbar-contact {
  display: flex;
  align-items: center;
  gap: 2rem;
  font-family: var(--font-body);
  font-size: 0.85rem;
  letter-spacing: 0.03em;
}

.contact-item {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--color-text-inverse);
  opacity: 0.9;
  white-space: nowrap;
  transition: opacity var(--transition), color var(--transition);
}

a.contact-item:hover {
  opacity: 1;
  color: var(--color-accent);
}

.contact-item svg {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  color: var(--color-accent);
}

.topbar-social {
  display: flex;
  align-items: center;
}

.topbar-social .nav-social-link svg {
  width: 18px;
  height: 18px;
}

.nav-inner {
  display: flex;
  align-items: stretch;
  justify-content: space-between;
  height: 100%;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 1rem 2rem 1rem 0;
  line-height: 1;
}

/* Logo */
.nav-logo {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  height: var(--nav-height);
  margin-top: -1rem;
  margin-bottom: -1rem;
  transition: height var(--transition);
}

body.nav-anim-shrink.is-scrolled .nav-logo {
  height: var(--nav-height-shrunk);
}

.nav-logo a {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  height: 100%;
  font-family: var(--font-heading);
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--color-text-inverse);
  line-height: 1;
  transition: opacity var(--transition);
}

.nav-logo a:hover {
  opacity: 0.85;
}

.nav-logo img {
  height: 100%;
  width: auto;
  /* Scaled with the logo so a wide wordmark isn't clipped at larger sizes. */
  max-width: calc(300px * var(--logo-scale, 1));
  object-fit: contain;
}

/* Image-logo recolor (Customizer > Logo). The logo file is used as a mask on the
   link and filled with --logo-color, so any solid color works — it swaps to
   --logo-color-scrolled once the page is scrolled (.is-scrolled), which is also
   when a transparent-over-hero nav turns solid. The <img> is hidden but kept in
   flow so the link keeps the logo's box for the mask to size against. Defaults to
   white, matching the previous brightness(0) invert(1) treatment. --logo-mask
   (the logo's URL) is injected inline by onyx_render_header. Scoped to the logo's
   own link (.custom-logo-link) so an optional title/tagline link beside it is
   never masked. */
.nav-logo--image .custom-logo-link {
  background-color: var(--logo-color, #ffffff);
  -webkit-mask: var(--logo-mask) center / contain no-repeat;
          mask: var(--logo-mask) center / contain no-repeat;
  transition: background-color var(--transition), opacity var(--transition);
}

.nav-logo--image .custom-logo-link img {
  visibility: hidden;
}

body.is-scrolled .nav-logo--image .custom-logo-link {
  background-color: var(--logo-color-scrolled, var(--logo-color, #ffffff));
}

/* "Show logo in original colors" (Customizer > Logo). Turns off the recolor mask
   so the logo image renders in its own colors: the <img> is revealed and the
   mask/fill removed, at all scroll states. */
.nav-logo--natural .custom-logo-link,
body.is-scrolled .nav-logo--natural .custom-logo-link {
  background-color: transparent;
  -webkit-mask: none;
          mask: none;
}

.nav-logo--natural .custom-logo-link img {
  visibility: visible;
}

/* Logo size (Customizer > Logo). --logo-scale feeds the text-logo font size and
   the image max-width at every width. The nav-height overrides below (which make
   the whole bar grow with the logo instead of overflowing it) are desktop-only —
   mobile keeps its own fixed --nav-height (see the max-width:1100px block). */
body.logo-size-small  { --logo-scale: 0.78; }
body.logo-size-medium { --logo-scale: 1; }
body.logo-size-large  { --logo-scale: 1.25; }
body.logo-size-xlarge { --logo-scale: 1.5; }

@media (min-width: 1101px) {
  /* Grow/shrink the header with the logo. The logo is sized to 100% of the bar,
     so scaling the bar scales the logo — no overhang. The shrunk twin scales in
     step so the shrink-on-scroll animation stays proportional. */
  body.logo-size-small {
    --nav-height: 90px;
    --nav-height-shrunk: 58px;
  }
  body.logo-size-large {
    --nav-height: 144px;
    --nav-height-shrunk: 88px;
  }
  body.logo-size-xlarge {
    --nav-height: 172px;
    --nav-height-shrunk: 104px;
  }
}

/* =============================================
   BRAND MARK  —  the small diamond before the logo   (Customizer > Logo)
   ---------------------------------------------
   A rotated square drawn as a ::before on .nav-logo, so it sits to the left of
   whatever the header holds — a logo image, the wordmark, or both — and inherits
   the container's existing 0.85rem gap. It's a pseudo-element rather than markup
   so it stays decorative: screen readers never announce it.

   Off by default. Customizer > Logo > "Diamond mark" adds one of:

     .nav-logo--mark        the diamond, drawn flat and still
     .nav-logo--mark-glow   + a warm halo that breathes like candlelight

   The glow is the nav-scale sibling of .hero-cinematic--glow, and follows the
   same rules: it pulses on the accent color, and it holds still for anyone
   browsing with reduced motion on.

   Tunable (set on .nav-logo, or globally in :root):
     --logo-mark-color        the diamond's color        default: --color-accent-bold
     --logo-mark-size         its width/height at scale 1  default: 11px
     --logo-mark-glow-speed   one full pulse cycle       default: 4.5s

   Size tracks --logo-scale (Customizer > Logo > Logo size), so the diamond grows
   and shrinks with the wordmark instead of drifting out of proportion to it.
   ============================================= */
.nav-logo--mark::before {
  content: '';
  flex: none;
  align-self: center;
  width:  calc(var(--logo-mark-size, 11px) * var(--logo-scale, 1));
  height: calc(var(--logo-mark-size, 11px) * var(--logo-scale, 1));
  background: var(--logo-mark-color, var(--color-accent-bold));
  transform: rotate(45deg);
  transition: background-color var(--transition);
}

/* The halo. Sits on box-shadow rather than a second element, so the diamond stays
   a single pseudo-element and the transform above is left free (the animation
   never touches it — rotate(45deg) would be wiped out if it did). */
.nav-logo--mark-glow::before {
  box-shadow: 0 0 10px 1px color-mix(in srgb, var(--logo-mark-color, var(--color-accent-bold)) 40%, transparent);
  animation: onyx-logo-mark-glow var(--logo-mark-glow-speed, 4.5s) ease-in-out infinite;
}

@keyframes onyx-logo-mark-glow {
  0%, 100% {
    box-shadow: 0 0 8px 0 color-mix(in srgb, var(--logo-mark-color, var(--color-accent-bold)) 32%, transparent);
  }
  50% {
    box-shadow: 0 0 20px 4px color-mix(in srgb, var(--logo-mark-color, var(--color-accent-bold)) 60%, transparent);
  }
}

/* Reduced motion: keep the halo, drop the flicker. */
@media (prefers-reduced-motion: reduce) {
  .nav-logo--mark-glow::before {
    animation: none;
  }
}

/* Brand text (site title + tagline). Shown on its own as the wordmark when there
   is no logo, and optionally beside the logo image (Customizer > Logo > Show site
   title / tagline). The title/tagline are separate spans so each can be toggled. */
.nav-logo .nav-logo-text {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  gap: 0.15rem;
  line-height: 1.1;
  transition: opacity var(--transition);
}

.nav-logo .nav-logo-text:hover {
  opacity: 0.8;
}

/* Tagline BESIDE the title instead of under it (Customizer > Logo > Tagline
   position). Grid rather than flex because this layout has to center on two axes
   at once, and a flex row only gives you the one control: align-items centers the
   tagline against the title, while align-content centers the resulting row inside
   the full-height logo link. In flex those would be the same property, and the
   row would pin itself to the top of the bar.

   The tagline is centered on the title, NOT sat on its baseline. Baseline is the
   typographer's instinct here, but the tagline is less than half the title's size,
   so a shared baseline drops it to the bottom of a tall wordmark and reads as
   misalignment rather than as a considered lockup. */
.nav-logo--inline .nav-logo-text {
  display: grid;
  grid-auto-flow: column;
  justify-content: start;
  align-content: center;   /* centers the row inside the bar */
  align-items: center;     /* centers the tagline against the title */
  column-gap: var(--logo-tagline-gap, 0.9rem);
}

/* Lets a long site title wrap rather than push the tagline off the bar — grid
   children default to min-content, which would otherwise refuse to shrink. */
.nav-logo--inline .nav-logo-title {
  min-width: 0;
}

/* The title's and tagline's fonts and colors are their own tokens, so a site can
   run (say) a serif wordmark beside a gold sans-serif tagline without touching
   the rest of the palette. Each falls back through to exactly what it resolved to
   before the tokens existed — an unset site renders identically:

     --logo-title-font      default: --font-logo   (Montserrat)
     --logo-title-color     default: --logo-color, then white
     --logo-tagline-font    default: --font-body
     --logo-tagline-color   default: --logo-color, then white
     --logo-tagline-opacity default: 0.85

   All five are injected on .nav-logo by onyx_render_header from Customizer > Logo,
   and only when actually set. */
.nav-logo-title {
  font-family: var(--logo-title-font, var(--font-logo));
  font-size: calc(1.8rem * var(--logo-scale, 1));
  font-weight: 300;
  letter-spacing: 0.1em;
  color: var(--logo-title-color, var(--logo-color, var(--color-text-inverse)));
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.18);
  transition: font-size var(--transition), letter-spacing var(--transition), color var(--transition);
}

/* SPLIT SITE TITLE — one word of the title set in its own face, the way a
   personal-brand wordmark pairs a script first name with a tracked-caps surname
   ("Arianne MORELAND"). Turned on in Customizer > Logo; the title stays ONE
   string in Settings > General and onyx_render_header wraps a word at render
   time, so nothing else that prints the site name is affected.

   Markup (from onyx_brand_title_markup):
     .nav-logo-title > .nav-logo-title-lead + .nav-logo-title-rest
   .nav-logo-title-lead is always the split-off word regardless of which end it
   came from, so "first word" and "last word" share one rule.

   Tunable (set on .nav-logo, or globally in :root):
     --logo-title-lead-font       default: --logo-title-font (i.e. no change)
     --logo-title-lead-color      default: --color-btn (the palette's Button
                                  color — otherwise unused on most sites, so the
                                  split word gives that swatch a visible job)
     --logo-title-lead-scale      default: 1.5  — script faces run small next to
                                  caps, so the lead word is sized up to match
     --logo-title-lead-spacing    default: normal — tracking is what makes the
                                  caps side read as caps; a script gets none
     --logo-title-rest-transform  default: uppercase
     --logo-title-rest-weight     default: 400
   The first two are injected from the Customizer's font/color pickers. */
.nav-logo--split-title .nav-logo-title {
  /* The two words carry their own sizes, so the line is set from the taller of
     them rather than from a font-size the title element no longer uses. */
  display: inline-flex;
  align-items: baseline;
  gap: 0.28em;
  line-height: 1.1;
}

.nav-logo--split-title .nav-logo-title-lead {
  font-family: var(--logo-title-lead-font, var(--logo-title-font, var(--font-logo)));
  font-size: calc(1em * var(--logo-title-lead-scale, 1.5));
  letter-spacing: var(--logo-title-lead-spacing, normal);
  color: var(--logo-title-lead-color, var(--color-btn));
}

.nav-logo--split-title .nav-logo-title-rest {
  text-transform: var(--logo-title-rest-transform, uppercase);
  font-weight: var(--logo-title-rest-weight, 400);
}

/* The 0.85 opacity is what makes an unstyled tagline read as secondary to the
   title. It's a variable because a tagline given its OWN color was chosen
   deliberately, and dimming a deliberate choice by 15% just makes it look wrong —
   so onyx_render_header sends --logo-tagline-opacity:1 whenever a tagline color
   is set. Left alone, the 0.85 stands. */
.nav-logo-tagline {
  font-family: var(--logo-tagline-font, var(--font-body, sans-serif));
  font-size: calc(0.72rem * var(--logo-scale, 1));
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--logo-tagline-color, var(--logo-color, var(--color-text-inverse)));
  opacity: var(--logo-tagline-opacity, 0.85);
  transition: font-size var(--transition), color var(--transition);
}

/* Beside a logo image the title/tagline play a supporting role, so they're a bit
   smaller than the standalone wordmark. */
.nav-logo--image .nav-logo-title {
  font-size: calc(1.2rem * var(--logo-scale, 1));
}

.nav-logo--image .nav-logo-tagline {
  font-size: calc(0.66rem * var(--logo-scale, 1));
}

/* Scrolled color for the brand text — mirrors the image-logo swap above. Each
   line resolves to its own color when the scrolled color is unset, so a site that
   sets a title/tagline color but no scrolled color keeps that color at every
   scroll position instead of snapping back to white. */
body.is-scrolled .nav-logo-title {
  color: var(--logo-color-scrolled, var(--logo-title-color, var(--logo-color, var(--color-text-inverse))));
}

body.is-scrolled .nav-logo-tagline {
  color: var(--logo-color-scrolled, var(--logo-tagline-color, var(--logo-color, var(--color-text-inverse))));
}

body.nav-anim-shrink.is-scrolled .nav-logo--text .nav-logo-title {
  font-size: calc(1.4rem * var(--logo-scale, 1));
  letter-spacing: 0.07em;
}

/* Right side wrapper (menu + social icons) */
.nav-right {
  display: flex;
  align-items: center;
  height: 100%;
  gap: 1.5rem;
}

/* Nav social icon */
.nav-social-link {
  display: flex;
  align-items: center;
  color: var(--color-text-inverse);
  opacity: 0.85;
  transition: opacity var(--transition);
}

.nav-social-link:hover {
  opacity: 1;
}

.nav-social-link svg {
  width: 22px;
  height: 22px;
}

/* The drawer has its own palette (gold ground, white links), so the icon follows
   --color-drawer-link rather than the nav's --color-text-inverse: a site running a
   light header inverts that token to a dark value, which is right in the nav bar
   but lands as a black icon among the drawer's white links. */
.nav-social-link--drawer {
  margin-top: 2rem;
  color: var(--color-drawer-link);
}

.nav-social-link--drawer:hover {
  color: var(--color-drawer-link-hover);
  opacity: 1;
}

/* Desktop menu */
.nav-menu {
  display: flex;
  align-items: center;
  gap: 2rem;
  margin: 0 0 0 5rem;
  padding: 0;
  height: 100%;
}

.nav-menu li {
  display: flex;
  align-items: center;
  align-self: stretch;   /* fill the nav height so dropdowns anchor to the bar's bottom edge (no hover dead-zone) */
  margin: 0;
  padding: 0;
  list-style: none;
  position: relative;
}

.nav-menu li::after {
  content: '';
  position: absolute;
  /* Anchored just under the (vertically centered) link text rather than the
     bottom of the now-full-height li, so the accent underline sits where it
     always has regardless of li height. */
  top: calc(50% + 0.8em);
  left: 0;
  width: 0;
  height: 1.5px;
  background: var(--color-accent);
  transition: width var(--transition);
}

.nav-menu li:hover::after,
.nav-menu .current-menu-item::after {
  width: 100%;
}

.nav-drawer .nav-menu li::after {
  display: none;
}

.nav-drawer .nav-menu .current-menu-item > a {
  color: var(--color-text-inverse);
  opacity: 0.55;
}

.nav-menu a {
  font-family: var(--font-body);
  font-size: 1.1rem;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-text-inverse);
  line-height: 1;
  transition: color var(--transition);
}

.nav-menu a:hover,
.nav-menu .current-menu-item > a {
  color: var(--color-accent);
}

/* =============================================
   NAV CTA  —  one menu item styled as a button
   ---------------------------------------------
   The item chosen in Customizer ▸ Nav CTA gets the .nav-cta class (added by
   onyx_nav_cta_class). It is the editorial Primary button (.is-style-primary)
   shrunk to nav scale — same barely-rounded 2px corners, same 1.5px border that
   matches the fill, same resting gradient + raised shadow, same left-to-right
   color wipe on hover. Only the footprint differs (a nav bar can't carry the
   hero's 1.45rem/3.6rem padding), plus a slow idle pulse to catch the eye.

   Colors come from the four btn-primary-* slots — the same pairs the editorial
   Primary button uses — so the two always match and re-theme from one place.
   (Do NOT reach for --color-primary / --color-text-inverse here: those are
   nav/body text tokens, and a site that runs a light header inverts them, which
   turns the filled button light-on-light.) */
.nav-menu .nav-cta > a {
  position: relative;
  z-index: 0;              /* stacking context so the wipe sits behind the label */
  overflow: hidden;        /* clip the wipe to the button box */
  display: inline-flex;
  align-items: center;
  padding: 0.85em 2.1rem;
  border-radius: 2px;      /* barely-rounded, editorial — matches .is-style-primary */
  border: 1.5px solid var(--color-nav-cta-bg);
  background: linear-gradient(150deg,
    color-mix(in srgb, var(--color-nav-cta-bg) 82%, #fff) 0%,
    color-mix(in srgb, var(--color-nav-cta-bg) 90%, #000) 100%);
  color: var(--color-nav-cta-text);
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  box-shadow: 0 3px 12px rgba(0, 0, 0, 0.14);   /* rests raised, so it reads as the primary action */
  transition: color 0.42s ease, border-color 0.42s ease, box-shadow 0.42s ease;
}

/* Signature left-to-right wipe — same pseudo-element trick + easing as the
   editorial buttons (.is-style-primary). Hidden (scaleX 0) until hover. */
.nav-menu .nav-cta > a::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--color-nav-cta-active-bg);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.42s cubic-bezier(0.65, 0, 0.35, 1);
}

.nav-menu .nav-cta > a:hover {
  color: var(--color-nav-cta-active-text);   /* label flips as the wipe fills in */
  border-color: var(--color-nav-cta-active-bg);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.22);
}

.nav-menu .nav-cta > a:hover::before {
  transform: scaleX(1);
}

/* Pressed state — pin the hover pair so the label flips to white in lockstep
   with the black wipe. Without this the button has no :active rule, so on tap
   (where :hover doesn't hold) the label stays btn-primary-text while the wipe
   paints btn-primary-hover-bg — on a site where those match, that reads as
   black-on-black for the duration of the press. */
.nav-menu .nav-cta > a:active {
  color: var(--color-nav-cta-active-text);
  border-color: var(--color-nav-cta-active-bg);
}

.nav-menu .nav-cta > a:active::before {
  transform: scaleX(1);
}

/* Current page — a settled "you are here" fill rather than a permanently-hovered
   button: paint the hover colors straight on (no gradient, and no wipe to animate
   in on page load) and stop the idle pulse, since there's nowhere left to nudge
   the visitor. */
.nav-menu .nav-cta.current-menu-item > a,
.nav-menu .nav-cta.current-menu-item > a:hover {
  background: var(--color-nav-cta-active-bg);
  border-color: var(--color-nav-cta-active-bg);
  color: var(--color-nav-cta-active-text);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
  animation: none;
}

.nav-menu .nav-cta.current-menu-item > a::before {
  content: none;
}

/* The button is its own affordance — suppress the sliding accent underline. */
.nav-menu .nav-cta::after {
  display: none;
}

/* Slow idle pulse so the CTA catches the eye without a hover. Motion-safe, and
   it pauses while hovered so it doesn't fight the wipe. */
@media (prefers-reduced-motion: no-preference) {
  .nav-menu .nav-cta > a {
    animation: onyx-cta-pulse 2.8s ease-in-out infinite;
  }
  .nav-menu .nav-cta > a:hover {
    animation: none;
  }
}

@keyframes onyx-cta-pulse {
  0%, 100% { box-shadow: 0 3px 12px rgba(0, 0, 0, 0.14); }
  50%      { box-shadow: 0 6px 20px rgba(0, 0, 0, 0.24); }
}

/* ---------------------------------------------------------------
   Drawer: the CTA sheds the button entirely.

   The desktop treatment — filled gradient, 1.5px border, raised shadow, idle
   pulse — is tuned to sit in the header bar. Dropped into the drawer it fights
   the surroundings: the fill sits too close in value to the gold ground for the
   label to hold, and the box/shadow/pulse read as clutter beside four lines of
   plain white type. So in the drawer, ORDER stays type. A hairline rule above it
   separates it from the nav items, an underline and a heavier weight mark it as
   the action, and an arrow points out of the drawer. Everything boxy is unwound
   below — undoing the desktop rules is the bulk of this block.
   --------------------------------------------------------------- */

/* Rule above, matching the .drawer-contact divider so the drawer has one
   consistent separator. */
.nav-drawer .nav-menu .nav-cta {
  margin-top: 0.5rem;
  padding-top: 1.4rem;
  border-top: 1px solid rgba(255, 255, 255, 0.3);
  width: 100%;
}

.nav-drawer .nav-menu .nav-cta > a {
  overflow: visible;   /* desktop clips the wipe; nothing left here to clip */
  gap: 0.55rem;        /* space for the arrow, which rides in as ::after */
  padding: 0 0 0.4rem;
  background: none;
  border: 0;
  border-bottom: 1.5px solid var(--color-drawer-link);   /* the underline */
  border-radius: 0;
  box-shadow: none;
  animation: none;     /* cancels the motion-safe pulse from the desktop rule */
  color: var(--color-drawer-link);
  font-size: 1.1rem;   /* same size as the nav links it sits under */
  letter-spacing: 0.14em;
  transition: color var(--transition), border-color var(--transition);
}

/* Kill the sliding wipe — there's no longer a box for it to fill. */
.nav-drawer .nav-menu .nav-cta > a::before {
  content: none;
}

/* The arrow. A pseudo-element rather than markup, so the menu item stays an
   ordinary WordPress link the client can rename in Appearance > Menus. */
.nav-drawer .nav-menu .nav-cta > a::after {
  content: '\2192';
  display: inline-block;
  transition: transform var(--transition);
}

/* Hover, and the settled "you are here" state, are the same move: the label and
   its underline take the drawer's dark ink, and the arrow nudges outward. The
   opacity reset undoes the drawer's dimmed current-item treatment — the CTA
   shouldn't fade out on its own page. */
.nav-drawer .nav-menu .nav-cta > a:hover,
.nav-drawer .nav-menu .nav-cta.current-menu-item > a,
.nav-drawer .nav-menu .nav-cta.current-menu-item > a:hover {
  background: none;
  border-bottom-color: var(--color-drawer-link-hover);
  color: var(--color-drawer-link-hover);
  box-shadow: none;
  opacity: 1;
}

.nav-drawer .nav-menu .nav-cta > a:hover::after,
.nav-drawer .nav-menu .nav-cta.current-menu-item > a::after {
  transform: translateX(4px);
}

/* =============================================
   DROPDOWN SUBMENUS
   ---------------------------------------------
   Parent menu items (Appearance > Menus, an item nested under another) get the
   class .menu-item-has-children from wp_nav_menu and contain a <ul.sub-menu>.
   Desktop  (.nav-right): the sub-menu is a hover/focus dropdown panel.
   Mobile   (.nav-drawer): the sub-menu is a tap-to-expand accordion; the caret
   toggle button is injected by js/nav.js.
   ============================================= */

/* ---- Desktop dropdown ---- */

/* Caret after parent-item links */
.nav-right .menu-item-has-children > a {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
}

.nav-right .menu-item-has-children > a::after {
  content: '';
  width: 0.4em;
  height: 0.4em;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-0.15em) rotate(45deg);
  transition: transform var(--transition);
  flex-shrink: 0;
}

/* Rotate the caret up when the menu is open */
.nav-right .menu-item-has-children:hover > a::after,
.nav-right .menu-item-has-children:focus-within > a::after {
  transform: translateY(0.1em) rotate(-135deg);
}

/* The dropdown panel */
.nav-right .sub-menu {
  position: absolute;
  top: 100%;               /* li is full-height, so this is the nav's bottom edge */
  left: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  min-width: 220px;
  margin: 0;
  padding: 0.5rem 0;
  list-style: none;
  background: var(--color-primary);
  border-top: 2px solid var(--color-accent-bold);
  box-shadow: 0 14px 30px rgba(0, 0, 0, 0.28);
  /* Hidden until the parent is hovered/focused */
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px);
  transition: opacity var(--transition), transform var(--transition), visibility var(--transition);
}

.nav-right .menu-item-has-children:hover > .sub-menu,
.nav-right .menu-item-has-children:focus-within > .sub-menu {
  opacity: 1;
  visibility: visible;
  transform: none;
}

/* Sub-menu items: plain stacked rows, no inherited underline or row layout */
.nav-right .sub-menu li {
  display: block;
  align-self: auto;
  width: 100%;
}

.nav-right .sub-menu li::after {
  display: none;
}

.nav-right .sub-menu a {
  display: block;
  padding: 0.6rem 1.4rem;
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  white-space: nowrap;
  transition: color var(--transition), background var(--transition);
}

.nav-right .sub-menu a:hover,
.nav-right .sub-menu .current-menu-item > a {
  color: var(--color-accent);
  background: rgba(255, 255, 255, 0.06);
}

/* ---- Mobile drawer accordion ---- */

/* Parent item becomes a row (link + caret button) that wraps the sub-menu below */
.nav-drawer .nav-menu .menu-item-has-children {
  flex-wrap: wrap;
  column-gap: 0.5rem;
  width: 100%;
}

/* Caret toggle button (injected by js/nav.js) */
.nav-drawer .submenu-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  margin-left: auto;          /* push it to the right edge of the row */
  padding: 0;
  background: none;
  border: none;
  color: var(--color-drawer-link);
  cursor: pointer;
}

.nav-drawer .submenu-toggle::before {
  content: '';
  width: 0.55rem;
  height: 0.55rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(-0.12rem) rotate(45deg);
  transition: transform var(--transition);
}

.nav-drawer .menu-item-has-children.submenu-open > .submenu-toggle::before {
  transform: translateY(0.12rem) rotate(-135deg);
}

/* Collapsed by default; expands when the parent gets .submenu-open */
.nav-drawer .sub-menu {
  flex-basis: 100%;
  width: 100%;
  margin: 0;
  padding-left: 1rem;
  list-style: none;
  max-height: 0;
  overflow: hidden;
  transition: max-height var(--transition);
}

.nav-drawer .menu-item-has-children.submenu-open > .sub-menu {
  max-height: 60vh;
}

.nav-drawer .sub-menu li {
  width: 100%;
}

.nav-drawer .sub-menu a {
  display: block;
  padding: 0.5rem 0;
  font-size: 1rem;
  opacity: 0.9;
}

/* Hamburger button */
.nav-hamburger {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 40px;
  height: 40px;
  background: none;
  border: none;
  cursor: pointer;
  gap: 6px;
  padding: 0;
  position: fixed;
  top: calc((var(--nav-height) - 40px) / 2);
  right: 2rem;
  z-index: 1100;
  transition: top var(--transition);
}

body.nav-anim-shrink.is-scrolled .nav-hamburger {
  top: calc((var(--nav-height-shrunk) - 40px) / 2);
}

/* only takes effect when you're logged in as an admin so the site isn't distorted when looking at it */
.admin-bar .site-header {
  top: 32px;
}

.admin-bar .nav-hamburger {
  top: calc(32px + (var(--nav-height) - 40px) / 2);
}

.admin-bar.nav-anim-shrink.is-scrolled .nav-hamburger {
  top: calc(32px + (var(--nav-height-shrunk) - 40px) / 2);
}

@media screen and (max-width: 782px) {
  .admin-bar .site-header {
    top: 46px;
  }

  .admin-bar .nav-hamburger {
    top: calc(46px + (var(--nav-height) - 40px) / 2);
  }

  .admin-bar.nav-anim-shrink.is-scrolled .nav-hamburger {
    top: calc(46px + (var(--nav-height-shrunk) - 40px) / 2);
  }
}

.nav-hamburger span {
  display: block;
  width: 24px;
  height: 2px;
  background: var(--color-text-inverse);
  border-radius: 2px;
  transition: transform var(--transition), opacity var(--transition);
  transform-origin: center;
}

/* Animate to X */
.nav-hamburger.is-open span:nth-child(1) {
  transform: translateY(8px) rotate(45deg);
}

.nav-hamburger.is-open span:nth-child(2) {
  opacity: 0;
  transform: scaleX(0);
}

.nav-hamburger.is-open span:nth-child(3) {
  transform: translateY(-8px) rotate(-45deg);
}

/* Mobile drawer */
.nav-drawer {
  position: fixed;
  top: 0;
  right: 0;
  width: 300px;
  height: 100vh;
  background: var(--color-drawer-bg);
  z-index: 1050;
  display: flex;
  flex-direction: column;
  padding: 6rem 2rem 2rem;
  transform: translateX(100%);
  transition: transform var(--transition);
}

.nav-drawer.is-open {
  transform: translateX(0);
}

.nav-drawer .nav-menu {
  flex-direction: column;
  align-items: flex-start;
  gap: 1.5rem;
  margin: 0;
  height: auto;
}

.nav-drawer .nav-menu a {
  font-size: 1.1rem;
  color: var(--color-drawer-link);
}

.nav-drawer .nav-menu a:hover,
.nav-drawer .nav-menu .current-menu-item > a {
  color: var(--color-drawer-link-hover);
}

.drawer-contact {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-top: 2rem;
  padding-top: 1.75rem;
  border-top: 1px solid rgba(255, 255, 255, 0.3);
}

.contact-item--drawer {
  font-size: 1rem;
  gap: 0.6rem;
  opacity: 1;
  color: var(--color-drawer-link);
  white-space: normal;        /* let long addresses wrap (top bar forces nowrap) */
  align-items: flex-start;    /* icon lines up with the first line of wrapped text */
  overflow-wrap: anywhere;    /* break a long email/URL rather than overflow */
}

.contact-item--drawer svg {
  margin-top: 0.15em;         /* nudge the top-aligned icon to sit on the text baseline */
}

.contact-item--drawer svg {
  width: 18px;
  height: 18px;
  color: var(--color-drawer-link);
}

.nav-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 1040;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition);
}

.nav-overlay.is-open {
  opacity: 1;
  pointer-events: all;
}

/* =============================================
   MAIN CONTENT
   ============================================= */
.site-main {
  min-height: 60vh;
  /* Header is position:fixed (out of flow), so reserve its height here. This
     padding transitions on the same timing as the header height changes, so the
     two stay locked together on scroll and never reveal a gap. */
  padding-top: var(--nav-height);
  transition: padding-top var(--transition);
}

/* With the top bar on, reserve the bar's height too. */
.has-topbar .site-main {
  padding-top: calc(var(--topbar-height) + var(--nav-height));
}

/* Scrolled: top bar is collapsed and nav is shrunk — reserve only the shrunk
   nav. Declared after the rules above so it wins at equal specificity. */
body.nav-anim-shrink.is-scrolled .site-main {
  padding-top: var(--nav-height-shrunk);
}

/* The padding-top above stops the first block's top margin from collapsing,
   which would otherwise show as a white gap below the nav. Zero it out. */
.site-main > *:first-child {
  margin-top: 0 !important;
}

/* =============================================
   BUTTONS  —  reusable editorial button system
   ---------------------------------------------
   Applied as block styles (Editor > Button > Styles):
     - Primary         (is-style-primary)          solid, main button color — hero CTA
     - Secondary       (is-style-secondary)        solid, secondary button color
     - Outline         (is-style-outline)          ghost, for DARK backgrounds
     - Outline Inverse (is-style-outline-inverse)  ghost, for LIGHT backgrounds
   Primary & Secondary also get the high-impact hero treatment (larger,
   gradient fill, sliding arrow). Every color comes from a Global Styles
   preset — the four btn-primary-* / btn-secondary-* slots and the three
   btn-outline-* / btn-outline-inverse-* slots — so the whole system
   re-themes from the palette with no per-site hex values.
   ============================================= */

/* Base — the default (Fill) button, no style selected */
.wp-block-button__link {
  background-color: var(--color-btn) !important;
  border-radius: 50px !important;
}

.wp-block-button__link:hover {
  opacity: 0.88;
}

/* Editorial base — shared by Primary / Secondary / both Outlines */
.is-style-primary .wp-block-button__link,
.is-style-secondary .wp-block-button__link,
.is-style-outline .wp-block-button__link,
.is-style-outline-inverse .wp-block-button__link {
  position: relative !important;
  z-index: 0 !important;               /* stacking context so the wipe sits behind the text */
  overflow: hidden !important;         /* clip the wipe to the button box */
  padding: 1.1rem 2.7rem !important;
  font-size: 0.9rem !important;
  font-weight: 700 !important;
  letter-spacing: 0.16em !important;
  text-transform: uppercase !important;
  border-radius: 2px !important;       /* barely-rounded, editorial */
  border: 1.5px solid transparent !important;
  background-color: transparent !important;
  opacity: 1 !important;
  transition: color 0.4s ease, border-color 0.4s ease,
              box-shadow 0.4s ease !important;
}

/* Left-to-right color wipe, revealed on hover (sits behind the text) */
.is-style-primary .wp-block-button__link::before,
.is-style-secondary .wp-block-button__link::before,
.is-style-outline .wp-block-button__link::before,
.is-style-outline-inverse .wp-block-button__link::before {
  content: '' !important;
  position: absolute !important;
  inset: 0 !important;
  z-index: -1 !important;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.42s cubic-bezier(0.65, 0, 0.35, 1);
}

.is-style-primary .wp-block-button__link:hover::before,
.is-style-secondary .wp-block-button__link:hover::before,
.is-style-outline .wp-block-button__link:hover::before,
.is-style-outline-inverse .wp-block-button__link:hover::before {
  transform: scaleX(1);
}

.is-style-primary .wp-block-button__link:hover,
.is-style-secondary .wp-block-button__link:hover,
.is-style-outline .wp-block-button__link:hover,
.is-style-outline-inverse .wp-block-button__link:hover {
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18) !important;
  opacity: 1 !important;
}

/* Primary — solid button color; wipes to the hover color on hover. All four
   slots (bg / text / hover bg / hover text) are their own palette presets. */
.is-style-primary .wp-block-button__link {
  background-color: var(--color-btn-primary-bg) !important;
  color: var(--color-btn-primary-text) !important;
}
.is-style-primary .wp-block-button__link::before {
  background-color: var(--color-btn-primary-hover-bg) !important;
}
.is-style-primary .wp-block-button__link:hover {
  color: var(--color-btn-primary-hover-text) !important;
}

/* Secondary — solid button color; wipes to the hover color on hover. All four
   slots (bg / text / hover bg / hover text) are their own palette presets. */
.is-style-secondary .wp-block-button__link {
  background-color: var(--color-btn-secondary-bg) !important;
  color: var(--color-btn-secondary-text) !important;
}
.is-style-secondary .wp-block-button__link::before {
  background-color: var(--color-btn-secondary-hover-bg) !important;
}
.is-style-secondary .wp-block-button__link:hover {
  color: var(--color-btn-secondary-hover-text) !important;
}

/* Ghost buttons — transparent, thin-bordered, for a secondary action beside a
   solid CTA (a phone number next to "Book now"). Two styles, one implementation:

     Outline          (is-style-outline)          for DARK backgrounds — a photo,
                                                  a Cover, the dark closing panel
     Outline Inverse  (is-style-outline-inverse)  for LIGHT backgrounds — a cream
                                                  or white section

   They differ only in the three variables below; every rule after this reads the
   local --ghost-* names, so the pair can never drift out of sync.

   The label and the border are the SAME token by design — a ghost whose text and
   outline disagree looks like a mistake, so there's no way to set them apart. The
   border simply draws that color at --btn-outline-border-alpha (55% by default),
   which is what makes the edge read as a ghost rather than a hard box. */
.is-style-outline {
  --ghost-color:      var(--color-btn-outline-color);
  --ghost-hover-bg:   var(--color-btn-outline-hover-bg);
  --ghost-hover-text: var(--color-btn-outline-hover-text);
}
.is-style-outline-inverse {
  --ghost-color:      var(--color-btn-outline-inverse-color);
  --ghost-hover-bg:   var(--color-btn-outline-inverse-hover-bg);
  --ghost-hover-text: var(--color-btn-outline-inverse-hover-text);
}

.is-style-outline .wp-block-button__link,
.is-style-outline-inverse .wp-block-button__link {
  background-color: transparent !important;
  color: var(--ghost-color) !important;
  border-color: color-mix(in srgb, var(--ghost-color) var(--btn-outline-border-alpha), transparent) !important;
}
/* The wipe fills with the hover color... */
.is-style-outline .wp-block-button__link::before,
.is-style-outline-inverse .wp-block-button__link::before {
  background-color: var(--ghost-hover-bg) !important;
}
/* ...and the border goes solid to meet it, so the button reads as one filled
   shape on hover rather than a fill inside a paler frame. */
.is-style-outline .wp-block-button__link:hover,
.is-style-outline-inverse .wp-block-button__link:hover {
  color: var(--ghost-hover-text) !important;
  border-color: var(--ghost-hover-bg) !important;
}

/* HIGH-IMPACT footprint — Primary / Secondary / both Outlines share the same size
   so a solid + ghost pair lines up as a set. What sets them apart is the
   fill: Primary/Secondary get the gradient + resting shadow + sliding arrow
   below; the ghosts stay transparent, flat, and arrow-less. */
.is-style-primary .wp-block-button__link,
.is-style-secondary .wp-block-button__link,
.is-style-outline .wp-block-button__link,
.is-style-outline-inverse .wp-block-button__link {
  padding: 1.45rem 3.6rem !important;
  min-width: 260px !important;
  font-size: 1rem !important;
  letter-spacing: 0.2em !important;
  text-align: center !important;
}

/* Resting raised shadow — solid buttons on a photo only (a ghost shouldn't cast
   a shadow at rest, and neither should a button on a light panel: against the
   theme's pale surfaces the shadow has nothing to separate from and just reads
   as a grey smudge hugging the button's edge). Scoped to Cover blocks, where a
   solid button does need lifting off the image. Everywhere else the button sits
   flat at rest and still gets the hover shadow below. */
.wp-block-cover :is(.is-style-primary, .is-style-secondary) .wp-block-button__link {
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.16) !important;
}

/* Gradient fills give depth at rest (the wipe still covers on hover) */
.is-style-primary .wp-block-button__link {
  background: linear-gradient(150deg,
    color-mix(in srgb, var(--color-btn-primary-bg) 82%, #fff) 0%,
    color-mix(in srgb, var(--color-btn-primary-bg) 90%, #000) 100%) !important;
}
.is-style-secondary .wp-block-button__link {
  background: linear-gradient(150deg,
    color-mix(in srgb, var(--color-btn-secondary-bg) 97%, #fff) 0%,
    color-mix(in srgb, var(--color-btn-secondary-bg) 93%, #000) 100%) !important;
}

/* Sliding arrow — appended after the label, glides right on hover */
.is-style-primary .wp-block-button__link::after,
.is-style-secondary .wp-block-button__link::after {
  content: '\2192' !important;                 /* -> */
  display: inline-block !important;
  margin-left: 0.75em !important;
  transition: transform 0.4s cubic-bezier(0.65, 0, 0.35, 1) !important;
}
.is-style-primary .wp-block-button__link:hover::after,
.is-style-secondary .wp-block-button__link:hover::after {
  transform: translateX(7px) !important;
}

.is-style-primary .wp-block-button__link:hover,
.is-style-secondary .wp-block-button__link:hover {
  box-shadow: 0 14px 32px rgba(0, 0, 0, 0.24) !important;
}

/* arrow-btn — square button with an arrow tip that grows on hover.
   Apply "arrow-btn" to any Button block. Padding/size stay as set
   in the block editor. */

/* Shrink wrapper to hug the button so it doesn't stretch full-column-width */
.arrow-btn {
  align-self: flex-start !important;
  overflow: visible !important;
}

.arrow-btn .wp-block-button__link {
  position: relative;
  border-radius: 0 !important;
  width: auto !important;
  overflow: visible !important;
}

/* Arrow tail + tip grow on hover — button box never changes so text never moves */
.arrow-btn .wp-block-button__link::after {
  content: '';
  position: absolute;
  left: 100%;
  top: 0;
  height: 100%;
  width: 20px;
  background: inherit;
  /* 5-point triangle (shoulders collapsed to 0 so it's a pure tip at rest) */
  clip-path: polygon(0 0, 0 0, 100% 50%, 0 100%, 0 100%);
  pointer-events: none;
  transition: width 0.35s ease, clip-path 0.35s ease;
}

.arrow-btn .wp-block-button__link:hover::after {
  width: 60px;
  /* same 5 points — now with a rectangular tail before the 20px tip */
  clip-path: polygon(0 0, calc(100% - 20px) 0, 100% 50%, calc(100% - 20px) 100%, 0 100%);
}

/* =============================================
   LIFT BUTTONS  —  pill buttons that rise on hover
   ---------------------------------------------
   Applied as block styles (Editor ▸ Button ▸ Styles):
     - Lift          (is-style-lift)          solid pill
     - Lift Outline  (is-style-lift-outline)  ghost pill, for LIGHT backgrounds

   A softer, friendlier alternative to the editorial Primary/Secondary set: full
   pill radius, a resting drop shadow, and on hover the button translates UP into
   a deeper shadow — the whole thing reads as lifting off the page. No color wipe
   and no arrow; the motion is the entire effect, so the pair stays quiet enough
   to sit side by side (a solid Lift beside a Lift Outline) without competing.

   Colors reuse the existing palette slots rather than adding new ones:
     Lift          → the four btn-primary-* presets
     Lift Outline  → the three btn-outline-inverse-* presets (label + border share
                     one slot, same rule as the ghost buttons above)
   so both re-theme from Global Styles with no per-site hex values.

   The shadow is always BLACK by default, never a tint of the button color — a
   colored shadow reads as a glow/print-registration error rather than as depth.
   Override --lift-btn-shadow-color only when the design genuinely calls for it.

   Tunable (set on the block via Styles ▸ Additional CSS, or globally in :root):
     --lift-btn-radius         999px    pill by default; drop to e.g. 4px for a slab
     --lift-btn-padding        1.15rem 2.6rem
     --lift-btn-font-size      0.9rem
     --lift-btn-tracking       0.14em
     --lift-btn-border-width   1.5px    the outline variant's edge
     --lift-btn-rise           4px      how far it travels up on hover
     --lift-btn-shadow-color   #000
     --lift-btn-shadow-rest    0 4px 12px   offset/blur at rest
     --lift-btn-shadow-hover   0 12px 24px  offset/blur while lifted
     --lift-btn-shadow-alpha       18%  shadow strength at rest
     --lift-btn-shadow-alpha-hover 26%  shadow strength while lifted
     --lift-btn-speed          0.32s
   ============================================= */
.wp-block-button.is-style-lift,
.wp-block-button.is-style-lift-outline {
  --lift-btn-radius: 999px;
  --lift-btn-padding: 1.15rem 2.6rem;
  --lift-btn-font-size: 0.9rem;
  --lift-btn-tracking: 0.14em;
  --lift-btn-border-width: 1.5px;
  --lift-btn-rise: 4px;
  --lift-btn-shadow-color: #000;
  --lift-btn-shadow-rest: 0 4px 12px;
  --lift-btn-shadow-hover: 0 12px 24px;
  --lift-btn-shadow-alpha: 18%;
  --lift-btn-shadow-alpha-hover: 26%;
  --lift-btn-speed: 0.32s;
}

.wp-block-button.is-style-lift       > .wp-block-button__link,
.wp-block-button.is-style-lift-outline > .wp-block-button__link {
  padding: var(--lift-btn-padding) !important;
  border-radius: var(--lift-btn-radius) !important;
  border: var(--lift-btn-border-width) solid transparent !important;
  font-size: var(--lift-btn-font-size) !important;
  font-weight: 700 !important;
  letter-spacing: var(--lift-btn-tracking) !important;
  text-transform: uppercase !important;
  text-align: center !important;
  opacity: 1 !important;
  box-shadow: var(--lift-btn-shadow-rest)
              color-mix(in srgb, var(--lift-btn-shadow-color) var(--lift-btn-shadow-alpha), transparent) !important;
  transition: transform var(--lift-btn-speed) cubic-bezier(0.34, 1.2, 0.64, 1),
              box-shadow var(--lift-btn-speed) ease,
              background-color var(--lift-btn-speed) ease,
              border-color var(--lift-btn-speed) ease,
              color var(--lift-btn-speed) ease !important;
}

/* The lift itself — shared by hover and keyboard focus so the button behaves the
   same however it's reached. */
.wp-block-button.is-style-lift       > .wp-block-button__link:hover,
.wp-block-button.is-style-lift       > .wp-block-button__link:focus-visible,
.wp-block-button.is-style-lift-outline > .wp-block-button__link:hover,
.wp-block-button.is-style-lift-outline > .wp-block-button__link:focus-visible {
  transform: translateY(calc(-1 * var(--lift-btn-rise)));
  box-shadow: var(--lift-btn-shadow-hover)
              color-mix(in srgb, var(--lift-btn-shadow-color) var(--lift-btn-shadow-alpha-hover), transparent) !important;
  opacity: 1 !important;
}

/* Pressed — settles back toward the page, so a click reads as the button being
   pushed down rather than the hover simply switching off. */
.wp-block-button.is-style-lift       > .wp-block-button__link:active,
.wp-block-button.is-style-lift-outline > .wp-block-button__link:active {
  transform: translateY(calc(var(--lift-btn-rise) * -0.25));
  box-shadow: var(--lift-btn-shadow-rest)
              color-mix(in srgb, var(--lift-btn-shadow-color) var(--lift-btn-shadow-alpha), transparent) !important;
}

/* Lift — solid fill, recolors to the primary hover slots as it rises. */
.wp-block-button.is-style-lift > .wp-block-button__link {
  background-color: var(--color-btn-primary-bg) !important;
  color: var(--color-btn-primary-text) !important;
}
.wp-block-button.is-style-lift > .wp-block-button__link:hover,
.wp-block-button.is-style-lift > .wp-block-button__link:focus-visible {
  background-color: var(--color-btn-primary-hover-bg) !important;
  color: var(--color-btn-primary-hover-text) !important;
}

/* Lift Outline — transparent pill for light panels. Label and border are the same
   token (see the ghost buttons above); the border draws it at
   --btn-outline-border-alpha so the edge reads as a hairline, then goes solid and
   fills on hover. */
.wp-block-button.is-style-lift-outline > .wp-block-button__link {
  background-color: transparent !important;
  color: var(--color-btn-outline-inverse-color) !important;
  border-color: color-mix(in srgb, var(--color-btn-outline-inverse-color) var(--btn-outline-border-alpha), transparent) !important;
}
.wp-block-button.is-style-lift-outline > .wp-block-button__link:hover,
.wp-block-button.is-style-lift-outline > .wp-block-button__link:focus-visible {
  background-color: var(--color-btn-outline-inverse-hover-bg) !important;
  color: var(--color-btn-outline-inverse-hover-text) !important;
  border-color: var(--color-btn-outline-inverse-hover-bg) !important;
}

/* Motion off — the color change still communicates hover, the travel doesn't. */
@media (prefers-reduced-motion: reduce) {
  .wp-block-button.is-style-lift       > .wp-block-button__link,
  .wp-block-button.is-style-lift-outline > .wp-block-button__link {
    transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease !important;
  }
  .wp-block-button.is-style-lift       > .wp-block-button__link:hover,
  .wp-block-button.is-style-lift       > .wp-block-button__link:focus-visible,
  .wp-block-button.is-style-lift       > .wp-block-button__link:active,
  .wp-block-button.is-style-lift-outline > .wp-block-button__link:hover,
  .wp-block-button.is-style-lift-outline > .wp-block-button__link:focus-visible,
  .wp-block-button.is-style-lift-outline > .wp-block-button__link:active {
    transform: none;
  }
}

/* =============================================
   STAR BULLETS  —  list style (List ▸ Styles ▸ "Star Bullets")
   ---------------------------------------------
   Swaps the default disc for the theme's gold four-point sparkle. Apply to
   any List block; registered as .is-style-stars in functions.php. The star is
   a ::before (not ::marker) so we control its color/size and top-align it to
   the first line of wrapped items — same trick as .contact-item--drawer.
   ============================================= */
.wp-block-list.is-style-stars {
  list-style: none;
  padding-left: 0;
  margin-left: 0;
}

.wp-block-list.is-style-stars li {
  position: relative;
  padding-left: 1.9em;          /* room for the star + gap */
  margin-bottom: 0.6em;
}

.wp-block-list.is-style-stars li::before {
  content: '\2726';             /* ✦ black four-pointed star */
  position: absolute;
  left: 0;
  top: 0.1em;                   /* nudge onto the first text line */
  color: var(--color-accent-bold);
  font-size: 0.85em;
  line-height: 1.4;             /* aligns the glyph with body text */
}

/* Nested lists indent normally and inherit the same star. */
.wp-block-list.is-style-stars ul,
.wp-block-list.is-style-stars ol {
  list-style: none;
  margin-top: 0.5em;
  padding-left: 1.9em;
}

/* =============================================
   LINE LABELS  —  list style (List ▸ Styles ▸ "Line")   1.5
   ---------------------------------------------
   Turns each list item into a short horizontal rule followed by a small,
   letter-spaced gold label — the section-eyebrow look, repeated down a list
   ("— WELCOME" / "— OUR STORY" / …). Good for an index of sections, a set of
   tracked headings, or a quiet decorative menu. Apply to any List block;
   registered as .is-style-line in functions.php. The rule is a ::before drawn in
   currentColor, so it always matches the label and stays a single element,
   vertically centered against the cap height of the text.

   Scoped to DIRECT children (> li), so a nested list inside an item is left as an
   ordinary list rather than being pulled onto the flex row.

   Tune per block (Styles ▸ Additional CSS) or globally in :root:
     --line-list-color   label + rule color        default: --color-accent
     --line-list-len     length of the rule         default: 2.4rem
     --line-list-thick   thickness of the rule      default: 2px
     --line-list-gap     rule-to-label gap          default: 1rem
     --line-list-size    label size                 default: 0.95rem
     --line-list-track   letter-spacing             default: 0.22em
     --line-list-row     space between items        default: 1.1rem
   ============================================= */
.wp-block-list.is-style-line,
.wp-block-list.is-style-line-center {
  --line-list-color: var(--color-accent);
  --line-list-len:   2.4rem;
  --line-list-thick: 2px;
  --line-list-gap:   1rem;
  --line-list-size:  0.95rem;
  --line-list-track: 0.22em;
  --line-list-row:   1.1rem;

  list-style: none;
  margin: 0;
  padding-left: 0;
  display: flex;
  flex-direction: column;
  gap: var(--line-list-row);
}

.wp-block-list.is-style-line > li,
.wp-block-list.is-style-line-center > li {
  display: flex;
  align-items: center;
  gap: var(--line-list-gap);
  margin: 0;
  font-family: var(--font-body) !important;
  font-size: var(--line-list-size);
  font-weight: 600;
  letter-spacing: var(--line-list-track);
  text-transform: uppercase;
  line-height: 1.25;
  color: var(--line-list-color);
}

/* the rule(s) — currentColor, so they track the label */
.wp-block-list.is-style-line > li::before,
.wp-block-list.is-style-line-center > li::before,
.wp-block-list.is-style-line-center > li::after {
  content: "";
  flex: none;
  width: var(--line-list-len);
  height: var(--line-list-thick);
  background: currentColor;
}

/* ---- Centered variant (List ▸ Styles ▸ "Line Both Sides")   1.5 ----
   The same label, with a matching rule closing it on the right and the whole
   row centered — the standalone section eyebrow ( — CONTACT US — ) rather than
   a left-aligned index. Everything else is inherited from "Line" above: same
   --line-list-* tokens, same ::before, same currentColor rules. */
.wp-block-list.is-style-line-center > li {
  justify-content: center;
  text-align: center;
}


/* =============================================
   NUMBERED CIRCLES  —  list style (List ▸ Styles ▸ "Numbered Circles")   1.6
   ---------------------------------------------
   Swaps the default bullets/markers for a small filled circle with the item's
   number inside (1, 2, 3 …), then the text — the quiet "step" look used in the
   buying / selling cards. The number is a CSS counter, not the list marker, so
   it renders our own circle on either a List or an ordered List block and stays
   consistent regardless of the block's list type.

   Scoped to DIRECT children (> li), so a nested list inside an item keeps its
   own ordinary markers rather than picking up the circles.

   Tune per block (Styles ▸ Additional CSS) or globally in :root:
     --num-list-size    circle diameter             default: 1.8em
     --num-list-color   number color                default: --color-accent-bold
     --num-list-bg      circle fill                 default: 12% accent tint
     --num-list-weight  number weight               default: 600
     --num-list-gap     circle-to-text gap          default: 0.9em
     --num-list-row     space between items         default: 0.9em
   ============================================= */
.wp-block-list.is-style-circle-numbers {
  --num-list-size:   1.8em;
  --num-list-color:  var(--color-accent-bold);
  --num-list-bg:     color-mix(in srgb, var(--color-accent-bold) 12%, transparent);
  --num-list-weight: 600;
  --num-list-gap:    0.9em;
  --num-list-row:    0.9em;

  list-style: none;
  margin: 0;
  padding-left: 0;
  counter-reset: onyx-num;
  display: flex;
  flex-direction: column;
  gap: var(--num-list-row);
}

.wp-block-list.is-style-circle-numbers > li {
  counter-increment: onyx-num;
  position: relative;
  margin: 0;
  padding-left: calc(var(--num-list-size) + var(--num-list-gap));
  min-height: var(--num-list-size);
}

.wp-block-list.is-style-circle-numbers > li::before {
  content: counter(onyx-num);
  position: absolute;
  left: 0;
  top: 0;                       /* centered on the first line for the usual
                                   1.4–1.6 body line-height; wrapped text flows
                                   under the text, not the circle */
  width: var(--num-list-size);
  height: var(--num-list-size);
  border-radius: 50%;
  background: var(--num-list-bg);
  color: var(--num-list-color);
  font-family: var(--font-body);
  font-size: 0.8em;
  font-weight: var(--num-list-weight);
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Nested lists indent normally and keep their own markers. */
.wp-block-list.is-style-circle-numbers ul,
.wp-block-list.is-style-circle-numbers ol {
  margin-top: 0.5em;
}

/* ---- Modifiers (add to the block's Additional CSS class(es)) ----
   The default number color is the gold accent, which disappears on a gold or
   light card. Type one of these next to the block to override just that list:
     num-list--ink       dark ink numbers + faint ink circle (light/gold cards)
     num-list--light     white numbers + faint white circle (dark cards)
   Both only reset the --num-list-* color tokens, so every size/spacing stays. */
.wp-block-list.is-style-circle-numbers.num-list--ink {
  --num-list-color: var(--color-text);
  --num-list-bg:    color-mix(in srgb, var(--color-text) 12%, transparent);
}

.wp-block-list.is-style-circle-numbers.num-list--light {
  --num-list-color: var(--color-text-inverse);
  --num-list-bg:    color-mix(in srgb, var(--color-text-inverse) 18%, transparent);
}


/* =============================================
   PHOTO BADGE  —  image with a floating corner tag
   ---------------------------------------------
   A photo with a small badge pinned to a corner: a price, a label,
   a "New" flag, etc. Reusable for listings, cards and galleries;
   colors come from theme presets. Editor classes:

     photo-badge      -> the Column/Group holding the photo (relative)
       Image .......... the photo (fills the frame)
       Group .......... photo-badge-tag   (the floating badge)
         Paragraph .... top line (large)   e.g. a price "$1,100 / week"
         Paragraph .... sub line (small caps)

   The tag styles its two lines by order (first = large, last = small
   caps), so keep them as two paragraphs in that order.
   ============================================= */

.photo-badge {
  position: relative !important;
  display: flex !important;
  flex-direction: column !important;
  padding: 0 !important;
  min-height: 460px !important;
}

.photo-badge .wp-block-image,
.photo-badge figure {
  flex: 1 !important;
  height: 100% !important;
  margin: 0 !important;
}

.photo-badge img {
  width: 100% !important;
  height: 100% !important;
  object-fit: cover !important;
  display: block !important;
}

/* Caption badge — hug the photo, don't hold a min-height floor.
   The base badge keeps a min-height so a cropped, cover-filled photo reads as a
   tall slab behind a price. The --caption mode is the opposite job: it names a
   photo shown at its natural height. Without relief the box would stay at the
   floor (min-height 320 on a narrow phone), cover-crop the photo to fill it, and
   the absolute tag would pin to the box bottom rather than the photo's edge.
   Drop the floor in caption mode so the box hugs the whole photo, uncropped,
   with the tag sitting on its bottom edge. */
.photo-badge:has(.photo-badge-tag--caption) {
  min-height: 0 !important;
}

/* The floating tag — pinned to the photo's lower-left corner */
.photo-badge-tag {
  position: absolute !important;
  left: 1.25rem !important;
  bottom: 1.25rem !important;
  max-width: 75% !important;
  background: var(--color-text) ;
  color: var(--color-text-inverse);
  padding: 1.4rem 2rem !important;
}

/* Top line (large) — e.g. a price */
.photo-badge-tag p:first-child {
  font-family: var(--font-heading);
  font-size: 2rem ;
  font-weight: 400 !important;
  line-height: 1 !important;
  margin: 0 !important;
}

/* Sub line (small caps) */
.photo-badge-tag p:last-child {
  margin: 0.55rem 0 0 !important;
  font-family: var(--font-body) ;
  font-size: 0.72rem !important;
  font-weight: 700 !important;
  letter-spacing: 0.12em !important;
  text-transform: uppercase !important;
  color: var(--color-muted);
}

/* Scale the tag down on phones so it doesn't crowd a smaller photo */
@media (max-width: 781px) {
  .photo-badge {
    min-height: 320px !important;
  }

  .photo-badge-tag {
    left: 0.75rem !important;
    bottom: 0.75rem !important;
    max-width: calc(100% - 1.5rem) !important;
    padding: 1rem 1.25rem !important;
  }

  .photo-badge-tag p:first-child {
    font-size: 1.5rem !important;
  }

  .photo-badge-tag p:last-child {
    font-size: 0.68rem !important;
    letter-spacing: 0.1em !important;
  }
}

/* ---------------------------------------------
   Modifier: photo-badge-tag--caption
   ---------------------------------------------
   The quiet version of the tag. Where the base tag is a solid slab
   built to shout a price, this is a slim glass bar built to *name*
   the photo — a cocktail, a dish, a room. One line of small, wide
   uppercase type on a translucent dark panel with a hairline rule
   and a soft blur pulling the photo through.

   Written for a single paragraph. A second paragraph still works and
   drops to a softer, tighter sub-line.

   Pair with photo-badge-tag--center to sit it along the bottom edge
   instead of the lower-left corner.

     Group .photo-badge-tag .photo-badge-tag--caption
       Paragraph .... the label   e.g. "Empress 1908 · Violet · Lemonade"
   --------------------------------------------- */
.photo-badge-tag.photo-badge-tag--caption {
  max-width: calc(100% - 2.5rem) !important;
  padding: 0.9rem 1.75rem !important;
  background: rgba(20, 20, 20, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.28);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  text-align: center !important;
}

.photo-badge-tag.photo-badge-tag--caption p,
.photo-badge-tag.photo-badge-tag--caption p:first-child,
.photo-badge-tag.photo-badge-tag--caption p:last-child {
  margin: 0 !important;
  font-family: var(--font-body) !important;
  font-size: 0.78rem !important;
  font-weight: 700 !important;
  line-height: 1.4 !important;
  letter-spacing: 0.18em !important;
  text-transform: uppercase !important;
  color: var(--color-accent) !important;
}

/* An optional second line sits under the first, quieter and tighter */
.photo-badge-tag.photo-badge-tag--caption p + p {
  margin-top: 0.4rem !important;
  font-size: 0.68rem !important;
  font-weight: 400 !important;
  letter-spacing: 0.12em !important;
  color: var(--color-text-inverse) !important;
  opacity: 0.72 !important;
}

/* ---------------------------------------------
   Modifier: photo-badge-tag--center
   ---------------------------------------------
   Centers the tag along the photo's bottom edge instead of pinning it
   to the lower-left corner. Works on the base tag or on --caption.
   --------------------------------------------- */
.photo-badge-tag.photo-badge-tag--center {
  left: 50% !important;
  right: auto !important;
  transform: translateX(-50%) !important;
}

@media (max-width: 781px) {
  .photo-badge-tag.photo-badge-tag--caption {
    padding: 0.7rem 1.1rem !important;
    max-width: calc(100% - 1.5rem) !important;
  }

  .photo-badge-tag.photo-badge-tag--caption p,
  .photo-badge-tag.photo-badge-tag--caption p:first-child,
  .photo-badge-tag.photo-badge-tag--caption p:last-child {
    font-size: 0.68rem !important;
    letter-spacing: 0.14em !important;
  }

  .photo-badge-tag.photo-badge-tag--caption p + p {
    font-size: 0.62rem !important;
  }

  .photo-badge-tag.photo-badge-tag--center {
    left: 50% !important;
  }
}


/* ---- Spec table (label | value rows with divider lines) ----
   Retint per-site with the --spec-table-* custom properties. */
.spec-table {
  --spec-table-rule:  color-mix(in srgb, var(--color-primary) 16%, transparent);
  --spec-table-label: var(--color-muted);
  --spec-table-value: var(--color-accent);
  --spec-table-hover: color-mix(in srgb, var(--color-accent) 7%, transparent);
  width: 100% !important;
}

.spec-table table {
  width: 100% !important;
  border-collapse: collapse !important;
}

.spec-table td {
  padding: 0.9rem 0.75rem !important;
  border: 0 !important;
  border-top: 1px solid var(--spec-table-rule) !important;
  vertical-align: baseline !important;
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.45;
}

.spec-table tr:first-child td {
  border-top: none !important;
}

.spec-table tr {
  transition: background-color .25s ease;
}

.spec-table tr:hover {
  background-color: var(--spec-table-hover) !important;
}

.spec-table td:first-child {
  padding-left: 0 !important;
  width: 45%;
  font-size: .8rem;
  font-weight: 600 !important;
  letter-spacing: .16em !important;
  text-transform: uppercase !important;
  color: var(--spec-table-label) !important;
}

.spec-table td:last-child {
  padding-right: 0 !important;
  text-align: right !important;
  font-weight: 600 !important;
  color: var(--spec-table-value) !important;
}

/* ---------------------------------------------
   Modifier: spec-table--inverse
   ---------------------------------------------
   For dark backgrounds. Only re-points the --spec-table-* tokens,
   so every layout rule above (and the mobile stack) still applies.
   --------------------------------------------- */
.spec-table.spec-table--inverse {
  --spec-table-rule:  color-mix(in srgb, var(--color-text-inverse) 22%, transparent);
  --spec-table-label: color-mix(in srgb, var(--color-text-inverse) 65%, transparent);
  --spec-table-value: var(--color-accent);
  --spec-table-hover: color-mix(in srgb, var(--color-text-inverse) 6%, transparent);
}

@media (max-width: 600px) {
  .spec-table td {
    display: block !important;
    padding: 0 !important;
    border-top: none !important;
    text-align: left !important;
  }

  .spec-table tr:hover {
    background-color: transparent !important;
  }

  .spec-table td:first-child {
    width: auto;
    padding: 0.9rem 0 0.2rem !important;
    border-top: 1px solid var(--spec-table-rule) !important;
  }

  .spec-table tr:first-child td:first-child {
    border-top: none !important;
    padding-top: 0 !important;
  }

  .spec-table td:last-child {
    padding-bottom: 0.9rem !important;
    text-align: left !important;
    font-size: 1.05rem;
  }
}

/* =============================================
   MEDIA CARD  +  ARROW LINK   (reusable)
   ---------------------------------------------
   MEDIA CARD — a content card: a photo on top (a Cover), an optional
   pill badge, then a body with heading, text and a link pinned to the
   bottom. Drop the cards into a Columns block. Editor classes:

     .media-card-grid   Columns block (equal-height cards)
       .media-card        each Column's Group (the card)
         .media-card-photo    Cover (photo; overlay 0, content top-left)
           .media-card-badge    Paragraph  e.g. "25 min away"
         .media-card-body     Group
           Heading h3/h4        (auto-styled serif)
           Paragraph            (description)
           Button (Arrow Link)  (a Button block with the "Arrow Link" style;
                                  pins to the card bottom — see below)

   ARROW LINK — a gold, uppercase CTA styled as a core Button block style
   (Button ▸ Styles ▸ "Arrow Link" or "Arrow Link Underline"). It is NOT a
   standalone class you attach to a Paragraph — pick it on a Button block. Both
   default to the arrow "swipe"; add arrow-link-sweep / arrow-link-trade in the
   block's Advanced ▸ Additional CSS class(es) for an animated underline.
   Registered in functions.php; colors come from theme presets. Typography
   (font family, size, weight) is left to the block's own Typography panel —
   only the uppercase + letter-spacing, the gold color and the arrow are set
   here. Retint per-site with --arrow-link-color / --arrow-link-hover.
   ============================================= */

/* The redraw keyframe used by the .arrow-link-trade animation below. */
@keyframes arrow-link-redraw {
  from { transform-origin: left center; transform: scaleX(0); }
  to   { transform-origin: left center; transform: scaleX(1); }
}

/* The scroll-through used by .arrow-link-sweep. The line stays solid the whole
   time — it scrolls off the right edge and is CLIPPED by the link's overflow (the
   "hidden wall"), then a fresh one scrolls in from the left and settles. It travels
   its own width plus a margin so it fully clears before the clip, so nothing floats
   past the button. The mid jump (right→left) happens off-screen, behind the wall, so
   it's never seen. Per-keyframe easing: accelerate out, decelerate back into place. */
@keyframes arrow-link-sweep {
  0%      { transform: translateX(0);                animation-timing-function: cubic-bezier(.55, 0, .9, .35); }
  46%     { transform: translateX(calc(100% + 3em)); }                       /* off the right, clipped */
  46.001% { transform: translateX(calc(-100% - 3em)); animation-timing-function: cubic-bezier(.1, .65, .35, 1); }  /* now off the left */
  100%    { transform: translateX(0); }                                     /* scroll in and settle */
}

/* ---- arrow-link as a Button block style ----
   Registered in functions.php as two Button ▸ Styles: "Arrow Link" (no underline)
   and "Arrow Link Underline" (a hairline under the label). A block style drops its
   is-style-* class on the .wp-block-button WRAPPER, and the inner link arrives
   wearing core's button chrome — so we strip that chrome and re-point the link at
   the arrow-link look. Both styles animate the same way by default: the arrow just
   slides a little on hover ("swipe").

   Fancier hover animations are OPT-IN, added by hand in the block's Advanced ▸
   Additional CSS class(es):
     arrow-link-sweep   the underline glides off to the right, disappears, then a
                        fresh one enters from the left and settles back in place
     arrow-link-trade   the underline retracts right, then redraws from the left
   Each brings its own underline, so it works on either style — and animates the
   static underline of "Arrow Link Underline" on hover.

   Typography is deliberately NOT set here beyond the uppercase + letter-spacing:
   font family, size and weight come from the block's own Typography panel (and
   Global Styles), so each site controls the type from the editor. */
.wp-block-button.is-style-arrow-link           > .wp-block-button__link,
.wp-block-button.is-style-arrow-link-underline > .wp-block-button__link {
  --arrow-link-color: var(--color-accent-bold);
  --arrow-link-hover: var(--color-accent-deep);
  /* strip core's button chrome */
  background: none !important;
  border: 0 !important;
  box-shadow: none !important;
  min-height: 0 !important;
  width: auto !important;
  /* arrow-link look — layout, color and CTA casing only; typeface/size/weight
     come from the block's Typography panel */
  position: relative !important;
  display: inline-flex !important;
  align-items: center !important;
  gap: 0.5rem !important;
  padding: 0 !important;
  border-radius: 0 !important;
  color: var(--arrow-link-color) !important;
  letter-spacing: 0.12em !important;
  text-transform: uppercase !important;
  text-decoration: none !important;
  transition: color var(--transition) !important;
}

.wp-block-button.is-style-arrow-link           > .wp-block-button__link:hover,
.wp-block-button.is-style-arrow-link-underline > .wp-block-button__link:hover {
  color: var(--arrow-link-hover) !important;
}

/* the arrow — trails the label and slides a little on hover (the default "swipe") */
.wp-block-button.is-style-arrow-link           > .wp-block-button__link::after,
.wp-block-button.is-style-arrow-link-underline > .wp-block-button__link::after {
  content: '\2192' !important;                 /* -> */
  color: currentColor !important;
  font-size: 1.15em !important;                /* em, so it tracks the block's font size */
  line-height: 1 !important;
  transition: transform var(--transition) !important;
}
.wp-block-button.is-style-arrow-link           > .wp-block-button__link:hover::after,
.wp-block-button.is-style-arrow-link-underline > .wp-block-button__link:hover::after {
  transform: translateX(4px) !important;
}

/* ---- the underline ----
   Sits beneath the LABEL only: the right offset clears the flex gap + the arrow
   glyph, so the line stops where the text does (matching the reference). Shared by
   the static "Underline" style and the two opt-in animation classes so they line up
   identically. */
.wp-block-button.is-style-arrow-link-underline > .wp-block-button__link,
.wp-block-button[class*="is-style-arrow-link"].arrow-link-sweep > .wp-block-button__link,
.wp-block-button[class*="is-style-arrow-link"].arrow-link-trade > .wp-block-button__link {
  padding-bottom: 0.4rem !important;
}
.wp-block-button.is-style-arrow-link-underline > .wp-block-button__link::before,
.wp-block-button[class*="is-style-arrow-link"].arrow-link-sweep > .wp-block-button__link::before,
.wp-block-button[class*="is-style-arrow-link"].arrow-link-trade > .wp-block-button__link::before {
  content: '' !important;
  position: absolute !important;
  left: 0 !important;
  right: calc(1.2em + 0.5rem) !important;       /* clear the arrow + gap → label only */
  bottom: 0 !important;
  height: 1.5px !important;
  background: currentColor !important;
  transform-origin: left center !important;
}

/* opt-in: sweep — the underline is in place at rest; on hover it scrolls off the
   right edge, is clipped behind the button's edge (overflow — the "hidden wall"),
   then a fresh one scrolls in from the left and settles. overflow:hidden is what
   masks the line at both edges; the arrow holds still on sweep so the clipped edge
   never crops its slide. */
.wp-block-button[class*="is-style-arrow-link"].arrow-link-sweep > .wp-block-button__link {
  overflow: hidden !important;
}
.wp-block-button[class*="is-style-arrow-link"].arrow-link-sweep > .wp-block-button__link:hover::after {
  transform: none !important;
}
.wp-block-button[class*="is-style-arrow-link"].arrow-link-sweep > .wp-block-button__link:hover::before {
  animation: arrow-link-sweep 0.7s linear !important;
}

/* opt-in: trade — underline retracts right, then a fresh one redraws from the left */
.wp-block-button[class*="is-style-arrow-link"].arrow-link-trade > .wp-block-button__link::before {
  transition: transform 0.28s ease !important;
}
.wp-block-button[class*="is-style-arrow-link"].arrow-link-trade > .wp-block-button__link:hover::before {
  transform-origin: right center !important;
  transform: scaleX(0) !important;
  animation: arrow-link-redraw 0.5s 0.28s forwards !important;
}

@media (prefers-reduced-motion: reduce) {
  .wp-block-button[class*="is-style-arrow-link"] > .wp-block-button__link::after,
  .wp-block-button[class*="is-style-arrow-link"] > .wp-block-button__link::before {
    transition: none !important;
    animation: none !important;
  }
  /* show the opt-in underlines statically instead of frozen mid-sweep */
  .wp-block-button[class*="is-style-arrow-link"].arrow-link-sweep > .wp-block-button__link::before,
  .wp-block-button[class*="is-style-arrow-link"].arrow-link-trade > .wp-block-button__link:hover::before {
    transform: scaleX(1) !important;
  }
}

/* ---- card grid ---- */
.media-card-grid {
  max-width: var(--max-width) !important;
  margin: 0 auto !important;
  gap: 1.75rem !important;
  align-items: stretch !important;
}

.media-card {
  background: var(--color-bg) !important;
  border-radius: 5px !important;
  overflow: hidden !important;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.07) !important;
  display: flex !important;
  flex-direction: column !important;
  height: 100% !important;
}

/* Force the card's children (photo + body) edge-to-edge, regardless of the
   Group's "inner blocks use content width" setting */
.media-card > * {
  width: 100% !important;
  max-width: 100% !important;
  margin: 0 !important;
}

/* photo area (a Cover block); set overlay 0 + content position top-left */
.media-card-photo {
  min-height: 250px !important;
  width: 100% !important;
}

.media-card-photo img {
  width: 100% !important;
  object-fit: cover !important;
}

.media-card-badge {
  display: inline-block !important;
  background: var(--color-primary) !important;
  color: var(--color-text-inverse) !important;
  font-family: var(--font-body) !important;
  font-size: 0.68rem !important;
  font-weight: 700 !important;
  letter-spacing: 0.1em !important;
  text-transform: uppercase !important;
  padding: 0.45rem 1rem !important;
  border-radius: 50px !important;
  margin: 0 !important;
}

/* card body — heading, text, then the link pinned to the bottom */
.media-card-body {
  padding: 1.75rem !important;
  display: flex !important;
  flex-direction: column !important;
  gap: 0.85rem !important;
  flex: 1 !important;
  background-color: var(--color-surface);
}

.media-card-body h3,
.media-card-body h4 {
  font-family: var(--font-heading) !important;
  font-size: 1.45rem !important;
  font-weight: 400 !important;
  line-height: 1.2 !important;
  color: var(--color-primary) !important;
  margin: 0 !important;
}

.media-card-body p {
  font-family: var(--font-body) !important;
  font-size: 1rem !important;
  line-height: 1.6 !important;
  color: var(--color-muted) !important;
  margin: 0 !important;
}

/* Pin the card's CTA to the bottom. The CTA is now a Button block carrying the
   "Arrow Link" style (a lone Button sits inside a core Buttons wrapper), so pin
   the Buttons wrapper — or a bare Button, whichever is the card-body child. */
.media-card-body .wp-block-buttons,
.media-card-body > .wp-block-button {
  margin-top: auto !important;
  padding-top: 0.5rem !important;
}


/* =============================================
   BLOCK EDITOR — ALIGNMENT BREAKOUT
   ============================================= */
.alignfull,
.wp-block-cover.alignfull {
  width: 100vw !important;
  max-width: 100vw !important;
  margin-left: calc(50% - 50vw) !important;
  margin-right: calc(50% - 50vw) !important;
}


/* =============================================
   FOOTER
   ============================================= */
.site-footer {
  background: var(--color-footer-bg);
  color: var(--color-footer-text);
}

.footer-inner {
  display: grid;
  grid-template-columns: 1.3fr 1fr 1fr;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 4.5rem 2rem 3rem;
}

.footer-col {
  padding: 0 2.5rem;
}

.footer-col:first-child {
  padding-left: 0;
}

.footer-col:last-child {
  padding-right: 0;
}

/* Faint vertical dividers between columns (Wine Diplomacy-style) */
.footer-col + .footer-col {
  border-left: 1px solid rgba(255, 255, 255, 0.12);
}

.footer-heading {
  font-family: var(--font-body);
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-text-inverse);
  margin: 0 0 1.25rem;
}

.footer-heading--hours {
  margin-top: 2rem;
}

/* About column */
.footer-logo img {
  max-height: 84px;
  width: auto;
}

/* Footer logo recolor — mirrors the nav logo (see .nav-logo--image /
   .nav-logo--natural). The logo file is used as a mask on its own link and
   filled with --logo-color, so any solid color works; the --natural class turns
   the mask off so the logo renders in its original colors. --logo-mask (the
   logo URL) and --logo-color are injected inline by onyx_render_footer, reading
   the same Customizer > Logo settings as the header. Defaults to white to match
   the previous brightness(0) invert(1) treatment. */
.footer-logo--image .custom-logo-link {
  display: inline-block;
  background-color: var(--logo-color, #ffffff);
  -webkit-mask: var(--logo-mask) center / contain no-repeat;
          mask: var(--logo-mask) center / contain no-repeat;
}

.footer-logo--image .custom-logo-link img {
  visibility: hidden;
}

.footer-logo--natural .custom-logo-link {
  background-color: transparent;
  -webkit-mask: none;
          mask: none;
}

.footer-logo--natural .custom-logo-link img {
  visibility: visible;
}

.footer-sitename {
  font-family: var(--font-logo);
  font-size: 1.5rem;
  letter-spacing: 0.08em;
  color: var(--color-text-inverse);
}

.footer-about-text {
  margin-top: 1.25rem;
  max-width: 34ch;
  font-size: 0.95rem;
  line-height: 1.8;
  color: var(--color-footer-text);
}

/* Contact + hours column */
.footer-contact-items {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.contact-item--footer {
  align-items: flex-start;
  gap: 0.6rem;
  font-size: 0.95rem;
  color: var(--color-footer-text);
  opacity: 1;
}

.contact-item--footer svg {
  width: 17px;
  height: 17px;
  margin-top: 2px;
  color: var(--color-accent);
}

a.contact-item--footer:hover {
  color: var(--color-accent);
}

.footer-hours {
  font-size: 0.95rem;
  line-height: 1.9;
  color: rgba(239, 231, 210, 0.7);
}

/* Social links (icon chips) */
.social-links {
  display: flex;
  gap: 0.75rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.social-links a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 6px;
  color: var(--color-text-inverse);
  transition: color var(--transition), background var(--transition),
              border-color var(--transition), transform var(--transition);
}

.social-links a:hover {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-primary);
  transform: translateY(-2px);
}

.social-links svg {
  width: 19px;
  height: 19px;
}

/* Bottom bar with gold accent rule */
.footer-bar {
  background: var(--color-footer-bg);
}

.footer-bar-inner {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 1.5rem 2rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  font-size: 0.85rem;
  color: rgba(239, 231, 210, 0.55);
  border-top: 2px solid var(--color-accent-bold);
}

/* Copyright line — was inheriting the faded cream (.footer-bar-inner color),
   which all but disappears on a light footer. Use the accent so it reads on any
   footer background. Swap to var(--color-footer-text) if you prefer the neutral
   text tone over the gold. */
.footer-copy {
  color: var(--color-accent);
}

.footer-bar-left,
.footer-bar-right {
  display: flex;
  align-items: center;
  gap: 1.25rem;
}

/* Privacy Policy link — set the page under Settings > Privacy in wp-admin.
   Uses the neutral footer text tone with an accent hover to match the bar. */
.footer-privacy {
  color: var(--color-footer-text);
  text-decoration: none;
  font-size: 0.9rem;
  transition: color 0.2s ease;
}

.footer-privacy:hover,
.footer-privacy:focus {
  color: var(--color-accent);
  text-decoration: underline;
}

/* "Site by Ixiun" credit lockup */
.footer-ixiun {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  text-decoration: none;
  line-height: 1;
  transition: opacity 0.2s ease;
}

.footer-ixiun--light { color: #ffffff; }
.footer-ixiun--dark  { color: #1a1a1a; }

.footer-ixiun-label {
  font-family: var(--font-body);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: currentColor;
  opacity: 0.55;
}

.footer-ixiun-logo {
  height: 50px;
  width: auto;
  display: block;
}

.footer-ixiun:hover { opacity: 0.75; }

/* Optional full-width image across the very bottom (e.g. a treeline) */
.footer-image {
  line-height: 0;
}

.footer-image img {
  display: block;
  width: 100%;
  height: auto;
}

/* Remove Media & Text gap so its 50% edge lines up with the Columns block */
.wp-block-media-text.alignfull {
  column-gap: 0 !important;
  gap: 0 !important;
}

.section-eyebrow {
  display: block;
  font-family: var(--font-body) !important;
  font-size: 1.12rem;
  font-weight: 600 !important;
  letter-spacing: 0.22em !important;
  text-transform: uppercase;
  color: var(--color-accent);
  margin-bottom: 0.85rem;
}

.section-eyebrow--primary {
  color: var(--color-primary);
}

.section-eyebrow--secondary {
  color: var(--color-secondary);
}

.section-eyebrow--muted {
  color: var(--color-muted);
}



/* =============================================
   FEATURE CIRCLES  +  PHOTO STACK   (reusable)
   ---------------------------------------------
   FEATURE CIRCLES — a row of round photo thumbnails, each with a short
   label, split by thin dividers. Good for "why stay here" highlights.
     feature-circles   Row block (the container)
       feature-circle    Group per item (Image FIRST, then a label Paragraph)
         feature-icon      the Image (forced to a round thumbnail — 160px)
   Items divide evenly; they stack vertically under 600px.

   PHOTO STACK — two photos overlapping on a stagger, for an editorial feel.
     photo-stack       Group/Row (the container)
       stack-photo stack-photo--front   Image (the top card)
       stack-photo stack-photo--back    Image (tucked lower, overlapping)
   Add a caption on an image for the gold figcaption tag; stacks vertically
   on phones. Colors/fonts come from theme presets.
   ============================================= */

/* Feature highlights — a Row of .feature-circle items, each a small
   rounded photo thumbnail with a short label beside it. */


.feature-circle {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  padding: 0 1.5rem;
  font-family: var(--font-body);
  font-size: 0.9rem;
  font-weight: 600;
  line-height: 1.3;
  text-align: center;
}

/* divider line between feature items */
.feature-circle + .feature-circle {
  border-left: 1px solid var(--color-muted);
}

/* the label sits under the thumb and is free to wrap normally */
.feature-circle p {
  margin: 0;
  min-width: 0;
}

/* The thumbnail is an Image block. Size is forced here so it stays a tidy
   thumbnail regardless of the editor's image size — change 160px to scale. */
.feature-icon,
.feature-icon figure {
  flex: none;
  margin: 0;
  line-height: 0;
}

.feature-icon img {
  display: block;
  width: 160px !important;
  height: 160px !important;
  object-fit: cover;
  border-radius: 100px;
}

/* ---- overlapping photo cards ----
   Cards are sized in % and the stagger uses clamp(), so the pair scales
   down cleanly on narrow screens. */
.photo-stack {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  gap: 0;
}

.stack-photo {
  position: relative;
  width: 100%;
  margin: 0;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  box-shadow: 0 25px 50px -22px rgba(26, 27, 26, 0.5);
}

.stack-photo--front {
  z-index: 2;
}

/* The second card tucks lower and slides left to overlap the first. */
.stack-photo--back {
  margin-left: -20%;
  margin-top: clamp(4rem, 11vw, 9rem);
  z-index: 1;
}

.stack-photo img,
.stack-photo-placeholder {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
}


/* photo-stack: cap the pair's width once its column stacks */
@media (max-width: 900px) {
  .photo-stack {
    max-width: 480px;
    margin: 0 auto;
  }
}

/* Phone: stack feature items vertically (dividers go horizontal), and drop
   the photo overlap (stack the two cards full-width). */
@media (max-width: 600px) {
  .feature-circles {
    flex-direction: column;
  }

  .feature-circle {
    padding: 0;
  }

  .feature-circle + .feature-circle {
    margin-top: 1.5rem;
    padding-top: 1.5rem;
    border-left: 0;
    border-top: 1px solid rgba(26, 27, 26, 0.18);
  }

  .photo-stack {
    flex-direction: column;
    align-items: center;
    max-width: 360px;
  }

  .stack-photo {
    width: 100%;
  }

  .stack-photo--back {
    margin-left: 0;
    margin-top: 1.25rem;
  }
}

/* =============================================
   STAT GRID   (reusable)
   A stats / facts panel. Locked to 2 columns (any number of rows);
   the divider color comes from the --stat-divider token.
   Apply class "stat-grid" to the Column that holds the 2×3 stat panel.
   Expects three Grid blocks inside, each with two Cover or Group blocks.

   Divider lines are drawn as real borders on the rows and cells (NOT as
   fixed-position pseudo-elements), so they always follow the actual cell
   heights — rows can be uneven (a label that wraps to two lines) and the
   lines still land exactly on the boundaries at every screen width.
   ============================================= */
.stat-grid {
  border: 3px solid var(--stat-divider);
  overflow: hidden;
  align-self: stretch;
}

/* Collapse the theme block-gap (24px) between the stacked rows so they sit
   flush — otherwise the gap splits the vertical center divider into three
   disconnected segments and detaches the horizontal lines. */
.stat-grid > * + * {
  margin-block-start: 0 !important;
}

/* Each row of the grid — two equal columns, no gap (borders draw the lines).
   align-items:stretch makes both cells match the taller one, so the vertical
   divider spans the full row height. */
.stat-grid .wp-block-group.is-layout-grid,
.stat-grid [class*="is-layout-grid"] {
  gap: 0 !important;
  grid-template-columns: 1fr 1fr !important;
  align-items: stretch !important;
}

/* Horizontal divider — bottom border on every row except the last */
.stat-grid [class*="is-layout-grid"]:not(:last-child) {
  border-bottom: 3px solid var(--stat-divider);
}

/* Vertical divider — right border on the left cell of each row */
.stat-grid [class*="is-layout-grid"] > *:first-child {
  border-right: 3px solid var(--stat-divider);
}

/* Strip Cover block backgrounds — cells sit on the section background.
   Flex so the content vertically centers when a sibling cell is taller. */
.stat-grid .wp-block-cover {
  background: transparent !important;
  background-image: none !important;
  min-height: 0 !important;
  padding: 0 !important;
  display: flex !important;
}

.stat-grid .wp-block-cover__background,
.stat-grid .wp-block-cover::after {
  display: none !important;
}

.stat-grid .wp-block-cover__inner-container {
  padding: 2.25rem 1.75rem !important;
  width: 100%;
}

/* Stat value — large serif */
.stat-grid .wp-block-cover__inner-container p:first-child,
.stat-grid .stat-value {
  font-family: var(--font-heading) !important;
  font-size: clamp(1.9rem, 3.5vw, 3rem) !important;
  font-weight: 400 !important;
  line-height: 1.1 !important;
  color: var(--color-primary) !important;
  margin-bottom: 0.4rem !important;
}

/* Stat label — small sans */
.stat-grid .wp-block-cover__inner-container p:last-child,
.stat-grid .stat-label {
  font-family: var(--font-body) !important;
  font-size: 1rem !important;
  font-weight: 400 !important;
  letter-spacing: 0.04em !important;
  color: var(--color-muted) !important;
  margin: 0 !important;
}

/* Mobile — keep the 2×3 shape but tighten padding and scale the numbers down
   so long values ("Seasonal", "Sat → Sat") don't overflow their cell. */
@media (max-width: 600px) {
  .stat-grid .wp-block-cover__inner-container {
    padding: 1.4rem 1rem !important;
  }

  .stat-grid .wp-block-cover__inner-container p:first-child,
  .stat-grid .stat-value {
    font-size: clamp(1.4rem, 6.5vw, 1.9rem) !important;
  }

  .stat-grid .wp-block-cover__inner-container p:last-child,
  .stat-grid .stat-label {
    font-size: 0.78rem !important;
    letter-spacing: 0.02em !important;
  }
}

/* =============================================
   CONTENT SECTION   (reusable layout)
   Three primitives you can mix: a padded shell (content-section), a
   2-column split (content-split), and an evenly-spaced content stack
   (content-stack). The stack works on ANY Group to auto-space its
   children — that's the "adds gaps between content" behavior. Below is
   the original text + framed-photo pair arrangement it grew from.

   content-section      → outer Group 
   content-split       → the Columns block 
   content-stack      → the LEFT Column (text)
   content-heading      → the H2 
   content-body         → each body Paragraph
   framed-photos       → the RIGHT Column (holds BOTH images)
   framed-photo        → each Image block (white frame + shadow)
   framed-photo--main  → the large photo
   framed-photo--inset → the small photo (overlaps bottom-left)
   ============================================= */
.content-section {
  padding: 6rem 2rem;
  padding-top: 120px;
  padding-bottom: 120px;
}

.content-split {
  max-width: var(--max-width) !important;
  margin: 0 auto;
  align-items: center !important;
  gap: 4rem !important;
}

.content-stack {
  display: flex !important;
  flex-direction: column !important;
  /* the "auto-spacing" — one gap between every child, tunable per section */
  gap: var(--stack-gap, 1.4rem) !important;
}

/* A core Separator (<hr>) draws its line as a border on a zero-height box.
   As a flex item it shrinks to zero on the vertical axis, so the line
   collapses and "disappears" (the block is still there). Give it a real
   box and stop it from shrinking, and paint the line ourselves so it
   survives regardless of the chosen separator style. */
.content-stack hr.wp-block-separator {
  flex: 0 0 auto !important;   /* don't let flex shrink it to zero */
  height: 2px !important;      /* give the line a real box */
  width: 100% !important;      /* full width of the stack */
  border: none !important;
  background: currentColor !important;
  opacity: 0.25 !important;
  margin: 0 !important;        /* the stack's gap already handles spacing */
}

.content-heading {
  font-size: clamp(2.2rem, 4.5vw, 3.4rem) !important;
  font-weight: 600 !important;
  line-height: 1.12 !important; 
}

.content-body {
  font-size: 1.15rem !important;
  line-height: 1.85 !important;
}

/* Right column is the positioning context for the overlapping inset photo.
   Pages may label the column .framed-photos (plural, per the convention above)
   or .framed-photo (singular, which is also what carries the .framed-photo img
   frame styling). Support both so the absolutely-positioned --inset always
   anchors to the column, never to a box higher up the page. Scoped to
   .wp-block-column so an image figure is never turned into the offset parent. */
.framed-photos,
.wp-block-column.framed-photo {
  position: relative !important;
  padding: 0 !important;
}

/* White polaroid frame + soft shadow on every photo in the stack */
.framed-photo img {
  display: block !important;
  width: 100% !important;
  height: auto !important;
  border-radius: 2px !important;
  box-shadow: 0 18px 42px rgba(0, 0, 0, 0.18) !important;
}

/* Large lake photo — cropped to a portrait frame like the reference */
.framed-photo--main img {
  aspect-ratio: 4 / 5 !important;
  object-fit: cover !important;
}

/* Small sign photo — overlaps the bottom-left corner of the main photo */
.framed-photo--inset {
  position: absolute !important;
  left: -2.5rem !important;
  bottom: -2.5rem !important;
  width: 50% !important;
  z-index: 2 !important;
}

@media (max-width: 781px) {
  .content-section {
    padding: 3.5rem 1.5rem;
    padding-top: 60px;
    padding-bottom: 60px;
  }

  .content-split {
    gap: 2.5rem !important;
  }

  /* Keep the overlap on mobile, but center the inset horizontally over the
     main photo behind it rather than hugging the left corner. */
  .framed-photo--inset {
    left: 50% !important;
    right: auto !important;
    bottom: -1rem !important;
    width: 60% !important;
    transform: translateX(-50%) !important;
    margin: 0 !important;
  }


  .framed-photo img {
    border-width: 7px !important;
    box-shadow: 0 12px 28px rgba(0, 0, 0, 0.16) !important;
  }
}

/* =============================================
   RESPONSIVE
   ============================================= */
@media (max-width: 1100px) {
  :root {
    /* 80px + room for the ~0.75rem gap the logo rule below opens up top and
       bottom. The bar has to grow rather than just inset its contents: the tall
       element is the brand text stack (a wrapped two-line title over the
       tagline), which is close to the bar's full height on its own, so insetting
       alone would leave it flush. */
    --nav-height:        104px;
    /* Mobile: don't shrink the nav on scroll. The shrink animates .site-main's
       reserved top padding (nav-height -> shrunk), which shortens the document
       mid-swipe and makes the hero visibly jump up. Keeping the shrunk height
       equal to the full height neutralizes every .is-scrolled size change (nav,
       logo, and the reserved padding) at once, so nothing reflows on scroll. */
    --nav-height-shrunk: var(--nav-height);
  }

  .nav-inner .nav-menu {
    display: none;
  }

  .nav-hamburger {
    display: flex;
  }

  /* Indent the wordmark to line up with the hero text below it. Desktop already
     aligns (nav + hero share the 1200px content box); on mobile the hero is a
     full-width Cover with ~2rem of side padding while .nav-inner has none, so the
     wordmark sits flush to the edge. Match that padding here. Tweak this value if
     the hero's side padding is changed in the editor. */
  .nav-inner {
    padding-left: 2rem;
  }

  .nav-right .nav-social-link {
    display: none;
  }

  /* Top bar hides on mobile by default; contact info lives in the drawer
     instead, so the header only reserves the nav height even when the bar is
     enabled. "Keep the top bar on mobile" (Customizer > Top Bar) opts back in —
     for a bar carrying branding rather than contact details. */
  .nav-topbar {
    display: none;
  }

  .has-topbar .site-main {
    padding-top: var(--nav-height);
  }

  .has-topbar-mobile .nav-topbar {
    display: block;
  }

  .has-topbar-mobile .site-main {
    padding-top: calc(var(--topbar-height) + var(--nav-height));
  }

  /* Tighter on small screens, and the side slots give way so a centered
     brand line keeps the full width before it truncates. */
  .has-topbar-mobile .topbar-inner {
    padding: 0 1rem;
    gap: 0.75rem;
  }

  .has-topbar-mobile .topbar-text {
    font-size: 0.7rem;
    letter-spacing: 0.12em;
  }

  /* Kill fixed-background "parallax" on Cover blocks on mobile. WordPress'
     "Fixed background" toggle renders the image as a pinned layer
     (background-attachment: fixed), which mobile browsers repaint jerkily so the
     hero image stutters/jumps on every scroll — while desktop (and emulators)
     render it fine. Forcing scroll makes the image travel with the content. */
  .wp-block-cover.has-parallax,
  .wp-block-cover .has-parallax,
  .wp-block-cover__image-background.has-parallax {
    background-attachment: scroll !important;
  }

  /* Logo: constant, compact size on mobile. The desktop logo shrinks on scroll
     (1.8rem -> 1.4rem); on mobile the nav no longer shrinks, so that animation
     only served to grow the long wordmark into the hamburger mid-transition.
     Freeze base + scrolled to one non-wrapping line, sized to the viewport with
     clamp() so it always clears the hamburger on any phone width. Tune the
     clamp() middle/max value if you want the wordmark larger or smaller. */
  .nav-logo--text .nav-logo-title,
  body.nav-anim-shrink.is-scrolled .nav-logo--text .nav-logo-title {
    font-size: clamp(0.95rem, 4.3vw, 1.2rem);
    letter-spacing: 0.05em;
    /* Allow the wordmark to drop to two lines rather than forcing one line. */
    white-space: normal;
    line-height: 1.15;
  }

  /* Cap the wordmark's width so a long name wraps before it slides under the
     fixed hamburger (right: 2rem, 40px wide) instead of running into it. */
  .nav-logo--text {
    max-width: calc(100% - 40px - 1rem);
  }

  /* An inline tagline stays inline on phones, just tighter — the two sit in one
     grid row, and that row has to share a capped-width bar with the hamburger. */
  .nav-logo--inline .nav-logo-text {
    column-gap: 0.5rem;
  }

  /* Breathing room above and below the logo. The base rule sizes the logo box to
     the full bar height, so it sits flush to both edges; shrink it by 1.5rem to
     open a 0.75rem gap top and bottom.

     align-self is what actually centers it. .nav-inner is align-items:stretch,
     and a flex item with a definite height under stretch falls back to
     flex-start — so the box would pin to the top of .nav-inner's content box and
     dump all the slack at the bottom. The base rule's negative margins exist to
     cancel .nav-inner's 1rem padding at full height; here they'd reintroduce that
     same top-heavy offset, so they're zeroed out and the gap comes purely from
     the height reduction, split evenly by the centering.

     The .is-scrolled selector has to be repeated or it would win and reset the
     height. Keep --nav-height above in step with the 1.5rem or the brand text
     (the tall element in this bar) goes flush again. */
  .nav-logo,
  body.nav-anim-shrink.is-scrolled .nav-logo {
    height: calc(var(--nav-height) - 1.5rem);
    align-self: center;
    margin-top: 0;
    margin-bottom: 0;
  }
}

@media (max-width: 768px) {
  /* ── Full-width CTA buttons on mobile ──────────────────────────
     Stack every standard Button block full-width so they're easy to
     tap — the same treatment the hero CTAs already get. Compact
     "tag" style pills (cabin-pills amenity tags, adventure-filter-btn
     map filters, arrow-btn) keep their natural width and are left out.

     The <a> already carries width:100%, but that's 100% of the .wp-block-button
     wrapper, which is a shrink-to-fit flex item — so the wrapper is what has to
     be widened. Column direction makes the cross axis horizontal (in a row, that
     stretch only stretched them vertically, which is why they still hugged their
     label), and the width:100% keeps .wp-block-buttons itself from shrinking to
     fit inside a flex/grid parent that centers its
     children with align-items:flex-start. */
  .wp-block-buttons:has(.is-style-primary, .is-style-secondary, .is-style-outline, .is-style-outline-inverse) {
    display: flex !important;
    flex-direction: column !important;
    align-items: stretch !important;
    width: 100% !important;
  }

  .wp-block-buttons:has(.is-style-primary, .is-style-secondary, .is-style-outline, .is-style-outline-inverse)
    > .wp-block-button {
    width: 100% !important;
  }

  .is-style-primary .wp-block-button__link,
  .is-style-secondary .wp-block-button__link,
  .is-style-outline .wp-block-button__link,
  .is-style-outline-inverse .wp-block-button__link {
    display: block !important;
    width: 100% !important;
    text-align: center !important;
    /* Scale the desktop "high-impact" footprint down to phone size. The label is
       full-width and centered here, so the 3.6rem side padding and the 260px
       floor buy nothing — and at 1rem/0.2em a CTA like "See the summer
       collection" wraps to two lines, which is what made these read as oversized
       slabs. Matches the hero CTA padding set further down. */
    min-width: 0 !important;
    padding: 1rem 1.5rem !important;
    font-size: 0.85rem !important;
    letter-spacing: 0.14em !important;
  }

  .footer-inner {
    grid-template-columns: 1fr;
    padding: 3rem 1.5rem 2rem;
  }

  .footer-col {
    padding: 0;
  }

  /* Stack: dividers go horizontal between rows instead of vertical */
  .footer-col + .footer-col {
    border-left: none;
    border-top: 1px solid rgba(255, 255, 255, 0.12);
    margin-top: 2.5rem;
    padding-top: 2.5rem;
  }

  .footer-bar-inner {
    flex-direction: column;
    gap: 0.5rem;
    text-align: center;
  }

  /* --- Hero: first Cover in <main> — mobile polish ---
     Heroes carry no class, so we target the first Cover in the main region.
     Structure per hero: H2.section-eyebrow · H1 · Paragraph · Buttons
     (Primary + Secondary styles). Rules below assume a left-aligned hero. */

  .site-main > .wp-block-cover:first-child .wp-block-cover__inner-container {
    padding: 40px 1.5rem 56px !important;
  }

  .section-eyebrow {
    font-size: 0.8rem !important;
    letter-spacing: 0.16em !important;
    margin-bottom: 0.6rem !important;
  }

  .site-main > .wp-block-cover:first-child h1 {
    font-size: clamp(3.4rem, 15vw, 5.4rem) !important;
    line-height: 1.0 !important;
    margin-bottom: 0.8rem !important;
  }

  .site-main > .wp-block-cover:first-child p:not(.section-eyebrow) {
    font-size: 1.02rem !important;
    line-height: 1.5 !important;
    max-width: 34ch !important; /* keep the line length comfortable to read */
  }

  /* Stack the two CTAs full-width so they're easy to tap and never overflow. */
  .site-main > .wp-block-cover:first-child .wp-block-buttons {
    flex-direction: column !important;
    align-items: stretch !important;
    gap: 0.75rem !important;
    width: 100%;
    max-width: 340px;
  }

  .site-main > .wp-block-cover:first-child .wp-block-button__link {
    display: block !important;
    width: 100% !important;
    text-align: center !important;
    padding: 1rem 1.5rem !important;
  }

  .wp-block-media-text__content {
    padding-top: 50px !important;
    padding-bottom: 50px !important;
  }
}


/* =============================================
   QUOTE  —  core Quote block (.wp-block-quote)
   ---------------------------------------------
   Styles the stock Quote block, so an editor gets the look with no class to
   remember. Structure WordPress outputs:

     blockquote.wp-block-quote
       Paragraph      the quote itself (serif italic, muted)
       cite           optional attribution (small caps, gold rule)

   The quote body runs in --color-muted so it reads as a set-aside voice rather
   than body copy. There is no left rule and no floating decorative mark: the
   quotation marks are inline, opening the first paragraph and closing the last,
   so they wrap the actual words the way a quote does on the page. They're sized
   in em, so they scale with the quote at every viewport with nothing to re-tune.

   Alignment is respected (Align Center is the variant in the seasonal-quote
   section). Core's own Plain and Large block styles still work — Plain drops the
   marks for bare type, Large scales the whole thing up.
   ============================================= */

.wp-block-quote {
  /* Core ships a heavy black border-left on this block — off, in favor of the
     inline marks below. */
  border: none;
  margin: 3rem 0;
  padding: 0;
  color: var(--color-muted);
  font-family: var(--font-heading);
  font-style: italic;
  /* Display-scale on desktop — a quote is a set piece, not a sidebar note. The
     clamp's ceiling is what a wide screen gets; the floor is what keeps it from
     shrinking past readable on a phone (see the mobile block below, which caps
     the vw growth so a long quote can't run away with a narrow column). */
  font-size: clamp(1.4rem, 2.9vw, 2.6rem);
  line-height: 1.35;
}

.wp-block-quote p {
  margin: 0 0 0.75rem;
}

.wp-block-quote p:last-of-type {
  margin-bottom: 0;
}

/* The marks themselves — accent-colored, upright (the quote is italic, but a
   slanted mark reads as a stray comma), and a touch larger than the words so
   they register as punctuation rather than letters. Sized in em so they track
   the clamped font size on their own.

   Multi-paragraph quotes get one opening mark on the first paragraph and one
   closing mark on the last, not a pair per paragraph — which is the typographic
   convention, and why these hang off :first-of-type / :last-of-type rather than
   the blockquote itself. */
.wp-block-quote p:first-of-type::before,
.wp-block-quote p:last-of-type::after {
  color: var(--color-accent);
  font-family: var(--font-heading);
  font-size: 1.3em;
  font-style: normal;
  line-height: 0;                    /* keeps the oversized glyph from opening up the line box */
  vertical-align: -0.12em;           /* sits the mark on the text, not above it */
}

.wp-block-quote p:first-of-type::before {
  content: '\201C';                  /* “ */
  margin-right: 0.06em;
}

.wp-block-quote p:last-of-type::after {
  content: '\201D';                  /* ” */
  margin-left: 0.04em;
}

/* Attribution — the counterweight to the italic serif above: upright, small,
   letterspaced caps in the body font, led by a short accent dash. */
.wp-block-quote cite,
.wp-block-quote .wp-block-quote__citation {
  display: block;
  margin-top: 1.5rem;
  font-family: var(--font-body);
  /* Nudged up with the quote, but deliberately still small — the gap in scale
     between the two is what keeps the attribution from reading as more quote. */
  font-size: 0.8rem;
  font-style: normal;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-muted);
  opacity: 0.8;
}

.wp-block-quote cite::before,
.wp-block-quote .wp-block-quote__citation::before {
  content: '';
  display: inline-block;
  width: 1.75rem;
  height: 1px;
  margin-right: 0.7rem;
  vertical-align: middle;
  background: var(--color-accent);
}

/* ---- Centered (block toolbar ▸ Align text ▸ Center) ----
   The marks travel with the words, so a centered quote needs no repositioning —
   only a line-length cap, since at display size a quote spanning a full-width
   column runs uncomfortably long. */
.wp-block-quote.has-text-align-center {
  max-width: 24ch;
  margin-inline: auto;
  text-align: center;
}

.wp-block-quote.has-text-align-center cite::before,
.wp-block-quote.has-text-align-center .wp-block-quote__citation::before {
  /* The dash becomes a rule above the attribution rather than a lead-in beside it. */
  display: block;
  width: 2.5rem;
  margin: 0 auto 0.9rem;
}

/* ---- Right-aligned ---- */
.wp-block-quote.has-text-align-right {
  text-align: right;
}

/* ---- Core's Plain style — bare type, no marks ---- */
.wp-block-quote.is-style-plain p:first-of-type::before,
.wp-block-quote.is-style-plain p:last-of-type::after {
  content: none;
}

/* ---- Core's Large style — same frame, bigger voice ---- */
.wp-block-quote.is-style-large {
  font-size: clamp(1.6rem, 3.8vw, 3.4rem);
}

/* ---- Phones ----
   The base sizes above are tuned for a wide screen, and their vw terms grow
   faster than a phone column can carry — a 2.9vw quote wraps to a wall of ragged
   lines in a narrow column. The type is re-clamped here into a tight readable
   band; the marks are em-sized, so they follow it down on their own. The centered
   variant drops its ch cap (the column is already narrower than 24ch, so the cap
   would only squeeze it further). */
@media (max-width: 781px) {
  .wp-block-quote {
    margin: 2.25rem 0;
    font-size: clamp(1.25rem, 4.6vw, 1.6rem);
    line-height: 1.4;
  }

  .wp-block-quote.has-text-align-center {
    max-width: none;
  }

  .wp-block-quote.is-style-large {
    font-size: clamp(1.4rem, 5.4vw, 1.95rem);
  }

  .wp-block-quote cite,
  .wp-block-quote .wp-block-quote__citation {
    margin-top: 1.15rem;
    font-size: 0.72rem;
  }
}

/* =============================================
   HERO CINEMATIC  —  layered scrim + candle glow   (reusable)

   Turns a Cover block into a cinematic hero. Three layers sit between the
   photo and the text:

     1. a SIDE SCRIM that darkens the half the headline sits on, while the
        far side of the photo stays open and readable as an image;
     2. a BOTTOM FADE that dissolves the photo into the page background, so
        the hero has no hard seam against the section under it;
     3. an optional GLOW — a soft warm halo that pulses like candlelight.

   Editor classes:

     Cover .hero-cinematic                      (Full width; pick the photo)
       ├─ Heading/Paragraph .section-eyebrow
       ├─ Heading (h1)
       ├─ Paragraph
       └─ Buttons › Button                       (any theme button style)

   Modifiers — add alongside the base class:
     .hero-cinematic--right   dark side on the RIGHT (for right-aligned text).
                              Also sends the glow to the opposite side.
     .hero-cinematic--center  no favored side — an even veil over the whole photo
                              for CENTERED text. Trades the side scrim for a
                              bottom-weighted fade plus a flat dim, and centers
                              the glow. Set Content position to Center Center.
     .hero-cinematic--glow    draws layer 3, the pulsing warm glow.
     Glow placement (with --glow) — the sibling of ambient-glow's modifiers:
     .hero-cinematic--glow-left / --glow-center / --glow-right   horizontal
     .hero-cinematic--glow-top / --glow-middle / --glow-bottom   vertical
                              Override the default off-to-the-side placement, and
                              beat --right's auto-shift when both are present.

   In the editor:
   · Content position — put it on the dark side (Center Left by default).
   · Focal point — drag it on the Cover's image; WordPress writes it as
     object-position, so reframing the shot needs no CSS.
   · Overlay — deliberately switched off inside this class: the scrim below
     IS the overlay, and a second flat dim on top only muddies it. If a busy
     photo still needs flattening, set --hero-dim instead.
   · Minimum height — the editor's field wins over the default below (it is
     written inline), so a per-page height is still one control away.

   Everything else is tuned with the variables below. Override them per site
   (a stylesheet, or the Cover's own Additional CSS) — never by editing the
   stops here, or every site sharing the theme moves with you.
   ============================================= */
.hero-cinematic {
  /* --- the scrim (layers 1 + 2) --- */
  --hero-scrim-color:    var(--color-primary);  /* the dark the photo fades into */
  --hero-scrim-angle:    95deg;                 /* direction of the side darkening */
  --hero-scrim-strength: 1;                     /* 0–1 · lower = a lighter scrim */
  --hero-fade-height:    34%;                   /* how far up the bottom fade climbs */
  --hero-dim:            transparent;           /* extra flat dim, e.g. rgba(0,0,0,.3) */
  /* --- the glow (layer 3, only drawn with --glow) --- */
  --hero-glow-color:     var(--color-accent-bold); /* hot center — the amber step */
  --hero-glow-color-2:   var(--color-accent-deep); /* falloff — the ember step */
  --hero-glow-size:      clamp(280px, 45vw, 520px);
  --hero-glow-x:         18%;                   /* in from the RIGHT edge */
  --hero-glow-y:         42%;                   /* down from the TOP edge */
  --hero-glow-speed:     4.5s;

  position: relative;
  /* Contain the three z-indexes below to this Cover, so a hero can never
     stack against the fixed nav or a following section. */
  isolation: isolate;
}

/* Compound selector (not a bare .hero-cinematic) so these beat core's
   .wp-block-cover defaults no matter which stylesheet WordPress prints last —
   while still losing to the editor's own inline min-height, as it should. */
.wp-block-cover.hero-cinematic {
  min-height: clamp(640px, 94vh, 1000px);
  background-color: var(--hero-scrim-color);  /* base tone under the photo */
}

/* Layers 1 + 2 — one element, stacked gradients (topmost listed first). */
.hero-cinematic::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;                    /* over the photo, under the glow and the text */
  pointer-events: none;
  opacity: var(--hero-scrim-strength);
  background-color: transparent; /* core tries to inherit the dim color here */
  background-image:
    /* 2 · bottom fade into the page background */
    linear-gradient(0deg,
      var(--hero-scrim-color) 2%,
      color-mix(in srgb, var(--hero-scrim-color) 0%, transparent) var(--hero-fade-height)),
    /* 1 · side scrim — solid behind the text, thinning across the photo */
    linear-gradient(var(--hero-scrim-angle),
      var(--hero-scrim-color) 14%,
      color-mix(in srgb, var(--hero-scrim-color) 86%, transparent) 38%,
      color-mix(in srgb, var(--hero-scrim-color) 34%, transparent) 68%,
      color-mix(in srgb, var(--hero-scrim-color) 55%, transparent) 100%),
    /* optional flat dim, off unless --hero-dim is set */
    linear-gradient(var(--hero-dim), var(--hero-dim));
}

/* Mirrored: 360 - 95 = 265deg turns the scrim around. The glow is placed from
   the right edge, so a right-side offset of 82% lands it 18% in from the left —
   it stays on the open half of the photo, opposite the text. */
.hero-cinematic--right {
  --hero-scrim-angle: 265deg;
  --hero-glow-x: 82%;
}

/* Centered hero — no side is favored, so centered text reads over the middle of
   the photo where the side scrim is thinnest. Two moves replace it:
     · the scrim turns vertical (0deg = darken bottom→top), so no half is
       privileged over the other;
     · a flat dim (tied to the scrim color, not a hardcoded black) lays an even
       veil across the whole photo so text in the vertical middle stays legible.
   Also centers the glow. --hero-dim uses color-mix so it tracks the palette; a
   busier photo can take it higher per page, a quieter one lower. */
.hero-cinematic--center {
  --hero-scrim-angle: 0deg;
  --hero-dim: color-mix(in srgb, var(--hero-scrim-color) 36%, transparent);
  --hero-glow-x: 50%;
}

/* Glow placement — opt-in, the sibling of ambient-glow's --left/--right/--top/
   --bottom. Each just sets --hero-glow-x / -y, so they're inert without --glow
   and layer over the defaults. Defined AFTER --right so an explicit position
   wins when a hero carries both (e.g. --right --glow-center).
   Note: --hero-glow-x measures in from the RIGHT edge (see .hero-cinematic),
   so a LEFT-side glow is a HIGH x and a right-side glow a low one. */
.hero-cinematic--glow-left    { --hero-glow-x: 84%; }
.hero-cinematic--glow-center  { --hero-glow-x: 50%; }
.hero-cinematic--glow-right   { --hero-glow-x: 16%; }
.hero-cinematic--glow-top     { --hero-glow-y: 22%; }
.hero-cinematic--glow-middle  { --hero-glow-y: 50%; }
.hero-cinematic--glow-bottom  { --hero-glow-y: 78%; }

/* Layer 3 — the candle glow. */
.hero-cinematic--glow::after {
  content: "";                   /* core blanks .wp-block-cover::after; take it back */
  position: absolute;
  z-index: 2;
  right: var(--hero-glow-x);
  top: var(--hero-glow-y);
  width: var(--hero-glow-size);
  height: var(--hero-glow-size);
  min-height: 0;                 /* core sets min-height:inherit — it would size the
                                    glow to the whole hero */
  transform: translate(50%, -50%);
  border-radius: 50%;
  background-image: radial-gradient(circle,
    color-mix(in srgb, var(--hero-glow-color) 50%, transparent) 0%,
    color-mix(in srgb, var(--hero-glow-color-2) 26%, transparent) 38%,
    color-mix(in srgb, var(--hero-glow-color-2) 0%, transparent) 70%);
  filter: blur(10px);
  pointer-events: none;
  animation: onyx-hero-glow var(--hero-glow-speed) ease-in-out infinite;
}

/* The pulse — opacity only, so it stays on the compositor and never repaints
   the photo underneath. The uneven steps are what makes it read as a flame
   rather than a breathing dot. */
@keyframes onyx-hero-glow {
  0%, 100% { opacity: 0.62; }
  25%      { opacity: 0.42; }
  52%      { opacity: 0.70; }
  78%      { opacity: 0.50; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-cinematic--glow::after {
    animation: none;
    opacity: 0.55;               /* keep the light, drop the flicker */
  }
}

/* Text rides above all three layers. */
.hero-cinematic > .wp-block-cover__inner-container {
  z-index: 3;
}

/* The Cover's own overlay/gradient is retired in favor of the scrim. Core
   drives its opacity from .has-background-dim-NN classes, which outweigh this
   selector — hence !important, per the theme's convention. */
.hero-cinematic > .wp-block-cover__background,
.hero-cinematic > .wp-block-cover__gradient-background {
  opacity: 0 !important;
}

/* HERO HEADING — the display-size sibling of .content-heading (style.css:2332).
   Same idea, one size band up: content-heading tops out at 3.4rem for a section
   title, this one carries a hero H1 to 5rem. Deliberately NOT scoped to
   .hero-cinematic — any hero can use it, with or without the scrim.

   Left free of the mobile hero rule (style.css:2649), which sizes the H1 of the
   first Cover in <main> on phones and wins on specificity there. That rule is
   the tighter, tested phone treatment; this clamp governs everything above it.

   text-wrap: balance evens the line lengths so a long headline doesn't leave a
   single orphaned word on the last line. Browsers without it just wrap normally. */
.hero-heading {
  font-size: clamp(2.6rem, 6.4vw, 5rem) !important;
  font-weight: 600 !important;
  line-height: 1.05 !important;
  text-wrap: balance;
}

/* =============================================
   AMBIENT GLOW  —  a warm pool of light behind a section   (reusable)

   .hero-cinematic--glow (style.css:3335) lights a Cover. This is the same idea
   with the Cover taken out of it: one class, on ANY block, that floats a soft
   radial warm light behind whatever the block contains — the candlelit-room look
   for a plain dark Group.

   Needs a dark background to read. On a light panel the light has nothing to
   glow against and just muddies the color.

   Apply to:  Group / Row / Stack / Columns — anything with a background and
              some content in it. (For a Cover, use hero-cinematic instead: its
              photo layer paints over anything drawn here.)

   Modifiers — add alongside the base class:
     .ambient-glow--pulse    the light breathes, like a flame
     .ambient-glow--wide     stretch the pool into a horizontal wash
     .ambient-glow--tall     stand it up into a vertical column of light
     .ambient-glow--top      pool sits high in the block, behind a heading
     .ambient-glow--bottom   pool sits low in the block
     .ambient-glow--left     pool sits to the left, lighting one column
     .ambient-glow--right    pool sits to the right
     .ambient-glow--soft     half strength — a hint of warmth
     .ambient-glow--strong   turned up, for a big empty section

   Tunable (set on the block, or globally in :root):
     --ambient-glow-color      hot center       default: --color-accent-bold
     --ambient-glow-color-2    falloff          default: --color-accent-deep
     --ambient-glow-x / -y     center of the pool, % of the block   default: 50% / 50%
     --ambient-glow-size       the pool's width — height follows    default: ~54%
     --ambient-glow-ratio      width ÷ height. 1 = circle           default: 1
     --ambient-glow-strength   0–1 master opacity                   default: 0.5
     --ambient-glow-blur       how soft the edge is                 default: 40px
     --ambient-glow-speed      one full pulse cycle                 default: 5s

   ROUND BY DEFAULT. The pool is a circle: one --size drives the width, and
   aspect-ratio derives the height from it, so the light doesn't distort with the
   shape of its block. Sizing height off the block instead (the obvious way, and
   the wrong one) squashes the pool flat in a wide section and stretches it into a
   pillar in a narrow one — the light ends up a different shape on every page it
   lands on. Reach for --ratio (or --wide / --tall) when the block genuinely wants
   a stretched wash; the base case is a round pool of candlelight.

   Size is a % of the block's WIDTH, so it still scales with the section rather
   than being a fixed disc that swamps a short block and gets lost in a tall one.
   The clamps keep it sane at both ends. It fades to transparent well inside its
   own box, so it bleeds softly past the block's edges rather than stopping at a
   seam — no overflow clipping is applied (it would cut off overlapping children
   like .photo-stack). */
.ambient-glow {
  --ambient-glow-color:    var(--color-accent-bold);
  --ambient-glow-color-2:  var(--color-accent-deep);
  --ambient-glow-x:        50%;
  --ambient-glow-y:        50%;
  --ambient-glow-size:     clamp(340px, 54%, 820px);
  --ambient-glow-ratio:    1;
  --ambient-glow-strength: 0.5;
  --ambient-glow-blur:     40px;
  --ambient-glow-speed:    5s;

  position: relative;
  /* Traps the z-index: -1 below inside this block. Without it the glow would be
     free to slide behind an ancestor's background and vanish. */
  isolation: isolate;
}

/* The pool itself. z-index: -1 puts it above the block's OWN background but
   below every child — which is exactly the painting order we want, and it means
   the content needs no z-index of its own (nothing is touched but this block). */
.ambient-glow::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: var(--ambient-glow-x);
  top: var(--ambient-glow-y);
  width: var(--ambient-glow-size);
  /* Height comes from the width, not from the block — this is what keeps the pool
     round no matter how wide or short the section it's sitting in turns out to be. */
  aspect-ratio: var(--ambient-glow-ratio);
  height: auto;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  /* `circle` rather than `ellipse`: the box is already the right shape, so let the
     falloff be perfectly round inside it and stay round when --ratio stretches the
     box. An ellipse gradient would re-fit itself to the box and undo the effect. */
  background-image: radial-gradient(circle,
    color-mix(in srgb, var(--ambient-glow-color) 34%, transparent) 0%,
    color-mix(in srgb, var(--ambient-glow-color-2) 15%, transparent) 42%,
    color-mix(in srgb, var(--ambient-glow-color-2) 0%, transparent) 72%);
  filter: blur(var(--ambient-glow-blur));
  opacity: var(--ambient-glow-strength);
  pointer-events: none;
}

/* Shape, placement, strength. Each one only moves a variable, so they compose:
   --top --strong --pulse together is a valid, and rather nice, stack. So do the
   two placement axes: --top --left puts the pool in the upper-left corner.

   The horizontal pair sits at 22%/78% rather than the vertical pair's 18%/82% —
   blocks are wider than they are tall, so an equal % is a longer travel sideways
   and the pool starts falling off the edge. This keeps the light inside the
   section on both axes. */
.ambient-glow--wide   { --ambient-glow-ratio: 1.9; --ambient-glow-size: clamp(460px, 76%, 1100px); }
.ambient-glow--tall   { --ambient-glow-ratio: 0.6; }
.ambient-glow--top    { --ambient-glow-y: 18%; }
.ambient-glow--bottom { --ambient-glow-y: 82%; }
.ambient-glow--left   { --ambient-glow-x: 22%; }
.ambient-glow--right  { --ambient-glow-x: 78%; }
.ambient-glow--soft   { --ambient-glow-strength: 0.28; }
.ambient-glow--strong { --ambient-glow-strength: 0.78; }

/* The pulse — opacity only, so it stays on the compositor and never repaints the
   section under it. Every step is a multiple of --strength, so turning the glow
   down turns the pulse down with it instead of un-dimming it on the way past.
   The uneven timing is what makes it read as a flame rather than a breathing dot. */
.ambient-glow--pulse::before {
  animation: onyx-ambient-glow var(--ambient-glow-speed) ease-in-out infinite;
}

@keyframes onyx-ambient-glow {
  0%, 100% { opacity: calc(var(--ambient-glow-strength) * 1);    }
  25%      { opacity: calc(var(--ambient-glow-strength) * 0.72); }
  52%      { opacity: calc(var(--ambient-glow-strength) * 1.15); }
  78%      { opacity: calc(var(--ambient-glow-strength) * 0.86); }
}

/* Reduced motion: keep the light, drop the flicker. */
@media (prefers-reduced-motion: reduce) {
  .ambient-glow--pulse::before {
    animation: none;
  }
}

/* =============================================
   WAVY EDGE  —  a soft wave where two sections meet   (reusable)

   The organic, torn-edge transition between two color bands: a cream section
   above rolling down into a purple one below, joined by a gentle wave rather
   than a ruler-straight seam.

   HOW IT WORKS — overlay, not a cut. The block stays a plain rectangle. A
   ::before pinned to the top edge is painted a solid --wave-color and then
   MASKED by a repeating SVG wave, so all that survives is a wavy band of the
   neighbor's color laid over this block's own background. Nothing is clipped,
   nothing depends on what sits behind the block or how the two sections overlap
   in the editor — which is what makes it safe to drop on any block on any site.

   THE ONE THING TO SET: --wave-color must match the section on the OTHER side of
   the seam (the wave is that neighbor reaching over). Default is --color-bg (the
   page cream), which is the common case: a colored Group butting up under the
   page background. Put the wave on the colored block, not the cream one.

   The shape is TILED (mask-repeat: repeat-x), so the crest rhythm stays the same
   width on a phone and on a widescreen instead of stretching lazy on one and
   cramping on the other. --wave-length is the width of one repeating tile — a few
   uneven rolls, not a single crest, so the loop point never reads as a seam;
   --wave-height is how far the wave travels vertically.

   Apply to:  Group / Row / Stack / Columns — anything with a background.
              Give it enough top padding that content clears --wave-height.

   Modifiers — add alongside the base class:
     .wavy-edge--bottom   put the wave on the BOTTOM edge instead of the top
     .wavy-edge--both     wave BOTH edges at once — top and bottom together, so a
                          band nestles between the two sections it touches. Use
                          this INSTEAD of --bottom, not with it (they'd stack two
                          waves on the bottom). The extra edge is a second masked
                          band drawn as ::after; everything else is shared.
     .wavy-edge--flip     mirror the crests left-to-right (breaks up a repeat
                          when two waved sections stack)
     .wavy-edge--tall     deeper, more dramatic swells
     .wavy-edge--subtle   shallow ripple instead of a full wave

   Tunable (set on the block, or globally in :root):
     --wave-color         wave color = the neighboring section       default: --color-bg
     --wave-color-bottom  bottom edge's color, when it differs from   default: --wave-color
                          the top (a --both band between two sections,
                          or a lone --bottom wave)
     --wave-height        vertical travel of the wave                default: clamp(18px, 3vw, 40px)
     --wave-length        width of one repeating tile (~2.5 rolls)   default: clamp(340px, 44vw, 720px)

   The wave rides at z-index: -1 — above this block's own background but below its
   content — the same painting trick as .ambient-glow, so the section's text needs
   no z-index of its own and is never covered. isolation: isolate traps that -1 so
   it can't slip behind an ancestor's background and vanish. */
.wavy-edge {
  --wave-color:  var(--color-bg);
  /* Bottom edge can take its own color, so a --both band connects to a DIFFERENT
     section above and below (cream over the top, the next section under the
     bottom). Defaults to --wave-color, so single-color waves need only set that. */
  --wave-color-bottom: var(--wave-color);
  --wave-height: clamp(18px, 3vw, 40px);
  --wave-length: clamp(340px, 44vw, 720px);
  /* The wave shape, as a mask. Held in a variable so both the top band (::before)
     and the --both bottom band (::after) draw the identical crest rhythm without
     repeating the SVG. The filled path is the part that SURVIVES the mask (the
     crests reaching into this block); the empty part lets this block's own color
     through in the troughs. Left and right edges of the tile meet at the same
     height, so it repeats seamlessly with no visible seam. */
  --wave-mask: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20240%2028'%20preserveAspectRatio='none'%3E%3Cpath%20d='M0,0%20H240%20V15%20C226,15%20212,11%20198,11%20C182,11%20166,19%20150,19%20C135,19%20119,12%20104,12%20C87,12%2069,18%2052,18%20C35,18%2017,15%200,15%20Z'%20fill='%23000'/%3E%3C/svg%3E");
  /* Composable flip axes — modifiers move these, never `transform` itself, so
     --bottom and --flip stack instead of overwriting each other. */
  --wave-flip-x: 1;
  --wave-flip-y: 1;

  position: relative;
  isolation: isolate;
}

/* The band(s). ::before is the top edge (always present); ::after is the bottom
   edge and only turns on under --both. Both are a solid rectangle of color that
   the mask carves the wave out of; they share everything but which edge they pin
   to, their vertical flip, and their color (set per-edge below). */
.wavy-edge::before,
.wavy-edge--both::after {
  content: "";
  position: absolute;
  z-index: -1;
  left: 0;
  right: 0;
  height: var(--wave-height);
  -webkit-mask-image: var(--wave-mask);
          mask-image: var(--wave-mask);
  -webkit-mask-repeat: repeat-x;
          mask-repeat: repeat-x;
  -webkit-mask-size: var(--wave-length) 100%;
          mask-size: var(--wave-length) 100%;
  pointer-events: none;
}

.wavy-edge::before {
  top: 0;                       /* --bottom re-pins this to the bottom edge */
  background-color: var(--wave-color);
  transform: scale(var(--wave-flip-x), var(--wave-flip-y));
}

/* Move the whole band to the bottom edge and flip it vertically so the crests
   rise UP into the block from below, mirroring the top-edge behavior. */
.wavy-edge--bottom::before {
  top: auto;
  bottom: 0;
  background-color: var(--wave-color-bottom);
}
.wavy-edge--bottom { --wave-flip-y: -1; }

/* --both: keep the top band (::before, untouched) and add a bottom one. The
   bottom band pins to the bottom edge and always flips vertically (its crests
   rise up), independent of --wave-flip-y so the top stays pointing down; --flip
   still mirrors both horizontally through the shared --wave-flip-x. */
.wavy-edge--both::after {
  top: auto;
  bottom: 0;
  background-color: var(--wave-color-bottom);
  transform: scale(var(--wave-flip-x), -1);
}

.wavy-edge--flip   { --wave-flip-x: -1; }
.wavy-edge--tall   { --wave-height: clamp(34px, 5.5vw, 72px); }
.wavy-edge--subtle { --wave-height: clamp(12px, 2vw, 24px); }

/* =============================================
   STEP FLOW  —  a sequence with arrows between the steps   (reusable)

   A row of short phrases with an arrow drawn between each pair — a recipe, a
   process, a route, a before → after. Reusable anywhere a few items need to read
   as an ordered chain rather than a list.

   Apply to:  Row (or Group) .step-flow
              └─ Paragraph            one per step — NO class, and NO typed arrows
              └─ Paragraph
              └─ Paragraph

   THE EDITOR NEVER TYPES AN ARROW. The separator is drawn by CSS, on every step
   but the first (`> * + *`), which is what makes the row safe to edit: add a step,
   delete a step, or reorder them and the arrows re-derive themselves. There is
   never a trailing arrow dangling off the end, and — because the arrow belongs to
   the step that FOLLOWS it, not the one before — a row that wraps onto a second
   line carries the arrow down with the step it points at, instead of stranding it
   at the end of line one.

   Step text is left alone: color, size and font come from the block editor, so
   the row inherits whatever the section around it is using. The arrow is the only
   thing this class colors.

   Modifiers — add alongside the base class:
     .step-flow--stack          stack vertically; the arrow turns to point down
     .step-flow--stack-mobile   horizontal on desktop, stacked on phones

   Tunable (set on the block, or globally in :root):
     --step-flow-arrow         the glyph, as a CSS string   default: '\2192'  (→)
     --step-flow-arrow-color   its color                    default: --color-accent-deep
     --step-flow-arrow-size    its size                     default: 1em (matches the step text)
     --step-flow-gap           space around each arrow      default: 0.9rem

   The arrow rides on accent-deep, the boldest step of the accent ramp — the same
   slot the Arrow Link button uses, and for the same reason: an arrow is a direction cue, not
   body text, so it takes the saturated tone. Swap --step-flow-arrow for any glyph
   the chain wants: '\00B7' (·), '\2022' (•), '\2044' (⁄), '\2726' (✦).
   ============================================= */

.step-flow {
  --step-flow-arrow:       '\2192';   /* → */
  --step-flow-arrow-color: var(--color-accent-deep);
  --step-flow-arrow-size:  1em;
  --step-flow-gap:         0.9rem;

  display: flex !important;
  flex-wrap: wrap !important;
  align-items: center !important;
  gap: var(--step-flow-gap) !important;
}

/* Steps sit flush — the flex gap is doing the spacing, so a paragraph's default
   margin would double it. */
.step-flow > * {
  margin: 0 !important;
}

/* The arrow. `> * + *` is the whole trick: it matches every step EXCEPT the first,
   so the count of arrows always trails the count of steps by exactly one, however
   many steps there turn out to be.

   Spacing is symmetric without a second variable: the flex gap opens the space
   BEFORE the arrow, and this margin opens the same space AFTER it. */
.step-flow > * + *::before {
  content: var(--step-flow-arrow);
  /* Repeated with the alt-text syntax (`content: <glyph> / ""`), which hides the
     arrow from screen readers — it's decoration, and "right arrow" read aloud
     between every step is noise. Browsers that don't support the syntax throw this
     second declaration away and keep the first, so the arrow still draws. Both
     lines are needed; neither is redundant. */
  content: var(--step-flow-arrow) / '';
  display: inline-block;
  margin-right: var(--step-flow-gap);
  color: var(--step-flow-arrow-color);
  font-size: var(--step-flow-arrow-size);
  line-height: 1;
  pointer-events: none;
}

/* Stacked — the chain runs down the page, so the arrow has to point down with it.
   Only the glyph and the axis change; everything else is inherited from the base. */
.step-flow--stack {
  --step-flow-arrow: '\2193';   /* ↓ */
  flex-direction: column !important;
  align-items: flex-start !important;
}

.step-flow--stack > * + *::before {
  display: block;
  margin: 0 0 var(--step-flow-gap);
}

/* Same move, but only once the row runs out of width. Three or four phrases that
   sit comfortably on one desktop line become a ragged two-line wrap on a phone;
   stacking them reads as the deliberate chain it is. */
@media (max-width: 600px) {
  .step-flow--stack-mobile {
    --step-flow-arrow: '\2193';   /* ↓ */
    flex-direction: column !important;
    align-items: flex-start !important;
  }

  .step-flow--stack-mobile > * + *::before {
    display: block;
    margin: 0 0 var(--step-flow-gap);
  }
}

/* =============================================
   TAG PILLS  —  a wrapping row of small outlined labels   (reusable)

   Short, non-clickable descriptors set in rounded outlines: tasting notes, room
   features, tour highlights, service tags. Distinct from .media-card-badge, which
   is ONE solid dark badge sitting on a photo inside a card — these are a set, they
   wrap, and they're outlined rather than filled.

   Apply to:  Row (or Group) .tag-pills
              └─ Paragraph        one per pill — NO class needed
              └─ Paragraph
              └─ Paragraph

   The container styles its own children, so the editor types the words and classes
   the Row once. For a single pill standing on its own (outside a .tag-pills row),
   put .tag-pill on the paragraph instead — the two are the same pill.

   Modifiers — add alongside the base class:
     .tag-pills--solid   filled instead of outlined (.tag-pill--solid for a lone pill)

   Tunable (set on the block, or globally in :root):
     --tag-pill-color    text + outline   default: --color-accent
     --tag-pill-border   the outline      default: --tag-pill-color at 45%
     --tag-pill-bg       the fill         default: transparent
     --tag-pill-size     text size        default: 0.72rem
     --tag-pill-pad      padding          default: 0.85em 1.7em  (em, so it scales with --size)
     --tag-pill-gap      space between    default: 0.75rem

   Every value is read with a var() FALLBACK rather than declared on the pill, so a
   site can set --tag-pill-* on the .tag-pills Row (or in :root) and have it reach
   the pills. Declaring the defaults on the pill itself would look equivalent and
   isn't: the pill's own declaration would out-rank the value inheriting from its
   parent, and setting the variable on the Row — the obvious place to set it —
   would silently do nothing.

   Pills are LABELS, not links or filters: no hover, no cursor change, nothing to
   click. If a set of them ever needs to be interactive, that's a different
   component — don't hang a click handler on this one.
   ============================================= */

.tag-pills {
  display: flex !important;
  flex-wrap: wrap !important;
  align-items: center !important;
  gap: var(--tag-pill-gap, 0.75rem) !important;
}

.tag-pills > *,
.tag-pill {
  display: inline-block !important;
  margin: 0 !important;
  padding: var(--tag-pill-pad, 0.85em 1.7em) !important;
  background: var(--tag-pill-bg, transparent) !important;
  border: 1px solid
    var(--tag-pill-border,
      color-mix(in srgb, var(--tag-pill-color, var(--color-accent)) 45%, transparent)) !important;
  border-radius: 999px !important;   /* any number past half the height gives a true capsule */
  color: var(--tag-pill-color, var(--color-accent)) !important;
  font-family: var(--font-body) !important;
  font-size: var(--tag-pill-size, 0.72rem) !important;
  font-weight: 700 !important;
  letter-spacing: 0.14em !important;
  line-height: 1 !important;
  text-transform: uppercase !important;
  /* A pill is one label. Let it wrap internally and "A HINT OF TANG" becomes a
     two-line lump among one-line pills; keep it whole and the ROW wraps instead,
     which is the behavior we actually want. */
  white-space: nowrap !important;
  
}



/* Filled. The outline disappears into the fill, and the label flips to the dark
   ink so it still holds against a gold ground. */
.tag-pills--solid,
.tag-pill--solid {
  --tag-pill-bg:     var(--color-accent);
  --tag-pill-color:  var(--color-primary);
  --tag-pill-border: transparent;
}

/* Interactive. Add ALONGSIDE .tag-pills (or .tag-pill on a lone pill) to make the
   pills respond to the pointer: on hover they fill with the accent, the label flips
   to the dark ink, and the whole capsule lifts a hair. Use this only when the pills
   are genuinely clickable (links) — the base pill is a plain label and stays inert.

   Reads the same --tag-pill-* tunables as the base, plus its own hover pair, so a
   site can retint the effect without touching this rule:
     --tag-pill-hover-bg     fill on hover     default: --tag-pill-color (the outline color)
     --tag-pill-hover-color  label on hover    default: --color-primary
*/
.tag-pills--hover > *,
.tag-pill--hover {
  cursor: pointer !important;
  transition:
    background-color 0.25s ease,
    border-color 0.25s ease,
    color 0.25s ease,
    transform 0.25s ease !important;
}

.tag-pills--hover > *:hover,
.tag-pills--hover > *:focus-visible,
.tag-pill--hover:hover,
.tag-pill--hover:focus-visible {
  background: var(--tag-pill-hover-bg,
    var(--tag-pill-color, var(--color-accent))) !important;
  border-color: var(--tag-pill-hover-bg,
    var(--tag-pill-color, var(--color-accent))) !important;
  color: var(--tag-pill-hover-color, var(--color-primary)) !important;
  transform: translateY(-2px) !important;
}

/* Size. Each modifier only sets --tag-pill-size — the one knob the whole pill is
   built around: the padding is in em, so it tracks the text, and the capsule
   radius is a flat 999px, so it stays a true capsule at any size. Set on the
   .tag-pills Row it reaches every pill; on a lone .tag-pill it sizes that one.
   For a size between the steps, skip these and set --tag-pill-size directly. */
.tag-pills--sm { --tag-pill-size: 0.6rem; }
.tag-pill--sm  { --tag-pill-size: 0.6rem; }

.tag-pills--lg { --tag-pill-size: 0.9rem; }
.tag-pill--lg  { --tag-pill-size: 0.9rem; }

.tag-pills--xl { --tag-pill-size: 1.1rem; }
.tag-pill--xl  { --tag-pill-size: 1.1rem; }

/* Phones: tighten the capsule so a long tag doesn't push the row off-screen. The
   padding is in em, so it follows the smaller text down on its own. */
@media (max-width: 600px) {
  .tag-pills {
    gap: var(--tag-pill-gap, 0.55rem) !important;
  }

  .tag-pills > *,
  .tag-pill {
    font-size: var(--tag-pill-size, 0.68rem) !important;
    letter-spacing: 0.12em !important;
  }
}

/* =============================================
   GALLERY SLIDER  —  block style (Gallery ▸ Styles ▸ "Slider")

   Turns a plain core Gallery block into an auto-sliding, snapping carousel:
   one photo at a time, arrows + dots below, autoplay that pauses whenever
   anyone is actually looking at it. Registered in functions.php; the arrows,
   the dots and the timer are built by js/slider.js.

   Why a block style and not a pattern or a plugin: the client keeps the normal
   Gallery editing experience — drop images in, drag to reorder, delete one —
   and the slider is a checkbox on top of it. Nothing to install, nothing new to
   learn, and the photos stay in the media library instead of being baked into
   markup.

   The track is a real scroll container (scroll-snap), NOT a JS-driven filmstrip.
   So with JS off it is still a usable gallery — swipe on a phone, flick on a
   trackpad, arrow keys when focused — it just loses the controls and the
   autoplay. It also means the browser owns the physics, so a drag feels like a
   drag rather than like an animation someone wrote.

   Tunable — set these in :root (site-wide), or on any block that WRAPS the
   gallery (one section). Defaults in brackets:
     --slider-interval        autoplay speed. "6s", "6000ms" and a bare "6" all
                              mean six seconds. 0 = no autoplay. [5s — the default
                              lives in slider.js, which reads this]
     --slider-ratio           slide aspect ratio [1 / 1 — square]
     --slider-peek            how much of the neighbouring slides shows [0]
     --slider-gap             space between slides [1rem]
     --slider-radius          corner rounding on a slide [--border-radius]
     --slider-nav-space       gap between the photos and the controls [1.6rem]
     --slider-arrow-*         size / color / border / hover of the two arrows
     --slider-dot-*           size / color / active color + width of the dots

   NOT on the gallery itself, for the control variables (--slider-arrow-*,
   --slider-dot-*, --slider-nav-space): slider.js puts the arrows and dots in a
   .slider-nav that is a SIBLING of the gallery, not a child, so a variable set on
   the gallery reaches the photos and never reaches the controls. Set it one level
   up — on :root, or on the Group that wraps the section — and it reaches both.

   Every value below is read with a var() FALLBACK rather than declared on the
   element, which is what makes any of that work: declaring the defaults on
   .slider-nav (or on the gallery) would look equivalent, and would silently
   swallow every override set further up the tree — an element's own declaration
   beats an inherited value. Same reason the tag-pill rules are written this way.

   Modifiers (Additional CSS classes on the same Gallery block):
     slider--peek       show a sliver of the next photo at each edge
     slider--landscape  3/2 slides        slider--wide      16/9 slides
     slider--portrait   4/5 slides        slider--auto      each slide keeps its
                                                            own natural ratio
   ============================================= */
.wp-block-gallery.is-style-slider {
  /* offsetLeft in slider.js is measured against the track, so the track has to be
     the slides' offsetParent. Without this the maths silently measures from
     whatever positioned ancestor happens to be up the tree, and the snap lands
     off-center. */
  position: relative;

  /* Core lays a nested-image gallery out as a WRAPPING flex grid and sizes each
     figure with flex-basis, so each of these has to out-shout a generated or
     inline core rule — the theme's usual !important convention. */
  display: flex !important;
  flex-wrap: nowrap !important;
  align-items: stretch !important;
  gap: var(--slider-gap, 1rem) !important;
  padding-inline: var(--slider-peek, 0px) !important;

  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  overscroll-behavior-x: contain;   /* a flick past the last photo must not hand
                                       the scroll on and navigate the page */
  -webkit-overflow-scrolling: touch;

  /* The scrollbar is redundant once there are dots, and on desktop it draws a
     grey bar across the bottom of the photo. Hidden in both engines; the track
     still scrolls. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.wp-block-gallery.is-style-slider::-webkit-scrollbar {
  display: none;
}

/* The keyboard focus ring belongs on the track (it's a scroll stop — see
   slider.js), but it has to clear the photos rather than draw on top of them. */
.wp-block-gallery.is-style-slider:focus-visible {
  outline: 2px solid var(--color-accent-deep);
  outline-offset: 4px;
}

.wp-block-gallery.is-style-slider > .wp-block-image {
  /* 0 0 = never grow, never shrink: a slide is exactly one viewport of the track,
     so "one photo at a time" holds no matter how many photos there are.

     100%, NOT calc(100% - 2 * peek): a percentage flex-basis resolves against the
     flex container's CONTENT box, which --slider-peek has already narrowed via the
     track's padding-inline. Subtracting the peek here as well double-counts it —
     the slide comes out 2 × peek too narrow and no longer centers under the snap
     point. The padding does the whole job; the slide just fills what's left. */
  flex: 0 0 100% !important;
  min-width: 0 !important;
  margin: 0 !important;
  scroll-snap-align: center;
  scroll-snap-stop: always;   /* one fast swipe = one photo, not a blur of four */
}

.wp-block-gallery.is-style-slider > .wp-block-image img {
  width: 100% !important;
  height: 100% !important;
  aspect-ratio: var(--slider-ratio, 1 / 1);
  object-fit: cover;
  border-radius: var(--slider-radius, var(--border-radius));
  display: block;
}

/* --- Aspect-ratio modifiers ------------------------------------------------- */
.wp-block-gallery.is-style-slider.slider--landscape { --slider-ratio: 3 / 2; }
.wp-block-gallery.is-style-slider.slider--wide      { --slider-ratio: 16 / 9; }
.wp-block-gallery.is-style-slider.slider--portrait  { --slider-ratio: 4 / 5; }

/* Let each photo keep its own shape. The track then takes its height from the
   TALLEST slide, so a mixed-orientation gallery leaves dead space beside the
   landscape ones — which is exactly why a fixed ratio is the default. */
.wp-block-gallery.is-style-slider.slider--auto > .wp-block-image img {
  aspect-ratio: auto;
  height: auto !important;
  object-fit: contain;
}

/* Show the edges of the neighbours, so the gallery reads as a strip that carries
   on rather than as a single framed photo. Fluid, so the sliver doesn't eat a
   phone screen. */
.wp-block-gallery.is-style-slider.slider--peek {
  --slider-peek: clamp(1.25rem, 6vw, 5rem);
}

/* --- Captions --------------------------------------------------------------- */
/* The Gallery block has TWO kinds of caption, and the slider treats them
   differently. The per-image one belongs ON the photo and travels with it. The
   gallery-level one (a direct child of the gallery) is a caption for the whole
   set, and in a nowrap track it would ride along at the end as a phantom slide —
   so slider.js moves it out, below the controls, and tags it .slider-caption.

   Hidden while it is still inside the track: that only happens before slider.js
   has moved it (or if JS never runs at all), and a stray panel of text scrolling
   in after the last photo reads as a bug. Once it's out of the track this rule
   stops matching and the caption shows. */
.wp-block-gallery.is-style-slider > figcaption {
  display: none;
}

/* Where that caption lands: under the arrows and dots, centered on them. Core's
   centering is scoped inside the gallery, so it doesn't survive the move — this
   restates it in the theme's voice rather than relying on core's rule. */
.slider-caption {
  color: var(--color-muted);
  font-family: var(--font-body);
  font-size: 0.9rem;
  margin-top: 0.9rem;
  text-align: center;
}

/* Core pins an image's caption over the bottom of the photo with a dark scrim.
   Keep the position; restate it in the theme's voice. */
.wp-block-gallery.is-style-slider > .wp-block-image figcaption {
  background: linear-gradient(to top, rgba(0, 0, 0, 0.65), transparent) !important;
  border-radius: 0 0 var(--slider-radius, var(--border-radius)) var(--slider-radius, var(--border-radius));
  color: var(--color-text-inverse);
  font-family: var(--font-body);
  font-size: 0.9rem;
  letter-spacing: 0.02em;
  padding: 2.4rem 1.2rem 1rem !important;
}

/* --- Controls (built by js/slider.js) --------------------------------------- */
/* Every var() here carries its default as a FALLBACK — nothing is declared on
   .slider-nav itself. See the header: the controls are a sibling of the gallery,
   so overrides arrive from further up the tree (:root, or the wrapping Group),
   and a declaration here would beat them all. */
.slider-nav {
  align-items: center;
  display: flex;
  gap: 1.25rem;
  justify-content: center;
  margin-top: var(--slider-nav-space, 1.6rem);
}

.slider-arrow {
  align-items: center;
  background: var(--slider-arrow-bg, transparent);
  border: 1px solid
    var(--slider-arrow-border,
      color-mix(in srgb, var(--color-primary) 25%, transparent));
  border-radius: 50%;
  color: var(--slider-arrow-color, var(--color-primary));
  cursor: pointer;
  display: flex;
  flex: 0 0 auto;
  height: var(--slider-arrow-size, 44px);
  justify-content: center;
  padding: 0;
  transition: background var(--transition), color var(--transition), border-color var(--transition);
  width: var(--slider-arrow-size, 44px);
}

.slider-arrow svg {
  height: 45%;
  width: 45%;
}

/* One chevron, drawn pointing left and mirrored for "next" — so the pair can
   never drift apart the way two hand-drawn glyphs would. */
.slider-arrow--next svg {
  transform: scaleX(-1);
}

.slider-arrow:hover,
.slider-arrow:focus-visible {
  background: var(--slider-arrow-hover-bg, var(--color-primary));
  border-color: var(--slider-arrow-hover-bg, var(--color-primary));
  color: var(--slider-arrow-hover-color, var(--color-text-inverse));
}

.slider-dots {
  align-items: center;
  display: flex;
  gap: 0.5rem;
}

.slider-dot {
  background: var(--slider-dot-color,
    color-mix(in srgb, var(--color-primary) 22%, transparent));
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  height: var(--slider-dot-size, 8px);
  padding: 0;
  transition: background var(--transition), width var(--transition);
  width: var(--slider-dot-size, 8px);
}

/* The active dot stretches into a pill rather than only changing color, so the
   position in the gallery still reads without relying on color alone.

   --slider-dot-active is the "you are here" color, and it's the one a site is
   most likely to want to change. Defaults to accent-deep — the theme's direction
   & emphasis slot, the same one the arrows and the glow falloff draw on. */
.slider-dot.is-active {
  background: var(--slider-dot-active, var(--color-accent-deep));
  width: var(--slider-dot-active-width, 30px);
}

/* Hover is derived from the active color rather than being its own slot, so a
   site that recolors the dot gets a matching hover for free. */
.slider-dot:hover {
  background: color-mix(in srgb,
    var(--slider-dot-active, var(--color-accent-deep)) 55%, transparent);
}

/* Phones: shrink the controls so the arrows and dots stay on one line. The
   smaller size is a new FALLBACK, not a declaration of --slider-arrow-size — a
   site that sets its own arrow size still wins here, instead of having it
   silently overruled below 600px. */
@media (max-width: 600px) {
  .slider-nav {
    gap: 0.9rem;
  }

  .slider-arrow {
    height: var(--slider-arrow-size, 38px);
    width: var(--slider-arrow-size, 38px);
  }
}

/* Someone who asked for less motion gets no autoplay (enforced in slider.js) and
   no smooth glide — the arrows and dots cut straight to the photo. */
@media (prefers-reduced-motion: reduce) {
  .wp-block-gallery.is-style-slider {
    scroll-behavior: auto;
  }

  .slider-dot {
    transition: background var(--transition);
  }
}

/* --- Inside the editor ------------------------------------------------------ */
/* style.css is loaded into the editor too (add_editor_style), but slider.js is
   NOT — so in the editor a slider gallery would be a scroll track with no arrows
   and no dots: a gallery with all but one photo hidden off the edge of a box the
   client can't scroll while dragging a thumbnail around. Unwind the track back
   into a wrapping grid there. Every photo stays visible and draggable, and the
   square crop still previews how each one will be cut. The front end is the
   slider; the editor is the contact sheet. */
.editor-styles-wrapper .wp-block-gallery.is-style-slider {
  flex-wrap: wrap !important;
  overflow: visible !important;
  padding-inline: 0 !important;
  scroll-snap-type: none !important;
}

.editor-styles-wrapper .wp-block-gallery.is-style-slider > .wp-block-image {
  flex: 1 1 calc(50% - var(--slider-gap)) !important;
}

/* The gallery-level caption is hidden while it sits inside the track (see
   Captions above) — but in the editor slider.js never runs to move it out, so
   that rule would leave the client typing into an invisible box. Put it back. */
.editor-styles-wrapper .wp-block-gallery.is-style-slider > figcaption {
  display: block;
  flex-basis: 100%;
  text-align: center;
}

/* =============================================
   IMAGE SHAPES  —  curved / arched photo frames   (reusable)

   Rounds a photo into a soft editorial shape: an arch (domed top, gentle bottom —
   the tall portrait look), a single deep off-kilter corner, an organic blob, or a
   leaf. Each is registered as an Image ▸ Style — pick it from the block's Styles
   panel, no typing — AND doubles as a typed .img-* class, so the same shape works
   on a Cover or a Group where there's no Styles entry.

   Apply to:
     • Image block   → Styles panel ▸ pick a shape (Arch, Curve …, Blob, Leaf),
                       or type its .img-* alias in Advanced ▸ Additional CSS class(es).
     • Cover / Group → type the .img-* alias (Cover/Group have no Styles entry).

   On a plain Image the curve rides on the <img>, so a caption underneath stays
   square. On a Cover the whole block clips, so the background photo/video and any
   overlay all take the shape. A shape reads best on a photo that FILLS its box, so
   pair it with .img-cover (below) + a portrait ratio for the tall arch — otherwise
   the arch's dome flattens out on a wide image.

   Registered in functions.php (onyx_register_image_styles). Component-scoped photo
   treatments keep their own sections: PHOTO BADGE, FRAMED PHOTOS, PHOTO STACK,
   FEATURE CIRCLES, MEDIA CARD — this block is only the generic, reusable shapes.

   Tunable (set on the block, or globally in :root):
     --img-curve-strong   the deep "funky" corner                default 42%
     --img-curve-soft     the gentle corners on the other edges     default 14px
     --img-round          the all-around radius of "Soft Corners"   default 24px
   ============================================= */

/* Shape table — each class only declares its corner radius as --img-radius.
   border-radius corner order is top-left, top-right, bottom-right, bottom-left. */
.wp-block-image.is-style-arch,     .img-arch      { --img-radius: 50% 50% var(--img-curve-soft, 14px) var(--img-curve-soft, 14px) / 42% 42% var(--img-curve-soft, 14px) var(--img-curve-soft, 14px); }
.wp-block-image.is-style-soft,     .img-soft      { --img-radius: var(--img-round, 24px); }
.wp-block-image.is-style-curve-tl, .img-curve-tl  { --img-radius: var(--img-curve-strong, 42%) var(--img-curve-soft, 14px) var(--img-curve-soft, 14px) var(--img-curve-soft, 14px); }
.wp-block-image.is-style-curve-tr, .img-curve-tr  { --img-radius: var(--img-curve-soft, 14px) var(--img-curve-strong, 42%) var(--img-curve-soft, 14px) var(--img-curve-soft, 14px); }
.wp-block-image.is-style-curve-br, .img-curve-br  { --img-radius: var(--img-curve-soft, 14px) var(--img-curve-soft, 14px) var(--img-curve-strong, 42%) var(--img-curve-soft, 14px); }
.wp-block-image.is-style-curve-bl, .img-curve-bl  { --img-radius: var(--img-curve-soft, 14px) var(--img-curve-soft, 14px) var(--img-curve-soft, 14px) var(--img-curve-strong, 42%); }
.wp-block-image.is-style-leaf,     .img-leaf      { --img-radius: var(--img-curve-strong, 42%) var(--img-curve-soft, 14px) var(--img-curve-strong, 42%) var(--img-curve-soft, 14px); }
.wp-block-image.is-style-blob,     .img-blob      { --img-radius: 30% 70% 70% 30% / 30% 30% 70% 70%; }
.wp-block-image.is-style-blob-alt, .img-blob-alt  { --img-radius: 41% 59% 43% 57% / 63% 45% 55% 37%; }

/* Apply the chosen shape to the photo. --img-radius is declared ONLY on the shape
   classes above, so this enumerated selector can never reach an unshaped image or
   override core's own Image styles (Default / Rounded). The <img> inherits
   --img-radius from its shape wrapper (custom properties inherit), so this reaches
   a bare Image (figure > img), a linked one (figure > a > img), and an image
   nested inside a Group/Cover carrying an .img-* alias. */
:is(
  .is-style-arch, .is-style-soft, .is-style-curve-tl, .is-style-curve-tr,
  .is-style-curve-br, .is-style-curve-bl, .is-style-leaf, .is-style-blob, .is-style-blob-alt,
  .img-arch, .img-soft, .img-curve-tl, .img-curve-tr,
  .img-curve-br, .img-curve-bl, .img-leaf, .img-blob, .img-blob-alt
) img {
  border-radius: var(--img-radius);
}

/* On a Cover the alias clips the whole block, so the background photo/video and
   the dim overlay all follow the curve together (Cover is already overflow:hidden;
   the radius is what shapes it). */
.wp-block-cover:is(
  .img-arch, .img-soft, .img-curve-tl, .img-curve-tr,
  .img-curve-br, .img-curve-bl, .img-leaf, .img-blob, .img-blob-alt
) {
  border-radius: var(--img-radius);
  overflow: hidden;
}

/* =============================================
   COVER PHOTO  —  .img-cover: fill an area edge to edge   (reusable)

   By default a WordPress Image sits at its own natural size and stops there — a
   small file leaves a gap, and a wide-and-thin one won't crop to fit a box. This
   forces the picture to COVER its area instead: it stretches to the full width
   AND height of its block, scaling UP if the file is too small and cropping the
   overflow so it always fills edge to edge, whatever the shape of the box. Same
   idea as a Cover block's background image, but on a plain Image you can drop
   anywhere.

   Apply to:  an Image block. (Also works on a Group/Column wrapping one image —
              the rule reaches the <img> inside either way.)

   HOW IT GETS ITS HEIGHT. object-fit needs a box to fill, and an Image block has
   no height of its own, so pick one:
     • Give the block a shape with a ratio modifier below (the usual choice) — the
       box becomes, say, a wide band and the photo crops into it.
     • Or drop the image into a container that already has a height (a Column set
       to stretch beside a taller sibling, a fixed-height Group) — with no ratio
       set, the photo simply fills whatever height it's given.
   With neither, there's no area to cover and the image just shows at full width,
   uncropped — harmless, but not what you're after.

   Modifiers — add alongside the base class to set the shape of the box:
     .img-cover--wide      21:9 cinematic band (the wide, thin strip)
     .img-cover--banner    a shallower 3:1 letterbox, for a header strip
     .img-cover--landscape 3:2, a classic photo shape
     .img-cover--square    1:1
     .img-cover--portrait  4:5, taller than wide

   Tunable (set on the block, or globally in :root):
     --img-cover-ratio   width ÷ height of the box   default: auto (fill parent)
     --img-cover-pos     which part to keep when cropping   default: center
                         (e.g. "top", "50% 30%" — the focal point that survives) */
.img-cover {
  /* No --img-cover-* defaults declared here on purpose — each is read with an inline
     var() fallback (aspect-ratio and object-position below), so a :root or block-level
     override reaches through instead of being swallowed by a declaration on the class:
     a value declared on the element beats the same variable INHERITED from :root, even
     an !important one, so declaring the defaults here would silently ignore an override. */
  display: block;
  width: 100%;
  /* When a ratio is set, this drives the box shape and the photo crops into it.
     When it's auto (the default), the block keeps its natural/stretched height
     and this does nothing — the two paths above. */
  aspect-ratio: var(--img-cover-ratio, auto);
  /* Fill a parent that carries its own height (a stretched column, a sized Group)
     in the no-ratio case. Against an auto-height parent, 100% resolves to auto, so
     it's a no-op there rather than a collapse. */
  height: 100%;
  /* No overflow:hidden here on purpose. object-fit already contains the crop to the
     img's own box, so there's nothing to clip, and clipping the figure would only
     risk eating a shadow or outline set on the image. */
}

/* The picture itself fills the box on both axes; object-fit does the covering,
   scaling a too-small file up and cropping whatever runs past the edges. Works on
   a bare Image (figure > img) and on a linked one (figure > a > img). */
.img-cover img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--img-cover-pos, center);
}

.img-cover--wide      { --img-cover-ratio: 21 / 9; }
.img-cover--banner    { --img-cover-ratio: 3 / 1; }
.img-cover--landscape { --img-cover-ratio: 3 / 2; }
.img-cover--square    { --img-cover-ratio: 1 / 1; }
.img-cover--portrait  { --img-cover-ratio: 4 / 5; }

/* --- .video-cover — the same cover-crop for a Video block ---------------------
   The video-block twin of .img-cover. Forces a Video to FILL the full width and
   height of its box, scaling up if the file is small and cropping the overflow,
   so a wide clip drops cleanly into a shallow band (or any other shape) instead
   of showing letterboxed at its native proportions. Use it to reshape a video
   the way you'd reshape a photo — the source file is never re-encoded, just
   cropped by the box it sits in.

   Apply to:  a Video block (Additional CSS class(es) on the block).

   HOW IT GETS ITS HEIGHT. Same as .img-cover — object-fit needs a box to fill:
     • Give the block a shape with a ratio modifier below (the usual choice), or
     • Drop it in a container that already has a height (a stretched Column, a
       sized Group), where it fills whatever height it's given.
   With neither, there's no area to cover and the video shows at full width,
   uncropped.

   For a seamless background-style loop, pair this class with the Video block's
   own toggles (block sidebar): Autoplay on, Loop on, Muted on, Controls off,
   Play inline on. Muted is what lets Autoplay run at all in modern browsers.

   Modifiers — add alongside the base class to set the shape of the box:
     .video-cover--wide      21:9 cinematic band
     .video-cover--banner    a shallower 3:1 letterbox strip
     .video-cover--landscape 16:9, standard video
     .video-cover--square    1:1
     .video-cover--portrait  9:16, a vertical / reel shape

   Tunable (set on the block, or globally in :root):
     --video-cover-ratio   width ÷ height of the box   default: auto (fill parent)
     --video-cover-pos     which part to keep when cropping   default: center */
.video-cover {
  /* Mirrors .img-cover: no --video-cover-* defaults declared here on purpose, so
     a :root or block-level override reaches through the inline var() fallbacks
     below instead of being swallowed by a declaration on the class. */
  display: block;
  width: 100%;
  aspect-ratio: var(--video-cover-ratio, auto);
  height: 100%;
  /* The box is a hard frame here (unlike .img-cover): clip the cropped video so
     it can't spill past the band, and hide the seam of a too-tall native player. */
  overflow: hidden;
}

/* The <video> fills the box on both axes; object-fit does the covering. */
.video-cover video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--video-cover-pos, center);
}

.video-cover--wide      { --video-cover-ratio: 21 / 9; }
.video-cover--banner    { --video-cover-ratio: 3 / 1; }
.video-cover--landscape { --video-cover-ratio: 16 / 9; }
.video-cover--square    { --video-cover-ratio: 1 / 1; }
.video-cover--portrait  { --video-cover-ratio: 9 / 16; }

/* =============================================
   MENU LIST — lives in the ixiun-menus plugin, not here.

   The priced-menu component (.menu-list*, for the Menu block and [ixiun_menu]
   shortcode) ships its own stylesheet inside the plugin, because those classes
   are only ever emitted by the plugin — styling them here would leave dead CSS
   on any site without it. The plugin's CSS reads this theme's design tokens
   (--color-accent, --font-heading, --font-script, …) with fallbacks, so the menu
   still inherits the site's palette and type; restyle it in Global Styles like
   any other component. See the ixiun-menus readme, and §10 of this README.
   ============================================= */

/* =============================================
   BLOCK LINK  —  make a whole block one big link   (reusable)

   Core Gutenberg links a Button, an Image, or a word — but not a whole Group.
   This turns any container block (Group, Row, Columns, Stack) into a single
   clickable link with the "stretched link" pattern: one real <a> inside is
   expanded to cover the whole block, so the entire card follows the link while
   staying a proper, focusable, right-click/open-in-new-tab link — no JavaScript.

   Editor recipe:
     1. Container block  →  Advanced ▸ Additional CSS class(es)  →  block-link
     2. Inside it, give ONE block a link and add the class  block-link__target
        to that block — a Heading (link its text), a Button, or an Image.
     3. Point that link anywhere, e.g. /menus/#wines.

   The whole container now follows that link. Any OTHER link/button/control inside
   that must stay independently clickable gets .block-link__above (it rides over
   the invisible overlay). Plain text under the overlay isn't selectable — the one
   trade-off of the pattern.
   ============================================= */
.block-link {
  position: relative;
  cursor: pointer;
  transition: transform 0.2s ease;
}

/* The hit area: the target block's own <a>, stretched over the whole container.
   The <a> is forced static so the overlay resolves against .block-link (the
   nearest positioned ancestor), not against a link some other rule positioned. */
.block-link .block-link__target a {
  position: static;
}
.block-link .block-link__target a::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* A subtle default affordance: lift the card slightly on hover/focus. Purely
   cosmetic — override or drop it per site. Held still under reduced motion. */
@media (prefers-reduced-motion: no-preference) {
  .block-link:hover,
  .block-link:focus-within {
    transform: translateY(-3px);
  }
}

/* Anything that must stay clickable in its own right (a second link, a button,
   a form field) rides above the overlay. */
.block-link__above {
  position: relative;
  z-index: 2;
}

/* ---------------------------------------------------------------------------
   Drop cap
   Restyles WordPress's built-in paragraph drop cap (the ⋮ Typography → "Drop
   cap" toggle, which adds .has-drop-cap) into an oversized decorative serif
   first letter in the theme accent color. Generic — reusable on any site;
   enable it per-paragraph from the block editor. Overrides the plain default
   from wp-includes/blocks/paragraph/style.css.
   --------------------------------------------------------------------------- */
.has-drop-cap:not(:focus)::first-letter {
  font-family: 'Cormorant Garamond', Georgia, serif;
  color: var(--color-accent-deep);
  font-size: 5.4em;
  line-height: 0.72;
  font-weight: 500;
  margin: 0.05em 0.12em 0 0;
  text-transform: none;
  font-style: normal;
}

/* =============================================
   ACCENT FRAME  —  a soft lit edge around any block   (reusable)

   Frames a container block in warm accent light rather than a hard border: the
   top edge catches the most light and it falls away down the sides and across the
   bottom, wrapped in a soft outer halo — so the block reads as lit from above, not
   outlined. It's built entirely from layered box-shadows (no real border), so it
   adds no layout width and never squares off the corners. Optionally the top of
   the edge parts around a centered ✦ sparkle clasp — the theme's list-marker
   glyph (\2726) — with a fine bar fading up to it from each side.

   Generic and reusable — nothing here is Soiree-specific. Enable it per block:

   Editor recipe:
     Container block (Group / Row / Columns) →
       Advanced ▸ Additional CSS class(es) →  accent-frame
     Add  accent-frame--diamond  for the sparkle clasp + fading bars.
     Add  accent-frame--top      to keep only the lit top edge (no side/bottom frame).
     Add  accent-frame--pulse    to make the light breathe like candlelight.

   Give the block its own inner Padding in the editor so content clears the edge,
   and leave the block's Overflow at "visible" — "hidden" would clip the clasp
   and the outer halo.

   Tunable (set on the block, or globally in :root):
     --accent-frame-color      edge + clasp color        default: --color-accent-bold
     --accent-frame-radius     corner radius             default: --border-radius
     --accent-frame-glow       outer halo spread         default: 26px
     --accent-frame-ornament   sparkle size              default: 18px
     --accent-frame-gap        clear space each side of the sparkle   default: 12px
     --accent-frame-bar        how far back the edge starts brightening toward
                               the gap (the "leading up" length)      default: 34px
   ============================================= */
.accent-frame {
  --_af-color: var(--accent-frame-color, var(--color-accent-bold));
  --_af-glow:  var(--accent-frame-glow, 26px);
  position: relative;
  border-radius: var(--accent-frame-radius, var(--border-radius));
  /* The lit edge, brightest at the top and fading around, as stacked shadows:
       1  a faint hairline on all four sides, so the frame reads all the way round
       2  a brighter top edge — where the light catches
       3  a warm wash hugging the inside of the top edge, fading downward
       4  a soft outer halo                                                       */
  box-shadow:
    inset 0 0 0 1px   color-mix(in srgb, var(--_af-color) 14%, transparent),
    inset 0 1px 0 0    color-mix(in srgb, var(--_af-color) 48%, transparent),
    inset 0 18px 34px -24px color-mix(in srgb, var(--_af-color) 34%, transparent),
    0 0 var(--_af-glow) -6px color-mix(in srgb, var(--_af-color) 22%, transparent);
}

/* Diamond clasp — the top edge runs as a hairline that brightens as it nears the
   center, PARTS into a clean gap, and a centered ✦ sparkle (the theme's list-marker
   glyph, \2726) sits in the gap. The whole thing is a gradient line (::before) plus
   the glyph (::after): the gap is real transparency in the gradient, so nothing has
   to be color-matched to the block — no background chip, no pale patch. */

/* Drop the base's straight top-edge highlight (shadow layer 2); ::before redraws
   that line WITH the gap. The other three layers (hairline outline, warm wash,
   outer halo) stay, so the frame looks the same everywhere else. */
.accent-frame--diamond {
  box-shadow:
    inset 0 0 0 1px         color-mix(in srgb, var(--_af-color) 14%, transparent),
    inset 0 18px 34px -24px color-mix(in srgb, var(--_af-color) 34%, transparent),
    0 0 var(--_af-glow) -6px color-mix(in srgb, var(--_af-color) 22%, transparent);
}

/* The lit top edge with the break. --_af-half is the half-width of the clear gap
   (so the sparkle stands alone); --_af-lead is how far back the brightening starts,
   so the line swells gently toward the gap and dissolves into it — no hard tip. */
.accent-frame--diamond::before {
  --_af-half: calc(var(--accent-frame-ornament, 18px) / 2 + var(--accent-frame-gap, 12px));
  --_af-lead: calc(var(--_af-half) + var(--accent-frame-bar, 34px));
  content: '';
  position: absolute;
  top: 0;
  left:  var(--accent-frame-radius, var(--border-radius));   /* start past the corner curve */
  right: var(--accent-frame-radius, var(--border-radius));
  height: 1.5px;
  background: linear-gradient(to right,
    color-mix(in srgb, var(--_af-color) 42%, transparent) 0,
    color-mix(in srgb, var(--_af-color) 42%, transparent) calc(50% - var(--_af-lead)),
    color-mix(in srgb, var(--_af-color) 88%, transparent) calc(50% - var(--_af-half) - 12px),
    transparent calc(50% - var(--_af-half)),                 /* fade into the gap  */
    transparent calc(50% + var(--_af-half)),                 /* gap, then mirror   */
    color-mix(in srgb, var(--_af-color) 88%, transparent) calc(50% + var(--_af-half) + 12px),
    color-mix(in srgb, var(--_af-color) 42%, transparent) calc(50% + var(--_af-lead)),
    color-mix(in srgb, var(--_af-color) 42%, transparent) 100%);
}

/* The sparkle, centered in the gap and straddling the top edge. */
.accent-frame--diamond::after {
  content: '\2726';                     /* ✦ — the theme's gold sparkle marker */
  position: absolute;
  top: 0;
  left: 50%;
  translate: -50% -50%;
  color: var(--_af-color);
  font-size: var(--accent-frame-ornament, 18px);
  line-height: 1;
  text-shadow: 0 0 8px color-mix(in srgb, var(--_af-color) 45%, transparent);
}

/* Top-only — keep just the lit top edge and drop the side + bottom frame, so the
   accent reads as a single lit line across the top rather than a full box. Use
   alone for a straight lit top edge, or add alongside --diamond for the clasp with
   no surrounding frame. The compound selector below outranks both single-class
   box-shadow rules, so class order in the editor doesn't matter. */
/* NOTE the wash uses spread (-40px) LARGER than its blur (38px). The base frame's
   wash (inset 0 18px 34px -24px) has blur > spread, so ~10px of it bleeds onto the
   left/right edges — normally hidden under the base's 4-side hairline. With the
   hairline dropped here, that bleed would show as faint side lines, so the spread is
   widened to clip the wash strictly to the top. */
.accent-frame--top {
  box-shadow:
    inset 0 1px 0 0         color-mix(in srgb, var(--_af-color) 48%, transparent),
    inset 0 20px 38px -40px color-mix(in srgb, var(--_af-color) 34%, transparent);
}
/* With the clasp: ::before redraws the broken top edge, so keep only the warm wash. */
.accent-frame--diamond.accent-frame--top {
  box-shadow:
    inset 0 20px 38px -40px color-mix(in srgb, var(--_af-color) 34%, transparent);
}

/* Optional: let the lit edge breathe like candlelight — the frame-scale sibling
   of .hero-cinematic--glow / .nav-logo--mark-glow. Only the warm inner wash and
   the outer halo pulse; the hairline and top edge hold steady so the frame keeps
   its shape. Held still for anyone browsing with reduced motion on. */
@media (prefers-reduced-motion: no-preference) {
  .accent-frame--pulse {
    animation: onyx-accent-frame-glow var(--accent-frame-pulse-speed, 4.5s) ease-in-out infinite;
  }
}

@keyframes onyx-accent-frame-glow {
  0%, 100% {
    box-shadow:
      inset 0 0 0 1px   color-mix(in srgb, var(--_af-color) 14%, transparent),
      inset 0 1px 0 0    color-mix(in srgb, var(--_af-color) 48%, transparent),
      inset 0 16px 30px -24px color-mix(in srgb, var(--_af-color) 26%, transparent),
      0 0 calc(var(--_af-glow) * 0.7) -6px color-mix(in srgb, var(--_af-color) 16%, transparent);
  }
  50% {
    box-shadow:
      inset 0 0 0 1px   color-mix(in srgb, var(--_af-color) 14%, transparent),
      inset 0 1px 0 0    color-mix(in srgb, var(--_af-color) 48%, transparent),
      inset 0 20px 38px -22px color-mix(in srgb, var(--_af-color) 42%, transparent),
      0 0 calc(var(--_af-glow) * 1.4) -6px color-mix(in srgb, var(--_af-color) 30%, transparent);
  }
}

/* =============================================
   FORM BODY LABELS  —  calmer ixiun-forms field labels   (reusable, opt-in)
   ---------------------------------------------
   The ixiun-forms plugin styles its field labels as a tracked-out editorial
   eyebrow (Nunito Sans, bold, 0.12em spacing). This opt-in helper swaps that for
   the theme's calm reading treatment — the same size and rhythm as .content-body
   (the body font, normal weight, no tracking) — for forms that want their labels
   to read as quiet body text rather than shout.

   Off by default, so every other site's forms are untouched. Add the class to the
   ixiun-forms block, OR to any block wrapping it (the descendant selector catches
   both). Requires the ixiun-forms plugin; harmless anywhere it's absent.

   Editor recipe:
     The Form block (or its container) → Advanced ▸ Additional CSS class(es) →
       form-labels-body

   NOTE ON CASE: the plugin does not force uppercase — a label typed "YOUR NAME"
   renders in caps because that's how it's authored. To get the full body look,
   also retype the field labels in sentence case ("Your name") in the form editor;
   CSS can't safely lowercase authored text without breaking names like "soirée".
   ============================================= */
.form-labels-body .ixiun-form__label {
  font-family: var(--font-body, inherit) !important;
  font-size: 1.15rem !important;          /* matches .content-body */
  font-weight: 400 !important;            /* plugin sets 700; force it back down */
  letter-spacing: 0 !important;
  line-height: 1.7;
  text-transform: none;
  /* Light text on a near-black form reads a weight heavier than it is; smoothing
     pulls it back to a true 400 instead of the "faux-bold" halo. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}


/* =============================================
   SCROLL REVEAL  (.scroll-reveal)
   ---------------------------------------------
   A drop-in entrance animation for any block: it
   starts a little below its resting spot, faded
   out, then slides up into place. Blocks already
   on screen at load animate in on first paint;
   blocks further down animate as you scroll to
   them — both driven by js/reveal.js, which adds
   .is-revealed once a block enters the viewport.

   Apply to ANY block (Advanced ▸ Additional CSS
   class(es) → scroll-reveal). Nothing is expected
   inside — it animates the block it's put on.

   Hidden state is gated on .onyx-js, a flag set in
   the document head before first paint. With JS
   off the flag never appears, so every block stays
   visible — the animation is pure enhancement.

   Modifiers (add alongside scroll-reveal):
     --down    starts above, drops down into place
     --left    starts to the right, slides left
     --right   starts to the left, slides right
     --fade    fade only, no movement
     --zoom    starts slightly smaller, scales up
     --fast / --slow   shorter / longer travel time

   Tune per-block (Styles ▸ Additional CSS on the
   block, or globally in :root):
     --reveal-distance  how far it travels (2.5rem)
     --reveal-duration  how long it takes (0.8s)
     --reveal-delay     wait before it starts (0s)
     --reveal-ease      the easing curve

   Stagger a set: put scroll-reveal-stagger on a
   parent and scroll-reveal on each child — the
   children cascade in, one after the next.
   ============================================= */
.onyx-js .scroll-reveal {
  opacity: 0;
  transform: translateY(var(--reveal-distance, 2.5rem));
  transition:
    opacity   var(--reveal-duration, 0.8s) var(--reveal-ease, cubic-bezier(0.22, 0.61, 0.36, 1)) var(--reveal-delay, 0s),
    transform var(--reveal-duration, 0.8s) var(--reveal-ease, cubic-bezier(0.22, 0.61, 0.36, 1)) var(--reveal-delay, 0s);
  will-change: opacity, transform;
}

.onyx-js .scroll-reveal.is-revealed {
  opacity: 1;
  transform: none;
}

/* Direction — each just changes where the block starts from. */
.onyx-js .scroll-reveal--down  { transform: translateY(calc(-1 * var(--reveal-distance, 2.5rem))); }
.onyx-js .scroll-reveal--left  { transform: translateX(var(--reveal-distance, 2.5rem)); }
.onyx-js .scroll-reveal--right { transform: translateX(calc(-1 * var(--reveal-distance, 2.5rem))); }
.onyx-js .scroll-reveal--fade  { transform: none; }
.onyx-js .scroll-reveal--zoom  { transform: scale(0.92); }

/* Speed presets — a lighter touch or a slower, more deliberate rise. */
.onyx-js .scroll-reveal--fast { --reveal-duration: 0.5s; }
.onyx-js .scroll-reveal--slow { --reveal-duration: 1.2s; }

/* Stagger — direct .scroll-reveal children of a group each read a growing
   delay, so a row or grid cascades in instead of arriving all at once.
   Nine steps, then it holds; a tenth+ child shares the last delay. */
.onyx-js .scroll-reveal-stagger > .scroll-reveal:nth-child(1) { --reveal-delay: 0s; }
.onyx-js .scroll-reveal-stagger > .scroll-reveal:nth-child(2) { --reveal-delay: 0.09s; }
.onyx-js .scroll-reveal-stagger > .scroll-reveal:nth-child(3) { --reveal-delay: 0.18s; }
.onyx-js .scroll-reveal-stagger > .scroll-reveal:nth-child(4) { --reveal-delay: 0.27s; }
.onyx-js .scroll-reveal-stagger > .scroll-reveal:nth-child(5) { --reveal-delay: 0.36s; }
.onyx-js .scroll-reveal-stagger > .scroll-reveal:nth-child(6) { --reveal-delay: 0.45s; }
.onyx-js .scroll-reveal-stagger > .scroll-reveal:nth-child(7) { --reveal-delay: 0.54s; }
.onyx-js .scroll-reveal-stagger > .scroll-reveal:nth-child(8) { --reveal-delay: 0.63s; }
.onyx-js .scroll-reveal-stagger > .scroll-reveal:nth-child(n + 9) { --reveal-delay: 0.72s; }

/* Reduced motion: no travel, no fade-in wait — the block is simply there.
   The observer still runs but the transition is neutralized, so .is-revealed
   changes nothing visible. */
@media (prefers-reduced-motion: reduce) {
  .onyx-js .scroll-reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ── Inline font utilities ──────────────────────────────────────────────
   Change the font family of a run of text *inside* a block — a single word
   in a heading, a phrase in a paragraph — without changing the block's own
   font. The block editor only sets font family per block, so wrap the words
   in a <span> (Options ▸ Edit as HTML) and give the span one of these
   classes. Each just points at a theme font variable, so re-theming the
   fonts in Global Styles re-themes these too.

     <h2>… makes room for <span class="font-script">everyone</span>.</h2>

   font-script  — the accent / script font (Playfair Display by default)
   font-heading — whatever the site's headings are set to
   font-body    — whatever the site's body text is set to
   Generic and reusable; not tied to any one block or site. */
.font-script  { font-family: var(--font-script); }
.font-heading { font-family: var(--font-heading); }
.font-body    { font-family: var(--font-body); }

/* =============================================
   FLOATING DECORATION  (.float-deco)  — reusable, opt-in
   ---------------------------------------------
   A small decorative image — a leaf, a sprig, a doodle, a wax seal — that
   floats *over* the block it sits in, overlapping a dotted panel, a color
   block, a photo or a card to add a soft, hand-placed touch. Purely
   ornamental: it's lifted out of the flow, ignores the mouse (clicks pass
   straight through to whatever is behind it), and carries a slight tilt so
   it reads as "placed", not "aligned".

     Group / Column / Columns / Cover   (the container it floats within)
       └─ Image .float-deco             (the ornament)

   The container that DIRECTLY holds a .float-deco becomes the positioning
   context automatically. If the ornament floats against the wrong box, add
   `deco-layer` to the exact container you want it to sit inside — that is all
   deco-layer does: mark the box the ornament pins to.

   PLACE with a corner/edge modifier; FINE-TUNE with the variables below.

   Position modifiers (add alongside float-deco). Default is --top-right:
     --top-left     --top      --top-right
     --left         --center   --right
     --bottom-left  --bottom   --bottom-right

   Tune (Styles ▸ Additional CSS on the image, or in :root):
     --deco-size    ornament width                          [140px]
     --deco-x       nudge horizontally (+ right / - left)   [0]
     --deco-y       nudge vertically   (+ down  / - up)     [0]
     --deco-rotate  tilt                                    [-6deg]
     --deco-inset   gap from the pinned edge                [8%]
     --deco-z       stacking order                          [3]

   Optional motion: add float-deco--bob for a slow, gentle hover (held still
   for anyone browsing with reduced motion on). Optional float-deco--hide-mobile
   drops the ornament on phones when it would crowd a smaller layout.

   NOTE: a container that clips its overflow (rounded card, Cover, anything with
   overflow:hidden) crops an ornament that pokes past its edge — keep --deco-inset
   positive so it floats inside, or move the ornament to a container that doesn't clip.

   Generic and reusable; not tied to any one block or site.
   ============================================= */
.wp-block-group:has(> .float-deco),
.wp-block-group__inner-container:has(> .float-deco),
.wp-block-column:has(> .float-deco),
.wp-block-columns:has(> .float-deco),
.wp-block-cover:has(> .float-deco),
.wp-block-cover__inner-container:has(> .float-deco),
.deco-layer {
  position: relative !important;
}

.float-deco {
  position: absolute !important;
  z-index: var(--deco-z, 3) !important;
  width: var(--deco-size, 140px) !important;
  max-width: 60% !important;
  height: auto !important;
  margin: 0 !important;
  pointer-events: none !important;         /* purely decorative — never eat a click */
  /* default corner — top-right; a position modifier overrides these */
  top: var(--deco-inset, 8%);
  right: var(--deco-inset, 8%);
  left: auto;
  bottom: auto;
  /* --_deco-tx / --_deco-ty are the centering offsets a modifier switches on */
  --_deco-tx: 0;
  --_deco-ty: 0;
  transform:
    translate(var(--deco-x, 0), var(--deco-y, 0))
    translate(var(--_deco-tx), var(--_deco-ty))
    rotate(var(--deco-rotate, -6deg)) !important;
  transform-origin: center;
}

.float-deco img {
  width: 100% !important;
  height: auto !important;
  display: block !important;
}

/* Placement — each pins the ornament and resets the edges it doesn't use.
   Edge modifiers center along the free axis via the --_deco-t* offsets. */
.float-deco.float-deco--top-left     { top: var(--deco-inset, 8%); left: var(--deco-inset, 8%); right: auto; bottom: auto; }
.float-deco.float-deco--top-right    { top: var(--deco-inset, 8%); right: var(--deco-inset, 8%); left: auto; bottom: auto; }
.float-deco.float-deco--bottom-left  { bottom: var(--deco-inset, 8%); left: var(--deco-inset, 8%); top: auto; right: auto; }
.float-deco.float-deco--bottom-right { bottom: var(--deco-inset, 8%); right: var(--deco-inset, 8%); top: auto; left: auto; }
.float-deco.float-deco--top          { top: var(--deco-inset, 8%); bottom: auto; left: 50%; right: auto; --_deco-tx: -50%; }
.float-deco.float-deco--bottom       { bottom: var(--deco-inset, 8%); top: auto; left: 50%; right: auto; --_deco-tx: -50%; }
.float-deco.float-deco--left         { left: var(--deco-inset, 8%); right: auto; top: 50%; bottom: auto; --_deco-ty: -50%; }
.float-deco.float-deco--right        { right: var(--deco-inset, 8%); left: auto; top: 50%; bottom: auto; --_deco-ty: -50%; }
.float-deco.float-deco--center       { top: 50%; left: 50%; right: auto; bottom: auto; --_deco-tx: -50%; --_deco-ty: -50%; }

/* Phones: a touch smaller and tucked closer to the edge, unless a per-image
   value was set (an inline Additional-CSS value outranks this). */
@media (max-width: 781px) {
  .float-deco { --deco-size: 96px; --deco-inset: 5%; }
  .float-deco--hide-mobile { display: none !important; }
}

/* Optional gentle hover. Uses the `translate` property, which composes with the
   `transform` tilt above instead of replacing it, so the ornament keeps its lean
   while it bobs. Held still for reduced-motion. */
@media (prefers-reduced-motion: no-preference) {
  .float-deco--bob { animation: onyx-deco-bob var(--deco-bob-speed, 5s) ease-in-out infinite; }
}
@keyframes onyx-deco-bob {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -8px; }
}
