/* Hozio Studio combined stylesheet — generated, do not edit. sig b8a026a775f0ab74 */

/* --- _base/a11y.css --- */
/* ─── Global accessibility safety net — a11y.css ────────────────────────
   Sanctioned SPEC §3.7 accessibility exception to the §2 engine `!important`
   ban. This is the ONLY reason `!important` is permitted in the engine layer
   outside of the third-party integrations exception (choices.js / Fluent
   Forms overrides): here it enforces a MOTION-SAFETY override that users who
   set `prefers-reduced-motion: reduce` are entitled to, and which must beat
   any component's own animation/transition durations regardless of specificity.

   STATE-SCOPED — this rule only applies inside the reduced-motion media
   query, so it does NOT change any element's at-rest computed style and keeps
   the byte-identical fingerprint gate at 0 diffs. The per-component
   reduced-motion guards that already exist remain in place and are harmless;
   this simply covers every component (present and future) in one place. */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}


/* --- _base/bleed.css --- */
/* ─── Bleed — escape the container, once ───────────────────────────────
   Wave S spatial vocabulary. The canonical container-escape math, declared
   ONE time so it stops being re-derived (usually wrong) in per-client CSS.

   THE CONTRACT. The design system's skeleton is
   `<section class="hzo-section"><div class="hzo-container">…</div></section>`:
   the section spans the viewport, the container holds the max-width. That is
   correct for 95% of content and it is also why an image or panel can never
   touch the viewport edge — the single most common "why does this look like a
   document and not like a site" symptom. .hzo-bleed is the sanctioned way for
   ONE element inside a container to reach the edge without unwrapping the
   section.

     <section class="hzo-section">
       <div class="hzo-container">
         <p>Normal measure copy…</p>
         <figure class="hzo-bleed"><img src="…" alt="…"></figure>
       </div>
     </section>

   ── The math ────────────────────────────────────────────────────────────
   `margin-inline: calc(50% - 50vw)` on a block-level child pulls each side out
   to the viewport edge: 50% is half the container's own width, 50vw is half
   the viewport, so the negative margin is exactly the gutter. No `width: 100vw`
   is needed (and it is deliberately NOT set — width:100vw double-counts the
   scrollbar and is the usual source of the phantom horizontal scrollbar).

   ⚠ OVERFLOW GUARD — READ THIS, IT IS THE ONE FOOTGUN.
   `vw` units include the vertical scrollbar's width. On a page WITH a vertical
   scrollbar, 50vw is ~8px wider than half the visible area, so a bleed element
   overhangs by about one scrollbar width and the page gains a horizontal
   scrollbar. The fix belongs on the page root, not here (a guard on .hzo-bleed
   itself cannot clip its own overhang, and a guard on .hzo-section would clip
   every deliberate overlap from _base/overlap.css). Add ONE line to the
   client's custom.css when the site uses bleed:

     body { overflow-x: clip; }

   Use `clip`, not `hidden`: `hidden` on an ancestor silently kills
   `position: sticky` inside it (the sticky header, the sticky-cta rail),
   `clip` does not. `.hzo-bleed-guard` below is that rule as a class for the
   cases where the guard needs to sit on a wrapper instead of on body.

   ── Half bleed ──────────────────────────────────────────────────────────
   --left / --right escape one side only and keep the container edge on the
   other, so an image can run off the page while its copy stays on the grid.
   This is the asymmetric-split move; it is the reason this file exists.

   ── Inner padding ───────────────────────────────────────────────────────
   A bled element is edge-to-edge, so any TEXT inside it needs its own gutter
   back. .hzo-bleed__inner re-applies the container's responsive padding
   without re-applying the max-width. Never put a bare paragraph directly in a
   .hzo-bleed — it will touch the glass on mobile.
   ────────────────────────────────────────────────────────────────────────── */

.hzo-bleed {
    margin-inline: calc(50% - 50vw);
}
.hzo-bleed--left {
    margin-inline-start: calc(50% - 50vw);
    margin-inline-end: 0;
}
.hzo-bleed--right {
    margin-inline-start: 0;
    margin-inline-end: calc(50% - 50vw);
}

/* Below 768px a half-bleed reads as a mistake rather than as art direction —
   the copy column is already full width, so an image escaping one side only
   looks misaligned. Both halves flatten to a full bleed on mobile. */
@media (max-width: 767.98px) {
    .hzo-bleed--left,
    .hzo-bleed--right { margin-inline: calc(50% - 50vw); }
}

/* Opt-in overflow guard (see the note above). Put it on body via custom.css,
   or on the nearest wrapper that owns the horizontal axis. */
.hzo-bleed-guard { overflow-x: clip; }

/* Gutter restore for content inside a bled element. Mirrors _base/container.css
   padding exactly — same five zones, same tokens — minus the max-width. */
.hzo-bleed__inner { padding: 0 var(--space-md); }
@media (min-width: 480px)  { .hzo-bleed__inner { padding: 0 var(--space-lg); } }
@media (min-width: 768px)  { .hzo-bleed__inner { padding: 0 var(--space-lg-2); } }
@media (min-width: 1024px) { .hzo-bleed__inner { padding: 0 var(--space-xl); } }
@media (min-width: 1250px) { .hzo-bleed__inner { padding: 0 var(--space-2xl); } }


/* --- _base/close.css --- */
/* ─── Shared close control — .hzo-close ────────────────────────────────
   The CANONICAL close button for overlays/dismissibles (modal, drawer,
   toast, mini-cart, banner, lightbox, mega-menu, mobile-menu). Distilled
   from the DOMINANT pattern shared by the modal / drawer / toast close
   buttons: a transparent square control, centered child svg, muted rest
   color, hover fill on --bg-alt with --text-heading foreground, a
   focus-visible ring on --focus-ring, and a color/background transition
   guarded by prefers-reduced-motion.

   NO-OP ON ARRIVAL (Wave 1): this class is intentionally NOT referenced by
   any existing markup, so it matches no DOM element and cannot change the
   computed-style fingerprint. It DEFINES the shared contract only.

   LATER WAVES: overlay/control components add `.hzo-close` to their close
   elements and DROP the duplicated declarations below, keeping only the
   per-component DELTAS that legitimately differ, e.g.:
     • size          — modal/drawer 44px · toast 28px · mini-cart 24px
     • border-radius — square (--radius-control) here · toast/mini-cart pill
     • position      — lightbox absolute top/right · banner flex-shrink
     • child svg size — 24 / 16 / 14 per component
     • hover model   — banner's color-mix currentColor + lightbox's white-on
                       -scrim are DELIBERATE deltas, not part of this base
   Tokens only — no hardcoded aesthetic literals. */
.hzo-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    border-radius: var(--radius-control);
    cursor: pointer;
    transition: background var(--transition-base) ease, color var(--transition-base) ease;
}
.hzo-close:hover { background: var(--bg-alt); color: var(--text-heading); }
.hzo-close:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 1px; }
.hzo-close > svg { display: block; }

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


/* --- _base/container.css --- */
/* ─── Container ───────────────────────────────────────────────────────
   Horizontal padding scales across five zones via the QA breakpoints
   (480, 768, 1024, 1250) — 16 / 24 / 32 / 48 / 64. Caps at 64px from
   1250 onward since wider screens don't need wider gutters. */
.hzo-container {
    max-width: var(--container-max-width);
    margin: 0 auto;
    padding: 0 var(--space-md);
}
@media (min-width: 480px)  { .hzo-container { padding: 0 var(--space-lg); } }
@media (min-width: 768px)  { .hzo-container { padding: 0 var(--space-lg-2); } }
@media (min-width: 1024px) { .hzo-container { padding: 0 var(--space-xl); } }
@media (min-width: 1250px) { .hzo-container { padding: 0 var(--space-2xl); } }


/* --- _base/core-markup-normalize.css --- */
/* ─── Core-markup normalization (always-loaded) ─────────────────────────────
   WordPress emits a handful of class names into content that IT does not
   fully style — it either leaves them to the active theme, or (in the emoji
   case) ships the rule from a hook a plugin can unhook. This engine replaces
   the theme, and hozio-studio.php's "Frontend cleanup" section unhooks core
   defaults. Between those two facts sits a gap: core-generated markup sitting
   in client post_content with no CSS owner at all.

   That gap is not theoretical. On 2026-08-28 (v1.11.1) eight pasted
   `<img class="emoji">` tags rendered at 667 px square on a live client page
   because the engine had unhooked print_emoji_styles and nothing else claimed
   the rule. This sheet claims them, under our own handle, so the fleet stops
   depending on core winning a race against every other plugin on the site.

   SCOPE — only markup that (a) core generates, (b) is broken or invisible
   without CSS, and (c) is not already owned by an .hzo-* component. Core BLOCK
   styles (wp-block-*) are deliberately NOT re-implemented here: per-block CSS
   still loads correctly for anything rendered through render_block(), and
   guessing at core's block output would calcify that guess into every client
   site — the failure mode CLAUDE.md's "Before touching CSS" section is about.
   ────────────────────────────────────────────────────────────────────────── */

/* ─── WP emoji / smiley images ──────────────────────────────────────────────
   Byte-for-byte the rule core's wp_enqueue_emoji_styles() emits, re-declared
   under our handle. Deliberate duplication: core's copy covers a client whose
   components/ folder is missing (the primitives.css fallback path in
   hzostudio_enqueue_components(), where no _base sheet enqueues at all), ours
   covers any other plugin on the site unhooking core's. Either alone is a
   single point of failure; together, neither is.

   `!important` is core's own choice here and is kept for the same reason it
   exists there: these tags land inside arbitrary content, and the rule has to
   outrank whatever generic `img` rule a theme, page builder or client
   custom.css has already declared. It is scoped to two exact class tokens on
   one element type, so it cannot leak.

   The matching PHP (includes/core-markup-normalize.php) removes these tags
   from rendered output entirely — this sheet is what catches the ones it
   deliberately declines to touch (an emoji <img> with an empty alt), plus
   anything reaching the browser through a path that filter does not run on. */
img.wp-smiley,
img.emoji {
    display: inline !important;
    border: none !important;
    box-shadow: none !important;
    height: 1em !important;
    width: 1em !important;
    margin: 0 0.07em !important;
    vertical-align: -0.1em !important;
    background: none !important;
    padding: 0 !important;
}

/* ─── .screen-reader-text ───────────────────────────────────────────────────
   Core's visually-hidden class — emitted by get_search_form(), comment forms,
   nav markup and pagination, and by this engine at
   includes/wc-shortcodes.php:275 (the WooCommerce quantity label). Themes are
   expected to style it; unstyled it does not degrade quietly, it renders the
   hidden label as visible body copy in the middle of the page.

   Same clip technique as .hzo-sr-only in sr-only.css — kept in sync with it
   deliberately rather than aliased, because that file documents the utility we
   author against and this one documents markup we merely receive. If the
   technique changes there, change it here too. */
.screen-reader-text {
    position: absolute !important;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* Focus reveal — core ships .screen-reader-text on skip links, which become a
   silent keyboard trap if they stay hidden while focused. */
.screen-reader-text:focus,
.screen-reader-text:focus-within {
    position: static !important;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    clip: auto;
    clip-path: none;
    white-space: normal;
}

/* ─── Classic caption + alignment classes ───────────────────────────────────
   [caption] shortcode output and the classic alignment classes. This fleet
   forces the raw-text Classic editor, so both still appear in authored content
   and in anything pasted from an older site. Core emits the markup and leaves
   the styling to the theme; with no theme in that role, an uncaptioned float
   collapses to full width and caption text renders as an unstyled paragraph.

   Tokens only, no fixed sizes: the caption inherits the type ladder and steps
   down relatively, so it tracks whatever context it lands in. */
.wp-caption {
    max-width: 100%;
    margin-bottom: var(--space-md);
}

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

.wp-caption-text,
.wp-caption .wp-caption-text {
    margin-top: var(--space-xs);
    margin-bottom: 0;
    font-size: 0.875em;
    line-height: var(--lh-snug);
    color: var(--text-muted);
}

.alignnone {
    max-width: 100%;
    height: auto;
}

.aligncenter {
    display: block;
    margin-inline: auto;
    max-width: 100%;
    height: auto;
}

.alignleft {
    float: left;
    max-width: 100%;
    height: auto;
    margin: 0 var(--space-md) var(--space-sm) 0;
}

.alignright {
    float: right;
    max-width: 100%;
    height: auto;
    margin: 0 0 var(--space-sm) var(--space-md);
}

/* Floats are a content-flow primitive with no owner further down the cascade —
   without a clear, the next section's heading rides up beside a floated image.
   Scoped to the engine's section skeleton so it cannot affect component
   internals that use float deliberately. */
.hzo-section::after {
    content: "";
    display: table;
    clear: both;
}

/* Narrow viewports: a floated image beside body copy leaves neither enough
   measure to read. Drop both floats to full-width blocks at the same
   breakpoint long-form-content.css uses for its table reflow. */
@media (max-width: 767px) {
    .alignleft,
    .alignright {
        float: none;
        display: block;
        margin-inline: auto;
        margin-bottom: var(--space-md);
    }
}


/* --- _base/dark-section.css --- */
/* ─── Dark section (class-scoped; only activates via .hzo-section--dark) ── */
/* Sections stack flush: cancel WordPress's default block-gap margin between
   stacked top-level sections. Each .hzo-section owns its vertical rhythm via
   padding, so sections must meet with NO gap; without this the 24px block-gap
   inserts a margin and the page background shows through as a seam between
   sections. Specificity (0,2,0) beats WP's `.is-layout-flow > * + *` (0,1,0),
   so it wins regardless of stylesheet order. */
:is(.is-layout-flow, .is-layout-constrained) > .hzo-section { margin-block-start: 0; }

.hzo-section--dark { background: var(--bg-dark-section); color: #fff; }
.hzo-section--dark h1, .hzo-section--dark h2, .hzo-section--dark h3, .hzo-section--dark h4, .hzo-section--dark h5, .hzo-section--dark h6 { color: var(--heading-color-on-dark, #fff); }
.hzo-section--dark p { color: rgba(255,255,255,0.85); }
.hzo-section--dark a { color: #fff; }
.hzo-section--dark .hzo-eyebrow,
.hzo-section--dark h1.hzo-eyebrow,
.hzo-section--dark h2.hzo-eyebrow,
.hzo-section--dark h3.hzo-eyebrow,
.hzo-section--dark h4.hzo-eyebrow { color: var(--accent) !important; }

/* ─── Dark-surface button auto-treatment (token remap) ──────────────────
   BARE .hzo-btn--outline / --ghost / --secondary on a brand-colored dark field
   render brand-on-brand and effectively vanish (~1:1): the base rules resolve
   --btn-outline-color / --btn-ghost-color / --btn-secondary-bg to var(--brand),
   which equals the section's own --bg-dark-section. A correct fix already
   exists as the opt-in `.hzo-btn--on-dark.*` rules (buttons.css:171-195), but
   authors must remember the class and shipped patterns (rate-menu, contact-
   split, Fluent Forms prev/upload, WooCommerce) don't.
   Remapping the *component tokens* here makes those base rules resolve to the
   on-dark values with NO .hzo-btn--on-dark needed — text/border go white,
   secondary flips to a white fill with brand text, hovers stay readable.
   Values read the SAME --btn-on-dark-* family the explicit on-dark rules use,
   so the bare and opt-in paths share one source of truth and can't visually
   diverge (a client theming --btn-on-dark-* themes both).
   Scope note (specificity/order): single class (0,1,0) is deliberate. Custom
   properties inherit from the nearest ancestor, so this surface-level
   declaration shadows any :root / tokens.overrides token for its whole subtree
   — no specificity boost is needed for it to win inside dark surfaces, and
   keeping it low lets a client still re-theme by targeting the surface.
   Folded onto :is(.hzo-section--dark, .hzo-surface--dark) so the opt-in
   surface-dark utility (surface-dark.css remaps text tokens but not these) is
   fixed at the same time. Only outline/ghost/secondary read these tokens —
   primary (accent/green) stays green, and danger/link are untouched. */
:is(.hzo-section--dark, .hzo-surface--dark) {
    --btn-outline-color:   var(--btn-on-dark-accent, #fff);          /* outline: white text + white border (rest); white fill on hover */
    --btn-ghost-color:     var(--btn-on-dark-accent, #fff);          /* ghost: white text (rest + hover) */
    --btn-ghost-hover:     rgba(255, 255, 255, 0.2);                 /* ghost hover: translucent white wash (matches on-dark ghost hover) */
    --btn-secondary-bg:    var(--btn-on-dark-bg, #fff);              /* secondary: white fill + white border */
    --btn-secondary-text:  var(--btn-on-dark-text, var(--brand));    /* secondary + outline-hover text: brand on white */
    --btn-secondary-hover: color-mix(in srgb, var(--btn-on-dark-bg, #fff) 75%, var(--btn-on-dark-text, var(--brand))); /* secondary hover: light brand tint (matches on-dark) */
}


/* --- _base/doc-layout-toc.css --- */
/* ─── Doc layout + sticky TOC sidebar (legal pages, long-form articles) ──
   Two-column grid: TOC sidebar on the left, document body on the right.
   The sidebar is sticky on desktop, stacks above the body on mobile.
   The active section is highlighted via IntersectionObserver scrollspy
   in main.js. Section numbers (.hzo-section-num) sit next to each <h2>
   in a flex container, aria-hidden so screen readers read only the
   heading. Both the inline number and the matching TOC number are
   hand-authored to keep the markup static + crawlable without JS. */
.hzo-doc-layout {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-xl);
    align-items: start;
}
@media (min-width: 1024px) {
    .hzo-doc-layout {
        grid-template-columns: 240px 1fr;
        gap: var(--space-3xl);
    }
}

.hzo-doc-toc { position: static; }
@media (min-width: 1024px) {
    .hzo-doc-toc {
        position: sticky;
        top: 104px;
        max-height: calc(100vh - 120px);
        overflow-y: auto;
        padding-right: 8px;
    }
}
.hzo-doc-toc::-webkit-scrollbar { width: 4px; }
.hzo-doc-toc::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }

.hzo-doc-toc__label {
    font-family: var(--font-body);
    font-size: var(--fs-50);
    font-weight: var(--fw-semibold, 600);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
    margin: 0 0 var(--space-md);
    padding-bottom: var(--space-sm);
    border-bottom: 1px solid var(--border);
}

.hzo-doc-toc__list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
}

.hzo-doc-toc__item { position: relative; }

/* !important on color: fights a:visited from per-client main.css.
   Matching :visited rule below preserves the active/idle colors. */
.hzo-doc-toc__link {
    display: flex;
    align-items: baseline;
    gap: 12px;
    padding: 8px 0 8px 14px;
    color: var(--text-muted) !important;
    text-decoration: none;
    font-family: var(--font-body);
    font-size: var(--fs-100);
    line-height: 1.4;
    border-left: 2px solid var(--border);
    transition: color 0.15s, border-color 0.15s;
}
.hzo-doc-toc__link:hover { color: var(--text-body) !important; }
.hzo-doc-toc__link.is-active {
    color: var(--brand-text) !important;
    border-left-color: var(--brand-text);
    font-weight: var(--fw-semibold, 600);
}
.hzo-doc-toc__link:visited { color: var(--text-muted) !important; }
.hzo-doc-toc__link:visited:hover { color: var(--text-body) !important; }
.hzo-doc-toc__link.is-active:visited { color: var(--brand-text) !important; }

.hzo-doc-toc__num {
    font-family: var(--font-body);
    font-size: var(--fs-50);
    font-weight: var(--fw-medium, 500);
    color: var(--text-muted);
    min-width: 22px;
    flex-shrink: 0;
}
.hzo-doc-toc__link.is-active .hzo-doc-toc__num { color: var(--brand); }

/* Section heading row inside the doc body — flex container that holds
   the decorative number + the real heading tag, baseline-aligned. The
   number is aria-hidden so screen readers read only the h2. */
.hzo-doc-section-head {
    display: flex;
    align-items: baseline;
    gap: var(--space-md);
    margin-bottom: var(--space-md);
}
.hzo-section-num {
    font-family: var(--font-body);
    font-size: var(--fs-100);
    font-weight: var(--fw-semibold, 600);
    color: var(--text-muted);
    flex-shrink: 0;
    line-height: 1.4;
}
/* Reset the eyebrow's default bottom margin when it sits inside a
   section-head flex row — the row owns spacing, not the eyebrow. */
.hzo-doc-section-head > .hzo-eyebrow,
.hzo-doc-section-head > h1.hzo-eyebrow,
.hzo-doc-section-head > h2.hzo-eyebrow,
.hzo-doc-section-head > h3.hzo-eyebrow,
.hzo-doc-section-head > h4.hzo-eyebrow {
    margin: 0 !important;
}

/* Section adjacency rule — when [hzo_final_cta] (or any .hzo-section)
   follows another .hzo-section, collapse the doubled padding (80+80=160
   on desktop, 48+48=96 on mobile) by stripping the second section's
   top padding. The preceding section's bottom padding becomes the sole
   gap, scaling responsively via the .hzo-section ladder (48/64/80).
   System-level fix replacing per-page padding-bottom:0 hacks. */
.hzo-section + .hzo-section,
.hzo-section + .hzo-final-cta {
    padding-top: 0;
}
/* …EXCEPT a colored surface band (--pale/--tint/--dark) or a hero must ALWAYS
   keep its own top padding, even when it follows another section — otherwise
   its background sits flush against the content (the "no top padding on
   colored sections" bug). Only plain white-on-white sections collapse.
   Restored responsively to match the .hzo-section ladder (48 / 64 / 80). */
.hzo-section + .hzo-section--pale,
.hzo-section + .hzo-section--tint,
.hzo-section + .hzo-section--dark,
.hzo-section + .hzo-hero {
    padding-top: var(--space-xl);
}
@media (min-width: 768px) {
    .hzo-section + .hzo-section--pale,
    .hzo-section + .hzo-section--tint,
    .hzo-section + .hzo-section--dark,
    .hzo-section + .hzo-hero { padding-top: var(--space-2xl); }
}
@media (min-width: 1250px) {
    .hzo-section + .hzo-section--pale,
    .hzo-section + .hzo-section--tint,
    .hzo-section + .hzo-section--dark,
    .hzo-section + .hzo-hero { padding-top: var(--space-3xl); }
}
/* …and the mirror case: a PLAIN section that FOLLOWS a colored surface (or
   hero) must also keep its top padding. The collapse above only holds for true
   white-on-white runs; when the background CHANGES at the boundary
   (colored → plain), zeroing the plain section's top padding jams its content
   against the color edge (the "What customers say follows the dark band" bug).
   Restore responsively to match the ladder (48 / 64 / 80). */
.hzo-section--pale + .hzo-section,
.hzo-section--tint + .hzo-section,
.hzo-section--dark + .hzo-section,
.hzo-hero + .hzo-section {
    padding-top: var(--space-xl);
}
@media (min-width: 768px) {
    .hzo-section--pale + .hzo-section,
    .hzo-section--tint + .hzo-section,
    .hzo-section--dark + .hzo-section,
    .hzo-hero + .hzo-section { padding-top: var(--space-2xl); }
}
@media (min-width: 1250px) {
    .hzo-section--pale + .hzo-section,
    .hzo-section--tint + .hzo-section,
    .hzo-section--dark + .hzo-section,
    .hzo-hero + .hzo-section { padding-top: var(--space-3xl); }
}

/* Doc page card — contained card with bg-alt + radius. Used for the
   hero and closing CTA on long-form doc pages (privacy, terms). Inner
   padding scales across five zones via QA breakpoints (480, 768, 1024,
   1250). Tightens on small phones, opens up on desktop, caps at
   64/48 from 1250 onward. */
.hzo-doc-card {
    background: var(--bg-alt);
    border-radius: var(--radius-card);
    padding: var(--space-lg-2) var(--space-md);
    text-align: center;
}
@media (min-width: 480px)  { .hzo-doc-card { padding: var(--space-xl-2) var(--space-lg); } }
@media (min-width: 768px)  { .hzo-doc-card { padding: var(--space-xl) var(--space-lg-2); } }
@media (min-width: 1024px) { .hzo-doc-card { padding: var(--space-2xl-2) var(--space-xl-2); } }
@media (min-width: 1250px) { .hzo-doc-card { padding: var(--space-2xl) var(--space-xl); } }

/* Vertical rhythm between adjacent doc-page siblings (.hzo-doc-card,
   .hzo-doc-layout) inside a single .hzo-section > .hzo-container.
   Same five-zone scale as the card padding. First sibling has no top
   gap — section padding-top handles that breathing room. */
.hzo-doc-card + .hzo-doc-card,
.hzo-doc-card + .hzo-doc-layout,
.hzo-doc-layout + .hzo-doc-card {
    margin-top: var(--space-lg-2);
}
@media (min-width: 480px) {
    .hzo-doc-card + .hzo-doc-card,
    .hzo-doc-card + .hzo-doc-layout,
    .hzo-doc-layout + .hzo-doc-card { margin-top: var(--space-xl-2); }
}
@media (min-width: 768px) {
    .hzo-doc-card + .hzo-doc-card,
    .hzo-doc-card + .hzo-doc-layout,
    .hzo-doc-layout + .hzo-doc-card { margin-top: var(--space-xl); }
}
@media (min-width: 1024px) {
    .hzo-doc-card + .hzo-doc-card,
    .hzo-doc-card + .hzo-doc-layout,
    .hzo-doc-layout + .hzo-doc-card { margin-top: var(--space-2xl-2); }
}
@media (min-width: 1250px) {
    .hzo-doc-card + .hzo-doc-card,
    .hzo-doc-card + .hzo-doc-layout,
    .hzo-doc-layout + .hzo-doc-card { margin-top: var(--space-2xl); }
}

/* CTA variant of .hzo-doc-card — used as the closing "Get in touch"
   block on long-form doc pages. Identical to the base card on desktop;
   on mobile (<768) the card flips to left-aligned and stacks the email
   and phone contact links onto separate lines so longer email addresses
   don't get awkwardly wrapped or centered on narrow screens. */
@media (max-width: 767px) {
    .hzo-doc-card--cta {
        text-align: left;
    }
    .hzo-doc-card--cta .hzo-doc-card__contact a {
        display: block;
    }
    .hzo-doc-card--cta .hzo-doc-card__sep {
        display: none;
    }
}

/* Body sections — separated by top rules + generous vertical rhythm.
   scroll-margin-top offsets the anchor jump so the section title lands
   below the sticky header (which is ~80px). */
.hzo-doc-body > section {
    margin-top: var(--space-3xl);
    padding-top: var(--space-3xl);
    border-top: 1px solid var(--border);
    scroll-margin-top: 100px;
}
.hzo-doc-body > section:first-child {
    margin-top: 0;
    padding-top: 0;
    border-top: none;
}
.hzo-doc-body h2:not(.hzo-eyebrow):first-child { margin-top: 0; }


/* --- _base/emphasis-decoration.css --- */
/* ─── Emphasis / decoration utilities ────────────────────────────────────
   Opt-in inline decorations keyed to --emphasis (a client's decorative accent;
   falls back to --accent if a client hasn't defined one). Re-key --emphasis per
   client in main.css for a distinct decorative hue. */

/* Keyword underline — clean rule beneath one/two key words; wraps per line and
   sits behind descenders. */
.hzo-mark {
    background-image: linear-gradient(var(--emphasis, var(--accent)), var(--emphasis, var(--accent)));
    background-repeat: no-repeat;
    background-position: 0 92%;
    background-size: 100% 0.12em;
    padding-bottom: 0.02em;
}

/* Stat brackets — [ ] framing a standout number. Wrap a number; the brackets
   carry the emphasis color, the number keeps its own. */
.hzo-bracket {
    position: relative;
    display: inline-block;
    padding: 0 0.4em;
}
.hzo-bracket::before,
.hzo-bracket::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    width: 0.2em;
    border: 3px solid var(--emphasis, var(--accent));
}
.hzo-bracket::before { left: 0;  border-right: 0; }
.hzo-bracket::after  { right: 0; border-left: 0; }

/* Pull-quote — editorial moment, emphasis left rule, heading font. */
.hzo-pullquote {
    font-family: var(--font-heading);
    font-size: clamp(1.25rem, 2.2vw, 1.6rem);
    font-weight: var(--fw-bold, 700);
    line-height: 1.3;
    color: var(--text-heading);
    border-left: 3px solid var(--emphasis, var(--accent));
    padding-left: var(--space-lg);
    margin: var(--space-xl) 0;
}
.hzo-section--dark .hzo-pullquote { color: #fff; }

/* ═══════════════════════════════════════════════════════════════════════
   SITE HEADER · NAV · DROPDOWN · HAMBURGER · MOBILE MENU
   Structure ported from the TGA production header. Uses hzo-* classes
   throughout. Breakpoints: hamburger below 1250px, horizontal nav above.
   ═══════════════════════════════════════════════════════════════════════ */

/* Cancel WP's default block-gap between direct children of .wp-site-blocks.
   WP emits `:where(.wp-site-blocks) > * { margin-block-start: 24px; }` from
   the block-layout system (driven by --wp--style--block-gap, default 24px).
   For block templates that compose utility-bar + header + main + sections +
   footer as direct siblings, this creates visible 24px white bands between
   every section (page background showing through between brand-colored
   regions). Our header/main/section/footer carry their own padding/spacing
   for whitespace control — we don't want WP's auto-gap on top of that. */
.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }

.hzo-site-header {
    background: var(--nav-header-bg, #fff);
    box-shadow: var(--nav-header-shadow, 0 1px 4px rgba(0, 0, 0, 0.08));
    position: sticky;
    top: 0;
    z-index: var(--z-header);
}
.hzo-site-header__inner {
    display: flex;
    align-items: center;
    min-height: var(--header-height-mobile, 72px);
    gap: 16px;
}
@media (min-width: 1250px) {
    .hzo-site-header__inner { min-height: var(--header-height-desktop, 80px); }
}


/* --- _base/gradient-scrims.css --- */
/* ─── Directional gradient scrims (opt-in on --image surfaces) ─────────
   Art-directed alternative to the flat wash: full strength on the text
   side, fading out so the photo breathes on the far side. Add alongside
   .hzo-hero--image / .hzo-section--image:
     .hzo-scrim--left     copy sits left  → dark anchors left
     .hzo-scrim--right    copy sits right → dark anchors right
     .hzo-scrim--bottom   dark rises from the bottom (tiles, captions)
     .hzo-scrim--top      dark descends from the top
   Strength rides the same knob as the flat wash (--hzo-section-scrim,
   default 60%); --scrim-color re-tints (default --brand-deep). Below 768px
   the side scrims flatten to a stronger uniform wash — copy spans the full
   width there, and a side gradient would leave its right half unreadable.
   No modifier = the original flat wash (back-compat; nothing shifts on
   existing pages).

   STRENGTH is the second axis and composes with all four directions:
     .hzo-scrim--weak     35%  the photo is the subject; short, large copy
     .hzo-scrim--base     60%  the shipped default — a no-op by construction
     .hzo-scrim--strong   82%  copy-heavy band, or a busy/light photo
   These set --hzo-section-scrim from the --scrim-* ladder in tokens.css, so the
   strength is a class on the section instead of a percentage in an inline style.
   They work on the flat wash too (no direction modifier). AA is NOT implied by
   any of them — contrast depends on the photograph, so measure the rendered
   band. */

/* Strength stops. Declared on the SECTION (the pseudo inherits) and before the
   direction rules so nothing here depends on source order among them. */
.hzo-hero--image.hzo-scrim--weak,
.hzo-section--image.hzo-scrim--weak   { --hzo-section-scrim: var(--scrim-weak, 35%); }
.hzo-hero--image.hzo-scrim--base,
.hzo-section--image.hzo-scrim--base   { --hzo-section-scrim: var(--scrim-base, 60%); }
.hzo-hero--image.hzo-scrim--strong,
.hzo-section--image.hzo-scrim--strong { --hzo-section-scrim: var(--scrim-strong, 82%); }

.hzo-hero--image.hzo-scrim--left::before,
.hzo-section--image.hzo-scrim--left::before {
    background: linear-gradient(to right,
        color-mix(in srgb, var(--scrim-color, var(--brand-deep)) min(calc(var(--hzo-section-scrim, 60%) * 1.5), 95%), transparent) 0%,
        color-mix(in srgb, var(--scrim-color, var(--brand-deep)) var(--hzo-section-scrim, 60%), transparent) 38%,
        color-mix(in srgb, var(--scrim-color, var(--brand-deep)) calc(var(--hzo-section-scrim, 60%) * 0.35), transparent) 68%,
        transparent 100%);
}
.hzo-hero--image.hzo-scrim--right::before,
.hzo-section--image.hzo-scrim--right::before {
    background: linear-gradient(to left,
        color-mix(in srgb, var(--scrim-color, var(--brand-deep)) min(calc(var(--hzo-section-scrim, 60%) * 1.5), 95%), transparent) 0%,
        color-mix(in srgb, var(--scrim-color, var(--brand-deep)) var(--hzo-section-scrim, 60%), transparent) 38%,
        color-mix(in srgb, var(--scrim-color, var(--brand-deep)) calc(var(--hzo-section-scrim, 60%) * 0.35), transparent) 68%,
        transparent 100%);
}
.hzo-hero--image.hzo-scrim--bottom::before,
.hzo-section--image.hzo-scrim--bottom::before {
    background: linear-gradient(to top,
        color-mix(in srgb, var(--scrim-color, var(--brand-deep)) min(calc(var(--hzo-section-scrim, 60%) * 1.5), 95%), transparent) 0%,
        color-mix(in srgb, var(--scrim-color, var(--brand-deep)) calc(var(--hzo-section-scrim, 60%) * 0.6), transparent) 45%,
        transparent 85%);
}
.hzo-hero--image.hzo-scrim--top::before,
.hzo-section--image.hzo-scrim--top::before {
    background: linear-gradient(to bottom,
        color-mix(in srgb, var(--scrim-color, var(--brand-deep)) min(calc(var(--hzo-section-scrim, 60%) * 1.5), 95%), transparent) 0%,
        color-mix(in srgb, var(--scrim-color, var(--brand-deep)) calc(var(--hzo-section-scrim, 60%) * 0.6), transparent) 45%,
        transparent 85%);
}
@media (max-width: 767.98px) {
    .hzo-hero--image.hzo-scrim--left::before,
    .hzo-section--image.hzo-scrim--left::before,
    .hzo-hero--image.hzo-scrim--right::before,
    .hzo-section--image.hzo-scrim--right::before {
        background: color-mix(in srgb, var(--scrim-color, var(--brand-deep)) min(calc(var(--hzo-section-scrim, 60%) * 1.25), 90%), transparent);
    }
}


/* --- _base/icon-sizing.css --- */
/* ─── Icon sizing scale ────────────────────────────────────────────────
   One icon family per site (art-direction §3) — this governs SIZE only, never
   family mixing. Icons inherit text color (currentColor) and track font-size via
   em, so an icon beside text scales + aligns with that text by default. Zero new
   tokens. a11y: decorative => aria-hidden="true"; meaningful => role="img" +
   aria-label. Wrap an inline SVG: <span class="hzo-icon hzo-icon--lg">…svg…</span>. */
.hzo-icon {
    display: inline-flex;
    flex-shrink: 0;
    width: 1em;
    height: 1em;
    vertical-align: -0.125em;   /* sit on the adjacent text baseline */
    line-height: var(--lh-none);
}
.hzo-icon > svg { width: 100%; height: 100%; display: block; fill: none; stroke: currentColor; }
.hzo-icon--filled > svg { fill: currentColor; stroke: none; }  /* solid (non-stroke) icons */
.hzo-icon--xs { font-size: 0.75em; }
.hzo-icon--sm { font-size: 0.875em; }
.hzo-icon--md { font-size: 1em; }      /* default */
.hzo-icon--lg { font-size: 1.5em; }
.hzo-icon--xl { font-size: 2em; }


/* --- _base/layout-primitives.css --- */
/* ─── Layout primitives — Stack / Cluster / Spacer ────────────────────
   Composition utilities so client builds compose spacing instead of sprinkling
   bespoke margins. Every gap/height maps to the --space ladder — zero new
   tokens. See art-direction.md "Compose spacing, don't hand-roll margins". */

/* Stack — vertical rhythm between direct children (gap-based flex column).
   Default gap = the base step (--space-md); modifiers step the ladder. */
.hzo-stack { display: flex; flex-direction: column; gap: var(--space-md); }
.hzo-stack--xs  { gap: var(--space-xs); }
.hzo-stack--sm  { gap: var(--space-sm); }
.hzo-stack--md  { gap: var(--space-md); }
.hzo-stack--lg  { gap: var(--space-lg); }
.hzo-stack--xl  { gap: var(--space-xl); }
.hzo-stack--2xl { gap: var(--space-2xl); }
.hzo-stack--3xl { gap: var(--space-3xl); }
/* --split: fill the parent and push the last child to the bottom
   (card footers, sidebar CTAs). Use on a stack inside a flex/grid cell. */
.hzo-stack--split { flex: 1 1 auto; }
.hzo-stack--split > :last-child { margin-top: auto; }

/* Cluster — horizontal group that wraps; gap + alignment. Default gap =
   --space-sm, centered cross-axis. */
.hzo-cluster { display: flex; flex-wrap: wrap; gap: var(--space-sm); align-items: center; }
.hzo-cluster--xs  { gap: var(--space-xs); }
.hzo-cluster--sm  { gap: var(--space-sm); }
.hzo-cluster--md  { gap: var(--space-md); }
.hzo-cluster--lg  { gap: var(--space-lg); }
.hzo-cluster--xl  { gap: var(--space-xl); }
.hzo-cluster--center   { justify-content: center; }
.hzo-cluster--between  { justify-content: space-between; }
.hzo-cluster--end      { justify-content: flex-end; }
.hzo-cluster--start    { align-items: flex-start; }
.hzo-cluster--baseline { align-items: baseline; }  /* icon + text rows */

/* Spacer — explicit vertical gap for the rare case a stack doesn't fit
   (e.g. between unrelated sibling blocks not in a stack). */
.hzo-spacer { display: block; flex-shrink: 0; height: var(--space-md); }
.hzo-spacer--xs  { height: var(--space-xs); }
.hzo-spacer--sm  { height: var(--space-sm); }
.hzo-spacer--md  { height: var(--space-md); }
.hzo-spacer--lg  { height: var(--space-lg); }
.hzo-spacer--xl  { height: var(--space-xl); }
.hzo-spacer--2xl { height: var(--space-2xl); }
.hzo-spacer--3xl { height: var(--space-3xl); }


/* --- _base/long-form-content.css --- */
/* ─── Long-form content (legal pages, articles) ────────────────────── */
/* Heading spacing inside hzo-sections — excludes the eyebrow pattern */
.hzo-section h2:not(.hzo-eyebrow) {
    margin-top: var(--space-xl);
    margin-bottom: var(--space-md);
}
.hzo-section h3:not(.hzo-eyebrow) {
    margin-top: var(--space-lg);
    margin-bottom: var(--space-sm);
}
.hzo-section h4:not(.hzo-eyebrow) {
    margin-top: var(--space-md);
    margin-bottom: var(--space-sm);
}
.hzo-section h2:not(.hzo-eyebrow):first-child,
.hzo-section h3:not(.hzo-eyebrow):first-child {
    margin-top: 0;
}

/* Block-table borders use the global border token (pale gray, not browser default) */
.hzo-section .wp-block-table table {
    border-collapse: collapse;
    width: 100%;
}
.hzo-section .wp-block-table thead tr {
    border-bottom: 2px solid var(--border);
}
.hzo-section .wp-block-table tbody tr {
    border-bottom: 1px solid var(--border);
}
.hzo-section .wp-block-table th,
.hzo-section .wp-block-table td {
    border: none;
    padding: 12px 16px;
    text-align: left;
    vertical-align: top;
}

/* Block-table mobile stacking — rows collapse to labeled-cell cards.
   data-label is injected at runtime by main.js from each table's thead. */
@media (max-width: 767px) {
    .hzo-section .wp-block-table table,
    .hzo-section .wp-block-table thead,
    .hzo-section .wp-block-table tbody,
    .hzo-section .wp-block-table tr,
    .hzo-section .wp-block-table th,
    .hzo-section .wp-block-table td {
        display: block;
        width: auto;
    }
    .hzo-section .wp-block-table thead {
        position: absolute;
        clip: rect(0, 0, 0, 0);
        height: 1px;
        width: 1px;
        overflow: hidden;
    }
    .hzo-section .wp-block-table tbody tr {
        margin-bottom: var(--space-md);
        padding: var(--space-md) 0;
        border-bottom: 1px solid var(--border);
    }
    .hzo-section .wp-block-table tbody tr:last-child {
        border-bottom: none;
    }
    .hzo-section .wp-block-table td {
        padding: var(--space-xs) 0;
        border: none;
    }
    .hzo-section .wp-block-table td::before {
        content: attr(data-label);
        display: block;
        font-weight: var(--fw-bold, 700);
        font-size: var(--fs-50);
        color: var(--text-heading);
        text-transform: uppercase;
        letter-spacing: 0.05em;
        margin-bottom: 4px;
    }
    .hzo-section .wp-block-table td:empty {
        display: none;
    }
}


/* --- _base/measure.css --- */
/* ─── Measure — the line-length ladder ─────────────────────────────────
   Wave S spatial vocabulary. Line length is a typographic decision with one
   right answer per role, and until now the system carried exactly one value
   (--prose-max-width: 65ch, consumed by .hzo-prose) plus a scattering of
   hand-written `max-width: 720px` inline styles in the block patterns.

   Three steps, because there are three jobs:
     --measure-45 / .hzo-measure-45   45ch  a headline or a pull-quote. Short
                                            lines are what make a big type size
                                            read as a statement instead of as a
                                            paragraph set in the wrong size.
     --measure-65 / .hzo-measure-65   65ch  body copy. The default, and the
                                            same value --prose-max-width has
                                            always carried.
     --measure-75 / .hzo-measure-75   75ch  dense reference text — tables,
                                            long-form legal, documentation.
   .hzo-measure with no step is 65ch. Add .hzo-measure--center to centre the
   block (margin-inline: auto), which is what a centred section almost always
   wants and what a bare `text-align: center` does NOT give you.

   ch UNITS, NOT px: a measure expressed in px is only correct at one font
   size, so it silently breaks the moment the design widget moves the type
   scale or a client sets a wider heading font. ch tracks the element's own
   font.

   These are max-widths only — no padding, no display change, nothing that
   could disturb a layout it is added to.
   ────────────────────────────────────────────────────────────────────────── */

.hzo-measure,
.hzo-measure-65 { max-width: var(--measure-65, 65ch); }
.hzo-measure-45 { max-width: var(--measure-45, 45ch); }
.hzo-measure-75 { max-width: var(--measure-75, 75ch); }
.hzo-measure--center { margin-inline: auto; }


/* --- _base/overlap.css --- */
/* ─── Overlap — pull utilities + the content z-index scale ─────────────
   Wave S spatial vocabulary. Boundary-breaking is the cheapest premium signal
   there is: one element that refuses to stay inside its section. Every band
   sitting neatly in its own box is what makes a page read as a stack of
   rectangles no matter how good the individual sections are.

   Pulling is a two-part move and BOTH parts are needed. A negative margin
   alone puts the element in the right place and then paints it underneath the
   neighbour's background, so the effect silently disappears. That is why
   every .hzo-pull-* here also opens a positioning context and raises the
   element. It is one class, not a recipe you have to remember.

     <section class="hzo-section hzo-section--dark">…</section>
     <section class="hzo-section">
       <div class="hzo-container">
         <div class="hzo-card hzo-pull-2">…</div>   <!-- rises into the dark band -->
       </div>
     </section>

   ── The scale ───────────────────────────────────────────────────────────
     .hzo-pull-1   48px   a nudge — a card breaking its section line
     .hzo-pull-2   64px   the default overlap; a panel straddling two surfaces
     .hzo-pull-3   80px   a hero card sitting deep in the band above
     .hzo-pull-4  120px   the statement move; use once per page at most
   Push variants pull the NEXT sibling up into THIS element instead:
     .hzo-push-1 … .hzo-push-4  (negative margin-bottom, same steps)

   RESPONSIVE BEHAVIOUR: below 768px every step clamps to --pull-1. A 120px
   overlap on a 375px screen is not a composition, it is two things on top of
   each other. The clamp lives in the CLASS, not in the token — tokens are not
   responsive, and a --pull-4 read from an inline style would hard-code 120px
   at every width.

   ── The content z-index scale ───────────────────────────────────────────
   The existing --z-100…700 / --z-header / --z-modal ladder governs CHROME
   (sticky bars, drawers, modals, toasts) and starts at 10. Content layering
   inside a section needs a vocabulary an order of magnitude below that, and
   it was missing — which is why component CSS reaches for bare `z-index: 1`.

     --z-behind  -1   decorative fill BEHIND the section's own content
     --z-base     0   the content plane
     --z-above    1   the element that must sit on top of its neighbours

   Utilities: .hzo-z-behind / .hzo-z-base / .hzo-z-above. Each opens a
   positioning context, because a z-index on a static element does nothing —
   the second-most-common silent no-op in this system after a bare modifier.

   ⚠ .hzo-z-behind needs a stacking context on the ancestor or it will slide
   behind the SECTION's background too, not just behind its content. Put
   .hzo-isolate on the section that owns the decoration.

   ⚠ AN ANCESTOR WITH overflow: hidden CLIPS EVERY PULL. If an overlap does
   not appear, that is the first thing to check — .hzo-media-band and several
   card variants set it deliberately. `overflow: clip` on the page root is
   safe (see _base/bleed.css); overflow on the section between the two is not.
   ────────────────────────────────────────────────────────────────────────── */

.hzo-pull-1, .hzo-pull-2, .hzo-pull-3, .hzo-pull-4,
.hzo-push-1, .hzo-push-2, .hzo-push-3, .hzo-push-4 {
    position: relative;
    z-index: var(--z-above, 1);
}

.hzo-pull-1 { margin-top: calc(-1 * var(--pull-1, 48px)); }
.hzo-pull-2,
.hzo-pull-3,
.hzo-pull-4 { margin-top: calc(-1 * var(--pull-1, 48px)); }  /* mobile clamp */
.hzo-push-1 { margin-bottom: calc(-1 * var(--pull-1, 48px)); }
.hzo-push-2,
.hzo-push-3,
.hzo-push-4 { margin-bottom: calc(-1 * var(--pull-1, 48px)); }  /* mobile clamp */

@media (min-width: 768px) {
    .hzo-pull-2 { margin-top: calc(-1 * var(--pull-2, 64px)); }
    .hzo-pull-3 { margin-top: calc(-1 * var(--pull-3, 80px)); }
    .hzo-pull-4 { margin-top: calc(-1 * var(--pull-4, 120px)); }
    .hzo-push-2 { margin-bottom: calc(-1 * var(--pull-2, 64px)); }
    .hzo-push-3 { margin-bottom: calc(-1 * var(--pull-3, 80px)); }
    .hzo-push-4 { margin-bottom: calc(-1 * var(--pull-4, 120px)); }
}

/* Content-layer utilities. Each opens a positioning context so the z-index
   is not a no-op. */
.hzo-z-behind { position: relative; z-index: var(--z-behind, -1); }
.hzo-z-base   { position: relative; z-index: var(--z-base, 0); }
.hzo-z-above  { position: relative; z-index: var(--z-above, 1); }

/* Stacking-context opener — the partner to .hzo-z-behind, and the correct
   answer whenever a section's decoration must not escape into the page's
   global stacking order. */
.hzo-isolate { isolation: isolate; }


/* --- _base/overlay-lock.css --- */
/* ─── Overlay scroll-lock (always-loaded; ungated) ─────────────────────────
   `body.hzo-overlay-open` is toggled by the SHARED overlay manager in main.js
   (hzo.lockOverlayBackground / unlockOverlayBackground) whenever ANY overlay is
   open — modal, drawer, mini-cart, lightbox, mobile-menu, or search. That JS is
   ungated (ships on every page), so its CSS MUST be ungated too, or a page that
   opens a drawer/mini-cart WITHOUT a modal present loses the scroll-lock (the
   modal component sheet — which used to hold this rule — isn't enqueued there).

   Audit fix: UI-audit gap G2 (scroll-lock CSS present only in the gated
   modal.css). Per interaction-standards.md §"class-set-by-ungated-JS ⇒
   CSS-in-ungated-sheet". Moved here 2026-07-15, byte-identical behavior. */
body.hzo-overlay-open { overflow: hidden; }


/* --- _base/print-styles.css --- */
/* ─── Print styles ─────────────────────────────────────────────────────
   Keep this block LAST in the file. Paper gets the content, not the chrome:
   nav/overlay/CTA chrome hidden, black-on-white text, accordions expanded
   (CSS-only best effort — ::details-content forces the content box open in
   supporting browsers; the sibling display rule covers older engines that
   honored it), external link URLs printed after main-content links, and no
   page breaks inside card-like units. */
@media print {
    /* Chrome that means nothing on paper */
    .hzo-skip-link,
    .hzo-utility-bar,
    .hzo-site-header,
    .hzo-mobile-menu,
    .hzo-mobile-trigger,
    .hzo-search-overlay,
    .hzo-sticky-bar,
    .hzo-back-to-top,
    .hzo-social-proof,
    .hzo-banner,
    .hzo-final-cta,
    .hzo-site-footer nav,
    .hzo-site-footer .hzo-footer-link,   /* [hzo_footer_nav] renders bare links, no <nav> wrapper */
    .hzo-footer-social,
    .hzo-carousel__btn,
    .hzo-carousel__dots,
    .hzo-carousel__toggle,
    [data-hzo-marquee-toggle],
    .hzo-scroll-row__btn,
    .hzo-video-toggle,          /* Batch-2 chrome */
    .hzo-map__load,
    .hzo-price-toggle,          /* the monthly (default) prices still print */
    .hzo-share-row,             /* pure share chrome — and its links would print their sharer URLs */
    .hzo-modal,
    .hzo-drawer,
    .hzo-lightbox,
    .hzo-toast-region { display: none !important; }
    /* Lite-video: hide the play badge but keep the poster (it's real content). */
    .hzo-lite-video__play { display: none !important; }
    /* Long below-fold sections marked content-visibility:auto must be forced
       visible or some Chromium versions print them blank. */
    .hzo-cv-auto { content-visibility: visible !important; }

    /* Black on white, no elevation. (!important is deliberate here — print
       must beat every component color, including the targeted !importants
       used against a:visited elsewhere in this file.) */
    * {
        background: transparent !important;
        color: #000 !important;
        box-shadow: none !important;
        text-shadow: none !important;
    }
    body { background: #fff !important; }

    /* Expand every <details> (accordions/FAQs) so answers print.
       ::details-content opens the content box where supported; the sibling
       rule is the legacy best-effort fallback. */
    details::details-content {
        content-visibility: visible !important;
        height: auto !important;
        opacity: 1 !important;
    }
    details > summary ~ * { display: block !important; }

    /* G13: force scroll-reveal content visible in print/PDF — an un-revealed
       element (opacity:0, scroll-triggered) would otherwise print blank. Covers
       the legacy .hzo-reveal path AND the .hzo-reveal--item orchestrator. */
    .hzo-reveal,
    .hzo-reveal.hzo-reveal--init,
    .hzo-reveal--item,
    .hzo-reveal--item.hzo-reveal--init,
    .hzo-reveal--item.hzo-reveal--init.hzo-reveal--in {
        opacity: 1 !important;
        transform: none !important;
        filter: none !important;
        animation: none !important;
    }

    /* Print destination URLs after external links in the content area only
       (nav/footer/buttons stay clean; same-page anchors carry no href^=http). */
    main a[href^="http"]::after {
        content: " (" attr(href) ")";
        font-size: 0.85em;
        font-weight: normal;
        word-break: break-all;
    }

    /* Keep card-like units on one page */
    .hzo-card,
    .hzo-accordion__item,
    .hzo-stat,
    .hzo-timeline__item,
    .hzo-feature__panel {
        break-inside: avoid;
        page-break-inside: avoid;
    }
}


/* --- _base/prose.css --- */
/* ─── Prose (.hzo-prose) ───────────────────────────────────────────────
   Long-form article body: measure + vertical rhythm for h2/h3/p/lists/
   blockquotes. Internals use :where() (zero specificity) so any component
   class used inside an article still wins — the same trick as the Batch 1
   text-wrap pass, which already targets .hzo-prose for `pretty` wrapping. */
.hzo-prose { max-width: var(--prose-max-width); }
.hzo-prose > :where(h2) { margin: var(--space-xl) 0 var(--space-md); }
.hzo-prose > :where(h3) { margin: var(--space-lg-2) 0 var(--space-sm); }
.hzo-prose > :where(h2:first-child, h3:first-child) { margin-top: 0; }
.hzo-prose > :where(p, ul, ol) { margin: 0 0 var(--space-md); }
.hzo-prose > :where(ul, ol) { padding-left: 1.4em; }
.hzo-prose :where(li + li) { margin-top: var(--space-xs); }
.hzo-prose > :where(blockquote) {
    margin: var(--space-lg) 0;
    padding: var(--space-xs) 0 var(--space-xs) var(--space-lg);
    border-left: 3px solid var(--emphasis, var(--accent));
    color: var(--text-heading);
    font-size: 1.1em;
}
.hzo-prose > :where(blockquote) :where(p:last-child) { margin-bottom: 0; }
/* Opt-in dropcap — the FIRST paragraph only, pure CSS (::first-letter on
   the first <p> child; no JS, no extra markup). Float + tuned line-height
   works everywhere; initial-letter isn't cross-browser yet. */
.hzo-prose--dropcap > p:first-of-type::first-letter {
    float: left;
    font-family: var(--font-heading);
    font-size: 3.1em;
    font-weight: var(--font-weight-heading, var(--h2-weight, 700));
    line-height: 0.82;
    padding: 0.08em 0.12em 0 0;
    color: var(--brand-text);
}


/* --- _base/rendering-hint.css --- */
/* ─── Rendering hint (.hzo-cv-auto) ────────────────────────────────────
   content-visibility:auto lets the browser skip layout/paint for offscreen
   sections. BELOW-FOLD SECTIONS ONLY — never the hero or anything that can
   start in the first viewport (the hint would delay its first paint), and
   never on containers whose in-page anchors must be measurable on load.
   contain-intrinsic-size reserves ~800px per skipped section so the
   scrollbar doesn't jump as they render in; `auto` remembers the real size
   after first render. */
.hzo-cv-auto {
    content-visibility: auto;
    contain-intrinsic-size: auto 800px;
}


/* --- _base/responsive-background.css --- */
/* ─── Responsive background utility ───────────────────────────────────
   Right-size a background photo per breakpoint via inline vars — an LCP win
   for every image hero (mobile stops downloading the desktop image):
     style="--hzo-bg-sm:url(...480w); --hzo-bg-md:url(...1024w);
            --hzo-bg-xl:url(...1920w); --hzo-bg-pos-sm: 70% center"
   Each zone falls back to the previous one, so setting only --hzo-bg-sm is
   valid. Position vars are optional (default center). Declared AFTER the
   --image surfaces so its background-position wins when composed with them. */
.hzo-bg-responsive {
    background-image: var(--hzo-bg-sm);
    background-position: var(--hzo-bg-pos-sm, center);
    background-size: cover;
}
@media (min-width: 768px) {
    .hzo-bg-responsive {
        background-image: var(--hzo-bg-md, var(--hzo-bg-sm));
        background-position: var(--hzo-bg-pos-md, var(--hzo-bg-pos-sm, center));
    }
}
@media (min-width: 1024px) {
    .hzo-bg-responsive {
        background-image: var(--hzo-bg-lg, var(--hzo-bg-md, var(--hzo-bg-sm)));
        background-position: var(--hzo-bg-pos-lg, var(--hzo-bg-pos-md, var(--hzo-bg-pos-sm, center)));
    }
}
@media (min-width: 1440px) {
    .hzo-bg-responsive {
        background-image: var(--hzo-bg-xl, var(--hzo-bg-lg, var(--hzo-bg-md, var(--hzo-bg-sm))));
        background-position: var(--hzo-bg-pos-xl, var(--hzo-bg-pos-lg, var(--hzo-bg-pos-md, var(--hzo-bg-pos-sm, center))));
    }
}


/* --- _base/responsive-content-grid.css --- */
/* ─── Responsive content grid — 2 / 3 / 4-up ──────────────────────────
   Mobile-first single column; expands at the QA breakpoints. Drives feature
   grids, stat rows, card rows, logo grids. */
.hzo-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-lg); }
@media (min-width: 768px) {
    .hzo-grid--2, .hzo-grid--3, .hzo-grid--4 { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
    .hzo-grid--3 { grid-template-columns: repeat(3, 1fr); }
    .hzo-grid--4 { grid-template-columns: repeat(4, 1fr); }
}

/* ─── Spatial vocabulary (Wave S) — spans, arbitrary template, gap ─────
   The 11 lines above are the frozen base: every existing .hzo-grid renders
   byte-identically. Everything below is ADDITIVE and opt-in by class.

   WHY THIS EXISTS. A uniform N-up grid is one composition. Premium layouts
   need ONE cell to carry more weight than its neighbours — the feature tile,
   the tall portrait, the full-width closer. That was possible before only by
   hand-writing grid-column in a client's custom.css, which is why it happened
   once and never became vocabulary.

   ── Column spans ────────────────────────────────────────────────────────
   RESPONSIVE-SAFE COLLAPSE is the whole design: a span is only declared at
   the breakpoint where the columns it asks for actually exist. Below 768px
   the base grid is a single column and NO span applies, so a spanned cell can
   never open an implicit extra track and blow the grid out horizontally.

     .hzo-span-2      2 columns from 768px  (in a --3/--4 grid at 768px the
                      grid is 2-up, so this reads as a full row — intended)
     .hzo-span-3      3 columns from 1024px (--3 / --4 grids)
     .hzo-span-4      4 columns from 1024px (--4 grids)
     .hzo-span-full   the whole row at EVERY breakpoint (1 / -1) — safe in a
                      single-column grid too, which is why it needs no gate
     .hzo-rowspan-2   2 rows from 768px — the tall cell. Pair with --dense so
                      the hole it leaves gets backfilled.
     .hzo-rowspan-3   3 rows from 1024px

   Spans are declared on the CELL, not the grid, and carry no .hzo-grid
   ancestor requirement — they work inside .hzo-bento__stack or any hand-rolled
   grid on the same page. That is deliberate: the utility describes the cell.

   ── Arbitrary template ──────────────────────────────────────────────────
   .hzo-grid--custom reads --hzo-grid-template (desktop) and the optional
   --hzo-grid-template-md (tablet). Mobile always stays single-column. This is
   the escape hatch for compositions the 2/3/4 ladder cannot express — a
   golden-ratio split, a 5-up logo wall, a sidebar rail:

     <div class="hzo-grid hzo-grid--custom" style="--hzo-grid-template: 1.6fr 1fr">

   Unset --hzo-grid-template falls back to a responsive auto-fit, so a typo
   degrades to a sensible grid rather than to `none` + auto columns.

   ── Gap ─────────────────────────────────────────────────────────────────
   Steps the --space ladder, same names as .hzo-stack / .hzo-cluster. Default
   (no modifier) is unchanged at --space-lg. --gap-0 is the flush-tile case
   (a mosaic where cells touch); it is NOT a substitute for a designed rhythm.

   ── Density ─────────────────────────────────────────────────────────────
   .hzo-grid--dense turns on grid-auto-flow: dense so later small cells
   backfill the gaps a span leaves. Note the a11y caveat: dense packing can
   put DOM order out of step with visual order. Keep source order meaningful.
   ────────────────────────────────────────────────────────────────────────── */

.hzo-span-full { grid-column: 1 / -1; }

@media (min-width: 768px) {
    .hzo-span-2    { grid-column: span 2; }
    .hzo-rowspan-2 { grid-row: span 2; }
}
@media (min-width: 1024px) {
    .hzo-span-3    { grid-column: span 3; }
    .hzo-span-4    { grid-column: span 4; }
    .hzo-rowspan-3 { grid-row: span 3; }
}

.hzo-grid--custom { grid-template-columns: 1fr; }
@media (min-width: 768px) {
    .hzo-grid--custom {
        grid-template-columns: var(--hzo-grid-template-md,
            var(--hzo-grid-template, repeat(auto-fit, minmax(240px, 1fr))));
    }
}
@media (min-width: 1024px) {
    .hzo-grid--custom {
        grid-template-columns: var(--hzo-grid-template, repeat(auto-fit, minmax(240px, 1fr)));
    }
}

.hzo-grid--gap-0   { gap: 0; }
.hzo-grid--gap-xs  { gap: var(--space-xs); }
.hzo-grid--gap-sm  { gap: var(--space-sm); }
.hzo-grid--gap-md  { gap: var(--space-md); }
.hzo-grid--gap-lg  { gap: var(--space-lg); }
.hzo-grid--gap-xl  { gap: var(--space-xl); }
.hzo-grid--gap-2xl { gap: var(--space-2xl); }

.hzo-grid--dense { grid-auto-flow: dense; }


/* --- _base/scroll-reveal.css --- */
/* ─── Scroll reveal (opt-in utility; reduced-motion-safe; no-JS-safe) ──────
   Add class="hzo-reveal" to any element to fade + rise in as it scrolls into
   view. main.js adds .hzo-reveal--init (ONLY to below-the-fold elements, so
   above-fold content never flashes), then .is-revealed when it enters view.
   No JS or reduced-motion → content stays fully visible, never hidden.

   The reveal itself is a KEYFRAME animation (not a transition): components
   that declare their own `transition` shorthand later in the file (buttons,
   tags, option cards…) would silently cancel a transition-based reveal by
   source order — an animation can't be clobbered that way, works on ANY
   element, and keeps fade + rise in lockstep. It only fires on elements
   that went through --init (below-fold), so above-fold content never
   animates on load. Stagger: main.js sets an inline animation-delay;
   `backwards` fill keeps the element hidden through that delay. */
.hzo-reveal { transition: opacity 0.6s ease, transform 0.6s ease; }
.hzo-reveal.hzo-reveal--init { opacity: 0; transform: translateY(20px); will-change: opacity, transform; }
.hzo-reveal.is-revealed { opacity: 1; transform: none; }
.hzo-reveal.hzo-reveal--init.is-revealed {
    animation: hzo-reveal-in 0.6s var(--ease-standard, ease) backwards;
}
@keyframes hzo-reveal-in {
    from { opacity: 0; transform: translateY(20px); }
    to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
    .hzo-reveal, .hzo-reveal.hzo-reveal--init { opacity: 1; transform: none; transition: none; animation: none; }
}


/* --- _base/section-edges.css --- */
/* ─── Section edges — parameterized boundary treatments ────────────────
   Wave S spatial vocabulary. A section boundary is a design decision. Today
   the fleet has exactly two answers to it: a hard colour change, or one of the
   four frozen paths in components/section-divider/section-dividers.css (wave /
   mosaic). Both are choices you make once and cannot tune. This file makes the
   boundary a PARAMETER: angle, size, shape, and colour.

   NOTHING HERE REPLACES ANYTHING. section-dividers.css keeps its wave and
   mosaic paths and keeps working exactly as before; this is the additive,
   token-driven vocabulary that new work should reach for first.

   ── The model: the edge IS the neighbour's surface ──────────────────────
   The treatment is a pseudo-element painted in --edge-color, laid over the
   bottom (or top) band of THIS section. Set --edge-color to the
   ADJACENT section's background and the two surfaces interlock — no third
   colour appears at the seam, which is what separates a designed transition
   from a decorative squiggle. On the standard alternator that is one line:

     <section class="hzo-section hzo-section--dark
                     hzo-section--edge-bottom hzo-edge--diagonal"
              style="--edge-color: var(--bg-alt)">

   The next section is --pale (--bg-alt), so the dark band appears to be cut
   on a diagonal by the pale one. Get --edge-color wrong and it reads as a
   stripe; that is the single thing to check in a screenshot.

   ── Classes ─────────────────────────────────────────────────────────────
     .hzo-section--edge-bottom   arm the bottom edge (uses ::after)
     .hzo-section--edge-top      arm the top edge    (uses ::before)
   Shape (compose one onto an armed edge):
     .hzo-edge--diagonal         straight cut, rises left → right
     .hzo-edge--diagonal-rev     straight cut, rises right → left
     .hzo-edge--angled           as --diagonal, but sized from --edge-angle
     .hzo-edge--angled-rev       as --diagonal-rev, but sized from --edge-angle
     .hzo-edge--curve            the neighbour rises as a wide dome
     .hzo-edge--arch             soft rounded corners; the quietest option
   No shape class = a flat band of --edge-color, which is a legitimate (and
   very quiet) treatment: a colour shelf under the section.

   ── diagonal vs angled: which straight cut ──────────────────────────────
   Both draw the SAME triangle. They differ only in what fixes its size, and
   therefore in what stays constant as the page widens:
     --diagonal   height is --edge-size. The band is a known px height at
                  every width, so the ANGLE flattens as the page widens
                  (64px reads 9.7° at 375 and 1.9° at 1920).
     --angled     height is derived from --edge-angle, so the ANGLE is what
                  holds and the band height grows with the viewport.
   Pick --diagonal when the band must fit a known vertical budget; pick
   --angled when the slope itself is the design. Do not stack them.

   ── Tokens ──────────────────────────────────────────────────────────────
     --edge-color   the neighbour's surface        default --bg-page
     --edge-size    band height (--diagonal etc.)  default 64px
     --edge-angle   used by --angled / --angled-rev only    default 6deg
     --edge-radius  used by --arch only            default --radius-3
   --edge-rise is DERIVED, not authored: it resolves to --edge-size normally
   and to the tan()-computed rise on an --angled edge. It is the single value
   both the band height and .hzo-section--edge-pad read, which is what keeps
   the pad honest on an angled edge. Read it; do not set it.

   ── What --edge-angle actually costs you ────────────────────────────────
   The cut spans the FULL width of the section, so a true angle θ needs a rise
   of `width × tan(θ)` — not half of it. That makes the band tall on a wide
   screen, and that is the honest price of a stated angle:
   (measured in Chromium, not calculated — see resources/release-checks/)
     --edge-angle   375     768    1250    1440    1920
       3deg        19.6px  40.2px  65.5px  75.5px  100.6px
       6deg        39.4px  80.7px  131.4px 151.3px 201.8px
      12deg        79.7px  163.2px 265.7px 306.1px 408.1px
   6deg is the token default and is a strong move at desktop; 2–4deg is the
   range that reads as a designed slope rather than a wedge. Whatever you
   pick, pair it with .hzo-section--edge-pad — a 200px band over an 80px
   padding is 120px of band sitting on copy.
   (`vw` includes the scrollbar, so on a scrolling desktop page the run is a
   scrollbar narrower than the rise assumes and the drawn angle lands under
   0.1° over the stated one — 6.06° for 6° at 1440 with a 15px bar. Same
   caveat, and the same magnitude, as the one at the top of _base/bleed.css.)

   ── Two things that will bite ───────────────────────────────────────────
   1. THE EDGE EATS PADDING. The band overlays the section's own bottom
      padding (48px mobile / 64px tablet / 80px desktop). At the default 64px
      an edge on mobile reaches past the padding into content. Either keep
      --edge-size at or below the section's padding on small screens, or add
      .hzo-section--edge-pad (below) which adds the band height back as extra
      padding on the edged side. Prefer the pad class; it is one word.
   2. ::before / ::after ARE SPENT ON IMAGE SECTIONS. .hzo-section--image and
      .hzo-hero--image already use ::before for their scrim, so an --edge-top
      cannot be added to an image section. Put a --edge-bottom on the section
      ABOVE it instead — the composition is identical and the pseudo is free.

   Content is lifted to --z-above on any edged section so the band can never
   paint over copy. Direct children only, mirroring what image-band.css
   already does for its scrim. Purely additive: these selectors require the
   new classes, so no existing section's computed output changes.
   ────────────────────────────────────────────────────────────────────────── */

/* --edge-rise is the ONE height the band and the pad both read. Declared on the
   armed section (not in :root) so it is scoped to edged sections and so the
   --angled override below can win on specificity alone. */
.hzo-section--edge-bottom,
.hzo-section--edge-top {
    position: relative;
    --edge-rise: var(--edge-size, 64px);
}

.hzo-section--edge-bottom > *,
.hzo-section--edge-top > * { position: relative; z-index: var(--z-above, 1); }

.hzo-section--edge-bottom::after,
.hzo-section--edge-top::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    height: var(--edge-rise, var(--edge-size, 64px));
    background: var(--edge-color, var(--bg-page));
    pointer-events: none;
}
.hzo-section--edge-bottom::after { bottom: 0; }
.hzo-section--edge-top::before   { top: 0; }

/* Diagonal / angled — the straight cut. The wedge keeps the corner it is
   anchored to and loses the opposite one, so THIS section's colour reads as the
   rising side. ONE corner-to-corner triangle spanning the whole band: the
   hypotenuse runs (0,100%) → (100%,0), so its rise is the band height and its
   run is the FULL band width. That is the geometry --angled has to satisfy.
   --angled shares the shape and differs only in how the height is derived. */
.hzo-section--edge-bottom.hzo-edge--diagonal::after,
.hzo-section--edge-bottom.hzo-edge--angled::after { clip-path: polygon(0 100%, 100% 0, 100% 100%); }
.hzo-section--edge-bottom.hzo-edge--diagonal-rev::after,
.hzo-section--edge-bottom.hzo-edge--angled-rev::after { clip-path: polygon(0 0, 100% 100%, 0 100%); }
.hzo-section--edge-top.hzo-edge--diagonal::before,
.hzo-section--edge-top.hzo-edge--angled::before { clip-path: polygon(0 0, 100% 0, 0 100%); }
.hzo-section--edge-top.hzo-edge--diagonal-rev::before,
.hzo-section--edge-top.hzo-edge--angled-rev::before { clip-path: polygon(0 0, 100% 0, 100% 100%); }

/* Curve — the neighbour's surface rises as one wide, shallow dome. The
   elliptical radius (`/`) is what keeps it shallow: 50% horizontally, the full
   band height vertically. A circular radius here would read as a bubble. */
.hzo-section--edge-bottom.hzo-edge--curve::after { border-radius: 50% 50% 0 0 / 100% 100% 0 0; }
.hzo-section--edge-top.hzo-edge--curve::before   { border-radius: 0 0 50% 50% / 0 0 100% 100%; }

/* Arch — rounded corners only. The restrained option: it softens the seam
   without announcing itself, and it is the one that survives at 375px. */
.hzo-section--edge-bottom.hzo-edge--arch::after {
    border-radius: var(--edge-radius, var(--radius-3)) var(--edge-radius, var(--radius-3)) 0 0;
}
.hzo-section--edge-top.hzo-edge--arch::before {
    border-radius: 0 0 var(--edge-radius, var(--radius-3)) var(--edge-radius, var(--radius-3));
}

/* Angled — the same straight cut, sized so it holds a TRUE --edge-angle at every
   viewport width instead of flattening as the page widens.

   THE RISE IS 100vw, NOT 50vw. The clip-path above is a single corner-to-corner
   triangle, so tan(θ) = rise / FULL width. A 50vw rise draws atan(0.5·tan θ) —
   3.0° for a stated 6° — which is what shipped in the first cut of this file and
   is why the class name and the token both lied. 50vw would only be right for a
   symmetric two-half peak, and no shape here is one.

   Overriding --edge-rise (not `height`) is deliberate: it keeps
   .hzo-section--edge-pad correct automatically, since the pad reads the same
   value. tan() is baseline in every browser the fleet supports; the @supports
   gate means an engine without it degrades to a --edge-size-tall straight cut —
   still a diagonal, just at a fixed band height — rather than losing the cut. */
@supports (height: calc(1px * tan(45deg))) {
    .hzo-section--edge-bottom.hzo-edge--angled,
    .hzo-section--edge-bottom.hzo-edge--angled-rev,
    .hzo-section--edge-top.hzo-edge--angled,
    .hzo-section--edge-top.hzo-edge--angled-rev {
        --edge-rise: calc(100vw * tan(var(--edge-angle, 6deg)));
    }
}

/* Padding restore — adds the band height back so the edge overlays empty
   space instead of copy. Compound selector (0,2,0) to beat the base
   .hzo-section padding ladder (0,1,0) at every breakpoint.

   Reads --edge-rise, NOT --edge-size, so it covers an --angled band too. With
   --edge-size it restored a flat 64px under a band that measures 101px at 1920,
   leaving 37px of cut sitting on the copy it exists to protect. */
.hzo-section.hzo-section--edge-pad.hzo-section--edge-bottom { padding-bottom: calc(var(--space-xl) + var(--edge-rise, var(--edge-size, 64px))); }
.hzo-section.hzo-section--edge-pad.hzo-section--edge-top    { padding-top: calc(var(--space-xl) + var(--edge-rise, var(--edge-size, 64px))); }
@media (min-width: 768px) {
    .hzo-section.hzo-section--edge-pad.hzo-section--edge-bottom { padding-bottom: calc(var(--space-2xl) + var(--edge-rise, var(--edge-size, 64px))); }
    .hzo-section.hzo-section--edge-pad.hzo-section--edge-top    { padding-top: calc(var(--space-2xl) + var(--edge-rise, var(--edge-size, 64px))); }
}
@media (min-width: 1250px) {
    .hzo-section.hzo-section--edge-pad.hzo-section--edge-bottom { padding-bottom: calc(var(--space-3xl) + var(--edge-rise, var(--edge-size, 64px))); }
    .hzo-section.hzo-section--edge-pad.hzo-section--edge-top    { padding-top: calc(var(--space-3xl) + var(--edge-rise, var(--edge-size, 64px))); }
}


/* --- _base/section-surface-variants.css --- */
/* ─── Section surface variants ────────────────────────────────────────
   Formalize the previously-inline section backgrounds into modifiers:
     (light)              default .hzo-section — white (--bg-page)
     .hzo-section--pale   neutral pale gray (--bg-alt) — the standard alternator
     .hzo-section--tint   brand-tinted light surface (--brand-pale), dark text
     .hzo-section--dark   brand solid + white text (defined earlier in this file)
   Vertical rhythm comes from the base .hzo-section padding ladder.
   (Named --tint, NOT --brand: "--brand" would read as the solid brand color;
   this is a pale brand TINT. The solid brand surface is --dark.) */
.hzo-section--pale  { background: var(--bg-alt); }
.hzo-section--tint  { background: var(--brand-pale); }

/* Radial "glow" variant of the dark surface. Pair WITH --dark so all the
   white-text rules still apply: class="hzo-section hzo-section--dark
   hzo-section--dark-glow". Lightens toward a focal point above center and
   sinks toward --brand-deep at the edges — adds depth to CTA/stat bands
   without touching the color system (everything derives from the same
   --bg-dark-section the flat variant uses). */
.hzo-section--dark.hzo-section--dark-glow {
    background: radial-gradient(120% 90% at 50% 30%,
        color-mix(in srgb, var(--bg-dark-section) 82%, white) 0%,
        var(--bg-dark-section) 55%,
        color-mix(in srgb, var(--bg-dark-section) 60%, var(--brand-deep)) 100%);
}

/* Compact padding modifier — one step down from the base section ladder
   (lg / xl / 2xl vs the base xl / 2xl / 3xl). For low-height utility bands —
   trust strips, thin logo/rating rows, an eyebrow-only intro — that would
   look over-padded at full section rhythm. Replaces the inline
   `style="padding-top: var(--space-lg); padding-bottom: var(--space-lg)"`
   that trust-row.html and the homepage trust bar hand-roll today.

   Compound `.hzo-section.hzo-section--compact` (0,2,0) so it beats BOTH the
   base `.hzo-section` padding (0,1,0, declared in main.css which loads after
   this file) AND the section-adjacency padding-top rules earlier in this file
   (0,2,0) — those are declared before this point, so equal specificity resolves
   to compact by source order. Author intent (a deliberately tight band) wins
   over the automatic adjacency restore, which is what we want. */
.hzo-section.hzo-section--compact { padding-top: var(--space-lg); padding-bottom: var(--space-lg); }
@media (min-width: 768px) {
    .hzo-section.hzo-section--compact { padding-top: var(--space-xl); padding-bottom: var(--space-xl); }
}
@media (min-width: 1250px) {
    .hzo-section.hzo-section--compact { padding-top: var(--space-2xl); padding-bottom: var(--space-2xl); }
}


/* --- _base/skeleton.css --- */
/* ─── Skeleton loading placeholder (always-loaded; reduced-motion-safe) ─────
   Audit G47 (the missing "loading vocabulary"). A space-reserving shimmer block
   for content loading in — AJAX grids, review loads, product loops — with ZERO
   CLS (it reserves the box). The ONE shared primitive so components compose it
   instead of each rolling a private loading cue (product-grid, filters, review
   grids all want this).

   Compose: give it a size (or a variant). Under reduced-motion the ambient
   shimmer STOPS (PRESERVE-5) but the muted box stays (still reads "loading").
   Under forced-colors it becomes a bordered box; in print it's removed. */
.hzo-skeleton {
    position: relative;
    overflow: hidden;
    background: var(--bg-alt);
    border-radius: var(--radius-2);
    /* space-reserving — set a height/width, or use a --* variant below */
}

.hzo-skeleton::after {
    content: "";
    position: absolute;
    inset: 0;
    transform: translateX(-100%);
    background: linear-gradient(
        90deg,
        transparent,
        color-mix(in srgb, var(--bg-page) 65%, transparent),
        transparent
    );
    /* Ambient loop (like the marquee/wave drift) — not a micro-interaction
       transition, so it carries its own tunable speed rather than a --dur-* step. */
    animation: hzo-skeleton-shimmer var(--hzo-skeleton-speed, 1.4s) ease-in-out infinite;
    will-change: transform;
}

@keyframes hzo-skeleton-shimmer {
    to { transform: translateX(100%); }
}

.hzo-skeleton--text   { height: 0.75em; margin-block: 0.3em; border-radius: var(--radius-1); }
.hzo-skeleton--line   { height: 1em; }
.hzo-skeleton--title  { height: 1.4em; width: 60%; }
.hzo-skeleton--circle { border-radius: var(--radius-full); aspect-ratio: 1; }
.hzo-skeleton--media  { aspect-ratio: 4 / 3; }
.hzo-skeleton--block  { min-height: 8rem; }

@media (prefers-reduced-motion: reduce) {
    .hzo-skeleton::after { animation: none; }
}
@media (forced-colors: active) {
    .hzo-skeleton { border: 1px solid CanvasText; background: Canvas; }
    .hzo-skeleton::after { display: none; }
}
@media print {
    .hzo-skeleton { display: none !important; }
}


/* --- _base/skip-link.css --- */
/* ─── Skip link (a11y) ────────────────────────────────────────────────
   Visually hidden until focused. Intended as the FIRST focusable element in
   the header, jumping keyboard/AT users past the nav to <main id="main">.
   Reveal on :focus (not :focus-visible — it must appear for keyboard Tab). */
.hzo-skip-link {
    position: fixed;
    top: 8px;
    left: 8px;
    z-index: var(--z-toast);
    transform: translateY(-150%);
    background: var(--brand);
    color: var(--text-on-brand) !important;
    padding: 12px 20px;
    border-radius: var(--radius-input);
    font-family: var(--font-body);
    font-weight: var(--fw-semibold, 600);
    text-decoration: none;
    transition: transform var(--transition-base) ease;
}
.hzo-skip-link:focus { transform: translateY(0); outline: 3px solid var(--color-focus); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .hzo-skip-link { transition: none; } }


/* --- _base/sr-only.css --- */
/* ─── Screen-reader-only utility (always-loaded) ───────────────────────────
   The single shared visually-hidden utility, so components stop re-inlining the
   clip technique (several audit findings needed one). `.hzo-sr-only` content is
   invisible on screen but stays in the accessibility tree AND the keyboard/tab
   flow — use it for the real label an icon-only control needs, a heading that
   orders the document outline, or status text that has a visible twin.

   Uses the clip technique (NOT display:none / visibility:hidden / [hidden],
   which drop the node from the a11y tree). `--focusable` reveals on focus (the
   skip-link pattern) so a hidden-but-focusable control isn't a silent trap. */
.hzo-sr-only {
    position: absolute !important;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

.hzo-sr-only--focusable:focus,
.hzo-sr-only--focusable:focus-within {
    position: static !important;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    clip: auto;
    clip-path: none;
    white-space: normal;
}


/* --- _base/surface-dark.css --- */
/* ─── Surface dark — scoped token remap ────────────────────────────────
   Re-points the generic surface/text/link tokens to dark-brand values within
   its subtree, so any .hzo-card / aside / summary reads dark without bespoke
   rules. Apply to a wrapper (nested cards inherit the remapped tokens) or
   straight onto a single card/aside. It only sets custom properties + its own
   bg/color, so it does NOT fight .hzo-section--dark's descendant rules (both
   resolve to light text). Utility — no block. */
.hzo-surface--dark {
    --card-bg:      color-mix(in srgb, var(--bg-dark-section) 84%, #000);
    --card-border:  rgba(255, 255, 255, 0.14);
    --border:       rgba(255, 255, 255, 0.14);
    --text-heading: #ffffff;
    --text-body:    rgba(255, 255, 255, 0.88);
    --text-muted:   rgba(255, 255, 255, 0.68);
    --brand-text:   var(--accent-hover-on-dark, var(--accent));
    --accent-text:  var(--accent-hover-on-dark, var(--accent));
    background: var(--surface-dark-bg, var(--bg-dark-section));
    color: var(--text-body);
    border-radius: var(--radius-card);
}


/* --- _base/text-wrapping-polish.css --- */
/* ─── Text wrapping polish — display heading (companion to main.css) ────
   main.css balances h1-h4 + .hzo-visual-h1..4; .hzo-display lives here so
   its rule does too. Progressive — ignored by older browsers. */
.hzo-display { text-wrap: balance; }


/* --- _base/typography.css --- */
/* ─── Typography ────────────────────────────────────────────────────── */
/* .hzo-display is the one step above h1 — the top-of-page statement. Its size
   is now the --fs-display token (Wave S); the inline fallback below is the
   historical literal, so a checkout without the token renders identically and
   the computed value is unchanged either way. Tokenizing it is what lets an
   archetype, the design widget, or a client's custom.css retune the display
   step without re-declaring this whole rule. */
.hzo-display {
    font-family: var(--font-heading);
    font-weight: var(--font-weight-heading, 700);
    font-size: calc(var(--fs-display, clamp(2.25rem, 5vw, 5rem)) * var(--hzo-type-scale, 1) * var(--hzo-heading-scale, 1));
    line-height: 1.05;
    letter-spacing: var(--heading-letter-spacing, -0.015em);
    text-transform: var(--heading-transform, none);
    color: var(--text-heading);
    margin: 0 0 var(--space-lg);
}
.hzo-subtitle {
    font-family: var(--font-body);
    font-size: 1.25rem;
    font-weight: var(--fw-regular, 400);
    line-height: var(--lh-normal);
    color: var(--text-muted);
    margin: 0 0 var(--space-xl);
}
.hzo-lead {
    font-size: 1.25rem;
    line-height: var(--lh-normal);
    color: var(--text-body);
    margin: 0 0 var(--space-lg);
}
.hzo-small {
    font-size: 0.875rem;
    color: var(--text-muted);
    margin: 0 0 var(--space-md);
}
.hzo-eyebrow {
    display: inline-block;
    font-family: var(--font-body);
    font-size: var(--eyebrow-size, 15px);
    font-weight: var(--fw-semibold, 600);
    text-transform: uppercase;
    letter-spacing: var(--eyebrow-tracking, 0.7px);
    color: var(--accent-text);
    margin: 0 0 var(--space-md);
}
/* SEO pattern: eyebrows ARE the heading tags (h1-h6) for keyword placement.
   The class must fully override heading element defaults AND any theme.json
   heading styles. !important on every property a theme could set on
   headings (font-family, font-size, font-weight, color, line-height,
   text-transform, letter-spacing, margin). See quality-rules.md §6.2. */
h1.hzo-eyebrow, h2.hzo-eyebrow, h3.hzo-eyebrow,
h4.hzo-eyebrow, h5.hzo-eyebrow, h6.hzo-eyebrow {
    display: inline-block !important;
    font-family: var(--font-body) !important;
    font-size: var(--eyebrow-size, 15px) !important;
    font-weight: var(--fw-semibold, 600) !important;
    line-height: 1.4 !important;
    text-transform: uppercase !important;
    letter-spacing: var(--eyebrow-tracking, 0.7px) !important;
    color: var(--accent-text) !important;
    margin: 0 0 var(--space-md) !important;
}
.hzo-meta {
    font-family: var(--font-body);
    font-size: var(--fs-75);
    font-weight: var(--fw-medium, 500);
    color: var(--text-muted);
    line-height: var(--lh-normal);
}
.hzo-caption {
    font-family: var(--font-body);
    font-size: var(--fs-75);
    color: var(--text-muted);
    font-style: italic;
    line-height: var(--lh-normal);
    margin: var(--space-sm) 0 0;
}


/* --- _base/utility-color-tokens.css --- */
/* ─── Utility color tokens (additive; warning + info not in main.css) ─── */
:root {
    --color-warning:        #D97706;
    --color-warning-bg:     #FFFBEB;
    --color-info:           #2563EB;
    --color-info-bg:        #EFF6FF;
    --color-warm-highlight: #FFF8E5;
}


/* --- _base/view-transitions.css --- */
/* ─── Cross-document View Transitions (roadmap big bet 2) ─────────────
   MPA navigations get a fast root crossfade, and the header + logo morph
   between pages instead of repainting — the "app-smooth" page-load feel.
   Supporting browsers only (Chrome/Edge 126+, Safari 18.2+); everywhere
   else this whole block is inert and navigation behaves exactly as today.
   Gated on prefers-reduced-motion: reduced-motion users keep instant
   loads (the @view-transition rule itself is inside the media query, so
   no transition is even attempted).

   Naming discipline: view-transition-name must be UNIQUE per rendered
   page or the browser SKIPS the whole transition. Only two guaranteed
   singletons are named — the site header and the logo INSIDE it (the
   mobile drawer also renders a .hzo-site-logo, so the scoped selector
   matters). Heroes are deliberately NOT named: the hero carousel renders
   several .hzo-hero--image elements on one page, which would void the
   transition sitewide on those pages. */
@media (prefers-reduced-motion: no-preference) {
    @view-transition {
        navigation: auto;
    }
    .hzo-site-header { view-transition-name: hzo-header; }
    .hzo-site-header .hzo-site-logo { view-transition-name: hzo-logo; }
    /* Root crossfade at the system's base speed — the UA default (~250ms)
       reads a touch slow for a marketing site; header/logo groups keep the
       UA's default morph timing. */
    ::view-transition-old(root),
    ::view-transition-new(root) {
        animation-duration: var(--duration-base, 200ms);
        animation-timing-function: var(--ease-standard, ease);
    }
}


/* --- _base/visited-normalization.css --- */
/* ─── :visited state normalization ───────────────────────────────────
   Browsers default :visited links to dark purple/grey, which overrides
   our styling once a link has been clicked. Each class-based link type
   below resets :visited to match its base color. This is critical for
   nav, footer, and card CTAs where visited links would otherwise shift
   color after a visitor's first navigation. */
.hzo-nav-link:visited,
.hzo-site-nav__link:visited,
.hzo-mega-menu__link:visited,
.hzo-mobile-menu__link:visited { color: var(--nav-link); }

/* :visited:hover — must be re-declared because :visited and :hover have the
   same specificity, so without this block the :visited rule above (which
   comes later in source order) would win on hover-over-visited links. */
.hzo-nav-link:visited:hover,
.hzo-site-nav__link:visited:hover,
.hzo-mega-menu__link:visited:hover,
.hzo-mobile-menu__link:visited:hover { color: var(--nav-link-hover); }

.hzo-breadcrumb a:visited { color: var(--text-muted); }
.hzo-breadcrumb a:visited:hover { color: var(--brand); }
.hzo-breadcrumb [aria-current="page"]:visited { color: var(--text-body); }

.hzo-card__link:visited { color: var(--accent-text) !important; }
.hzo-card__link:visited:hover { color: color-mix(in srgb, var(--accent-text) 70%, black) !important; }

.hzo-site-logo:visited { color: var(--brand); }

/* Dark-footer variant */
.hzo-site-footer a:visited { color: var(--footer-link-base-dark, rgba(255,255,255,0.85)); }
.hzo-footer-link:visited { color: var(--footer-link-dark, rgba(255,255,255,0.8)) !important; }

/* Light-footer variant */
.hzo-site-footer--light a:visited { color: var(--text-body) !important; }
.hzo-site-footer--light .hzo-footer-link:visited { color: var(--footer-link) !important; }

/* Buttons do NOT need :visited rules. The base .hzo-btn rules use !important
   on `color` and `background` which already overrides a:visited from per-client
   main.css. Adding :visited here creates a maintenance trap: if the base color
   changes, the :visited rule goes stale and shows the wrong color once a
   button link has been clicked. */


/* --- alert/alert.css --- */
/* ─── Alerts ────────────────────────────────────────────────────────── */
.hzo-alert {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: var(--space-md) var(--space-lg);
    border-left: 4px solid;
    border-radius: 4px;
    font-size: 15px;
    font-weight: var(--fw-500, 500);
    line-height: var(--lh-normal);
}
.hzo-alert > svg { flex-shrink: 0; }
.hzo-alert--info    { background: var(--color-info-bg);    border-color: var(--color-info);    color: var(--color-info); }
.hzo-alert--success { background: var(--success-bg);       border-color: var(--success);       color: var(--success-text, #166534); }
.hzo-alert--warning { background: var(--color-warning-bg); border-color: var(--color-warning); color: var(--warning-text, #92400E); }
.hzo-alert--error   { background: var(--error-bg);         border-color: var(--error);         color: var(--error-text, #991B1B); }


/* --- announcer/announcer.css --- */
/* ─── Live-Region Announcer (.hzo-announcer-region) ────────────────────────
   The two aria-live regions (polite + assertive) that hzo.announce() writes
   into. They must be in the accessibility tree at ALL times — so they are
   hidden with the CLIP technique, NEVER display:none / visibility:hidden /
   [hidden], which drop the node from the a11y tree and kill announcements
   (WCAG 4.1.3). Zero-size, non-interactive, never focusable.

   forced-colors safe by construction: clipped (not opacity/visibility-hidden),
   so `forced-colors: active` cannot surface it (avoids the §12.2 opacity trap).
   No motion ships (SR text injection only) → no transition to kill and nothing
   is ever motion-hidden, so no prefers-reduced-motion block is needed. Needs no
   z-index (clip-hidden, zero-size); the optional toast:true delegate resolves
   to --z-toast inside toast.css, never here. */
.hzo-announcer-region {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    -webkit-user-select: none;
    user-select: none;
    pointer-events: none;
}

/* Page-level [hzo_announce config="true"] node: sets JS defaults, emits no UI.
   NOT a live region, so display:none is safe (and correct — never announced). */
.hzo-announcer-config {
    display: none !important;
}

/* Optional [hzo_announce] click trigger. Unstyled by default — it is an author
   affordance that composes inline; add .hzo-btn* (or your own classes) via the
   shortcode `class` attribute when a visible button is wanted. */
.hzo-announce-trigger {
    font: inherit;
    color: inherit;
}


/* --- answer/answer.css --- */
/* ─── AEO Answer Block (.hzo-answer) ────────────────────────────────────────
   The visible surface for W-004 (answer.php): ONE question as a real heading
   + a 40–60-word answer-first paragraph optimized for AI Overviews / featured
   snippets. Three display modes — inline (default, always-visible AEO shape),
   card (boxed), disclosure (native <details>). The FAQPage/QAPage JSON-LD is
   PHP-emitted and has no styling surface.

   Tokens only — no bare hex/px/ms, no z-index (this block never portals or
   stacks). schema="off" still renders the visible block; a schema-carrying
   block adds only an invisible <script>. */

.hzo-answer {
    max-width: var(--prose-max-width);
}

/* Question heading — a REAL heading; its size comes from the type-scale ladder
   (base.css), so level (h2/h3/h4) stays decoupled from visual weight. */
.hzo-answer__question {
    margin: 0 0 var(--space-sm);
    color: var(--text-heading);
}

/* Answer-first paragraph. */
.hzo-answer__text {
    margin: 0;
    color: var(--text-body);
    line-height: var(--body-line-height, 1.6);
}

/* Optional byline: <cite> author · <time> date. */
.hzo-answer__byline {
    margin: var(--space-md) 0 0;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-sm);
    color: var(--text-muted);
    font-size: var(--fs-100, 14px);
}
.hzo-answer__author {
    font-style: normal;                 /* <cite> defaults italic; keep it plain */
    font-weight: var(--fw-medium, 500);
    color: var(--text-body);
}
.hzo-answer__byline-sep {
    color: var(--text-muted);
}

/* ── Card display: boxed, elevated ── */
.hzo-answer--card {
    padding: var(--space-lg);
    background: var(--surface);
    border: var(--border-1) solid var(--card-border, var(--border));
    border-radius: var(--radius-panel);
    box-shadow: var(--card-shadow);
}

/* ── Group: multiple Q&As sharing ONE FAQPage node ── */
.hzo-answer--group {
    display: flex;
    flex-direction: column;
    gap: var(--space-xl);
}
.hzo-answer--group.hzo-answer--card {
    gap: var(--space-lg);               /* tighter when boxed as a single card */
}
.hzo-answer__item > .hzo-answer__question {
    margin: 0 0 var(--space-sm);
}
.hzo-answer__item > .hzo-answer__text {
    margin: 0;
}

/* ── Disclosure display: native <details> = APG Disclosure pattern ──
   The <summary> carries button semantics + Enter/Space + aria-expanded for
   free; the caret is our own (default marker hidden). */
.hzo-answer--disclosure {
    padding: 0 var(--space-lg);
    border: var(--border-1) solid var(--border);
    border-radius: var(--radius-panel);
}
.hzo-answer__summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    min-height: 44px;                   /* ≥44×44 comfort tap target (§11.2) */
    padding-block: var(--space-md);
    cursor: pointer;
    list-style: none;                   /* hide default marker; use our caret */
}
.hzo-answer__summary::-webkit-details-marker {
    display: none;
}
.hzo-answer__summary > .hzo-answer__question {
    margin: 0;
}
/* Chevron caret (rotates on open). */
.hzo-answer__summary::after {
    content: "";
    flex-shrink: 0;
    width: 0.5em;
    height: 0.5em;
    border-right: var(--border-2) solid currentColor;
    border-bottom: var(--border-2) solid currentColor;
    transform: rotate(45deg);
    transition: transform var(--dur-1) var(--ease-standard);
}
.hzo-answer--disclosure[open] > .hzo-answer__summary::after {
    transform: rotate(-135deg);
}
/* Real focus ring on the summary (WCAG 2.4.13) — outline (not shadow-only) so
   it survives forced-colors. */
.hzo-answer__summary:focus-visible {
    outline: var(--border-2) solid var(--focus-ring);
    outline-offset: 2px;
}
.hzo-answer__body {
    padding-bottom: var(--space-md);
}

/* ── Print: an AEO answer is exactly what customers print/PDF — force it fully
   visible. Open a collapsed <details>, reset an opt-in reveal, drop the caret
   and box shadow (G13 force-reveal). ── */
@media print {
    .hzo-answer.hzo-reveal {
        opacity: 1 !important;
        transform: none !important;
    }
    .hzo-answer--card,
    .hzo-answer--disclosure {
        box-shadow: none;
        border-color: currentColor;
    }
    .hzo-answer--disclosure:not([open]) > .hzo-answer__body {
        display: block !important;      /* override the UA details-collapse rule */
    }
    .hzo-answer__summary::after {
        display: none;
    }
}

/* ── Forced-colors: never convey state through color/shadow alone — keep a
   system-color border, caret, and focus ring (§12.2). ── */
@media (forced-colors: active) {
    .hzo-answer--card,
    .hzo-answer--disclosure {
        border: var(--border-1) solid CanvasText;
    }
    .hzo-answer__summary::after {
        border-color: CanvasText;
    }
    .hzo-answer__summary:focus-visible {
        outline-color: CanvasText;
    }
}

/* ── Reduced-motion: kill the only animated affordance (the caret rotate).
   PRESERVE-5 — ambient motion stops; content stays visible. ── */
@media (prefers-reduced-motion: reduce) {
    .hzo-answer__summary::after {
        transition: none;
    }
}


/* --- avatar/avatar.css --- */
/* ─── Avatar ──────────────────────────────────────────────────────────
   Circular user/brand image with an initials fallback. Base 48px; --sm 32,
   --lg 64. Image: <span class="hzo-avatar"><img alt="..."></span>. Initials:
   <span class="hzo-avatar hzo-avatar--initials">JD</span> (brand-pale field +
   brand-text letters). Composed by the testimonial + team/bio blocks. */
.hzo-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 48px;
    height: 48px;
    border-radius: 50%;
    overflow: hidden;
    background: var(--bg-alt);
    color: var(--text-muted);
    font-family: var(--font-body);
    font-weight: var(--fw-600, 600);
    font-size: 1rem;
    line-height: var(--lh-none);
    text-transform: uppercase;
    letter-spacing: 0.02em;
    user-select: none;
}
.hzo-avatar > img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.hzo-avatar--initials { background: var(--brand-pale); color: var(--brand-text); }
.hzo-avatar--sm { width: 32px; height: 32px; font-size: 0.8125rem; }
.hzo-avatar--lg { width: 64px; height: 64px; font-size: 1.25rem; }


/* --- avatar-group/avatar-group.css --- */
/* avatar-group — @tokens: --avatar-group-overlap

   An overlapping avatar stack — composes `.hzo-avatar` children into a
   condensed "who's here" cluster (testimonial rows, team credits, "+N more").
   Each disc gets a ring in the page colour so it reads as separate, and a
   negative left margin so it tucks under its left neighbour. Later children
   paint ABOVE earlier ones (source order + z-index) so each ring cleanly
   overlaps the one before it.

   Token-only: the overlap distance has no semantic token, so it is a component
   token with a literal fallback (the icon-button.css dial pattern). Ring width
   + shape read the semantic border/radius scale; the pill matches the base
   avatar footprint (48px, same dimension literal avatar.css uses). */
.hzo-avatar-group {
    display: inline-flex;
    align-items: center;
}

/* Direct children (the `.hzo-avatar` discs + the `+N` pill) overlap + ring. */
.hzo-avatar-group > * {
    border: var(--border-2) solid var(--bg-page);
    border-radius: var(--radius-full);
    margin-left: calc(-1 * var(--avatar-group-overlap, 12px));
}
.hzo-avatar-group > *:first-child { margin-left: 0; }

/* Later children win the stacking so each ring sits on its left neighbour. */
.hzo-avatar-group > *:nth-child(1) { z-index: 1; }
.hzo-avatar-group > *:nth-child(2) { z-index: 2; }
.hzo-avatar-group > *:nth-child(3) { z-index: 3; }
.hzo-avatar-group > *:nth-child(4) { z-index: 4; }
.hzo-avatar-group > *:nth-child(5) { z-index: 5; }

/* Overflow pill — the "+N" summary. Same footprint as a base avatar; muted ink
   on the alt surface so it reads as a count, not a person. Sits last, on top. */
.hzo-avatar-group__more {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 48px;
    height: 48px;
    background: var(--bg-alt);
    color: var(--text-muted);
    font-size: var(--fs-50);
    font-weight: var(--fw-600, 600);
    line-height: var(--lh-none);
    user-select: none;
    z-index: 6;
}


/* --- back-to-top/back-to-top.css --- */
/* ─── Back-to-top ──────────────────────────────────────────────────────
   Site chrome (ships fleet-wide from parts/footer.html — deliberately not
   opt-in; [hzo_back_to_top] can emit further instances). back-to-top.js
   (Phase-2 widget; behavior moved out of main.js) adds .is-visible once
   scrollY passes two viewports — or the data-hzo-back-to-top-threshold[-px]
   override; click smooth-scrolls to top (instant under reduced motion).
   Quiet circle: brand bg, white arrow, --shadow-md, 44px target. Rides
   between --z-sticky-bar (10) and --z-header (20) — above the mobile sticky
   CTA, below every overlay. Hidden from print (components/_base/
   print-styles.css). */
.hzo-back-to-top {
    position: fixed;
    right: var(--space-lg);
    bottom: calc(var(--space-lg) + env(safe-area-inset-bottom, 0px));
    z-index: var(--z-back-to-top, 15);
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: var(--radius-full);
    background: var(--back-to-top-bg, var(--brand));
    color: var(--back-to-top-color, var(--text-on-brand));
    box-shadow: var(--shadow-md);
    cursor: pointer;
    opacity: 0;
    visibility: hidden;
    transform: translateY(var(--space-sm));
    transition: opacity var(--duration-base) var(--ease-standard),
                transform var(--duration-base) var(--ease-out),
                visibility var(--duration-base),
                background var(--duration-base) var(--ease-standard);
}
.hzo-back-to-top svg { width: 20px; height: 20px; display: block; }
.hzo-back-to-top.is-visible {
    opacity: 1;
    visibility: visible;
    transform: none;
}
.hzo-back-to-top:hover { background: var(--back-to-top-bg-hover, var(--brand-hover-on-light)); }
.hzo-back-to-top:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
    .hzo-back-to-top { transition: none; transform: none; }
}


/* --- badge/badge.css --- */
/* ─── Badges ─────────────────────────────────────────────────────── */
.hzo-badge {
    display: inline-flex;
    align-items: center;
    line-height: var(--lh-none);
    padding: 6px 11px;
    border-radius: var(--badge-radius, var(--radius-full));
    font-size: var(--fs-50);
    font-weight: var(--badge-weight, var(--fw-600, 600));
    letter-spacing: 0.03em;
    text-transform: uppercase;
    background: var(--bg-alt);
    color: var(--text-body);
}
.hzo-badge--primary   { background: var(--brand); color: var(--text-on-brand); }
.hzo-badge--secondary { background: var(--accent); color: var(--text-on-accent); }
/* State variants — PF-043.
   These four used to hardcode `color: #fff` on the shared semantic state fill,
   with no on-state ink token able to co-vary with a retint. Badge text is
   --fs-50 (12px), i.e. NORMAL text, so WCAG AA wants 4.5:1. White on fill:

       --info     #2563EB   5.17:1   pass
       --error    #DC2626   4.83:1   pass, thin
       --warning  #D97706   3.19:1   FAIL
       --success  #16A34A   3.30:1   FAIL

   These are engine tokens, not recipe-derived, so the two failures were
   identical on every install — not a per-client palette problem. And the
   failure was one-way: a client who retinted --success/--warning LIGHTER made
   it worse, because the ink stayed white.

   Fixed by giving the badge its own fill + ink tokens rather than by moving
   --success / --warning themselves. Those two are also consumed by alert and
   callout, where they are already safe (pale -bg fill + dark -ink text), and
   darkening the shared token to fix a badge would have shifted the semantic hue
   everywhere it is used. Defaults are the two darkened fills PF-043 proposed:

       --badge-success-bg  #15803D   white on it = 5.02:1   pass
       --badge-warning-bg  #B45309   white on it = 5.02:1   pass

   error/info route through the same token names at their existing values, so a
   client retinting any state has ONE place to do it and an ink token to go with
   it. Anyone overriding a fill LIGHTER must also set --badge-on-state dark.

   The tokens are declared here as var() fallbacks rather than in tokens.css's
   Badge block (which today holds only --badge-radius / --badge-weight). Folding
   them in there alongside the other component tokens is the tidier end state and
   is a safe follow-up — the fallbacks keep working either way. */
.hzo-badge--success   { background: var(--badge-success-bg, #15803D); color: var(--badge-on-state, #fff); }
.hzo-badge--warning   { background: var(--badge-warning-bg, #B45309); color: var(--badge-on-state, #fff); }
.hzo-badge--error     { background: var(--badge-error-bg, var(--error)); color: var(--badge-on-state, #fff); }
.hzo-badge--info      { background: var(--badge-info-bg, var(--color-info)); color: var(--badge-on-state, #fff); }
.hzo-badge--outline   { background: transparent; color: var(--brand-text); border: 1px solid var(--brand-text); }
/* Sale — promo attention badge. Reuses the accent (the system's `--emphasis`
   attention colour) so it stands apart from the stock states; no new token. Always
   pair with the word "Sale" so the meaning is never carried by colour alone.
   Stock states compose existing variants: in-stock = --success, low-stock =
   --warning, out-of-stock = the base (neutral) badge — unavailability is a state,
   not an error, so red (--error) is reserved for genuine errors. */
.hzo-badge--sale      { background: var(--accent); color: var(--text-on-accent); }


/* --- banner/banner.css --- */
/* ─── Top banner / promo bar ───────────────────────────────────────────
   Full-width announcement strip. Reuses brand/accent/section-surface tokens.
   Dismissed in-session via [data-hzo-banner-dismiss] (banner.js) — NO browser
   storage; collapses height + opacity (no layout jank), reduced-motion-bounded.
   Static markup needs no aria-live; if inserted dynamically use aria-live=polite. */
.hzo-banner {
    overflow: hidden;
    background: var(--brand);
    color: var(--text-on-brand);
    transition: height var(--transition-base) ease, opacity var(--transition-base) ease;
}
.hzo-banner--accent { background: var(--accent); color: var(--text-on-accent); }
.hzo-banner--pale   { background: var(--bg-alt); color: var(--text-body); }
.hzo-banner__inner {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding-top: 10px;
    padding-bottom: 10px;
}
.hzo-banner__text { flex: 1; margin: 0; text-align: center; font-size: var(--fs-100); line-height: 1.4; }
.hzo-banner a { color: inherit; text-decoration: underline; }
.hzo-banner__close {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 0;
    background: transparent;
    color: inherit;
    border-radius: var(--radius-full);
    cursor: pointer;
    transition: background var(--transition-base) ease;
}
.hzo-banner__close:hover { background: color-mix(in srgb, currentColor 15%, transparent); }
.hzo-banner__close:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
.hzo-banner__close > svg { width: 16px; height: 16px; display: block; }
.hzo-banner.is-dismissing { opacity: 0; }
@media (prefers-reduced-motion: reduce) { .hzo-banner { transition: none; } }


/* --- blog-duo/blog-duo.css --- */
/* blog-duo — a responsive two-panel layout: a featured item beside a companion
   list (e.g. a lead article next to a "more articles" column). Single column on
   mobile; an asymmetric 5fr / 6fr split from 900px. CSS-only; token-only spacing;
   brand-agnostic. */
.hzo-blog-duo {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-xl);
}
@media (min-width: 900px) {
    .hzo-blog-duo {
        grid-template-columns: 5fr 6fr;
        gap: var(--space-2xl);
        align-items: start;
    }
}


/* --- breadcrumb/breadcrumb.css --- */
/* ─── Breadcrumb ────────────────────────────────────────────────────── */
.hzo-breadcrumb {
    font-family: var(--font-body);
    font-size: var(--fs-75);
    color: var(--text-muted);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    list-style: none;
    padding: 0;
    margin: 0;
}
.hzo-breadcrumb a {
    color: var(--text-muted);
    text-decoration: none;
    transition: color 0.15s;
}
.hzo-breadcrumb a:hover { color: var(--brand-text); }
.hzo-breadcrumb a:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
.hzo-breadcrumb__sep { color: var(--border); margin: 0 2px; }
.hzo-breadcrumb [aria-current="page"] { color: var(--text-body); font-weight: var(--fw-500, 500); }

/* Simple underline default (Track-2 design pass) — a restrained hover underline
   grows in from the left, matching the nav-link / card-link family. Token-driven
   color (var(--link-underline, currentColor)). A client with a richer treatment
   (e.g. Hozio's background-image highlight-wipe, which is NOT a pseudo) neutralizes
   this with `.hzo-breadcrumb a::after { display: none }` in custom.css so only the
   bespoke skin shows — byte-identical (a pseudo adds no DOM node). */
.hzo-breadcrumb a { position: relative; }
.hzo-breadcrumb a::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -2px;
    height: 2px;
    background: var(--link-underline, currentColor);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform .2s ease;
    pointer-events: none;
}
.hzo-breadcrumb a:hover::after,
.hzo-breadcrumb a:focus-visible::after { transform: scaleX(1); }
@media (prefers-reduced-motion: reduce) {
    .hzo-breadcrumb a::after { transition: none; }
}


/* --- buttons/buttons.css --- */
/* ─── Buttons ────────────────────────────────────────────────────────
   !important is scoped to `color` and `background` only — those fight
   `a:visited` from per-client main.css (links styled as buttons would
   otherwise shift to the visited color). All other properties (padding,
   border, border-radius, text-decoration) win on class specificity alone
   and do not need !important. See bottom-of-file note on why buttons
   intentionally use !important here instead of `.hzo-btn:visited` rules. */
/* Button component tokens (Hozio Studio Live, Phase 2a). Each property reads a
   --btn-* token that DEFAULTS (via the var() fallback) to the original semantic
   value — so with no override the render is byte-identical, and the design
   widget can override any of them per variant. "Linked by default": the
   fallback IS the link to --accent / --brand; an override breaks just that link. */
/* Variant sheets — @imported here so the component loader (which enqueues only
   buttons.css) pulls them on the same request (widget convention; precedent:
   carousel, tooltip). primitives.css also imports button-icon-slide.css
   directly for the monolith path — the duplicate rules are identical, so the
   cascade is unchanged. */
/* hzo-bundle: skipped @import ./button-icon-slide.cssver1788182399 */

.hzo-btn,
.hzo-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-sm);
    padding: var(--btn-padding-y, 14px) var(--btn-padding-x, 32px);
    font-family: var(--font-body);
    font-size: 1rem;
    font-weight: var(--btn-weight, var(--fw-semibold, 600));
    text-decoration: none;
    border-radius: var(--btn-radius, var(--radius-pill));
    border: var(--border-1) solid transparent;
    cursor: pointer;
    transition: background var(--transition-base), border-color var(--transition-base), color var(--transition-base), box-shadow var(--transition-base), transform 0.1s;
    min-height: 48px;
    line-height: var(--lh-none);
    white-space: nowrap;
    background: var(--btn-primary-bg, var(--accent)) !important;
    color: var(--btn-primary-text, var(--text-on-accent)) !important;
}
/* PF-042: both tokens are DECLARED in tokens.css now, so the inline fallbacks
   below are unreachable — kept only as a record of what this path used to
   resolve to. The old comment here claimed "bg darkens on hover → flip text
   light for contrast"; that assumption was never checked and measured 2.62:1 on
   a live client (and 2.19:1 at the engine's own default accent). See the
   --btn-primary-hover block in tokens.css for the measurements and for what is
   still NOT fixed. */
.hzo-btn:hover, .hzo-btn.is-hover,
.hzo-button:hover, .hzo-button.is-hover {
    background: var(--btn-primary-hover, var(--accent-hover-on-light)) !important;
    color: var(--btn-primary-hover-text, #fff) !important;
}
.hzo-btn:focus-visible, .hzo-btn.is-focus,
.hzo-button:focus-visible, .hzo-button.is-focus {
    outline: 3px solid var(--color-focus);
    outline-offset: 2px;
}
.hzo-btn.is-active, .hzo-btn:active,
.hzo-button.is-active, .hzo-button:active { transform: translateY(1px); }
.hzo-btn.is-disabled, .hzo-btn[disabled], .hzo-btn[aria-disabled="true"],
.hzo-button.is-disabled, .hzo-button[disabled], .hzo-button[aria-disabled="true"] {
    opacity: var(--opacity-disabled);
    pointer-events: none;
}

.hzo-btn--primary,
.hzo-button--primary {
    background: var(--btn-primary-bg, var(--accent)) !important;
    color: var(--btn-primary-text, var(--text-on-accent)) !important;
    border-color: var(--btn-primary-bg, var(--accent));
}
/* PF-042: this path used to darken to a DIFFERENT colour than bare .hzo-btn
   above (--accent-text vs --accent-hover-on-light), because --btn-primary-hover
   was undeclared and the two fallbacks disagreed — so a site got one of two
   hover colours depending on whether the author wrote the variant class.
   Declaring the token reconciles them; these fallbacks are now unreachable. */
.hzo-btn--primary:hover,
.hzo-button--primary:hover {
    background: var(--btn-primary-hover, var(--accent-text)) !important;
    border-color: var(--btn-primary-hover, var(--accent-text));
    color: var(--btn-primary-hover-text, #fff) !important;
}
.hzo-btn--secondary,
.hzo-button--secondary {
    background: var(--btn-secondary-bg, var(--brand)) !important;
    color: var(--btn-secondary-text, var(--text-on-brand)) !important;
    border-color: var(--btn-secondary-bg, var(--brand));
}
.hzo-btn--secondary:hover,
.hzo-button--secondary:hover {
    background: var(--btn-secondary-hover, var(--brand-hover-on-light)) !important;
    border-color: var(--btn-secondary-hover, var(--brand-hover-on-light));
    color: var(--btn-secondary-text, var(--text-on-brand)) !important;
}
.hzo-btn--outline,
.hzo-button--outline {
    background: transparent !important;
    color: var(--btn-outline-color, var(--brand)) !important;
    border-color: var(--btn-outline-color, var(--brand));
}
.hzo-btn--outline:hover,
.hzo-button--outline:hover {
    background: var(--btn-outline-color, var(--brand)) !important;
    color: var(--btn-secondary-text, var(--text-on-brand)) !important;
    border-color: var(--btn-outline-color, var(--brand));
}
.hzo-btn--ghost,
.hzo-button--ghost {
    background: transparent !important;
    color: var(--btn-ghost-color, var(--brand)) !important;
    border-color: transparent;
}
.hzo-btn--ghost:hover,
.hzo-button--ghost:hover {
    background: var(--btn-ghost-hover, var(--brand-pale)) !important;
    color: var(--btn-ghost-color, var(--brand)) !important;
}
/* Tertiary — the quiet FILLED tier, and the missing rung on this ladder.
   Between --outline (a brand-coloured border, which is loud) and --ghost
   (transparent, which has no rest-state affordance at all on a light surface),
   there was nothing: a neutral, obviously-clickable, obviously-subordinate
   action. That gap is why dense UI ("Load more", "Filters", "Back to all
   services", pagination-adjacent actions) kept reaching for --outline and
   putting three brand-coloured borders in one row.

   Neutral by design: it reads --bg-alt / --border / --text-heading, no brand
   colour at all, so it can never compete with the primary. It IS visible at
   rest, which is the whole point and the reason it is not just another ghost.
   Hover deepens the fill and the border together (two properties, per the
   interaction-state rule) using color-mix off its own tokens, so it retints
   automatically with a client's neutrals. */
.hzo-btn--tertiary,
.hzo-button--tertiary {
    background: var(--btn-tertiary-bg, var(--bg-alt)) !important;
    color: var(--btn-tertiary-text, var(--text-heading)) !important;
    border-color: var(--btn-tertiary-border, var(--border));
}
.hzo-btn--tertiary:hover,
.hzo-button--tertiary:hover {
    background: var(--btn-tertiary-hover, color-mix(in srgb, var(--btn-tertiary-bg, var(--bg-alt)) 88%, var(--btn-tertiary-text, var(--text-heading)))) !important;
    color: var(--btn-tertiary-text, var(--text-heading)) !important;
    border-color: var(--btn-tertiary-hover-border, var(--text-muted));
}
.hzo-btn--danger,
.hzo-button--danger {
    background: var(--btn-danger-bg, var(--error)) !important;
    color: #fff !important;
    border-color: var(--btn-danger-bg, var(--error));
}
.hzo-btn--danger:hover,
.hzo-button--danger:hover {
    background: var(--btn-danger-hover, #B91C1C) !important;
    border-color: var(--btn-danger-hover, #B91C1C);
    color: #fff !important;
}

/* A little depth on hover so filled buttons feel tactile instead of flat — a
   default-polish touch; per-client themes still control everything via --btn-*.
   Selector list carries the S4 .hzo-button alias so both vocabularies match. */
.hzo-btn--primary:hover,
.hzo-btn--secondary:hover,
.hzo-btn--danger:hover,
.hzo-button--primary:hover,
.hzo-button--secondary:hover,
.hzo-button--danger:hover { box-shadow: var(--shadow-md); }

.hzo-btn--sm,
.hzo-button--sm { padding: calc(var(--btn-padding-y, 14px) - 4px) calc(var(--btn-padding-x, 32px) - 10px); font-size: 0.875rem; min-height: 36px; }
.hzo-btn--lg,
.hzo-button--lg { padding: calc(var(--btn-padding-y, 14px) + 2px) calc(var(--btn-padding-x, 32px) + 8px); font-size: 1.125rem; min-height: 52px; }

/* Full-width — stretches to the container at all breakpoints. (Mobile already
   stretches non-inline buttons; this opts desktop in too — e.g. a form submit
   or a stacked CTA pair on a narrow card.) */
.hzo-btn--block,
.hzo-button--block { width: 100%; }

/* Icon-only — square button sized to the button height, for a lone icon
   (search / menu / close, nav arrows, social). Pair with --sm / --lg to
   resize. Always intrinsic width (excluded from the mobile full-width rule
   below). The author MUST supply an aria-label — there is no text node. */
.hzo-btn--icon,
.hzo-button--icon { padding: 0; width: 48px; min-width: 48px; }
.hzo-btn--icon.hzo-btn--sm,
.hzo-button--icon.hzo-button--sm { width: 36px; min-width: 36px; }
.hzo-btn--icon.hzo-btn--lg,
.hzo-button--icon.hzo-button--lg { width: 52px; min-width: 52px; }
.hzo-btn--icon > svg,
.hzo-button--icon > svg { width: 20px; height: 20px; }

/* Link button — button semantics, link appearance. For the lowest-emphasis
   inline actions ("Cancel", "Skip"). !important on color/background matches the
   base button rules so it wins the per-client a:visited fight (see the
   bottom-of-file note) — no stale :visited rule needed. */
.hzo-btn--link,
.hzo-button--link {
    background: transparent !important;
    color: var(--brand-text) !important;
    border-color: transparent;
    min-height: 0;
    padding: var(--space-xs) 0;
    border-radius: var(--radius-0);
    text-decoration: underline;
    text-underline-offset: 3px;
}
.hzo-btn--link:hover,
.hzo-button--link:hover {
    background: transparent !important;
    color: var(--brand-hover-on-dark) !important;
    text-decoration-thickness: 2px;
}
.hzo-btn--on-dark.hzo-btn--link,
.hzo-btn--on-dark.hzo-btn--link:hover,
.hzo-button--on-dark.hzo-button--link,
.hzo-button--on-dark.hzo-button--link:hover { color: #fff !important; }

/* Mobile: buttons go full-width by default for easier tapping + stacked
   layout. Opt out with .hzo-btn--inline when a button needs to stay
   intrinsic width on mobile (side-by-side pairs, etc.). Icon + link buttons
   are intrinsic-width by nature, so they're excluded too. */
@media (max-width: 767px) {
    .hzo-btn:not(.hzo-btn--inline):not(.hzo-btn--icon):not(.hzo-btn--link),
.hzo-button:not(.hzo-button--inline):not(.hzo-button--icon):not(.hzo-button--link) { width: 100%; }
}

/* ═══ MODIFIER vs VARIANT — the contract (PF-062) ══════════════════════════
   Read this before adding any class to this file.

   A VARIANT answers "which button is this": --primary, --secondary, --outline,
   --ghost, --tertiary, --link, --danger. Exactly one per button. It sets the
   fill, the ink and the border.

   A MODIFIER answers "under what conditions": --on-dark, --sm, --lg, --block,
   --inline, --icon. Zero or more. It ADJUSTS a variant. It must never be the
   only thing a button carries and expect to look like anything.

   THE FAILURE THIS PREVENTS. Before Wave S every --on-dark rule in this file
   was written as a COMPOUND (.hzo-btn--on-dark.hzo-btn--secondary, …), so
   `class="hzo-btn hzo-btn--on-dark hzo-btn--lg"` matched no on-dark rule at
   all, fell through to the base .hzo-btn, and rendered as the ACCENT FILL —
   a second primary button, sitting next to the real primary, with no visual
   hierarchy between them. Nothing errored. The class was real, the markup
   looked deliberate, the button rendered perfectly. It was visible only in a
   screenshot, and the engine shipped it in its own reference patterns.

   TWO RULES FOLLOW, and both are enforced below:
   1. Every modifier gets a BARE fallback that degrades to something sensible,
      so a missing variant is a slightly-wrong button rather than a silently
      wrong one.
   2. Reference patterns name the variant explicitly. A pattern is a teaching
      surface; it should model the contract, not the shorthand.

   When adding a new modifier to this file, write its bare rule first.
   ═══════════════════════════════════════════════════════════════════════════ */

/* BARE --on-dark FALLBACK (PF-062). Degrades to the on-dark OUTLINE treatment:
   transparent fill, white ink, white border. That is the one answer correct in
   BOTH on-dark contexts — over a photo (where a filled secondary fights the
   image) and over a solid brand band (where an outline is clearly subordinate
   to the accent primary).

   The :not() chain is load-bearing, not defensive noise. This rule is declared
   AFTER the variant rules above, and `.hzo-btn--on-dark` alone is (0,1,0) —
   the same specificity as `.hzo-btn--primary`. Without the exclusions it would
   win on source order and silently turn every correctly-authored
   `--primary --on-dark` button into an outline. Every variant in this file
   must appear in the chain; add new ones here when you add them above. */
.hzo-btn--on-dark:not(.hzo-btn--primary):not(.hzo-btn--secondary):not(.hzo-btn--tertiary):not(.hzo-btn--outline):not(.hzo-btn--ghost):not(.hzo-btn--link):not(.hzo-btn--danger),
.hzo-button--on-dark:not(.hzo-button--primary):not(.hzo-button--secondary):not(.hzo-button--tertiary):not(.hzo-button--outline):not(.hzo-button--ghost):not(.hzo-button--link):not(.hzo-button--danger) {
    background: transparent !important;
    color: var(--btn-on-dark-accent, #fff) !important;
    border-color: var(--btn-on-dark-accent, #fff);
}
.hzo-btn--on-dark:not(.hzo-btn--primary):not(.hzo-btn--secondary):not(.hzo-btn--tertiary):not(.hzo-btn--outline):not(.hzo-btn--ghost):not(.hzo-btn--link):not(.hzo-btn--danger):hover,
.hzo-button--on-dark:not(.hzo-button--primary):not(.hzo-button--secondary):not(.hzo-button--tertiary):not(.hzo-button--outline):not(.hzo-button--ghost):not(.hzo-button--link):not(.hzo-button--danger):hover {
    background: var(--btn-on-dark-accent, #fff) !important;
    color: var(--btn-on-dark-text, var(--brand)) !important;
}

/* Dark-section button overrides.
   On brand-colored dark sections, the SECONDARY button (now blue) would
   disappear since it shares the section's color. Flip it to a white button
   with brand text for clear visibility. Primary (green) stays green — it
   has plenty of contrast against blue — and its hover gets the lighter
   on-dark variant for extra feedback. !important on color/background
   matches base rules so on-dark variants win the :visited fight. */
.hzo-btn--on-dark.hzo-btn--primary:hover,
.hzo-button--on-dark.hzo-button--primary:hover {
    /* Palest-accent fill on dark — accent family, lightest stop (--accent-pale);
       the dark accent-on-fill text (--text-on-accent) reads cleanly on it. */
    background: var(--accent-pale) !important;
    border-color: var(--accent-pale);
    color: var(--text-on-accent) !important;
}
.hzo-btn--on-dark.hzo-btn--secondary,
.hzo-button--on-dark.hzo-button--secondary {
    background: var(--btn-on-dark-bg, #fff) !important;
    color: var(--btn-on-dark-text, var(--brand)) !important;
    border-color: var(--btn-on-dark-bg, #fff);
}
.hzo-btn--on-dark.hzo-btn--secondary:hover,
.hzo-button--on-dark.hzo-button--secondary:hover {
    /* Clear branded tint away from white so the hover obviously reads. */
    background: color-mix(in srgb, var(--btn-on-dark-bg, #ffffff) 75%, var(--btn-on-dark-text, var(--brand))) !important;
    border-color: color-mix(in srgb, var(--btn-on-dark-bg, #ffffff) 75%, var(--btn-on-dark-text, var(--brand)));
    color: var(--brand-hover-on-light) !important;
}
.hzo-btn--on-dark.hzo-btn--outline,
.hzo-button--on-dark.hzo-button--outline {
    color: var(--btn-on-dark-accent, #fff) !important;
    border-color: var(--btn-on-dark-accent, #fff);
    background: transparent !important;
}
.hzo-btn--on-dark.hzo-btn--outline:hover,
.hzo-button--on-dark.hzo-button--outline:hover {
    background: var(--btn-on-dark-accent, #fff) !important;
    color: var(--btn-on-dark-text, var(--brand)) !important;
}
/* Tertiary on dark — the same "quiet but visible" job, inverted: a low-opacity
   white veil rather than the light neutral fill, which would read as a second
   white (secondary) button on a dark band. */
.hzo-btn--on-dark.hzo-btn--tertiary,
.hzo-button--on-dark.hzo-button--tertiary {
    background: rgba(255, 255, 255, 0.14) !important;
    color: var(--btn-on-dark-accent, #fff) !important;
    border-color: rgba(255, 255, 255, 0.34);
}
.hzo-btn--on-dark.hzo-btn--tertiary:hover,
.hzo-button--on-dark.hzo-button--tertiary:hover {
    background: rgba(255, 255, 255, 0.26) !important;
    color: var(--btn-on-dark-accent, #fff) !important;
    border-color: rgba(255, 255, 255, 0.55);
}
.hzo-btn--on-dark.hzo-btn--ghost,
.hzo-button--on-dark.hzo-button--ghost { color: var(--btn-on-dark-accent, #fff) !important; }
.hzo-btn--on-dark.hzo-btn--ghost:hover,
.hzo-button--on-dark.hzo-button--ghost:hover {
    background: rgba(255,255,255,0.2) !important;
    color: var(--btn-on-dark-accent, #fff) !important;
}

/* Loading state — add `is-loading` + `aria-busy="true"`. The label is hidden
   (color → transparent) and a spinner is painted centered; the label still
   occupies space so the button keeps its width and doesn't reflow. pointer-
   events off blocks a double-submit. The ring color is set per variant so it
   reads on each background. */
.hzo-btn.is-loading,
.hzo-button.is-loading {
    color: transparent !important;
    pointer-events: none;
    position: relative;
}
.hzo-btn.is-loading::after,
.hzo-button.is-loading::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 18px;
    height: 18px;
    margin: -9px 0 0 -9px;
    border-radius: 50%;
    /* Default: dark ring, reads on the accent/primary fill. */
    border: var(--border-2) solid color-mix(in srgb, var(--text-on-accent) 30%, transparent);
    border-top-color: var(--text-on-accent);
    animation: hzo-spin 0.6s linear infinite;
}
/* Filled-dark variants → white ring. */
.hzo-btn--secondary.is-loading::after,
.hzo-btn--danger.is-loading::after,
.hzo-button--secondary.is-loading::after,
.hzo-button--danger.is-loading::after {
    border-color: rgba(255, 255, 255, 0.35);
    border-top-color: #fff;
}
/* Transparent variants → brand ring. */
.hzo-btn--outline.is-loading::after,
.hzo-btn--ghost.is-loading::after,
.hzo-btn--link.is-loading::after,
.hzo-button--outline.is-loading::after,
.hzo-button--ghost.is-loading::after,
.hzo-button--link.is-loading::after {
    border-color: color-mix(in srgb, var(--brand) 30%, transparent);
    border-top-color: var(--brand);
}
@media (prefers-reduced-motion: reduce) {
    .hzo-btn.is-loading::after,
.hzo-button.is-loading::after { animation-duration: 1.5s; }
}

/* Added confirmation (add-to-cart) — briefly swaps the label for a success check
   after a successful add. Mirrors `.is-loading`: label hidden, glyph centered, no
   reflow, pointer-events off. The JS driver (buttons.js, this folder) toggles it on, fires
   hzo.toast('Added to cart'), then reverts to default after a short window. It's a
   state swap, not an animation, so it's reduced-motion-safe by construction. An
   integration wanting a persistent state sets the label to "In cart" instead. */
.hzo-btn.is-added,
.hzo-button.is-added {
    color: transparent !important;
    pointer-events: none;
    position: relative;
}
.hzo-btn.is-added::after,
.hzo-button.is-added::after {
    content: "\2713"; /* ✓ */
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 1.15em;
    font-weight: var(--fw-700);
    line-height: var(--lh-none);
    color: var(--text-on-accent); /* reads on the primary/accent fill */
}
.hzo-btn--secondary.is-added::after,
.hzo-btn--danger.is-added::after,
.hzo-button--secondary.is-added::after,
.hzo-button--danger.is-added::after { color: #fff; }
.hzo-btn--outline.is-added::after,
.hzo-btn--ghost.is-added::after,
.hzo-btn--link.is-added::after,
.hzo-button--outline.is-added::after,
.hzo-button--ghost.is-added::after,
.hzo-button--link.is-added::after { color: var(--brand); }
/* Tertiary's fill is neutral, so the check takes the neutral heading ink
   rather than the brand (which would be the only brand-coloured thing on an
   intentionally brand-free button). */
.hzo-btn--tertiary.is-added::after,
.hzo-button--tertiary.is-added::after { color: var(--btn-tertiary-text, var(--text-heading)); }


/* --- callback/callback.css --- */
/* ═══════════════════════════════════════════════════════════════════════════
   Instant Callback Request (.hzo-callback) — W-041

   The lowest-friction phone-first lead form: masked tel field + "Call me back",
   optional when-window chips (native radios), a promise line that swaps to
   after-hours copy, and a success surface (countdown / thank-you / tel). The
   default strip is a single column; layout=inline packs field+CTA on one row
   (auto-collapsing on a narrow container, no JS); layout=card wraps it in a
   surface panel.

   Tokens ONLY — --space-* / --radius-* / --shadow-* / --dur-* / --ease-* /
   --border-* / --text-* / --surface / --accent* / --error* / --focus-ring /
   --fs-* / --lh-*. No hardcoded hex; px only for hairline borders / icon sizing
   where existing components already do. The widget renders no top-layer surface,
   so it claims NO z-index (inline errors never elevate above --z-toast).

   GATING: ALWAYS-LOAD (callback.php un-gates the shortcode) — the CSS ships
   wherever the shortcode can render (sticky/hero/footer).
   ═══════════════════════════════════════════════════════════════════════════ */

.hzo-callback {
    container-type: inline-size;
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    margin: 0;
    max-width: 32rem;
    text-align: start;
}
.hzo-callback--align-center { margin-inline: auto; align-items: stretch; text-align: center; }
.hzo-callback--align-start  { margin-inline: 0; }

/* ── Card layout: elevate onto a surface panel ── */
.hzo-callback--card {
    padding: var(--space-lg);
    background: var(--surface);
    border: var(--border-1) solid var(--border);
    border-radius: var(--radius-2);
    box-shadow: var(--shadow-2);
}

.hzo-callback__heading {
    margin: 0;
    font-size: var(--fs-500);
    line-height: var(--lh-snug);
    color: var(--text-heading);
}

/* ── Promise line (in-hours ↔ after-hours copy swap target) ── */
.hzo-callback__promise {
    margin: 0;
    color: var(--text-body);
    font-size: var(--fs-200);
    line-height: var(--lh-normal);
    transition: color var(--dur-1) var(--ease-standard);
}
.hzo-callback.is-after-hours .hzo-callback__promise { color: var(--text-muted); }

/* ── Field ── */
.hzo-callback__field {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
}
.hzo-callback__label {
    font-size: var(--fs-100);
    font-weight: 600;
    color: var(--text-heading);
    line-height: var(--lh-snug);
}
.hzo-callback__input {
    width: 100%;
    padding: var(--space-smd) var(--space-md);
    font-size: var(--fs-300);          /* ≥16px avoids iOS zoom-on-focus */
    font-family: var(--font-body);
    color: var(--text-body);
    background: var(--bg-page);
    border: var(--border-1) solid var(--input-border, var(--border));
    border-radius: var(--radius-1);
    min-height: 48px;
    transition: border-color var(--dur-1) var(--ease-standard), box-shadow var(--dur-1) var(--ease-standard);
}
.hzo-callback__input:focus-visible {
    outline: var(--border-2) solid var(--focus-ring);
    outline-offset: var(--space-2xs);
    border-color: var(--focus-ring);
}
/* Visible error border (never invisible — G49) + error-tinted ring. */
.hzo-callback__input--error,
.hzo-callback__input[aria-invalid="true"] {
    border-color: var(--error);
}
.hzo-callback__input--error:focus-visible,
.hzo-callback__input[aria-invalid="true"]:focus-visible {
    outline-color: var(--error);
    border-color: var(--error);
}

.hzo-callback__error {
    color: var(--error-ink);
    font-size: var(--fs-100);
    line-height: var(--lh-snug);
}

/* ── When-window chips (native radios styled as chips; APG radiogroup) ── */
.hzo-callback__window {
    margin: 0;
    padding: 0;
    border: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}
.hzo-callback__legend {
    padding: 0;
    font-size: var(--fs-100);
    font-weight: 600;
    color: var(--text-heading);
}
.hzo-callback__chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
}
.hzo-callback__chip {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;                 /* comfortable tap height */
    padding: var(--space-sm) var(--space-md);
    border: var(--border-1) solid var(--border);
    border-radius: var(--radius-full);
    color: var(--text-body);
    background: var(--bg-page);
    font-size: var(--fs-100);
    line-height: 1;
    cursor: pointer;
    transition: border-color var(--dur-1) var(--ease-standard),
                background var(--dur-1) var(--ease-standard),
                color var(--dur-1) var(--ease-standard);
}
.hzo-callback__chip:hover { border-color: var(--accent-text); }
/* The real radio is visually hidden but focus lands on it → ring the label. */
.hzo-callback__chip-input {
    position: absolute;
    opacity: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    inset: 0;
    cursor: pointer;
}
.hzo-callback__chip:has(.hzo-callback__chip-input:checked) {
    background: var(--accent-pale);
    border-color: var(--accent-text);
    color: var(--accent-text);
    font-weight: 600;
}
.hzo-callback__chip:has(.hzo-callback__chip-input:focus-visible) {
    outline: var(--border-2) solid var(--focus-ring);
    outline-offset: var(--space-2xs);
}
.hzo-callback__chip:active { transform: translateY(1px); }

/* ── Consent (TCPA) ── */
.hzo-callback__consent { font-size: var(--fs-75); color: var(--text-muted); line-height: var(--lh-normal); }
.hzo-callback__consent-label {
    display: flex;
    align-items: flex-start;
    gap: var(--space-sm);
    cursor: pointer;
}
.hzo-callback__consent-box {
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    margin-top: 2px;
    accent-color: var(--accent-text);
}
.hzo-callback__consent-box:focus-visible {
    outline: var(--border-2) solid var(--focus-ring);
    outline-offset: var(--space-2xs);
}
.hzo-callback__consent-text { margin: 0; }

/* ── Honeypot — off-screen, never shown, never announced ── */
.hzo-callback__hp {
    position: absolute !important;
    left: -9999px !important;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* ── Submit CTA (extends .hzo-btn --primary; adds a busy spinner) ── */
.hzo-callback__submit {
    position: relative;
    width: 100%;
}
.hzo-callback--inline .hzo-callback__submit { width: auto; }
.hzo-callback__spinner {
    display: none;
    width: 1em;
    height: 1em;
    margin-inline-start: var(--space-sm);
    border: var(--border-2) solid currentColor;
    border-top-color: transparent;
    border-radius: var(--radius-full);
    animation: hzo-callback-spin var(--dur-4) linear infinite;
}
.hzo-callback__submit.is-loading .hzo-callback__spinner { display: inline-block; }
@keyframes hzo-callback-spin { to { transform: rotate(360deg); } }

/* ── Inline layout: field + CTA share a row; collapses on a narrow container ── */
.hzo-callback--inline {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-end;
    max-width: none;
}
.hzo-callback--inline .hzo-callback__promise { flex: 1 0 100%; }
.hzo-callback--inline .hzo-callback__field { flex: 1 1 12rem; }
@container (max-width: 26rem) {
    .hzo-callback--inline { flex-direction: column; align-items: stretch; }
    .hzo-callback--inline .hzo-callback__submit { width: 100%; }
}

/* ── Success surface (role=status; server ships it hidden + empty) ── */
.hzo-callback__success {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    align-items: inherit;
}
.hzo-callback--align-center .hzo-callback__success { align-items: center; text-align: center; }
.hzo-callback__success-title {
    margin: 0;
    font-size: var(--fs-400);
    line-height: var(--lh-snug);
    color: var(--text-heading);
}
.hzo-callback__success-title:focus-visible {
    outline: var(--border-2) solid var(--focus-ring);
    outline-offset: var(--space-2xs);
    border-radius: var(--radius-1);
}
.hzo-callback__success-text { margin: 0; color: var(--text-body); font-size: var(--fs-200); }

/* success=tel — a real, tappable, ≥44px tel link (never auto-dials) ── */
.hzo-callback__success-tel {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 48px;
    padding: var(--space-smd) var(--space-lg);
    border-radius: var(--radius-full);
    background: var(--accent-pale);
    color: var(--accent-text);
    font-weight: 600;
    text-decoration: none;
    transition: background var(--dur-1) var(--ease-standard);
}
.hzo-callback__success-tel:hover { background: var(--accent); color: var(--text-on-accent); }
.hzo-callback__success-tel:focus-visible {
    outline: var(--border-2) solid var(--focus-ring);
    outline-offset: var(--space-2xs);
}

/* ── Countdown ring (decorative; number carries the meaning) ── */
.hzo-callback__countdown {
    --hzo-cb-progress: 0;
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-xs);
}
.hzo-callback__countdown-num {
    display: grid;
    place-items: center;
    width: 4.5rem;
    height: 4.5rem;
    border-radius: var(--radius-full);
    font-size: var(--fs-500);
    font-family: var(--font-number, var(--font-body));
    font-weight: 700;
    color: var(--accent-text);
    /* Decorative progress dial: an accent wedge grows over a quiet track as the
       countdown elapses (--hzo-cb-progress 0→1). The inner disc keeps the number
       legible. Purely visual — the number/text carry the meaning. */
    background:
        radial-gradient(var(--surface) 54%, transparent 55%),
        conic-gradient(var(--accent) calc(var(--hzo-cb-progress) * 360deg), var(--border) 0deg);
}
.hzo-callback__countdown-text { color: var(--text-body); font-size: var(--fs-100); }

/* ═══ Reduced motion — spinner slows (never freezes), ring goes static ═══════ */
@media (prefers-reduced-motion: reduce) {
    .hzo-callback__spinner { animation-duration: 2s; }
    .hzo-callback__countdown-num { background: var(--surface-2); }
    .hzo-callback__promise,
    .hzo-callback__input,
    .hzo-callback__chip,
    .hzo-callback__success-tel { transition: none; }
}

/* ═══ Forced colors — real borders + aria-invalid carry state (never shadow/
   opacity alone); the ring falls back to the static remaining-time text ═══════ */
@media (forced-colors: active) {
    .hzo-callback__input { border: 1px solid ButtonBorder; }
    .hzo-callback__input[aria-invalid="true"] { border-color: Mark; }
    .hzo-callback__chip { border: 1px solid ButtonBorder; }
    .hzo-callback__chip:has(.hzo-callback__chip-input:checked) { border-color: Highlight; }
    .hzo-callback__countdown-num { background: ButtonFace; forced-color-adjust: none; }
}

/* ═══ Print — force the form + promise + consent visible; hide the ring; keep
   the success tel number so a printed estimate keeps the callback path ═══════ */
@media print {
    .hzo-callback,
    .hzo-callback__promise,
    .hzo-callback__consent { display: flex !important; }
    .hzo-callback__spinner,
    .hzo-callback__countdown-num { display: none !important; }
    .hzo-callback__success[hidden] { display: block !important; }
}


/* --- callout/callout.css --- */
/* callout — @tokens: --callout-gap, --callout-padding, --callout-radius

   A static inline note box for content emphasis (info / tip / success / warning
   / danger): a tinted panel with a coloured left accent bar. Distinct from the
   `.hzo-alert` feedback component — alert is dismissible UI feedback; callout is
   permanent editorial emphasis inside prose. Primitive: CSS-only, not
   interactive (no focus/disabled idioms).

   Token-only: the base bar reads --brand and the tint reads --bg-alt; each
   modifier swaps to a state colour + its matching -bg tint (all four state
   colours have a -bg pair in tokens.css). The accent-bar width has no semantic
   role, so it's a component token with a --border-3 fallback (the icon-button.css
   dial pattern). Gap + padding follow the same fallback shape. */
.hzo-callout {
    display: flex;
    gap: var(--callout-gap, var(--space-md));
    padding: var(--callout-padding, var(--space-md));
    border-radius: var(--callout-radius, var(--radius-panel));
    border-left: var(--callout-bar-width, var(--border-3)) solid var(--brand);
    background: var(--bg-alt);
    color: var(--text-body);
}

/* Optional leading glyph — sits in the numeral/heading ink so it reads as chrome,
   not body copy. Sized to the body line-box; doesn't stretch in the flex row. */
.hzo-callout__icon {
    flex: 0 0 auto;
    line-height: var(--lh-none);
    color: var(--text-heading);
}

/* Text stack — title + body live in a column beside the icon. */
.hzo-callout__title {
    font-weight: var(--fw-600);
    line-height: var(--lh-snug);
    color: var(--text-heading);
}
.hzo-callout__body {
    line-height: var(--lh-normal);
    color: var(--text-body);
}

/* Intent modifiers — recolor the accent bar + tint together. Each reads its
   state colour and paired -bg tint (no borrowed tokens). Danger maps to the
   error role. */
.hzo-callout--info    { border-left-color: var(--info);    background: var(--info-bg); }
.hzo-callout--success { border-left-color: var(--success); background: var(--success-bg); }
.hzo-callout--warning { border-left-color: var(--warning); background: var(--warning-bg); }
.hzo-callout--danger  { border-left-color: var(--error);   background: var(--error-bg); }


/* --- card/card.css --- */
/* ─── Card (family manifest) ──────────────────────────────────────────
   The card family is split across sheets: base rules (card-base.css),
   full-bleed media (card-media.css), depth/layout/link-card variants
   (card-variants.css), the category tile (category-card.css), and the
   grid-level equal-height helper (equal-height-cards.css).

   The loader enqueues ONLY <name>.css, so this file @imports every split
   sheet to give it a load path on the same request (the wave-4 video.css
   lesson: a variant sheet without an @import here is dead CSS in
   components mode — before this manifest, all four variant sheets only
   loaded via the assets/css/primitives.css fallback).

   Import order mirrors primitives.css (card.css → card-media →
   card-variants → category-card → equal-height-cards). card-variants
   overrides base .hzo-card declarations at EQUAL specificity
   (--bordered's box-shadow:none, --horizontal's flex-direction:row), so
   base MUST come first — and since CSS requires @imports before any
   rule, the base rules live in card-base.css rather than here. */
/* hzo-bundle: skipped @import ./card-base.cssver1788182399 */
/* hzo-bundle: skipped @import ./card-media.cssver1788182399 */
/* hzo-bundle: skipped @import ./card-variants.cssver1788182399 */
/* hzo-bundle: skipped @import ./category-card.cssver1788182399 */
/* hzo-bundle: skipped @import ./equal-height-cards.cssver1788182399 */


/* --- carousel/carousel.css --- */
/* ─── Carousel ─────────────────────────────────────────────────────────
   Core accessible carousel (one slide per view): prev/next + arrow keys,
   aria-roledescription, per-slide labels, inactive slides inert. Optional
   opt-in autoplay (data-hzo-carousel-autoplay) pauses on hover/focus and is
   disabled under reduced-motion; it needs a [data-hzo-carousel-toggle] pause
   button (WCAG 2.2.2). Touch-swipe, infinite-loop (manual), and dot indicators
   are deferred to Wave E.1. Wired by the carousel IIFE in main.js. Markup
   contract: component-patterns §38. Reuses existing tokens; zero new tokens.

   Phase-2 widget: the option contract (per-view/gap/pagination/effect/mousewheel/
   …) lives in carousel.js + carousel.php. The variant sheets are @imported here
   so the component loader (which enqueues only carousel.css) pulls them on demand. */
/* hzo-bundle: skipped @import ./carousel-crossfade.cssver1788182399 */

.hzo-carousel { position: relative; }
.hzo-carousel__viewport { overflow: hidden; touch-action: pan-y; }  /* swipe horiz, keep vertical page scroll */
.hzo-carousel__track {
    display: flex;
    gap: var(--hzo-cv-gap, 0px);   /* px between slides; JS sets --hzo-cv-gap */
    transition: transform var(--hzo-cv-speed, var(--transition-base)) ease;
}
/* Per-view sizing: JS sets --hzo-cv-per-view / --hzo-cv-gap; the no-JS fallback
   (1 / 0px) resolves to a single 100%-wide slide, so markup still degrades to a
   plain horizontal scroller. */
.hzo-carousel__slide {
    flex: 0 0 calc((100% - (var(--hzo-cv-per-view, 1) - 1) * var(--hzo-cv-gap, 0px)) / var(--hzo-cv-per-view, 1));
    min-width: 0;
}

.hzo-carousel__btn {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border: 0;
    border-radius: var(--radius-full);
    background: var(--surface, #fff);
    color: var(--text-heading);
    box-shadow: var(--shadow-md);
    cursor: pointer;
    z-index: 1;
    transition: background var(--transition-base) ease;
}
.hzo-carousel__btn:hover { background: var(--bg-alt); }
.hzo-carousel__btn:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
.hzo-carousel__btn--prev { left: 8px; }
.hzo-carousel__btn--next { right: 8px; }
.hzo-carousel__btn[disabled] { opacity: 0.4; cursor: default; box-shadow: var(--shadow-sm); }
.hzo-carousel__btn > svg { width: 22px; height: 22px; display: block; }

.hzo-carousel__toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-top: var(--space-md);
    padding: 6px var(--space-smd);
    border: var(--border-1) solid var(--border);
    border-radius: var(--radius-full);
    background: var(--surface, #fff);
    color: var(--text-body);
    font-size: var(--fs-75);
    cursor: pointer;
}
.hzo-carousel__toggle:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }

/* Dot indicators (generated by the carousel IIFE). 24px hit area, 10px visual dot. */
.hzo-carousel__dots { display: flex; justify-content: center; gap: var(--space-xs); margin-top: var(--space-md); }
.hzo-carousel__dot {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: pointer;
}
.hzo-carousel__dot::before {
    content: "";
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--carousel-dot-color, var(--border));
    transition: background var(--transition-base) ease, transform var(--transition-base) ease;
}
.hzo-carousel__dot[aria-current="true"]::before { background: var(--carousel-dot-active-color, var(--brand)); transform: scale(1.3); }
.hzo-carousel__dot:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 1px; border-radius: 50%; }

/* Progress-ring dots — the active dot draws a circular countdown matching the
   autoplay interval (opt-in: data-hzo-carousel-ring-dots + autoplay; the JS
   injects the SVG and sets --carousel-ring-duration from the autoplay ms).
   The animation is keyed to [aria-current] so it restarts naturally on every
   slide change; .is-paused (hover/focus/toggle) freezes it mid-draw. Ring
   color follows the active dot color. Hidden entirely under reduced motion
   (autoplay is off there anyway). */
.hzo-carousel__dots--ring .hzo-carousel__dot { position: relative; }
.hzo-carousel__dot-ring {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    transform: rotate(-90deg);
    pointer-events: none;
}
.hzo-carousel__dot-ring circle {
    fill: none;
    stroke: var(--carousel-ring-color, var(--carousel-dot-active-color, var(--brand)));
    stroke-width: 2;
    stroke-dasharray: 62.83;   /* 2πr, r=10 in the 28×28 viewBox */
    stroke-dashoffset: 62.83;
    stroke-linecap: round;
}
.hzo-carousel__dot[aria-current="true"] .hzo-carousel__dot-ring circle {
    animation: hzo-ring-progress var(--carousel-ring-duration, 6000ms) linear forwards;
}
.hzo-carousel__dots--ring.is-paused .hzo-carousel__dot-ring circle { animation-play-state: paused; }
@keyframes hzo-ring-progress { to { stroke-dashoffset: 0; } }
@media (prefers-reduced-motion: reduce) {
    .hzo-carousel__dot-ring { display: none; }
}

/* Fraction pagination ("3 / 8") — a compact status line under the deck. */
.hzo-carousel__fraction {
    margin-top: var(--space-md);
    text-align: center;
    font-size: 13px;
    font-variant-numeric: tabular-nums;
    color: var(--text-muted);
    letter-spacing: 0.02em;
}

/* Progress-bar pagination — a track that fills as you advance. */
.hzo-carousel__progress {
    margin-top: var(--space-md);
    height: 3px;
    border-radius: var(--radius-pill);
    background: var(--carousel-dot-color, var(--border));
    overflow: hidden;
}
.hzo-carousel__progress-bar {
    display: block;
    height: 100%;
    width: 100%;
    transform-origin: left center;
    transform: scaleX(0);
    background: var(--carousel-dot-active-color, var(--brand));
    transition: transform var(--transition-base) ease;
}

@media (prefers-reduced-motion: reduce) {
    .hzo-carousel__track,
    .hzo-carousel__btn,
    .hzo-carousel__progress-bar,
    .hzo-carousel__dot::before { transition: none; }
}


/* --- category-grid/category-grid.css --- */
/* ─── Category grid ───────────────────────────────────────────────────
   Section pattern: shop-by-category (or a locations index) in two shapes —
   a multi-up grid of clickable category cards (image, name, item count;
   blocks/category-grid.html) or the connected tile-collage slab (photo
   tiles + scrim + label + hover zoom; blocks/category-tiles.html).
   Composes always-loaded pieces — .hzo-card / .hzo-cat-card /
   .hzo-card__media (components/card), .hzo-ribbon (components/ribbon),
   .hzo-grid (_base/responsive-content-grid), .hzo-eyebrow /
   .hzo-visual-h2 (base type) — and owns only the tile-collage variant,
   split into tile-collage.css.

   The loader enqueues ONLY <name>.css, so this file @imports the split
   sheet to give it a load path on the same request (the wave-4 video.css
   lesson: a variant sheet without an @import here is dead CSS in
   components mode; before this file existed, tile-collage.css only ever
   loaded through the no-components primitives.css fallback).

   .hzo-category-grid / .hzo-category-grid--cards / .hzo-category-grid--tiles
   on the [hzo_category_grid] shortcode root are structural hooks (unstyled
   by design — the pieces above carry all the visuals). */
/* hzo-bundle: skipped @import ./tile-collage.cssver1788182399 */


/* --- check-list/check-list.css --- */
/* check-list — a checklist with token-tinted circular check marks. One column on
   mobile, two from the 600px breakpoint. CSS-only; items are `<li>` children.
   Token-only (space + brand-pale fill + accent-text check); brand-agnostic. */
.hzo-check-list {
    list-style: none;
    margin: var(--space-lg) 0 0;
    padding: 0;
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-sm) var(--space-lg);
}
@media (min-width: 600px) {
    .hzo-check-list { grid-template-columns: 1fr 1fr; }
}
.hzo-check-list li {
    position: relative;
    padding-left: 1.9em;
    color: var(--text-body);
    font-weight: var(--fw-medium, 500);
    line-height: 1.4;
}
.hzo-check-list li::before {
    content: "\2713";
    position: absolute;
    left: 0;
    top: -0.05em;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.35em;
    height: 1.35em;
    background: var(--brand-pale);
    color: var(--accent-text);
    border-radius: 50%;
    font-size: 0.8em;
    font-weight: 700;
}


/* --- chip/chip.css --- */
/* chip — @tokens: --chip-padding-x, --chip-padding-y

   A compact interactive pill: filter / selection token / removable tag. Sibling
   to the static `.hzo-badge` (a chip is INTERACTIVE — hover / selected / focus /
   removable — so it lives on its own). Composes inside `.hzo-tag-group`.

   Token-only: the control's padding pair has no semantic token in the scale, so
   it's a component token with a literal fallback — the same dial pattern
   icon-button.css / buttons.css use. Everything else reads a semantic/L1 token. */
.hzo-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    padding: var(--chip-padding-y, 4px) var(--chip-padding-x, 12px);
    border: var(--border-1) solid transparent;
    border-radius: var(--radius-full);
    background: var(--bg-alt);
    color: var(--text-body);
    font-size: var(--fs-50);
    line-height: var(--lh-none);
    cursor: pointer;
    transition: background var(--transition-base) ease, color var(--transition-base) ease, border-color var(--transition-base) ease;
}

/* Leading dot / icon — optional status swatch or glyph. */
.hzo-chip__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.hzo-chip__icon > svg { display: block; }

/* Trailing × — its OWN focusable <button>, so it gets its own focus ring. */
.hzo-chip__remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    background: transparent;
    color: inherit;
    border-radius: var(--radius-full);
    opacity: var(--opacity-muted);
    cursor: pointer;
    transition: opacity var(--transition-base) ease;
}
.hzo-chip__remove:hover { opacity: 1; }
.hzo-chip__remove > svg { display: block; }

/* Hover — border firms up to signal interactivity (ghost, no fill). */
.hzo-chip:hover {
    border-color: var(--border);
    color: var(--text-heading);
}

/* Selected — brand fill with on-brand ink. Two spellings of one meaning: the
   `--selected` modifier for static markup, `.is-active` for the JS-toggled state. */
.hzo-chip--selected,
.hzo-chip.is-active {
    background: var(--brand);
    color: var(--text-on-brand);
    border-color: var(--brand);
}

/* A11y — required. Focus ring on the chip AND on the remove control; disabled
   state. Reduced-motion is handled globally by components/_base/a11y.css. */
.hzo-chip:focus-visible,
.hzo-chip__remove:focus-visible {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
}
.hzo-chip[disabled],
.hzo-chip[aria-disabled="true"],
.hzo-chip.is-disabled {
    opacity: var(--opacity-disabled);
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    .hzo-chip,
    .hzo-chip__remove { transition: none; }
}


/* --- collage/collage.css --- */
/* collage — a photo-montage grid: one lead image spanning the full width across
   the top, the remaining images in an even two-up grid below. CSS-only layout
   primitive; images are direct children (`<img>`), so no per-image class is
   required. Token-only (space + card radius); brand-agnostic. */
/* ── ONE COMPOSITION (Wave S) ────────────────────────────────────────────
   This component ships exactly ONE composition: one full-width lead image above an even two-up grid.

   That is a feature. One composition is why it is responsive, accessible and
   identical on two hundred sites, and why a client build can place it without
   re-deciding anything. It is also a hard limit: the component cannot express
   a SECOND composition, and making it try — nesting it, stacking modifiers on
   it, overriding its grid from a client sheet — is how a page ends up fighting
   its own components and still looking generic.

   FOR AN AMPLIFY OR ELEVATE SECTION, DO NOT BEND THIS COMPONENT. Take one of:
     • RAW mode — author the section's markup directly, or
     • hand-author to the class contract on the spatial utilities:
       .hzo-grid + .hzo-span-* / .hzo-rowspan-* / --hzo-grid-template,
       .hzo-bleed, .hzo-pull-* / .hzo-z-*, .hzo-section--edge-*, .hzo-measure-*.
   Both are the STANDARD route for a high-intensity section, not an escape
   hatch, and the composition plan is what says which sections those are.

   PLUMBING AND PROOF COMPONENTS ARE UNAFFECTED. Every JS widget and every
   proof/data component (reviews, credentials, ratings, schema) stays
   mandatory — never hand-roll those. This note is about LAYOUT only.
   ────────────────────────────────────────────────────────────────────────── */

.hzo-collage {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-sm);
}
.hzo-collage img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: var(--radius-card, 12px);
    display: block;
}
.hzo-collage > img:first-child { grid-column: 1 / -1; aspect-ratio: 16 / 9; }
.hzo-collage > img:not(:first-child) { aspect-ratio: 1 / 1; }


/* --- combobox/combobox.css --- */
/* combobox — @tokens: --combobox-max-height

   A searchable / autocomplete select: a text `__input` styled like a select
   (with a chevron) over a floating `__listbox` of `__option`s. CSS STRUCTURE
   ONLY — the search/filter behavior + `.is-open` toggle + `.is-active`
   roving-focus come later from JS, like the other interactive components. Its
   own `.hzo-combobox` root — it mirrors the dropdown panel idiom (surface /
   border / shadow-lg) without colliding with `.hzo-dropdown` or `.hzo-form`.

   Token-only: the panel's scroll cap has no semantic token in the scale, so
   it's a component token with a literal fallback — the same dial pattern
   icon-button.css uses. The chevron is a `background` SVG whose stroke is the
   muted-ink literal (matches the .hzo-form select chevron idiom). Everything
   else reads a semantic/L1 token. */
.hzo-combobox {
    position: relative;
}

/* Trigger — a text input painted like a select; chevron room on the right. */
.hzo-combobox__input {
    width: 100%;
    padding: var(--space-sm) var(--space-xl) var(--space-sm) var(--space-md);
    border: var(--border-1) solid var(--border);
    border-radius: var(--radius-control);
    background: var(--surface) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>") no-repeat right var(--space-md) center / 16px 16px;
    color: var(--text-body);
    font-family: var(--font-body);
    font-size: var(--fs-200);
    line-height: var(--lh-normal);
    cursor: text;
    box-sizing: border-box;
    transition: border-color var(--transition-base) ease;
}

/* Floating panel — mirrors the .hzo-dropdown__menu surface idiom; hidden until
   JS adds `.is-open` to the root. */
.hzo-combobox__listbox {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: var(--z-500);
    margin-top: var(--space-2xs);
    max-height: var(--combobox-max-height, 260px);
    overflow: auto;
    list-style: none;
    padding: 0;
    background: var(--surface);
    border: var(--border-1) solid var(--border);
    border-radius: var(--radius-panel);
    box-shadow: var(--shadow-lg);
    display: none;
}
.hzo-combobox.is-open .hzo-combobox__listbox { display: block; }

/* Option row — hover / keyboard-active / selected all share the alt-surface
   highlight. `.is-active` is the JS-driven roving-focus row; `[aria-selected]`
   is the committed choice. */
.hzo-combobox__option {
    padding: var(--space-sm) var(--space-md);
    color: var(--text-body);
    cursor: pointer;
}
.hzo-combobox__option:hover,
.hzo-combobox__option.is-active,
.hzo-combobox__option[aria-selected="true"] {
    background: var(--bg-alt);
    color: var(--text-heading);
}

/* A11y — required. Focus ring on the input; disabled state on the input.
   Reduced-motion is handled globally by components/_base/a11y.css. */
.hzo-combobox__input:focus-visible {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
}
.hzo-combobox__input[disabled],
.hzo-combobox__input[aria-disabled="true"],
.hzo-combobox.is-disabled {
    opacity: var(--opacity-disabled);
    pointer-events: none;
}

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


/* --- contact-hub/contact-hub.css --- */
/* ═══════════════════════════════════════════════════════════════════════════
   FLOATING CONTACT HUB (W-040) — fixed corner FAB + native-channel disclosure.

   A position:fixed FAB that expands into a stack/fan/radial of native contact
   links (tel:/sms:/wa.me/mailto:/maps). APG Disclosure, NOT a modal trap — it
   never scroll-locks the page. Layered by --hzo-contact-hub-z, a peer of
   --z-back-to-top (15): below every overlay / --z-toast (70) so cart toasts,
   tooltips and modal scrims render above it, and it is inerted by an overlay
   background lock rather than fighting it. All motion is transform/opacity with
   a --i stagger (no JS timers); killed under prefers-reduced-motion.

   Local component tokens (declared here, resolved from the shared ladder — never
   a bare z-index integer, never a hardcoded brand hex). Per-instance offsets +
   channel count arrive as inline data vars from the shortcode.
   ═══════════════════════════════════════════════════════════════════════════ */
.hzo-contact-hub {
    /* Layer peer of back-to-top (15), safely below --z-toast (70). */
    --hzo-contact-hub-z:        var(--z-back-to-top, 15);
    --hzo-contact-hub-offset-x: var(--space-lg);
    --hzo-contact-hub-offset-y: var(--space-lg);
    --hzo-contact-hub-fab:      56px;   /* toggle FAB (>=44 target)   */
    --hzo-contact-hub-target:   48px;   /* channel target (>=44)      */
    --hzo-contact-hub-gap:      var(--space-sm);
    --hzo-contact-hub-bg:       var(--brand);
    --hzo-contact-hub-fg:       var(--text-on-brand);
    --hzo-contact-hub-panel-bg: var(--surface, #fff);
    --hzo-contact-hub-panel-fg: var(--text-body);

    position: fixed;
    z-index: var(--hzo-contact-hub-z);
    display: flex;
    flex-direction: column-reverse;   /* toggle sits at the corner, panel above */
    align-items: flex-end;
    gap: var(--hzo-contact-hub-gap);
    /* Vertical safe-area padding so the FAB clears an iOS home indicator. */
    bottom: calc(var(--hzo-contact-hub-offset-y) + env(safe-area-inset-bottom, 0px));
    right: calc(var(--hzo-contact-hub-offset-x) + env(safe-area-inset-right, 0px));
}

/* ── Corners ──────────────────────────────────────────────────────────────── */
.hzo-contact-hub--bottom-left {
    right: auto;
    left: calc(var(--hzo-contact-hub-offset-x) + env(safe-area-inset-left, 0px));
    align-items: flex-start;
}
.hzo-contact-hub--top-right {
    bottom: auto;
    top: calc(var(--hzo-contact-hub-offset-y) + env(safe-area-inset-top, 0px));
    flex-direction: column;           /* panel opens downward */
}
.hzo-contact-hub--top-left {
    bottom: auto;
    right: auto;
    top: calc(var(--hzo-contact-hub-offset-y) + env(safe-area-inset-top, 0px));
    left: calc(var(--hzo-contact-hub-offset-x) + env(safe-area-inset-left, 0px));
    align-items: flex-start;
    flex-direction: column;
}

/* mobile-only: desktop keeps its header CTA. */
@media (min-width: 782px) {
    .hzo-contact-hub--mobile-only { display: none; }
}

/* ── Toggle FAB ───────────────────────────────────────────────────────────── */
.hzo-contact-hub__toggle {
    position: relative;
    flex: 0 0 auto;
    width: var(--hzo-contact-hub-fab);
    height: var(--hzo-contact-hub-fab);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: var(--border-1) solid transparent;   /* forced-colors visibility fallback */
    border-radius: var(--radius-pill);
    background: var(--hzo-contact-hub-bg);
    color: var(--hzo-contact-hub-fg);
    box-shadow: var(--shadow-lg);
    cursor: pointer;
    transition: transform var(--dur-2) var(--ease-out),
                box-shadow var(--dur-2) var(--ease-standard),
                background var(--dur-2) var(--ease-standard);
}
.hzo-contact-hub__toggle:hover { box-shadow: var(--shadow-xl); transform: translateY(-2px); }
.hzo-contact-hub__toggle:active { transform: translateY(0); }
.hzo-contact-hub__toggle:focus-visible {
    outline: var(--border-2) solid var(--focus-ring);
    outline-offset: var(--border-2);
}
.hzo-contact-hub__toggle svg { width: 26px; height: 26px; display: block; }

/* Glyph swap: message/etc. when closed, X when open. */
.hzo-contact-hub__toggle-glyph {
    position: absolute;
    transition: opacity var(--dur-1) var(--ease-standard),
                transform var(--dur-2) var(--ease-out);
}
.hzo-contact-hub__toggle-glyph--close { opacity: 0; transform: rotate(-45deg); }
.hzo-contact-hub.is-open .hzo-contact-hub__toggle-glyph--open  { opacity: 0; transform: rotate(45deg); }
.hzo-contact-hub.is-open .hzo-contact-hub__toggle-glyph--close { opacity: 1; transform: rotate(0); }

/* Count / label bubble. */
.hzo-contact-hub__badge {
    position: absolute;
    top: -2px;
    right: -2px;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-pill);
    background: var(--accent);
    color: var(--text-on-accent);
    font-family: var(--font-body);
    font-size: var(--fs-50);
    font-weight: var(--fw-700);
    line-height: 1;
}

/* ── Panel (role=group) ───────────────────────────────────────────────────── */
.hzo-contact-hub__panel {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--hzo-contact-hub-gap);
}
.hzo-contact-hub--bottom-left .hzo-contact-hub__panel,
.hzo-contact-hub--top-left .hzo-contact-hub__panel { align-items: flex-start; }
.hzo-contact-hub__panel[hidden] { display: none; }

/* ── Channel links ────────────────────────────────────────────────────────── */
.hzo-contact-hub__channel {
    --hzo-contact-hub-channel-accent: var(--brand);
    min-height: var(--hzo-contact-hub-target);
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-md);
    border: var(--border-1) solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--hzo-contact-hub-panel-bg);
    color: var(--hzo-contact-hub-panel-fg);
    box-shadow: var(--shadow-md);
    text-decoration: none;
    font-family: var(--font-body);
    font-size: var(--fs-100);
    font-weight: var(--fw-600);
    white-space: nowrap;
    /* Reveal choreography: staggered by --i (set inline per channel). */
    opacity: 0;
    transform: translateY(var(--space-sm));
    transition: opacity var(--dur-2) var(--ease-standard),
                transform var(--dur-3) var(--ease-out),
                border-color var(--dur-1) var(--ease-standard),
                box-shadow var(--dur-1) var(--ease-standard);
    transition-delay: calc(var(--i, 0) * 40ms);
}
/* Left-corner / downward reveals travel from the opposite side. */
.hzo-contact-hub--top-right .hzo-contact-hub__channel,
.hzo-contact-hub--top-left .hzo-contact-hub__channel { transform: translateY(calc(var(--space-sm) * -1)); }

.hzo-contact-hub.is-open .hzo-contact-hub__channel { opacity: 1; transform: none; }

.hzo-contact-hub__channel:hover {
    border-color: var(--hzo-contact-hub-channel-accent);
    box-shadow: var(--shadow-lg);
}
.hzo-contact-hub__channel:active { box-shadow: var(--shadow-md); }
.hzo-contact-hub__channel:focus-visible {
    outline: var(--border-2) solid var(--focus-ring);
    outline-offset: var(--border-2);
}
.hzo-contact-hub__channel[hidden] { display: none; }

.hzo-contact-hub__icon {
    flex: 0 0 auto;
    width: 22px;
    height: 22px;
    color: var(--hzo-contact-hub-channel-accent);
}
.hzo-contact-hub__label { display: inline-block; }

/* Desktop tel: dead-path fallback — the selectable / copied number. */
.hzo-contact-hub__num {
    display: none;
    font-family: var(--font-body);
    font-size: var(--fs-75);
    font-weight: var(--fw-400);
    color: var(--text-muted);
    -webkit-user-select: all;
    user-select: all;
}
@media (hover: hover) and (pointer: fine) {
    .hzo-contact-hub__channel--call .hzo-contact-hub__num { display: inline-block; margin-inline-start: var(--space-sm); }
}

/* ── labels: hover / none — coarse pointer always shows text ──────────────── */
@media (hover: hover) and (pointer: fine) {
    .hzo-contact-hub--labels-hover .hzo-contact-hub__label {
        max-width: 0;
        overflow: hidden;
        opacity: 0;
        transition: max-width var(--dur-2) var(--ease-out), opacity var(--dur-2) var(--ease-standard);
    }
    .hzo-contact-hub--labels-hover .hzo-contact-hub__channel:hover .hzo-contact-hub__label,
    .hzo-contact-hub--labels-hover .hzo-contact-hub__channel:focus-visible .hzo-contact-hub__label {
        max-width: 20ch;
        opacity: 1;
    }
}
/* labels=none — icon-only; the <a> keeps its aria-label (accessible name). */
.hzo-contact-hub--labels-none .hzo-contact-hub__channel {
    width: var(--hzo-contact-hub-target);
    padding: 0;
    justify-content: center;
}
.hzo-contact-hub--labels-none .hzo-contact-hub__label,
.hzo-contact-hub--labels-none .hzo-contact-hub__num { display: none; }

/* ── expand: fan / radial (progressive; base is a vertical stack) ──────────── */
.hzo-contact-hub--fan .hzo-contact-hub__channel { transform-origin: bottom right; }
.hzo-contact-hub--fan.is-open .hzo-contact-hub__channel { transform: none; }
.hzo-contact-hub--radial .hzo-contact-hub__panel { align-items: flex-end; }

/* ── after-scroll: hidden until the JS drops the flag ─────────────────────── */
.hzo-contact-hub--hidden-until-scroll { opacity: 0; visibility: hidden; pointer-events: none; }
.hzo-contact-hub--hidden-until-scroll.is-revealed {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition: opacity var(--dur-3) var(--ease-standard);
}

/* ── Attention pulse (one-shot; JS adds .is-pulsing, removes on first open) ── */
@keyframes hzo-contact-hub-pulse {
    0%   { box-shadow: var(--shadow-lg), 0 0 0 0 color-mix(in srgb, var(--brand) 45%, transparent); }
    70%  { box-shadow: var(--shadow-lg), 0 0 0 16px color-mix(in srgb, var(--brand) 0%, transparent); }
    100% { box-shadow: var(--shadow-lg), 0 0 0 0 color-mix(in srgb, var(--brand) 0%, transparent); }
}
.hzo-contact-hub.is-pulsing .hzo-contact-hub__toggle {
    animation: hzo-contact-hub-pulse 1.8s var(--ease-out) 3;
}

/* ── Environment ──────────────────────────────────────────────────────────── */
@media print {
    .hzo-contact-hub { display: none !important; }
}

@media (forced-colors: active) {
    .hzo-contact-hub__toggle,
    .hzo-contact-hub__channel { border-color: ButtonText; }
    .hzo-contact-hub__toggle:focus-visible,
    .hzo-contact-hub__channel:focus-visible { outline-color: Highlight; }
    .hzo-contact-hub__icon { color: ButtonText; }
}

@media (prefers-reduced-motion: reduce) {
    .hzo-contact-hub__toggle,
    .hzo-contact-hub__toggle-glyph,
    .hzo-contact-hub__channel,
    .hzo-contact-hub__label { transition: none; }
    .hzo-contact-hub__channel { transition-delay: 0s; }
    .hzo-contact-hub.is-pulsing .hzo-contact-hub__toggle { animation: none; }
    /* Never leave revealed content at opacity:0 (G13/G60): open state is instant. */
    .hzo-contact-hub.is-open .hzo-contact-hub__channel { opacity: 1; transform: none; }
}


/* --- contact-split/contact-split.css --- */
/* contact-split — @tokens: --contact-split-gap, --contact-split-info-gap

   A two-column contact SECTION: business info / NAP (name-address-phone, hours,
   social) on one side, a form or a map on the other. Full-bleed section that
   OWNS its vertical rhythm (padding via .hzo-section) and holds a .hzo-container.
   A composition, not a control — it only LAYS OUT existing components (reuse
   .hzo-definition-list for the NAP, .hzo-btn in the form, .hzo-map for the map
   side); it never restyles them, so it carries no focus/disabled idioms itself —
   the inner form fields and .hzo-map bring their own a11y.

   Token-only: the two-column gap + the info-stack rhythm have no single semantic
   role that fits, so they are component tokens with a space-scale literal fallback
   (the icon-button.css dial pattern). Everything else reads an L1/L2 token. */

/* The 2-col grid. Info | form on desktop; stacks to one column below the
   breakpoint (max-width query — the QA-ladder direction the standard prefers). */
.hzo-contact-split__grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--contact-split-gap, var(--space-3xl));
    align-items: start;
}
@media (max-width: 768px) {
    .hzo-contact-split__grid {
        grid-template-columns: 1fr;
        gap: var(--space-xl);
    }
}

/* Info column — heading + NAP + hours + social stacked with a shared rhythm.
   Holds plain markup or a reused .hzo-definition-list; adds no type styling of
   its own (the heading / eyebrow / definition-list classes carry that). */
.hzo-contact-split__info {
    display: flex;
    flex-direction: column;
    gap: var(--contact-split-info-gap, var(--space-lg));
}

/* Form / map column — a bare layout slot. The inner form fields and .hzo-map
   style themselves; this only holds them. First/last child margins collapse so
   the slot's contents align flush with the info column top. */
.hzo-contact-split__form > :first-child { margin-top: 0; }
.hzo-contact-split__form > :last-child { margin-bottom: 0; }

/* Modifier — swap the column order so the form/map leads on desktop. Only flips
   above the stack breakpoint; on mobile the source order (info first) is kept. */
@media (min-width: 769px) {
    .hzo-contact-split--form-first .hzo-contact-split__info { order: 2; }
    .hzo-contact-split--form-first .hzo-contact-split__form { order: 1; }
}


/* --- credentials/credentials.css --- */
/* ─── License & Insurance Verification Card (.hzo-credentials) ──────────────
   W-060 — a structured credentials card: one row per license / certification /
   insurance / bond, each with a server-computed status pill (Active / Expires
   soon / Expired), an optional masked number + click-to-copy, and a
   "Verify on {board}" deep link. Markup is emitted by credentials.php
   ([hzo_credentials] > [hzo_credential] rows); this sheet styles ONLY the
   classes that handler outputs.

   The card is fully functional WITHOUT JS — status pills, verify links and the
   full (sr-only) number are all server-rendered. credentials.js only adds the
   copy + mask-reveal convenience; the reduced-motion / forced-colors passes at
   the foot keep the card readable when either is active.

   Tokens only: --surface / --border / --text-* / state --*-bg + --*-ink (pills),
   --brand-text / --accent-text (ink + icon tint), --space-* (rhythm),
   --radius-* (panel + pill + control), --shadow-sm (rest elevation), --fs-* /
   --fw-* / --tracking-* (type), --dur-* / --ease-* (transitions). No hardcoded
   hex, no bare z-index (the card claims none). */

.hzo-credentials-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
}

/* Optional card heading (a real <h2>, styled by base.css) — only owns rhythm. */
.hzo-credentials__heading {
    margin: 0;
}

/* ── Layouts ────────────────────────────────────────────────────────────────
   grid  (default) — responsive card grid, one credential per card.
   list  — a single column of the same cards, full width.
   table — compact divided rows inside one bordered panel (no per-row chrome). */
.hzo-credentials {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr));
    gap: var(--space-lg);
    margin: 0;
    padding: 0;
    list-style: none;
}
.hzo-credentials--list {
    grid-template-columns: 1fr;
}
.hzo-credentials--table {
    display: block;
    border: var(--border-1) solid var(--surface-border);
    border-radius: var(--radius-panel);
    overflow: hidden;
}

/* ── Item (the credential card) ─────────────────────────────────────────────
   container-type lets the label/value rows collapse to a stack on a narrow
   card (no media query, no JS). */
.hzo-credentials__item {
    container-type: inline-size;
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    padding: var(--space-lg);
    background: var(--surface);
    border: var(--border-1) solid var(--surface-border);
    border-radius: var(--radius-panel);
    box-shadow: var(--shadow-sm);
}
/* Table layout strips the card chrome down to divided rows. */
.hzo-credentials--table .hzo-credentials__item {
    background: transparent;
    border: 0;
    border-bottom: var(--border-1) solid var(--border);
    border-radius: 0;
    box-shadow: none;
    padding: var(--space-md) var(--space-lg);
}
.hzo-credentials--table .hzo-credentials__item:last-child {
    border-bottom: 0;
}

/* ── Head: type icon + label + status pill ──────────────────────────────── */
.hzo-credentials__head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
}
.hzo-credentials__type {
    margin-inline-end: auto;          /* push the status pill to the far edge */
    color: var(--text-heading);
    font-weight: var(--fw-600);
}

/* Shared icon base (every SVG the handler emits carries this class). */
.hzo-credentials__icon {
    width: 1.15em;
    height: 1.15em;
    flex-shrink: 0;
}
/* The type glyph (direct child of head) reads at the trust-icon accent tint,
   a step larger than the inline icons. */
.hzo-credentials__head > .hzo-credentials__icon {
    width: 1.5em;
    height: 1.5em;
    color: var(--accent-text);
}

/* ── Status pill (server-computed; word + glyph carry meaning, never colour
   alone — the icon is decorative reinforcement) ─────────────────────────── */
.hzo-credentials__status {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    padding: 4px 10px;
    border-radius: var(--radius-pill);
    font-size: var(--fs-75);
    font-weight: var(--fw-600);
    line-height: var(--lh-none);
    /* neutral fallback; the data-status variants below tint per state */
    background: var(--bg-alt);
    color: var(--text-body);
}
.hzo-credentials__status .hzo-credentials__icon {
    width: 1em;
    height: 1em;
}
.hzo-credentials__status[data-status="active"] {
    background: var(--success-bg);
    color: var(--success-ink);
}
.hzo-credentials__status[data-status="expiring"] {
    background: var(--warning-bg);
    color: var(--warning-ink);
}
.hzo-credentials__status[data-status="expired"] {
    background: var(--error-bg);
    color: var(--error-ink);
}

/* ── Meta (definition list: number / issued by / expires) ───────────────── */
.hzo-credentials__meta {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    margin: 0;
}
.hzo-credentials__field {
    display: grid;
    grid-template-columns: 5.5rem 1fr;
    gap: var(--space-sm);
    align-items: baseline;
}
.hzo-credentials__label {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--fs-75);
    font-weight: var(--fw-600);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
}
.hzo-credentials__meta dd {
    margin: 0;
    color: var(--text-body);
}
.hzo-credentials__meta time {
    font-variant-numeric: tabular-nums;
}

/* Number row: value + optional mask toggle + optional copy, inline. */
.hzo-credentials__number {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
}
.hzo-credentials__num {
    color: var(--text-heading);
    font-weight: var(--fw-600);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
}

/* Clipped full-number span — always in the DOM for AT + print (PROGRESSIVE
   ENHANCEMENT: the widget is complete without JS). */
.hzo-credentials__vh {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ── Inline action buttons (mask toggle + copy) ─────────────────────────── */
.hzo-credentials__mask-toggle,
.hzo-credentials__copy {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    min-height: 1.75em;
    padding: var(--space-2xs);
    border: 0;
    border-radius: var(--radius-control);
    background: transparent;
    color: var(--accent-text);
    font: inherit;
    font-size: var(--fs-75);
    line-height: var(--lh-none);
    cursor: pointer;
    transition: color var(--dur-1) var(--ease-standard),
                background-color var(--dur-1) var(--ease-standard);
}
.hzo-credentials__mask-toggle:hover,
.hzo-credentials__copy:hover {
    background: var(--bg-alt);
    color: var(--brand-text);
}
.hzo-credentials__mask-toggle:focus-visible,
.hzo-credentials__copy:focus-visible {
    outline: var(--border-2) solid var(--focus-ring);
    outline-offset: 2px;
}
/* Mask toggle carries BOTH glyphs (eye = masked, eye-off = revealed); CSS shows
   exactly one per aria-pressed state, so no JS icon swap is needed and the button
   is correct even mid-enhancement. */
.hzo-credentials__mask-toggle .hzo-credentials__icon:last-of-type,
.hzo-credentials__mask-toggle[aria-pressed="true"] .hzo-credentials__icon:first-of-type {
    display: none;
}
.hzo-credentials__mask-toggle[aria-pressed="true"] .hzo-credentials__icon:last-of-type {
    display: inline;
}

/* Brief "Copied" acknowledgement (credentials.js swaps the text + this class). */
.hzo-credentials__copy.is-copied {
    color: var(--success-ink);
}
/* Copy fell back to manual (clipboard blocked / insecure context): the number
   text is selected + the label reads "Press Ctrl+C" — a still-working recovery,
   never a dead end (never colour-only; the relabel carries the meaning). */
.hzo-credentials__copy.is-copy-failed {
    color: var(--warning-ink);
}

/* ── Verify deep link — pinned to the card foot so links align across cards
   of differing height ───────────────────────────────────────────────────── */
.hzo-credentials__verify {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    align-self: flex-start;
    margin-top: auto;
    color: var(--brand-text);
    font-size: var(--fs-100);
    font-weight: var(--fw-600);
    text-decoration: none;
    transition: color var(--dur-1) var(--ease-standard);
}
.hzo-credentials__verify:hover {
    color: var(--brand-hover-on-light);
    text-decoration: underline;
}
.hzo-credentials__verify:focus-visible {
    outline: var(--border-2) solid var(--focus-ring);
    outline-offset: 2px;
    border-radius: var(--radius-control);
}

/* ── Narrow card: stack each label above its value (no JS) ───────────────── */
@container (max-width: 22rem) {
    .hzo-credentials__field {
        grid-template-columns: 1fr;
        gap: var(--space-2xs);
    }
}

/* ── Reduced motion — the card carries no essential motion; kill the hover /
   state transitions so nothing animates (content stays fully visible). ──── */
@media (prefers-reduced-motion: reduce) {
    .hzo-credentials__mask-toggle,
    .hzo-credentials__copy,
    .hzo-credentials__verify {
        transition: none;
    }
}

/* ── Forced colors — state is carried by the pill's WORD, not colour alone, so
   High Contrast stays correct. Restore a visible edge on pills + action
   buttons and underline the verify affordance. ─────────────────────────── */
@media (forced-colors: active) {
    .hzo-credentials__status {
        border: var(--border-1) solid currentColor;
    }
    .hzo-credentials__mask-toggle,
    .hzo-credentials__copy {
        border: var(--border-1) solid currentColor;
    }
    .hzo-credentials__verify {
        text-decoration: underline;
    }
}

/* ── Print / PDF (G13) — home-service customers routinely print or PDF an
   estimate / service page, so the credentials must survive it: force-reveal the
   card even if it was wrapped in scroll-reveal (opacity:0 below the fold), print
   the FULL number (drop the masked span, un-clip the sr-only full number), spell
   out the verify URL as visible text, and hide the copy / mask chrome (useless on
   paper). ─────────────────────────────────────────────────────────────────── */
@media print {
    .hzo-credentials.hzo-reveal,
    .hzo-credentials.hzo-reveal--init,
    .hzo-credentials .hzo-reveal {
        opacity: 1 !important;
        transform: none !important;
    }
    .hzo-credentials__item {
        break-inside: avoid;
        box-shadow: none;
    }
    /* Show the full number: the masked visible span is aria-hidden decoration on
       paper; the clipped full-number span becomes the printed value. */
    .hzo-credentials__num[data-hzo-mask],
    .hzo-credentials__mask-toggle,
    .hzo-credentials__copy {
        display: none !important;
    }
    .hzo-credentials__vh {
        position: static;
        width: auto;
        height: auto;
        margin: 0;
        overflow: visible;
        clip: auto;
        white-space: normal;
        color: var(--text-heading);
        font-weight: var(--fw-600);
    }
    /* Print the destination so a paper reader can still reach the board lookup. */
    .hzo-credentials__verify[href]::after {
        content: " (" attr(href) ")";
        font-weight: var(--fw-400);
        word-break: break-all;
    }
}


/* --- cta-band/cta-band.css --- */
/* ═════════════════════════════════════════════════════════════════════════
   CLOSING CTA BAND (W-091) — component styles

   The full-width bottom-of-page closer between the thin promo banner and the
   floating sticky-cta. Layouts (centered / split / contained), backgrounds
   (solid / gradient / image / video), and the contrast-safe overlay scrim are
   ALL pure CSS; cta-band.js is progressive enhancement only (video pause,
   async capture, desktop click-to-copy, mobile sticky handoff).

   STACKING (spec §8.5): the root sets `isolation:isolate` to open its own
   stacking context, then layers video -> scrim -> content -> pause-toggle by
   DOM ORDER using positioned elements only — NO z-index token is spent (and no
   bare z-index integer either). Every layer is position:absolute except the
   content wrapper (position:relative); painted last-in-DOM = topmost.

   Variant sheets are @imported so the loader (which enqueues only cta-band.css)
   pulls them on the same request.
   ═════════════════════════════════════════════════════════════════════════ */
/* ── ONE COMPOSITION (Wave S) ────────────────────────────────────────────
   This component ships exactly ONE composition: a full-width closing band - heading plus actions, centred, split or contained.

   That is a feature. One composition is why it is responsive, accessible and
   identical on two hundred sites, and why a client build can place it without
   re-deciding anything. It is also a hard limit: the component cannot express
   a SECOND composition, and making it try — nesting it, stacking modifiers on
   it, overriding its grid from a client sheet — is how a page ends up fighting
   its own components and still looking generic.

   FOR AN AMPLIFY OR ELEVATE SECTION, DO NOT BEND THIS COMPONENT. Take one of:
     • RAW mode — author the section's markup directly, or
     • hand-author to the class contract on the spatial utilities:
       .hzo-grid + .hzo-span-* / .hzo-rowspan-* / --hzo-grid-template,
       .hzo-bleed, .hzo-pull-* / .hzo-z-*, .hzo-section--edge-*, .hzo-measure-*.
   Both are the STANDARD route for a high-intensity section, not an escape
   hatch, and the composition plan is what says which sections those are.

   PLUMBING AND PROOF COMPONENTS ARE UNAFFECTED. Every JS widget and every
   proof/data component (reviews, credentials, ratings, schema) stays
   mandatory — never hand-roll those. This note is about LAYOUT only.
   ────────────────────────────────────────────────────────────────────────── */

/* hzo-bundle: skipped @import ./cta-band-split.cssver1788182399 */
/* hzo-bundle: skipped @import ./cta-band-contained.cssver1788182399 */

.hzo-cta-band {
    position: relative;
    isolation: isolate;                 /* own stacking context — see header */
    overflow: hidden;
    padding: var(--space-2xl) var(--space-lg);
    background: var(--hzo-cta-bg, var(--brand));
    color: var(--text-on-brand, #fff);
}

/* ── Background variants ─────────────────────────────────────────────────── */
/* --gradient-brand-sweep IS the literal that used to sit here (tokens.css) — one
   definition of the fleet's diagonal brand wash, shared with .hzo-mega-cta. */
.hzo-cta-band--bg-gradient { background: var(--hzo-cta-gradient, var(--gradient-brand-sweep, linear-gradient(135deg, var(--brand), var(--brand-deep)))); }
.hzo-cta-band--bg-image,
.hzo-cta-band--bg-video {
    background-color: var(--brand-deep);            /* fallback under the media */
    background-image: var(--hzo-cta-image, none);   /* image url / video poster */
    background-size: cover;
    background-position: center;
}

/* Decorative background <video> — muted/loop/aria-hidden, painted first. */
.hzo-cta-band__video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* AA-floor overlay scrim (color-mix). Strength/tint come from the handler as
   --hzo-cta-overlay (%) + --hzo-cta-overlay-color; only rendered when > 0. */
.hzo-cta-band__scrim {
    position: absolute;
    inset: 0;
    background: color-mix(in srgb, var(--hzo-cta-overlay-color, var(--brand-deep)) var(--hzo-cta-overlay, 55%), transparent);
    pointer-events: none;
}

/* Content wrapper — positioned (relative) so it paints above the absolute
   video + scrim purely by DOM order. */
.hzo-cta-band__inner {
    position: relative;
    max-width: var(--container-max-width, 1350px);
    margin-inline: auto;
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
}

/* ── Centered (default) ──────────────────────────────────────────────────── */
.hzo-cta-band--centered .hzo-cta-band__inner { text-align: center; align-items: center; }
.hzo-cta-band--centered .hzo-cta-band__copy { max-width: 46rem; }
.hzo-cta-band--centered .hzo-cta-band__actions { justify-content: center; }

/* ── Copy column ─────────────────────────────────────────────────────────── */
.hzo-cta-band__copy {
    display: flex;
    flex-direction: column;
    gap: var(--space-smd, 12px);
}
.hzo-cta-band__eyebrow {
    margin: 0;
    font-size: var(--eyebrow-size, 15px);
    font-weight: var(--fw-semibold, 600);
    letter-spacing: var(--tracking-wide, 0.05em);
    text-transform: uppercase;
    opacity: var(--opacity-muted, 0.85);
}
.hzo-cta-band__heading {
    margin: 0;
    text-wrap: balance;
    color: inherit;
}
.hzo-cta-band__subhead {
    margin: 0;
    max-width: var(--prose-max-width, 65ch);
    font-size: 1.125rem;
    line-height: var(--lh-relaxed, 1.7);
    opacity: 0.92;
}
.hzo-cta-band--centered .hzo-cta-band__subhead { margin-inline: auto; }

/* ── Trust line + star rating ────────────────────────────────────────────── */
.hzo-cta-band__trust {
    margin: 0;
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-sm);
    font-size: 0.95rem;
    opacity: 0.9;
}
.hzo-cta-band__rating {
    position: relative;
    display: inline-block;
    font-size: 1.05rem;
    line-height: 1;
    white-space: nowrap;
}
.hzo-cta-band__stars-base { color: color-mix(in srgb, currentColor 35%, transparent); }
.hzo-cta-band__stars-fill {
    position: absolute;
    inset: 0;
    overflow: hidden;
    width: var(--hzo-cta-stars, 100%);
    color: #f6c945;                     /* warm review-star gold (not a brand hue) */
}

/* ── Actions ─────────────────────────────────────────────────────────────── */
.hzo-cta-band__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-md);
}
.hzo-cta-band__action { display: inline-flex; align-items: center; gap: var(--space-sm); }
.hzo-cta-band__icon { width: 20px; height: 20px; flex-shrink: 0; }

/* ── Inline capture (reuses .hzo-input-group) ────────────────────────────── */
.hzo-cta-band__capture { width: 100%; max-width: 32rem; }
.hzo-cta-band--centered .hzo-cta-band__capture { margin-inline: auto; }
.hzo-cta-band__capture .hzo-input-group { max-width: none; }
/* In-flight submit-lock: dimmed but the spinner/text still read (forced-colors
   safe — never the ONLY busy signal). */
.hzo-cta-band__submit[aria-busy="true"],
.hzo-cta-band__submit.is-loading { opacity: var(--opacity-muted, 0.65); }
/* Invalid capture field — a VISIBLE border + ring, never suppressed (G49). */
.hzo-cta-band__capture input[aria-invalid="true"] {
    border-color: var(--error, #dc2626);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--error, #dc2626) 30%, transparent);
}
.hzo-cta-band__capture-status {
    margin: var(--space-sm) 0 0;
    font-size: 0.9rem;
}
.hzo-cta-band__capture-status.is-error { color: color-mix(in srgb, #fff 88%, var(--error)); }
.hzo-cta-band--text-dark .hzo-cta-band__capture-status.is-error { color: var(--error-text, #991b1b); }
/* Confirmation swap fragment (JS replaces the input-group with this). */
.hzo-cta-band__confirm {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    font-size: 1.05rem;
    font-weight: var(--fw-semibold, 600);
}

/* ── Text scheme ─────────────────────────────────────────────────────────── */
.hzo-cta-band--text-light { color: var(--heading-color-on-dark, #fff); }
.hzo-cta-band--text-light .hzo-cta-band__heading,
.hzo-cta-band--text-light .hzo-cta-band__eyebrow { color: var(--heading-color-on-dark, #fff); }
.hzo-cta-band--text-dark { color: var(--text-body); }
.hzo-cta-band--text-dark .hzo-cta-band__heading,
.hzo-cta-band--text-dark .hzo-cta-band__eyebrow { color: var(--text-heading); }
.hzo-cta-band--text-dark .hzo-cta-band__eyebrow { color: var(--brand-text); }

/* ── Video pause/play control — visible at rest (WCAG 2.2.2), painted last ── */
.hzo-cta-band__video-toggle {
    position: absolute;
    right: var(--space-lg);
    bottom: var(--space-lg);
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 10px 18px;
    border: 1px solid var(--surface-border, transparent);
    border-radius: var(--radius-pill);
    background: var(--surface, #fff);
    color: var(--on-surface, var(--text-heading));
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: var(--fw-semibold, 600);
    box-shadow: var(--shadow-md);
    cursor: pointer;
    transition: background var(--dur-2) var(--ease-standard);
}
.hzo-cta-band__video-toggle:hover { background: var(--bg-alt); }
.hzo-cta-band__video-toggle:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px; }

/* ── Visually-hidden capture label (associated <label>, never a placeholder) ─ */
.hzo-cta-band__sr-only {
    position: absolute !important;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ── Focus ring floor (real outline — forced-colors + box-shadow-unreliable) ─ */
.hzo-cta-band .hzo-btn:focus-visible,
.hzo-cta-band__capture input:focus-visible { outline-offset: 2px; }

/* ── Environment ─────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
    /* Ambient gradient/video motion is class/JS-gated; kill control transitions
       and any reveal travel here (content must never stay opacity:0). */
    .hzo-cta-band__video-toggle { transition: none; }
    .hzo-cta-band.hzo-reveal { opacity: 1 !important; transform: none !important; }
}

@media print {
    /* Force the band visible if .hzo-reveal left it hidden (G13); drop the
       decorative chrome; keep the copy + actions legible on paper. */
    .hzo-cta-band { background: none !important; color: #000 !important; }
    .hzo-cta-band.hzo-reveal { opacity: 1 !important; transform: none !important; }
    .hzo-cta-band__video,
    .hzo-cta-band__scrim,
    .hzo-cta-band__video-toggle { display: none !important; }
    .hzo-cta-band__heading,
    .hzo-cta-band__eyebrow { color: #000 !important; }
    /* Expose the destination of each action so a printed page keeps the number/URL. */
    .hzo-cta-band__action[href]::after {
        content: " (" attr(href) ")";
        font-weight: 400;
        font-size: 0.85em;
    }
}

@media (forced-colors: active) {
    /* The color-mix scrim is dropped by the OS; don't rely on it for contrast.
       Give text a real system pairing and the toggle a real border. */
    .hzo-cta-band { background: Canvas; color: CanvasText; }
    .hzo-cta-band__scrim { display: none; }
    .hzo-cta-band__heading,
    .hzo-cta-band__eyebrow,
    .hzo-cta-band--text-light,
    .hzo-cta-band--text-light .hzo-cta-band__heading,
    .hzo-cta-band--text-light .hzo-cta-band__eyebrow { color: CanvasText; }
    .hzo-cta-band__video-toggle { border: 1px solid CanvasText; }
}


/* --- definition-list/definition-list.css --- */
/* definition-list — @tokens: --definition-list-gap, --definition-list-inline-gap

   A styled semantic <dl>. Structural primitive (no interaction) — term/description
   pairs for glossaries, meta panels, spec summaries. The element selectors (dt/dd)
   are scoped UNDER the root class per the authoring standard's semantic-element
   rule; they borrow no other block's classes and belong to this block only.

   Token-only: the pair rhythm + the inline column gap have no single semantic role
   that fits, so they are component tokens with a space-scale literal fallback (the
   icon-button.css dial pattern). Everything else reads an L1/L2 token directly.
   Static/structural, so no focus/disabled a11y idioms — nothing here is focusable. */
.hzo-definition-list {
    margin: 0;
    font-size: var(--fs-200);
    line-height: var(--lh-normal);
    color: var(--text-body);
}

/* Term — heading ink, semibold, one step below body for a label feel. */
.hzo-definition-list dt {
    font-weight: var(--fw-600);
    color: var(--text-heading);
    font-size: var(--fs-100);
}

/* Description — body ink; reset the UA left indent; carry the pair rhythm below. */
.hzo-definition-list dd {
    margin: 0 0 var(--definition-list-gap, var(--space-md));
    color: var(--text-body);
}
.hzo-definition-list dd:last-child {
    margin-bottom: 0;
}

/* Inline — dt/dd side-by-side on a two-column grid (label auto, value fills).
   The row gap keeps the vertical rhythm; the column gap separates term from
   description. */
.hzo-definition-list--inline {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--space-xs) var(--definition-list-inline-gap, var(--space-md));
    align-items: baseline;
}
.hzo-definition-list--inline dd {
    margin: 0;
}


/* --- desktop-nav/desktop-nav.css --- */
/* Aggregator sheet — the component loader (includes/component-loader.php)
   enqueues ONLY <name>.css per folder, so desktop-nav's sibling variant sheets
   are @imported here to ride the same front-end request. This is their ONLY
   front-end load path: header-condense.css + desktop-ctas.css are named
   non-<name>.css and are referenced nowhere else, so without these @imports the
   header-condense chrome and the desktop CTA rail under-load fleet-wide.
   (Same fix pattern as footer.css @importing footer-typography + footer-chrome,
   and video.css @importing lite-video + video-background.) @imports must precede
   all style rules — keep them first. */
/* hzo-bundle: skipped @import ./desktop-ctas.cssver1788182399 */
/* hzo-bundle: skipped @import ./header-condense.cssver1788182399 */

/* ─── Desktop nav menu (UL/LI structure) ────────────────────────── */
.hzo-site-nav {
    display: none;
    list-style: none;
    margin: 0;
    padding: 0;
    align-items: stretch;
    align-self: stretch;
    gap: 0;
    flex: 1;
    justify-content: center;
}
@media (min-width: 1250px) { .hzo-site-nav { display: flex; } }

/* display:flex + align-items:center keeps the link text vertically centered while
   the item itself spans the header's full height (align-items:stretch on the nav +
   align-self:stretch on the nav column). That makes the mega-menu's `top:100%`
   anchor to the header's BOTTOM edge instead of the middle of the header, and it
   auto-follows the condense-on-scroll height change. */
.hzo-nav-item { position: relative; display: flex; align-items: center; }

/* !important on color: fights a:visited from per-client main.css.
   Matching :visited rule at bottom-of-file preserves the color. */
.hzo-site-nav__link {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 14px 13px;
    font-family: var(--font-body);
    font-size: 0.9375rem;
    font-weight: var(--fw-600, 600);
    color: var(--nav-link) !important;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: color 200ms ease;
}
/* Non-clickable dropdown parents: <span> triggers have no href, so no pointer cursor */
span.hzo-site-nav__link { cursor: default; }

.hzo-site-nav__link:hover,
.hzo-nav-item:hover > .hzo-site-nav__link,
.hzo-nav-item.is-open > .hzo-site-nav__link { color: var(--nav-link-hover) !important; }
a.hzo-site-nav__link:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -2px; }

/* G37: current page — brand color, heavier weight, and a short underline bar so
   "where am I" reads at a glance (H1 visibility of status). */
.hzo-site-nav__link.is-active,
.hzo-site-nav__link[aria-current="page"] {
    position: relative;
    color: var(--nav-link-active, var(--brand-text, var(--brand))) !important;
    font-weight: var(--fw-bold, 700);
}
.hzo-site-nav__link.is-active::after,
.hzo-site-nav__link[aria-current="page"]::after {
    content: "";
    position: absolute;
    left: 13px; right: 13px; bottom: 8px;
    height: 2px;
    background: currentColor;
    border-radius: 2px;
}
.hzo-mega-menu__link.is-active { color: var(--brand-text, var(--brand)) !important; font-weight: var(--fw-bold, 700); }

.hzo-nav-chevron {
    flex-shrink: 0;
    opacity: 0.45;
    transition: transform 200ms ease, opacity 200ms ease;
}
.hzo-nav-item:hover > .hzo-site-nav__link .hzo-nav-chevron,
.hzo-nav-item.is-open > .hzo-site-nav__link .hzo-nav-chevron {
    transform: rotate(180deg);
    opacity: 0.8;
}

/* Reduced motion: kill the nav-link colour + chevron rotate/fade transitions
   (the rotated/coloured end-states still apply — only the animation is dropped).
   header-condense.css ships its own reduced-motion block for the header shell. */
@media (prefers-reduced-motion: reduce) {
    .hzo-site-nav__link,
    .hzo-nav-chevron { transition: none; }
}


/* --- divider/divider.css --- */
/* ─── Divider / rule ──────────────────────────────────────────────────
   Horizontal separator. Plain <hr class="hzo-divider"> or a labeled variant:
   <div class="hzo-divider hzo-divider--labeled"><span>or</span></div>. */
.hzo-divider {
    border: 0;
    border-top: var(--border-1) solid var(--border);
    height: 0;
    margin: var(--space-lg) 0;
}
.hzo-divider--labeled {
    border-top: 0;
    height: auto;
    display: flex;
    align-items: center;
    gap: var(--space-md);
    color: var(--text-muted);
    font-family: var(--font-body);
    font-size: var(--fs-75);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
}
.hzo-divider--labeled::before,
.hzo-divider--labeled::after {
    content: "";
    flex: 1;
    border-top: var(--border-1) solid var(--border);
}


/* --- drawer/drawer.css --- */
/* ─── Drawer / off-canvas ──────────────────────────────────────────────
   Edge-anchored side panel (filters, mini-cart, secondary nav). Shares the
   modal's overlay MECHANICS — focus trap, the shared background inert + scroll
   lock, Esc / backdrop / close-button dismiss, focus return (drawer.js, built
   on hzo.trapFocus in main.js) — but NOT its layout: it stays a side panel
   at EVERY width. Phones cap the width but keep the side anchor; it never
   becomes a bottom sheet (that's the modal). Default slides from the right;
   --left slides from the left. Portaled to <body> on init. */
.hzo-drawer {
    position: fixed;
    inset: 0;
    z-index: var(--z-drawer);
    display: flex;
    justify-content: flex-end;
    background: rgba(0, 0, 0, 0.5);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--transition-base) ease, visibility var(--transition-base);
}
.hzo-drawer--left { justify-content: flex-start; }
.hzo-drawer.is-open { opacity: 1; visibility: visible; }

.hzo-drawer__panel {
    position: relative;
    display: flex;
    flex-direction: column;
    width: min(85vw, 360px);
    max-width: 100%;
    height: 100%;
    background: var(--surface, #fff);
    box-shadow: var(--shadow-lg);
    overflow-y: auto;
    transform: translateX(100%);
    transition: transform var(--transition-base) ease;
}
.hzo-drawer--left .hzo-drawer__panel { transform: translateX(-100%); }
.hzo-drawer.is-open .hzo-drawer__panel { transform: none; }
.hzo-drawer__panel--wide { width: min(92vw, 480px); }

.hzo-drawer__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    padding: var(--space-lg);
    border-bottom: var(--border-1) solid var(--border);
    flex-shrink: 0;
}
.hzo-drawer__title {
    margin: 0;
    font-family: var(--font-heading);
    font-size: var(--fs-400);
    line-height: 1.3;
    color: var(--text-heading);
}
.hzo-drawer__close {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin: -10px -10px -10px 0;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    border-radius: var(--radius-control);
    cursor: pointer;
    transition: background var(--transition-base) ease, color var(--transition-base) ease;
}
.hzo-drawer__close:hover { background: var(--bg-alt); color: var(--text-heading); }
.hzo-drawer__close:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }

.hzo-drawer__body {
    flex: 1 1 auto;
    min-height: 0;                    /* G7: let the body shrink so IT is the sole scroller — */
    overflow-y: auto;                 /* the footer (mini-cart Checkout) is never pushed off-screen */
    overscroll-behavior: contain;
    padding: var(--space-lg);
    color: var(--text-body);
}
.hzo-drawer__body > :first-child { margin-top: 0; }
.hzo-drawer__body > :last-child { margin-bottom: 0; }

.hzo-drawer__footer {
    flex-shrink: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    padding: var(--space-lg);
    border-top: var(--border-1) solid var(--border);
}

@media (prefers-reduced-motion: reduce) {
    .hzo-drawer,
    .hzo-drawer__panel,
    .hzo-drawer__close { transition: none; }
}


/* --- embed/embed.css --- */
/* ─── Embed frame (.hzo-embed) ─────────────────────────────────────────
   Aspect-ratio wrapper for any embedded media — iframes, <video>, or the
   .hzo-lite-video facade below. --embed-aspect reshapes it (default 16/9;
   e.g. style="--embed-aspect: 9 / 16" for vertical shorts). The frame owns
   radius + clipping; whatever fills it stretches edge to edge. */
.hzo-embed {
    position: relative;
    aspect-ratio: var(--embed-aspect, 16 / 9);
    border-radius: var(--radius-panel);
    overflow: hidden;
    background: var(--bg-alt);   /* letterbox field while media loads */
}
.hzo-embed > iframe,
.hzo-embed > video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
}
.hzo-embed > video { object-fit: cover; }


/* --- empty-state/empty-state.css --- */
/* ─── Empty state ─────────────────────────────────────────────────────
   Zero-state for no search results, an empty cart, or no posts/orders.
   Centered icon + headline (markup uses .hzo-visual-h3) + one-line
   explanation + a primary action (and optional secondary links). Composes
   .hzo-btn for the action; no new tokens. */
.hzo-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--space-sm);
    max-width: 480px;
    margin-inline: auto;
    padding: var(--space-2xl) 0;
}
.hzo-empty__icon {
    width: 64px;
    height: 64px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--brand);
    background: var(--brand-pale);
    border-radius: var(--radius-full);
    margin-bottom: var(--space-sm);
}
.hzo-empty__icon svg { width: 32px; height: 32px; }
.hzo-empty__text { color: var(--text-muted); line-height: var(--lh-normal); margin: 0; max-width: 40ch; }
.hzo-empty__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-sm); margin-top: var(--space-md); }

/* ── Widget modifiers (opt-in via [hzo_empty_state]; Phase-2 wave 6) ──
   ADDITIVE ONLY. The base .hzo-empty rules above are a shared class contract —
   mini-cart chrome (.hzo-empty.hzo-minicart__empty) and wc-ajax's no-results
   injection reuse them — so they never change; variants are new classes. */
.hzo-empty--left {
    align-items: flex-start;
    text-align: left;
    margin-inline: 0;
}
.hzo-empty--left .hzo-empty__actions { justify-content: flex-start; }
.hzo-empty--compact { padding: var(--space-lg) 0; }
.hzo-empty--compact .hzo-empty__icon { width: 48px; height: 48px; }
.hzo-empty--compact .hzo-empty__icon svg { width: 24px; height: 24px; }

/* ═══════════════════════════════════════════════════════════════════════
   STATIC BLOCK PRIMITIVES (component library Wave D) — CSS-only, no JS,
   no new tokens.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── Error variant (audit G48) — a FAILED load, visually distinct from an
   empty result. The 'alert' glyph carries the --error tone; the retry button
   reuses .hzo-btn styling. ─────────────────────────────────────────────── */
.hzo-empty--error .hzo-empty__icon { color: var(--error); }


/* --- fat-footer/fat-footer.css --- */
/* fat-footer — a rich multi-column footer LAYOUT that arranges the engine's
   existing footer components (footer-chrome / footer-heading / trust-row /
   input-group / footer-legal) into a brand column + link columns + an offers
   card, a newsletter row, a trust-badge row, and a legal/credits bar. Layout +
   token-driven color only; it composes existing components and reinvents nothing.

   Brand-agnostic: the footer surface comes from --footer-bg (set on
   .hzo-site-footer, footer-chrome.css); the offers panel reads
   --fat-footer-offers-bg (falls back to --bg-dark-section); legal bar + fav read
   --brand-deep / --brand; white-on-dark text is the dark-footer convention.
   A client dials the exact footer tones via those tokens in its own :root. */

/* ── Final CTA band (arranges .hzo-section--dark + eyebrow + visual-h2 + btn) ── */
.hzo-fat-footer__cta-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-lg);
    flex-wrap: wrap;
}
.hzo-fat-footer__cta .hzo-eyebrow { color: var(--accent); }
.hzo-fat-footer__cta .hzo-visual-h2 { margin: 6px 0 0; }
.hzo-fat-footer__cta-phone { color: rgba(255, 255, 255, 0.85); font-weight: 700; }
.hzo-fat-footer__cta-phone a { color: #fff; }

/* ── Fat-footer grid (arranges existing footer components) ── */
.hzo-fat-footer {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-xl);
    padding-bottom: var(--space-xl);
}
@media (min-width: 640px) { .hzo-fat-footer { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1024px) {
    .hzo-fat-footer { grid-template-columns: 1.15fr 1fr 1fr 1fr auto; gap: var(--space-lg-2); align-items: start; }
}
.hzo-fat-footer__list { list-style: none; margin: 0; padding: 0; }
.hzo-fat-footer__list li { margin: 0 0 10px; }
.hzo-fat-footer__h2 { margin-top: var(--space-lg); }
.hzo-fat-footer__loc { margin: var(--space-md) 0; color: rgba(255, 255, 255, 0.72); font-size: 14px; }
.hzo-fat-footer__loc p { margin: 0 0 4px; }

/* Free Tools card = existing .hzo-card--bordered, re-toned for the dark footer */
.hzo-fat-footer__offers { align-self: start; background: var(--fat-footer-offers-bg, var(--bg-dark-section)); border-color: rgba(255, 255, 255, 0.10); }
@media (min-width: 1024px) { .hzo-fat-footer__offers { width: 260px; } }
.hzo-fat-footer__offers .hzo-footer-heading { color: var(--accent); }

/* ── Newsletter row (wraps the existing .hzo-input-group) ── */
.hzo-fat-footer__news {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-lg);
    flex-wrap: wrap;
    padding: var(--space-lg) 0;
    border-top: 1px solid rgba(255, 255, 255, 0.1);
    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.hzo-fat-footer__news-h { text-transform: none; color: #fff; font-size: 17px; margin: 0 0 4px; opacity: 1; }
.hzo-fat-footer__news-p { color: rgba(255, 255, 255, 0.55); font-size: 13px; margin: 0; }

/* ── Trust badges (wraps the existing .hzo-trust-row) ── */
.hzo-fat-footer__badges { padding: var(--space-lg) 0 var(--space-xl-2); }
.hzo-fat-footer__badges-lead { color: rgba(255, 255, 255, 0.5); margin-bottom: var(--space-md); }

/* ── Legal / credits bar (engine footer-legal classes) ── */
.hzo-fat-footer__legalwrap { background: var(--brand-deep); }
.hzo-fat-footer__legalwrap .hzo-site-footer__legal { padding: 18px 0; }
.hzo-fat-footer__credit { display: inline-flex; align-items: center; gap: 12px; }
.hzo-fat-footer__fav {
    width: 30px; height: 30px; flex: none;
    background: var(--brand); color: #fff;
    border-radius: var(--radius-input);
    display: inline-flex; align-items: center; justify-content: center;
    font-family: var(--font-heading); font-weight: 900; font-size: 17px;
}
.hzo-fat-footer__legal-links { display: flex; gap: 16px; flex-wrap: wrap; }


/* --- feature-tabs/feature-tabs.css --- */
/* feature-tabs — @tokens: --feature-tabs-gap, --feature-tabs-media-radius

   A tabbed feature showcase. The TAB UI is the existing .hzo-tabs (its list,
   tabs, panels + the main.js roving-focus wiring, reused verbatim — this file
   adds NO tab styling and re-themes nothing). All this component contributes is
   the layout INSIDE each panel: a two-column feature — media on one side, copy
   (heading + text + CTA) on the other. It composes .hzo-tabs + .hzo-btn; it only
   arranges them.

   No focus/disabled idioms here — the focusable parts (tabs, CTA) belong to
   .hzo-tabs / .hzo-btn and carry their own a11y. Token-only: the two-column gap +
   the media radius have no single semantic role that fits, so they are component
   tokens with a literal-token fallback (the icon-button.css dial pattern). */

/* Two-column panel layout — media | body, stacked on mobile, side-by-side above
   the breakpoint (min-width, mirroring .hzo-feature / .hzo-hero--split). Applied
   inside each .hzo-tabs__panel; .hzo-tabs owns the panel's top padding + hidden. */
.hzo-feature-tabs__panel {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--feature-tabs-gap, var(--space-xl));
    align-items: center;
}
@media (min-width: 1024px) {
    .hzo-feature-tabs__panel {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-3xl);
    }
}

/* Media side — image fills the column at panel radius (matches .hzo-feature__media). */
.hzo-feature-tabs__media img {
    width: 100%;
    height: auto;
    display: block;
    border-radius: var(--feature-tabs-media-radius, var(--radius-panel));
}

/* Body side — heading + text + CTA stack. Reset first/last margins so the copy
   aligns flush with the media top; the heading / body / .hzo-btn classes carry
   all type + control styling. */
.hzo-feature-tabs__body > :first-child { margin-top: 0; }
.hzo-feature-tabs__body > :last-child { margin-bottom: 0; }

/* Modifier — media on the opposite side on desktop (alternating rhythm across
   panels). Below the breakpoint the source order (media first) is kept. */
@media (min-width: 1024px) {
    .hzo-feature-tabs__panel--media-last .hzo-feature-tabs__media { order: 2; }
}


/* --- field/field.css --- */
/* field — @tokens: (none — layout + typography only, all semantic/L1 tokens)

   A standalone single-field wrapper (label + control + hint + error) for an input
   used OUTSIDE a full `.hzo-form` — e.g. a search box or filter in a widget. This
   styles LAYOUT + label/hint/error TYPOGRAPHY only; it deliberately does NOT touch
   the input element itself (that's the input's own job — `.hzo-form` inputs, native
   controls, or a primitive like `.hzo-range`). Nothing in the wrapper is focusable,
   so no focus idiom here — the control inside carries its own focus ring.

   Token-only: every value is a semantic/L1 token straight off the scale, so there
   are no component-token dials to declare. */
.hzo-field {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
}

/* Label — heading-ink, semibold, small-text step. */
.hzo-field__label {
    font-weight: var(--fw-600);
    color: var(--text-heading);
    font-size: var(--fs-100);
    line-height: var(--lh-snug);
}

/* Control slot — holds the input; the input styles itself. */
.hzo-field__control {
    display: block;
}

/* Hint — muted helper text below the control. */
.hzo-field__hint {
    color: var(--text-muted);
    font-size: var(--fs-50);
    line-height: var(--lh-normal);
}

/* Error — same size as the hint, error ink. Hidden by default; the --error
   modifier reveals it (error text only exists when the field is in error). */
.hzo-field__error {
    display: none;
    color: var(--error);
    font-size: var(--fs-50);
    line-height: var(--lh-normal);
}

/* Modifier: error — surface the error message and tint the label to match. */
.hzo-field--error .hzo-field__error {
    display: block;
}
.hzo-field--error .hzo-field__label {
    color: var(--error);
}

/* Modifier: inline — label sits BESIDE the control (label auto, control fills). */
.hzo-field--inline {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: center;
    gap: var(--space-sm);
}
/* Hint + error span both columns so they align under the control. */
.hzo-field--inline .hzo-field__hint,
.hzo-field--inline .hzo-field__error {
    grid-column: 2;
}

/* Disabled — dims the whole field group (the control inside is disabled in its
   own markup; this greys the label/hint to match). No focus idiom: nothing in
   the wrapper is focusable. */
.hzo-field[aria-disabled="true"],
.hzo-field.is-disabled {
    opacity: var(--opacity-disabled);
    pointer-events: none;
}


/* --- file-upload/file-upload.css --- */
/* file-upload — @tokens: --file-upload-icon-size

   A dropzone that wraps a hidden native <input type="file">. The root is the
   <label>, so a click anywhere on the zone opens the file picker while the input
   stays keyboard-focusable behind the sr-only pattern. `__zone` is the visible
   dashed panel; `.is-dragover` is JS-toggled onto it during a drag.

   Token-only: the glyph box has no semantic token in the scale, so it's a
   component token with a literal fallback — the same dial pattern
   icon-button.css uses (`var(--icon-button-icon-size, 20px)`). The hover /
   dragover tint reads --brand-pale (a declared L2 token). Everything else reads
   a semantic/L1 token. */
.hzo-file-upload {
    display: block;
    cursor: pointer;
}

/* Native input — visually hidden (sr-only) but kept in the tab order + as the
   drop target. */
.hzo-file-upload__input {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
    opacity: 0;
}

/* Visible dropzone — the dashed panel the user sees and drags onto. */
.hzo-file-upload__zone {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-xl);
    border: var(--border-2) dashed var(--border);
    border-radius: var(--radius-panel);
    background: var(--bg-alt);
    color: var(--text-muted);
    text-align: center;
    transition: border-color var(--transition-base) ease, background var(--transition-base) ease;
}

/* Leading glyph — fixed box, muted ink. */
.hzo-file-upload__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--file-upload-icon-size, 40px);
    height: var(--file-upload-icon-size, 40px);
    color: var(--text-muted);
}
.hzo-file-upload__icon > svg { display: block; }

/* Prompt text — can carry a bolded <strong> "Browse" + a muted remainder. */
.hzo-file-upload__text {
    font-size: var(--fs-100);
    line-height: var(--lh-normal);
}

/* Hover / dragover — brand border + subtle brand tint to signal the live drop
   target. Two spellings of one meaning: :hover for pointer, `.is-dragover` for
   the JS-toggled drag state. */
.hzo-file-upload:hover .hzo-file-upload__zone,
.hzo-file-upload__zone.is-dragover {
    border-color: var(--brand);
    background: var(--brand-pale);
}

/* A11y — required. Focus lives on the sr-only input; surface the ring on the
   visible zone via the sibling combinator. Disabled state. Reduced-motion is
   handled globally by components/_base/a11y.css. */
.hzo-file-upload__input:focus-visible ~ .hzo-file-upload__zone {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
}
.hzo-file-upload.is-disabled,
.hzo-file-upload__input[disabled] ~ .hzo-file-upload__zone,
.hzo-file-upload__input[aria-disabled="true"] ~ .hzo-file-upload__zone {
    opacity: var(--opacity-disabled);
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    .hzo-file-upload__zone { transition: none; }
}


/* --- filters/filters.css --- */
/* ─── Filters / facets (UI surface) ───────────────────────────────────
   Accessible faceted-filter UI: fieldset/legend groups of checkboxes (multi)
   or radios (single), active-filter chips (reuse .hzo-tag--removable), a
   clear-all control, and an aria-live result-count region. Behaviour — chip↔box
   sync, clear-all, count announce, and the hzo:filterchange event — is wired by
   filters.js (Phase-2 widget) off the .hzo-filters / [data-hzo-filters] contract.
   UI surface only: it emits state, it does not query. Zero new tokens.
   Markup contract: component-patterns.md §47. */
.hzo-filters { display: flex; flex-direction: column; gap: var(--space-md); max-width: 320px; }
.hzo-filters__bar {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-sm); flex-wrap: wrap;
}
.hzo-filters__count { margin: 0; font-size: 0.875rem; color: var(--text-muted); }
.hzo-filter-group { border: 0; margin: 0; padding: 0; min-width: 0; }
.hzo-filter-group > legend {
    padding: 0; margin: 0 0 var(--space-sm);
    font-family: var(--font-heading);
    font-weight: var(--fw-600);
    font-size: 0.9375rem; color: var(--text-heading);
}
.hzo-filter-group + .hzo-filter-group { border-top: var(--border-1) solid var(--border); padding-top: var(--space-md); }
.hzo-filter-group__options { display: flex; flex-direction: column; gap: var(--space-sm); }
.hzo-filter-option {
    display: flex; align-items: center; gap: 10px;
    font-size: 0.9375rem; color: var(--text-body); line-height: 1.3; cursor: pointer;
}
.hzo-filter-option > input { flex: 0 0 auto; width: 16px; height: 16px; accent-color: var(--brand); cursor: pointer; }

/* G26 — no-JS "Apply filters" submit (rendered inside <noscript>, so present
   only when scripting is off). Spaced from the last fieldset. */
.hzo-filters__apply { margin-top: var(--space-md); align-self: flex-start; }


/* --- footer/footer.css --- */
/* ─── Site footer chrome + typography ──────────────────────────────────
   Aggregator sheet — the component loader (includes/component-loader.php)
   enqueues ONLY <name>.css per folder, so the footer's two variant sheets
   are @imported here to ride the same request. This is their ONLY front-end
   load path: on the front end hzostudio_enqueue_components() REPLACES the
   whole-file primitives.css enqueue, so primitives.css's own @imports of
   these sheets (lines 27 + 41) load in the block-editor context only. Every
   other component's primary sheet is already named <name>.css and is picked
   up directly; footer's was split into two non-<name>.css files, so without
   this aggregator the footer renders UNSTYLED on the front end.
   (Same fix pattern as video.css @importing lite-video + video-background —
   Wave-4 lesson: variant sheets predating widgetization need a <name>.css.)
   Order matches primitives.css: typography (link/heading/NAP) then chrome
   (background + grid). @imports must precede all rules — keep them first. */
/* hzo-bundle: skipped @import ./footer-typography.cssver1788182399 */
/* hzo-bundle: skipped @import ./footer-chrome.cssver1788182399 */

/* Reduced-motion: kill the color transitions the footer link + NAP anchors
   animate (belt-and-braces on top of the variant sheets — per the convention
   the aggregator ends with a transition-kill; mirrors the shipped video.css). */
@media (prefers-reduced-motion: reduce) {
    .hzo-footer-link,
    .hzo-footer-nap__row a { transition: none; }
}


/* --- form/form.css --- */
/* Variant sheets — @imported here so the component loader, which enqueues
   only form.css for this folder, pulls them on the same request (widget
   convention; precedent: carousel.css, tooltip.css). Order: Fluent wrapper/
   layout first, then the field-level enhancement overlays. */
/* hzo-bundle: skipped @import ./fluent-forms-wrapper.cssver1788182399 */
/* hzo-bundle: skipped @import ./fluent-forms-multistep.cssver1788182399 */
/* hzo-bundle: skipped @import ./fluent-forms-file-upload.cssver1788182399 */
/* hzo-bundle: skipped @import ./choices.cssver1788182399 */
/* hzo-bundle: skipped @import ./flatpickr.cssver1788182399 */
/* hzo-bundle: skipped @import ./format-validation.cssver1788182399 */
/* hzo-bundle: skipped @import ./switch-toggle.cssver1788182399 */
/* hzo-bundle: skipped @import ./textarea-resize.cssver1788182399 */
/* hzo-bundle: skipped @import ./disabled-fields.cssver1788182399 */
/* hzo-bundle: skipped @import ./ff-list-buttons.cssver1788182399 */

/* ─── Forms (opt-in via .hzo-form wrapper) ──────────────────────
   Rules activate only inside a .hzo-form container. Targets two
   surfaces:

   1. Native HTML form elements (input/textarea/select) — also catches
      Fluent Forms' rendered output (Fluent Forms emits standard HTML
      inputs inside .ff-el-form-control wrappers; the .hzo-form wrapper
      style applies via tag-name match, no plugin-specific selectors
      required).
   2. Hozio Studio BEM classes (.hzo-form__label, .hzo-form__description,
      .hzo-form__option, .hzo-form__message--error/success,
      .hzo-form__input--error) — direct authoring + validation states.

   !important is retained on field-level rules so the styles win
   against equal-specificity plugin output (Fluent Forms, or any form
   plugin we swap to later) when source order isn't guaranteed. NOT
   carried into structural rules. */
.hzo-form { max-width: 640px; }

.hzo-form input[type="text"],
.hzo-form input[type="email"],
.hzo-form input[type="tel"],
.hzo-form input[type="number"],
.hzo-form input[type="url"],
.hzo-form input[type="password"],
.hzo-form input[type="date"],
.hzo-form textarea,
.hzo-form select {
    width: 100%;
    padding: var(--input-padding, 12px 16px) !important;
    font-family: var(--font-body) !important;
    font-size: 1rem !important;
    color: var(--text-body) !important;
    background: var(--input-bg, #fff) !important;
    border: 1px solid var(--input-border, var(--border)) !important;
    border-radius: var(--input-radius, var(--radius-input)) !important;
    line-height: var(--lh-normal);
    transition: border-color 0.15s, box-shadow 0.15s;
    box-sizing: border-box;
}

.hzo-form input:focus,
.hzo-form textarea:focus,
.hzo-form select:focus {
    outline: none;
    border-color: var(--color-focus) !important;
    box-shadow: 0 0 0 2px var(--brand-pale) !important;
}

.hzo-form input::placeholder,
.hzo-form textarea::placeholder { color: var(--color-placeholder); }

/* Validation error state — opt-in modifier class. Applied to the input
   element itself (e.g. <input class="hzo-form__input--error">). */
.hzo-form .hzo-form__input--error,
.hzo-form input.hzo-form__input--error,
.hzo-form textarea.hzo-form__input--error,
.hzo-form select.hzo-form__input--error {
    border-color: var(--error) !important;
    box-shadow: 0 0 0 2px var(--error-bg) !important;
}

/* G49: mirror the same red border + ring onto Fluent's live-validation hook.
   Fluent flags an invalid field's group with .ff-el-group.ff-el-is-error; the
   engine's own modifier never lands on plugin-rendered fields, so without this
   a Fluent form's client-side errors would show the plugin's default styling
   instead of ours. Checkboxes/radios keep their control box untouched. */
.hzo-form .ff-el-is-error input:not([type="checkbox"]):not([type="radio"]),
.hzo-form .ff-el-is-error textarea,
.hzo-form .ff-el-is-error select {
    border-color: var(--error) !important;
    box-shadow: 0 0 0 2px var(--error-bg) !important;
}
.hzo-form .ff-el-is-error .text-danger,
.hzo-form .ff-el-is-error .error { color: var(--error) !important; }

/* PF-069 — `.is-loading` must always PAINT something. form.js's submit-lock puts
   the class on whatever the form's submit control is: `.ff-btn-submit` (Fluent —
   spinner recipe in choices.css), `.hzo-btn`/`.hzo-button` (buttons.css spinner),
   or, in a hand-authored .hzo-form, a bare <button type="submit"> that matches
   NEITHER — which rendered nothing at all: the class hid no label, painted no
   spinner, and left an inert-looking button as the only feedback. Cover the
   remainder here. Own keyframe because spinner.css is gated and may be absent on
   a form-only page (same reason choices.css declares its own). */
@keyframes hzo-form-submit-spin { to { transform: rotate(360deg); } }
.hzo-form button[type="submit"].is-loading:not(.hzo-btn):not(.hzo-button):not(.ff-btn-submit) {
    color: transparent !important;
    position: relative;
    pointer-events: none;
}
.hzo-form button[type="submit"].is-loading:not(.hzo-btn):not(.hzo-button):not(.ff-btn-submit)::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 18px;
    height: 18px;
    margin: -9px 0 0 -9px;
    border-radius: 50%;
    /* NOT currentColor — the rule above sets the element's own color to
       transparent to hide the label, and a pseudo-element inherits it, so
       currentColor would paint an invisible spinner. Same reason choices.css
       names the token explicitly. */
    border: 2px solid color-mix(in srgb, var(--btn-primary-text, var(--text-on-accent)) 30%, transparent);
    border-top-color: var(--btn-primary-text, var(--text-on-accent));
    animation: hzo-form-submit-spin 0.6s linear infinite;
}
/* <input type="submit"> has no ::after to spin — a dim + progress cursor is the
   only in-element cue the element type can carry. */
.hzo-form input[type="submit"].is-loading {
    opacity: 0.6;
    cursor: progress;
    pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
    .hzo-form button[type="submit"].is-loading:not(.hzo-btn):not(.hzo-button):not(.ff-btn-submit)::after {
        animation-duration: 1.5s;
    }
}

/* Select dropdown: hide native arrow, paint a custom chevron (Hozio-style).
   Uses the `background` shorthand to override the .hzo-form input/select
   rule above (which also uses the shorthand with !important).
   Stroke color #1F2937 matches --text-heading for stronger contrast. */
.hzo-form select {
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    padding-right: 44px !important;
    background: var(--input-bg, #fff) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231F2937' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>") no-repeat right 16px center / 16px 16px !important;
    cursor: pointer;
}

/* Field labels — BEM class (direct authoring). Matches the Fluent
   .ff-el-input--label label rule below so direct-authored and plugin-
   rendered forms look identical. */
.hzo-form .hzo-form__label {
    display: block;
    font-family: var(--font-body);
    font-size: 15px;
    font-weight: var(--fw-600);
    line-height: 1.4;
    color: var(--text-body);
    margin-bottom: 6px;
}

/* Field descriptions — BEM class. */
.hzo-form .hzo-form__description {
    font-size: 15px;
    color: var(--text-muted);
    margin-top: 6px;
}

/* Validation messages — BEM modifier classes (rendered by the form handler
   or hand-placed). */
.hzo-form .hzo-form__message--error   { color: var(--error); }
.hzo-form .hzo-form__message--success { color: var(--success); }

/* Radio/checkbox option row layout — BEM class. */
.hzo-form .hzo-form__option {
    display: inline-flex;
    align-items: center;
    font-size: 15px;
    color: var(--text-body);
    line-height: 1.4;
    padding: 6px 0;
    cursor: pointer;
}

/* Custom-styled checkboxes and radios.
   Hozio-style: 20px square/round, border --border, filled --brand when checked
   with white check/dot indicator. !important kept on appearance/visual
   properties because browser-default rendering + plugin form output may
   apply equal-specificity styling that source order does not guarantee
   defeating. */
.hzo-form input[type="checkbox"],
.hzo-form input[type="radio"] {
    appearance: none !important;
    -webkit-appearance: none !important;
    -moz-appearance: none !important;
    width: 20px !important;
    height: 20px !important;
    padding: 0 !important;
    margin-right: 10px !important;
    border: 1.5px solid var(--border) !important;
    background: #fff !important;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
    flex-shrink: 0;
    position: relative;
    display: inline-block;
}
.hzo-form input[type="checkbox"] { border-radius: 4px !important; }
.hzo-form input[type="radio"]    { border-radius: 50% !important; }

.hzo-form input[type="checkbox"]:hover,
.hzo-form input[type="radio"]:hover {
    border-color: var(--color-focus) !important;
}

.hzo-form input[type="checkbox"]:focus-visible,
.hzo-form input[type="radio"]:focus-visible {
    outline: none !important;
    border-color: var(--color-focus) !important;
    box-shadow: 0 0 0 3px var(--brand-pale) !important;
}

.hzo-form input[type="checkbox"]:checked,
.hzo-form input[type="radio"]:checked {
    background: var(--brand) !important;
    border-color: var(--brand) !important;
}

/* Checkmark for checked checkbox */
.hzo-form input[type="checkbox"]:checked::after {
    content: "";
    position: absolute;
    left: 5px;
    top: 1px;
    width: 6px;
    height: 11px;
    border: solid #fff;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

/* Dot for selected radio */
.hzo-form input[type="radio"]:checked::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #fff;
    transform: translate(-50%, -50%);
}


/* --- free-ship/free-ship.css --- */
/* ─── Free-Shipping Progress Bar (.hzo-free-ship) — W-134 ──────────────────
   A live-cart meter: a message line above a track whose fill grows toward the
   free-shipping threshold, flipping to an unlocked state at 100%. Flow-level
   component (no z-index / no positioning of its own) so it composes inside a
   page section, a banner slot, or the mini-cart body. Tokens only — no bare hex,
   px-where-a-token-exists, or bare motion literals (interaction-standards §9).

   The fill width is driven by --hzo-free-ship-pct (0–100), which free-ship.js
   sets from the live subtotal; the server bakes the same value into first paint,
   so the bar is correct with JS off (progressive enhancement). */

.hzo-free-ship {
	display: block;
	width: 100%;
}

/* The server-truth data node — machine-readable only, never shown. Not a live
   region, so display:none is safe (announcements route through hzo.announce). */
.hzo-free-ship__data {
	display: none !important;
}

.hzo-free-ship__message {
	margin: 0 0 var(--space-sm);
	font-size: var(--fs-100);
	font-weight: var(--fw-600);
	line-height: var(--lh-snug);
	color: var(--text-body);
}

/* The emphasised money figure reads as brand-forward once unlocked. */
.hzo-free-ship.is-unlocked .hzo-free-ship__message {
	color: var(--success-ink);
}

.hzo-free-ship__track {
	position: relative;
	width: 100%;
	height: 8px;
	background: var(--bg-alt);
	border: var(--border-1) solid var(--border);
	border-radius: var(--radius-pill);
	overflow: hidden;
}

.hzo-free-ship__fill {
	/* --hzo-free-ship-pct set by JS / first paint; 0 fallback keeps it sane. */
	width: clamp(0%, calc(var(--hzo-free-ship-pct, 0) * 1%), 100%);
	height: 100%;
	border-radius: inherit;
	background: var(--brand);
	transition: width var(--dur-4) var(--ease-out), background-color var(--dur-3) var(--ease-standard);
}

.hzo-free-ship.is-unlocked .hzo-free-ship__fill {
	background: var(--success);
}

/* ── Variant: slim — a thinner track, tighter message ──────────────────────*/
.hzo-free-ship--slim .hzo-free-ship__message {
	font-size: var(--fs-75);
	margin-bottom: var(--space-xs);
}
.hzo-free-ship--slim .hzo-free-ship__track {
	height: 4px;
}

/* ── Variant: pill — a filled card wrapper around the meter ────────────────*/
.hzo-free-ship--pill {
	padding: var(--space-smd) var(--space-md);
	background: var(--surface);
	border: var(--border-1) solid var(--border);
	border-radius: var(--radius-panel);
	box-shadow: var(--elevation-rest);
}
.hzo-free-ship--pill.is-unlocked {
	border-color: var(--success);
	background: var(--success-bg);
}

/* ── Pulse-on-unlock (JS adds .is-pulsing for ~900ms) ──────────────────────
   A one-shot celebratory pulse on the track when the threshold is crossed.
   Ambient/decorative motion → fully removed under reduced-motion (PRESERVE-5);
   the state change itself remains conveyed by color + message. */
@keyframes hzo-free-ship-pulse {
	0%   { transform: scaleX(1);    }
	40%  { transform: scaleX(1.02); }
	100% { transform: scaleX(1);    }
}

.hzo-free-ship.is-pulsing .hzo-free-ship__track {
	transform-origin: left center;
	animation: hzo-free-ship-pulse var(--dur-4) var(--ease-spring);
}

/* A soft glow ring on the whole bar as it unlocks — decorative, motion-gated. */
@keyframes hzo-free-ship-glow {
	0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--success) 40%, transparent); }
	100% { box-shadow: 0 0 0 var(--space-sm) color-mix(in srgb, var(--success) 0%, transparent); }
}
.hzo-free-ship.is-pulsing {
	border-radius: var(--radius-pill);
	animation: hzo-free-ship-glow var(--dur-4) var(--ease-out);
}

/* ── Reduced motion — stop the ambient pulse/glow AND the fill slide; the fill
   still snaps to the correct width, state still conveyed by color + text. ──*/
@media (prefers-reduced-motion: reduce) {
	.hzo-free-ship__fill {
		transition: none;
	}
	.hzo-free-ship.is-pulsing,
	.hzo-free-ship.is-pulsing .hzo-free-ship__track {
		animation: none;
	}
}

/* ── Forced colors (Windows High Contrast) — an opacity/paint-only fill
   vanishes, so express the track/fill with system colors + a visible boundary
   so progress stays perceivable (interaction-standards §12.2). ──*/
@media (forced-colors: active) {
	.hzo-free-ship__track {
		border-color: CanvasText;
	}
	.hzo-free-ship__fill {
		background: Highlight;
		forced-color-adjust: none;
	}
	.hzo-free-ship.is-unlocked .hzo-free-ship__fill {
		background: LinkText;
	}
}


/* --- hamburger/hamburger.css --- */
/* ─── Hamburger trigger (below 1250px) ─────────────────────────── */
.hzo-mobile-trigger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    margin-left: auto;
    /* Visual right-edge alignment: the SVG has ~10px of internal whitespace
       padding to its icon, so the icon appears inset from the button's right
       edge. Pull the button -10px right so the icon's visible edge aligns
       with the container's right edge (matching the logo's left alignment). */
    margin-right: -10px;
    border: 0;
    background: transparent;
    cursor: pointer;
    color: var(--nav-mobile-trigger-color, var(--text-heading));
    flex-shrink: 0;
}
.hzo-mobile-trigger:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
@media (min-width: 1250px) { .hzo-mobile-trigger { display: none; } }


/* --- icon-button/icon-button.css --- */
/* icon-button — @tokens: --icon-button-size, --icon-button-icon-size, --icon-button-radius, --icon-button-color, --icon-button-hover-bg, --icon-button-hover-color

   The general-purpose square/round icon control: a bare <button> (or <a>) whose
   only content is an icon (svg / icon font). Sibling to the specialized
   `.hzo-close` (which is the dismiss-specific variant); use `.hzo-icon-button`
   for toolbar actions, card actions, pagination arrows, share/like toggles, etc.

   Token-only: control DIMENSIONS (box + icon size) have no semantic token in the
   scale, so they are component tokens with a literal fallback — the same dial
   pattern buttons.css uses (`var(--btn-padding-y, 14px)`). Everything else reads
   a semantic/L1 token. */
.hzo-icon-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: var(--icon-button-size, 40px);
    height: var(--icon-button-size, 40px);
    padding: 0;
    border: var(--border-1) solid transparent;
    background: transparent;
    color: var(--icon-button-color, var(--text-muted));
    border-radius: var(--icon-button-radius, var(--radius-control));
    cursor: pointer;
    transition: background var(--transition-base) ease, color var(--transition-base) ease, border-color var(--transition-base) ease;
}
.hzo-icon-button > svg,
.hzo-icon-button > .hzo-icon {
    display: block;
    width: var(--icon-button-icon-size, 20px);
    height: var(--icon-button-icon-size, 20px);
}

/* Hover / active — ghost fill on the alt surface with heading-ink foreground. */
.hzo-icon-button:hover,
.hzo-icon-button.is-active {
    background: var(--icon-button-hover-bg, var(--bg-alt));
    color: var(--icon-button-hover-color, var(--text-heading));
}

/* Variants — solid (brand) and outline. Default (no modifier) is the ghost above. */
.hzo-icon-button--solid {
    background: var(--brand);
    color: var(--text-on-brand);
    border-color: var(--brand);
}
.hzo-icon-button--solid:hover,
.hzo-icon-button--solid.is-active {
    background: var(--brand-hover-on-light);
    color: var(--text-on-brand);
    border-color: var(--brand-hover-on-light);
}
.hzo-icon-button--outline {
    border-color: var(--border);
    color: var(--text-body);
}
.hzo-icon-button--outline:hover,
.hzo-icon-button--outline.is-active {
    border-color: var(--brand);
    color: var(--brand-text);
    background: transparent;
}

/* Sizes — small / large dial the box + icon together. */
.hzo-icon-button--sm { --icon-button-size: 32px; --icon-button-icon-size: 16px; }
.hzo-icon-button--lg { --icon-button-size: 48px; --icon-button-icon-size: 24px; }

/* A11y — required. Focus ring; disabled state. Reduced-motion handled globally
   by components/_base/a11y.css. */
.hzo-icon-button:focus-visible {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
}
.hzo-icon-button[disabled],
.hzo-icon-button[aria-disabled="true"],
.hzo-icon-button.is-disabled {
    opacity: var(--opacity-disabled);
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    .hzo-icon-button { transition: none; }
}


/* --- icon-grid/icon-grid.css --- */
/* icon-grid — @tokens: --icon-grid-min, --icon-grid-icon-size

   A responsive grid of icon + title + text cells (services / features / benefits).
   A CONTENT BLOCK dropped INSIDE a `.hzo-section` > `.hzo-container` (it is NOT
   full-bleed — the section owns the vertical rhythm + background). Primitive:
   CSS-only, not interactive (carries no control, so no focus/disabled idioms).

   Token-only: the responsive column floor + the icon box have no dedicated
   semantic role in the scale, so they're component tokens with a literal
   fallback (the icon-button.css dial pattern). Everything else reads a
   semantic/L1 token — so an archetype re-themes the ink + accent from :root. */
.hzo-icon-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(var(--icon-grid-min, 240px), 1fr));
    gap: var(--space-xl);
}

/* Each cell — a stacked flex column: icon over title over text. */
.hzo-icon-grid__item {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

/* Accent-tinted glyph in a fixed box so ragged svg viewBoxes align on the grid.
   flex: 0 0 auto keeps it from stretching in the column. */
.hzo-icon-grid__icon {
    flex: 0 0 auto;
    width: var(--icon-grid-icon-size, 40px);
    height: var(--icon-grid-icon-size, 40px);
    color: var(--accent);
}
.hzo-icon-grid__icon > svg {
    display: block;
    width: 100%;
    height: 100%;
}

/* Cell heading — heading ink, semibold, a heading-scale size. */
.hzo-icon-grid__title {
    margin: 0;
    font-size: var(--fs-400);
    font-weight: var(--fw-600);
    line-height: var(--lh-snug);
    color: var(--text-heading);
}

/* Supporting copy — muted ink, comfortable leading. */
.hzo-icon-grid__text {
    margin: 0;
    font-size: var(--fs-200);
    line-height: var(--lh-normal);
    color: var(--text-muted);
}

/* Center modifier — centers each cell's contents + its text block. */
.hzo-icon-grid--center .hzo-icon-grid__item {
    align-items: center;
    text-align: center;
}


/* --- icon-list/icon-list.css --- */
/* icon-list — @tokens: --icon-list-icon-size

   An unordered list with a leading icon per row (feature bullets, spec sheets,
   "what's included" / checklists). The list resets its native marker and lays
   the rows out on a grid; each row is a flex line with a fixed-size leading
   icon and top-aligned text. The `--checklist` modifier recolours the icon to
   the success role for "✓ included" lists.

   Token-only: gap/colour read the semantic scale; the fixed icon box has no
   semantic token, so it is a component token with a literal fallback (the
   icon-button.css dial pattern). */
.hzo-icon-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    gap: var(--space-sm);
    color: var(--text-body);
}

.hzo-icon-list__item {
    display: flex;
    gap: var(--space-sm);
    align-items: flex-start;
}

/* Leading icon — accent-tinted, fixed square, never shrinks under long text. */
.hzo-icon-list__icon {
    flex: 0 0 auto;
    width: var(--icon-list-icon-size, 20px);
    height: var(--icon-list-icon-size, 20px);
    color: var(--accent);
}

/* Checklist variant — swap the accent tint for the success (✓ done) role. */
.hzo-icon-list--checklist .hzo-icon-list__icon {
    color: var(--success);
}


/* --- img/img.css --- */
/* ─── LQIP Progressive Image (.hzo-img) ────────────────────────────────────
   W-023 · closes audit gap G51 (missing aspect/dimensions → CLS). The fleet's
   photo primitive: an aspect-ratio box that reserves EXACT layout space (zero
   CLS) with a tiny LQIP placeholder that blurs up to the full image on decode.

   FAIL-VISIBLE BY CONSTRUCTION (mirrors the reveal orchestrator, W-020): the
   armed leg that hides the full image (opacity:0) applies ONLY under the
   JS-set `html.hzo-img-ready` flag. No JS, a thrown init, reduced-motion,
   forced-colors, or print → the flag is absent → the FULL image is fully
   visible and the whole widget degrades to a plain <img> in a reserved box.
   The box + placeholder are server-rendered and paint on the first frame with
   zero layout dependency on JS — the reserved box IS the sub-100ms guarantee.

   Single-cell grid overlay stacks the LQIP under the full image WITHOUT a
   z-index (§9.3): grid-area:1/1 + DOM order (LQIP first, full second) is the
   paint order. No new z-index token is introduced. */

.hzo-img {
    display: grid;
    width: 100%;
    max-width: 100%;
    /* Reserved box → zero CLS. `auto` (unset aspect) falls back to the intrinsic
       ratio the width/height attrs on .hzo-img__full provide. */
    aspect-ratio: var(--hzo-img-aspect, auto);
    overflow: hidden;
    /* A neutral frame shows through until the LQIP/full paints (and stays as the
       error frame). Token only — no hardcoded color. */
    background-color: var(--surface-2);
}

/* Both layers occupy the one grid cell and fill it — object-fit does the crop. */
.hzo-img > .hzo-img__lqip,
.hzo-img > .hzo-img__full {
    grid-area: 1 / 1;
    display: block;
    width: 100%;
    height: 100%;
    margin: 0;
    object-fit: var(--hzo-img-fit, cover);
    object-position: var(--hzo-img-pos, center);
}

/* ── The full image. Default opacity:1 so NO-JS / reduced-motion / forced-colors
      / print all show it. The fade is declared here so the armed→revealed
      transition (opacity 0→1) animates once the armed leg stops matching. ── */
.hzo-img__full {
    opacity: 1;
    transition: opacity var(--hzo-img-fade, var(--dur-3)) var(--ease-out);
}

/* ── The LQIP placeholder (decorative, aria-hidden in markup). ── */
.hzo-img__lqip {
    opacity: 1;
    transition: opacity var(--hzo-img-fade, var(--dur-3)) var(--ease-out);
}
/* base64 / blurhash: a tiny bitmap scaled up + blurred. The slight scale hides
   the blur's soft edges against the box. */
.hzo-img__lqip--blur {
    filter: blur(var(--hzo-img-blur, 12px));
    transform: scale(1.04);
}
/* color: a flat dominant-color wash (no bitmap). */
.hzo-img__lqip--color {
    background-color: var(--hzo-img-lqip-color, var(--surface-2));
}

/* ═══ Armed (hidden) leg — ONLY under the JS-ready flag ════════════════════
   Before decode, hide the full image so the LQIP shows through; img.js flips
   .hzo-img--loading → .hzo-img--loaded on decode, this selector stops matching,
   and the full image fades in over --hzo-img-fade. */
.hzo-img-ready .hzo-img--loading .hzo-img__full {
    opacity: 0;
}

/* Decoded: the full image is visible (default opacity:1 via the rule above) and
   the LQIP fades out beneath it, then is inert. */
.hzo-img--loaded .hzo-img__lqip {
    opacity: 0;
}

/* ═══ Error state — reconcile the optimistic blur to a real fallback ════════
   img.js sets .hzo-img--error on the <img> `error` event (or a never-resolving
   decode watchdog): drop the frozen blur, keep the neutral frame, and show the
   alt as visible text so a broken source is never a silent empty box. */
.hzo-img--error .hzo-img__lqip {
    display: none;
}
.hzo-img--error .hzo-img__full {
    opacity: 0; /* the broken <img> icon is suppressed; the fallback text carries meaning */
}
.hzo-img__fallback {
    grid-area: 1 / 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-md);
    text-align: center;
    color: var(--text-muted);
    font-size: var(--fs-100);
    line-height: var(--lh-snug);
}

/* ═══ Fail-visible environment branches (spec §12) ═════════════════════════ */

/* reduced-motion [PRESERVE-5 · G13/G60]: the blur-up cross-fade is AMBIENT, so
   it fully STOPS — the full image appears instantly on decode, never stranded
   at opacity:0. (img.js also skips the ready flag under reduce; this is the CSS
   belt to its suspenders.) */
@media (prefers-reduced-motion: reduce) {
    .hzo-img__full,
    .hzo-img__lqip {
        transition: none !important;
    }
    .hzo-img--loading .hzo-img__full {
        opacity: 1 !important;
    }
}

/* forced-colors / High Contrast [§12.2]: opacity/blur intermediates are
   unreliable, so show the full image instantly and drop the decorative blur.
   The error frame uses SYSTEM colors (not opacity) so it stays visible. */
@media (forced-colors: active) {
    .hzo-img--loading .hzo-img__full {
        opacity: 1 !important;
    }
    .hzo-img__lqip--blur {
        display: none;
    }
    .hzo-img--error {
        background-color: Canvas;
        border: var(--border-1) solid GrayText;
    }
    .hzo-img--error .hzo-img__fallback {
        color: CanvasText;
    }
}

/* @media print [G13 — the reveal-hidden-content culprit class, applied to the
      decode-fade]: a printed estimate/service page must NEVER show a blurred
      thumbnail. Force the full image visible and hide the placeholder. The
      beforeprint JS handler additionally flushes any still-loading instance. */
@media print {
    .hzo-img__full {
        opacity: 1 !important;
    }
    .hzo-img__lqip {
        display: none !important;
    }
}


/* --- input-group/input-group.css --- */
/* ─── Input group (input + button) ────────────────────────────────────
   Joins a text input + a .hzo-btn into one control (header search, footer
   newsletter). Use STANDALONE — not inside .hzo-form, which stacks full-width
   fields; the group styles its own input. */
.hzo-input-group { display: flex; width: 100%; max-width: 480px; }
.hzo-input-group > input {
    flex: 1;
    min-width: 0;
    padding: var(--input-padding, 13px 16px);
    font-family: var(--font-body);
    font-size: 1rem;
    color: var(--text-body);
    background: var(--input-bg, #fff);
    border: 1px solid var(--input-border, var(--border));
    border-right: 0;
    border-radius: var(--input-radius, var(--radius-input)) 0 0 var(--input-radius, var(--radius-input));
}
.hzo-input-group > input:focus {
    outline: none;
    border-color: var(--color-focus);
    box-shadow: 0 0 0 2px var(--brand-pale);
    position: relative;
    z-index: 1;
}
.hzo-input-group > input::placeholder { color: var(--color-placeholder); }
.hzo-input-group > .hzo-btn {
    flex-shrink: 0;
    border-radius: 0 var(--btn-radius, var(--radius-input)) var(--btn-radius, var(--radius-input)) 0;
}
/* The base mobile rule sets non-inline buttons to width:100%, which would crush
   the input in this flex row. Keep input-group buttons intrinsic on mobile —
   specificity (0,0,5,0) beats the base rule (0,0,4,0) regardless of source order. */
@media (max-width: 767px) {
    .hzo-input-group > .hzo-btn:not(.hzo-btn--inline):not(.hzo-btn--icon):not(.hzo-btn--block) { width: auto; }
}


/* --- kbd/kbd.css --- */
/* kbd — @tokens: (none — all semantic/L1)

   An inline keyboard-key hint (`<kbd>Ctrl</kbd>`) — the little raised key cap
   used in docs / shortcut lists / tooltips. A thicker BOTTOM border than the
   other sides gives the key-depth (pressable-cap) look. Static; no interaction.

   Token-only: type/spacing/surface/border all read the semantic scale;
   monospace is a generic family keyword (not an aesthetic literal). */
.hzo-kbd {
    display: inline-block;
    font-family: monospace;
    font-size: var(--fs-50);
    line-height: var(--lh-none);
    padding: var(--space-2xs) var(--space-xs);
    background: var(--bg-alt);
    color: var(--text-heading);
    border: var(--border-1) solid var(--border);
    border-bottom-width: var(--border-2);
    border-radius: var(--radius-control);
}


/* --- key-value-row/key-value-row.css --- */
/* key-value-row — @tokens: (none — all values map to L1/L2 tokens)

   Flexible label→value rows for specs / details / summary panels. Distinct from
   `definition-list` (which is <dl>/dt/dd semantics): this is a flex spec-row layout
   — key on the left, value pushed to the right, a hairline rule between rows.
   Root class = folder name per the authoring standard, so the container is
   `.hzo-key-value-row` and each pair is a `__row` (BEM avoids the flat-sibling
   `.hzo-key-value-row-row` drift).

   Token-only: every border / spacing / weight / colour reads a semantic or L1
   token directly, so no component tokens are needed. Static/structural primitive
   — nothing is focusable, so no focus/disabled a11y idioms. */
.hzo-key-value-row {
    font-size: var(--fs-200);
    line-height: var(--lh-normal);
    color: var(--text-body);
}

/* Row — key/value on a baseline, value pushed to the far edge, hairline below. */
.hzo-key-value-row__row {
    display: flex;
    justify-content: space-between;
    gap: var(--space-md);
    padding: var(--space-sm) 0;
    border-bottom: var(--border-1) solid var(--border);
}
.hzo-key-value-row__row:last-child {
    border-bottom: 0;
}

/* Key — muted label ink; never shrinks below its content. */
.hzo-key-value-row__key {
    color: var(--text-muted);
    flex-shrink: 0;
}

/* Value — heading ink, semibold, right-aligned so numbers/values line up. */
.hzo-key-value-row__value {
    color: var(--text-heading);
    font-weight: var(--fw-600);
    text-align: right;
}

/* Plain — drop the row rules for a borderless list (padding rhythm kept). */
.hzo-key-value-row--plain .hzo-key-value-row__row {
    border-bottom: 0;
}


/* --- list-group/list-group.css --- */
/* list-group — @tokens: (none — all semantic/L1)

   A bordered, divided vertical list (Bootstrap list-group idiom): settings
   rows, nav lists, summary line-items. The panel owns one outer border +
   radius and clips its corners; each item carries a bottom divider (the last
   one drops it). The `--interactive` modifier turns items into hoverable /
   focusable rows (links or buttons); the base list is static text.

   Token-only: border/radius/spacing/colour all read the semantic scale. */
.hzo-list-group {
    display: flex;
    flex-direction: column;
    border: var(--border-1) solid var(--border);
    border-radius: var(--radius-panel);
    overflow: hidden;
    font-size: var(--fs-200);
    line-height: var(--lh-normal);
}

.hzo-list-group__item {
    padding: var(--space-md);
    border-bottom: var(--border-1) solid var(--border);
    color: var(--text-body);
}
.hzo-list-group__item:last-child { border-bottom: 0; }

/* Selected/current row — brand fill with its paired on-brand ink. */
.hzo-list-group__item.is-active {
    background: var(--brand);
    color: var(--text-on-brand);
}

/* Interactive modifier — items become hoverable / focusable controls.
   Guard the hover fill transition behind reduced-motion. */
.hzo-list-group--interactive .hzo-list-group__item {
    cursor: pointer;
    transition: background var(--transition-base) ease, color var(--transition-base) ease;
}
.hzo-list-group--interactive .hzo-list-group__item:hover {
    background: var(--bg-alt);
}
/* A11y — focus ring + disabled idiom on the interactive rows. */
.hzo-list-group--interactive .hzo-list-group__item:focus-visible {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
}
.hzo-list-group--interactive .hzo-list-group__item[disabled],
.hzo-list-group--interactive .hzo-list-group__item[aria-disabled="true"],
.hzo-list-group--interactive .hzo-list-group__item.is-disabled {
    opacity: var(--opacity-disabled);
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    .hzo-list-group--interactive .hzo-list-group__item { transition: none; }
}


/* --- load-more/load-more.css --- */
/* load-more — @tokens: --load-more-gap */
/* ─── Load more ───────────────────────────────────────────────────────
   A centered "Load more" trigger + a count line. The button is a composed
   `.hzo-btn` and reuses the existing `.hzo-btn.is-loading` spinner — this
   file styles ONLY the wrapper + count and never touches `.hzo-btn` (its
   focus ring + disabled state are carried by the button component). The
   actual result-append is app-level; the JS just toggles the loading state
   and emits the integration event. */
.hzo-load-more {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--load-more-gap, var(--space-sm));
    margin-top: var(--space-lg);
    text-align: center;
}

.hzo-load-more__count {
    font-size: var(--fs-75);
    color: var(--text-muted);
    margin: 0;
}


/* --- loading-overlay/loading-overlay.css --- */
/* loading-overlay — @tokens: --space-xs, --surface, --z-overlay, --z-modal, --transition-base, --fs-50, --text-body */
/* Async-lock veil that covers a position:relative host (a card/section) and centers a composed
   .hzo-spinner. The host MUST be position:relative — or use the --fixed modifier for a
   full-viewport lock. Styles ONLY its own classes; never the composed .hzo-spinner. */
.hzo-loading-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    background: color-mix(in srgb, var(--surface, #fff) 65%, transparent);
    z-index: var(--z-overlay);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--transition-base) ease, visibility var(--transition-base) ease;
}
.hzo-loading-overlay.is-active {
    opacity: 1;
    visibility: visible;
}
.hzo-loading-overlay__label {
    font-size: var(--fs-50);
    color: var(--text-body, currentColor);
}

/* Full-viewport variant — pins to the viewport and stacks at modal level. */
.hzo-loading-overlay--fixed {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
}

@media (prefers-reduced-motion: reduce) {
    .hzo-loading-overlay { transition: none; }
}


/* --- logo/logo.css --- */
/* ─── Logo ─────────────────────────────────────────────────────────── */
.hzo-site-logo {
    flex-shrink: 1;
    min-width: 0;
    font-family: var(--font-heading);
    font-size: 22px;
    font-weight: var(--fw-700, 700);
    color: var(--brand-text);
    text-decoration: none;
    display: inline-flex;
    align-items: center;
}
.hzo-site-logo img { height: 40px; width: auto; max-width: 260px; display: block; }
@media (min-width: 1250px) {
    .hzo-site-logo img { height: 46px; max-width: 300px; }
}


/* --- mega-menu/mega-menu.css --- */
/* Aggregator sheet — the component loader (includes/component-loader.php)
   enqueues ONLY <name>.css per folder, so the wide-panel variant sheet is
   @imported here to ride the same request. This is its ONLY front-end load
   path: on the front end hzostudio_enqueue_components() REPLACES the whole-file
   primitives.css enqueue, so primitives.css's own @import of mega-menu-wide.css
   loads in the block-editor context only. Without this @import the wide mega
   menu (featured card, column grid) renders UNSTYLED on the front end. (Same
   fix pattern as footer.css @importing footer-typography + footer-chrome.)
   @imports must precede all rules — keep this first. */
/* hzo-bundle: skipped @import ./mega-menu-wide.cssver1788182399 */

/* ─── Dropdown (mega menu) ─────────────────────────────────────────
   Centered under the parent nav item. Fades + slides in on hover/open.
   Uses opacity+visibility+transform so it animates cleanly both ways. */
.hzo-mega-menu {
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%) translateY(-8px);
    min-width: 220px;
    background: var(--nav-dropdown-bg, var(--surface, #fff));
    border-top: var(--border-2) solid var(--accent);
    border-radius: 0 0 var(--radius-control) var(--radius-control);
    box-shadow: var(--nav-dropdown-shadow, 0 12px 40px rgba(0,0,0,0.10), 0 2px 8px rgba(0,0,0,0.04));
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    overflow: hidden; /* clip link hover backgrounds to the rounded corners */
    transition: opacity 200ms ease, transform 200ms ease, visibility 200ms;
}
.hzo-nav-item:hover > .hzo-mega-menu,
.hzo-nav-item.is-open > .hzo-mega-menu {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateX(-50%) translateY(0);
}

.hzo-mega-menu__list { list-style: none; margin: 0; padding: 0; }
/* !important on color: fights a:visited. Matching :visited rule at bottom-of-file. */
.hzo-mega-menu__link {
    display: block;
    padding: 14px var(--space-lg);
    font-family: var(--font-body);
    font-size: var(--fs-100);
    font-weight: var(--fw-500, 500);
    color: var(--nav-link) !important;
    text-decoration: none;
    white-space: nowrap;
    transition: color 200ms ease, background-color 200ms ease;
}
.hzo-mega-menu__link:hover {
    color: var(--nav-link-hover) !important;
    background: var(--nav-dropdown-link-hover-bg, var(--bg-alt));
}
/* INSET focus ring (offset -2px) so the ring hugs inside the dropdown row rather
   than painting outside/clipping the panel edge (the global base.css ring is
   +2px outset). Restored from OURS during the unified merge. */
.hzo-mega-menu__link:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -2px; }

/* Reduced-motion: kill the dropdown fade/slide + link color transitions (belt-
   and-braces on top of mega-menu-wide.css's own .hzo-mega-cta kill — per the
   convention the aggregator ends with a transition-kill; mirrors footer.css). */
@media (prefers-reduced-motion: reduce) {
    .hzo-mega-menu,
    .hzo-mega-menu__link { transition: none; }
}


/* --- metric-tile/metric-tile.css --- */
/* metric-tile — @tokens: --metric-tile-gap, --metric-tile-padding, --metric-tile-radius

   A KPI tile: one big number + a label + an optional signed delta. A small
   elevated panel — sibling to `.hzo-card` but purpose-built for a single metric
   (dashboards, stat rows, pricing highlights). Primitive: CSS-only, not
   interactive (carries no control, so no focus/disabled idioms).

   Token-only: the panel surface + ink read semantic tokens so a dark archetype
   re-themes it from :root. The internal stack gap + padding have no dedicated
   semantic role, so they're component tokens with a literal-token fallback (the
   icon-button.css dial pattern). The value uses --font-number so the metric
   renders in the numeral face. */
.hzo-metric-tile {
    display: flex;
    flex-direction: column;
    gap: var(--metric-tile-gap, var(--space-sm));
    padding: var(--metric-tile-padding, var(--space-lg));
    background: var(--surface);
    border: var(--border-1) solid var(--border);
    border-radius: var(--metric-tile-radius, var(--radius-panel));
    box-shadow: var(--shadow-sm);   /* soft rest elevation — matches card default */
}

/* The headline metric — largest, heaviest, numeral face, tight leading. */
.hzo-metric-tile__value {
    font-family: var(--font-number);
    font-size: var(--fs-700);
    font-weight: var(--fw-700);
    line-height: var(--lh-none);
    color: var(--text-heading);
}

/* The caption paired with the value — small, muted, tracked-out caps. */
.hzo-metric-tile__label {
    font-size: var(--fs-100);
    line-height: var(--lh-snug);
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
    color: var(--text-muted);
}

/* Optional change indicator. Base is muted; direction modifiers recolor it with
   the matching state colour. Pair the sign/arrow in markup so meaning never
   rides on colour alone. */
.hzo-metric-tile__delta {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    font-size: var(--fs-100);
    font-weight: var(--fw-600);
    line-height: var(--lh-none);
    color: var(--text-muted);
}
.hzo-metric-tile__delta--positive { color: var(--success); }
.hzo-metric-tile__delta--negative { color: var(--error); }


/* --- mini-cart/mini-cart.css --- */
/* ─── Mini-cart ────────────────────────────────────────────────────────
   A .hzo-drawer COMPOSITION (it is a drawer — all open/close/trap/lock/Esc/
   focus-return machinery is reused). Only the line-item layout + totals are new
   here. Empty state reuses .hzo-empty; remove reuses the tag-removal focus-move
   pattern (mini-cart.js, Phase-2 widget system). Zero new tokens.
   Contract: component-patterns §46. */
.hzo-minicart__items { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-lg); }
.hzo-minicart__item {
    display: grid;
    grid-template-columns: 56px 1fr auto;
    gap: var(--space-md);
    align-items: start;
}
.hzo-minicart__thumb {
    width: 56px;
    height: 56px;
    border-radius: var(--radius-control);
    overflow: hidden;
    background: var(--bg-alt);
    flex-shrink: 0;
}
.hzo-minicart__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hzo-minicart__info { min-width: 0; display: flex; flex-direction: column; gap: var(--space-sm); }
.hzo-minicart__title { margin: 0; font-size: var(--fs-100); font-weight: 600; color: var(--text-heading); line-height: 1.3; }
/* The live cart fragment (includes/wc-ajax.php) wraps the title in <a>; without
   this the global `a` color/underline would restyle every product title. */
.hzo-minicart__title a { color: inherit; text-decoration: none; background-image: none; }
.hzo-minicart__title a:hover { text-decoration: underline; }
.hzo-minicart__qty {
    display: inline-flex;
    align-items: center;
    width: max-content;
    border: 1px solid var(--border);
    border-radius: var(--radius-control);
}
.hzo-minicart__step {
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    background: transparent;
    color: var(--text-body);
    font-size: var(--fs-200);
    line-height: var(--lh-none);
    cursor: pointer;
}
.hzo-minicart__step:hover { color: var(--brand-text); }
.hzo-minicart__step:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -2px; }
.hzo-minicart__step[disabled], .hzo-minicart__step[aria-disabled="true"] { opacity: var(--opacity-disabled); pointer-events: none; }
.hzo-minicart__count { min-width: 30px; text-align: center; font-size: var(--fs-100); font-variant-numeric: tabular-nums; }
.hzo-minicart__line { display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-sm); }
.hzo-minicart__linetotal { font-size: var(--fs-100); font-weight: 600; color: var(--text-heading); font-variant-numeric: tabular-nums; }
.hzo-minicart__remove {
    width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    border-radius: 50%;
    cursor: pointer;
    transition: background var(--transition-base) ease, color var(--transition-base) ease;
}
.hzo-minicart__remove:hover { background: var(--bg-alt); color: var(--text-heading); }
.hzo-minicart__remove:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 1px; }
.hzo-minicart__remove > svg { width: 14px; height: 14px; display: block; }
/* Footer overrides the drawer's row footer → stacked subtotal + full-width CTA. */
.hzo-minicart__footer { flex-direction: column; align-items: stretch; gap: var(--space-md); }
.hzo-minicart__subtotal { display: flex; justify-content: space-between; align-items: baseline; font-size: 15px; color: var(--text-body); }
.hzo-minicart__subtotal-value { font-size: var(--fs-300); font-weight: 700; color: var(--text-heading); font-family: var(--font-number); }
/* These elements set display: flex, which would beat the UA [hidden] rule — restore
   it so the JS empty/footer/list toggles actually hide (specificity 0,0,2,0 > 0,0,1,0). */
.hzo-minicart__items[hidden],
.hzo-minicart__empty[hidden],
.hzo-minicart__footer[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) { .hzo-minicart__remove { transition: none; } }


/* --- mobile-menu/mobile-menu.css --- */
/* ─── Mobile menu overlay (backdrop) ──────────────────────────── */
.hzo-mobile-menu {
    position: fixed;
    inset: 0;
    background: var(--nav-mobile-overlay-bg, rgba(0, 0, 0, 0.5));
    z-index: var(--z-overlay);
    opacity: 0;
    visibility: hidden;
    overflow: hidden; /* clip the translateX'd panel so it can't cause horizontal scroll */
    transition: opacity 300ms ease, visibility 300ms;
}
.hzo-mobile-menu.is-open { opacity: 1; visibility: visible; }

/* Mobile menu panel (slides from the right) */
.hzo-mobile-menu__panel {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    max-width: 360px;
    background: var(--nav-mobile-panel-bg, var(--surface, #fff));
    display: flex;
    flex-direction: column;
    transform: translateX(100%);
    transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}
.hzo-mobile-menu.is-open .hzo-mobile-menu__panel { transform: translateX(0); }

/* Sidebar header: logo + close */
.hzo-mobile-menu__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-md) var(--space-lg);
    border-bottom: var(--border-1) solid var(--nav-mobile-divider-color, var(--border));
    flex-shrink: 0;
    min-height: 60px;
}
.hzo-mobile-close {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: pointer;
    color: var(--text-body);
    flex-shrink: 0;
}
.hzo-mobile-close:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }

/* Sidebar nav */
.hzo-mobile-nav {
    flex: 1;
    padding: var(--space-xs) 0;
    overflow-y: auto;
}

/* Base mobile menu link — context wrappers (.hzo-mobile-simple, .hzo-mobile-panel)
   layer padding + background on top; this rule guarantees color + no underline
   even when the class is used standalone. */
.hzo-mobile-menu__link,
.hzo-mobile-menu__link:visited {
    color: var(--text-body);
    text-decoration: none;
    display: block;
    font-weight: var(--fw-600, 600);
}
.hzo-mobile-menu__link:hover,
.hzo-mobile-menu__link:visited:hover { color: var(--nav-link-hover); }
.hzo-mobile-menu__link:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -2px; }

/* Simple (non-accordion) row — a single link.
   !important on color: fights a:visited from per-client main.css. */
.hzo-mobile-simple { border-bottom: var(--border-1) solid var(--nav-mobile-divider-color, var(--border)); }
.hzo-mobile-simple > .hzo-mobile-menu__link {
    display: block;
    padding: var(--space-md) var(--space-lg);
    font-family: var(--font-body);
    font-weight: var(--fw-600, 600);
    font-size: 15px;
    color: var(--nav-mobile-link-color, var(--text-body)) !important;
    text-decoration: none;
    transition: color 200ms ease;
}
.hzo-mobile-simple > .hzo-mobile-menu__link:hover { color: var(--nav-link-hover) !important; }

/* Accordion row (nav item with sub-items) */
.hzo-mobile-accordion { border-bottom: var(--border-1) solid var(--nav-mobile-divider-color, var(--border)); }
.hzo-mobile-toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: var(--space-md) var(--space-lg);
    border: 0;
    background: transparent;
    font-family: var(--font-body);
    font-weight: var(--fw-600, 600);
    font-size: 15px;
    color: var(--text-body);
    cursor: pointer;
    text-align: left;
}
.hzo-mobile-accordion.is-expanded > .hzo-mobile-toggle { color: var(--nav-link-hover); }
.hzo-mobile-toggle:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -2px; }

.hzo-mobile-chevron {
    flex-shrink: 0;
    display: block;
    opacity: 0.4;
    margin-left: var(--space-smd);
    transition: transform 300ms ease, opacity 200ms ease;
}
.hzo-mobile-accordion.is-expanded > .hzo-mobile-toggle > .hzo-mobile-chevron {
    transform: rotate(180deg);
    opacity: 0.7;
}

/* Accordion collapsible panel */
.hzo-mobile-panel {
    max-height: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    transition: max-height 300ms ease;
}
/* 2400px (not 600) so an expanded nested sub-accordion — or a long flat
   service list — still fits without silent clipping. The overlay itself
   scrolls, so the cap only needs to exceed real content heights. */
.hzo-mobile-accordion.is-expanded > .hzo-mobile-panel { max-height: 2400px; }

/* Nested sub-accordion (3rd menu level — mirrors the desktop column groups).
   main.js already binds .hzo-mobile-subtoggle. */
.hzo-mobile-subaccordion { border-bottom: var(--border-1) solid var(--nav-mobile-divider-color, var(--border)); }
.hzo-mobile-subaccordion:last-child { border-bottom: 0; }
.hzo-mobile-subtoggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: var(--space-smd) var(--space-lg) var(--space-smd) var(--space-lg-plus);
    background: transparent;
    border: 0;
    font-family: var(--font-body);
    font-size: var(--fs-100);
    font-weight: var(--fw-600, 600);
    color: var(--text-body);
    cursor: pointer;
    text-align: left;
}
.hzo-mobile-subaccordion.is-expanded > .hzo-mobile-subtoggle { color: var(--nav-link-hover); }
.hzo-mobile-subtoggle:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -2px; }
.hzo-mobile-subaccordion.is-expanded > .hzo-mobile-subtoggle > .hzo-mobile-chevron {
    transform: rotate(180deg);
    opacity: 0.7;
}
.hzo-mobile-subaccordion.is-expanded > .hzo-mobile-panel { max-height: 600px; }
.hzo-mobile-subaccordion .hzo-mobile-panel > .hzo-mobile-menu__link { padding-left: 44px; }

/* Featured tile (child with menu-item class hzo-mega-featured) — thumbnail +
   label row; the mobile counterpart of the desktop photo card. */
.hzo-mobile-featured {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-smd) var(--space-lg) var(--space-smd) var(--space-lg-plus);
    font-size: var(--fs-100);
    font-weight: var(--fw-600, 600);
    color: var(--text-body);
    text-decoration: none;
    border-bottom: var(--border-1) solid var(--nav-mobile-divider-color, var(--border));
}
.hzo-mobile-featured img {
    width: 56px;
    height: 56px;
    object-fit: cover;
    border-radius: var(--radius-control);
    flex-shrink: 0;
}
.hzo-mobile-featured:hover { color: var(--nav-link-hover); }

/* !important on color: fights a:visited from per-client main.css. */
.hzo-mobile-panel > .hzo-mobile-menu__link {
    display: block;
    padding: var(--space-smd) var(--space-lg) var(--space-smd) var(--space-lg-plus);
    font-size: var(--fs-100);
    font-weight: var(--fw-500, 500);
    color: var(--nav-mobile-link-color, var(--text-body)) !important;
    text-decoration: none;
    border-bottom: var(--border-1) solid var(--nav-mobile-divider-color, var(--border));
    transition: color 200ms ease, background 200ms ease;
}
.hzo-mobile-panel > .hzo-mobile-menu__link:last-child { border-bottom: 0; }
.hzo-mobile-panel > .hzo-mobile-menu__link:hover {
    color: var(--nav-link-hover) !important;
    background: var(--nav-mobile-link-hover-bg, var(--bg-alt));
}

/* Sidebar footer (contact info) */
.hzo-mobile-menu__footer {
    padding: var(--space-lg);
    border-top: var(--border-1) solid var(--nav-mobile-divider-color, var(--border));
    background: var(--nav-mobile-footer-bg, var(--bg-alt));
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    gap: 14px;
}
/* !important on color: fights a:visited from per-client main.css. */
.hzo-mobile-menu__contact {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    font-size: var(--fs-100);
    font-weight: var(--fw-500, 500);
    color: var(--text-body) !important;
    text-decoration: none;
    transition: color 200ms ease;
}
.hzo-mobile-menu__contact:hover { color: var(--nav-link-hover) !important; }
.hzo-mobile-menu__contact svg { color: var(--accent); flex-shrink: 0; }

/* Prevent body scroll when menu is open */
body.hzo-menu-open { overflow: hidden; }

.hzo-utility-bar {
    background: var(--utility-bar-bg, var(--brand));
    color: var(--utility-bar-text, #fff);
    font-size: var(--fs-75);
    padding: var(--utility-bar-padding, 7px 0);
}
.hzo-utility-bar__inner {
    display: flex;
    align-items: center;
    justify-content: flex-end; /* desktop/tablet: right-aligned */
    gap: var(--space-smd);
}
.hzo-utility-bar__links {
    display: flex;
    align-items: center;
    gap: var(--space-smd);
}
.hzo-utility-bar__divider { color: var(--utility-bar-divider-color, rgba(255,255,255,0.3)); user-select: none; }

/* !important on color: fights a:visited from per-client main.css. */
.hzo-utility-bar a,
.hzo-utility-bar a:visited {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--utility-link) !important;
    text-decoration: none;
    font-weight: var(--fw-700, 700);
    white-space: nowrap;
    transition: color 200ms ease;
}
.hzo-utility-bar a:hover,
.hzo-utility-bar a:visited:hover { color: var(--utility-link-hover) !important; }
.hzo-utility-bar a svg { flex-shrink: 0; }

/* Mobile: center the links (no hours on mobile) */
@media (max-width: 767px) {
    .hzo-utility-bar__inner { justify-content: center; }
}

/* Light utility bar variant — pale background with dark text/links.
   CSS custom properties are scoped to this selector, so --utility-link
   and --utility-link-hover resolve to the lighter-context values for
   everything inside. Apply via: <div class="hzo-utility-bar hzo-utility-bar--light"> */
.hzo-utility-bar--light {
    background: var(--utility-bar-light-bg, var(--bg-alt));
    color: var(--utility-bar-light-text, var(--text-body));
    border-bottom: var(--border-1) solid var(--utility-bar-light-border-color, var(--border));
    --utility-link: var(--utility-light-link, var(--text-muted));
    --utility-link-hover: var(--utility-light-link-hover, var(--brand));
}

.hzo-mega-menu__close:hover { color: var(--brand); }

/* G38: "View all {Section}" — the parent landing link that leads an expanded
   accordion panel. The toggle only expands, so this restores reachability of the
   parent's own page on mobile. Brand-toned + semibold + a trailing arrow so it
   reads as the section overview, distinct from the child links below it. */
.hzo-mobile-panel > .hzo-mobile-viewall {
    color: var(--nav-link-active, var(--brand-text, var(--brand))) !important;
    font-weight: var(--fw-semibold, 600);
}
.hzo-mobile-panel > .hzo-mobile-viewall::after {
    content: "\2192";
    margin-left: 6px;
    display: inline-block;
}


/* --- modal/modal.css --- */
/* ─── Modal / dialog ──────────────────────────────────────────────────
   A backdrop (.hzo-modal) centers a panel (.hzo-modal__dialog, which carries
   role="dialog" + aria-modal="true"). Closed = opacity:0; visibility:hidden, so
   the panel's controls are non-focusable and click-inert until opened; the
   .is-open class reveals it (200ms fade + rise, reduced-motion-bounded). Focus
   trap, background inert, Esc/backdrop close, and focus-return live in modal.js
   (built on hzo.trapFocus in main.js). Modals are portaled to <body> on init
   so ancestor overflow/transform/z-index can't clip or mis-stack them. */
.hzo-modal {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-lg);
    background: rgba(0, 0, 0, 0.5);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--transition-base) ease, visibility var(--transition-base);
}
.hzo-modal.is-open { opacity: 1; visibility: visible; }

.hzo-modal__dialog {
    position: relative;
    display: flex;
    flex-direction: column;
    width: 100%;
    max-width: 520px;
    max-height: calc(100vh - var(--space-2xl));
    overflow-y: auto;
    background: var(--surface, #fff);
    border-radius: var(--radius-panel);
    box-shadow: var(--shadow-xl, var(--shadow-lg));
    transform: translateY(12px);
    transition: transform var(--transition-base) ease;
}
.hzo-modal.is-open .hzo-modal__dialog { transform: none; }
.hzo-modal__dialog--sm { max-width: 400px; }
.hzo-modal__dialog--lg { max-width: 760px; }

.hzo-modal__header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-md);
    padding: var(--space-lg) var(--space-lg) 0;
}
.hzo-modal__title {
    margin: 0;
    font-family: var(--font-heading);
    font-size: 22px;
    line-height: 1.3;
    color: var(--text-heading);
}
.hzo-modal__close {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin: -10px -10px 0 0;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    border-radius: var(--radius-control);
    cursor: pointer;
    transition: background var(--transition-base) ease, color var(--transition-base) ease;
}
.hzo-modal__close:hover { background: var(--bg-alt); color: var(--text-heading); }
.hzo-modal__close:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }

.hzo-modal__body {
    padding: var(--space-md) var(--space-lg) var(--space-lg);
    color: var(--text-body);
}
.hzo-modal__body > :first-child { margin-top: 0; }
.hzo-modal__body > :last-child { margin-bottom: 0; }

.hzo-modal__footer {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--space-sm);
    padding: 0 var(--space-lg) var(--space-lg);
}

/* Background scroll-lock (`body.hzo-overlay-open`) moved to the always-loaded
   base bucket — `components/_base/overlay-lock.css` — so it applies on pages
   that open a drawer/mini-cart without a modal present (audit gap G2). Do not
   re-add it here: this sheet is gated (only loads where a modal is used). */

/* Phone: dock as a bottom sheet with full-width stacked actions. */
@media (max-width: 480px) {
    .hzo-modal { padding: 0; align-items: flex-end; }
    .hzo-modal__dialog,
    .hzo-modal__dialog--sm,
    .hzo-modal__dialog--lg {
        max-width: none;
        max-height: 92vh;
        border-radius: var(--radius-panel) var(--radius-panel) 0 0;
    }
    .hzo-modal__footer .hzo-btn { flex: 1 1 auto; }
}

@media (prefers-reduced-motion: reduce) {
    .hzo-modal,
    .hzo-modal__dialog,
    .hzo-modal__close { transition: none; }
    .hzo-modal__dialog { transform: none; }
}


/* --- nap/nap.css --- */
/* ─── LocalBusiness NAP Block (.hzo-nap) ───────────────────────────────────
   The canonical NAP card. render="card" styles live here; render="footer"
   reuses the legacy .hzo-footer-nap rules (primitives.css / footer styling —
   untouched by this widget so the migration stays byte-identical), and
   render="schema-only" emits no DOM at all.

   Scoped to .hzo-nap*; every value pulls from a design token — no raw hex/px
   where a token exists. The optional open-now pill (.hzo-nap__status, driven by
   nap.js consuming hzo:hourschange) is additive over the server-rendered <dl>. */

.hzo-nap--card {
    display: block;
    font-style: normal; /* <address> defaults to italic */
    color: var(--text-body);
}

.hzo-nap__heading {
    margin: 0 0 var(--space-md);
    color: var(--text-heading, var(--text-body));
}

.hzo-nap__name {
    margin: 0 0 var(--space-sm);
    font-weight: var(--fw-bold, 700);
    color: var(--text-heading, var(--text-body));
}

.hzo-nap__row {
    display: flex;
    align-items: flex-start;
    gap: var(--space-sm);
    margin: 0 0 var(--space-sm);
    line-height: 1.5;
}

.hzo-nap__icon {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    margin-top: 0.15em; /* optical align to the first text line */
    color: var(--accent-text, var(--brand));
}

/* Nav-link role: color + underline shift on hover/focus; focus-visible rings
   (WCAG 2.4.13). Never color-alone — the underline carries state for
   forced-colors / color-blind users. */
.hzo-nap__link {
    color: var(--text-link, var(--brand));
    text-decoration: none;
    border-bottom: 1px solid transparent;
    transition: color var(--dur-1, 150ms) var(--ease-standard, ease),
                border-color var(--dur-1, 150ms) var(--ease-standard, ease);
}
.hzo-nap__link:hover {
    color: var(--text-link-hover, var(--accent-text, var(--brand)));
    border-bottom-color: currentColor;
}
.hzo-nap__link:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
    border-radius: var(--radius-xs, 2px);
}

/* ── Hours ── */
.hzo-nap__hours {
    margin-top: var(--space-md);
}
.hzo-nap__hours-heading {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    margin: 0 0 var(--space-xs);
    font-weight: var(--fw-medium, 500);
    color: var(--text-heading, var(--text-body));
}

/* Open-now pill — hidden until nap.js reveals it (progressive enhancement).
   Words ("Open now" / "Closed") carry the meaning; color is decorative. */
.hzo-nap__status {
    margin-left: auto;
    padding: 0.1em 0.6em;
    border-radius: var(--radius-full, 999px);
    font-size: 0.8em;
    font-weight: var(--fw-medium, 500);
    line-height: 1.6;
    transition: background-color var(--dur-1, 150ms) var(--ease-standard, ease),
                color var(--dur-1, 150ms) var(--ease-standard, ease);
}
.hzo-nap__status.is-open {
    background: var(--bg-success, rgba(22, 163, 74, 0.12));
    color: var(--text-success, #15803d);
}
.hzo-nap__status.is-closed {
    background: var(--bg-muted, rgba(0, 0, 0, 0.06));
    color: var(--text-muted, currentColor);
}

.hzo-nap .hzo-hours {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--space-2xs, 0.25rem) var(--space-lg);
    margin: 0;
}
.hzo-nap .hzo-hours dt {
    font-weight: var(--fw-medium, 500);
    color: var(--text-body);
}
.hzo-nap .hzo-hours dd {
    margin: 0;
    color: var(--text-muted, var(--text-body));
}
/* Today's row highlight — nap.js sets [aria-current] over the static <dl>. */
.hzo-nap .hzo-hours dt[aria-current="true"],
.hzo-nap .hzo-hours dt[aria-current="true"] + dd {
    color: var(--text-heading, var(--text-body));
    font-weight: var(--fw-bold, 700);
}

/* ── Service-area towns (native <details> disclosure) ── */
.hzo-nap__towns {
    margin-top: var(--space-md);
}
.hzo-nap__towns-summary {
    margin: 0 0 var(--space-xs);
    font-weight: var(--fw-medium, 500);
    cursor: pointer;
    color: var(--text-body);
}
.hzo-nap__towns-summary:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
    border-radius: var(--radius-xs, 2px);
}
.hzo-nap__towns-list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2xs, 0.25rem) var(--space-sm);
    margin: 0;
    padding: 0;
    list-style: none;
    color: var(--text-muted, var(--text-body));
}
.hzo-nap__towns-list > li:not(:last-child)::after {
    content: "\00B7"; /* middot separator */
    margin-left: var(--space-sm);
    color: var(--text-muted, currentColor);
}

/* Forced-colors: box-shadow rings are unreliable; ensure a system outline. */
@media (forced-colors: active) {
    .hzo-nap__link:focus-visible,
    .hzo-nap__towns-summary:focus-visible {
        outline: 2px solid CanvasText;
    }
    .hzo-nap__status {
        border: 1px solid CanvasText;
    }
}

/* @media print — the NAP is what home-service customers print. Keep it fully
   visible, expand the town list, and never let a reveal hook hide it. */
@media print {
    .hzo-nap__towns[open] > .hzo-nap__towns-list,
    .hzo-nap__towns > .hzo-nap__towns-list {
        display: flex !important;
    }
    .hzo-nap.hzo-reveal {
        opacity: 1 !important;
        transform: none !important;
    }
}

/* Reduced motion — kill the pill/link transitions. */
@media (prefers-reduced-motion: reduce) {
    .hzo-nap__link,
    .hzo-nap__status {
        transition: none;
    }
}


/* --- nav-link/nav-link.css --- */
/* ─── Navigation link ───────────────────────────────────────────────── */
.hzo-nav-link {
    font-family: var(--font-body);
    font-size: 15px;
    font-weight: var(--fw-500, 500);
    color: var(--nav-link);
    text-decoration: none;
    transition: color 0.15s;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.hzo-nav-link:hover {
    color: var(--brand-hover-on-dark); /* brighter brand — a clear jump, not one step */
}
.hzo-nav-link:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }

/* Simple underline default (Track-2 design pass) — a restrained hover underline
   grows in from the left. Token-driven color (var(--link-underline, currentColor)).
   A client can replace it with a richer treatment: its own .hzo-nav-link::after
   in custom.css loads last and fully overrides this default (byte-identical). */
.hzo-nav-link { position: relative; }
.hzo-nav-link::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -2px;
    height: 2px;
    background: var(--link-underline, currentColor);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform .2s ease;
    pointer-events: none;
}
.hzo-nav-link:hover::after,
.hzo-nav-link:focus-visible::after { transform: scaleX(1); }
@media (prefers-reduced-motion: reduce) {
    .hzo-nav-link::after { transition: none; }
}


/* --- newsletter/newsletter.css --- */
/* newsletter — @tokens: --newsletter-padding-y, --newsletter-input-min

   An email-capture band: a FULL-BLEED section (owns its own vertical padding +
   optional background) wrapping a `.hzo-container` with a title, supporting
   line, and an inline email input + submit button. Sibling to blocks/cta-band —
   same section shape, but the CTA here is a capture form rather than a link.

   NOTE: real submission wires to a Fluent Form (the .php handler / block swaps
   the inner control for the form embed). This is the STYLED capture band — the
   `<input>` is a visual/native control, NOT a live subscribe endpoint. The
   input still gets a proper label (aria-label in the template) + its own focus
   ring so the static band is accessible and demo-able on its own.

   Token-only: the band's vertical padding + the input's responsive min-width
   have no dedicated semantic role, so they're component tokens with a literal
   fallback (the icon-button.css dial pattern). Everything else reads a
   semantic/L1 token. */
.hzo-newsletter {
    padding-top: var(--newsletter-padding-y, var(--space-3xl));
    padding-bottom: var(--newsletter-padding-y, var(--space-3xl));
    background: var(--bg-alt);   /* light default surface; --dark flips it below */
}

/* Band heading — visual-h2 scale, heading ink, serif display face. */
.hzo-newsletter__title {
    margin: 0 0 var(--space-sm);
    font-family: var(--font-heading);
    font-size: var(--fs-600);
    line-height: var(--lh-tight);
    color: var(--text-heading);
}

/* Supporting line — muted subtitle, measure-capped for readability. */
.hzo-newsletter__text {
    margin: 0 0 var(--space-lg);
    max-width: var(--prose-max-width);
    font-size: var(--fs-300);
    line-height: var(--lh-normal);
    color: var(--text-muted);
}

/* Inline row: input + submit. Wraps → stacks on narrow viewports. */
.hzo-newsletter__form {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
}

/* Native text input styled as a proper control (mirrors form.css / search.css).
   Grows to fill the row but never below its min so it stacks cleanly on mobile. */
.hzo-newsletter__input {
    flex: 1 1 var(--newsletter-input-min, 260px);
    min-width: 0;
    padding: var(--space-smd) var(--space-md);
    border: var(--border-1) solid var(--border);
    border-radius: var(--radius-control);
    background: var(--surface);
    color: var(--text-body);
    font-family: var(--font-body);
    font-size: var(--fs-200);
    line-height: var(--lh-normal);
    transition: border-color var(--transition-base) ease, box-shadow var(--transition-base) ease;
}
.hzo-newsletter__input::placeholder { color: var(--text-muted); }

/* A11y — focus ring on the input (its own control). Reduced-motion handled
   globally by components/_base/a11y.css. */
.hzo-newsletter__input:focus-visible {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
    border-color: var(--color-focus);
}
.hzo-newsletter__input[disabled],
.hzo-newsletter__input[aria-disabled="true"] {
    opacity: var(--opacity-disabled);
    pointer-events: none;
}

/* Dark modifier — composes the .hzo-section--dark look: dark surface + on-dark
   ink. The submit button should ALSO get `.hzo-btn--on-dark` in this context
   (buttons.css owns that variant; this modifier only re-themes the band). */
.hzo-newsletter--dark {
    /* --surface-dark-bg lives in the (currently inert) L3 block, so fall back to
       the live --bg-dark-section — the exact idiom _base/surface-dark.css uses. */
    background: var(--surface-dark-bg, var(--bg-dark-section));
}
.hzo-newsletter--dark .hzo-newsletter__title {
    color: var(--heading-color-on-dark, #fff);
}
.hzo-newsletter--dark .hzo-newsletter__text {
    color: rgba(255, 255, 255, 0.85);
}

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


/* --- notification-dot/notification-dot.css --- */
/* notification-dot — @tokens: --radius-full, --error, --border-2, --card-bg, --space-2xs, --fs-50, --fw-600, --brand, --text-on-brand, --accent, --text-on-accent */
/* Generic count / activity indicator (cart count, unread) — distinct from the inventory-bound
   stock-status dot. Bare dot, or a numbered pill via --count; the --corner modifier overlays
   an icon on a position:relative host. Not animated → no reduced-motion block needed. */
.hzo-notification-dot {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--notification-dot-size, 8px);
    height: var(--notification-dot-size, 8px);
    border-radius: var(--radius-full);
    background: var(--error);
    box-shadow: 0 0 0 var(--border-2, 2px) var(--card-bg, #fff); /* ring so it reads over an icon */
}

/* Numbered pill variant. */
.hzo-notification-dot--count {
    min-width: var(--notification-dot-count-size, 18px);
    height: var(--notification-dot-count-size, 18px);
    padding: 0 var(--space-2xs);
    font-size: var(--fs-50);
    font-weight: var(--fw-600);
    line-height: 1;
    color: #fff; /* white on the coloured badge — the badge.css convention */
}

/* Colour modifiers — default stays --error. */
.hzo-notification-dot--brand  { background: var(--brand);  color: var(--text-on-brand); }
.hzo-notification-dot--accent { background: var(--accent); color: var(--text-on-accent); }

/* Overlay an icon — host MUST be position:relative. */
.hzo-notification-dot--corner {
    position: absolute;
    top: 0;
    right: 0;
    transform: translate(50%, -50%);
}


/* --- page-header/page-header.css --- */
/* page-header — @tokens: --page-header-max-width

   The lightweight title band at the top of an interior page, sitting directly
   below the site nav (a services / about / legal page…). NOT the marketing hero
   (`.hzo-hero` is the top-of-homepage composition with actions + media); this is
   the quieter "you are here" header: eyebrow + h1 + one supporting line, with an
   optional breadcrumb slot. Full-bleed band — the root OWNS the vertical padding
   and the optional tint, and wraps a `.hzo-container` for the horizontal gutter.
   Primitive: CSS-only, not interactive (carries no control, so no focus/disabled
   idioms — a breadcrumb dropped in the slot styles + focuses itself).

   Token-only: the band padding reads --space-3xl, the tint reads --bg-alt, and
   the type reads the shared L1 --fs-* ladder + semantic inks so a dark archetype
   re-themes it from :root. The subtitle's readability measure has no semantic
   token, so it's a component token with a literal fallback (the icon-button.css
   dial pattern). */
.hzo-page-header {
    padding-block: var(--space-3xl);
    background: transparent;
}

/* Tinted variant — pale alt surface to separate the header from white content. */
.hzo-page-header--alt {
    background: var(--bg-alt);
}

/* Small tracked label above the title. Mirrors the .hzo-eyebrow look (uppercase,
   tracked, small) but scoped to this block so it never borrows another class. */
.hzo-page-header__eyebrow {
    display: inline-block;
    font-size: var(--fs-100);
    font-weight: var(--fw-600);
    line-height: var(--lh-snug);
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
    color: var(--brand-text);
    margin: 0 0 var(--space-sm);
}

/* The page h1 — large heading ink. Sizes from the L1 ladder rather than a fixed
   px so it re-scales with the type system; tight leading for a display line. */
.hzo-page-header__title {
    font-family: var(--font-heading);
    font-size: var(--fs-700);
    font-weight: var(--fw-700);
    line-height: var(--lh-tight);
    letter-spacing: var(--tracking-tight);
    color: var(--text-heading);
    margin: 0;
}

/* One supporting line — muted, capped to a readable measure. */
.hzo-page-header__subtitle {
    max-width: var(--page-header-max-width, 60ch);
    font-size: var(--fs-300);
    line-height: var(--lh-normal);
    color: var(--text-muted);
    margin: var(--space-md) 0 0;
}

/* Breadcrumb slot — spacing only. The breadcrumb component styles + focuses
   itself; this just seats it above the eyebrow with rhythm. */
.hzo-page-header__breadcrumb {
    margin: 0 0 var(--space-md);
}

/* Center-aligned variant — also centers the capped subtitle measure. */
.hzo-page-header--center {
    text-align: center;
}
.hzo-page-header--center .hzo-page-header__subtitle {
    margin-inline: auto;
}


/* --- pagination/pagination.css --- */
/* ─── Pagination ──────────────────────────────────────────────────────
   Archive / search page nav. 44px tap targets; current page brand-filled;
   disabled prev/next muted + non-interactive. !important on color fights
   per-client a:visited (matching :visited rule below). */
.hzo-pagination { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--space-sm); list-style: none; margin: var(--space-xl) 0 0; padding: 0; }
.hzo-pagination__link {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 44px; min-height: 44px; padding: 0 var(--space-smd);
    font-family: var(--font-body); font-weight: var(--fw-600, 600); font-size: 15px;
    color: var(--brand-text) !important; text-decoration: none;
    border: var(--border-1) solid var(--border); border-radius: var(--radius-control);
    transition: background var(--transition-base), border-color var(--transition-base), color var(--transition-base);
}
.hzo-pagination__link:hover { background: var(--brand-pale); border-color: var(--brand); color: var(--brand-text) !important; }
.hzo-pagination__link:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
.hzo-pagination__link.is-current { background: var(--brand) !important; border-color: var(--brand); color: var(--text-on-brand) !important; pointer-events: none; }
.hzo-pagination__link.is-disabled,
.hzo-pagination__link[aria-disabled="true"] { color: var(--text-muted) !important; pointer-events: none; opacity: 0.6; }
.hzo-pagination__link:visited { color: var(--brand-text) !important; }

/* ─── G20: WP query-loop + WooCommerce archive pagination ─────────────────────
   WP emits a.page-numbers / span.page-numbers.current inside
   .wp-block-query-pagination (block) and .woocommerce-pagination (WC). Give them
   the SAME 44px-tap, brand-current pager look instead of raw theme defaults. */
.wp-block-query-pagination,
.woocommerce-pagination .page-numbers:not(li) {
    display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
    gap: var(--space-sm); margin: var(--space-xl) 0 0; list-style: none; padding: 0;
}
.wp-block-query-pagination-numbers { display: inline-flex; flex-wrap: wrap; gap: var(--space-sm); }
.page-numbers,
.wp-block-query-pagination-previous,
.wp-block-query-pagination-next {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 44px; min-height: 44px; padding: 0 12px;
    font-family: var(--font-body); font-weight: var(--fw-semibold, 600); font-size: 15px;
    color: var(--brand-text) !important; text-decoration: none;
    border: 1px solid var(--border); border-radius: var(--radius-input);
    transition: background var(--transition-base), border-color var(--transition-base), color var(--transition-base);
}
a.page-numbers:hover,
.wp-block-query-pagination-previous:hover,
.wp-block-query-pagination-next:hover { background: var(--brand-pale); border-color: var(--brand); color: var(--brand-text) !important; }
a.page-numbers:focus-visible,
.wp-block-query-pagination-previous:focus-visible,
.wp-block-query-pagination-next:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.page-numbers.current { background: var(--brand) !important; border-color: var(--brand); color: var(--text-on-brand) !important; }
.page-numbers.dots { border-color: transparent; min-width: auto; padding: 0 4px; pointer-events: none; }
.page-numbers:visited { color: var(--brand-text) !important; }


/* --- price/price.css --- */
/* price — @tokens: (none — fully semantic/L1)

   A WooCommerce product-price display atom for custom card / product layouts.
   Non-interactive; tabular figures so aligned prices in a list stay in column.
   Token-only: every colour / size / weight reads a semantic or L1 token. */
.hzo-price {
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-2xs);
    color: var(--text-heading);
    font-size: var(--fs-200);
    font-weight: var(--fw-600);
    font-variant-numeric: tabular-nums;
}

/* The effective / current price — inherits the block's size, weight and colour. */
.hzo-price__amount {
    color: inherit;
}

/* The struck original price when on sale — muted + line-through, lighter weight. */
.hzo-price__regular {
    color: var(--text-muted);
    font-size: var(--fs-75);
    font-weight: var(--fw-500);
    text-decoration: line-through;
}

/* The emphasised sale price — accent ink, heavier weight. */
.hzo-price__sale {
    color: var(--accent-text);
    font-weight: var(--fw-700);
}

/* Small muted affixes ("From", "/mo"). */
.hzo-price__prefix,
.hzo-price__suffix {
    color: var(--text-muted);
    font-size: var(--fs-75);
    font-weight: var(--fw-500);
}

/* Product-page hero price. */
.hzo-price--lg {
    font-size: var(--fs-400);
}


/* --- product-card/product-card.css --- */
/* product-card — @tokens: --card-bg, --card-border, --card-radius, --card-shadow, --card-hover-shadow */
/* ─── Product card ───────────────────────────────────────────────────────────
   Self-contained card for CUSTOM commerce grids (the [wc_cards] path). It
   DELIBERATELY reads the same shared card-surface tokens the native WC product
   loop uses (assets/css/woocommerce.css → .woocommerce ul.products li.product,
   and components/card/card.css → .hzo-card): --card-bg, --card-border,
   --card-radius, --card-shadow, --card-hover-shadow, with the exact inline
   fallbacks those call-sites use. This is intentional cross-surface consistency
   — a custom card must read byte-identically to a native loop card and respond
   to the same archetype dials — NOT token-borrowing drift.
   Composes foreign primitives (.hzo-price, .hzo-stock-status, .hzo-badge--sale,
   .hzo-wishlist, .hzo-btn) inside its slots but styles ONLY its own
   .hzo-product-card__* structure — never those foreign classes. */
.hzo-product-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    padding: var(--space-md);
    background: var(--card-bg, #fff);
    border: var(--border-1) solid var(--card-border, var(--border));
    border-radius: var(--card-radius, var(--radius-card));
    box-shadow: var(--card-shadow, var(--shadow-sm));
    position: relative;
    transition: box-shadow var(--transition-base), transform var(--transition-base);
}
.hzo-product-card:hover {
    box-shadow: var(--card-hover-shadow, var(--shadow-lg));
    transform: translateY(-4px);
}

/* Image slot */
.hzo-product-card__media {
    position: relative;
    border-radius: var(--radius-control);
    overflow: hidden;
    margin: 0;
}
.hzo-product-card__media img {
    display: block;
    width: 100%;
    height: auto;
    object-fit: cover;
}

/* Sale-badge slot (holds .hzo-badge--sale) */
.hzo-product-card__badge {
    position: absolute;
    top: var(--space-sm);
    left: var(--space-sm);
}

/* Wishlist slot (holds .hzo-wishlist) */
.hzo-product-card__wishlist {
    position: absolute;
    top: var(--space-sm);
    right: var(--space-sm);
}

.hzo-product-card__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    flex: 1 1 auto;
}

.hzo-product-card__title {
    font-family: var(--font-heading);
    font-size: var(--fs-300);
    font-weight: var(--fw-700);
    color: var(--text-heading);
    line-height: var(--lh-tight);
    margin: 0;
}
.hzo-product-card__title a {
    color: inherit;
    text-decoration: none;
    background-image: none; /* a title link is a heading — never let a client content-link highlight ride onto it */
}
.hzo-product-card__title a:hover {
    text-decoration: underline;
}

/* Price + stock row; margin-top:auto pins it to the bottom above actions.
   Holds .hzo-price + .hzo-stock-status. */
.hzo-product-card__meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    margin-top: auto;
}

/* Add-to-cart row (holds the .hzo-btn) */
.hzo-product-card__actions {
    display: flex;
    gap: var(--space-sm);
    margin-top: var(--space-sm);
}

/* Compact density */
.hzo-product-card--compact {
    padding: var(--space-sm);
    gap: var(--space-xs);
}

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


/* --- product-grid/product-grid.css --- */
/* ═════════════════════════════════════════════════════════════════════════
   WOOCOMMERCE PRODUCT GRID (W-130) — styles

   Scoped to .hzo-product-grid*. Tokens only (no bare hex / px / z-index where a
   token exists). The container marker [data-hzo-pg] already carries an
   aria-busy dim rule in woocommerce.css (loaded on WC pages); these rules add
   the ENGINE card (.hzo-product-card), the responsive grid geometry driven by
   the --hzo-pg-cols* custom properties the PHP sets, the Load-more control, the
   stock pill, and the skeleton loading state.

   Motion: durations/easings are --dur-*/--ease-* tokens only. Reduced-motion
   (PRESERVE-5) — ambient shimmer STOPS, the status spinner SLOWS (never freezes),
   content stays visible. Forced-colors gets an explicit branch.
   ═════════════════════════════════════════════════════════════════════════ */

.hzo-product-grid {
    --hzo-pg-cols: 4;
    --hzo-pg-cols-md: 3;
    --hzo-pg-cols-sm: 2;
    --hzo-pg-gap: var(--space-lg);
    display: block;
}

.hzo-product-grid .hzo-eyebrow,
.hzo-product-grid .hzo-visual-h2 {
    text-align: center;
}

/* ── Grid geometry (container-aware column counts) ─────────────────────────── */
.hzo-product-grid__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--hzo-pg-gap);
    grid-template-columns: repeat(var(--hzo-pg-cols), minmax(0, 1fr));
    margin-top: var(--space-xl);
}
.hzo-product-grid--empty .hzo-empty { margin-top: var(--space-xl); }

/* Heading absent → no top gap eats the section rhythm. */
.hzo-product-grid__list:first-child { margin-top: 0; }

@media (max-width: 1024px) {
    .hzo-product-grid__list { grid-template-columns: repeat(var(--hzo-pg-cols-md), minmax(0, 1fr)); }
}
@media (max-width: 640px) {
    .hzo-product-grid__list {
        grid-template-columns: repeat(var(--hzo-pg-cols-sm), minmax(0, 1fr));
        gap: var(--space-md);
    }
}

/* ── The card ──────────────────────────────────────────────────────────────── */
.hzo-product-card {
    /* Card-integrated add-to-cart: a moderate radius reads more considered than a
       full pill inside a card (per-client themes still override --btn-radius). */
    --btn-radius: var(--radius-1);
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--space-smd);
    background: var(--card-bg, var(--surface));
    border: var(--border-1) solid var(--card-border, var(--border));
    border-radius: var(--card-radius, var(--radius-2));
    padding: var(--space-smd);
    box-shadow: var(--card-shadow, var(--shadow-sm));
    transition: box-shadow var(--dur-3) var(--ease-out),
                transform var(--dur-3) var(--ease-out),
                border-color var(--dur-3) var(--ease-out);
    text-align: left;
}
/* Decorative lift only — the card is NOT a single click target (it holds a real
   title link AND an add-to-cart control), so no false whole-card affordance
   (PRESERVE-4 / G50). */
.hzo-product-card:hover {
    box-shadow: var(--card-hover-shadow, var(--shadow-lg));
    transform: translateY(-4px);
    border-color: color-mix(in srgb, var(--brand) 22%, var(--border));
}

.hzo-product-card__media {
    display: block;
    position: relative;
    border-radius: var(--radius-1);
    overflow: hidden;
    aspect-ratio: 1 / 1;          /* zero-CLS media box (G51) */
    background: var(--surface-2);
}
.hzo-product-card__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    margin: 0;
    transition: transform var(--dur-4) var(--ease-out);
}
/* Subtle image zoom on card hover — a touch of life, not a gimmick. */
.hzo-product-card:hover .hzo-product-card__media img { transform: scale(1.05); }

/* Sale flash — a crisp uppercase brand pill at the media corner. */
.hzo-product-card__flash {
    position: absolute;
    top: var(--space-sm);
    left: var(--space-sm);
    z-index: 1;
    padding: 5px 11px;
    background: var(--brand);
    color: var(--text-on-brand, #fff);
    font-size: 0.68rem;
    font-weight: var(--fw-bold, 700);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    line-height: 1.3;
    border-radius: var(--radius-pill);
    box-shadow: var(--shadow-sm);
}

.hzo-product-card__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    flex: 1 1 auto;
}

.hzo-product-card__title {
    font-family: var(--font-heading);
    font-size: 1.05rem;
    font-weight: var(--fw-bold, 700);
    line-height: 1.35;
    color: var(--text-heading);
    margin: 0;
    padding: 0;
    display: -webkit-box;
    -webkit-line-clamp: 2;          /* even card heights; long names never blow out the row */
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.hzo-product-card__title a {
    color: inherit;
    text-decoration: none;
    background-image: none; /* a title link is a heading — never let a client content-link highlight ride onto it */
}
.hzo-product-card__title a:hover { color: var(--brand-text, var(--brand)); }

.hzo-product-card__rating { font-size: 0.9rem; margin-top: calc(-1 * var(--space-2xs)); }

/* Price hierarchy: the current price is the loudest thing on the card; a sale
   price takes the brand color, the original strikes through smaller + muted. */
.hzo-product-card__price {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--space-xs);
    color: var(--text-heading);
    font-size: 1.2rem;
    font-weight: var(--fw-extrabold, 800);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
}
.hzo-product-card__price del {
    color: var(--text-muted);
    font-size: 0.9rem;
    font-weight: var(--fw-medium, 500);
    opacity: 1;
    margin: 0;
}
.hzo-product-card__price ins {
    color: var(--brand-text, var(--brand));
    text-decoration: none;
    font-weight: var(--fw-extrabold, 800);
}

/* ── Stock pill ────────────────────────────────────────────────────────────── */
.hzo-product-card__stock {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    font-size: 0.75rem;
    font-weight: var(--fw-semibold, 600);
    line-height: 1.4;
    border-radius: var(--radius-pill);
}
.hzo-product-card__stock::before {
    content: "";
    width: 7px;
    height: 7px;
    border-radius: var(--radius-full);
    background: currentColor;
}
.hzo-product-card__stock--in   { color: var(--success-ink); background: var(--success-bg); }
.hzo-product-card__stock--low  { color: var(--warning-ink); background: var(--warning-bg); }
.hzo-product-card__stock--out  { color: var(--error-ink);   background: var(--error-bg); }

/* ── Add-to-cart control (engine .hzo-btn inherits its state visuals from
   buttons.css: .is-loading spinner / .is-added check — do NOT redefine them). ── */
.hzo-product-card__add {
    margin-top: auto;              /* pin to card bottom regardless of body height */
    align-self: stretch;
    justify-content: center;
    min-height: 44px;              /* substantial, comfortable tap (§11.2) */
    font-weight: var(--fw-bold, 700);
    letter-spacing: 0.01em;
}

/* ── Load-more control ─────────────────────────────────────────────────────── */
.hzo-product-grid__foot {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-sm);
    margin-top: var(--space-xl);
}
.hzo-product-grid__more {
    position: relative;
    min-height: 44px;
    min-width: 220px;
    justify-content: center;
}
.hzo-product-grid__more-spinner {
    display: none;
    width: 1em;
    height: 1em;
    margin-left: var(--space-sm);
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: var(--radius-full);
    animation: hzo-pg-spin var(--dur-4) linear infinite;
}
.hzo-product-grid__more.is-loading .hzo-product-grid__more-spinner { display: inline-block; }
.hzo-product-grid__more[aria-disabled="true"] { pointer-events: none; opacity: 0.7; }

.hzo-product-grid__error {
    margin: 0;
    color: var(--error-ink);
    font-size: 0.9rem;
    font-weight: var(--fw-semibold, 600);
}

@keyframes hzo-pg-spin { to { transform: rotate(360deg); } }

/* ── Skeleton loading state (G47 — a visible cue that works on FIRST load, not
   an opacity dim). Shimmer is AMBIENT motion → stops under reduced-motion. ──── */
.hzo-product-card--skeleton {
    pointer-events: none;
    gap: var(--space-sm);
}
.hzo-product-card--skeleton:hover { transform: none; box-shadow: var(--card-shadow, var(--shadow-sm)); }
.hzo-skel {
    display: block;
    border-radius: var(--radius-input);
    background: linear-gradient(
        100deg,
        var(--surface-2) 30%,
        var(--border) 50%,
        var(--surface-2) 70%
    );
    background-size: 200% 100%;
    animation: hzo-pg-shimmer var(--dur-4) var(--ease-standard) infinite;
}
.hzo-skel.hzo-product-card__media { aspect-ratio: 1 / 1; }
.hzo-skel--line { height: 0.9rem; border-radius: var(--radius-pill); }
.hzo-skel--short { width: 55%; }

@keyframes hzo-pg-shimmer {
    from { background-position: 200% 0; }
    to   { background-position: -200% 0; }
}

/* ── Reduced motion (PRESERVE-5): ambient shimmer + hover lift stop; the status
   spinner SLOWS but keeps moving; content stays fully visible. ─────────────── */
@media (prefers-reduced-motion: reduce) {
    .hzo-product-card,
    .hzo-product-card:hover { transition: none; transform: none; }
    .hzo-skel { animation: none; background: var(--surface-2); }
    .hzo-product-grid__more-spinner { animation-duration: 1.5s; }
}

/* ── Forced-colors / High Contrast (§12.2): the skeleton conveys nothing and the
   spinner border must remain visible; stock pills fall back to system colors. ─ */
@media (forced-colors: active) {
    .hzo-skel { background: Canvas; border: var(--border-1) solid CanvasText; animation: none; }
    .hzo-product-grid__more-spinner { border-color: CanvasText; border-top-color: transparent; }
    .hzo-product-card__stock { border: var(--border-1) solid CanvasText; }
    .hzo-product-card__flash { border: var(--border-1) solid CanvasText; }
}

/* ── Print (§12.1): drop the interactive chrome; keep the catalog readable. ─── */
@media print {
    .hzo-product-grid__foot,
    .hzo-product-card__add,
    .hzo-product-card--skeleton { display: none !important; }
    .hzo-product-card { box-shadow: none; break-inside: avoid; }
}

/* ─── Static block-pattern grids (merge graft from Track-1/2) ────────────────
   blocks/product-card.html renders cards DIRECTLY inside .hzo-product-grid with
   a --cols-N modifier and no __list child, so for that markup the grid must
   live on the element itself. The [hzo_product_grid] widget above never emits a
   --cols-N class (it drives columns via inline --hzo-pg-cols on __list), so
   these rules cannot collide with it — the two layouts stay independent.
   Base is auto-fill so mobile stays fluid; counts lock in at >=768px. */
.hzo-product-grid--cols-2,
.hzo-product-grid--cols-3,
.hzo-product-grid--cols-4 {
    display: grid;
    gap: var(--product-grid-gap, var(--space-lg));
    grid-template-columns: repeat(auto-fill, minmax(var(--product-grid-min, 240px), 1fr));
}

@media (min-width: 768px) {
    .hzo-product-grid--cols-2 { grid-template-columns: repeat(2, 1fr); }
    .hzo-product-grid--cols-3 { grid-template-columns: repeat(3, 1fr); }
    .hzo-product-grid--cols-4 { grid-template-columns: repeat(4, 1fr); }
}


/* --- progress-bar/progress-bar.css --- */
/* progress-bar — @tokens: --progress-bar-height, --progress-bar-height-sm, --progress-bar-height-lg */
/* ─── Progress bar ────────────────────────────────────────────────────
   Linear progress indicator, determinate + indeterminate. role="progressbar"
   + aria-valuenow/min/max live in markup (not focusable). The __fill width
   is set inline for the determinate case; the --indeterminate modifier
   pins width and slides the fill via a transform-only keyframe. */
.hzo-progress-bar {
    display: block;
    width: 100%;
    height: var(--progress-bar-height, 8px);
    background: var(--bg-alt);
    border-radius: var(--radius-pill);
    overflow: hidden;
}
.hzo-progress-bar__fill {
    height: 100%;
    background: var(--brand);
    border-radius: var(--radius-pill);
    transition: width var(--transition-base) ease;
}

/* ── Colour modifiers (recolour the fill) ── */
.hzo-progress-bar--accent  .hzo-progress-bar__fill { background: var(--accent); }
.hzo-progress-bar--success .hzo-progress-bar__fill { background: var(--success); }
.hzo-progress-bar--warning .hzo-progress-bar__fill { background: var(--warning); }
.hzo-progress-bar--error   .hzo-progress-bar__fill { background: var(--error); }
.hzo-progress-bar--info    .hzo-progress-bar__fill { background: var(--info); }

/* ── Size modifiers ── */
.hzo-progress-bar--sm { height: var(--progress-bar-height-sm, 4px); }
.hzo-progress-bar--lg { height: var(--progress-bar-height-lg, 12px); }

/* ── Indeterminate ── */
.hzo-progress-bar--indeterminate .hzo-progress-bar__fill {
    width: 40%;
    animation: hzo-progress-indeterminate 1.4s ease-in-out infinite;
}
@keyframes hzo-progress-indeterminate {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(250%); }
}

/* Reduced motion — disable the slide (static bar) + the width transition. */
@media (prefers-reduced-motion: reduce) {
    .hzo-progress-bar--indeterminate .hzo-progress-bar__fill {
        animation: none;
        transform: none;
    }
    .hzo-progress-bar__fill { transition: none; }
}


/* --- pullquote/pullquote.css --- */
/* pullquote — @tokens: --pullquote-mark-size, --pullquote-mark-offset, --pullquote-measure

   A large editorial quote block pulled out of running copy — a designer drops it
   inside a `.hzo-container` (it is NOT a full-bleed band; the section supplies the
   padding + gutter). Root is a semantic <blockquote>. Distinct from
   `.hzo-testimonial` (a card with an avatar/rating for social proof); a pullquote
   is bare typographic emphasis. Primitive: CSS-only, not interactive (no
   focus/disabled idioms).

   Token-only: the quote reads --font-heading + the big end of the L1 --fs-*
   ladder + heading ink; the decorative mark + accent bar read --border / --brand
   so a per-client recolor + a dark archetype re-theme from :root. Two values have
   no semantic role — the oversized quote-mark glyph and the centered readability
   measure — so they're component tokens with literal fallbacks (the icon-button.css
   dial pattern). The glyph's optical baseline nudge is a component token too. */
.hzo-pullquote {
    margin: 0;
    color: var(--text-heading);
}

/* Optional oversized opening quote mark — low-opacity chrome in the border ink
   so it reads as decoration, not content. Set the glyph in markup (an aria-hidden
   ::before glyph carries no meaning a screen reader needs). */
.hzo-pullquote__quote::before {
    content: "\201C";               /* “ — decorative opening double-quote */
    font-family: var(--font-heading);
    font-size: var(--pullquote-mark-size, var(--fs-900));
    line-height: var(--lh-none);
    color: var(--border);
    opacity: var(--opacity-muted);
    margin-right: var(--space-2xs);
    vertical-align: var(--pullquote-mark-offset, -0.15em);
}

/* The quote itself — large, serif display face, relaxed leading for readability
   at size. Sizes from the L1 ladder rather than a fixed px. */
.hzo-pullquote__quote {
    font-family: var(--font-heading);
    font-size: var(--fs-500);
    font-weight: var(--fw-500);
    line-height: var(--lh-relaxed);
    color: var(--text-heading);
    margin: 0;
}

/* Attribution — muted, small, upright (not italic). Prefix the em-dash in markup. */
.hzo-pullquote__cite {
    display: block;
    font-family: var(--font-body);
    font-size: var(--fs-200);
    font-style: normal;
    line-height: var(--lh-normal);
    color: var(--text-muted);
    margin: var(--space-md) 0 0;
}

/* Centered variant — also caps + centers the quote to a readable measure. */
.hzo-pullquote--center {
    text-align: center;
}
.hzo-pullquote--center .hzo-pullquote__quote {
    max-width: var(--pullquote-measure, 32ch);
    margin-inline: auto;
}

/* Brand variant — a quieter inline treatment: a brand accent bar on the left
   (the callout.css idiom) instead of the oversized mark. */
.hzo-pullquote--brand {
    padding-left: var(--space-lg);
    border-left: var(--border-3) solid var(--brand);
}
.hzo-pullquote--brand .hzo-pullquote__quote::before {
    content: none;
}


/* --- quantity-stepper/quantity-stepper.css --- */
/* ─── Quantity stepper — fused capsule ─────────────────────────────────
   minus / numeric input / plus in one pill. quantity-stepper.js
   ([data-hzo-qty]) clamps to data-min / data-max (or the input's min/max),
   disables the button at each bound, and emits a bubbling `hzo:qty-change`
   CustomEvent — it never POSTs (parity with the add-to-cart surface).
   Expected markup (emitted by the [hzo_quantity_stepper] shortcode):
     <div class="hzo-qty" data-hzo-qty data-min="1" data-max="9">
       <button type="button" class="hzo-qty__btn hzo-qty__btn--minus" data-hzo-qty-minus aria-label="Decrease quantity">…</button>
       <input class="hzo-qty__input" type="text" inputmode="numeric" value="1" aria-label="Quantity">
       <button type="button" class="hzo-qty__btn hzo-qty__btn--plus" data-hzo-qty-plus aria-label="Increase quantity">…</button>
     </div>
   Commerce control — no standalone block. */
.hzo-qty {
    display: inline-flex;
    align-items: center;
    border: var(--border-1) solid var(--border);
    border-radius: var(--radius-pill);
    overflow: hidden;
    background: var(--card-bg, #fff);
}
.hzo-qty__btn {
    appearance: none;
    border: 0;
    background: transparent;
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    color: var(--text-heading);
    line-height: var(--lh-none);
    transition: background var(--transition-base), color var(--transition-base);
}
.hzo-qty__btn svg { width: 18px; height: 18px; display: block; }
.hzo-qty__btn:hover:not(:disabled) { background: var(--bg-alt); }
.hzo-qty__btn:focus-visible { outline: 3px solid var(--color-focus); outline-offset: -3px; }
.hzo-qty__btn:disabled { opacity: 0.4; cursor: not-allowed; }
.hzo-qty__input {
    width: var(--qty-input-width, 3ch);
    text-align: center;
    border: 0;
    background: transparent;
    font-family: var(--font-body);
    font-weight: var(--fw-600);
    color: var(--text-heading);
    -moz-appearance: textfield;
}
.hzo-qty__input::-webkit-outer-spin-button,
.hzo-qty__input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.hzo-qty__input:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 1px; border-radius: var(--radius-input); }
@media (prefers-reduced-motion: reduce) {
    .hzo-qty__btn { transition: none; }
}


/* --- quick-view/quick-view.css --- */
/* ═══════════════════════════════════════════════════════════════════════════
   PRODUCT QUICK-VIEW MODAL (W-132) — styles

   Scoped to .hzo-quick-view*. The OVERLAY chrome (backdrop, panel, open/close
   motion, scroll-lock, z-index) is inherited from components/modal (.hzo-modal /
   .hzo-modal__dialog) — this sheet only styles the trigger affordance and the
   quick-view BODY (gallery, details, skeleton, error). Tokens only: no bare hex,
   no bare z-index, no bare ms/ease. z-index comes entirely from the modal layer
   (--z-modal). Reduced-motion stops the skeleton shimmer (PRESERVE-5: the
   aria-busy state + hzo.announce carry "loading", so no motion is required).
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Trigger affordance (composes on .hzo-btn) ─────────────────────────────── */
.hzo-quick-view {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
}
.hzo-quick-view__icon {
    width: 1.15em;
    height: 1.15em;
    flex-shrink: 0;
}
/* Compact card-corner variant: icon only, but keeps a real accessible name. */
.hzo-quick-view--icon-only {
    gap: 0;
    min-width: 44px;
    min-height: 44px;
    padding-left: var(--space-sm);
    padding-right: var(--space-sm);
}
.hzo-quick-view__sr-only {
    position: absolute !important;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ── Quick-view dialog sizing (wider than the base modal for two columns) ──── */
.hzo-quick-view__dialog {
    width: 100%;
    max-width: 860px;
}
.hzo-quick-view__header {
    /* No title row — the close button sits top-right over the body. Keep it in
       the flow but let the grid start at the top. */
    padding-bottom: 0;
    justify-content: flex-end;
    min-height: 0;
}
.hzo-quick-view__body {
    padding-top: var(--space-sm);
}

/* ── Layout grid ───────────────────────────────────────────────────────────── */
.hzo-quick-view__grid {
    display: grid;
    grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
    gap: var(--space-lg);
    align-items: start;
}

/* ── Gallery ───────────────────────────────────────────────────────────────── */
.hzo-quick-view__stage {
    border-radius: var(--radius-card);
    overflow: hidden;
    background: var(--bg-alt);
}
.hzo-quick-view__main-wrap { display: block; }
.hzo-quick-view__main,
.hzo-quick-view__main-wrap img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
    object-fit: cover;
}
.hzo-quick-view__thumbs {
    list-style: none;
    margin: var(--space-sm) 0 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
}
.hzo-quick-view__thumb-item { margin: 0; }
.hzo-quick-view__thumb {
    display: block;
    width: 56px;
    height: 56px;
    padding: 0;
    border: 2px solid transparent;
    border-radius: var(--radius-input);
    background: var(--bg-alt);
    cursor: pointer;
    overflow: hidden;
    transition: border-color var(--dur-2) var(--ease-standard);
}
.hzo-quick-view__thumb img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.hzo-quick-view__thumb:hover { border-color: var(--text-muted); }
.hzo-quick-view__thumb.is-active { border-color: var(--brand); }
.hzo-quick-view__thumb:focus-visible {
    outline: 3px solid var(--focus-ring);
    outline-offset: 2px;
}

/* ── Details column ────────────────────────────────────────────────────────── */
.hzo-quick-view__details {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}
.hzo-quick-view__title {
    margin: 0;
    font-family: var(--font-heading);
    font-size: 24px;
    line-height: 1.25;
    color: var(--text-heading);
}
.hzo-quick-view__title:focus-visible {
    outline: 3px solid var(--focus-ring);
    outline-offset: 3px;
    border-radius: var(--radius-input);
}
.hzo-quick-view__rating { display: inline-flex; }
.hzo-quick-view__price {
    font-family: var(--font-number, var(--font-heading));
    font-size: 22px;
    font-weight: 700;
    color: var(--text-heading);
}
.hzo-quick-view__price del { color: var(--text-muted); font-weight: 400; margin-right: var(--space-xs); }
.hzo-quick-view__price ins { text-decoration: none; }
.hzo-quick-view__desc {
    color: var(--text-body);
    line-height: 1.6;
}
.hzo-quick-view__desc > :first-child { margin-top: 0; }
.hzo-quick-view__desc > :last-child { margin-bottom: 0; }

/* ── Buy row (qty + engine ATC button) ─────────────────────────────────────── */
.hzo-quick-view__buy {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
    margin-top: var(--space-xs);
}
.hzo-quick-view__buy--link { display: block; }
.hzo-quick-view__add { flex: 1 1 auto; }

.hzo-quick-view__full {
    margin-top: var(--space-xs);
    font-size: 14px;
    color: var(--text-muted);
    text-decoration: underline;
    text-underline-offset: 2px;
    align-self: flex-start;
}
.hzo-quick-view__full:hover { color: var(--brand); }

/* ── Loading skeleton (§2 async-gap; REDUCED-MOTION-SLOWS-STATUS / G47) ─────── */
.hzo-qv-skel {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-input);
    background: var(--bg-alt);
}
.hzo-qv-skel::after {
    content: "";
    position: absolute;
    inset: 0;
    transform: translateX(-100%);
    background: linear-gradient(90deg, transparent 0, var(--surface) 50%, transparent 100%);
    opacity: 0.55;
    animation: hzo-qv-shimmer calc(var(--dur-4) * 3) var(--ease-standard) infinite;
}
.hzo-qv-skel--stage { aspect-ratio: 1 / 1; border-radius: var(--radius-card); }
.hzo-qv-skel--title { height: 28px; width: 80%; }
.hzo-qv-skel--price { height: 24px; width: 40%; }
.hzo-qv-skel--line  { height: 14px; width: 100%; }
.hzo-qv-skel--short { width: 60%; }
.hzo-qv-skel--buy   { height: 48px; width: 100%; margin-top: var(--space-sm); }

@keyframes hzo-qv-shimmer {
    to { transform: translateX(100%); }
}

/* ── Error state (§1 error / §2 fail — names failure + recovery) ───────────── */
.hzo-quick-view__error {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    align-items: flex-start;
    padding: var(--space-lg) 0;
}
.hzo-quick-view__error-title {
    margin: 0;
    font-family: var(--font-heading);
    font-size: 20px;
    color: var(--text-heading);
}
.hzo-quick-view__error-text { margin: 0; color: var(--text-body); }
.hzo-quick-view__error-actions { display: flex; gap: var(--space-sm); }

/* ── Phone: single column, dialog docks as a bottom sheet (modal.css) ──────── */
@media (max-width: 640px) {
    .hzo-quick-view__grid { grid-template-columns: 1fr; }
    .hzo-quick-view__stage { max-width: 320px; margin-inline: auto; }
}

/* ── Reduced motion: freeze the shimmer to a static placeholder (no busy
     freeze — the aria-busy state + hzo.announce carry "loading"). ──────────── */
@media (prefers-reduced-motion: reduce) {
    .hzo-qv-skel::after { animation: none; }
    .hzo-quick-view__thumb { transition: none; }
}

/* ── Forced colors / High Contrast (§12.2): skeleton + active thumb readable
     without relying on tinted backgrounds. ─────────────────────────────────── */
@media (forced-colors: active) {
    .hzo-qv-skel { border: 1px solid CanvasText; background: Canvas; }
    .hzo-qv-skel::after { animation: none; background: none; }
    .hzo-quick-view__thumb.is-active { border-color: Highlight; }
}


/* --- range/range.css --- */
/* range — @tokens: --range-track-height, --range-thumb-size

   A styled `<input type="range">` slider. The root IS the input — `.hzo-range`
   sits directly on the element, so both vendor track + thumb pseudo-elements are
   painted from here. Native chrome is stripped (`appearance: none`) and rebuilt
   token-only.

   Token-only: the control's track HEIGHT and thumb DIAMETER have no semantic
   token in the scale, so they're component tokens with a literal fallback — the
   same dial pattern icon-button.css / chip.css use. Everything else reads a
   semantic/L1 token.

   Vendor caveat: WebKit and Firefox expose the track + thumb through DIFFERENT,
   non-groupable pseudo-elements (a browser drops the whole selector list if it
   doesn't recognise one pseudo), so each rule is written per-vendor. The focus
   ring goes on the THUMB via box-shadow — `outline` on a range input is unreliable
   across engines. */
.hzo-range {
    appearance: none;
    -webkit-appearance: none;
    width: 100%;
    background: transparent;
    cursor: pointer;
}

/* ─── Track ─── height + pill fill on the alt surface. Per-vendor (not groupable). */
.hzo-range::-webkit-slider-runnable-track {
    height: var(--range-track-height, 6px);
    background: var(--bg-alt);
    border-radius: var(--radius-full);
}
.hzo-range::-moz-range-track {
    height: var(--range-track-height, 6px);
    background: var(--bg-alt);
    border-radius: var(--radius-full);
}

/* ─── Thumb ─── brand circle. WebKit needs `-webkit-appearance: none` + a
   `margin-top` to vertically centre the thumb on the track (thumb-size vs
   track-height, halved and negated). Firefox centres its thumb natively. */
.hzo-range::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: var(--range-thumb-size, 18px);
    height: var(--range-thumb-size, 18px);
    margin-top: calc((var(--range-track-height, 6px) - var(--range-thumb-size, 18px)) / 2);
    background: var(--brand);
    border: 0;
    border-radius: var(--radius-full);
}
.hzo-range::-moz-range-thumb {
    width: var(--range-thumb-size, 18px);
    height: var(--range-thumb-size, 18px);
    background: var(--brand);
    border: 0;
    border-radius: var(--radius-full);
}

/* A11y — required. The ring is an OUTLINE on the input itself (offset out from
   the track): visible AND reliably detectable, unlike a thumb-only pseudo ring
   (which several engines don't expose to getComputedStyle). Disabled state. */
.hzo-range:focus-visible {
    outline: 2px solid var(--color-focus);
    outline-offset: 4px;
    border-radius: var(--radius-full);
}
.hzo-range[disabled],
.hzo-range[aria-disabled="true"],
.hzo-range.is-disabled {
    opacity: var(--opacity-disabled);
    pointer-events: none;
}


/* --- rate-menu/rate-menu.css --- */
/* ─── Service Rate Menu (W-080) ────────────────────────────────────────
   A categorized, scannable rate card: rows of "{service} … {price}" on
   dotted-leader lines, grouped under real <h3> category headings, with
   optional per-row badges + a book/quote/call CTA. Category B (no JS) — the
   "what's included" disclosure is a native <details>.

   Token-only: colors/space/radius/shadow all resolve to the design-token
   layer (assets/css/tokens.css). No raw hex/px where a token exists. The
   decorative leader is currentColor + aria-hidden (forced-colors safe); price
   emphasis is weight + font, never color-only.
   Shortcode: rate-menu.php [hzo_rate_menu] + child [hzo_rate_item]. */

.hzo-rate-menu {
    display: block;
}

/* Category heading — clusters the rows below it. First heading hugs the top;
   later ones get breathing room. (Real <h3> inherits the type ramp/color.) */
.hzo-rate-menu__category {
    margin: var(--space-xl) 0 var(--space-md);
}
.hzo-rate-menu__category:first-child {
    margin-top: 0;
}

.hzo-rate-menu__list {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* ── The leader row ──────────────────────────────────────────────────────
   grid: name | leader (fills) | price. Extra lines (desc / aside / details)
   span the full width beneath. */
.hzo-rate-row {
    display: grid;
    grid-template-columns: minmax(0, auto) 1fr minmax(0, auto);
    align-items: baseline;
    column-gap: var(--space-sm);
    row-gap: var(--space-xs);
    padding: var(--space-smd) 0;
}

.hzo-rate-row__name {
    grid-column: 1;
    font-weight: var(--fw-600);
    color: var(--text-heading);
    line-height: var(--lh-snug);
}

/* The dotted leader — decorative, currentColor so it survives forced-colors.
   align-self:end + a small nudge drops it onto the text baseline. */
.hzo-rate-row__leader {
    grid-column: 2;
    align-self: end;
    height: 0;
    border-bottom: var(--border-1) dotted currentColor;
    opacity: 0.4;
    transform: translateY(-0.28em);
    transition: opacity var(--dur-2) var(--ease-standard);
}

.hzo-rate-row__price {
    grid-column: 3;
    justify-self: end;
    font-family: var(--font-number);
    font-weight: var(--fw-700);
    color: var(--text-heading);
    white-space: nowrap;
    transition: color var(--dur-2) var(--ease-standard);
}

/* Below-name supporting copy */
.hzo-rate-row__desc {
    grid-column: 1 / -1;
    margin: 0;
    color: var(--text-muted);
    font-size: var(--fs-100);
    line-height: var(--lh-normal);
}

/* Badges + CTA aside */
.hzo-rate-row__aside {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    margin-top: var(--space-xs);
}
.hzo-rate-row__badges {
    display: inline-flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
}

/* Per-row CTA — composes .hzo-btn .hzo-btn--outline .hzo-btn--sm; only the
   icon size is component-specific. min-height 36px (btn--sm) clears WCAG 2.5.8
   (24px min); comfortable tap target on touch. */
.hzo-rate-row__cta {
    margin-left: auto;
}
.hzo-rate-row__cta-icon {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

/* "What's included" — native <details> Disclosure (APG). */
.hzo-rate-row__details {
    grid-column: 1 / -1;
}
.hzo-rate-row__details-toggle {
    cursor: pointer;
    color: var(--brand-text);
    font-size: var(--fs-100);
    font-weight: var(--fw-500);
    padding: var(--space-xs) 0;
    width: fit-content;
}
.hzo-rate-row__details-toggle:focus-visible {
    outline: var(--border-3) solid var(--focus-ring);
    outline-offset: 2px;
    border-radius: var(--radius-1);
}
.hzo-rate-row__details-body {
    margin: var(--space-xs) 0 0;
    color: var(--text-body);
    font-size: var(--fs-100);
    line-height: var(--lh-normal);
}

/* ── Hover emphasis (has a resting + tap equivalent — G50; never a hover-only
   reveal) ──────────────────────────────────────────────────────────────── */
.hzo-rate-row:hover .hzo-rate-row__leader {
    opacity: 0.65;
}
.hzo-rate-row:hover .hzo-rate-row__price {
    color: var(--brand-text);
}

/* ── Leader-fill variants ──────────────────────────────────────────────── */
.hzo-rate-menu--leader-rule .hzo-rate-row__leader {
    border-bottom-style: solid;
    opacity: 0.25;
}
.hzo-rate-menu--leader-none .hzo-rate-row__leader {
    border-bottom: 0;
}

/* ── Cards layout ──────────────────────────────────────────────────────── */
.hzo-rate-menu--cards .hzo-rate-menu__list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: var(--space-md);
}
.hzo-rate-menu--cards .hzo-rate-row {
    grid-template-columns: 1fr;
    align-items: start;
    padding: var(--space-md);
    background: var(--surface);
    border: var(--border-1) solid var(--border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-sm);
}
.hzo-rate-menu--cards .hzo-rate-row__leader {
    display: none;
}
.hzo-rate-menu--cards .hzo-rate-row__name {
    font-size: var(--fs-300);
}
.hzo-rate-menu--cards .hzo-rate-row__price {
    justify-self: start;
    font-size: var(--fs-400);
    margin-top: var(--space-xs);
}

/* ── Print — the rate card is exactly what customers print/PDF (G13). Reveal
   every disclosure, drop the decorative leaders + the interactive CTAs, keep
   all prices/rows. ─────────────────────────────────────────────────────── */
@media print {
    .hzo-rate-row__leader,
    .hzo-rate-row__cta {
        display: none !important;
    }
    /* Force-open closed <details> so nothing below the fold is hidden on paper. */
    .hzo-rate-row__details > *:not(summary) {
        display: block !important;
    }
    .hzo-rate-row__details-toggle {
        font-weight: var(--fw-600);
    }
}

/* ── Forced colors — leader/badge/price must not rely on color alone. ────── */
@media (forced-colors: active) {
    .hzo-rate-row__leader {
        opacity: 1;
        border-bottom-color: currentColor;
    }
    .hzo-rate-row__cta:focus-visible,
    .hzo-rate-row__details-toggle:focus-visible {
        outline: var(--border-2) solid;
    }
}

/* ── Reduced motion — kill this component's transitions (PRESERVE-5). ────── */
@media (prefers-reduced-motion: reduce) {
    .hzo-rate-row__leader,
    .hzo-rate-row__price {
        transition: none;
    }
}


/* --- rating/rating.css --- */
/* ─── Rating (static stars) ───────────────────────────────────────────
   Read-only star row for trust / review contexts.

   STAR FILL — --rating-star-color (PF-031). Defaults to --color-warning, the
   engine's amber, so this is byte-identical to the previous hardcoded value.
   The token exists so a site can darken its stars WITHOUT moving --color-warning
   and every warning surface with it.

   ⛔ Constraint, measured, and it is the reason the token exists. Stars are
   non-text content, so WCAG 1.4.11 wants 3:1 against their background. The
   default amber #D97706 against the three light section surfaces the engine
   ships:

       --bg-page   #FFFFFF                white   3.19:1   passes, narrowly
       --bg-alt    #F9FAFB                pale    3.05:1   passes, barely
       --brand-pale (10% brand + white)   tint     2.68:1   FAILS

   So: STAR RATINGS MUST NOT SIT ON .hzo-section--tint. The tint figure was
   computed for one client's brand (#004A3C -> --brand-pale ~#E6EDEC), but
   --brand-pale is a fixed 10% mix, so ANY brand dark enough to be usable as a
   brand colour lands in the same range. The amber has ~0.19 of headroom on
   white and a 10% tint consumes all of it.

   A site that needs stars on tint must set --rating-star-color to something
   darker and re-measure; the default is not safe there and no amount of
   token indirection changes that.

   Empty stars use --border. Author renders N filled + (5−N) empty star SVGs,
   optional count after. For AT, put role="img" + aria-label="Rated 4.8 out of
   5" on the .hzo-rating container and aria-hidden on the SVGs.
   Or generate all of it: [hzo_rating value="4" count="4.0 (128 reviews)"]
   (rating.php) emits this exact contract. */
.hzo-rating { display: inline-flex; align-items: center; gap: 2px; }
.hzo-rating svg { width: 18px; height: 18px; flex-shrink: 0; }
/* Whole-star classes — still emitted by testimonial.php's helper and by
   hand-authored markup (the [hzo_rating] shortcode now uses the fractional
   layers below). */
.hzo-rating__star { color: var(--rating-star-color, var(--color-warning)); }
.hzo-rating__star--empty { color: var(--border); }
.hzo-rating__count { margin-left: var(--space-sm); font-size: var(--fs-100); color: var(--text-muted); }

/* G42 — fractional star row (used by [hzo_rating]): a gray base row + an amber
   fill clipped to value/5, so 4.6 renders as 4.6 stars. Same two-layer clip the
   reviews widget uses; --hzo-stars-pct is set inline on the .hzo-rating root. */
.hzo-rating__stars { position: relative; display: inline-flex; line-height: 0; flex-shrink: 0; }
.hzo-rating__stars-base,
.hzo-rating__stars-fill { display: inline-flex; gap: 2px; }
.hzo-rating__stars-base { color: var(--border); }
.hzo-rating__stars-fill {
    position: absolute;
    inset: 0 auto 0 0;
    width: var(--hzo-stars-pct, 0%);
    overflow: hidden;
    white-space: nowrap;
    color: var(--rating-star-color, var(--color-warning));
}
/* Windows High-Contrast: clip layers collapse to system colors — keep the fill
   visible as Highlight so the rating isn't lost. */
@media (forced-colors: active) { .hzo-rating__stars-fill { color: Highlight; } }


/* --- rating-input/rating-input.css --- */
/* rating-input — @tokens: --rating-input-size, --rating-input-gap

   The accessible, keyboard-operable, form-submittable star-rating INPUT — a
   radio group styled as a row of stars. Sibling to the read-only `.hzo-rating`
   (static display stars); this is the WRITE version, so it lives on its own root
   with its own tokens (do NOT borrow `.hzo-rating`'s).

   Intended markup (the coordinator wires this into the kitchen-sink): five
   <input type="radio" name="…"> each immediately followed by its <label> star,
   emitted in DESCENDING value order (5→1). The container reverses them so DOM
   5→1 renders visually 1→5 left-to-right, which is what lets the pure-CSS sibling
   combinator fill a star + everything to its visual left:

     <span class="hzo-rating-input">
       <input type="radio" id="r5" name="demo-rating" value="5" class="hzo-rating-input__radio">
       <label for="r5" class="hzo-rating-input__star" aria-label="5 stars"><svg…/></label>
       …r4, r3, r2, r1…   (DESCENDING — 5 first, 1 last)
     </span>

   FILL DIRECTION (the fiddly part — verified): with row-reverse a later DOM
   sibling paints to the LEFT. A checked/hovered star at value N has later DOM
   siblings N-1…1, which render visually leftward = the lower values. So
   `:checked ~ __star` (plus the checked radio's own adjacent `+` label) fills
   value N and everything below it — the star + its leftward run. `:hover, :hover
   ~ __star` gives the same leftward preview run. This is exactly the star-rating
   convention (select 4 → stars 1–4 lit).

   Token-only: the star box has no semantic dimension token in the scale, so it's
   a component token with a literal fallback — the same dial pattern
   icon-button.css uses (`var(--icon-button-size, 40px)`). Empty stars read
   `--border`; filled stars read `--accent` (the engine's interactive brand-accent
   highlight; the display `.hzo-rating` uses `--warning` amber, but this control
   aligns to the accent). Everything else reads a semantic/L1 token. */
.hzo-rating-input {
    display: inline-flex;
    flex-direction: row-reverse;   /* DOM 5→1 renders visually 1→5 */
    justify-content: flex-end;
    align-items: center;
    gap: var(--rating-input-gap, var(--space-2xs));
}

/* Radio — visually hidden but still focusable & form-submittable (sr-only). Never
   display:none — that removes it from the tab order and the submitted form. */
.hzo-rating-input__radio {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    border: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    opacity: 0;
}

/* Star — the clickable <label> for its radio. Empty = --border. */
.hzo-rating-input__star {
    width: var(--rating-input-size, 28px);
    height: var(--rating-input-size, 28px);
    color: var(--border);
    cursor: pointer;
    transition: color var(--transition-base) ease;
}
.hzo-rating-input__star > svg {
    width: 100%;
    height: 100%;
    display: block;
}

/* Fill — a checked star and every star to its visual left light up. The checked
   radio's own label is the adjacent `+`; the lower-value stars are the later `~`
   siblings (leftward under row-reverse). */
.hzo-rating-input__radio:checked + .hzo-rating-input__star,
.hzo-rating-input__radio:checked ~ .hzo-rating-input__star {
    color: var(--accent);
}

/* Hover preview — same leftward run: the hovered star + its later `~` siblings
   (lower values, visually left). */
.hzo-rating-input__star:hover,
.hzo-rating-input__star:hover ~ .hzo-rating-input__star {
    color: var(--accent);
}

/* A11y — required. The radio is hidden, so the focus ring rides its label star. */
.hzo-rating-input__radio:focus-visible + .hzo-rating-input__star {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
}
.hzo-rating-input__radio:disabled + .hzo-rating-input__star,
.hzo-rating-input.is-disabled {
    opacity: var(--opacity-disabled);
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    .hzo-rating-input__star { transition: none; }
}


/* --- recent/recent.css --- */
/* recent — recent-posts layout for the [hzo_recent_posts] widget. Two layouts:
   a FEATURED card (image over a text stack) and a COMPACT list (thumbnail beside
   title + date). The shortcode renders the markup (includes/recent-posts-
   shortcode.php); this sheet is layout only. Token-only, brand-agnostic. */

/* ── Featured card ────────────────────────────────────────────────────────── */
.hzo-recent--featured .hzo-recent__card--featured {
    display: flex;
    flex-direction: column;
}
.hzo-recent__card--featured .hzo-recent__media {
    display: block;
    aspect-ratio: 4 / 3;
    overflow: hidden;
    border-radius: var(--radius-card, 12px);
    background: var(--bg-alt);
    margin-bottom: var(--space-md);
}
.hzo-recent__card--featured .hzo-recent__media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.hzo-recent__eyebrow {
    font-size: var(--eyebrow-size, 15px);
    font-weight: var(--fw-semibold, 600);
    text-transform: uppercase;
    letter-spacing: var(--eyebrow-tracking, 0.7px);
    color: var(--accent-text);
    margin-bottom: var(--space-xs);
}
.hzo-recent__excerpt {
    color: var(--text-muted);
    line-height: 1.6;
    margin: var(--space-sm) 0 0;
}
.hzo-recent__card--featured .hzo-recent__btn {
    margin-top: var(--space-md);
    align-self: flex-start;
}

/* ── Compact list ─────────────────────────────────────────────────────────── */
.hzo-recent--list {
    display: flex;
    flex-direction: column;
}
.hzo-recent__card--compact {
    display: flex;
    flex-direction: row;
    gap: var(--space-md);
    align-items: center;
    padding: var(--space-md) 0;
    border-bottom: 1px solid var(--border);
}
.hzo-recent__card--compact:first-child { padding-top: 0; }
.hzo-recent__card--compact .hzo-recent__media {
    flex: 0 0 92px;
    width: 92px;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    border-radius: var(--radius-input, 8px);
    background: var(--bg-alt);
    display: block;
}
.hzo-recent__card--compact .hzo-recent__media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.hzo-recent__card--compact .hzo-recent__body {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.hzo-recent__date {
    font-size: 12px;
    color: var(--text-muted);
    font-weight: var(--fw-semibold, 600);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.hzo-recent__title {
    margin: 0;
    font-size: 1.05rem;
    line-height: 1.35;
}
/* Title links are bare <a> (headings), so a client's broad bare-link skin
   (e.g. `:where(main) a:not([class])`) matches them at equal (0,1,1) specificity
   and would win by load order now that this rule lives in the engine. The __card
   ancestor lifts this to (0,2,1) so the component robustly owns its own
   heading-ink title color regardless of any client bare-link skin. A client
   highlight can also ride in via `background-image` (a highlight-wipe underline),
   which this reset neutralizes too — a title link is a heading, never an inline
   body link. */
.hzo-recent__card .hzo-recent__title a { color: var(--text-heading); text-decoration: none; background-image: none; }
.hzo-recent__card .hzo-recent__title a:hover { color: var(--brand-text); }


/* --- ribbon/ribbon.css --- */
/* ─── Ribbon — rotated corner label ───────────────────────────────────
   Diagonal badge across a card/tile corner ("Popular", "Sale"). The parent
   needs position:relative + overflow:hidden — .hzo-tile and .hzo-card__media
   already qualify. Meaningful text stays in the DOM (not aria-hidden). */
.hzo-ribbon {
    position: absolute;
    top: 18px;
    right: -34px;
    z-index: 2;
    transform: rotate(45deg);
    background: var(--ribbon-bg, var(--accent));
    color: var(--ribbon-color, var(--text-on-accent));
    font-family: var(--font-body);
    font-size: var(--fs-50);
    font-weight: var(--fw-700, 700);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    line-height: var(--lh-none);
    text-align: center;
    padding: 6px 40px;
    box-shadow: var(--shadow-sm);
    pointer-events: none;
}
.hzo-ribbon--tl { right: auto; left: -34px; transform: rotate(-45deg); }


/* --- scroll-progress/scroll-progress.css --- */
/* scroll-progress — @tokens: --scroll-progress-height */
/* ─── Scroll progress ─────────────────────────────────────────────────
   Thin reading-progress bar fixed at the top of the viewport, filling
   left→right as the page scrolls. Decorative (aria-hidden in markup, not
   focusable). JS sets the fill ratio via the --hzo-scroll-progress custom
   property (0..1); the bar is scaled on the X axis from that ratio. No
   transition — the value updates every scroll frame, so a transition would
   lag behind the pointer; hence no reduced-motion block is needed (this is
   scroll-driven, not autonomous motion). */
.hzo-scroll-progress {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: var(--scroll-progress-height, 3px);
    background: transparent;
    z-index: var(--z-overlay);
    pointer-events: none;
}

.hzo-scroll-progress__bar {
    display: block;
    height: 100%;
    width: 100%;
    background: var(--brand);
    transform-origin: left center;
    transform: scaleX(var(--hzo-scroll-progress, 0));
}

/* ── Colour modifier (recolour the bar) ── */
.hzo-scroll-progress--accent .hzo-scroll-progress__bar {
    background: var(--accent);
}


/* --- section-divider/section-divider.css --- */
/* ─── Section divider (loader manifest) ───────────────────────────────
   The actual rules live in section-dividers.css (plural — the filename
   predates widgetization and is still @imported by the legacy
   assets/css/primitives.css fallback at its line 70, so it keeps its
   name rather than breaking that path). The loader enqueues ONLY
   <name>.css, so this manifest @imports the sheet to give it a load
   path in components mode (the wave-4 video.css lesson: before this
   file existed, the wave/mosaic divider styles had NO load path on a
   normal checkout — primitives.css is only enqueued in fallback mode).
   In the rare double-fallback case both paths import the same sheet;
   identical rules twice are a harmless no-op.

   ⚠ REACH FOR _base/section-edges.css FIRST (Wave S). This component's paths
   are FROZEN shapes: you pick one of four and you are done — no angle, no
   size, no way to make the boundary belong to the two surfaces it sits
   between. _base/section-edges.css is the parameterized alternative
   (--edge-color / --edge-size / --edge-angle / --edge-radius, six shapes,
   top or bottom) and it always loads. This sheet stays supported and
   unchanged for every page already using it; it is simply no longer the
   default answer to "how should this boundary look". */
/* hzo-bundle: skipped @import ./section-dividers.cssver1788182399 */


/* --- section-intro/section-intro.css --- */
/* ─── Section Intro Header (.hzo-section-intro) ─────────────────────────────
   W-090 — the standardized opener for every marketing section: eyebrow,
   semantically-correct heading (level decoupled from visual size), a
   measure-capped paragraph, and an optional actions row (0–2 buttons, an APG
   tablist, or a filter-chip group). Composes existing primitives (button/tag
   tokens, .hzo-reveal, divider tokens) and owns only the layout + a11y
   scaffolding.

   GATING: always-load (the shortcode un-gates). The common eyebrow+heading+text
   case is pure CSS — zero runtime effect (section-intro.js only does work when a
   filter group or a tablist is present, plus a delegated tel: mirror).

   Tokens only: --space-* (rhythm), --border / --border-* (divider + chip border),
   --brand* / --accent-text (chip + eyebrow ink), --radius-* (chip pill),
   --fs-h1..h3 (visual size ladder), --dur-* / --ease-* (chip/tab transitions),
   --font-heading / --font-body. No hardcoded hex, no bare z-index (the intro is
   flow content — it claims none), px only for the hairline border + icon sizing. */

.hzo-section-intro {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-md);
    margin: 0;
}

/* ── Uniform bottom spacing (opener → the content it introduces) ───────────── */
.hzo-section-intro--space-sm { margin-block-end: var(--space-lg); }
.hzo-section-intro--space-md { margin-block-end: var(--space-xl); }
.hzo-section-intro--space-lg { margin-block-end: var(--space-2xl); }

/* ── Hairline divider (shared --border token; padding matches the spacing) ── */
.hzo-section-intro--rule-top,
.hzo-section-intro--rule-both { border-block-start: 1px solid var(--border); padding-block-start: var(--space-lg); }
.hzo-section-intro--rule-bottom,
.hzo-section-intro--rule-both { border-block-end: 1px solid var(--border); padding-block-end: var(--space-lg); }

/* ── Alignment (left default; center for hero-style openers) ──────────────── */
.hzo-section-intro--align-center {
    align-items: center;
    text-align: center;
}
.hzo-section-intro--align-center .hzo-section-intro__text { margin-inline: auto; }

/* ── Eyebrow — ALWAYS a <p> (never a heading; can't pollute the outline) ──── */
.hzo-section-intro__eyebrow {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    margin: 0;
    font-family: var(--font-body);
    line-height: 1.3;
}
/* text: uppercase kicker on accent ink (mirrors the shared .hzo-eyebrow look). */
.hzo-section-intro__eyebrow--text,
.hzo-section-intro__eyebrow--icon {
    font-size: var(--eyebrow-size, 15px);
    font-weight: var(--fw-semibold, 600);
    text-transform: uppercase;
    letter-spacing: var(--eyebrow-tracking, 0.7px);
    color: var(--accent-text);
}
/* tag: a quiet bordered chip. pill: a filled brand-pale pill. Both are static
   (a <p>, not interactive) — sentence-cased kicker inside a token. */
.hzo-section-intro__eyebrow--tag,
.hzo-section-intro__eyebrow--pill {
    padding: 5px 12px;
    font-size: 13px;
    font-weight: var(--fw-semibold, 600);
    line-height: 1.2;
    letter-spacing: var(--tracking-wide, 0.05em);
    text-transform: uppercase;
    border-radius: var(--radius-pill, var(--radius-full));
}
.hzo-section-intro__eyebrow--tag {
    color: var(--brand-text);
    background: var(--bg-alt);
    border: 1px solid var(--border);
}
.hzo-section-intro__eyebrow--pill {
    color: var(--brand-text);
    background: var(--brand-pale);
    border: 1px solid transparent;
}
.hzo-section-intro__eyebrow-icon {
    width: 1.1em;
    height: 1.1em;
    flex-shrink: 0;
}

/* ── Heading — semantic <h2>/<h3>, VISUAL size from the root modifier. ──────
   text-wrap:balance for a tidy multi-line headline; the id (for anchors/TOC)
   lives on this element, not the wrapper. */
.hzo-section-intro__heading {
    margin: 0;
    font-family: var(--font-heading);
    line-height: 1.15;
    text-wrap: balance;
    color: var(--h2-color, var(--text-heading));
}
.hzo-section-intro--size-sm .hzo-section-intro__heading {
    font-size: calc(var(--fs-h3) * var(--hzo-type-scale, 1) * var(--hzo-heading-scale, 1));
    font-weight: var(--font-weight-heading, var(--h3-weight, 600));
}
.hzo-section-intro--size-md .hzo-section-intro__heading {
    font-size: calc(var(--fs-h2) * var(--hzo-type-scale, 1) * var(--hzo-heading-scale, 1));
    font-weight: var(--font-weight-heading, var(--h2-weight, 700));
}
.hzo-section-intro--size-lg .hzo-section-intro__heading {
    font-size: calc(var(--fs-h1) * var(--hzo-type-scale, 1) * var(--hzo-heading-scale, 1));
    font-weight: var(--font-weight-heading, var(--h1-weight, 700));
}

/* ── Supporting text — measure-capped for readability (~45–75ch). ─────────── */
.hzo-section-intro__text {
    margin: 0;
    max-inline-size: var(--hzo-intro-measure, 60ch);
    color: var(--text-body);
    font-size: var(--fs-body);
    line-height: var(--body-line-height, 1.6);
}

/* ── Actions row (buttons | tabs | filters) ───────────────────────────────── */
.hzo-section-intro__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
    margin: 0;
    padding: 0;
    margin-block-start: var(--space-xs);
}
.hzo-section-intro--align-center .hzo-section-intro__actions { justify-content: center; }
.hzo-section-intro__tabs-root { align-self: stretch; }
.hzo-section-intro--align-center .hzo-section-intro__tabs-root .hzo-section-intro__actions--tabs { justify-content: center; }

/* Tabs/filters can overflow a narrow container — scroll horizontally rather than
   wrap into a ragged block; the group keeps its role/aria intact. */
.hzo-section-intro__actions--tabs,
.hzo-section-intro__actions--filters {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
}
.hzo-section-intro__actions--tabs::-webkit-scrollbar,
.hzo-section-intro__actions--filters::-webkit-scrollbar { display: none; }

/* ── Chip (shared by filter toggle-buttons AND tabs) ─────────────────────────
   A real <button> with text; selected state is carried by aria-pressed / .is-active
   (attribute, never :hover — G50). ≥44px comfort target (≥24 floor). */
.hzo-section-intro__chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    min-height: 44px;
    padding: 8px 16px;
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: var(--fw-medium, 500);
    line-height: 1.2;
    white-space: nowrap;
    color: var(--text-body);
    background: var(--bg-alt);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill, var(--radius-full));
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background var(--dur-1) var(--ease-standard),
                border-color var(--dur-1) var(--ease-standard),
                color var(--dur-1) var(--ease-standard);
}
.hzo-section-intro__chip:hover {
    background: var(--brand-pale);
    border-color: var(--brand);
    color: var(--brand-text);
}
.hzo-section-intro__chip:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}
.hzo-section-intro__chip:active { transform: translateY(1px); }

/* Selected filter chip (aria-pressed) + selected tab (aria-selected / .is-active)
   — state by attribute, not hover. */
.hzo-section-intro__chip.is-active,
.hzo-section-intro__chip[aria-pressed="true"],
.hzo-section-intro__chip[aria-selected="true"] {
    background: var(--brand-pale);
    border-color: var(--brand);
    color: var(--brand-text);
    font-weight: var(--fw-semibold, 600);
}

/* A filter group whose target selector matched nothing on the page — JS marks the
   whole group disabled rather than shipping a dead control (G11). */
.hzo-section-intro__actions--filters[aria-disabled="true"] .hzo-section-intro__chip {
    opacity: var(--opacity-disabled, 0.5);
    pointer-events: none;
}

/* ── Buttons row — composes the shared .hzo-btn primitive; no extra chrome. ── */
.hzo-section-intro__actions--buttons { gap: var(--space-md); }

/* ── Reduced motion — kill chip/tab transitions (state changes stay instant;
   PRESERVE-5). The reveal hook, when used, is owned/killed by main.js. ─────── */
@media (prefers-reduced-motion: reduce) {
    .hzo-section-intro__chip { transition: none; }
}

/* ── Forced colors — the selected chip must not signal via background alone
   (stripped in High Contrast): add a system-color border so aria-pressed /
   aria-selected keeps a visible correlate; focus resolves to a real outline. ── */
@media (forced-colors: active) {
    .hzo-section-intro__chip.is-active,
    .hzo-section-intro__chip[aria-pressed="true"],
    .hzo-section-intro__chip[aria-selected="true"] {
        border-color: Highlight;
        outline: 1px solid Highlight;
    }
    .hzo-section-intro__chip:focus-visible { outline: 2px solid ButtonText; }
}

/* ── Print — openers are LCP-visible, so force-reveal if the reveal hook left the
   header at opacity:0 (G13), and hide the non-functional actions row. ───────── */
@media print {
    .hzo-section-intro.hzo-reveal,
    .hzo-section-intro.hzo-reveal--init {
        opacity: 1 !important;
        transform: none !important;
    }
    .hzo-section-intro__actions--tabs,
    .hzo-section-intro__actions--filters { display: none; }
}


/* --- segmented-control/segmented-control.css --- */
/* segmented-control — @tokens: --segmented-control-gap, --segmented-control-pad, --segmented-control-option-pad-y, --segmented-control-option-pad-x

   The iOS-style segmented control: a mutually-exclusive PILL button group riding a
   track, where the selected segment lifts to a raised light pill. This is the
   wayfinding/view-switch control (List / Grid / Map) — distinct from `.hzo-tabs`,
   which are underline content-panel tabs over a panel. Batch E (navigation).

   Radio-backed control — same pattern as its siblings variation-swatch and
   rating-input: sr-only radios (still focusable & form-submittable), the focus ring
   rides the adjacent label, and checked/disabled are driven by `:checked +` /
   `:disabled +`. Each option is independent; only the CHECKED one lifts (`+`, the
   adjacent label only — no cumulative `~`).

   Intended markup (coordinator writes the demo):

     <span class="hzo-segmented-control" role="radiogroup" aria-label="View">
       <input type="radio" id="v-list" name="view" class="hzo-segmented-control__radio" checked>
       <label for="v-list" class="hzo-segmented-control__option">List</label>
       <input type="radio" id="v-grid" name="view" class="hzo-segmented-control__radio">
       <label for="v-grid" class="hzo-segmented-control__option">Grid</label>
       <input type="radio" id="v-map" name="view" class="hzo-segmented-control__radio">
       <label for="v-map" class="hzo-segmented-control__option">Map</label>
     </span>

   Token-only. The track's inner gap/padding and the option's snug pad have no
   semantic tokens in the scale (they're the sub-pixel dial that gives the raised-pill
   look), so they are component tokens with literal fallbacks — the same dial pattern
   variation-swatch/icon-button use. Everything else reads a semantic/L1 token. */
.hzo-segmented-control {
    display: inline-flex;
    align-items: center;
    gap: var(--segmented-control-gap, 2px);
    padding: var(--segmented-control-pad, 3px);
    background: var(--bg-alt);
    border-radius: var(--radius-pill);
}

/* Radio — visually hidden but still focusable & form-submittable (sr-only). Never
   display:none — that removes it from the tab order and the submitted form.
   (copied verbatim from variation-swatch__radio.) */
.hzo-segmented-control__radio {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    border: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    opacity: 0;
}

/* Option — the clickable <label> segment. Unselected reads as quiet body text. */
.hzo-segmented-control__option {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--segmented-control-option-pad-y, 6px) var(--segmented-control-option-pad-x, 16px);
    border-radius: var(--radius-control);
    color: var(--text-body);
    font-size: var(--fs-75);
    line-height: var(--lh-none);
    cursor: pointer;
    white-space: nowrap;
    transition: background var(--transition-base) ease,
                color var(--transition-base) ease,
                box-shadow var(--transition-base) ease;
}

/* Hover (unselected) — darken toward the heading colour as a cue. */
.hzo-segmented-control__option:hover {
    color: var(--text-heading);
}

/* Selected (checked) — the raised light pill: lifts off the track with a light
   fill + small shadow. The checked radio's own adjacent label is the only one lit
   (`+`, no cumulative `~`). */
.hzo-segmented-control__radio:checked + .hzo-segmented-control__option {
    background: var(--card-bg, #fff);
    color: var(--text-heading);
    box-shadow: var(--shadow-sm);
    font-weight: var(--fw-600);
}

/* A11y — required. The radio is hidden, so the focus ring rides its label option. */
.hzo-segmented-control__radio:focus-visible + .hzo-segmented-control__option {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
}
.hzo-segmented-control__radio:disabled + .hzo-segmented-control__option,
.hzo-segmented-control.is-disabled {
    opacity: var(--opacity-disabled);
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    .hzo-segmented-control__option { transition: none; }
}


/* --- service-schema/service-schema.css --- */
/* ─── Service + Offer Schema Block (.hzo-service-schema) ─────────────────
   The visible price-framing card for W-003 (service-schema.php). Structured
   content, not a composite widget: a heading, a measure-capped description,
   a price framing line, an optional priced offer list (native <details> when
   collapsed past offers-max), and an optional CTA. The Service JSON-LD is
   PHP-emitted and has no styling surface.

   Tokens only — no bare hex/px/ms, no z-index (this block never portals or
   stacks). schema-only render emits no DOM, so none of this applies there. */
.hzo-service-schema {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    max-width: var(--prose-max-width);
    padding: var(--space-lg);
    background: var(--card-bg, var(--surface));
    border: 1px solid var(--card-border, var(--border));
    border-radius: var(--radius-panel);
    box-shadow: var(--card-shadow);
}

.hzo-service-schema__heading {
    margin: 0;
    color: var(--text-heading);
}

.hzo-service-schema__desc {
    margin: 0;
    max-width: var(--prose-max-width);
    color: var(--text-body);
}

/* ── Price framing ── */
.hzo-service-schema__price {
    margin: 0;
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--space-sm);
}
.hzo-service-schema__price-value {
    color: var(--brand-text);
    font-family: var(--font-number, inherit);
    font-weight: var(--fw-bold, 700);
    /* Size inherits from the type-scale ladder (base.css); emphasis is carried
       by the number font + weight + brand color, not a hardcoded font-size. */
}
.hzo-service-schema__price-unit {
    color: var(--text-muted);
    font-weight: var(--fw-medium, 500);
}

/* ── Offer list + native <details> disclosure ── */
.hzo-service-schema__offers {
    margin: 0;
}
.hzo-service-schema__offers-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}
.hzo-service-schema__offer {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-md);
    padding-block: var(--space-2xs);
    border-bottom: 1px solid var(--card-border, var(--border));
    color: var(--text-body);
}
.hzo-service-schema__offer:last-child {
    border-bottom: 0;
}
.hzo-service-schema__offer-link {
    color: var(--brand-text);
    text-decoration: none;
}
.hzo-service-schema__offer-link:hover {
    text-decoration: underline;
}
.hzo-service-schema__offer-price {
    color: var(--text-heading);
    font-family: var(--font-number, inherit);
    font-weight: var(--fw-semibold, 600);
    white-space: nowrap;
    flex-shrink: 0;
}

/* <summary> toggle — native disclosure; ≥24px hit area, real focus ring.
   No box-shadow ring (forced-colors-safe): relies on outline (§12.2). */
.hzo-service-schema__offers-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    min-height: 24px;
    padding-block: var(--space-2xs);
    color: var(--brand-text);
    font-weight: var(--fw-semibold, 600);
    cursor: pointer;
    list-style: none;              /* hide default marker; use our own caret */
}
.hzo-service-schema__offers-toggle::-webkit-details-marker {
    display: none;
}
.hzo-service-schema__offers-toggle::after {
    content: "";
    width: 0.5em;
    height: 0.5em;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg);
    transition: transform var(--dur-1) var(--ease-standard);
}
.hzo-service-schema__offers[open] > .hzo-service-schema__offers-toggle::after {
    transform: rotate(-135deg);
}
.hzo-service-schema__offers[open] > .hzo-service-schema__offers-toggle {
    margin-bottom: var(--space-sm);
}

/* ── CTA (native <a>; inherits .hzo-btn) ── */
.hzo-service-schema__cta {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    min-height: 24px;              /* .hzo-btn already exceeds this; explicit floor */
}
.hzo-service-schema__cta-icon {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}

/* ── Print: customers print estimates — the card, price, and FULL offer list
   must print. Force any collapsed <details> open, and force-reveal an opt-in
   .hzo-reveal card so it is never blank (G13). Drop the decorative caret. ── */
@media print {
    .hzo-service-schema {
        box-shadow: none;
        border-color: currentColor;
    }
    .hzo-service-schema.hzo-reveal {
        opacity: 1 !important;
        transform: none !important;
    }
    .hzo-service-schema__offers[open] > .hzo-service-schema__offers-toggle::after,
    .hzo-service-schema__offers-toggle::after {
        display: none;
    }
    .hzo-service-schema__offers-list {
        display: flex !important;   /* browsers auto-open <details> for print, but be explicit */
    }
}

/* ── Forced-colors: convey no state through color/shadow alone; keep a border. ── */
@media (forced-colors: active) {
    .hzo-service-schema {
        border: 1px solid CanvasText;
    }
    .hzo-service-schema__offers-toggle::after {
        border-color: CanvasText;
    }
}

/* ── Reduced-motion: kill the only animated affordance (the caret rotate).
   PRESERVE-5 — ambient motion stops; content stays visible. ── */
@media (prefers-reduced-motion: reduce) {
    .hzo-service-schema__offers-toggle::after {
        transition: none;
    }
}


/* --- sidebar-nav/sidebar-nav.css --- */
/* sidebar-nav — @tokens: --sidebar-nav-link-pad-y, --sidebar-nav-link-pad-x */

/* Reusable vertical in-page / app navigation (wayfinding). Distinct from
   the overlay mobile-menu and the doc-specific doc-toc: a plain nestable
   link list with an optional group label and a brand accent on the
   current item. Batch E (navigation/wayfinding). */
.hzo-sidebar-nav {
    display: block;
    width: 100%;
}

/* Groups stack with breathing room between them. */
.hzo-sidebar-nav__group + .hzo-sidebar-nav__group {
    margin-top: var(--space-sm);
}

/* Small uppercase section heading above a list. */
.hzo-sidebar-nav__label {
    margin: 0;
    padding: var(--space-2xs) var(--space-sm);
    font-size: var(--fs-50);
    font-weight: var(--fw-600);
    line-height: var(--lh-normal);
    letter-spacing: 0.04em;   /* structural micro-tracking for the uppercase label */
    text-transform: uppercase;
    color: var(--text-muted);
}

.hzo-sidebar-nav__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
}

/* Nested child list — indented under its parent link. */
.hzo-sidebar-nav__list--nested {
    padding-left: var(--space-sm);
    margin-top: var(--space-2xs);
}

.hzo-sidebar-nav__link {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    padding: var(--sidebar-nav-link-pad-y, 8px) var(--sidebar-nav-link-pad-x, 12px);
    border-radius: var(--radius-control);
    color: var(--text-body);
    text-decoration: none;
    font-size: var(--fs-100);
    line-height: var(--lh-normal);
    transition: background var(--transition-base) ease, color var(--transition-base) ease;
}

.hzo-sidebar-nav__link:hover {
    background: var(--bg-alt);
    color: var(--text-heading);
}

/* Current item — brand-pale surface + a left brand accent bar. Both the
   .is-active class and [aria-current="page"] resolve to the same state. */
.hzo-sidebar-nav__link.is-active,
.hzo-sidebar-nav__link[aria-current="page"] {
    background: var(--brand-pale);
    color: var(--brand-text);
    font-weight: var(--fw-600);
    box-shadow: inset 3px 0 0 var(--brand);   /* structural: 3px accent bar width */
}

.hzo-sidebar-nav__link:focus-visible {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .hzo-sidebar-nav__link {
        transition: none;
    }
}


/* --- skeleton/skeleton.css --- */
/* skeleton — @tokens: --skeleton-text-height, --skeleton-title-height, --skeleton-circle-size */
/* ─── Skeleton ────────────────────────────────────────────────────────
   Loading placeholder / skeleton screen. Purely presentational — markup
   is aria-hidden and a sibling sr-only "Loading…" carries the meaning.
   Default is a cheap, GPU-free opacity pulse (reduced-motion-safe); the
   optional --shimmer modifier adds a swept highlight. Shape modifiers
   (text / title / circle / media) size the placeholder to its content. */
@keyframes hzo-skeleton-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.55; }
}
@keyframes hzo-skeleton-shimmer {
    100% { transform: translateX(100%); }
}

.hzo-skeleton {
    display: block;
    position: relative;
    overflow: hidden;
    background: var(--bg-alt);
    border-radius: var(--radius-control);
    animation: hzo-skeleton-pulse 1.4s ease-in-out infinite;
}

/* ── Shape modifiers ─────────────────────────────────────────────── */
.hzo-skeleton--text {
    height: var(--skeleton-text-height, 0.8em);
    border-radius: var(--radius-control);
    margin-bottom: var(--space-xs);
}

.hzo-skeleton--title {
    height: var(--skeleton-title-height, 1.4em);
}

.hzo-skeleton--circle {
    width: var(--skeleton-circle-size, 48px);
    height: var(--skeleton-circle-size, 48px);
    border-radius: var(--radius-full);
}

.hzo-skeleton--media {
    width: 100%;
    aspect-ratio: 16 / 9;
    border-radius: var(--radius-panel);
}

/* ── Optional shimmer ────────────────────────────────────────────────
   A moving highlight swept across the placeholder. Disables the base
   pulse so the two effects don't compound. */
.hzo-skeleton--shimmer {
    animation: none;
}
.hzo-skeleton--shimmer::after {
    content: "";
    position: absolute;
    inset: 0;
    transform: translateX(-100%);
    background: linear-gradient(
        90deg,
        transparent,
        color-mix(in srgb, var(--card-bg, #fff) 60%, transparent),
        transparent
    );
    animation: hzo-skeleton-shimmer 1.4s ease-in-out infinite;
}

/* ── Reduced motion — static placeholder, no motion ─────────────────── */
@media (prefers-reduced-motion: reduce) {
    .hzo-skeleton,
    .hzo-skeleton--shimmer::after {
        animation: none;
    }
}


/* --- skip-link/skip-link.css --- */
/* ─── Skip-links (multi-target bypass block · .hzo-skip-links) ─────────────
   ADDITIVE to the single, always-loaded .hzo-skip-link (components/_base/
   skip-link.css, PRESERVE-6). That base sheet owns the reveal-on-focus PILL
   (clip-hidden → brand pill on :focus, --z-toast, reduced-motion kill). Per the
   §6 / G2 coupling rule the reveal MUST live in an always-loaded sheet — it does
   (_base), so a page that never enqueues THIS sheet still reveals every link.

   This sheet adds ONLY the group concerns the single link can't express:
     • the wrapper (position:fixed children, so it takes no layout space),
     • RTL-safe position modifiers (logical inset props override the base's
       physical `left`),
     • the `always` persistent-bar mode (kiosk / forced-colors),
     • print + forced-colors handling.
   Each child KEEPS the base .hzo-skip-link class, so the pill, z-index, focus
   ring, and reduced-motion behavior are inherited — never re-declared here. */

/* Group wrapper — the .hzo-skip-link children are position:fixed (base sheet),
   so the wrapper contributes no box; display:contents keeps it out of flow while
   the descendant position modifiers below still match. */
.hzo-skip-links {
    display: contents;
}

/* ── Position (logical, RTL-safe). Overrides the base's physical `left: 8px`;
      `top` is unchanged (block-start is top in both writing directions). ── */
.hzo-skip-links--start .hzo-skip-link {
    left: auto;
    inset-inline-start: var(--space-sm);
}
.hzo-skip-links--end .hzo-skip-link {
    left: auto;
    inset-inline-start: auto;
    inset-inline-end: var(--space-sm);
}
.hzo-skip-links--center .hzo-skip-link {
    left: auto;
    inset-inline-start: 50%;
    transform: translate(-50%, -150%);
}
/* Higher specificity than the base `.hzo-skip-link:focus` rule, so the centered
   pill reveals in place instead of snapping to the base's translateY(0). */
.hzo-skip-links--center .hzo-skip-link:focus {
    transform: translate(-50%, 0);
}

/* ── `always` mode — a persistent visible bar (kiosk / high-contrast setups).
      Children drop out of the fixed/clip reveal and flow inline in the bar. ── */
.hzo-skip-links--always {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    padding: var(--space-sm);
    background: var(--brand);
}
.hzo-skip-links--always .hzo-skip-link {
    position: static;
    inset: auto;
    transform: none;
}

/* ── Environment: navigation chrome is useless on paper; hide when printed.
      (Not reveal-hidden content — G13 force-reveal does not apply.) ── */
@media print {
    .hzo-skip-links,
    .hzo-skip-links .hzo-skip-link {
        display: none !important;
    }
}

/* Forced-colors: the revealed pill uses system colors and keeps a real outline
   (box-shadow rings are dropped in High-Contrast — §12.2). The base already
   supplies the outline; pin the fill to system colors so the bar mode survives. */
@media (forced-colors: active) {
    .hzo-skip-links--always {
        background: Canvas;
    }
    .hzo-skip-links .hzo-skip-link {
        outline: 1px solid CanvasText;
    }
}

/* Defensive reduced-motion kill (the base already zeroes the .hzo-skip-link
   transition; this keeps the centered-reveal transform instant if a future base
   edit ever animates transform under reduce). */
@media (prefers-reduced-motion: reduce) {
    .hzo-skip-links .hzo-skip-link {
        transition: none;
    }
}


/* --- social-icons/social-icons.css --- */
/* ─── Social icons (v1.1.3) ────────────────────────────────────────────────
   [hzo_social_icons] outputs inline SVGs without explicit width/height
   attributes. Without an explicit CSS constraint, SVGs in a flex container
   fall to fill-the-parent sizing — produced the 150px+ Facebook/Instagram
   icons surfaced 2026-05-22. Constrains both the default .hzo-social-icons
   class and the .hzo-footer-social variant the footer.html part passes. */
.hzo-social-icons,
.hzo-footer-social {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    flex-wrap: wrap;
    margin-top: var(--space-smd);
}
/* Social icon links carry the .hzo-social-icons__link class (added in
   [hzo_social_icons], v1.1.93) — again, the class exists so a per-client
   `a:not([class])` content-link rule excludes them. Styling stays on the
   original descendant selectors, which win on cascade order within this file.
   No !important. */
.hzo-social-icons a,
.hzo-footer-social a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 44px tap target (WCAG 2.5.5 comfort size) — G53 gate requires the family
       bar move off the old 36px, especially now the row carries more platforms.
       The glyph stays 20px; the extra size is padding around it. */
    width: 44px;
    height: 44px;
    color: var(--footer-social-link, var(--accent, currentColor));
    text-decoration: none;
    background-image: none;
    border-radius: 50%;
    /* Smooth ease-out settle rather than a snappy linear flip (v1.1.93). */
    transition: color 0.3s cubic-bezier(0.22, 1, 0.36, 1),
                background-color 0.3s cubic-bezier(0.22, 1, 0.36, 1),
                transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
/* Hold the accent color after the (external) social link is visited — without
   this, `.hzo-site-footer a:visited` (rgba .85) repaints visited icons. Mirrors
   the .hzo-footer-link:visited pattern; placed after that rule so it wins the
   same-specificity tie on cascade order. No !important. */
.hzo-social-icons a:visited,
.hzo-footer-social a:visited { color: var(--footer-social-link, var(--accent, currentColor)); }
.hzo-social-icons a:hover,
.hzo-social-icons a:focus-visible,
.hzo-footer-social a:hover,
.hzo-footer-social a:focus-visible {
    color: var(--footer-social-link-hover, var(--text-on-accent, #fff));
    background-color: var(--footer-social-bg-hover, var(--accent-hover-on-dark, rgba(255,255,255,0.1)));
    transform: translateY(-2px);
}
@media (prefers-reduced-motion: reduce) {
    .hzo-social-icons a,
    .hzo-footer-social a { transition: color 0.15s ease, background-color 0.15s ease; }
    .hzo-social-icons a:hover,
    .hzo-footer-social a:hover { transform: none; }
}
.hzo-social-icons svg,
.hzo-footer-social svg {
    width: 20px;
    height: 20px;
    display: block;
}

/* Footer social row: a touch tighter (symmetric row + column gap) and a smaller
   hover disc. The 44px tap target (WCAG comfort) is PRESERVED — only the visible
   hover circle shrinks, via a centred ::before behind the glyph, so the icons read
   cleaner without losing reach. Scoped to .hzo-footer-social; the general
   .hzo-social-icons row is unchanged. */
.hzo-footer-social { gap: var(--space-sm); }
.hzo-footer-social a { position: relative; z-index: 0; }
.hzo-footer-social a,
.hzo-footer-social a:hover,
.hzo-footer-social a:focus-visible { background-color: transparent; }
.hzo-footer-social a::before {
    content: "";
    position: absolute;
    inset: 5px;                 /* 44px box → 34px visible disc, centred */
    border-radius: 50%;
    /* Always-on "glass" disc so each icon reads as a defined chip on the dark
       footer. Translucent (not solid white) to stay subtle; hover fills to the
       accent disc below. Client-tunable via --footer-social-bg. */
    background-color: var(--footer-social-bg, rgba(255,255,255,0.12));
    z-index: -1;
    transition: background-color 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
.hzo-footer-social a:hover::before,
.hzo-footer-social a:focus-visible::before {
    background-color: var(--footer-social-bg-hover, var(--accent-hover-on-dark, rgba(255,255,255,0.24)));
}
/* A touch more breathing room for the glyph inside the disc (the disc size stays
   the same; the icon just gets a hair more padding). */
.hzo-footer-social svg { width: 18px; height: 18px; }
@media (prefers-reduced-motion: reduce) {
    .hzo-footer-social a::before { transition: background-color 0.15s ease; }
}

/* ═══════════════════════════════════════════════════════════════════════
   WAVE B — style-NEUTRAL, opt-in capability primitives (Group 4)
   Every rule below is class-scoped .hzo-*, token-driven, WCAG-AA, and gates
   all motion behind prefers-reduced-motion. New classes only — nothing here
   changes how any existing markup renders. Component-local vars carry token
   fallbacks so no main.css :root edit is needed.
   ═══════════════════════════════════════════════════════════════════════ */


/* --- spinner/spinner.css --- */
/* ─── Spinner ─────────────────────────────────────────────────────────
   Standalone async indicator (form submit, cart, lazy content). Inherits
   currentColor so it adapts to any text context — drop it in a dark section
   and the ring goes light automatically. Sizes via --sm / --lg. Shares the
   hzo-spin keyframe with the button loading state above. */
@keyframes hzo-spin { to { transform: rotate(360deg); } }
.hzo-spinner {
    display: inline-block;
    width: 20px;
    height: 20px;
    border: var(--border-2) solid color-mix(in srgb, currentColor 25%, transparent);
    border-top-color: currentColor;
    border-radius: 50%;
    animation: hzo-spin 0.6s linear infinite;
    vertical-align: -0.2em;
}
.hzo-spinner--sm { width: 14px; height: 14px; border-width: 1.5px; }
.hzo-spinner--lg { width: 32px; height: 32px; border-width: var(--border-3); }
@media (prefers-reduced-motion: reduce) {
    .hzo-spinner { animation-duration: 1.5s; }
}


/* --- stat-block/stat-block.css --- */
/* ─── Stat block (headline metric row) ──────────────────────────────────
   Aggregator sheet — the component loader enqueues ONLY <name>.css, so the
   component's own stat.css is @imported here to ride the same request (this
   is its only load path in components mode — same fix as video.css in Wave
   4; in fallback mode primitives.css @imports stat.css directly):
     stat.css   .hzo-stat — centers a number/label pair within a grid cell

   Everything else is composition, no styling of its own: the metric grid is
   the _base responsive .hzo-grid--2/--3/--4, the .hzo-stat-number /
   .hzo-stat-label typography lives in stats/stats.css (always-loaded), the
   count-up is the cross-cutting data-hzo-countup contract in stats/stats.js,
   and .hzo-reveal is the _base scroll-reveal hook. The .hzo-stat-block root
   class emitted by the shortcode is an unstyled, stable hook.

   Static content — no transitions of its own to kill under reduced motion
   (the count-up/reveal reduced-motion gating lives with their owners).
   Shortcode: stat-block.php [hzo_stat_block]. */
/* hzo-bundle: skipped @import ./stat.cssver1788182399 */


/* --- stats/stats.css --- */
/* ─── Stats ─────────────────────────────────────────────────────────── */
.hzo-stat-number {
    font-family: var(--font-number);
    font-size: clamp(2.25rem, 4vw, 3rem);
    font-weight: var(--fw-700);
    line-height: var(--lh-none);
    color: var(--brand-text);
    letter-spacing: -0.01em;
    margin: 0 0 var(--space-sm);
}
.hzo-stat-label {
    font-family: var(--font-body);
    font-size: var(--fs-100);
    font-weight: var(--fw-500);
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    line-height: 1.4;
}

/* ─── [hzo_stats] widget root (Phase-2 widget system) ──────────────────────
   The shortcode composes the always-loaded base grid (.hzo-grid --2/--3/--4);
   only the stat-item centering is needed here, SCOPED under .hzo-stats so the
   hand-authored pattern markup (stat-block, page variants — .hzo-stat without
   an .hzo-stats ancestor) is untouched. (Unscoped `.hzo-stat` centering is
   owned by components/stat-block/stat.css.)
   Wave S: reads --stat-align (default center — computed output unchanged) so a
   site-wide alignment flip is one token rather than two overrides. The
   .hzo-stat--left/--rule/--boxed modifiers carry a `.hzo-stats .hzo-stat.…`
   form specifically to beat this selector, which loads after stat.css. */
.hzo-stats .hzo-stat { text-align: var(--stat-align, center); }

/* Reduced motion: the count-up itself is JS-driven text (stats.js never
   animates under prefers-reduced-motion); kill any transition/animation on
   the widget's own elements for belt-and-suspenders. Scoped to this
   component's classes only — the .hzo-reveal utility handles its own
   reduced-motion state in _base/scroll-reveal.css. */
@media (prefers-reduced-motion: reduce) {
    .hzo-stats,
    .hzo-stat-number,
    .hzo-stat-label {
        transition: none !important;
        animation: none !important;
    }
}


/* --- steps/steps.css --- */
/* ─── Steps connectors (.hzo-steps--connected) ─────────────────────────
   Opt-in modifier for a SINGLE-ROW steps grid (compose onto
   .hzo-grid.hzo-grid--2/--3/--4 of .hzo-stat items — see blocks/steps.html).
   Draws a connector between consecutive step centers: horizontal from 768px
   (each item after the first draws back to the previous item's center;
   nothing ever paints left of the first or right of the last item), vertical
   through the stack gap below 768. The step number masks the line with
   --connector-mask (default --bg-page; set to --bg-alt on tinted sections).
   Tokens: --connector-color (default --border), --connector-style (solid),
   --connector-width (2px), --connector-offset (line height from item top).
   Static decoration — no motion to gate. Single-row use only: in a wrapped
   grid the line would connect across rows. */
/* ── ONE COMPOSITION (Wave S) ────────────────────────────────────────────
   This component ships exactly ONE composition: an evenly-spaced single-row grid of numbered items with an optional connector.

   That is a feature. One composition is why it is responsive, accessible and
   identical on two hundred sites, and why a client build can place it without
   re-deciding anything. It is also a hard limit: the component cannot express
   a SECOND composition, and making it try — nesting it, stacking modifiers on
   it, overriding its grid from a client sheet — is how a page ends up fighting
   its own components and still looking generic.

   FOR AN AMPLIFY OR ELEVATE SECTION, DO NOT BEND THIS COMPONENT. Take one of:
     • RAW mode — author the section's markup directly, or
     • hand-author to the class contract on the spatial utilities:
       .hzo-grid + .hzo-span-* / .hzo-rowspan-* / --hzo-grid-template,
       .hzo-bleed, .hzo-pull-* / .hzo-z-*, .hzo-section--edge-*, .hzo-measure-*.
   Both are the STANDARD route for a high-intensity section, not an escape
   hatch, and the composition plan is what says which sections those are.

   PLUMBING AND PROOF COMPONENTS ARE UNAFFECTED. Every JS widget and every
   proof/data component (reviews, credentials, ratings, schema) stays
   mandatory — never hand-roll those. This note is about LAYOUT only.
   ────────────────────────────────────────────────────────────────────────── */

.hzo-steps--connected > * { position: relative; }
@media (min-width: 768px) {
    .hzo-steps--connected > * + *::before {
        content: "";
        position: absolute;
        top: var(--connector-offset, 24px);
        right: 50%;
        width: calc(100% + var(--space-lg));   /* item width + the .hzo-grid gap = previous item's center */
        border-top: var(--connector-width, 2px) var(--connector-style, solid) var(--connector-color, var(--border));
        pointer-events: none;
    }
    .hzo-steps--connected .hzo-stat-number {
        position: relative;
        z-index: 1;
        display: inline-block;
        padding-inline: var(--space-md);
        background: var(--connector-mask, var(--bg-page));
    }
}
@media (max-width: 767.98px) {
    .hzo-steps--connected > * + *::before {
        content: "";
        position: absolute;
        bottom: 100%;                          /* fills the stack gap between items */
        left: 50%;
        height: var(--space-lg);
        border-left: var(--connector-width, 2px) var(--connector-style, solid) var(--connector-color, var(--border));
        pointer-events: none;
    }
}


/* --- sticky-cta/sticky-cta.css --- */
/* ─── Sticky CTA widget (mobile bottom action bar) ─────────────────────
   Aggregator sheet — the component loader enqueues ONLY <name>.css, so the
   two carved-out sheets are @imported here to ride the same request (this
   is their only load path in component mode; the legacy whole-file
   primitives.css fallback imports them directly and never loads alongside
   this file):
     sticky-bar.css          .hzo-sticky-bar — fixed bottom bar + reveal slide
     sticky-bar-product.css  .hzo-sticky-bar--product — product-page modifier
   Behavior: sticky-cta.js. Shortcode: sticky-cta.php [hzo_sticky_cta].
   @imports must precede all rules — keep them first. */
/* hzo-bundle: skipped @import ./sticky-bar.cssver1788182399 */
/* hzo-bundle: skipped @import ./sticky-bar-product.cssver1788182399 */

/* Dismiss control — OPT-IN (emitted only by [hzo_sticky_cta dismiss="true"]
   or authored by hand); attribute-free legacy markup never renders it, so
   the default path is untouched. flex 0 0 auto opts the button out of the
   bar's equal-stretch children rule; 44px square keeps the WCAG 2.5.8
   target size (same floor as back-to-top). */
.hzo-sticky-bar__close {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    border-radius: var(--radius-input);
    background: transparent;
    color: var(--text-muted);
    cursor: pointer;
}
.hzo-sticky-bar__close:hover { color: var(--text-heading); }
.hzo-sticky-bar__close svg { width: 18px; height: 18px; }

/* Reduced-motion: kill every transition this component animates (belt &
   braces on top of sticky-bar.css's own block — per the convention, the
   aggregator ends with a transition-kill). */
@media (prefers-reduced-motion: reduce) {
    .hzo-sticky-bar { transition: none; }
}


/* --- stock-status/stock-status.css --- */
/* stock-status — @tokens: --stock-status-dot-size

   An inline WooCommerce availability indicator: a small status dot + a text
   label. Non-interactive display atom — the MEANING is carried by the label
   text, never colour alone (the dot only reinforces it). Per the badge.css
   stock mapping, unavailability is a STATE not an error — red (--error) is
   reserved for genuine errors, so out-of-stock uses a neutral muted dot.

   Token-only: the dot diameter has no semantic token in the scale, so it's a
   component token with a literal fallback — the same dial pattern chip.css uses.
   Everything else reads a semantic/L1 token. */
.hzo-stock-status {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    color: var(--text-body);
    font-size: var(--fs-50);
    line-height: var(--lh-none);
}

/* The status swatch — a small circle. Inherits currentColor by default; state
   modifiers below recolour it. */
.hzo-stock-status__dot {
    flex-shrink: 0;
    width: var(--stock-status-dot-size, 8px);
    height: var(--stock-status-dot-size, 8px);
    border-radius: var(--radius-full);
    background: currentColor;
}

/* The label — the load-bearing meaning. */
.hzo-stock-status__label {
    color: inherit;
}

/* State modifiers set the DOT colour only (per badge.css's stock mapping). */
.hzo-stock-status--in-stock     .hzo-stock-status__dot { background: var(--success); }
.hzo-stock-status--low-stock    .hzo-stock-status__dot { background: var(--warning); }
.hzo-stock-status--out-of-stock .hzo-stock-status__dot { background: var(--text-muted); }
.hzo-stock-status--backorder    .hzo-stock-status__dot { background: var(--info); }


/* --- story-band/story-band.css --- */
/* ─── Story band (aggregator) ──────────────────────────────────────────
   The component loader enqueues ONLY <name>.css, so the actual styles — which
   live in media-band.css (root class .hzo-media-band; the historical filename
   is kept so the legacy primitives.css fallback @import keeps resolving) —
   are @imported here to ride the same request. This is the component's load
   path in component-loader mode. Shortcode: story-band.php [hzo_story_band].
   Zero JS.

   No trailing reduced-motion transition-kill block: media-band.css declares no
   transitions or animations of its own (the .hzo-reveal hook's motion is owned
   by _base/scroll-reveal.css, which carries its own reduced-motion guard). */
/* ── ONE COMPOSITION (Wave S) ────────────────────────────────────────────
   This component ships exactly ONE composition: a rounded photo panel with a scrimmed copy panel pinned to one side.

   That is a feature. One composition is why it is responsive, accessible and
   identical on two hundred sites, and why a client build can place it without
   re-deciding anything. It is also a hard limit: the component cannot express
   a SECOND composition, and making it try — nesting it, stacking modifiers on
   it, overriding its grid from a client sheet — is how a page ends up fighting
   its own components and still looking generic.

   FOR AN AMPLIFY OR ELEVATE SECTION, DO NOT BEND THIS COMPONENT. Take one of:
     • RAW mode — author the section's markup directly, or
     • hand-author to the class contract on the spatial utilities:
       .hzo-grid + .hzo-span-* / .hzo-rowspan-* / --hzo-grid-template,
       .hzo-bleed, .hzo-pull-* / .hzo-z-*, .hzo-section--edge-*, .hzo-measure-*.
   Both are the STANDARD route for a high-intensity section, not an escape
   hatch, and the composition plan is what says which sections those are.

   PLUMBING AND PROOF COMPONENTS ARE UNAFFECTED. Every JS widget and every
   proof/data component (reviews, credentials, ratings, schema) stays
   mandatory — never hand-roll those. This note is about LAYOUT only.
   ────────────────────────────────────────────────────────────────────────── */

/* hzo-bundle: skipped @import ./media-band.cssver1788182399 */


/* --- swatches/swatches.css --- */
/* ═════════════════════════════════════════════════════════════════════════
   VARIATION SWATCHES (W-131) — styles, scoped to .hzo-swatches*

   Progressive enhancement over WooCommerce's variations_form. swatches.js adds
   `.hzo-swatches-active` to the form (hides the native <select>s, kept as the
   source of truth + no-JS fallback) and injects one `.hzo-swatches` radiogroup
   per attribute row.

   Tokens only — no bare hex / px where a token exists, no z-index above
   --z-toast, focus ring via `outline` (not a hand-rolled box-shadow width, per
   interaction-standards §9.2) so it also survives forced-colors (§12.2).
   ═════════════════════════════════════════════════════════════════════════ */

/* When JS is active, the swatches replace the raw dropdown. Without JS the class
   is never added, so the native <select> stays fully visible + operable (G26 /
   §12.3 no-JS recovery). */
.hzo-swatches-active .variations td.value select,
.hzo-swatches-active .variations select,
.hzo-swatches-active .variations td.value .select2-container,
.hzo-swatches-active .variations .select2-container {
    display: none;
}

.hzo-swatches {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
    margin: var(--space-sm) 0;
}

/* ── The swatch (44×44 minimum target, WCAG 2.5.8 §11.2) ─────────────────── */
.hzo-swatches__swatch {
    --hzo-swatch-size: 44px;
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--hzo-swatch-size);
    min-height: var(--hzo-swatch-size);
    margin: 0;
    padding: 0;
    border: var(--border-2) solid var(--border);
    border-radius: var(--radius-control);
    background: var(--surface);
    color: var(--text-body);
    font: inherit;
    line-height: 1;
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
    transition:
        border-color var(--dur-2) var(--ease-standard),
        box-shadow var(--dur-2) var(--ease-standard),
        transform var(--dur-1) var(--ease-standard);
}

.hzo-swatches__swatch:hover {
    border-color: var(--text-muted);
}

/* One focus-ring recipe: outline (not a box-shadow width) — §9.2 + forced-colors. */
.hzo-swatches__swatch:focus-visible {
    outline: var(--border-2) solid var(--color-focus);
    outline-offset: var(--space-2xs);
}

/* Selected — branded border + inner ring (box-shadow here is state, not a focus
   ring). A forced-colors fallback below re-asserts it with an outline. */
.hzo-swatches__swatch.is-selected {
    border-color: var(--brand);
    box-shadow: inset 0 0 0 var(--border-2) var(--brand);
    transform: translateY(-1px);
}

/* ── Color chip ──────────────────────────────────────────────────────────── */
.hzo-swatches__swatch--color {
    padding: 3px;
}
.hzo-swatches__chip {
    display: block;
    width: 100%;
    height: 100%;
    min-width: 34px;
    min-height: 34px;
    border-radius: calc(var(--radius-control) - 2px);
    background-color: var(--hzo-swatch-color, var(--surface-2));
    background-size: cover;
    background-position: center;
    /* keep a hairline so white/very-light chips stay visible on white surface */
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08);
}
.hzo-swatches__chip--image {
    background-color: var(--surface-2);
}

/* ── Size / text pill ────────────────────────────────────────────────────── */
.hzo-swatches__swatch--pill {
    padding: 0 var(--space-md);
}
.hzo-swatches__text {
    display: inline-block;
    font-weight: 600;
    white-space: nowrap;
}

/* ── Out-of-stock / incompatible — struck + dimmed, still focusable ───────── */
.hzo-swatches__swatch.is-unavailable {
    cursor: not-allowed;
    opacity: 0.5;
}
.hzo-swatches__swatch.is-unavailable::before {
    content: "";
    position: absolute;
    left: 8%;
    right: 8%;
    top: 50%;
    /* border-based diagonal (survives forced-colors better than a gradient) */
    border-top: var(--border-2) solid var(--text-muted);
    transform: rotate(-45deg);
    pointer-events: none;
}
.hzo-swatches__swatch--pill.is-unavailable .hzo-swatches__text {
    text-decoration: line-through;
}
.hzo-swatches__swatch--pill.is-unavailable::before {
    display: none;   /* the line-through already reads OOS for text pills */
}

/* ── Supplemental hover/focus tip (chip swatches only) ────────────────────── */
.hzo-swatches__tip {
    position: absolute;
    bottom: calc(100% + var(--space-2xs));
    left: 50%;
    z-index: var(--z-popover);   /* below --z-toast; never covers cart feedback */
    padding: 2px var(--space-sm);
    border-radius: var(--radius-control);
    background: var(--text-heading);
    color: var(--surface);
    font-size: 0.75rem;
    font-weight: 500;
    white-space: nowrap;
    opacity: 0;
    transform: translateX(-50%) translateY(2px);
    pointer-events: none;
    transition:
        opacity var(--dur-1) var(--ease-standard),
        transform var(--dur-1) var(--ease-standard);
}
.hzo-swatches__swatch:hover .hzo-swatches__tip,
.hzo-swatches__swatch:focus-visible .hzo-swatches__tip {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}

/* ── Selected-term label beside the group ────────────────────────────────── */
.hzo-swatches__selected {
    margin-left: var(--space-sm);
    color: var(--text-heading);
    font-weight: 600;
}
.hzo-swatches__selected:empty {
    display: none;
}

/* ── Reduced motion (PRESERVE-5: ambient stops; no status motion here) ────── */
@media (prefers-reduced-motion: reduce) {
    .hzo-swatches__swatch,
    .hzo-swatches__tip {
        transition: none;
    }
    .hzo-swatches__swatch.is-selected {
        transform: none;
    }
}

/* ── Forced-colors / Windows High Contrast (§12.2) ───────────────────────── */
@media (forced-colors: active) {
    .hzo-swatches__swatch.is-selected {
        outline: var(--border-2) solid Highlight;
        outline-offset: var(--space-2xs);
    }
    .hzo-swatches__swatch.is-unavailable {
        opacity: 1;   /* opacity is unreliable in forced-colors — rely on the strike */
    }
    .hzo-swatches__swatch.is-unavailable::before {
        border-top-color: GrayText;
    }
    .hzo-swatches__chip {
        box-shadow: inset 0 0 0 1px CanvasText;
    }
}

/* ── Print (§12.1: keep swatches visible; drop chrome) ───────────────────── */
@media print {
    .hzo-swatches__tip {
        display: none;
    }
    .hzo-swatches__swatch {
        border-color: #000;
    }
}


/* --- table/table.css --- */
/* ─── Tables ───────────────────────────────────────────────────────────
   Phase-2 widget: [hzo_table] (table.php) generates this markup contract —
   headers + the per-cell data-labels the mobile stacking needs. The widget's
   opt-out skin classes (.hzo-table--ruled / .hzo-table--striped) live in the
   @imported variant sheet so the component loader (which enqueues only
   table.css) pulls them on the same request; everything below this import is
   the frozen hand-authored contract, unchanged. */
/* hzo-bundle: skipped @import ./table-ruled.cssver1788182399 */

/* G17: horizontal scroller so a wide table never overflows its container. */
.hzo-table-scroll { overflow-x: auto; overscroll-behavior-x: contain; }
.hzo-table { font-family: var(--font-body); }
.hzo-table thead th { font-family: var(--font-heading); color: var(--text-heading); }

/* Responsive table: on mobile, rows stack vertically and each <td> shows
   its column label via the data-label attribute. Requires each <td> to
   have data-label="Column Name" so the label appears next to the value.
   Desktop rendering is unchanged. */
@media (max-width: 767px) {
    .hzo-table thead { display: none; }
    .hzo-table,
    .hzo-table tbody,
    .hzo-table tr,
    .hzo-table td { display: block; width: 100%; box-sizing: border-box; }
    .hzo-table tr {
        border: var(--border-1) solid var(--border);
        border-radius: var(--radius-control);
        padding: var(--space-sm) var(--space-md);
        margin-bottom: var(--space-smd);
    }
    .hzo-table td {
        padding: 10px 0;
        border: 0;
        border-bottom: var(--border-1) solid var(--border);
        display: flex;
        justify-content: space-between;
        gap: var(--space-md);
        font-size: var(--fs-100);
    }
    .hzo-table tr td:last-child { border-bottom: 0; }
    .hzo-table td[data-label]::before {
        content: attr(data-label);
        font-weight: var(--fw-600, 600);
        color: var(--text-muted);
        flex-shrink: 0;
    }
}


/* --- tag/tag.css --- */
/* ─── Tags / chips ────────────────────────────────────────────────────
   Distinct from a badge: a tag is an INTERACTIVE category/filter chip
   (sentence case; can be a link or a removable token), where a badge is a
   STATIC status label (uppercase). <span> = static, <a> = filter link,
   <button> = toggle/removable. */
.hzo-tag {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px var(--space-smd);
    font-family: var(--font-body);
    font-size: var(--fs-75);
    font-weight: var(--fw-500, 500);
    line-height: 1.2;
    color: var(--text-body);
    background: var(--bg-alt);
    border: var(--border-1) solid var(--border);
    border-radius: var(--badge-radius, var(--radius-full));
}
/* Interactive tags (filter links / toggles) lift to a brand tint on hover.
   !important on color for the <a> form fights per-client a:visited; the
   matching :visited rule sits just below (co-located, same pattern as
   .hzo-footer-nap / .hzo-social-icons). */
a.hzo-tag,
button.hzo-tag {
    cursor: pointer;
    text-decoration: none;
    transition: background var(--transition-base), border-color var(--transition-base), color var(--transition-base);
}
a.hzo-tag { color: var(--text-body) !important; }
a.hzo-tag:visited { color: var(--text-body) !important; }
a.hzo-tag:hover,
a.hzo-tag:visited:hover,
button.hzo-tag:hover { background: var(--brand-pale); border-color: var(--brand); color: var(--brand-text) !important; }
a.hzo-tag:focus-visible,
button.hzo-tag:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
/* Selected / active filter chip. */
.hzo-tag.is-active,
.hzo-tag[aria-pressed="true"] {
    background: var(--brand-pale);
    border-color: var(--brand);
    color: var(--brand-text) !important;
}
/* Removable chip — trailing × button. Removal wiring (click ✕ → remove chip,
   move focus, fire hzo:tagremoved) lives in components/tag/tag.js; this styles
   the affordance and its states. */
.hzo-tag--removable { padding-right: 5px; }
.hzo-tag__remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    border-radius: 50%;
    cursor: pointer;
    transition: background var(--transition-base), color var(--transition-base);
}
.hzo-tag__remove:hover { background: var(--border); color: var(--text-heading); }
.hzo-tag__remove:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 1px; }
.hzo-tag__remove > svg { width: 12px; height: 12px; display: block; }
/* Container for a set of (often removable) chips. Removal + focus management is
   wired by components/tag/tag.js off the .hzo-tag__remove buttons. */
.hzo-tag-list { display: flex; flex-wrap: wrap; gap: var(--space-sm); }
.hzo-tag-list:focus { outline: none; }   /* -1 fallback focus target when the last chip is removed */


/* --- tag-group/tag-group.css --- */
/* tag-group — @tokens: (none — pure layout, all semantic)

   An inline-wrap layout container for tags / chips / badges. Pure layout
   primitive: it owns only spacing + wrapping, never the aesthetics of its
   children (each child — `.hzo-chip`, `.hzo-badge` — styles itself). No
   interactive states. Token-only: gap reads the semantic --space-* scale. */
.hzo-tag-group {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
}

/* Tight — denser packing for label clouds / inline meta rows. */
.hzo-tag-group--tight {
    gap: var(--space-xs);
}


/* --- team-grid/team-grid.css --- */
/* ─── Team / bio grid ─────────────────────────────────────────────────
   Section pattern: a multi-up grid of team/bio cards — avatar, name,
   role, short bio, optional social links. Composes always-loaded
   pieces — .hzo-card (components/card), .hzo-avatar (components/avatar),
   .hzo-social-icons (components/social-icons), .hzo-grid
   (_base/responsive-content-grid), .hzo-eyebrow / .hzo-visual-h2 /
   .hzo-meta (base type) — and owns only the centered bio-card
   alignment, split into bio-team-card.css.

   The loader enqueues ONLY <name>.css, so this file @imports the split
   sheet to give it a load path on the same request (the wave-4
   video.css lesson: a variant sheet without an @import here is dead
   CSS in components mode; before this file existed, bio-team-card.css
   never loaded at all).

   .hzo-team-grid on the [hzo_team_grid] shortcode root is a structural
   hook (unstyled by design — the pieces above carry all the visuals). */
/* hzo-bundle: skipped @import ./bio-team-card.cssver1788182399 */


/* --- toast/toast.css --- */
/* ─── Toast / snackbar ─────────────────────────────────────────────────
   Transient status messages (form success, add-to-cart, errors). NOT an
   overlay: no focus trap, no background inert — it must NEVER steal focus.
   Announced via an aria-live region (polite default; assertive for errors).
   Created + managed by hzo.toast() in toast.js. Reuses the alert status colours;
   zero new tokens. The empty region is click-through; toasts re-enable pointer
   events so their dismiss button works. */
.hzo-toast-region {
    position: fixed;
    z-index: var(--z-toast);
    right: var(--space-lg);
    bottom: var(--space-lg);
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    width: min(92vw, 380px);
    pointer-events: none;
}
/* Assertive region sits just above the polite one so they don't overlap. */
.hzo-toast-region--assertive { bottom: auto; top: var(--space-lg); }
@media (max-width: 480px) {
    .hzo-toast-region {
        left: var(--space-md);
        right: var(--space-md);
        bottom: var(--space-md);
        width: auto;
    }
    .hzo-toast-region--assertive { top: var(--space-md); bottom: auto; }
}

.hzo-toast {
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: var(--space-md);
    background: var(--surface, #fff);
    border-left: 4px solid var(--text-muted);
    border-radius: var(--radius-control);
    box-shadow: var(--shadow-lg);
    font-size: 15px;
    line-height: var(--lh-normal);
    color: var(--text-body);
    opacity: 0;
    transform: translateY(10px);
    transition: opacity var(--transition-base) ease, transform var(--transition-base) ease;
}
.hzo-toast.is-visible { opacity: 1; transform: none; }
.hzo-toast--success { border-left-color: var(--success); }
.hzo-toast--warning { border-left-color: var(--color-warning); }
.hzo-toast--error   { border-left-color: var(--error); }
.hzo-toast--info    { border-left-color: var(--color-info); }
.hzo-toast__message { flex: 1 1 auto; min-width: 0; }

/* Action slot (Undo / View cart) — a compact, focusable text action (audit G46). */
.hzo-toast__action {
    flex-shrink: 0;
    align-self: center;
    display: inline-flex;
    align-items: center;
    min-height: 44px;                /* WCAG 2.5.8 comfort tap target */
    padding: var(--space-xs) var(--space-sm);
    background: none;
    border: 0;
    border-radius: var(--radius-control);
    font: inherit;
    font-weight: 700;
    color: var(--accent-text);   /* AA text-on-light accent (never bare --accent as text) */
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}
.hzo-toast__action:hover { text-decoration: none; }
.hzo-toast__action:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 1px;
    text-decoration: none;
}

.hzo-toast__close {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    margin: -2px -2px 0 0;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    border-radius: 50%;
    cursor: pointer;
    transition: background var(--transition-base) ease, color var(--transition-base) ease;
}
.hzo-toast__close:hover { background: var(--bg-alt); color: var(--text-heading); }
.hzo-toast__close:focus-visible { outline: var(--border-2) solid var(--color-focus); outline-offset: 1px; }
.hzo-toast__close > svg { width: 14px; height: 14px; display: block; }

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


/* --- tooltip/tooltip.css --- */
/* ─── Tooltip ──────────────────────────────────────────────────────────
   Short text hint, shown on hover/focus (toggled by tooltip.js). Dark surface
   (--text-heading) + white text. Default above the trigger; --bottom flips
   below when the edge-flip positioner detects top clipping. Never interactive
   (pointer-events:none) — that's a popover. Reuses existing tokens.
   The clamp-tooltip variant sheet is @imported here so the component loader —
   which enqueues only tooltip.css — pulls it on the same request. */
/* hzo-bundle: skipped @import ./clamp-tooltip.cssver1788182399 */
.hzo-tooltip-wrap { position: relative; display: inline-flex; }
.hzo-tooltip {
    position: absolute;
    bottom: calc(100% + var(--space-sm));
    left: 50%;
    transform: translateX(-50%);
    z-index: var(--z-popover);
    width: max-content;
    max-width: 240px;
    padding: 6px 10px;
    background: var(--text-heading);
    color: var(--bg-page, #fff);   /* inverse ink: page bg on a --text-heading chip — flips with the theme */
    border-radius: var(--radius-control);
    font-size: var(--fs-75);
    line-height: 1.4;
    pointer-events: none;
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--transition-base) ease, visibility var(--transition-base);
}
.hzo-tooltip.is-open { opacity: 1; visibility: visible; }
.hzo-tooltip--bottom { bottom: auto; top: calc(100% + var(--space-sm)); }
@media (prefers-reduced-motion: reduce) { .hzo-tooltip { transition: none; } }


/* --- variation-swatch/variation-swatch.css --- */
/* variation-swatch — @tokens: --variation-swatch-size, --variation-swatch-pad-y, --variation-swatch-pad-x

   The WooCommerce variation selector styled as swatch chips. WC ships only native
   <select> dropdowns for variable-product attributes; this is the styled radio-group
   control that replaces one such dropdown. One `.hzo-variation-swatch` = one
   attribute (Colour, Size…), rendered as N × (sr-only radio + label option). The JS
   that mirrors the checked radio back into WC's hidden variation <select> is deferred
   (Track 2 wiring), so this ships CSS-only — no `.js`.

   Sibling to rating-input: same radio-backed control pattern (sr-only radios, focus
   ring rides the label, checked/disabled via `:checked +` / `:disabled +`). Unlike
   rating-input there is NO row-reverse and NO cumulative `~` fill — each option is
   an independent chip, emitted in normal ascending order, and only the CHECKED one
   lights up (`:checked +`, the adjacent label only).

   Intended markup (coordinator writes the demo):

     <span class="hzo-variation-swatch">
       <input type="radio" id="c-red" name="attr_colour" value="red" class="hzo-variation-swatch__radio">
       <label for="c-red" class="hzo-variation-swatch__option hzo-variation-swatch__option--color"
              style="--swatch-color:#c0392b">Red</label>
       …  (ascending / normal order)
     </span>

   Two option flavours share every state rule: the default TEXT pill (Size, label
   values) and the `--color` circle (colour attributes; the demo sets --swatch-color
   inline per option). The swatch box has no semantic dimension token in the scale, so
   it's a component token with a literal fallback — the icon-button.css dial pattern
   (`var(--icon-button-size, 40px)`). Everything else reads a semantic/L1 token. */
.hzo-variation-swatch {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-xs);
}

/* Radio — visually hidden but still focusable & form-submittable (sr-only). Never
   display:none — that removes it from the tab order and the submitted form.
   (copied verbatim from rating-input__radio.) */
.hzo-variation-swatch__radio {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    border: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    opacity: 0;
}

/* Option — the clickable <label>. Default = TEXT swatch (size/label pill). */
.hzo-variation-swatch__option {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--variation-swatch-size, 32px);
    min-height: var(--variation-swatch-size, 32px);
    padding: var(--variation-swatch-pad-y, 6px) var(--variation-swatch-pad-x, 12px);
    border: var(--border-1) solid var(--border);
    border-radius: var(--radius-control);
    background: var(--bg-alt);
    color: var(--text-body);
    font-size: var(--fs-75);
    line-height: var(--lh-none);
    cursor: pointer;
    transition: border-color var(--transition-base) ease,
                box-shadow var(--transition-base) ease,
                color var(--transition-base) ease;
}

/* COLOUR swatch — a circle; the demo sets --swatch-color inline per option. Text is
   hidden (font-size 0) so the colour reads clean; all selected/hover/focus rules below
   apply unchanged. */
.hzo-variation-swatch__option--color {
    width: var(--variation-swatch-size, 32px);
    height: var(--variation-swatch-size, 32px);
    min-width: var(--variation-swatch-size, 32px);
    min-height: var(--variation-swatch-size, 32px);
    padding: 0;
    border: var(--border-1) solid var(--border);
    border-radius: var(--radius-full);
    background: var(--swatch-color, var(--bg-alt));
    overflow: hidden;
    font-size: 0;
}

/* Hover — brand border cue. */
.hzo-variation-swatch__option:hover {
    border-color: var(--brand);
}

/* Selected (checked) — a firm brand ring that reads even over a coloured swatch. The
   checked radio's own adjacent label is the only one lit (`+`, no cumulative `~`). */
.hzo-variation-swatch__radio:checked + .hzo-variation-swatch__option {
    border-color: var(--brand);
    box-shadow: 0 0 0 2px var(--brand);
}

/* A11y — required. The radio is hidden, so the focus ring rides its label option. */
.hzo-variation-swatch__radio:focus-visible + .hzo-variation-swatch__option {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
}
.hzo-variation-swatch__radio:disabled + .hzo-variation-swatch__option,
.hzo-variation-swatch.is-disabled {
    opacity: var(--opacity-disabled);
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    .hzo-variation-swatch__option { transition: none; }
}


/* --- video/video.css --- */
/* ─── Video widget (lite facade + background band) ─────────────────────
   Aggregator sheet — the component loader enqueues ONLY <name>.css, so the
   two variant sheets are @imported here to ride the same request (this is
   their only load path in gated mode):
     lite-video.css       .hzo-lite-video — click-to-load facade
     video-background.css .hzo-section--video band + .hzo-video-toggle
   Behavior: video.js (lite + background subsystems only — mode="player" is
   deliberately JS-free and uses native <video controls>).
   Shortcode: video.php [hzo_video].
   @imports must precede all rules — keep them first. */
/* hzo-bundle: skipped @import ./lite-video.cssver1788182399 */
/* hzo-bundle: skipped @import ./video-background.cssver1788182399 */

/* ─── Self-hosted player frame (mode="player", PF-032) ──────────────────
   .hzo-embed sets its child <video> to object-fit: cover, which is correct for
   ambient/decorative media and wrong for a player: cropping a commercial cuts
   off content the client paid to have in frame. --player letterboxes instead
   and paints the letterbox bars a real black rather than the frame's --bg-alt,
   so the pillarbox reads as part of the video rather than as a layout gap.
   Opt back into cropping with fit="cover" on the shortcode. */
.hzo-embed--player { background: #000; }
.hzo-embed--player > video { object-fit: contain; }

/* Reduced-motion: kill every transition this component animates (belt &
   braces on top of the variant sheets' own blocks — per the convention,
   the aggregator ends with a transition-kill). */
@media (prefers-reduced-motion: reduce) {
    .hzo-lite-video > img,
    .hzo-lite-video__play,
    .hzo-video-toggle { transition: none; }
}


/* --- wishlist/wishlist.css --- */
/* wishlist — @tokens: --wishlist-size, --wishlist-padding-x, --wishlist-padding-y

   A styled save/favorite toggle: a bare <button> heart that flips between an
   empty outline (unsaved) and a filled accent heart (saved). Interactive
   sibling family to the commerce controls — it owns its OWN root + tokens, it
   does NOT borrow icon-button's or chip's dials. STYLED CONTROL ONLY: the
   toggle is cosmetic (JS flips .is-active + aria-pressed and emits an event);
   persistence / storage / server sync are intentionally out of scope.

   Token-only: the box dimension and the labeled-variant padding pair have no
   semantic token in the scale, so they are component tokens with a literal
   fallback — the same dial pattern icon-button.css / chip.css use. Everything
   else reads a semantic / L1 token. */
.hzo-wishlist {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--wishlist-size, 40px);
    height: var(--wishlist-size, 40px);
    padding: 0;
    border: var(--border-1) solid var(--border);
    border-radius: var(--radius-full);
    background: transparent;
    color: var(--text-muted);
    cursor: pointer;
    transition: color var(--transition-base) ease, border-color var(--transition-base) ease, background var(--transition-base) ease;
}

/* Icon wrapper — centers the heart glyph; the svg is drawn as an outline
   (fill:none; stroke:currentColor set in the markup) so it inherits the
   button's color. */
.hzo-wishlist__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.hzo-wishlist__icon > svg {
    display: block;
    width: 60%;
    height: 60%;
}

/* Hover — border firms up to brand, ink darkens to heading (still empty). */
.hzo-wishlist:hover {
    border-color: var(--brand);
    color: var(--text-heading);
}

/* Saved — the one state vocab (.is-active); markup carries aria-pressed="true".
   Accent ink + border, and the heart fills to a solid accent silhouette. */
.hzo-wishlist.is-active {
    color: var(--accent);
    border-color: var(--accent);
}
.hzo-wishlist.is-active .hzo-wishlist__icon > svg {
    fill: currentColor;
}

/* Labeled variant — becomes a pill with trailing text (e.g. "Save"). Width
   relaxes to the content, component-token padding + a small gap. Minimal. */
.hzo-wishlist--labeled {
    width: auto;
    gap: var(--space-2xs);
    padding: var(--wishlist-padding-y, 8px) var(--wishlist-padding-x, 12px);
    border-radius: var(--radius-pill);
}
.hzo-wishlist__label {
    font-size: var(--fs-75);
    line-height: var(--lh-none);
}

/* A11y — required. Focus ring on the button; disabled state. Reduced-motion is
   also handled globally by components/_base/a11y.css. */
.hzo-wishlist:focus-visible {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
}
.hzo-wishlist[disabled],
.hzo-wishlist[aria-disabled="true"] {
    opacity: var(--opacity-disabled);
    pointer-events: none;
}

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


/* --- zip-checker/zip-checker.css --- */
/* ─── Service-Area ZIP Checker (.hzo-zip-checker) ──────────────────────────────
   W-042 — an interactive "do you come to my town?" gate. Type a ZIP/town → an
   instant, fully client-side answer + per-region messaging, a nearest-branch
   card, a miss CTA. On a hit it reveals success content and passes the ZIP to
   the lead form. The town-list + areaServed schema are server-rendered (this
   sheet only styles the interactive shell).

   GATING: ALWAYS-LOAD (zip-checker.php un-gates itself) — it renders from a
   sticky/header CTA, a footer strip, or an ACF field the scan can't see.

   Tokens only: --brand/--accent families, --space-*, --radius-*, --shadow-*,
   --border-*, --dur-*/--ease-*, --z-* ladder (the suggest listbox rides
   --z-popover=60, below --z-toast=70). No hardcoded hex; px only for icon
   sizing / hairline borders, matching the input + button primitives. */

.hzo-zip-checker {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    margin: 0;
    max-width: 34rem;
    color: var(--text-body);
}

/* ── Field: label + controls + hint ──────────────────────────────────────── */
.hzo-zip-checker__field {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
}

.hzo-zip-checker__label {
    font-size: var(--fs-100);
    font-weight: var(--fw-600);
    color: var(--text-heading);
}

.hzo-zip-checker__controls {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    gap: var(--space-sm);
}

/* Input owns the flexible width; the combobox listbox anchors to this wrap. */
.hzo-zip-checker__inputwrap {
    position: relative;
    flex: 1 1 12rem;
    min-width: 0;
    display: block;
}

.hzo-zip-checker__input {
    width: 100%;
    box-sizing: border-box;
    padding: var(--space-sm) var(--space-md);
    font-size: var(--fs-200);
    color: var(--text-body);
    background: var(--input-bg);
    border: var(--border-1) solid var(--input-border);
    border-radius: var(--radius-input);
    transition: border-color var(--dur-1) var(--ease-standard),
                box-shadow var(--dur-1) var(--ease-standard);
}
.hzo-zip-checker__input::placeholder { color: var(--color-placeholder); }

.hzo-zip-checker__input:focus-visible {
    outline: var(--border-2) solid var(--focus-ring);
    outline-offset: 2px;
    border-color: var(--brand);
}

/* Input-validation error (malformed / non-numeric when match=zip). The MISS
   answer is NOT an error — it never sets this state. */
.hzo-zip-checker__input.is-error,
.hzo-zip-checker__input[aria-invalid="true"] {
    border-color: var(--error);
}
.hzo-zip-checker__input.is-error:focus-visible,
.hzo-zip-checker__input[aria-invalid="true"]:focus-visible {
    outline-color: var(--error);
}

/* ── Buttons (submit + geolocate) — lean on the shared control tokens ────── */
.hzo-zip-checker__submit,
.hzo-zip-checker__geo {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    /* ≥44×44 comfort target */
    min-height: 44px;
    padding: var(--space-sm) var(--space-md);
    font-size: var(--fs-100);
    font-weight: var(--btn-weight);
    line-height: var(--lh-none);
    border-radius: var(--radius-input);
    border: var(--border-1) solid transparent;
    cursor: pointer;
    transition: background-color var(--dur-1) var(--ease-standard),
                border-color var(--dur-1) var(--ease-standard),
                color var(--dur-1) var(--ease-standard);
}

.hzo-zip-checker__submit {
    background: var(--btn-primary-bg);
    color: var(--btn-primary-text);
}
.hzo-zip-checker__submit:hover { background: var(--accent-hover-on-light); }
.hzo-zip-checker__submit:active { transform: translateY(1px); }

.hzo-zip-checker__geo {
    background: transparent;
    color: var(--brand-text);
    border-color: var(--border);
}
.hzo-zip-checker__geo:hover {
    background: var(--brand-pale);
    border-color: var(--brand);
}
.hzo-zip-checker__geo:active { transform: translateY(1px); }

.hzo-zip-checker__submit:focus-visible,
.hzo-zip-checker__geo:focus-visible {
    outline: var(--border-2) solid var(--focus-ring);
    outline-offset: 2px;
}

.hzo-zip-checker__geo-icon { width: 18px; height: 18px; flex-shrink: 0; }

/* Loading (geolocate path ONLY — the check itself is synchronous, no spinner).
   The dot SLOWS under reduced-motion, never freezes (PRESERVE-5 / G13/G60). */
.hzo-zip-checker__geo.is-loading {
    color: var(--text-muted);
    cursor: progress;
}
.hzo-zip-checker__geo.is-loading .hzo-zip-checker__geo-icon {
    animation: hzo-zip-geo-pulse 1s var(--ease-in-out) infinite;
}
@keyframes hzo-zip-geo-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.4; }
}

/* ── Inline hint / input-validation message (aria-describedby target) ────── */
.hzo-zip-checker__hint {
    margin: 0;
    font-size: var(--fs-75);
    color: var(--text-muted);
}
.hzo-zip-checker__hint.is-error { color: var(--error-text); }

/* ── Result region (role=status). Hit = affirmative, miss = neutral info. ── */
.hzo-zip-checker__result {
    margin: 0;
    font-size: var(--fs-200);
    line-height: var(--lh-snug);
    min-height: 0;
}
.hzo-zip-checker__result:empty { display: none; }

.hzo-zip-checker__result-msg { display: inline; }

/* Non-color glyph carries the hit/miss meaning too (forced-colors safe). */
.hzo-zip-checker__result::before {
    speak: none;
}
.hzo-zip-checker__result.is-hit {
    color: var(--success-text);
    font-weight: var(--fw-600);
}
.hzo-zip-checker__result.is-hit::before {
    content: "\2713\00A0"; /* ✓ */
}
.hzo-zip-checker__result.is-miss {
    color: var(--text-body);
}
.hzo-zip-checker__result.is-miss::before {
    content: "\2139\00A0"; /* ℹ (info, not error) */
    color: var(--info);
}

/* The inline CTA rendered inside the result region (hit / miss action). */
.hzo-zip-checker__cta {
    display: inline-flex;
    align-items: center;
    margin-inline-start: var(--space-sm);
    padding: var(--space-xs) var(--space-smd);
    font-size: var(--fs-75);
    font-weight: var(--fw-600);
    color: var(--btn-primary-text);
    background: var(--btn-primary-bg);
    border-radius: var(--radius-input);
    text-decoration: none;
    cursor: pointer;
    border: var(--border-1) solid transparent;
    transition: background-color var(--dur-1) var(--ease-standard);
}
.hzo-zip-checker__cta:hover { background: var(--accent-hover-on-light); }
.hzo-zip-checker__cta:focus-visible {
    outline: var(--border-2) solid var(--focus-ring);
    outline-offset: 2px;
}

/* ── Reveal block (coupon / branch / booking) ─────────────────────────────── */
.hzo-zip-checker__reveal {
    padding: var(--space-md);
    background: var(--surface-2);
    border: var(--border-1) solid var(--border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-sm);
    animation: hzo-zip-reveal-in var(--dur-3) var(--ease-out);
}
.hzo-zip-checker__reveal[hidden] { display: none; }
.hzo-zip-checker__reveal:focus-visible {
    outline: var(--border-2) solid var(--focus-ring);
    outline-offset: 2px;
}
.hzo-zip-checker__reveal-title {
    margin: 0 0 var(--space-xs);
    font-size: var(--fs-300);
    font-weight: var(--fw-700);
    color: var(--text-heading);
}
.hzo-zip-checker__reveal-line { margin: 0 0 var(--space-xs); }
.hzo-zip-checker__reveal-line:last-child { margin-bottom: 0; }
.hzo-zip-checker__coupon-code {
    display: inline-block;
    padding: var(--space-xs) var(--space-smd);
    font-family: var(--font-number);
    font-size: var(--fs-400);
    letter-spacing: var(--tracking-wide);
    color: var(--brand-text);
    background: var(--brand-pale);
    border: var(--border-1) dashed var(--brand);
    border-radius: var(--radius-input);
}
.hzo-zip-checker__reveal-link {
    color: var(--brand-text);
    font-weight: var(--fw-600);
}

@keyframes hzo-zip-reveal-in {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
}

/* ── Suggest combobox listbox (APG) — rides --z-popover (below --z-toast) ── */
.hzo-zip-checker__listbox {
    position: absolute;
    z-index: var(--z-popover);
    top: calc(100% + var(--space-xs));
    left: 0;
    right: 0;
    max-height: 15rem;
    margin: 0;
    padding: var(--space-xs);
    list-style: none;
    overflow-y: auto;              /* internal scroll — never hijacks page scroll */
    overscroll-behavior: contain;
    background: var(--surface);
    border: var(--border-1) solid var(--border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-lg);
}
.hzo-zip-checker__listbox[hidden] { display: none; }

.hzo-zip-checker__option {
    display: block;
    padding: var(--space-sm) var(--space-smd);
    min-height: 44px;             /* finger-height option rows */
    box-sizing: border-box;
    border-radius: var(--radius-input);
    cursor: pointer;
    color: var(--text-body);
}
.hzo-zip-checker__option:hover,
.hzo-zip-checker__option.is-active,
.hzo-zip-checker__option[aria-selected="true"] {
    background: var(--brand-pale);
    color: var(--brand-text);
}
.hzo-zip-checker__option--empty {
    color: var(--text-muted);
    cursor: default;
}

/* ── Server-rendered town list (crawlable; visible with or without JS) ────── */
.hzo-zip-checker__towns {
    margin-top: var(--space-xs);
    font-size: var(--fs-100);
}
.hzo-zip-checker__towns-summary {
    margin: 0 0 var(--space-xs);
    font-weight: var(--fw-600);
    color: var(--text-heading);
    cursor: pointer;
}
details.hzo-zip-checker__towns > summary { list-style: revert; }
.hzo-zip-checker__town-list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs) var(--space-md);
    margin: 0;
    padding: 0;
    list-style: none;
    color: var(--text-muted);
}
.hzo-zip-checker__town { white-space: nowrap; }

/* ── Reduced motion (PRESERVE-5): stop reveal entrance + geo pulse with
   content visible; the answer text stays put. ─────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
    .hzo-zip-checker__reveal { animation: none; }
    .hzo-zip-checker__geo.is-loading .hzo-zip-checker__geo-icon { animation-duration: 2.4s; }
    .hzo-zip-checker__input,
    .hzo-zip-checker__submit,
    .hzo-zip-checker__geo,
    .hzo-zip-checker__cta { transition: none; }
    .hzo-zip-checker__submit:active,
    .hzo-zip-checker__geo:active { transform: none; }
}

/* ── Forced colors: hit/miss carry a non-color glyph + text; keep controls and
   the selected option legible under High Contrast. ───────────────────────── */
@media (forced-colors: active) {
    .hzo-zip-checker__input,
    .hzo-zip-checker__submit,
    .hzo-zip-checker__geo,
    .hzo-zip-checker__reveal,
    .hzo-zip-checker__listbox {
        border: 1px solid CanvasText;
    }
    .hzo-zip-checker__option.is-active,
    .hzo-zip-checker__option[aria-selected="true"] {
        background: Highlight;
        color: HighlightText;
    }
}

/* ── Print: server-rendered town-list + schema always print; hide the
   interactive geolocate/combobox chrome; the input prints as a plain field. ── */
@media print {
    .hzo-zip-checker__geo,
    .hzo-zip-checker__listbox { display: none !important; }
    .hzo-zip-checker__reveal[hidden] { display: none; }
}

