/* ═══════════════════════════════════════════════════════════════════════════
   HOZIO PRIMITIVE LAYER — class-scoped only

   Every rule below is scoped to a .hzo-* class. Nothing targets raw
   element selectors (h1-h6, a, p, input, etc) at top level. The file
   activates only when a template explicitly uses a .hzo-* class — the
   Global Kit preview page or any new template you build on the Hozio
   vocabulary.

   Existing client templates using .tga-* or .bs-* classes are UNAFFECTED
   because this stylesheet does not touch their selectors.

   `!important` is used only where there is a documented cascade fight:
   `:visited` from per-client main.css (link colors), form plugin
   output (form field rules), or same-file variant overrides. It is NOT
   the default — see the cluster-C A5 lock + quality-rules §1.1.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ─── 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;
}

/* ─── 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); } }

/* ─── 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; }
}

/* ─── Typography ────────────────────────────────────────────────────── */
.hzo-display {
    font-family: var(--font-heading);
    font-weight: var(--font-weight-heading, 700);
    font-size: calc(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: 1.5;
    color: var(--text-muted);
    margin: 0 0 var(--space-xl);
}
.hzo-lead {
    font-size: 1.25rem;
    line-height: 1.5;
    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: 13px;
    font-weight: var(--fw-medium, 500);
    color: var(--text-muted);
    line-height: 1.5;
}
.hzo-caption {
    font-family: var(--font-body);
    font-size: 13px;
    color: var(--text-muted);
    font-style: italic;
    line-height: 1.5;
    margin: var(--space-sm) 0 0;
}

/* ─── Dark section (class-scoped; only activates via .hzo-section--dark) ── */
.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; }

/* ─── Navigation link ───────────────────────────────────────────────── */
.hzo-nav-link {
    font-family: var(--font-body);
    font-size: 15px;
    font-weight: var(--fw-medium, 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 */
}

/* ─── Breadcrumb ────────────────────────────────────────────────────── */
.hzo-breadcrumb {
    font-family: var(--font-body);
    font-size: 13px;
    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__sep { color: var(--border); margin: 0 2px; }
.hzo-breadcrumb [aria-current="page"] { color: var(--text-body); font-weight: var(--fw-medium, 500); }

/* ─── Stats ─────────────────────────────────────────────────────────── */
.hzo-stat-number {
    font-family: var(--font-number);
    font-size: clamp(2.25rem, 4vw, 3rem);
    font-weight: var(--fw-bold, 700);
    line-height: 1;
    color: var(--brand-text);
    letter-spacing: -0.01em;
    margin: 0 0 var(--space-sm);
}
.hzo-stat-label {
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: var(--fw-medium, 500);
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    line-height: 1.4;
}

/* ─── Footer typography (for dark footer contexts) ──────────────────── */
.hzo-footer-heading {
    display: block;
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: var(--fw-bold, 700);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--footer-heading-dark, #fff);
    margin: 0 0 var(--space-lg);
}
/* !important on color: fights a:visited from per-client main.css.
   Footer-link variants below match this !important so they can beat the base. */
.hzo-footer-link {
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: var(--fw-medium, 500);
    color: var(--footer-link-dark, rgba(255,255,255,0.8)) !important;
    text-decoration: none;
    line-height: 1.5;
    padding: 8px 0;
    transition: color 0.15s;
    display: block;
    width: fit-content;  /* stack vertically, but only clickable on the text itself */
}
.hzo-footer-link:hover,
.hzo-footer-link:focus-visible { color: var(--footer-link-hover-dark, #fff) !important; }
.hzo-footer-legal {
    font-family: var(--font-body);
    font-size: 13px;
    color: var(--footer-legal-text-dark, rgba(255,255,255,0.65));
    line-height: 1.6;
}

/* NAP (Name, Address, Phone) block — for the footer contact column and any
   other spot that needs a formatted business-info listing. Each row has a
   small accent-colored icon on the left and text/link on the right. */
.hzo-footer-nap {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin: 0;
}
.hzo-footer-nap__name {
    font-family: var(--font-body);
    font-weight: var(--fw-bold, 700);
    color: var(--footer-heading-dark, #fff);
    font-size: 15px;
    margin: 0 0 4px;
}
.hzo-footer-nap__row {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: var(--fw-medium, 500);
    line-height: 1.5;
    color: var(--footer-link-dark, rgba(255,255,255,0.8));
    margin: 0;
}
/* Icons hidden by default. A client can re-show them with
   `.hzo-footer-nap__icon { display: flex; }` in their main.css. */
.hzo-footer-nap__icon {
    display: none;
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    color: var(--accent);
    margin-top: 3px;
}
/* Contact lines read like the other footer links: inherit the footer link color
   and lighten to #fff on hover. The NAP anchors carry the .hzo-footer-nap__link
   class (added in [hzo_nap], v1.1.93) — the class EXISTS purely so a per-client
   content-link rule like `a:not([class])` excludes them by contract; the styling
   here keeps the original descendant selectors, which win on normal cascade
   order within this file. No !important needed. */
.hzo-footer-nap__row a,
.hzo-footer-nap__row a:visited {
    color: inherit;
    background-image: none;
    text-decoration: none;
    transition: color 0.2s ease;
}
.hzo-footer-nap__row a:hover,
.hzo-footer-nap__row a:visited:hover,
.hzo-footer-nap__row a:focus-visible { color: var(--footer-link-hover-dark, #fff); }

/* Light footer variant — when the footer sits on a light background.
   !important on color matches the base .hzo-footer-link so the light
   variant wins the same-specificity fight against a:visited. */
.hzo-site-footer--light { background: var(--footer-bg-light, var(--bg-alt)); color: var(--footer-text-light, var(--text-body)); }
.hzo-site-footer--light .hzo-footer-heading { color: var(--footer-heading-light, var(--text-heading)); }
.hzo-site-footer--light .hzo-footer-link { color: var(--footer-link) !important; }
.hzo-site-footer--light .hzo-footer-link:hover,
.hzo-site-footer--light .hzo-footer-link:focus-visible { color: var(--footer-link-hover) !important; }
.hzo-site-footer--light .hzo-footer-legal { color: var(--footer-legal-text-light, var(--text-muted)); }
.hzo-site-footer--light a { color: var(--text-body) !important; }
/* NAP contact lines mirror the footer-link polarity on light footers too. */
.hzo-site-footer--light .hzo-footer-nap__row,
.hzo-site-footer--light .hzo-footer-nap__row a { color: var(--footer-link) !important; }
.hzo-site-footer--light .hzo-footer-nap__row a:hover,
.hzo-site-footer--light .hzo-footer-nap__row a:focus-visible { color: var(--footer-link-hover) !important; }

/* ─── 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. */
.hzo-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    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: 1px 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: 1;
    white-space: nowrap;
    background: var(--btn-primary-bg, var(--accent)) !important;
    color: var(--btn-primary-text, var(--text-on-accent)) !important;
}
.hzo-btn:hover, .hzo-btn.is-hover {
    background: var(--btn-primary-hover, var(--accent-hover-on-light)) !important;
    color: var(--btn-primary-hover-text, #fff) !important; /* bg darkens on hover → flip text light for contrast */
}
.hzo-btn:focus-visible, .hzo-btn.is-focus {
    outline: 3px solid var(--focus-ring);
    outline-offset: 2px;
}
.hzo-btn.is-active, .hzo-btn:active { transform: translateY(1px); }
.hzo-btn.is-disabled, .hzo-btn[disabled], .hzo-btn[aria-disabled="true"] {
    opacity: 0.5;
    pointer-events: none;
}

.hzo-btn--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));
}
.hzo-btn--primary:hover {
    /* Deep-accent fill on hover — accent family, darkest stop (--accent-text). */
    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; /* bg darkens on hover → flip text light for contrast */
}
.hzo-btn--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 {
    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 {
    background: transparent !important;
    color: var(--btn-outline-color, var(--brand)) !important;
    border-color: var(--btn-outline-color, var(--brand));
}
.hzo-btn--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 {
    background: transparent !important;
    color: var(--btn-ghost-color, var(--brand)) !important;
    border-color: transparent;
}
.hzo-btn--ghost:hover {
    background: var(--btn-ghost-hover, var(--brand-pale)) !important;
    color: var(--btn-ghost-color, var(--brand)) !important;
}
.hzo-btn--danger {
    background: var(--btn-danger-bg, var(--error)) !important;
    color: #fff !important;
    border-color: var(--btn-danger-bg, var(--error));
}
.hzo-btn--danger:hover {
    background: var(--btn-danger-hover, #B91C1C) !important;
    border-color: var(--btn-danger-hover, #B91C1C);
    color: #fff !important;
}

.hzo-btn--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 { 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 { 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 { padding: 0; width: 48px; min-width: 48px; }
.hzo-btn--icon.hzo-btn--sm { width: 36px; min-width: 36px; }
.hzo-btn--icon.hzo-btn--lg { width: 52px; min-width: 52px; }
.hzo-btn--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 {
    background: transparent !important;
    color: var(--brand-text) !important;
    border-color: transparent;
    min-height: 0;
    padding: 4px 0;
    border-radius: 0;
    text-decoration: underline;
    text-underline-offset: 3px;
}
.hzo-btn--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 { 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) { width: 100%; }
}

/* 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 {
    /* 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 {
    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 {
    /* 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 {
    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 {
    background: var(--btn-on-dark-accent, #fff) !important;
    color: var(--btn-on-dark-text, var(--brand)) !important;
}
.hzo-btn--on-dark.hzo-btn--ghost { color: var(--btn-on-dark-accent, #fff) !important; }
.hzo-btn--on-dark.hzo-btn--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 {
    color: transparent !important;
    pointer-events: none;
    position: relative;
}
.hzo-btn.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: 2px 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 {
    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 {
    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 { 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 (main.js) 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 {
    color: transparent !important;
    pointer-events: none;
    position: relative;
}
.hzo-btn.is-added::after {
    content: "\2713"; /* ✓ */
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 1.15em;
    font-weight: 700;
    line-height: 1;
    color: var(--text-on-accent); /* reads on the primary/accent fill */
}
.hzo-btn--secondary.is-added::after,
.hzo-btn--danger.is-added::after { color: #fff; }
.hzo-btn--outline.is-added::after,
.hzo-btn--ghost.is-added::after,
.hzo-btn--link.is-added::after { color: var(--brand); }

/* ─── 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: 2px 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: 3px; }
@media (prefers-reduced-motion: reduce) {
    .hzo-spinner { animation-duration: 1.5s; }
}

/* ─── Cards ─────────────────────────────────────────────────────────── */
.hzo-card {
    background: var(--card-bg, #fff);
    border: 1px solid var(--card-border, var(--border));
    border-radius: var(--card-radius, var(--radius-card));
    padding: var(--card-padding, var(--space-lg));
    box-shadow: var(--card-shadow, var(--shadow-sm)); /* soft rest elevation; widget Shadow control overrides */
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    /* opacity rides along so .hzo-reveal works on cards directly — this
       shorthand outranks .hzo-reveal's by source order, so without the
       opacity entry a revealed card would snap instead of fade. */
    transition: box-shadow var(--transition-base), transform var(--transition-base), opacity 0.6s ease;
}
/* Only cards that are actually clickable (contain a link) get the hover lift —
   static info cards stay flat. :has() is well-supported in current browsers. */
.hzo-card:has(a):hover { box-shadow: var(--card-hover-shadow, var(--shadow-lg)); transform: translateY(-4px); }
@media (prefers-reduced-motion: reduce) { .hzo-card:has(a):hover { transform: none; } }
/* Badges, icons, and other inline-sized elements inside cards shouldn't
   stretch to full width (default flex behavior). */
.hzo-card > .hzo-badge,
.hzo-card > .hzo-card__icon { align-self: flex-start; }
.hzo-card__icon {
    width: 56px;
    height: 56px;
    background: var(--brand-pale);
    border-radius: var(--radius-input);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--brand);
    font-size: 28px;
    line-height: 1;
    margin-bottom: var(--space-sm);
}
.hzo-card__title {
    font-family: var(--font-heading);
    font-size: 1.25rem;
    font-weight: var(--fw-bold, 700);
    color: var(--text-heading);
    margin: 0;
    letter-spacing: 0.015em;
}
.hzo-card__text {
    color: var(--text-muted);
    margin: 0;
    line-height: 1.6;
}
/* !important on color: fights a:visited from per-client main.css.
   Matching :visited rule at the bottom of this file preserves the color
   after the link has been clicked. */
.hzo-card__link {
    color: var(--accent-text) !important;
    font-weight: var(--fw-semibold, 600);
    text-decoration: none;
    align-self: flex-start;  /* inline — don't stretch to full card width */
    margin-top: auto;
    padding-top: var(--space-sm);
    transition: color 0.15s;
}
.hzo-card__link:hover { color: color-mix(in srgb, var(--accent-text) 70%, black) !important; }
/* Auto-rendered CTA arrow (bold Lucide arrow-right, masked so it inherits the
   link color). Sized 1em — same scale as the label — and floats right on hover.
   Replaces the old literal "→" glyph in the markup (cleaner + animatable). */
.hzo-card__link::after {
    content: "";
    display: inline-block;
    width: 1em;
    height: 1em;
    margin-left: 0.4em;
    vertical-align: -0.12em;
    flex-shrink: 0;
    background-color: currentColor;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E") no-repeat center / contain;
            mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E") no-repeat center / contain;
    transition: transform var(--duration-slow, 400ms) var(--ease-spring, cubic-bezier(0.34, 1.56, 0.5, 1));
}
.hzo-card__link:hover::after,
.hzo-card__link:focus-visible::after { transform: translateX(5px); }
@media (prefers-reduced-motion: reduce) {
    .hzo-card__link::after { transition: none; }
}

/* ─── 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); }
}

/* ─── Logo ─────────────────────────────────────────────────────────── */
.hzo-site-logo {
    flex-shrink: 1;
    min-width: 0;
    font-family: var(--font-heading);
    font-size: 22px;
    font-weight: var(--fw-bold, 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; }
}

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

.hzo-nav-item { position: relative; }

/* !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-semibold, 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; }

.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;
}

/* ─── 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: 2px solid var(--accent);
    border-radius: 0 0 var(--radius-input) var(--radius-input);
    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 24px;
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: var(--fw-medium, 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));
}

/* ─── Wide mega menu (opt-in via the WP menu UI — see [hzo_nav]) ──────
   Structure comes from the menu itself: 3rd-level items make column groups,
   a child with menu-item class "hzo-mega-featured" becomes a photo card,
   parent class "hzo-mega-wide" forces the wide panel for a flat list. The
   panel spans the nav bar (the parent li goes position:static so the panel
   anchors to .hzo-site-nav). --mega-cols is set inline by the shortcode. */
.hzo-site-nav { position: relative; }
.hzo-nav-item--static { position: static; }
.hzo-mega-menu--wide {
    left: 0;
    right: 0;
    transform: translateY(-8px);
    min-width: 0;
    display: grid;
    grid-template-columns: repeat(var(--mega-cols, 3), minmax(0, 1fr));
    gap: var(--space-lg);
    padding: var(--space-lg);
}
.hzo-nav-item:hover > .hzo-mega-menu--wide,
.hzo-nav-item.is-open > .hzo-mega-menu--wide { transform: translateY(0); }
.hzo-mega-menu--wide .hzo-mega-menu__link {
    padding: 10px 12px;
    border-radius: var(--radius-input);
    white-space: normal;
}
/* Column group heading — small-caps label; a link when the group page exists. */
.hzo-mega-menu__group-title {
    display: block;
    margin: 0 0 var(--space-xs);
    padding: 10px 12px 0;
    font-family: var(--font-body);
    font-size: 12px;
    font-weight: var(--fw-semibold, 600);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted) !important;
    text-decoration: none;
}
a.hzo-mega-menu__group-title:hover { color: var(--nav-link-hover) !important; }
/* Optional one-line description under a dropdown link (WP menu-item
   "Description" field). */
.hzo-mega-menu__desc {
    display: block;
    margin-top: 2px;
    font-size: 12.5px;
    font-weight: var(--fw-regular, 400);
    color: var(--text-muted);
    white-space: normal;
}
/* A lone long flat list in a wide panel flows into columns. */
.hzo-mega-menu__list--columns { columns: 2; column-gap: var(--space-lg); }
.hzo-mega-menu__list--columns li { break-inside: avoid; }

/* Featured photo card — linked page's featured image + bottom scrim + label.
   main.js crossfades the image/label to whichever [data-hzo-mega-img] link
   is hovered/focused (pointer-only enhancement; card itself stays a link). */
.hzo-mega-cta {
    position: relative;
    display: flex;
    align-items: flex-end;
    min-height: 200px;
    border-radius: var(--radius-input);
    overflow: hidden;
    padding: var(--space-md);
    text-decoration: none;
    background-image: var(--hzo-mega-bg, linear-gradient(135deg, var(--brand), var(--brand-deep)));
    background-size: cover;
    background-position: center;
    transition: opacity 150ms ease;
}
.hzo-mega-cta::before {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to top,
        color-mix(in srgb, var(--brand-deep) 85%, transparent) 0%,
        color-mix(in srgb, var(--brand-deep) 35%, transparent) 45%,
        transparent 75%);
}
.hzo-mega-cta.is-fading { opacity: 0.35; }
.hzo-mega-cta__body { position: relative; display: block; }
.hzo-mega-cta__label {
    display: block;
    font-family: var(--font-heading);
    font-size: 1.15rem;
    font-weight: var(--fw-bold, 700);
    line-height: 1.25;
    color: #fff !important;
}
.hzo-mega-cta__desc {
    display: block;
    margin-top: 2px;
    font-size: 13px;
    color: rgba(255, 255, 255, 0.85);
}
@media (prefers-reduced-motion: reduce) {
    .hzo-mega-cta { transition: none; }
}

/* ─── Desktop CTAs (right side) ─────────────────────────────────── */
.hzo-site-nav__ctas {
    display: none;
    align-items: center;
    gap: var(--space-sm);
    flex-shrink: 0;
}
@media (min-width: 1250px) { .hzo-site-nav__ctas { display: flex; } }

/* ─── 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;
}
@media (min-width: 1250px) { .hzo-mobile-trigger { display: none; } }

/* ─── 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: 16px 24px;
    border-bottom: 1px 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;
}

/* Sidebar nav */
.hzo-mobile-nav {
    flex: 1;
    padding: 4px 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-semibold, 600);
}
.hzo-mobile-menu__link:hover,
.hzo-mobile-menu__link:visited:hover { color: var(--nav-link-hover); }

/* Simple (non-accordion) row — a single link.
   !important on color: fights a:visited from per-client main.css. */
.hzo-mobile-simple { border-bottom: 1px solid var(--nav-mobile-divider-color, var(--border)); }
.hzo-mobile-simple > .hzo-mobile-menu__link {
    display: block;
    padding: 16px 24px;
    font-family: var(--font-body);
    font-weight: var(--fw-semibold, 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: 1px solid var(--nav-mobile-divider-color, var(--border)); }
.hzo-mobile-toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 16px 24px;
    border: 0;
    background: transparent;
    font-family: var(--font-body);
    font-weight: var(--fw-semibold, 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-chevron {
    flex-shrink: 0;
    display: block;
    opacity: 0.4;
    margin-left: 12px;
    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: 1px 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: 12px 24px 12px 32px;
    background: transparent;
    border: 0;
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: var(--fw-semibold, 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-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: 12px 24px 12px 32px;
    font-size: 14px;
    font-weight: var(--fw-semibold, 600);
    color: var(--text-body);
    text-decoration: none;
    border-bottom: 1px solid var(--nav-mobile-divider-color, var(--border));
}
.hzo-mobile-featured img {
    width: 56px;
    height: 56px;
    object-fit: cover;
    border-radius: var(--radius-input);
    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: 12px 24px 12px 32px;
    font-size: 14px;
    font-weight: var(--fw-medium, 500);
    color: var(--nav-mobile-link-color, var(--text-body)) !important;
    text-decoration: none;
    border-bottom: 1px 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: 24px;
    border-top: 1px 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: 14px;
    font-weight: var(--fw-medium, 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: 13px;
    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: 12px;
}
.hzo-utility-bar__links {
    display: flex;
    align-items: center;
    gap: 12px;
}
.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-bold, 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: 1px 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); }

/* ─── 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: 12px;
        color: var(--text-heading);
        text-transform: uppercase;
        letter-spacing: 0.05em;
        margin-bottom: 4px;
    }
    .hzo-section .wp-block-table td:empty {
        display: none;
    }
}

/* ─── 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: 12px;
    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: 14px;
    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: 12px;
    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: 14px;
    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); }
}

/* 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; }

/* ─── Site footer chrome ────────────────────────────────────────────── */
.hzo-site-footer { background: var(--footer-bg, transparent); color: var(--footer-text, #fff); }
.hzo-site-footer a { color: var(--footer-link-base-dark, rgba(255,255,255,0.85)); text-decoration: none; }
.hzo-site-footer a:hover { color: var(--footer-link-hover-dark, #fff); }

/* Footer column grid — single column on mobile, 4-column on tablet+.
   Brand column (first) takes 2× the width of the other columns on desktop. */
.hzo-site-footer__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--footer-gap, 32px);
}
@media (min-width: 768px) {
    .hzo-site-footer__grid {
        grid-template-columns: 2fr 1fr 1fr 1fr;
        gap: var(--footer-gap, 40px);
    }
}

/* Footer legal bar — stacks on mobile, spreads on tablet+ */
.hzo-site-footer__legal {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--footer-legal-gap, 12px);
}
@media (min-width: 768px) {
    .hzo-site-footer__legal {
        flex-direction: row;
        justify-content: space-between;
        align-items: center;
        gap: var(--footer-legal-gap, 16px);
    }
}

/* ─── 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-medium, 500);
    line-height: 1.5;
}
.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); }

/* ─── Badges ─────────────────────────────────────────────────────── */
.hzo-badge {
    display: inline-flex;
    align-items: center;
    line-height: 1;
    padding: 6px 11px;
    border-radius: var(--badge-radius, var(--radius-pill));
    font-size: 12px;
    font-weight: var(--badge-weight, var(--fw-semibold, 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); }
.hzo-badge--success   { background: var(--success); color: #fff; }
.hzo-badge--warning   { background: var(--color-warning); color: #fff; }
.hzo-badge--error     { background: var(--error); color: #fff; }
.hzo-badge--info      { background: var(--color-info); color: #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); }

/* ─── 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 12px;
    font-family: var(--font-body);
    font-size: 13px;
    font-weight: var(--fw-medium, 500);
    line-height: 1.2;
    color: var(--text-body);
    background: var(--bg-alt);
    border: 1px solid var(--border);
    border-radius: var(--badge-radius, var(--radius-pill));
}
/* 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(--focus-ring); 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 is the author's (a small
   JS handler); 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(--focus-ring); 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 the tag-removal IIFE in main.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 */

/* ─── 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
   the filters IIFE in main.js 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-semibold, 600);
    font-size: 0.9375rem; color: var(--text-heading);
}
.hzo-filter-group + .hzo-filter-group { border-top: 1px 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; }

/* ─── 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-semibold, 600);
    font-size: 1rem;
    line-height: 1;
    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; }

/* ─── 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: 1px 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: 13px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.hzo-divider--labeled::before,
.hzo-divider--labeled::after {
    content: "";
    flex: 1;
    border-top: 1px solid var(--border);
}

/* ─── Tables ───────────────────────────────────────────────────────── */
.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: 1px solid var(--border);
        border-radius: var(--radius-input);
        padding: 8px 16px;
        margin-bottom: 12px;
    }
    .hzo-table td {
        padding: 10px 0;
        border: 0;
        border-bottom: 1px solid var(--border);
        display: flex;
        justify-content: space-between;
        gap: 16px;
        font-size: 14px;
    }
    .hzo-table tr td:last-child { border-bottom: 0; }
    .hzo-table td[data-label]::before {
        content: attr(data-label);
        font-weight: var(--fw-semibold, 600);
        color: var(--text-muted);
        flex-shrink: 0;
    }
}

/* ─── Comparison table (feature-comparison variant of .hzo-table) ─────
   Plans across the top, features down the side, ✓/✗ (or text) in cells.
   Markup contract: <th scope="col"> plan headers, <th scope="row"> feature
   names, each value <td> carries data-label="<plan>" for the stacked view.
   Reuses .hzo-table's responsive stacking — the additions are desktop
   alignment, the featured-column tint, and the mobile rule that turns the
   feature-name row-header into a card title (base stacking only blockifies
   <td>, never <th>, so a comparison table would otherwise break when stacked). */
.hzo-table--compare { width: 100%; border-collapse: collapse; }
.hzo-table--compare th,
.hzo-table--compare td {
    padding: 14px 16px;
    border-bottom: 1px solid var(--border);
    text-align: center;
    vertical-align: middle;
}
.hzo-table--compare thead th { border-bottom: 2px solid var(--border); }
/* Feature-name column reads left-aligned (header + row-headers). */
.hzo-table--compare th:first-child,
.hzo-table--compare td:first-child { text-align: left; }
.hzo-table--compare tbody th[scope="row"] {
    font-family: var(--font-body);
    font-weight: var(--fw-semibold, 600);
    color: var(--text-heading);
}
/* ✓ / ✗ cell icons (mirrors the .hzo-pricing__features check). The SVGs carry
   role="img" + aria-label in markup so the state is announced to AT. */
.hzo-table--compare svg { width: 20px; height: 20px; }
.hzo-table__yes { color: var(--success); }
.hzo-table__no  { color: var(--text-muted); }
/* Recommended / featured column — tints each cell in the column (desktop) and,
   once stacked, the matching plan row in every card (mobile). No new token. */
.hzo-table__col--featured { background: var(--brand-pale); }

@media (max-width: 767px) {
    /* .hzo-table's base mobile rules already hide thead, block-ify tr/td, and
       add the data-label "<plan>: value" rows. The ONLY missing piece for a
       comparison table is the feature-name row-header (<th>) — base never
       touches <th> — so promote it to the card title. */
    .hzo-table--compare tbody th[scope="row"] {
        display: block;
        width: 100%;
        text-align: left;
        font-family: var(--font-heading);
        font-size: 1.0625rem;
        color: var(--text-heading);
        padding: 4px 0 10px;
        border-bottom: 1px solid var(--border);
        margin-bottom: 4px;
    }
}

/* ─── Accordion ─────────────────────────────────────────────────── */
.hzo-accordion { display: flex; flex-direction: column; gap: 8px; }
.hzo-accordion__item {
    background: var(--surface, #fff);
    border: 1px solid var(--border);
    border-radius: var(--radius-input);
    overflow: hidden;
}
.hzo-accordion__question {
    padding: 18px 20px;
    font-family: var(--font-heading);
    font-size: 1.0625rem;
    font-weight: var(--fw-bold, 700);
    color: var(--text-heading);
    cursor: pointer;
    list-style: none;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 16px;
}
.hzo-accordion__question::-webkit-details-marker,
.hzo-accordion__question::marker { display: none; content: none; }

.hzo-accordion__icon {
    flex-shrink: 0;
    width: 18px;
    height: 18px;
    color: var(--text-heading); /* dark enough to read on the white panel */
    transition: transform 300ms ease, color 200ms ease;
}
.hzo-accordion__item[open] > .hzo-accordion__question > .hzo-accordion__icon {
    transform: rotate(180deg);
    color: var(--brand); /* highlight the open row's caret */
}

.hzo-accordion__answer { padding: 8px 20px 20px; color: var(--text-muted); }

/* When open, tighten the question's bottom padding so the title box (and its
   hover tint) hugs the title, while the answer's own top padding gives the body
   breathing room — same overall gap, but no cramped body against the tint. */
.hzo-accordion__item[open] > .hzo-accordion__question { padding-bottom: 10px; }

/* Hover lifts ONLY the title box to the lightest brand tint. Scoped to the
   question row so the open answer-body area never tints on hover. */
.hzo-accordion__question { transition: background-color 200ms ease; }
.hzo-accordion__question:hover { background: var(--brand-pale); }
@media (prefers-reduced-motion: reduce) {
    .hzo-accordion__question { transition: none; }
}

/* ─── Tabs ─────────────────────────────────────────────────────── */
.hzo-tabs__list {
    display: flex;
    gap: 4px;
    border-bottom: 1px solid var(--border);
    overflow-x: auto;
    flex-wrap: nowrap;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
}
.hzo-tabs__list::-webkit-scrollbar { display: none; }
/* Tabs can be authored as <button> or <a>. !important on color fights
   a:visited from per-client main.css when authored as <a>. The rest
   of the rule wins on class specificity alone. */
.hzo-tabs__tab {
    appearance: none;
    -webkit-appearance: none;
    background: transparent;
    border: 0;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    outline: none;
    box-shadow: none;
    -webkit-tap-highlight-color: transparent;
    padding: 14px 24px;
    font-family: var(--font-body);
    font-size: 15px;
    font-weight: var(--fw-medium, 500);
    color: var(--text-muted) !important;
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s;
    margin-bottom: -1px;
    line-height: 1.2;
    flex-shrink: 0;
    white-space: nowrap;
}
.hzo-tabs__tab:hover { color: var(--brand-text) !important; }
.hzo-tabs__tab:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--focus-ring) 35%, transparent); border-radius: 3px; }
.hzo-tabs__tab.is-active {
    color: var(--brand-text) !important;
    border-bottom-color: var(--brand-text);
    font-weight: var(--fw-semibold, 600);
}
/* Tab panels — switched by the tabs IIFE in main.js (manual activation:
   arrow keys move focus between tabs; Enter/Space/click activates). */
.hzo-tabs__panel { padding-top: var(--space-lg); }
.hzo-tabs__panel[hidden] { display: none; }
.hzo-tabs__panel:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 4px; border-radius: 4px; }
.hzo-tabs__panel > :first-child { margin-top: 0; }
.hzo-tabs__panel > :last-child { margin-bottom: 0; }

/* ─── 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: 1.5;
    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(--focus-ring) !important;
    box-shadow: 0 0 0 2px var(--brand-pale) !important;
}

.hzo-form input::placeholder,
.hzo-form textarea::placeholder { color: #9ca3af; }

/* 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;
}

/* 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-semibold, 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(--focus-ring) !important;
}

.hzo-form input[type="checkbox"]:focus-visible,
.hzo-form input[type="radio"]:focus-visible {
    outline: none !important;
    border-color: var(--focus-ring) !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%);
}

/* ─── Switch / toggle (styled NATIVE checkbox — zero JS) ──────────────
   A toggle is just a checkbox re-skinned as a track + thumb. The native
   <input type="checkbox"> keeps ALL behavior + accessibility for free:
   Tab focus, Space toggles, checked state exposed to assistive tech — so
   NO role / aria-checked is needed (it IS a checkbox). Opt in by wrapping
   the checkbox in <label class="hzo-switch">.

   These rules sit AFTER the base checkbox rules above and at higher
   specificity (.hzo-form .hzo-switch input = 0,0,3,1 vs base 0,0,2,1), so
   they win cleanly. The thumb is drawn with ::before; the base checkbox's
   ::after checkmark is suppressed (content:none). */
.hzo-form .hzo-switch {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
    font-size: 15px;
    color: var(--text-body);
    line-height: 1.4;
}
.hzo-form .hzo-switch input[type="checkbox"] {
    appearance: none !important;
    -webkit-appearance: none !important;
    -moz-appearance: none !important;
    flex-shrink: 0;
    width: 44px !important;
    height: 24px !important;
    margin: 0 !important;
    padding: 0 !important;
    border: none !important;
    border-radius: 9999px !important;   /* geometric capsule — must stay round under square-corner archetypes */
    background: var(--border) !important;
    position: relative;
    cursor: pointer;
    transition: background 0.2s ease;
}
/* Suppress the base checkbox checkmark (drawn via ::after) — the switch uses
   ::before for its thumb instead. */
.hzo-form .hzo-switch input[type="checkbox"]::after { content: none !important; }
.hzo-form .hzo-switch input[type="checkbox"]::before {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #fff;
    box-shadow: var(--shadow-sm);
    transition: transform 0.2s ease;
}
.hzo-form .hzo-switch input[type="checkbox"]:checked { background: var(--brand) !important; }
.hzo-form .hzo-switch input[type="checkbox"]:checked::before { transform: translateX(20px); }
.hzo-form .hzo-switch input[type="checkbox"]:focus-visible {
    outline: none !important;
    box-shadow: 0 0 0 3px var(--brand-pale) !important;
}
@media (prefers-reduced-motion: reduce) {
    .hzo-form .hzo-switch input[type="checkbox"],
    .hzo-form .hzo-switch input[type="checkbox"]::before { transition: none; }
}

/* ─── Disabled fields ─────────────────────────────────────────────────
   The browser default washes disabled controls to a low-opacity gray that
   reads as "broken." Use the muted surface + muted text + not-allowed
   cursor at full opacity instead. Same-specificity-as-base + later source
   order wins the bg/color (both carry !important). */
.hzo-form input:disabled,
.hzo-form textarea:disabled,
.hzo-form select:disabled {
    background: var(--bg-alt) !important;
    color: var(--text-muted) !important;
    -webkit-text-fill-color: var(--text-muted); /* Safari keeps its own disabled text color without this */
    cursor: not-allowed;
    opacity: 1;
}
.hzo-form input:disabled::placeholder,
.hzo-form textarea:disabled::placeholder { color: #b8bec9; }
.hzo-form input[type="checkbox"]:disabled,
.hzo-form input[type="radio"]:disabled,
.hzo-form .hzo-switch input[type="checkbox"]:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* ─── Fluent Forms wrapper styling (universal, inside .hzo-form) ─────
   The .hzo-form rules above target NATIVE HTML elements
   (input/textarea/select/checkbox/radio), which already catches
   Fluent's rendered inputs because Fluent emits standard HTML inside
   its own .ff-el-* wrapper divs. This block fills the gap: styling
   for those wrapper divs (labels, help text, field-to-field spacing,
   multi-column rows, section breaks, submit button, error states).

   Source: actual Fluent Forms output sampled from studio.hoziodev.com.
   See component-patterns.md §12 for the markup spec.

   !important is retained where Fluent's own default rule sets the same
   property — same rationale as the base .hzo-form rules above. */

/* Field-to-field vertical rhythm */
.hzo-form .ff-el-group {
    margin-bottom: var(--space-lg);
}

/* Label wrapper + inner label typography */
.hzo-form .ff-el-input--label {
    margin-bottom: var(--space-sm);
}

.hzo-form .ff-el-input--label label {
    display: inline;
    font-family: var(--font-body) !important;
    font-size: 15px !important;
    font-weight: var(--fw-semibold, 600) !important;
    line-height: 1.4 !important;
    color: var(--text-body) !important;
    margin: 0 !important;
}

/* Required asterisk — Fluent emits .ff-el-is-required.asterisk-right
   on the label wrapper. We render the asterisk via ::after on the
   inner label so we control the color and weight. */
.hzo-form .ff-el-input--label.ff-el-is-required label::after {
    content: " *";
    color: var(--error);
    font-weight: var(--fw-bold, 700);
    margin-left: 2px;
}

/* Tooltip info icon — Fluent emits .ff-el-tooltip with an SVG inside
   the label wrapper, surfacing the field's help text on hover. Restyle
   the icon color + bring the cursor signal in line with our body type. */
.hzo-form .ff-el-tooltip {
    display: inline-flex !important;
    align-items: center;
    margin-left: 4px;
    color: var(--text-muted);
    cursor: help;
}
.hzo-form .ff-el-tooltip svg {
    width: 14px;
    height: 14px;
    fill: currentColor;
}

/* Help message — Fluent's equivalent to our .hzo-form__description */
.hzo-form .ff-el-help-message {
    font-size: 15px;
    color: var(--text-muted);
    line-height: 1.5;
    margin-top: var(--space-sm);
    margin-bottom: 0;
}

/* Multi-column rows — Fluent uses .ff-t-container / .ff-t-cell for
   side-by-side field pairs (name first/last, address city/state).
   Mobile collapses to single column; tablet+ shows side-by-side. */
.hzo-form .ff-t-container {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-md);
}

@media (min-width: 768px) {
    .hzo-form .ff-t-container {
        grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
    }
}

.hzo-form .ff-t-cell {
    min-width: 0; /* Prevent grid blowout from long content / overflow */
}

/* Section break — Fluent wraps in .ff-el-section-break containing an
   h3 .ff-el-section-title, optional description, and an <hr>. Replace
   Fluent's default look with a Hozio-style top-border + heading. */
.hzo-form .ff-el-section-break {
    margin: var(--space-2xl) 0 var(--space-lg) 0;
    padding-top: var(--space-lg);
    border-top: 1px solid var(--border);
}

.hzo-form .ff-el-section-title {
    font-family: var(--font-heading);
    font-size: 24px;
    font-weight: var(--fw-bold, 700);
    line-height: 1.3;
    color: var(--text-heading);
    margin: 0 0 var(--space-sm) 0;
}

.hzo-form .ff-section_break_desk {
    font-size: 16px;
    color: var(--text-body);
    line-height: 1.5;
    margin: 0;
}

.hzo-form .ff-el-section-break hr {
    display: none; /* We use the section-break's own border-top */
}

/* Checkbox + radio option container — Fluent wraps each option in
   .ff-el-form-check with a <label.ff-el-form-check-label> wrapping the
   input and a <span> with the option text. The native checkbox/radio
   inside is already styled by the .hzo-form input[type=...] rules above. */
.hzo-form .ff-el-form-check {
    display: block;
    padding: 6px 0;
}

.hzo-form .ff-el-form-check-label {
    display: inline-flex;
    align-items: flex-start;
    gap: 10px;
    font-size: 15px;
    color: var(--text-body);
    line-height: 1.5;
    cursor: pointer;
    margin: 0;
}

/* Native browser default vertical-aligns the checkbox/radio input mid-
   line, which looks fine for single-line option text but misaligns when
   the label wraps to multiple lines (e.g. the marketing consent statement
   with Privacy Policy link). Nudge the input down 2px so it sits flush
   with the cap-height of the first line of text. */
.hzo-form .ff-el-form-check-label > input[type="checkbox"],
.hzo-form .ff-el-form-check-label > input[type="radio"] {
    margin-top: 2px !important;
    flex-shrink: 0;
}

/* ─── .ff_list_buttons — radio/checkbox button rows ──────────────────
   Fluent's `ff_list_buttons` layout renders each option as a clickable
   button styled on the inner <span> (background, border, padding, font).
   Fluent's defaults produce a compact connected-button-bar look that
   already matches our design language well — the only thing we need to
   override is the SELECTED state, which Fluent paints in bright blue
   (#1a7efb) and reads identically to a CTA. Replace just the selected
   span colors with a light brand tint + accent border so selection is
   visible but less prominent than the submit button.

   Fluent marks the selected option via the `ff_item_selected` class on
   the .ff-el-form-check wrapper (managed via Fluent's own JS), which
   makes the override straightforward. */
.hzo-form .ff-el-group.ff_list_buttons .ff-el-form-check.ff_item_selected label > span,
.hzo-form .ff-el-group.ff_list_buttons .ff-el-form-check.ff_item_selected:first-child label > span,
.hzo-form .ff-el-group.ff_list_buttons .ff-el-form-check.ff_item_selected:last-child label > span {
    background-color: var(--brand-pale) !important;
    color: var(--text-body) !important;
    /* Don't override the border — keep it at Fluent's default gray so
       the connected-bar layout stays consistent (no item shifts when
       toggled). Paint the brand outline via box-shadow instead, which
       draws OVER neighbor edges + doesn't double-up on shared edges. */
    box-shadow: 0 0 0 1.5px var(--brand) !important;
    position: relative;
    z-index: 2;
}

/* Suppress Fluent's :focus-within blue (#b3d4fc) on un-selected pills.
   After a mouse click that toggles a checkbox OFF, the underlying input
   keeps focus until the user clicks elsewhere — so Fluent's focus-within
   rule paints the span blue and reads as a stuck "active" state. Force
   the span back to white when the option isn't selected, regardless of
   focus. Selected items keep their --brand-pale via the rule above
   (specificity wins via the .ff_item_selected class). */
.hzo-form .ff-el-group.ff_list_buttons .ff-el-form-check:not(.ff_item_selected) label:focus-within > span {
    background-color: #fff !important;
}

/* Mobile: full-width stacked pills.
   Fluent's mobile media query already sets `.ff-el-form-check { display:
   block; width: 100% }` and `.ff-el-form-check label > span { display:
   block; width: 100% }` — but Fluent leaves the LABEL between them as
   inline (its CSS default), so the span's `width: 100%` resolves
   against the label's content width and pills end up shrunk to text
   width with awkward right-side gaps. Forcing the label to block fills
   the cascade. Also: Fluent's mobile span rule sets `border: 1px solid`
   with NO color, falling back to currentColor — re-specify --border so
   the outline is the expected light gray, and add a vertical rhythm gap
   between stacked options. */
@media only screen and (max-width: 768px) {
    .hzo-form .ff-el-group.ff_list_buttons .ff-el-form-check {
        margin: 0 0 8px 0 !important;
    }
    .hzo-form .ff-el-group.ff_list_buttons .ff-el-form-check:last-child {
        margin-bottom: 0 !important;
    }
    .hzo-form .ff-el-group.ff_list_buttons .ff-el-form-check-label {
        display: block !important;
        width: 100% !important;
        margin: 0 !important;
    }
    .hzo-form .ff-el-group.ff_list_buttons .ff-el-form-check-label > span {
        display: block !important;
        width: 100% !important;
        border: 1px solid var(--border) !important;
        border-radius: var(--radius-input) !important;
        box-sizing: border-box;
        text-align: center;
        padding: 12px 16px !important;
    }
    /* Selected state at mobile — re-assert box-shadow outline + bg so
       the media-query specificity doesn't drop the override. */
    .hzo-form .ff-el-group.ff_list_buttons .ff-el-form-check.ff_item_selected label > span {
        background-color: var(--brand-pale) !important;
        box-shadow: 0 0 0 1.5px var(--brand) !important;
    }
}

/* Multi-select — Fluent emits .ff_has_multi_select on <select multiple>.
   Hide the chevron (only meaningful on single-select). Height left to
   render naturally — the browser sizes the box from the option count. */
.hzo-form select.ff_has_multi_select {
    background-image: none !important;
    padding-right: 16px !important;
}

/* Single-select chevron override — Fluent's .ff-default .ff-el-form-control
   (0,0,2,0) and select.ff-el-form-control:not([size]):not([multiple]) at
   (0,0,3,1) both target this element. Mine at (0,0,3,1) needs !important
   on every property to win source-order ties (Fluent's CSS loads after
   primitives.css on the engine enqueue chain). Critically: Fluent sets
   `height: var(--fluentform-input-select-height)` which collapses the
   select to a fixed height different from text inputs (which size via
   padding). `height: auto !important` restores padding-driven sizing so
   the select matches text inputs above/below. */
.hzo-form select.ff-el-form-control:not(.ff_has_multi_select) {
    appearance: none !important;
    -webkit-appearance: none !important;
    -moz-appearance: none !important;
    height: auto !important;
    padding: 12px 44px 12px 16px !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.25' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>") no-repeat right 16px center / 18px 18px !important;
    cursor: pointer;
}

/* ─── Choices.js (multi-select widget) ──────────────────────────────────
   Fluent enhances <select multiple> via Choices.js — replacing the native
   widget with its own div-based DOM (.choices > .choices__inner >
   .choices__list--multiple + .choices__input). Without these overrides,
   the wrapper renders with Choices.js defaults: .25rem radius, light gray
   border, and a nested .choices__input that draws its OWN border + bg —
   producing the "box within a box" effect. Style the wrapper to look
   exactly like our text inputs, and dissolve the inner input into the
   wrapper. */

/* Outer container — kill Choices' margin (we control via .ff-el-group) */
.hzo-form .choices {
    margin-bottom: 0 !important;
    font-size: 1rem;
}

/* The visible "field" surface — match .hzo-form input styling. Padding
   here mirrors `.hzo-form input` (12px 16px) with right-pad expanded to
   44px to clear the chevron. min-height 50px = 48px content + 2px border
   so single + multi selects vertically match the text inputs above/below. */
.hzo-form .choices .choices__inner {
    background: var(--input-bg, #fff) !important;
    border: 1px solid var(--input-border, var(--border)) !important;
    border-radius: var(--input-radius, var(--radius-input)) !important;
    padding: 12px 44px 12px 16px !important;
    min-height: 50px !important;
    font-family: var(--font-body) !important;
    font-size: 1rem !important;
    color: var(--text-body) !important;
    line-height: 1.5 !important;
    transition: border-color 0.15s, box-shadow 0.15s;
    box-sizing: border-box;
}

/* Single-select value/placeholder — Choices wraps the selected value in
   .choices__list--single > .choices__item. Strip the list's default
   inline-block padding (6px 24px 6px 12px) so .choices__inner controls
   all spacing — otherwise the value text appears double-padded. */
.hzo-form .choices[data-type*="select-one"] .choices__inner {
    padding: 12px 44px 12px 16px !important;
}

.hzo-form .choices .choices__list--single {
    padding: 0 !important;
    width: 100% !important;
    display: block !important;
}

.hzo-form .choices .choices__list--single .choices__item {
    font-family: var(--font-body) !important;
    font-size: 1rem !important;
    color: var(--text-body) !important;
    line-height: 1.5 !important;
    padding: 0 !important;
}

/* Placeholder option in single-select reads dimmer */
.hzo-form .choices .choices__list--single .choices__item--placeholder {
    color: #9ca3af !important;
    opacity: 1 !important;
}

/* Focused / open state — match our text-input focus ring */
.hzo-form .choices.is-focused .choices__inner,
.hzo-form .choices.is-open .choices__inner {
    border-color: var(--focus-ring) !important;
    box-shadow: 0 0 0 2px var(--brand-pale) !important;
}

/* When dropdown is open, Choices' default flips the wrapper border-radius
   to square at the bottom. Keep it rounded — the dropdown popup gets its
   own radius below. */
.hzo-form .choices.is-open .choices__inner {
    border-radius: var(--radius-input) !important;
}

/* The typing/search input nested inside the wrapper. Choices.js styles it
   as a separate boxed input by default (border + bg + radius) — strip
   that so it visually blends into .choices__inner. */
.hzo-form .choices .choices__input {
    background: transparent !important;
    border: none !important;
    border-radius: 0 !important;
    box-shadow: none !important;
    margin: 0 !important;
    padding: 4px 0 !important;
    font-family: var(--font-body) !important;
    font-size: 1rem !important;
    color: var(--text-body) !important;
    width: 100% !important;
    min-width: 100px;
}

.hzo-form .choices .choices__input:focus {
    outline: none !important;
    border: none !important;
    box-shadow: none !important;
}

/* Placeholder — Choices.js renders "Select all that apply" via either
   the search input's placeholder attr or a .choices__placeholder span */
.hzo-form .choices .choices__placeholder {
    opacity: 1 !important;
    color: #9ca3af !important;
}
.hzo-form .choices .choices__input::placeholder {
    color: #9ca3af !important;
    opacity: 1 !important;
}

/* Selected items in the multi-select control — chips/tags
   Match the .ff_list_buttons selected state so multi-select chips visually
   tie back to the connected-bar style (light brand fill + brand border +
   body-text color). */
.hzo-form .choices .choices__list--multiple .choices__item {
    background: var(--brand-pale) !important;
    border: 1px solid var(--brand) !important;
    border-radius: var(--radius-pill) !important;
    color: var(--text-body) !important;
    padding: 4px 12px !important;
    font-size: 14px !important;
    font-weight: var(--fw-medium, 500) !important;
    margin: 2px 4px 2px 0 !important;
    line-height: 1.4 !important;
}

/* The "x" remove-button on each chip. Choices.js paints the X as an SVG
   background-image baked white into its core CSS — invisible against our
   light --brand-pale chip background. Override the background-image with
   a brand-colored X. No left divider; tight spacing against the chip
   label. */
.hzo-form .choices .choices__list--multiple .choices__button {
    border-left: none !important;
    margin: 0 0 0 6px !important;
    padding-left: 0 !important;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23114899' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3C/svg%3E") !important;
    background-position: center !important;
    background-repeat: no-repeat !important;
    background-size: 10px 10px !important;
    opacity: 0.7 !important;
    width: 14px !important;
    height: 14px !important;
}
.hzo-form .choices .choices__list--multiple .choices__button:hover {
    opacity: 1 !important;
}

/* Dropdown popup container */
.hzo-form .choices .choices__list--dropdown,
.hzo-form .choices .choices__list[aria-expanded] {
    background: #fff !important;
    border: 1px solid var(--border) !important;
    border-top-left-radius: var(--radius-input) !important;
    border-top-right-radius: var(--radius-input) !important;
    border-bottom-left-radius: var(--radius-input) !important;
    border-bottom-right-radius: var(--radius-input) !important;
    margin-top: 4px !important;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08) !important;
    overflow: hidden;
    z-index: 100;
}

/* Items in dropdown popup */
.hzo-form .choices .choices__list--dropdown .choices__item,
.hzo-form .choices .choices__list[aria-expanded] .choices__item {
    padding: 10px 16px !important;
    font-family: var(--font-body) !important;
    font-size: 15px !important;
    color: var(--text-body) !important;
    line-height: 1.4 !important;
}

/* Hover / keyboard-highlighted item */
.hzo-form .choices .choices__list--dropdown .choices__item--selectable.is-highlighted,
.hzo-form .choices .choices__list[aria-expanded] .choices__item--selectable.is-highlighted {
    background: var(--brand-pale) !important;
    color: var(--text-body) !important;
}

/* Selected (already-picked) item in dropdown — dimmed */
.hzo-form .choices .choices__list--dropdown .choices__item--selectable[data-select-text]::after,
.hzo-form .choices .choices__list[aria-expanded] .choices__item--selectable[data-select-text]::after {
    display: none; /* Hide Choices' "Press to select" hint — feels noisy */
}

/* Custom chevron — Choices.js paints a CSS-triangle (border hack) via
   :after on the wrapper. Replace it with our V-chevron SVG to match the
   single-select. */
.hzo-form .choices[data-type*="select-multiple"]::after,
.hzo-form .choices[data-type*="select-one"]::after {
    content: "" !important;
    border: none !important;
    background: 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.25' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>") no-repeat center / 18px 18px !important;
    width: 18px !important;
    height: 18px !important;
    right: 16px !important;
    top: 50% !important;
    margin-top: -9px !important;
    pointer-events: none !important;
    transition: transform 0.15s !important;
}

/* Flip chevron when dropdown is open */
.hzo-form .choices.is-open::after {
    transform: rotate(180deg) !important;
    margin-top: -9px !important;
    border: none !important;
}

/* Submit button — Fluent emits .ff-btn.ff-btn-submit.ff-btn-md.
   Match the .hzo-btn--primary visual language exactly (accent bg, pill
   radius, heading-weight typography). Override every transform / shadow /
   transition Fluent may set on this button so the hover state is a
   single bg-color change, not a double-flash from competing defaults.

   The "double hover" bug came from Fluent's own ff-btn hover styles
   (transform: translateY + box-shadow on hover) firing at the same time
   as ours but with different transition curves — visually reading as
   two animations stacking. Explicitly negate them. */
.hzo-form .ff-btn.ff-btn-submit,
.hzo-form .ff-btn.ff-btn-submit:hover,
.hzo-form .ff-btn.ff-btn-submit:focus,
.hzo-form .ff-btn.ff-btn-submit:focus-visible,
.hzo-form .ff-btn.ff-btn-submit:active {
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: var(--btn-padding-y, 14px) var(--btn-padding-x, 32px) !important;
    font-family: var(--font-body) !important;
    font-size: 1rem !important;
    font-weight: var(--btn-weight, var(--fw-semibold, 600)) !important;
    text-decoration: none !important;
    border-radius: var(--btn-radius, var(--radius-pill)) !important;
    border: 1px solid var(--btn-primary-bg, var(--accent)) !important;
    color: var(--btn-primary-text, var(--text-on-accent)) !important;
    cursor: pointer;
    min-height: 48px;
    line-height: 1;
    white-space: nowrap;
    margin-top: var(--space-md);
    /* Nuke every property Fluent might animate on hover that could
       cause perceived double-flash (separate transition curves for
       background-color vs background-image vs box-shadow vs transform
       all firing at once). Single uniform transition on background-color
       only, fast (120ms) so any cascade-cleanup is imperceptible. */
    box-shadow: none !important;
    transform: none !important;
    text-shadow: none !important;
    filter: none !important;
    background-image: none !important;
    /* Both bg AND border animate together at identical timing — otherwise
       border-color snaps instantly (no transition) while bg fades over 120ms,
       which the eye reads as a 1px border flash followed by a separate bg
       shift = the "double-flash" the user kept reporting. */
    transition: background-color 120ms linear, border-color 120ms linear !important;
    animation: none !important;
}

.hzo-form .ff-btn.ff-btn-submit {
    background-color: var(--btn-primary-bg, var(--accent)) !important;
    opacity: 1 !important;
}

.hzo-form .ff-btn.ff-btn-submit:hover,
.hzo-form .ff-btn.ff-btn-submit:focus,
.hzo-form .ff-btn.ff-btn-submit:active {
    background-color: var(--btn-primary-hover, var(--accent-hover-on-light)) !important;
    border-color: var(--btn-primary-hover, var(--accent-hover-on-light)) !important;
    /* Negate `.ff-default .ff_btn_style:hover { opacity: .8 }` from
       fluentform-public-default.css. That rule fires INSTANTLY on hover
       (no transition declared by Fluent) while our background-color
       fades over 120ms — the eye reads the instant 80% dim as a first
       "flash," then the bg-color shift as a second separate event. Lock
       opacity at 1 so the only visible hover change is the smooth
       bg/border fade. Diagnosed via Claude-for-Chrome Animations panel
       trace 2026-05-29. */
    opacity: 1 !important;
}

.hzo-form .ff-btn.ff-btn-submit:focus-visible {
    outline: 3px solid var(--focus-ring) !important;
    outline-offset: 2px;
}

/* Mobile: submit becomes full-width by default (matches .hzo-btn behavior) */
@media (max-width: 767px) {
    .hzo-form .ff-btn.ff-btn-submit {
        display: flex !important;
        width: 100%;
    }
}

/* Fluent's screen-reader-only form title — hide visually (duplicates
   the page heading; only meaningful to AT users when there is no
   visible heading on the page). */
.hzo-form .ff_screen_reader_title {
    position: absolute !important;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
}

/* Validation error states — Fluent adds .error to inputs on validation
   failure and emits .text-danger for the error message text. Match the
   existing .hzo-form__input--error / .hzo-form__message--error rules. */
.hzo-form .ff-el-form-control.error {
    border-color: var(--error) !important;
    box-shadow: 0 0 0 2px var(--error-bg) !important;
}

.hzo-form .text-danger {
    color: var(--error);
    font-size: 15px;
    line-height: 1.4;
    margin-top: var(--space-sm);
    display: block;
}

/* ─── Real-time format validation feedback ──────────────────────────────
   Companion to the JS hook in `assets/js/main.js` (search for "real-time
   format validation feedback"). The JS toggles `.hzo-input-valid` on
   email + masked-phone inputs as the user types; this CSS paints the
   positive cue — green check icon outside the input, subtle green
   border. Negative state is unstyled — browser red outline on blur via
   HTML5 + Fluent's server-side error on submit handle that. */

.hzo-form .ff-el-input--content {
    position: relative;
}

.hzo-form input.hzo-input-valid {
    border-color: #2ecc71 !important;
    padding-right: 44px !important;
}

.hzo-form .ff-el-input--content:has(input.hzo-input-valid)::after {
    content: '';
    position: absolute;
    right: 14px;
    top: 50%;
    transform: translateY(-50%);
    width: 20px;
    height: 20px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232ecc71' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: center;
    pointer-events: none;
}

/* ─── Flatpickr (date/time picker popup) ────────────────────────────────
   Fluent Forms' `input_date` field renders a regular text input with the
   `.ff-el-datepicker` class. On focus, Fluent enqueues the Flatpickr
   library which paints a popup calendar (`.flatpickr-calendar`) anchored
   to the input. The closed-state input is already styled by our base
   `.hzo-form input[type="text"]` rules — this block targets the OPEN
   popup so the calendar matches our design language instead of
   Flatpickr's stock blue-on-white look.

   Library reference: github.com/flatpickr/flatpickr (class catalog
   extracted from src/style/flatpickr.styl). Loaded conditionally — these
   rules are inert on pages without a date field. */

/* Popup container — sits outside .hzo-form in the DOM (Flatpickr
   appends it to <body>), so selectors here are unscoped. Use specific
   class names to avoid bleed. */
.flatpickr-calendar {
    background: #fff !important;
    border: 1px solid var(--border) !important;
    border-radius: var(--radius-input) !important;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.10) !important;
    font-family: var(--font-body) !important;
    color: var(--text-body) !important;
    /* IMPORTANT: no padding here — Fluent's CSS sets `.fluentform * {
       box-sizing: border-box }`, so any padding on .flatpickr-calendar
       eats into the content area. Flatpickr's JS sets the calendar's
       inline width to 307.875px (set to fit its dayContainer at
       307.875px). Padding + border-box = content area shrinks to
       291.875px → dayContainer overflows by 16px → Saturday column
       clipped. Box-shadow + border-radius give the popup its visual
       chrome without padding. */
    box-sizing: content-box !important;
}

/* Month/year nav row — explicit min-height so the absolutely-positioned
   .flatpickr-current-month inside it has enough vertical content area
   to render its 38px-tall children (month <select> + year wrapper) without
   clipping. Without this min-height, Flatpickr's row content area is
   ~35px (padding 8px × 2 = 16px overhead) — too small to fit 38px boxes,
   so the boxes overflow vertically and the calendar looks broken. */
.flatpickr-months {
    background: transparent !important;
    border-bottom: 1px solid var(--border-light, var(--border)) !important;
    padding: 8px 4px !important;
    margin-bottom: 8px !important;
    min-height: 54px !important;
    box-sizing: border-box !important;
}

/* `.flatpickr-month` (singular) is the inner row that holds the prev
   arrow + the current-month container + the next arrow. Flatpickr's
   default is `height: 34px` + `overflow: hidden`. The engine bumped
   the .numInputWrapper + .flatpickr-monthDropdown-months children to
   38px tall to match the form's other dropdown chrome — but the 34px
   parent with overflow:hidden shears 4px off the bottom of the
   children, including the year input's bottom border. Raise to 38px
   to match. Diagnosed via Claude-for-Chrome DevTools session
   2026-05-29. */
.flatpickr-month {
    height: 38px !important;
}

.flatpickr-current-month {
    font-family: var(--font-body) !important;
    font-size: 14px !important;
    font-weight: var(--fw-bold, 700) !important;
    color: var(--text-heading) !important;
    /* Flex layout for predictable horizontal spacing between month
       dropdown + year input + prev/next arrows. Generous gap so
       elements have breathing room. `height: auto` overrides Flatpickr's
       core 28px height default; absolute top:0/bottom:0 still stretches
       it to fill the parent's content area, which we sized to 38px above. */
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    gap: 8px !important;
    padding: 0 8px !important;
    height: auto !important;
}

/* Month <select> + year input — Flatpickr emits a real <select> for the
   month and a numeric <input type="number"> wrapper for the year. Both
   must match the form's other dropdowns visually AND must visually align
   (same height + same bottom-edge Y).

   IMPORTANT — selector specificity:
   The base form rules `.hzo-form select` (0,0,1,1) and `.hzo-form
   input[type="number"]` (0,0,2,1) both ship `!important` declarations
   (border, padding, height). A flat `.flatpickr-monthDropdown-months`
   (0,0,1,0) loses the cascade despite `!important`, because the base
   rule's element selector tips specificity. Scope the Flatpickr rules
   under `.hzo-form` to add one class layer + reach (0,0,2,0) for the
   select and (0,0,3,0) for the year input — both win the cascade. */
.hzo-form .flatpickr-monthDropdown-months {
    font-family: var(--font-body) !important;
    font-weight: var(--fw-semibold, 600) !important;
    font-size: 14px !important;
    line-height: 1.2 !important;
    color: var(--text-heading) !important;
    appearance: none !important;
    -webkit-appearance: none !important;
    -moz-appearance: none !important;
    background-color: #fff !important;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231b1f23' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E") !important;
    background-repeat: no-repeat !important;
    background-position: right 8px center !important;
    background-size: 14px !important;
    border: 1px solid var(--border) !important;
    border-radius: var(--radius-input) !important;
    /* Explicit height + matching padding so month select bottom-aligns with
       year wrapper. Both elements: 38px box, identical inner content area. */
    height: 38px !important;
    padding: 0 30px 0 12px !important;
    margin: 0 !important;
    box-sizing: border-box !important;
    width: auto !important;
    min-width: 0 !important;
    cursor: pointer !important;
    transition: border-color 150ms ease, box-shadow 150ms ease !important;
}
.hzo-form .flatpickr-monthDropdown-months:hover {
    border-color: var(--brand) !important;
}
.hzo-form .flatpickr-monthDropdown-months:focus {
    border-color: var(--brand) !important;
    outline: none !important;
    box-shadow: 0 0 0 2px var(--brand-pale) !important;
}

/* Year input wrapper — the visible "box" the user sees for the year. All
   border/background chrome lives here; the inner <input> is transparent
   and unbordered so there are no competing visible borders. Wrapper has
   an explicit width (76px) so it visually balances the month dropdown,
   and an explicit height (38px) to bottom-align with the month select.
   `overflow: hidden` clips Flatpickr's absolutely-positioned arrow spans
   inside the rounded border. */
.hzo-form .flatpickr-current-month .numInputWrapper {
    background-color: #fff !important;
    border: 1px solid var(--border) !important;
    border-radius: var(--radius-input) !important;
    padding: 0 !important;
    margin: 0 !important;
    width: 76px !important;
    height: 38px !important;
    overflow: hidden !important;
    box-sizing: border-box !important;
    position: relative !important;
    transition: border-color 150ms ease, box-shadow 150ms ease !important;
}
.hzo-form .flatpickr-current-month .numInputWrapper:hover {
    border-color: var(--brand) !important;
}
.hzo-form .flatpickr-current-month .numInputWrapper:focus-within {
    border-color: var(--brand) !important;
    box-shadow: 0 0 0 2px var(--brand-pale) !important;
}
/* Year <input> — fills wrapper exactly; no visible border of its own (the
   `.hzo-form input[type="number"]` base rule paints one at 1px solid var
   (--border) with !important, so we negate it at higher specificity here).
   Right padding leaves room for the spinner arrows pinned to the wrapper
   right edge. */
.hzo-form .flatpickr-current-month .numInput {
    font-family: var(--font-body) !important;
    font-weight: var(--fw-semibold, 600) !important;
    font-size: 14px !important;
    line-height: 1.2 !important;
    color: var(--text-heading) !important;
    background: transparent !important;
    border: 0 !important;
    border-radius: 0 !important;
    padding: 0 22px 0 10px !important;
    margin: 0 !important;
    width: 100% !important;
    height: 100% !important;
    box-shadow: none !important;
    box-sizing: border-box !important;
    -moz-appearance: textfield !important;
    appearance: textfield !important;
}
.hzo-form .flatpickr-current-month .numInput::-webkit-outer-spin-button,
.hzo-form .flatpickr-current-month .numInput::-webkit-inner-spin-button {
    -webkit-appearance: none !important;
    margin: 0 !important;
}
.hzo-form .flatpickr-current-month .numInput:focus {
    outline: none !important;
    box-shadow: none !important;
}
/* Spinner arrows — keep inside wrapper, divide its 38px height in half */
.hzo-form .flatpickr-current-month .numInputWrapper .arrowUp,
.hzo-form .flatpickr-current-month .numInputWrapper .arrowDown {
    height: 19px !important;
    border: 0 !important;
}

/* Prev/next arrow buttons */
.flatpickr-prev-month,
.flatpickr-next-month {
    color: var(--text-body) !important;
    fill: var(--text-body) !important;
    padding: 8px !important;
    border-radius: var(--radius-input) !important;
    transition: background var(--transition-base), color var(--transition-base) !important;
}
.flatpickr-prev-month:hover,
.flatpickr-next-month:hover {
    background: var(--brand-pale) !important;
    color: var(--accent-text) !important;
    fill: var(--accent-text) !important;
}
.flatpickr-prev-month svg,
.flatpickr-next-month svg {
    fill: inherit !important;
}

/* Weekday header row (Su Mo Tu We Th Fr Sa) */
.flatpickr-weekdaycontainer .flatpickr-weekday {
    color: var(--text-muted) !important;
    font-family: var(--font-body) !important;
    font-weight: var(--fw-semibold, 600) !important;
    font-size: 13px !important;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Day cells */
.flatpickr-day {
    color: var(--text-body) !important;
    font-family: var(--font-body) !important;
    font-size: 14px !important;
    border-radius: var(--radius-input) !important;
    border: 1px solid transparent !important;
    transition: background var(--transition-base), border-color var(--transition-base), color var(--transition-base) !important;
}
.flatpickr-day:hover {
    background: var(--brand-pale) !important;
    color: var(--text-body) !important;
    border-color: var(--brand-pale) !important;
}
.flatpickr-day.today {
    border-color: var(--accent) !important;
    color: var(--accent-text) !important;
    background: #fff !important;
}
.flatpickr-day.today:hover {
    background: var(--brand-pale) !important;
}
.flatpickr-day.selected,
.flatpickr-day.startRange,
.flatpickr-day.endRange,
.flatpickr-day.selected.inRange,
.flatpickr-day.startRange.inRange,
.flatpickr-day.endRange.inRange,
.flatpickr-day.selected:hover {
    background: var(--accent) !important;
    border-color: var(--accent) !important;
    color: var(--text-on-accent) !important;
}
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay {
    color: var(--text-muted) !important;
    opacity: 0.5;
}

/* Time picker section — shown when `is_time_enabled: true` */
.flatpickr-time {
    border-top: 1px solid var(--border-light, var(--border)) !important;
    padding-top: 8px !important;
    margin-top: 8px !important;
}
.flatpickr-time input,
.flatpickr-time .flatpickr-am-pm {
    color: var(--text-body) !important;
    font-family: var(--font-body) !important;
    font-size: 14px !important;
}

/* Calendar icon on the date-picker input — signals to users that clicking
   opens a calendar (without it the field looks like any other text input).
   Inline SVG (no extra HTTP request). Padding-right on the input prevents
   the icon from overlapping the user's typed text. */
.hzo-form input.ff-el-datepicker {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'/%3E%3Cline x1='16' y1='2' x2='16' y2='6'/%3E%3Cline x1='8' y1='2' x2='8' y2='6'/%3E%3Cline x1='3' y1='10' x2='21' y2='10'/%3E%3C/svg%3E") !important;
    background-repeat: no-repeat !important;
    background-position: right 14px center !important;
    background-size: 18px 18px !important;
    padding-right: 44px !important;
    cursor: pointer;
}

/* Flatpickr wrapper — in static:true mode Flatpickr wraps the input
   in a .flatpickr-wrapper that defaults to `display: inline-block`,
   which shrinks both the wrapper AND the input inside it to content
   width instead of filling the form column. Force the wrapper to
   block + 100% width so the date input sits at the same full-width
   as every other input in the form. */
.hzo-form .flatpickr-wrapper {
    display: block !important;
    width: 100% !important;
    position: relative !important;
}

/* Calendar popup — let it take its natural width (~308px, set by
   Flatpickr's dayContainer to fit 7 × 39px day cells). Don't clamp
   width; don't hide overflow. The static-mode positioning places it
   under the input, and the form column is wide enough to contain
   the popup at its natural size. */

/* ─── Textarea — disable horizontal resize ──────────────────────────────
   User can drag the bottom-right corner to grow vertically (helpful for
   long messages), but horizontal resize is locked. Prevents users from
   dragging the textarea narrow + visually breaking the form layout. */
.hzo-form textarea,
.hzo-form textarea.ff-el-form-control {
    resize: vertical !important;
}
.flatpickr-time input:hover,
.flatpickr-time .flatpickr-am-pm:hover,
.flatpickr-time input:focus,
.flatpickr-time .flatpickr-am-pm:focus {
    background: var(--brand-pale) !important;
}

/* ═══════════════════════════════════════════════════════════════════════
   MARKETING BLOCK PRIMITIVES (component library Group 3, Wave A)
   Layout / composition primitives that the blocks/ patterns compose. All
   token-driven + class-scoped; the light surfaces inherit the default text
   colors (no overrides needed).
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── 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%);
}

/* ─── 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); }
}

/* ─── 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); }

/* ─── Hero ────────────────────────────────────────────────────────────
   Top-of-page composition; compose inside a .hzo-section (which supplies the
   vertical rhythm). These add the layout + the actions row. */
.hzo-hero__actions { display: flex; gap: var(--space-md); flex-wrap: wrap; margin-top: var(--space-lg); }
.hzo-hero--centered { text-align: center; }
.hzo-hero--centered .hzo-display,
.hzo-hero--centered .hzo-subtitle { margin-left: auto; margin-right: auto; }
.hzo-hero--centered .hzo-hero__actions { justify-content: center; }
/* Split — copy + media side by side on desktop, stacked on mobile. */
.hzo-hero--split { display: grid; grid-template-columns: 1fr; gap: var(--space-xl); align-items: center; }
@media (min-width: 1024px) { .hzo-hero--split { grid-template-columns: 1fr 1fr; gap: var(--space-3xl); } }
.hzo-hero__media img { width: 100%; height: auto; display: block; border-radius: var(--radius-card); }
/* Image-background — author sets background-image inline (per-page photo). A
   brand-deep scrim keeps the light text readable over any image. */
.hzo-hero--image {
    position: relative;
    background-size: cover;
    background-position: center;
    color: #fff;
}
.hzo-hero--image::before {
    content: "";
    position: absolute;
    inset: 0;
    background: color-mix(in srgb, var(--brand-deep) 60%, transparent);
}
.hzo-hero--image > * { position: relative; } /* lift content above the scrim */
.hzo-hero--image .hzo-display,
.hzo-hero--image .hzo-eyebrow { color: var(--heading-color-on-dark, #fff) !important; }
.hzo-hero--image .hzo-subtitle { color: rgba(255, 255, 255, 0.9); }

/* ─── Image / overlay band ────────────────────────────────────────────
   Generalizes the hero --image treatment to ANY .hzo-section: a full-bleed
   background photo + brand-deep scrim, with light text (like --dark). Set the
   section's background-image inline; the scrim keeps text readable over any
   photo. Tune scrim strength with --hzo-section-scrim (default 60%) — e.g.
   style="--hzo-section-scrim:40%" for a lighter wash. */
.hzo-section--image {
    position: relative;
    background-size: cover;
    background-position: center;
    color: #fff;
}
.hzo-section--image::before {
    content: "";
    position: absolute;
    inset: 0;
    background: color-mix(in srgb, var(--brand-deep) var(--hzo-section-scrim, 60%), transparent);
}
.hzo-section--image > * { position: relative; } /* lift content above the scrim */
.hzo-section--image h1, .hzo-section--image h2, .hzo-section--image h3,
.hzo-section--image h4, .hzo-section--image h5, .hzo-section--image h6,
.hzo-section--image .hzo-eyebrow, .hzo-section--image .hzo-display { color: var(--heading-color-on-dark, #fff) !important; }
.hzo-section--image p, .hzo-section--image .hzo-subtitle { color: rgba(255, 255, 255, 0.9); }
.hzo-section--image a:not(.hzo-btn) { color: #fff; }

/* ─── 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). */
.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);
    }
}

/* ─── 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))));
    }
}

/* ─── Tile collage — connected category grid ──────────────────────────
   Rows of image tiles separated by hairline gaps; radius clips only on the
   four OUTER corners so the grid reads as one connected slab. Each tile is
   a link: photo + bottom scrim + label + CTA line, photo zoom on hover.
   Compose rows with different tile counts (a 3-up row over a 4-up row gives
   the offset-masonry feel). Mobile: rows collapse to a 2-col grid of squares
   with per-tile radius. Markup: blocks/category-tiles.html. */
.hzo-tile-collage {
    display: flex;
    flex-direction: column;
    gap: var(--tile-gap, 6px);
    border-radius: var(--tile-radius, var(--radius-card));
    overflow: hidden;   /* clips inner corners → outer-only radius */
}
.hzo-tile-row {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;   /* column count = number of tiles in the row */
    gap: var(--tile-gap, 6px);
}
.hzo-tile {
    position: relative;
    display: block;
    aspect-ratio: var(--tile-aspect, 5 / 4.5);
    overflow: hidden;
    text-decoration: none;
    color: #fff;
}
.hzo-tile__img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform var(--duration-slow, 400ms) var(--ease-standard, ease);
}
.hzo-tile:hover .hzo-tile__img,
.hzo-tile:focus-visible .hzo-tile__img { transform: scale(1.05); }
/* Bottom scrim — pins label readability over any photo. */
.hzo-tile::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to top,
        color-mix(in srgb, var(--scrim-color, var(--brand-deep)) 88%, transparent) 0%,
        color-mix(in srgb, var(--scrim-color, var(--brand-deep)) 45%, transparent) 38%,
        transparent 70%);
}
.hzo-tile__content {
    position: absolute;
    inset: auto 0 0 0;
    padding: var(--space-md) var(--space-lg);
    z-index: 1;   /* above the scrim */
}
.hzo-tile__label {
    font-family: var(--font-heading);
    font-size: 1.35rem;
    font-weight: var(--fw-bold, 700);
    line-height: 1.2;
    margin: 0;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}
.hzo-tile__cta {
    display: inline-block;
    font-size: 14px;
    font-weight: var(--fw-semibold, 600);
    margin-top: var(--space-xs);
    opacity: 0.9;
}
.hzo-tile:hover .hzo-tile__cta,
.hzo-tile:focus-visible .hzo-tile__cta { text-decoration: underline; }
.hzo-tile:focus-visible { outline: 2px solid #fff; outline-offset: -3px; }
@media (max-width: 767.98px) {
    .hzo-tile-collage { border-radius: 0; overflow: visible; }
    .hzo-tile-row { grid-auto-flow: row; grid-template-columns: 1fr 1fr; grid-auto-columns: unset; }
    .hzo-tile { aspect-ratio: 1 / 1; border-radius: var(--tile-radius, var(--radius-card)); }
}
@media (prefers-reduced-motion: reduce) {
    .hzo-tile__img { transition: none; }
    .hzo-tile:hover .hzo-tile__img,
    .hzo-tile:focus-visible .hzo-tile__img { transform: none; }
}

/* ─── 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: 12px;
    font-weight: var(--fw-bold, 700);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    line-height: 1;
    text-align: center;
    padding: 6px 40px;
    box-shadow: var(--shadow-sm);
    pointer-events: none;
}
.hzo-ribbon--tl { right: auto; left: -34px; transform: rotate(-45deg); }

/* ─── Section dividers — wave + mosaic ────────────────────────────────
   Decorative seams between sections; both are zero-JS. Place a divider
   BETWEEN two <section>s: its fill color should match the NEXT section's
   background so the shape reads as that section's top edge.

   Wave: inline SVG (fill="currentColor") drifting slowly sideways.
     .hzo-wave-divider          color = --wave-fill (default --bg-alt)
     .hzo-wave-divider--flip    mirror vertically (use above a section end)
     .hzo-wave-divider--static  no drift even with motion allowed
   Drift is ambient background motion → always disabled under reduced motion
   (do NOT copy the old client build's opt-out). Markup: blocks/section-divider-wave.html.

   Mosaic: two thin rows of "tiles" from pure repeating-gradients — free
   texture, no images, auto-themes from --brand (override --mosaic-c1..c4).
   Markup: blocks/section-divider-mosaic.html. */
.hzo-wave-divider {
    position: relative;
    overflow: hidden;
    height: var(--wave-height, 70px);
    color: var(--wave-fill, var(--bg-alt));
    margin-bottom: -1px;   /* kill the sub-pixel seam against the next section */
}
.hzo-wave-divider svg {
    position: absolute;
    left: 0;
    bottom: 0;
    width: 200%;
    height: 100%;
    display: block;
    animation: hzo-wave-drift var(--wave-duration, 12s) ease-in-out infinite alternate;
}
.hzo-wave-divider--flip { transform: scaleY(-1); margin-bottom: 0; margin-top: -1px; }
.hzo-wave-divider--static svg { animation: none; }
@media (min-width: 768px) {
    .hzo-wave-divider { height: var(--wave-height-lg, 100px); }
}
@keyframes hzo-wave-drift {
    from { transform: translateX(0); }
    to   { transform: translateX(-25%); }
}
@media (prefers-reduced-motion: reduce) {
    .hzo-wave-divider svg { animation: none; }
}

.hzo-mosaic-divider {
    /* Private per-instance palette: overridable via --mosaic-c1..c4, defaults
       derive from the brand so the band auto-themes on every client. */
    --_mc1: var(--mosaic-c1, color-mix(in srgb, var(--brand) 25%, white));
    --_mc2: var(--mosaic-c2, color-mix(in srgb, var(--brand) 45%, white));
    --_mc3: var(--mosaic-c3, color-mix(in srgb, var(--brand) 65%, white));
    --_mc4: var(--mosaic-c4, color-mix(in srgb, var(--brand) 35%, white));
    background: var(--mosaic-bg, var(--brand-pale));
    padding: var(--space-md) 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.hzo-mosaic-divider__row {
    height: 26px;
    background-image: repeating-linear-gradient(to right,
        var(--_mc1) 0,    var(--_mc1) 26px,  transparent 26px,  transparent 30px,
        var(--_mc2) 30px, var(--_mc2) 56px,  transparent 56px,  transparent 60px,
        var(--_mc3) 60px, var(--_mc3) 86px,  transparent 86px,  transparent 90px,
        var(--_mc4) 90px, var(--_mc4) 116px, transparent 116px, transparent 120px);
}
.hzo-mosaic-divider__row--offset { background-position: 60px 0; }

/* ─── Before / after compare slider ───────────────────────────────────
   Two stacked images; drag the handle (or use the keyboard — the control is a
   native range input, so keyboard + click + drag all work for free) to wipe
   between "before" and "after". Built for transformations (yards, remodels,
   restorations). main.js maps the range value to --hzo-compare-pos.
   Markup: blocks/compare.html. */
.hzo-compare {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-card);
    aspect-ratio: 16 / 9;
    --hzo-compare-pos: 50%;
    user-select: none;
}
.hzo-compare__img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    -webkit-user-drag: none;
}
/* The "before" image sits on top, clipped from the right to --hzo-compare-pos,
   so the left portion shows "before" and the right reveals the "after" beneath. */
.hzo-compare__img--before {
    clip-path: inset(0 calc(100% - var(--hzo-compare-pos)) 0 0);
}
.hzo-compare__divider {
    position: absolute;
    top: 0;
    bottom: 0;
    left: var(--hzo-compare-pos);
    width: 2px;
    margin-left: -1px;
    background: #fff;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15);
    pointer-events: none;
}
.hzo-compare__divider::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 42px;
    height: 42px;
    transform: translate(-50%, -50%);
    border: 2px solid #fff;
    border-radius: 9999px;   /* geometric circle — must stay round under square-corner archetypes */
    background: color-mix(in srgb, var(--brand) 80%, transparent);
    box-shadow: var(--shadow-md);
}
/* The accessible control: a real range input overlaying the whole image. */
.hzo-compare__range {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: ew-resize;
}
.hzo-compare__range:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 3px; }
.hzo-compare__label {
    position: absolute;
    bottom: var(--space-md);
    padding: 4px 12px;
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--brand-deep) 72%, transparent);
    color: #fff;
    font-size: 0.8125rem;
    font-weight: var(--fw-medium, 500);
    pointer-events: none;
}
.hzo-compare__label--before { left: var(--space-md); }
.hzo-compare__label--after  { right: var(--space-md); }

/* ─── Stat ────────────────────────────────────────────────────────────
   Centers a .hzo-stat-number + .hzo-stat-label pair within a grid cell. */
.hzo-stat { text-align: center; }

/* ─── Logo / brand strip ──────────────────────────────────────────────
   Centered, wrapping row of partner/brand logos. Muted + monochrome at rest,
   full color on hover (honors prefers-reduced-motion). */
.hzo-logo-strip {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--space-xl);
}
.hzo-logo-strip img {
    height: 36px;
    width: auto;
    opacity: 0.6;
    filter: grayscale(1);
    transition: opacity var(--transition-base), filter var(--transition-base);
}
.hzo-logo-strip a:hover img,
.hzo-logo-strip img:hover { opacity: 1; filter: grayscale(0); }
@media (prefers-reduced-motion: reduce) {
    .hzo-logo-strip img { transition: none; }
}

/* ─── Marquee — auto-drifting horizontal strip ────────────────────────
   Infinite sideways drift for logo rows, badges, or card strips. The root is
   a REAL scroller (overflow-x) so touch users can always swipe it; main.js
   ([data-hzo-marquee]) clones the item set for the seamless wrap and drives
   the drift, pausing on hover/focus/touch/offscreen. Reduced motion → no
   clones, no drift: a plain swipeable row. Edge fades via mask-image;
   --marquee-fade sets their width (24px on mobile via the media query).
   Speed is set in markup: data-hzo-marquee-speed="<px per second>".
   --logos modifier applies the logo-strip treatment (muted grayscale,
   color on hover) to images inside the track. */
.hzo-marquee {
    overflow-x: auto;
    scrollbar-width: none;   /* Firefox */
    cursor: grab;
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--marquee-fade, 64px), #000 calc(100% - var(--marquee-fade, 64px)), transparent 100%);
            mask-image: linear-gradient(to right, transparent 0, #000 var(--marquee-fade, 64px), #000 calc(100% - var(--marquee-fade, 64px)), transparent 100%);
}
.hzo-marquee::-webkit-scrollbar { display: none; }
.hzo-marquee:active { cursor: grabbing; }
.hzo-marquee__track {
    display: flex;
    align-items: center;
    gap: var(--marquee-gap, var(--space-xl));
    width: max-content;
    padding: var(--space-xs) 0;   /* room for focus rings inside the scroller */
}
.hzo-marquee__track > * { flex-shrink: 0; }
.hzo-marquee--logos img {
    height: 36px;
    width: auto;
    opacity: 0.6;
    filter: grayscale(1);
    transition: opacity var(--transition-base), filter var(--transition-base);
}
.hzo-marquee--logos a:hover img,
.hzo-marquee--logos img:hover { opacity: 1; filter: grayscale(0); }
@media (max-width: 767.98px) {
    .hzo-marquee { --marquee-fade: 24px; }
}
@media (prefers-reduced-motion: reduce) {
    .hzo-marquee--logos img { transition: none; }
    /* Reduced motion = no drift (the JS module exits): the strip sits at
       scrollLeft 0, so a left-edge fade would permanently dim the first
       logo. Keep only the right fade as the "more content" hint. */
    .hzo-marquee {
        -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - var(--marquee-fade, 64px)), transparent 100%);
                mask-image: linear-gradient(to right, #000 0, #000 calc(100% - var(--marquee-fade, 64px)), transparent 100%);
    }
}

/* ═══════════════════════════════════════════════════════════════════════
   CONTENT BLOCK PRIMITIVES (component library Group 3, Wave B)
   Composition helpers for testimonial / bio / pricing / post-card / feature
   rows. Token-driven, class-scoped, no new tokens.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── Testimonial author row ──────────────────────────────────────────
   Avatar + name/role under a quote. The quote uses .hzo-pullquote (single
   featured) or plain card text (multi-up cards). */
.hzo-testimonial__author { display: flex; align-items: center; gap: var(--space-md); margin-top: var(--space-md); }
.hzo-testimonial__name { font-family: var(--font-body); font-weight: var(--fw-semibold, 600); color: var(--text-heading); line-height: 1.3; }
.hzo-testimonial__role { font-family: var(--font-body); font-size: 14px; color: var(--text-muted); line-height: 1.3; }

/* ─── Bio / team card ─────────────────────────────────────────────────
   Centered card content (avatar on top). Composes .hzo-card + .hzo-avatar;
   name reuses .hzo-card__title, role reuses .hzo-meta. */
.hzo-bio { text-align: center; align-items: center; }
.hzo-bio .hzo-social-icons,
.hzo-bio .hzo-footer-social { justify-content: center; }

/* ─── Pricing ─────────────────────────────────────────────────────────
   Composes .hzo-card + .hzo-btn; the price reuses .hzo-stat-number. Only the
   feature checklist + the featured-tier emphasis are new. */
.hzo-pricing__features { list-style: none; margin: var(--space-md) 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-sm); }
.hzo-pricing__features li { display: flex; align-items: flex-start; gap: var(--space-sm); color: var(--text-body); line-height: 1.5; }
.hzo-pricing__features li > svg { flex-shrink: 0; width: 18px; height: 18px; color: var(--accent-text); margin-top: 2px; }
/* Featured tier — brand emphasis via border + lift (no new tokens). Scoped as
   .hzo-card.hzo-pricing--featured (0,0,2,0) so it outranks .hzo-card's border
   (0,0,1,0) regardless of source order — not a source-position tiebreak. */
.hzo-card.hzo-pricing--featured { border-color: var(--brand); box-shadow: var(--card-elevated-shadow, var(--shadow-md)); }

/* ─── Card media (post / blog cards) ──────────────────────────────────
   Full-bleed image at the top of a .hzo-card — negative margins cancel the
   card's padding so the image meets the inner edges; top corners stay rounded. */
.hzo-card__media {
    margin: calc(-1 * var(--card-padding, var(--space-lg))) calc(-1 * var(--card-padding, var(--space-lg))) 0;
    border-radius: var(--card-radius, var(--radius-card)) var(--card-radius, var(--radius-card)) 0 0;
    overflow: hidden;
    aspect-ratio: 16 / 9;
}
.hzo-card__media img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform var(--duration-slow, 400ms) var(--ease-standard, ease); }
/* Alternate media ratios (default is 16/9 above). */
.hzo-card__media--square { aspect-ratio: 1 / 1; }
.hzo-card__media--wide   { aspect-ratio: 21 / 9; }
/* Subtle image zoom on hover for clickable cards (reduced-motion-safe). */
.hzo-card:has(a):hover .hzo-card__media img { transform: scale(1.05); }
@media (prefers-reduced-motion: reduce) {
    .hzo-card:has(a):hover .hzo-card__media img { transform: none; }
}

/* ─── Card variants (extend .hzo-card; compose its subparts) ──────────
   Depth choice per art-direction §5 "flat-with-intent": --bordered = flat +
   border (the system default, made explicit); --elevated = shadow, no border.
   Zero new tokens — bordered uses --border, elevated uses --shadow-md. */
.hzo-card--bordered { box-shadow: none; border-color: var(--border); }
.hzo-card--elevated { box-shadow: var(--card-elevated-shadow, var(--shadow-md)); border-color: transparent; }

/* Body wrapper — groups title/text/CTA, mainly for the horizontal layout. */
.hzo-card__body { display: flex; flex-direction: column; gap: var(--space-sm); min-width: 0; }

/* Horizontal card — media beside body; stacks to vertical at the mobile
   breakpoint (767px, the system's mobile boundary). Reuses __media/__body. */
.hzo-card--horizontal { flex-direction: row; align-items: stretch; gap: var(--space-lg); }
.hzo-card--horizontal > .hzo-card__media {
    flex: 0 0 40%;
    aspect-ratio: auto;          /* fill the card height instead of forcing 16/9 */
    margin: calc(-1 * var(--card-padding, var(--space-lg))) 0 calc(-1 * var(--card-padding, var(--space-lg))) calc(-1 * var(--card-padding, var(--space-lg)));
    border-radius: var(--card-radius, var(--radius-card)) 0 0 var(--card-radius, var(--radius-card));
}
.hzo-card--horizontal > .hzo-card__body { flex: 1 1 auto; }
@media (max-width: 767px) {
    .hzo-card--horizontal { flex-direction: column; gap: var(--space-md); }
    .hzo-card--horizontal > .hzo-card__media {
        flex-basis: auto;
        aspect-ratio: 16 / 9;
        margin: calc(-1 * var(--card-padding, var(--space-lg))) calc(-1 * var(--card-padding, var(--space-lg))) 0;
        border-radius: var(--card-radius, var(--radius-card)) var(--card-radius, var(--radius-card)) 0 0;
    }
}

/* Link card — the WHOLE card is one clickable target (accessible stretched-link:
   a real anchor on the title, ::after covers the card). The card must contain
   exactly ONE interactive element (the title link). Hover lift comes from the
   base .hzo-card:has(a):hover rule; the focus ring shows for keyboard only. */
.hzo-card--link { position: relative; }
.hzo-card--link .hzo-card__title a { color: inherit; text-decoration: none; }
.hzo-card--link .hzo-card__title a::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
.hzo-card--link:has(.hzo-card__title a:focus-visible) { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* ─── 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: 1;
}
.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; }

/* ─── Feature split — alternating image/copy content rows ─────────────
   Distinct from .hzo-hero--split: a repeatable content row. --reverse swaps
   the column order on desktop for alternating rhythm. Stacks on mobile. */
.hzo-feature { display: grid; grid-template-columns: 1fr; gap: var(--space-xl); align-items: center; }
@media (min-width: 1024px) {
    .hzo-feature { grid-template-columns: 1fr 1fr; gap: var(--space-3xl); }
    .hzo-feature--reverse .hzo-feature__media { order: 2; }
}
.hzo-feature__media img { width: 100%; height: auto; display: block; border-radius: var(--radius-card); }

/* ─── 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-pill);
    margin-bottom: var(--space-sm);
}
.hzo-empty__icon svg { width: 32px; height: 32px; }
.hzo-empty__text { color: var(--text-muted); line-height: 1.5; 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); }

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

/* ─── Rating (static stars) ───────────────────────────────────────────
   Read-only star row for trust / review contexts. Filled stars reuse
   --color-warning (the engine's amber — there is no dedicated star token);
   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. */
.hzo-rating { display: inline-flex; align-items: center; gap: 2px; }
.hzo-rating svg { width: 18px; height: 18px; flex-shrink: 0; }
.hzo-rating__star { color: var(--color-warning); }
.hzo-rating__star--empty { color: var(--border); }
.hzo-rating__count { margin-left: var(--space-sm); font-size: 14px; color: var(--text-muted); }

/* ─── 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 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);
}
.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(--focus-ring); 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; }

/* ─── Category card (a .hzo-card variant) ─────────────────────────────
   Shop-by-category / locations-index tile: media on top, centered name +
   count. Usually a whole-card link — the <a> form gets its own hover lift
   (the base .hzo-card :has(a) lift doesn't apply when the card IS the link). */
.hzo-cat-card { text-align: center; align-items: center; }
.hzo-cat-card .hzo-card__title { margin: 0; }
.hzo-cat-card__count { font-size: 14px; color: var(--text-muted); margin: 0; }
a.hzo-cat-card,
a.hzo-cat-card:visited { text-decoration: none; color: inherit; }
a.hzo-cat-card:hover { box-shadow: var(--card-hover-shadow, var(--shadow-md)); }
a.hzo-cat-card:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* ─── Gallery (static grid; lightbox is Wave E / JS) ──────────────────
   Responsive image grid (2/3/4-up), tiles cropped square via aspect-ratio,
   tighter gap than the content .hzo-grid. No lightbox yet — wrap tiles in
   <a href> to the full image until the JS lightbox ships. */
.hzo-gallery { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-sm); }
@media (min-width: 768px)  { .hzo-gallery { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1024px) { .hzo-gallery { grid-template-columns: repeat(4, 1fr); } }
.hzo-gallery__item { display: block; overflow: hidden; border-radius: var(--radius-input); aspect-ratio: 1 / 1; background: var(--bg-alt); }
.hzo-gallery__item img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform var(--duration-slow, 400ms) var(--ease-standard, ease); }
a.hzo-gallery__item:hover img { transform: scale(1.05); }
@media (prefers-reduced-motion: reduce) {
    .hzo-gallery__item img { transition: none; }
    a.hzo-gallery__item:hover img { transform: none; }
}

/* ─── 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 the modal
   IIFE in main.js (built on hzo.trapFocus). 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-card);
    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-input);
    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__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);
}

/* Lock background scroll while any overlay (modal/drawer) is open — set by the
   shared overlay lock in main.js (hzo.lockOverlayBackground). */
body.hzo-overlay-open { overflow: hidden; }

/* 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-card) var(--radius-card) 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; }
}

/* ─── 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 IIFE in
   main.js, built on hzo.trapFocus) — 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: 1px solid var(--border);
    flex-shrink: 0;
}
.hzo-drawer__title {
    margin: 0;
    font-family: var(--font-heading);
    font-size: 20px;
    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-input);
    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__body {
    flex: 1 1 auto;
    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: 1px solid var(--border);
}

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

/* ─── 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 (in main.js). 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-input);
    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: 14px; 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; }
.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-input);
}
.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: 16px;
    line-height: 1;
    cursor: pointer;
}
.hzo-minicart__step:hover { color: var(--brand-text); }
.hzo-minicart__step:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.hzo-minicart__count { min-width: 30px; text-align: center; font-size: 14px; 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: 14px; 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(--focus-ring); 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: 18px; 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; } }

/* ─── 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 main.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-input);
    box-shadow: var(--shadow-lg);
    font-size: 15px;
    line-height: 1.5;
    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; }
.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: 2px solid var(--focus-ring); outline-offset: 1px; }
.hzo-toast__close > svg { width: 14px; height: 14px; display: block; }

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

/* ─── Dropdown / menu ──────────────────────────────────────────────────
   Menu-button popup (account menu, nav dropdown, row actions). A transient
   popup, NOT an overlay — no focus trap, no background inert. The menu-button
   IIFE in main.js manages aria-expanded, roving arrow-key focus, Esc +
   click-outside close, and focus return. Markup contract: component-patterns
   §35. Anchored to the trigger; --right aligns the menu to the trigger's right
   edge. Reuses existing tokens; zero new tokens. */
.hzo-dropdown { position: relative; display: inline-block; }
.hzo-dropdown__menu {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: var(--z-popover);
    min-width: 200px;
    margin: 0;
    padding: 6px;
    list-style: none;
    background: var(--surface, #fff);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-lg);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-4px);
    transition: opacity var(--transition-base) ease, transform var(--transition-base) ease, visibility var(--transition-base);
}
.hzo-dropdown__menu--right { left: auto; right: 0; }
.hzo-dropdown.is-open .hzo-dropdown__menu { opacity: 1; visibility: visible; transform: none; }

.hzo-dropdown__item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 9px 12px;
    border: 0;
    border-radius: var(--radius-input);
    background: transparent;
    font-family: var(--font-body);
    font-size: 14px;
    line-height: 1.3;
    color: var(--text-body) !important;   /* fights a:visited when authored as <a> */
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    transition: background var(--transition-base) ease, color var(--transition-base) ease;
}
.hzo-dropdown__item:hover,
.hzo-dropdown__item:focus {
    background: var(--bg-alt);
    color: var(--text-heading) !important;
    outline: none;
}
.hzo-dropdown__item:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.hzo-dropdown__item[aria-disabled="true"] { color: var(--text-muted) !important; cursor: default; }
.hzo-dropdown__item[aria-disabled="true"]:hover { background: transparent; }
.hzo-dropdown__sep { height: 1px; margin: 6px 4px; background: var(--border); }

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

/* ─── Tooltip ──────────────────────────────────────────────────────────
   Short text hint, shown on hover/focus (toggled by main.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. */
.hzo-tooltip-wrap { position: relative; display: inline-flex; }
.hzo-tooltip {
    position: absolute;
    bottom: calc(100% + 8px);
    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-input);
    font-size: 13px;
    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% + 8px); }
@media (prefers-reduced-motion: reduce) { .hzo-tooltip { transition: none; } }

/* ─── Popover ──────────────────────────────────────────────────────────
   A richer dropdown surface (interactive content allowed). Anchored below the
   trigger by default; --right right-aligns; --flip places it above when the
   edge-flip positioner detects bottom clipping. Managed by main.js (open/close,
   Esc, click-outside, focus return). Reuses existing tokens. */
.hzo-popover { position: relative; display: inline-block; }
.hzo-popover__panel {
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    z-index: var(--z-popover);
    min-width: 220px;
    max-width: 320px;
    padding: var(--space-md);
    background: var(--surface, #fff);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-lg);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-4px);
    transition: opacity var(--transition-base) ease, transform var(--transition-base) ease, visibility var(--transition-base);
}
.hzo-popover__panel--right { left: auto; right: 0; }
.hzo-popover.is-open .hzo-popover__panel { opacity: 1; visibility: visible; transform: none; }
.hzo-popover__panel--flip { top: auto; bottom: calc(100% + 8px); }
@media (prefers-reduced-motion: reduce) { .hzo-popover__panel { transition: none; } }

/* ─── Top banner / promo bar ───────────────────────────────────────────
   Full-width announcement strip. Reuses brand/accent/section-surface tokens.
   Dismissed in-session via [data-hzo-banner-dismiss] (main.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: 14px; 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-pill);
    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; } }

/* ─── 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. */
.hzo-carousel { position: relative; }
.hzo-carousel__viewport { overflow: hidden; touch-action: pan-y; }  /* swipe horiz, keep vertical page scroll */
.hzo-carousel__track {
    display: flex;
    transition: transform var(--transition-base) ease;
}
.hzo-carousel__slide { flex: 0 0 100%; 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-pill);
    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(--focus-ring); 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 12px;
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--surface, #fff);
    color: var(--text-body);
    font-size: 13px;
    cursor: pointer;
}
.hzo-carousel__toggle:focus-visible { outline: 2px solid var(--focus-ring); 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: 4px; 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(--focus-ring); 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; }
}

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

/* ─── Carousel crossfade mode ─────────────────────────────────────────
   .hzo-carousel--fade: slides stack in one grid cell and fade instead of
   sliding — the right feel for full-bleed hero carousels and testimonial
   decks. The tallest slide sizes the deck (grid stacking — no JS height
   sync). main.js toggles .is-active per slide; nav/dots/autoplay/inert
   logic is shared with the slide mode. */
.hzo-carousel--fade .hzo-carousel__track {
    display: grid;
    transform: none !important;   /* fade mode never translates */
    transition: none;
}
.hzo-carousel--fade .hzo-carousel__slide {
    grid-area: 1 / 1;
    opacity: 0;
    /* visibility rides along so inactive slides (and their CTAs) are also
       removed from hit-testing + tab order when JS never runs (no inert
       without JS). It interpolates discretely: the outgoing slide stays
       visible through the crossfade, then hides at transition end. */
    visibility: hidden;
    transition: opacity var(--carousel-fade-duration, 500ms) ease, visibility var(--carousel-fade-duration, 500ms);
}
.hzo-carousel--fade .hzo-carousel__slide.is-active { opacity: 1; visibility: visible; }
@media (prefers-reduced-motion: reduce) {
    .hzo-carousel--fade .hzo-carousel__slide { transition: none; }
}

/* ─── Hero carousel composition ───────────────────────────────────────
   Wrapper for a fade-mode carousel of full-bleed image heroes
   (blocks/hero-carousel.html). Overlays the dots on the image and re-tints
   them for a dark surface. */
.hzo-hero-carousel .hzo-carousel__dots {
    position: absolute;
    left: 0;
    right: 0;
    bottom: var(--space-md);
    margin: 0;
    z-index: 2;
    --carousel-dot-color: var(--carousel-dot-on-image, rgba(255, 255, 255, 0.45));
    --carousel-dot-active-color: var(--carousel-dot-active-on-image, #fff);
}
.hzo-hero-carousel .hzo-carousel__toggle {
    position: absolute;
    right: var(--space-md);
    bottom: var(--space-md);
    margin: 0;
    z-index: 2;
    color: #fff;
    background: rgba(0, 0, 0, 0.35);
    border-radius: var(--radius-pill);
}

/* ─── Gallery lightbox ─────────────────────────────────────────────────
   Full-screen image viewer launched from a .hzo-gallery[data-hzo-lightbox].
   Built + managed by the lightbox IIFE in main.js (portaled to <body>): focus
   trap (via the shared trap stack, so it nests over a modal), Esc / backdrop /
   close-button dismiss, arrow-key + prev/next navigation, focus return to the
   clicked thumbnail. Sits at --z-popover so it layers above a modal it opens
   from. Reuses existing tokens; zero new tokens. Markup contract: §39. */
.hzo-lightbox {
    position: fixed;
    inset: 0;
    z-index: var(--z-popover);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-xl);
    background: rgba(0, 0, 0, 0.85);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--transition-base) ease, visibility var(--transition-base);
}
.hzo-lightbox.is-open { opacity: 1; visibility: visible; }
.hzo-lightbox__figure {
    margin: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-sm);
    max-width: 100%;
    max-height: 100%;
}
.hzo-lightbox__img {
    max-width: 100%;
    max-height: 80vh;
    object-fit: contain;
    border-radius: var(--radius-input);
    background: var(--surface, #fff);   /* backing for transparent images — follows the surface remap */
}
.hzo-lightbox__caption { color: #fff; font-size: 14px; line-height: 1.5; text-align: center; max-width: 60ch; }
.hzo-lightbox__close,
.hzo-lightbox__nav {
    position: absolute;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border: 0;
    border-radius: var(--radius-pill);
    background: rgba(255, 255, 255, 0.12);
    color: #fff;
    cursor: pointer;
    transition: background var(--transition-base) ease;
}
.hzo-lightbox__close:hover,
.hzo-lightbox__nav:hover { background: rgba(255, 255, 255, 0.25); }
.hzo-lightbox__close:focus-visible,
.hzo-lightbox__nav:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.hzo-lightbox__close { top: 16px; right: 16px; }
.hzo-lightbox__nav { top: 50%; transform: translateY(-50%); }
.hzo-lightbox__nav--prev { left: 16px; }
.hzo-lightbox__nav--next { right: 16px; }
.hzo-lightbox__close > svg,
.hzo-lightbox__nav > svg { width: 24px; height: 24px; display: block; }

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

/* ─── 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(--focus-ring); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .hzo-skip-link { transition: none; } }

/* ─── 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(--focus-ring);
    box-shadow: 0 0 0 2px var(--brand-pale);
    position: relative;
    z-index: 1;
}
.hzo-input-group > input::placeholder { color: #9ca3af; }
.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; }
}

/* ─── :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. */

/* ─── 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: 16px;
    flex-wrap: wrap;
    margin-top: 12px;
}
/* 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;
    width: 36px;
    height: 36px;
    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;
}

/* ═══════════════════════════════════════════════════════════════════════
   STYLE ARCHETYPES — structural rules (body class from helpers/archetype.php)
   Token values live in the generated tokens.archetype.css (:root). These
   rules add what tokens can't express — and they set PROPERTIES only,
   never re-declare tokens (a body-scoped custom property would beat the
   design widget's :root overrides by inheritance proximity).
   classic-professional has no rules: the engine defaults ARE it.
   ═══════════════════════════════════════════════════════════════════════ */

/* Bold-editorial — confident, typographic, high-contrast. */
.hzo-archetype-bold-editorial .hzo-btn {
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-size: 0.9375rem;
}
.hzo-archetype-bold-editorial .hzo-section--dark:not(.hzo-section--dark-glow) {
    background: linear-gradient(160deg,
        var(--bg-dark-section) 0%,
        color-mix(in srgb, var(--bg-dark-section) 55%, var(--brand-deep)) 100%);
}

/* Warm-craft — soft, layered, welcoming. */
.hzo-archetype-warm-craft .hzo-card { border-color: transparent; }
.hzo-archetype-warm-craft .hzo-section--dark:not(.hzo-section--dark-glow) {
    background: linear-gradient(180deg,
        var(--bg-dark-section) 0%,
        color-mix(in srgb, var(--bg-dark-section) 70%, var(--brand-deep)) 100%);
}
/* Desktop only: below 768px the side scrims flatten to the WCAG-floor wash
   (min(strength*1.25, 90%)) — multiplying that by 0.92 would drop the
   guaranteed minimum, so the softening never applies on mobile. */
@media (min-width: 768px) {
    .hzo-archetype-warm-craft .hzo-hero--image::before,
    .hzo-archetype-warm-craft .hzo-section--image::before {
        /* warm archetypes soften the scrim edge with a touch less strength */
        opacity: 0.92;
    }
}

/* Sharp-tech — precise, flat, engineered. */
.hzo-archetype-sharp-tech .hzo-btn { letter-spacing: 0.02em; }
.hzo-archetype-sharp-tech .hzo-card { border-color: color-mix(in srgb, var(--border) 70%, var(--text-muted)); }
.hzo-archetype-sharp-tech .hzo-card:has(a):hover { transform: translateY(-2px); }
.hzo-archetype-sharp-tech .hzo-eyebrow { font-weight: var(--fw-bold, 700); }

/* Midnight — dark-site identity (a brand choice per client, not a toggle).
   Token values live in the generated tokens.archetype.css. The rules below
   exist for ONE reason: --brand-text / --accent-text are derived for contrast
   on WHITE by tokens.derived.css (which loads after the archetype tokens), so
   the recipe cannot re-point them — and body-scoped token re-declarations are
   forbidden (see section banner). Instead each rule assigns the color PROPERTY
   to the pipeline's existing dark-safe siblings --brand-hover-on-dark /
   --accent-hover-on-dark. The :where(:not(…)) guards are zero-specificity and
   skip contexts that already carry their own on-dark colors (header/menus/
   footer/utility/banner chrome + dark/image/video sections + .hzo-surface--dark,
   which remaps the -text tokens itself). */

/* Base content links: --brand-text is on-white ink — swap for the lightened
   brand. (Base a:hover already uses --brand-hover-on-dark; unchanged.) The
   :visited variant out-specifies main.css's later-loading `a:visited` (0,1,1).
   Guard also skips anchors that ARE chrome (.hzo-tile image tiles, the
   .hzo-skip-link brand pill) and contexts with own muted/inherit link ink
   (breadcrumbs, doc TOC, linked-card titles, social icon rows). */
.hzo-archetype-midnight a:where(:not(:is(.hzo-site-header, .hzo-mobile-menu, .hzo-site-footer, .hzo-utility-bar, .hzo-banner, .hzo-breadcrumb, .hzo-doc-toc, .hzo-card--link, .hzo-social-icons, .hzo-footer-social, .hzo-section--dark, .hzo-hero--image, .hzo-section--image, .hzo-section--video, .hzo-media-band, .hzo-surface--dark) a, .hzo-tile, .hzo-skip-link)),
.hzo-archetype-midnight a:visited:where(:not(:is(.hzo-site-header, .hzo-mobile-menu, .hzo-site-footer, .hzo-utility-bar, .hzo-banner, .hzo-breadcrumb, .hzo-doc-toc, .hzo-card--link, .hzo-social-icons, .hzo-footer-social, .hzo-section--dark, .hzo-hero--image, .hzo-section--image, .hzo-section--video, .hzo-media-band, .hzo-surface--dark) a, .hzo-tile, .hzo-skip-link)) {
    color: var(--brand-hover-on-dark);
}
/* Card CTA links: --accent-text is on-white ink; hover darkened toward black —
   on dark surfaces lift the rest color and flip the hover to lighten instead.
   !important matches the base rules (they fight per-client a:visited). */
.hzo-archetype-midnight .hzo-card__link,
.hzo-archetype-midnight .hzo-card__link:visited { color: var(--accent-hover-on-dark) !important; }
.hzo-archetype-midnight .hzo-card__link:hover,
.hzo-archetype-midnight .hzo-card__link:visited:hover { color: color-mix(in srgb, var(--accent-hover-on-dark) 70%, white) !important; }
/* Eyebrows: --accent-text fails on the dark page; dark/image sections keep
   their own (guarded) treatment. Heading-tag eyebrows need !important to match
   the base h*.hzo-eyebrow rule. */
.hzo-archetype-midnight .hzo-eyebrow:where(:not(:is(.hzo-section--dark, .hzo-hero--image, .hzo-section--image, .hzo-section--video, .hzo-media-band, .hzo-surface--dark) *)) { color: var(--accent-hover-on-dark); }
.hzo-archetype-midnight :is(h1, h2, h3, h4, h5, h6).hzo-eyebrow:where(:not(:is(.hzo-section--dark, .hzo-hero--image, .hzo-section--image, .hzo-section--video, .hzo-media-band, .hzo-surface--dark) *)) { color: var(--accent-hover-on-dark) !important; }
/* Text-only site logos sit on the (now dark) header surface. */
.hzo-archetype-midnight .hzo-site-logo { color: var(--brand-hover-on-dark); }
/* Low-emphasis link-buttons: --brand-text ink (the --on-dark variant already
   handles dark sections — leave it alone). */
.hzo-archetype-midnight .hzo-btn--link:where(:not(.hzo-btn--on-dark)) { color: var(--brand-hover-on-dark) !important; }
/* Breadcrumb hovers jump to --brand-text / --brand (on-white ink). */
.hzo-archetype-midnight .hzo-breadcrumb a:hover,
.hzo-archetype-midnight .hzo-breadcrumb a:visited:hover { color: var(--brand-hover-on-dark); }
/* Feature-check / trust-row icons: --accent-text on dark cards. */
.hzo-archetype-midnight :is(.hzo-pricing__features li > svg, .hzo-trust-row svg) { color: var(--accent-hover-on-dark); }
/* --tint is --brand-pale (a light wash the pipeline derives for white) and the
   recipe leaves the text tokens near-white — so a tint band would be light text
   on a light wash. Re-surface tint sections as a subtle raised dark panel so the
   inherited light text reads. (--dark/--image/--video/media-band already carry
   their own light-on-dark treatment above.) */
.hzo-archetype-midnight .hzo-section--tint {
    background: var(--surface-2, #151A21);
    border-top: 1px solid var(--surface-border, rgba(255, 255, 255, 0.14));
    border-bottom: 1px solid var(--surface-border, rgba(255, 255, 255, 0.14));
}
/* Two of today's own components ink directly on the page with white-derived
   tokens the Batch-5 sweep didn't cover: the timeline date (--accent-text) and
   the prose dropcap (--brand-text) both land ~2:1 on #0E1217. Route them to the
   on-dark variants. */
.hzo-archetype-midnight .hzo-timeline__date { color: var(--accent-hover-on-dark); }
.hzo-archetype-midnight .hzo-prose--dropcap > p:first-of-type::first-letter { color: var(--brand-hover-on-dark); }
/* Brand bands (--bg-dark-section = brand) can sit close in value to the dark
   page — a hairline top/bottom keeps them reading as bands. */
.hzo-archetype-midnight .hzo-section--dark {
    border-top: 1px solid var(--surface-border, rgba(255, 255, 255, 0.14));
    border-bottom: 1px solid var(--surface-border, rgba(255, 255, 255, 0.14));
}
/* Logo strips: pure grayscale(1) renders many logos near-black — invisible on
   the dark page. Brighten the rest state; full color on hover is re-asserted
   because the rest rule (0,2,1) would otherwise out-specify the base hover
   rules (0,1,2). */
.hzo-archetype-midnight .hzo-logo-strip img,
.hzo-archetype-midnight .hzo-marquee--logos img { filter: grayscale(1) brightness(1.8); }
.hzo-archetype-midnight .hzo-logo-strip a:hover img,
.hzo-archetype-midnight .hzo-logo-strip img:hover,
.hzo-archetype-midnight .hzo-marquee--logos a:hover img,
.hzo-archetype-midnight .hzo-marquee--logos img:hover { filter: grayscale(0); }
/* Focus rings: --focus-ring is derived to raw brand (owned by the pipeline —
   the recipe can't retune it). A deep brand can vanish against #0E1217, so
   rest on the lightened brand instead — always visible on dark neutrals. */
.hzo-archetype-midnight :focus-visible { outline-color: var(--brand-hover-on-dark); }

/* ═══════════════════════════════════════════════════════════════════════
   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.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── Bento — asymmetric primary + stack grid ─────────────────────────
   One large primary cell beside a stacked column of smaller cells. Cells are
   plain .hzo-card so hover/reveal/tokens come for free. Mobile collapses to a
   single column (primary, then each stack card). --bento-gap sets both the
   outer and the stack gap; --bento-primary-col tunes the desktop split. */
.hzo-bento {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--bento-gap, var(--space-lg));
}
.hzo-bento__stack {
    display: grid;
    gap: var(--bento-gap, var(--space-lg));
    grid-auto-rows: 1fr;   /* stack cells share the height evenly */
}
/* Cells fill their track so the primary matches the stack's total height. */
.hzo-bento__primary,
.hzo-bento__stack > * { height: 100%; }
@media (min-width: 768px) {
    .hzo-bento { grid-template-columns: var(--bento-primary-col, 1.6fr) 1fr; align-items: stretch; }
}

/* ─── Media band — inset story panel with scrimmed copy ────────────────
   A rounded photo panel inset from the container gutter with copy sitting on
   its scrimmed side (reuses the directional-scrim approach; --scrim-color
   default --brand-deep). --reverse flips copy to the other side. The photo
   is set via the inline --band-image var. Below 768px the band STACKS — image
   on top, copy on a SOLID var(--bg-dark-section) panel underneath: on small
   screens we never scrim over text, which is the AA guarantee. Zero JS.
   Markup: blocks/story-band.html. */
.hzo-media-band {
    position: relative;
    border-radius: var(--band-radius, var(--radius-card));
    overflow: hidden;
}
.hzo-media-band__media {
    aspect-ratio: var(--band-aspect, 16 / 9);   /* mobile: image is a top block */
    background-image: var(--band-image);
    background-size: cover;
    background-position: var(--band-image-pos, center);
}
.hzo-media-band__body {
    background: var(--band-panel-bg, var(--bg-dark-section));   /* solid on mobile → AA */
    color: #fff;
    padding: var(--space-xl) var(--space-lg);
}
/* Force headline/eyebrow/body colors light on both the mobile solid panel and
   the desktop scrim (headings otherwise default to the dark --text-heading). */
.hzo-media-band__body :is(h1, h2, h3, h4, .hzo-visual-h1, .hzo-visual-h2, .hzo-visual-h3, .hzo-visual-h4) { color: #fff; }
.hzo-media-band__body p { color: rgba(255, 255, 255, 0.9); }
.hzo-media-band__body .hzo-eyebrow { color: var(--accent); }
/* h1-h6 eyebrows carry !important base colors (the SEO heading pattern) —
   match that strength here or they render the on-light --accent-text over
   the dark panel/scrim (same fix .hzo-section--dark already carries). */
.hzo-media-band__body h1.hzo-eyebrow,
.hzo-media-band__body h2.hzo-eyebrow,
.hzo-media-band__body h3.hzo-eyebrow,
.hzo-media-band__body h4.hzo-eyebrow { color: var(--accent) !important; }
@media (min-width: 768px) {
    .hzo-media-band {
        display: grid;
        align-items: center;
        min-height: var(--band-min-height, 460px);
    }
    .hzo-media-band__media { position: absolute; inset: 0; aspect-ratio: auto; }
    .hzo-media-band__body {
        position: relative;
        z-index: 1;
        background: transparent;
        justify-self: start;
        max-width: var(--band-copy-width, 44ch);
        padding: var(--space-2xl) var(--space-2xl);
    }
    /* Directional scrim anchored under the copy side, fading toward the photo.
       z-index: 1 — the photo (__media, z auto) is later in tree order, so at
       equal stacking the image would paint OVER the scrim, leaving white copy
       on the raw photo. The body is z-index:1 AND later in tree order, so it
       still paints above the scrim. */
    .hzo-media-band::before {
        content: "";
        position: absolute;
        inset: 0;
        z-index: 1;
        background: linear-gradient(to right,
            color-mix(in srgb, var(--scrim-color, var(--brand-deep)) 82%, transparent) 0%,
            color-mix(in srgb, var(--scrim-color, var(--brand-deep)) 58%, transparent) 42%,
            transparent 72%);
    }
    .hzo-media-band--reverse .hzo-media-band__body { justify-self: end; }
    .hzo-media-band--reverse::before {
        background: linear-gradient(to left,
            color-mix(in srgb, var(--scrim-color, var(--brand-deep)) 82%, transparent) 0%,
            color-mix(in srgb, var(--scrim-color, var(--brand-deep)) 58%, transparent) 42%,
            transparent 72%);
    }
}

/* ─── Scroll row — snap card strip with edge fades + arrows ────────────
   Horizontal scroll-snap row for any cards. The track is a real scroller
   (touch/keyboard native); main.js ([data-hzo-scroll-row]) drives the arrows
   and toggles .is-start / .is-end to hide the spent edge fade and disable the
   spent arrow. --scroll-fade-color (default --bg-page) tints the fades;
   --scroll-fade-width sizes them. Reduced motion → instant (non-smooth)
   scroll. Markup: blocks/card-row.html. */
.hzo-scroll-row { position: relative; }
.hzo-scroll-row__track {
    display: flex;
    gap: var(--scroll-gap, var(--space-lg));
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    scroll-padding-inline: var(--space-xs);
    padding: var(--space-xs) 0;   /* room for focus rings / card shadows */
    scrollbar-width: none;         /* Firefox */
    -webkit-overflow-scrolling: touch;
}
.hzo-scroll-row__track::-webkit-scrollbar { display: none; }
.hzo-scroll-row__track:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px; border-radius: var(--radius-card); }
.hzo-scroll-row__track > * { scroll-snap-align: start; flex: 0 0 var(--scroll-card-width, clamp(240px, 74vw, 320px)); }
/* Edge fades — sit above the track edges; the JS hides the spent side. */
.hzo-scroll-row::before,
.hzo-scroll-row::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    width: var(--scroll-fade-width, 48px);
    pointer-events: none;
    z-index: 1;
    opacity: 1;
    transition: opacity var(--transition-base) ease;
}
.hzo-scroll-row::before { left: 0;  background: linear-gradient(to right, var(--scroll-fade-color, var(--bg-page)), transparent); }
.hzo-scroll-row::after  { right: 0; background: linear-gradient(to left,  var(--scroll-fade-color, var(--bg-page)), transparent); }
.hzo-scroll-row.is-start::before { opacity: 0; }
.hzo-scroll-row.is-end::after    { opacity: 0; }
/* Prev / next arrows — circular, disabled (hidden) at the respective bound. */
.hzo-scroll-row__btn {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
    width: 44px;
    height: 44px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border);
    background: var(--bg-page);
    color: var(--text-heading);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    box-shadow: var(--shadow-md);
    transition: background var(--transition-base), opacity var(--transition-base);
}
.hzo-scroll-row__btn:hover { background: var(--bg-alt); }
.hzo-scroll-row__btn:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px; }
.hzo-scroll-row__btn[disabled] { opacity: 0; pointer-events: none; }
.hzo-scroll-row__btn--prev { left: var(--space-sm); }
.hzo-scroll-row__btn--next { right: var(--space-sm); }
@media (prefers-reduced-motion: reduce) {
    .hzo-scroll-row__track { scroll-behavior: auto; }
    .hzo-scroll-row::before,
    .hzo-scroll-row::after,
    .hzo-scroll-row__btn { transition: none; }
}

/* ─── Sticky bar — mobile bottom CTA ───────────────────────────────────
   Fixed bottom action bar for phones (desktop keeps its header CTA, so this is
   display:none above the mobile breakpoint). main.js ([data-hzo-sticky-bar]
   + data-hzo-sticky-after="<selector>") reveals it once the referenced element
   scrolls out of view. Rides var(--z-sticky-bar); pads for the iOS home
   indicator via env(safe-area-inset-bottom). Reduced motion → appears without
   the slide. Markup: blocks/sticky-cta.html. */
.hzo-sticky-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: var(--z-sticky-bar);
    display: none;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-md);
    padding-bottom: calc(var(--space-sm) + env(safe-area-inset-bottom, 0px));
    background: var(--sticky-bar-bg, var(--bg-page));
    border-top: 1px solid var(--border);
    box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.08);
    transform: translateY(110%);
    transition: transform var(--duration-base) var(--ease-out);
}
.hzo-sticky-bar > * { flex: 1 1 auto; }
.hzo-sticky-bar.is-visible { transform: translateY(0); }
@media (max-width: 767.98px) {
    .hzo-sticky-bar { display: flex; }
}
@media (prefers-reduced-motion: reduce) {
    .hzo-sticky-bar { transition: none; }
}

/* ─── Decor — flanking decorative overlay images ───────────────────────
   Opt-in ambient art on the section gutters. Put .hzo-section--decorated on a
   section (relative + its .hzo-container lifted above), then drop
   .hzo-decor--left / --right children holding aria-hidden alt="" images.
   Decor is pointer-events:none and hidden below 769px so it never crowds
   mobile. --decor-width sizes the flanks. No JS, no motion. Composes onto any
   section (e.g. append to blocks/section.html) — no standalone block needed.
     <section class="hzo-section hzo-section--decorated">
       <div class="hzo-decor hzo-decor--left" aria-hidden="true"><img src="" alt=""></div>
       <div class="hzo-container"> … </div>
     </section> */
.hzo-section--decorated { position: relative; overflow: hidden; }
.hzo-section--decorated > .hzo-container { position: relative; z-index: 1; }
.hzo-decor {
    position: absolute;
    top: 0;
    bottom: 0;
    width: var(--decor-width, 220px);
    pointer-events: none;
    z-index: 0;
    display: none;
}
.hzo-decor img,
.hzo-decor svg { width: 100%; height: 100%; object-fit: contain; object-position: center; display: block; }
.hzo-decor--left  { left: 0; }
.hzo-decor--right { right: 0; }
@media (min-width: 769px) {
    .hzo-decor { display: block; }
}

/* ─── Option card — selectable radio rows ──────────────────────────────
   A label wrapping a native radio + copy, with an optional __panel that opens
   via :has(:checked). Selected state keys off var(--brand). Reuses --border /
   --radius-input. Custom radio dot; the native input stays for a11y/keyboard.
   Markup: blocks/option-cards.html. */
.hzo-option-group { display: flex; flex-direction: column; gap: var(--space-md); }
.hzo-option-card {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: var(--space-md);
    padding: var(--space-lg);
    border: 1px solid var(--border);
    border-radius: var(--radius-input);
    background: var(--card-bg, #fff);
    cursor: pointer;
    transition: border-color var(--transition-base), box-shadow var(--transition-base), background var(--transition-base);
}
.hzo-option-card:hover { border-color: color-mix(in srgb, var(--brand) 40%, var(--border)); }
.hzo-option-card input[type="radio"] {
    appearance: none;
    -webkit-appearance: none;
    margin: 0;
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: 2px solid var(--border);
    background: #fff;
    display: grid;
    place-content: center;
    cursor: pointer;
    transition: border-color var(--transition-base);
}
.hzo-option-card input[type="radio"]::before {
    content: "";
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--brand);
    transform: scale(0);
    transition: transform var(--transition-base) var(--ease-spring);
}
.hzo-option-card input[type="radio"]:checked { border-color: var(--brand); }
.hzo-option-card input[type="radio"]:checked::before { transform: scale(1); }
.hzo-option-card input[type="radio"]:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px; }
.hzo-option-card__body { display: flex; flex-direction: column; gap: var(--space-xs); flex: 1 1 auto; }
.hzo-option-card__title { font-weight: var(--fw-semibold, 600); color: var(--text-heading); line-height: 1.3; }
.hzo-option-card__desc  { font-size: 14px; color: var(--text-muted); line-height: 1.4; }
.hzo-option-card__price { font-weight: var(--fw-bold, 700); color: var(--text-heading); white-space: nowrap; }
/* Selected treatment — brand ring + pale wash (ring via box-shadow keeps the
   1px border from shifting layout). */
.hzo-option-card:has(:checked) { border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand); background: var(--brand-pale); }
/* Optional reveal panel — collapsed until the row is selected. */
.hzo-option-card__panel { display: none; width: 100%; margin-top: var(--space-sm); padding-top: var(--space-sm); border-top: 1px solid var(--border); font-size: 14px; color: var(--text-body); }
.hzo-option-card:has(:checked) .hzo-option-card__panel { display: block; }
@media (prefers-reduced-motion: reduce) {
    .hzo-option-card,
    .hzo-option-card input[type="radio"],
    .hzo-option-card input[type="radio"]::before { transition: none; }
}

/* ─── Quantity stepper — fused capsule ─────────────────────────────────
   minus / numeric input / plus in one pill. main.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:
     <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: 1px 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: 1;
    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(--focus-ring); 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-semibold, 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(--focus-ring); outline-offset: 1px; border-radius: var(--radius-input); }
@media (prefers-reduced-motion: reduce) {
    .hzo-qty__btn { transition: none; }
}

/* ─── 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);
}

/* ─── Button icon-slide — trailing arrow nudge ─────────────────────────
   Modifier on .hzo-btn: a trailing arrow (::after) that slides on hover/focus,
   mirroring .hzo-card__link. Reuses the button's flex gap for spacing.
   --icon-slide-distance (4px) with var(--ease-spring). Reduced motion → no
   transition (arrow stays put). No block. */
.hzo-btn--icon-slide::after {
    content: "";
    display: inline-block;
    width: 1em;
    height: 1em;
    flex-shrink: 0;
    background-color: currentColor;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E") no-repeat center / contain;
            mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E") no-repeat center / contain;
    transition: transform var(--transition-base) var(--ease-spring, cubic-bezier(0.34, 1.56, 0.5, 1));
}
.hzo-btn--icon-slide:hover::after,
.hzo-btn--icon-slide:focus-visible::after { transform: translateX(var(--icon-slide-distance, 4px)); }
@media (prefers-reduced-motion: reduce) {
    .hzo-btn--icon-slide::after { transition: none; }
}

/* ─── Clamp tooltip — truncation-detect hint ───────────────────────────
   .hzo-clamp is a token-driven line-clamp helper (--clamp-lines). Any element
   carrying [data-hzo-clamp-tooltip] is measured by main.js: when its content
   overflows the clamp it gets .is-truncated, is made focusable, and reveals a
   floating .hzo-tooltip (reused styling) with the full text on hover AND focus
   (WCAG 1.4.13), rechecked on debounced resize. */
.hzo-clamp {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: var(--clamp-lines, 2);
    line-clamp: var(--clamp-lines, 2);
    overflow: hidden;
}
[data-hzo-clamp-tooltip].is-truncated { cursor: help; }
/* The floating hint reuses .hzo-tooltip visuals but is repositioned to fixed
   coordinates by the JS (so measuring the clamped element is never disturbed). */
.hzo-clamp-tooltip {
    position: fixed;
    top: 0;
    left: 0;
    bottom: auto;
    transform: none;
    max-width: 320px;
    z-index: var(--z-toast);
}

/* ═══════════════════════════════════════════════════════════════════════
   ROADMAP BATCH 1 — polish & structure. Every rule below is additive:
   opt-in classes/attributes only, token-driven, all motion gated behind
   prefers-reduced-motion. The only deliberate default changes are the
   text-wrap pass (main.css + .hzo-display below) and the print stylesheet
   at the very end of this file.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── 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; }

/* ─── Header condense on scroll ────────────────────────────────────────
   main.js (scroll-chrome IIFE) toggles .is-scrolled on .hzo-site-header once
   scrollY passes 24px (hysteresis: removed again below 8px so the boundary
   never flickers). Condensed: the inner bar's min-height multiplies by
   --header-condensed-scale (default 0.85 ≈ 72→61 / 80→68px), the logo scales
   down via transform (no layout shift), and the shadow steps up one notch —
   the fallback chain keeps any per-client --nav-header-shadow in play.
   Sticky positioning, the mega-menu anchor (top:100% inside the header) and
   the mobile header all follow the header's live height automatically.
   The transition rules below are inert until .is-scrolled toggles, so
   resting rendering is unchanged. Reduced motion → instant swap. */
.hzo-site-header {
    transition: box-shadow var(--duration-base) var(--ease-standard);
}
.hzo-site-header .hzo-site-header__inner {
    transition: min-height var(--duration-base) var(--ease-standard);
}
.hzo-site-header .hzo-site-logo img {
    transform-origin: left center;
    transition: transform var(--duration-base) var(--ease-standard);
}
.hzo-site-header.is-scrolled .hzo-site-header__inner {
    min-height: calc(var(--header-height-mobile, 72px) * var(--header-condensed-scale, 0.85));
}
.hzo-site-header.is-scrolled .hzo-site-logo img {
    transform: scale(var(--header-logo-condensed-scale, 0.85));
}
.hzo-site-header.is-scrolled {
    box-shadow: var(--nav-header-shadow-scrolled, var(--nav-header-shadow, 0 2px 10px rgba(0, 0, 0, 0.10)));
}
@media (min-width: 1250px) {
    .hzo-site-header.is-scrolled .hzo-site-header__inner {
        min-height: calc(var(--header-height-desktop, 80px) * var(--header-condensed-scale, 0.85));
    }
}
@media (prefers-reduced-motion: reduce) {
    .hzo-site-header,
    .hzo-site-header .hzo-site-header__inner,
    .hzo-site-header .hzo-site-logo img { transition: none; }
}

/* ─── Accordion motion pack ────────────────────────────────────────────
   Animates the native <details> open/close in browsers that support
   ::details-content + interpolate-size (Chrome 131+/Safari 18.4+); a clean
   no-op everywhere else (unknown pseudo-element → rule dropped; details
   keep their instant native toggle). interpolate-size is scoped to the
   accordion — NOT :root — so keyword sizes elsewhere are untouched.
   Browsers with ::details-content but without interpolate-size (Firefox)
   get the opacity fade only. Reduced motion → native instant toggle.

   JS companions in main.js:
   - [data-hzo-accordion-exclusive] wrapper → injects a shared `name` into
     child <details> (native exclusive-open group).
   - [data-hzo-accordion-expand-all] button → opens/closes every child
     <details>; label flips Expand all/Collapse all + aria-expanded. */
.hzo-accordion { interpolate-size: allow-keywords; }
.hzo-accordion__item::details-content {
    height: 0;
    opacity: 0;
    overflow-y: clip;
    transition: height var(--duration-base) var(--ease-standard),
                opacity var(--duration-base) var(--ease-standard),
                content-visibility var(--duration-base) allow-discrete;
}
.hzo-accordion__item[open]::details-content {
    height: auto;
    opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
    .hzo-accordion__item::details-content { transition: none; }
}
/* Optional row for the expand-all control above an accordion — right-aligned,
   quiet. The button itself composes .hzo-btn--ghost .hzo-btn--sm. */
.hzo-accordion-controls {
    display: flex;
    justify-content: flex-end;
    margin-bottom: var(--space-sm);
}

/* ─── 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. */
.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;
    }
}

/* ─── Timeline ─────────────────────────────────────────────────────────
   Vertical spine of dated milestones. Structure:
     <ol class="hzo-timeline">
       <li class="hzo-timeline__item">
         <span class="hzo-timeline__marker" aria-hidden="true"></span>
         <p class="hzo-timeline__date">2019</p>
         <h3 class="hzo-timeline__title">…</h3>
         <p class="hzo-timeline__body">…</p>
       </li>
     </ol>
   Default: single column, spine on the left at every width. Optional
   .hzo-timeline--alternate switches to a centered spine with items
   alternating sides from 1024px (still single-column below).
   Tokens: --timeline-spine-color (--border), --timeline-marker-size (14px),
   --timeline-marker-color (--brand), --timeline-marker-ring (--bg-page),
   --timeline-gap (--space-xl), --timeline-indent (--space-lg-2).
   Static component — motion comes only from composing .hzo-reveal. */
.hzo-timeline {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--timeline-gap, var(--space-xl));
    margin: 0;
    padding: 0;
    list-style: none;
}
.hzo-timeline::before {
    content: "";
    position: absolute;
    top: var(--space-xs);
    bottom: var(--space-xs);
    left: calc(var(--timeline-marker-size, 14px) / 2);
    border-left: 2px solid var(--timeline-spine-color, var(--border));
}
.hzo-timeline__item {
    position: relative;
    padding-left: calc(var(--timeline-marker-size, 14px) + var(--timeline-indent, var(--space-lg-2)));
}
.hzo-timeline__marker {
    position: absolute;
    top: var(--space-xs);
    left: 0;
    width: var(--timeline-marker-size, 14px);
    height: var(--timeline-marker-size, 14px);
    border-radius: var(--radius-pill);
    background: var(--timeline-marker-color, var(--brand));
    /* ring lifts the dot off the spine on any section background */
    box-shadow: 0 0 0 3px var(--timeline-marker-ring, var(--bg-page));
    z-index: 1;
}
.hzo-timeline__date {
    font-family: var(--font-body);
    font-size: 13px;
    font-weight: var(--fw-semibold, 600);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--accent-text);
    margin: 0 0 var(--space-xs);
}
.hzo-timeline__title {
    font-family: var(--font-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));
    line-height: 1.2;
    color: var(--text-heading);
    margin: 0 0 var(--space-sm);
}
.hzo-timeline__body { color: var(--text-body); margin: 0; max-width: var(--prose-max-width); }
@media (min-width: 1024px) {
    .hzo-timeline--alternate::before {
        left: 50%;
        transform: translateX(-50%);
    }
    .hzo-timeline--alternate .hzo-timeline__item {
        width: calc(50% - var(--timeline-indent, var(--space-lg-2)));
        padding-left: 0;
    }
    .hzo-timeline--alternate .hzo-timeline__item:nth-child(odd) {
        align-self: flex-start;
        text-align: right;
    }
    .hzo-timeline--alternate .hzo-timeline__item:nth-child(odd) .hzo-timeline__body { margin-left: auto; }
    .hzo-timeline--alternate .hzo-timeline__item:nth-child(even) { align-self: flex-end; }
    .hzo-timeline--alternate .hzo-timeline__item:nth-child(odd) .hzo-timeline__marker {
        left: auto;
        right: calc(-1 * var(--timeline-indent, var(--space-lg-2)));
        transform: translateX(50%);
    }
    .hzo-timeline--alternate .hzo-timeline__item:nth-child(even) .hzo-timeline__marker {
        left: calc(-1 * var(--timeline-indent, var(--space-lg-2)));
        transform: translateX(-50%);
    }
}

/* ─── Feature overlap (.hzo-feature--overlap) ──────────────────────────
   NEW modifier on .hzo-feature — the base 50/50 split is untouched.
   Asymmetric editorial split on a 12-col grid from 768px: media spans
   cols 1-7, the .hzo-feature__panel spans cols 6-12 and floats OVER the
   media's right edge on an elevated card surface, dropped by --overlap-rise
   for an offset composition. .hzo-feature--reverse mirrors the whole
   arrangement. Below 768px: normal stacked flow, NO overlap — the panel
   simply keeps its card surface (base .hzo-feature single-column grid +
   gap are inherited untouched).
   Tokens: --overlap-rise (default --space-xl), plus the shared card tokens
   (--card-bg, --shadow-lg, --radius-card). Static layout — no motion. */
.hzo-feature--overlap .hzo-feature__panel {
    position: relative;
    z-index: 1;
    background: var(--card-bg, #fff);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-lg);
    padding: var(--space-lg);
}
@media (min-width: 768px) {
    .hzo-feature--overlap {
        grid-template-columns: repeat(12, 1fr);
        gap: 0;                                   /* the overlap IS the gutter */
        align-items: center;
    }
    .hzo-feature--overlap .hzo-feature__media { grid-column: 1 / 8; grid-row: 1; }
    .hzo-feature--overlap .hzo-feature__panel {
        grid-column: 6 / 13;
        grid-row: 1;
        /* margin (not transform) drops the panel by --overlap-rise: transform
           would fight the .hzo-reveal keyframe when the panel is reveal-wired,
           and a margin keeps the panel INSIDE the grid row (the row grows to
           contain it), so the offset can never collide with content below. */
        margin-top: var(--overlap-rise, var(--space-xl));
        padding: var(--space-xl);
    }
    .hzo-feature--overlap.hzo-feature--reverse .hzo-feature__media { grid-column: 6 / 13; }
    .hzo-feature--overlap.hzo-feature--reverse .hzo-feature__panel { grid-column: 1 / 8; }
}

/* ─── Back-to-top ──────────────────────────────────────────────────────
   Site chrome (ships fleet-wide from parts/footer.html — deliberately not
   opt-in). main.js (scroll-chrome IIFE) adds .is-visible once scrollY passes
   two viewports; 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 (chrome block below). */
.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-pill);
    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(--focus-ring); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
    .hzo-back-to-top { transition: none; transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   ROADMAP BATCH 2 — rich media & conversion. Every rule below is additive:
   opt-in classes/attributes only, token-driven, all motion gated behind
   prefers-reduced-motion. No default rendering changes. This section sits
   ABOVE the print block deliberately — print keeps its "last in file"
   contract.

   Architecture locks honored (Playbook quality-rules §1.22-1.24):
   - §1.22 surfaces: NEW components consume the semantic surface tokens
     with light fallbacks — var(--surface, #fff), var(--on-surface,
     var(--text-heading)), var(--on-surface-muted, var(--text-muted)),
     var(--surface-border, var(--border)). The token layer lands with the
     dark-archetype work; the fallbacks keep today's rendering identical
     until it does, and the remap "just works" the day it ships.
   - §1.23 one reveal lane: the ONLY animation-timeline use below is the
     decorative .hzo-parallax drift — no content depends on it.
   - §1.24 container queries: none of this batch's new components are
     grid-cell components with internal layout shifts, and .hzo-grid does
     not yet carry container-type (retrofitting a shipped primitive with
     containment risks fleet-wide layout changes — deferred to its own
     pass). Narrow-width behavior here uses intrinsic techniques instead
     (flex-wrap on .hzo-author-bio, aspect-ratio on media frames).
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── 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-card);
    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; }

/* ─── Lite video facade (.hzo-lite-video) ──────────────────────────────
   Click-to-load video: a plain <button> holding the poster image + a play
   badge — ZERO third-party network cost until the visitor activates it.
   main.js (lite-video IIFE) swaps in the real iframe on activation
   (click; Enter/Space come free with <button>), reading:
     data-hzo-video-src    REQUIRED — the youtube-nocookie / Vimeo EMBED URL
     data-hzo-video-title  injected iframe title (default "Video player")
     data-hzo-video-allow  iframe allow list (default covers YT/Vimeo)
   The button MUST carry an aria-label naming the video ("Play video: …").
   Focus moves to the injected iframe and a bubbling hzo:videoopen fires.
   Play badge is 64px — comfortably past the 44px target floor. Focus ring
   draws INSET because the .hzo-embed wrapper clips overflow. Markup:
   blocks/video.html / blocks/video-testimonial.html. */
.hzo-lite-video {
    position: relative;
    display: block;
    width: 100%;
    height: 100%;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: pointer;
}
.hzo-lite-video > img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform var(--duration-slow, 400ms) var(--ease-standard, ease);
}
.hzo-lite-video__play {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 64px;
    height: 64px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-pill);
    background: var(--lite-video-play-bg, var(--brand));
    color: var(--lite-video-play-icon, var(--text-on-brand));
    box-shadow: var(--shadow-lg);
    transition: transform var(--duration-base) var(--ease-spring),
                background var(--duration-base) var(--ease-standard);
}
.hzo-lite-video__play svg {
    width: 24px;
    height: 24px;
    display: block;
    margin-left: 3px;   /* optical centering for the triangle */
}
.hzo-lite-video:hover > img,
.hzo-lite-video:focus-visible > img { transform: scale(1.03); }
.hzo-lite-video:hover .hzo-lite-video__play,
.hzo-lite-video:focus-visible .hzo-lite-video__play {
    transform: translate(-50%, -50%) scale(1.08);
    background: var(--lite-video-play-bg-hover, var(--brand-hover-on-light));
}
.hzo-lite-video:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: -3px; }
@media (prefers-reduced-motion: reduce) {
    .hzo-lite-video > img,
    .hzo-lite-video__play { transition: none; }
    .hzo-lite-video:hover > img,
    .hzo-lite-video:focus-visible > img { transform: none; }
    .hzo-lite-video:hover .hzo-lite-video__play,
    .hzo-lite-video:focus-visible .hzo-lite-video__play { transform: translate(-50%, -50%); }
}

/* ─── Video background band (.hzo-section--video) ──────────────────────
   The --image band with a looping ambient <video> instead of a photo.
   Structure (full example in blocks/video.html):
     <section class="hzo-section hzo-section--video">
       <video autoplay muted loop playsinline preload="metadata"
              poster="frame.jpg" aria-hidden="true">
         <source src="ambient.mp4" type="video/mp4"></video>
       <button type="button" class="hzo-video-toggle" data-hzo-video-toggle
               aria-pressed="false">Pause</button>
       <div class="hzo-container"> … </div>
     </section>
   The scrim reuses the --image knobs (--hzo-section-scrim strength,
   --scrim-color tint). Stacking: the video is a positioned child painted
   first; the scrim ::before carries z-index:1 (an unpositioned ::before
   would paint UNDER a later positioned sibling — same trap the media-band
   documents); every other child rides z-index:2 above both. The pause
   control is REQUIRED (WCAG 2.2.2 — >5s of auto-motion needs a visible
   pause); main.js (bg-video IIFE) wires it (aria-pressed + visible label
   sync), NEVER autoplays under prefers-reduced-motion (paused immediately
   at the poster/first frame; the control still works), and pauses the
   file while the band is offscreen. */
.hzo-section--video {
    position: relative;
    overflow: hidden;
}
.hzo-section--video > video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.hzo-section--video::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    background: color-mix(in srgb, var(--scrim-color, var(--brand-deep)) var(--hzo-section-scrim, 60%), transparent);
}
.hzo-section--video > :where(:not(video):not(.hzo-video-toggle)) {
    position: relative;
    z-index: 2;   /* lift content above the scrim (:where = zero specificity) */
}
/* Light text over the scrim — same guarantees as --image, expressed through
   the on-dark token (fallback white lives INSIDE var() per §1.22). */
.hzo-section--video h1, .hzo-section--video h2, .hzo-section--video h3,
.hzo-section--video h4, .hzo-section--video h5, .hzo-section--video h6,
.hzo-section--video .hzo-eyebrow, .hzo-section--video .hzo-display { color: var(--heading-color-on-dark, #fff) !important; }
.hzo-section--video p, .hzo-section--video .hzo-subtitle { color: color-mix(in srgb, var(--heading-color-on-dark, #fff) 90%, transparent); }
.hzo-section--video a:not(.hzo-btn) { color: var(--heading-color-on-dark, #fff); }
/* The visible pause/play control — a quiet surface pill, bottom-right. */
.hzo-video-toggle {
    position: absolute;
    right: var(--space-lg);
    bottom: var(--space-lg);
    z-index: 2;
    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(--duration-base) var(--ease-standard);
}
.hzo-video-toggle:hover { background: var(--bg-alt); }
.hzo-video-toggle:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
    .hzo-video-toggle { transition: none; }
}

/* ─── Map facade (.hzo-map) ────────────────────────────────────────────
   Static map preview + "Load interactive map" button — the maps iframe
   (and its megabyte of third-party JS) never loads until the visitor asks.
     <div class="hzo-map" data-hzo-map
          data-hzo-map-src="<Google Maps EMBED url>"
          data-hzo-map-title="Map of …">
       <img class="hzo-map__preview" src="…" alt="Map preview of …">   ← optional screenshot
       <span class="hzo-map__pin" aria-hidden="true"><svg …></svg></span> ← placeholder pin (omit with a real screenshot)
       <button type="button" class="hzo-map__load" data-hzo-map-load>Load interactive map</button>
     </div>
   main.js (map-facade IIFE) injects the iframe, moves focus to it, and
   fires a bubbling hzo:mapopen. --map-aspect reshapes the frame;
   --map-radius: 0 when nested in an already-clipped .hzo-card__media.
   Markup: blocks/map.html; live use: page-variants/contact-1.html. */
.hzo-map {
    position: relative;
    aspect-ratio: var(--map-aspect, 16 / 9);
    border-radius: var(--map-radius, var(--radius-card));
    overflow: hidden;
    background: var(--bg-alt);
}
.hzo-map > iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
}
.hzo-map__preview {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.hzo-map__pin {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -100%);   /* pin tip marks the spot */
    width: 40px;
    height: 40px;
    color: var(--brand);
    pointer-events: none;
}
.hzo-map__pin svg { width: 100%; height: 100%; display: block; }
.hzo-map__load {
    position: absolute;
    left: 50%;
    bottom: var(--space-lg);
    transform: translateX(-50%);
    z-index: 1;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 10px 20px;
    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);
    white-space: nowrap;
    box-shadow: var(--shadow-md);
    cursor: pointer;
    transition: background var(--duration-base) var(--ease-standard);
}
.hzo-map__load:hover { background: var(--bg-alt); }
.hzo-map__load:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
    .hzo-map__load { transition: none; }
}

/* ─── Masonry gallery (.hzo-gallery--masonry) ──────────────────────────
   Column-flow variant of .hzo-gallery: tiles keep their natural aspect
   ratios and pack into 2/3/4 CSS columns across the standard breakpoints,
   same --space-sm gap rhythm as the base grid. CAVEAT — CSS columns order
   items DOWN each column, so visual order differs from DOM/reading order;
   keep tiles self-contained (no numbering, no caption that says "next").
   Lightbox: data-hzo-lightbox still works — prev/next cycle DOM order,
   which can hop between columns visually. Acceptable for a photo wall;
   use the base grid when strict order matters. */
.hzo-gallery--masonry {
    display: block;
    columns: 2;
    column-gap: var(--space-sm);
}
@media (min-width: 768px)  { .hzo-gallery--masonry { columns: 3; } }
@media (min-width: 1024px) { .hzo-gallery--masonry { columns: 4; } }
.hzo-gallery--masonry .hzo-gallery__item {
    display: block;
    break-inside: avoid;
    aspect-ratio: auto;              /* natural image heights make the masonry */
    margin-bottom: var(--space-sm);
}
.hzo-gallery--masonry .hzo-gallery__item img { height: auto; }

/* ─── Decorative background parallax (.hzo-parallax) ───────────────────
   Opt-in on --image surfaces: the background photo drifts gently as the
   band crosses the viewport. Implemented as a scroll-driven animation on
   background-position-y ONLY — the ::before scrim, the text stacking, and
   the layout are untouched (no new DOM, no transform). Movement is capped
   subtle: 46% → 54%, an 8% crop shift around center. PERMITTED use of
   animation-timeline per quality-rules §1.23 — decorative ambient motion
   only, no content depends on it. Unsupported browsers (@supports) and
   reduced-motion (@media) both get the normal static band. */
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        .hzo-section--image.hzo-parallax,
        .hzo-hero--image.hzo-parallax {
            animation: hzo-parallax-drift linear both;
            animation-timeline: view();
        }
        @keyframes hzo-parallax-drift {
            from { background-position-y: 46%; }
            to   { background-position-y: 54%; }
        }
    }
}

/* ─── Sticky bar — product modifier (.hzo-sticky-bar--product) ─────────
   Product-page layout for the existing mobile sticky bar: 40px thumb +
   title/price stack + ATC button. Reveal wiring is unchanged — point
   data-hzo-sticky-after at the on-page product form so the bar appears
   once the real add-to-cart scrolls away:
     <div class="hzo-sticky-bar hzo-sticky-bar--product" data-hzo-sticky-bar
          data-hzo-sticky-after="form.cart" role="region" aria-label="Add to cart">
       <img class="hzo-sticky-bar__thumb" src="…" alt="">
       <span class="hzo-sticky-bar__info">
         <span class="hzo-sticky-bar__title">Product name</span>
         <span class="hzo-sticky-bar__price">$49.00</span>
       </span>
       <button type="button" class="hzo-btn hzo-btn--primary" data-hzo-add-to-cart="123">Add to cart</button>
     </div>
   data-hzo-add-to-cart MUST carry the product id (as above). A bare
   data-hzo-add-to-cart with no value has no product to post: on a WC site
   commerce.js resets it silently, and on a non-WC site the button would sit
   in .is-loading forever (main.js only self-resolves demo buttons). Use the
   real id, or data-hzo-add-to-cart-demo for a non-functional showcase.
   No new JS — reveal + add-to-cart states both reuse shipped modules. */
.hzo-sticky-bar--product > * { flex: 0 0 auto; }   /* cancel the base equal-stretch */
.hzo-sticky-bar--product .hzo-sticky-bar__info {
    flex: 1 1 auto;
    min-width: 0;                    /* lets the title ellipsis instead of overflowing */
    display: flex;
    flex-direction: column;
}
.hzo-sticky-bar__thumb {
    width: 40px;
    height: 40px;
    border-radius: var(--radius-input);
    object-fit: cover;
    display: block;
}
.hzo-sticky-bar__title {
    font-size: 14px;
    font-weight: var(--fw-semibold, 600);
    color: var(--on-surface, var(--text-heading));
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.hzo-sticky-bar__price {
    font-size: 13px;
    color: var(--on-surface-muted, var(--text-muted));
    line-height: 1.3;
}

/* ─── Fluent Forms — multi-step (progress, panes, prev/next) ───────────
   Fluent's multi-step markup: .ff-step-titles (the progress <ul>; Fluent
   marks the current <li> ff_active and finished ones ff_completed),
   .ff-step-container > .ff-step-body-content > .fluentform-step panes, and
   a .step-nav row holding .ff-btn-prev / .ff-btn-next. Same policy as the
   main Fluent block earlier in this file: scoped under .hzo-form,
   !important retained only where Fluent's own stylesheet sets the same
   property on the same element. */
.hzo-form .ff-step-titles {
    display: flex !important;        /* Fluent lays titles out as table cells */
    flex-wrap: wrap;
    gap: var(--space-sm) var(--space-lg);
    margin: 0 0 var(--space-lg) !important;
    padding: 0 !important;
    list-style: none;
    counter-reset: hzo-ff-step;
}
.hzo-form .ff-step-titles li {
    display: inline-flex !important;
    align-items: center;
    gap: var(--space-sm);
    padding: 0 !important;
    counter-increment: hzo-ff-step;
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: var(--fw-medium, 500);
    color: var(--text-muted) !important;
    background: none !important;
    text-align: left;
}
/* Our numbered chip replaces Fluent's ::before decoration (same pseudo);
   Fluent's ::after connector line is suppressed. */
.hzo-form .ff-step-titles li::before {
    content: counter(hzo-ff-step) !important;
    position: static !important;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    margin: 0 !important;
    border: 2px solid var(--border) !important;
    border-radius: var(--radius-pill) !important;
    background: var(--surface, transparent) !important;
    color: var(--text-muted) !important;
    font-size: 13px;
    font-weight: var(--fw-semibold, 600);
    line-height: 1 !important;
}
.hzo-form .ff-step-titles li::after { content: none !important; }
.hzo-form .ff-step-titles li.ff_active {
    color: var(--text-heading) !important;
    font-weight: var(--fw-semibold, 600);
}
.hzo-form .ff-step-titles li.ff_active::before {
    border-color: var(--brand) !important;
    background: var(--brand) !important;
    color: var(--text-on-brand) !important;
}
.hzo-form .ff-step-titles li.ff_completed { color: var(--text-body) !important; }
.hzo-form .ff-step-titles li.ff_completed::before {
    content: "\2713" !important;     /* checkmark */
    border-color: var(--accent-text) !important;
    background: var(--accent-pale) !important;
    color: var(--accent-text) !important;
}
/* Step pane entrance — a soft fade+rise on the pane Fluent activates.
   Additive (Fluent's own step switch still runs); animation, not
   transition, so it can't fight Fluent's inline-style animation of the
   container. Reduced motion → no animation, instant swap. */
@media (prefers-reduced-motion: no-preference) {
    .hzo-form .ff-step-body-content .fluentform-step.active {
        animation: hzo-ff-step-in var(--duration-base) var(--ease-out) both;
    }
}
@keyframes hzo-ff-step-in {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
}
/* Prev / next — the same tokenized language as .ff-btn-submit above: next
   is the primary (accent) action, previous the quiet outline. Same
   double-flash prophylactics as the submit button (Fluent animates
   transform/shadow/opacity on .ff-btn hover with its own curves). */
.hzo-form .step-nav {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-md);
    margin-top: var(--space-md);
}
.hzo-form .ff-btn-next,
.hzo-form .ff-btn-prev,
.hzo-form .ff-btn-next:hover,
.hzo-form .ff-btn-prev:hover,
.hzo-form .ff-btn-next:focus,
.hzo-form .ff-btn-prev:focus,
.hzo-form .ff-btn-next:active,
.hzo-form .ff-btn-prev:active {
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    padding: calc(var(--btn-padding-y, 14px) - 2px) calc(var(--btn-padding-x, 32px) - 6px) !important;
    font-family: var(--font-body) !important;
    font-size: 1rem !important;
    font-weight: var(--btn-weight, var(--fw-semibold, 600)) !important;
    text-decoration: none !important;
    border-radius: var(--btn-radius, var(--radius-pill)) !important;
    min-height: 44px;
    line-height: 1;
    cursor: pointer;
    box-shadow: none !important;
    transform: none !important;
    text-shadow: none !important;
    filter: none !important;
    background-image: none !important;
    transition: background-color 120ms linear, border-color 120ms linear, color 120ms linear !important;
    animation: none !important;
    opacity: 1 !important;
}
.hzo-form .ff-btn-next {
    background-color: var(--btn-primary-bg, var(--accent)) !important;
    border: 1px solid var(--btn-primary-bg, var(--accent)) !important;
    color: var(--btn-primary-text, var(--text-on-accent)) !important;
}
.hzo-form .ff-btn-next:hover,
.hzo-form .ff-btn-next:focus,
.hzo-form .ff-btn-next:active {
    background-color: var(--btn-primary-hover, var(--accent-hover-on-light)) !important;
    border-color: var(--btn-primary-hover, var(--accent-hover-on-light)) !important;
    color: var(--btn-primary-hover-text, #fff) !important;   /* bg darkens on hover → flip text light (matches .hzo-btn--primary) */
}
.hzo-form .ff-btn-prev {
    background-color: transparent !important;
    border: 1px solid var(--btn-outline-color, var(--brand)) !important;
    color: var(--btn-outline-color, var(--brand)) !important;
}
.hzo-form .ff-btn-prev:hover,
.hzo-form .ff-btn-prev:focus,
.hzo-form .ff-btn-prev:active {
    background-color: var(--btn-outline-color, var(--brand)) !important;
    border-color: var(--btn-outline-color, var(--brand)) !important;
    color: var(--btn-secondary-text, var(--text-on-brand)) !important;
}
.hzo-form .ff-btn-next:focus-visible,
.hzo-form .ff-btn-prev:focus-visible {
    outline: 3px solid var(--focus-ring) !important;
    outline-offset: 2px;
}

/* ─── Fluent Forms — file upload ───────────────────────────────────────
   Fluent's uploader: .ff_file_upload_holder wraps the trigger + hidden
   input; uploaded files render as .ff-upload-preview rows holding
   .ff-upload-thumb / .ff-upload-details (.ff-upload-filename,
   .ff-upload-filesize, .ff-upload-progress-inline, .ff-upload-error) and
   a remove control. The holder becomes a dashed drop zone with a brand
   lift on hover/focus/drag-over; uploaded rows echo the .hzo-tag chip
   language (quiet border, pale field, muted meta). Best-effort on the
   drag-over class — Fluent has shipped both spellings. */
.hzo-form .ff_file_upload_holder {
    padding: var(--space-lg);
    border: 2px dashed var(--border);
    border-radius: var(--radius-input);
    background: var(--bg-alt);
    text-align: center;
    transition: border-color var(--duration-base) var(--ease-standard),
                background var(--duration-base) var(--ease-standard);
}
.hzo-form .ff_file_upload_holder:hover,
.hzo-form .ff_file_upload_holder:focus-within,
.hzo-form .ff_file_upload_holder.ff_dragover,
.hzo-form .ff_file_upload_holder.drag-over {
    border-color: var(--brand);
    background: var(--brand-pale);
}
@media (prefers-reduced-motion: reduce) {
    .hzo-form .ff_file_upload_holder { transition: none; }
}
/* The visible "choose file" trigger inside the zone — outline-button language. */
.hzo-form .ff_file_upload_holder .ff_upload_btn {
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    padding: 10px 20px !important;
    font-family: var(--font-body) !important;
    font-size: 15px !important;
    font-weight: var(--fw-semibold, 600) !important;
    border: 1px solid var(--btn-outline-color, var(--brand)) !important;
    border-radius: var(--btn-radius, var(--radius-pill)) !important;
    background: transparent !important;
    color: var(--btn-outline-color, var(--brand)) !important;
    box-shadow: none !important;
    cursor: pointer;
    transition: background-color 120ms linear, color 120ms linear !important;
}
.hzo-form .ff_file_upload_holder .ff_upload_btn:hover {
    background: var(--btn-outline-color, var(--brand)) !important;
    color: var(--btn-secondary-text, var(--text-on-brand)) !important;
}
/* Uploaded-file rows — .hzo-tag visual language (chip field + hairline). */
.hzo-form .ff_file_upload_holder .ff-upload-preview {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    margin-top: var(--space-sm);
    padding: 6px 12px;
    text-align: left;
    font-size: 13px;
    background: var(--surface, #fff);
    border: 1px solid var(--surface-border, var(--border)) !important;
    border-radius: var(--radius-input);
}
.hzo-form .ff-upload-filename {
    color: var(--text-body);
    font-weight: var(--fw-medium, 500);
    overflow-wrap: anywhere;
}
.hzo-form .ff-upload-filesize { color: var(--text-muted); }
.hzo-form .ff-upload-error { color: var(--error); font-size: 13px; }
/* Inline progress bar during upload — quiet track, accent fill. */
.hzo-form .ff-upload-progress-inline {
    height: 4px;
    border-radius: var(--radius-pill);
    background: var(--border);
    overflow: hidden;
}
.hzo-form .ff-upload-progress-inline .ff-el-progress-bar {
    height: 100%;
    background: var(--accent) !important;
}
/* Remove ✕ on an uploaded row — echoes .hzo-tag__remove. */
.hzo-form .ff-upload-preview .ff-upload-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;
}
.hzo-form .ff-upload-preview .ff-upload-remove:hover {
    background: var(--border);
    color: var(--text-heading);
}

/* ─── Pricing period toggle (.hzo-price-toggle) ────────────────────────
   Two-button aria-pressed group; main.js (price-toggle IIFE) mirrors the
   pressed button into data-period="monthly|annual" on the closest
   [data-hzo-price-group] ancestor, and the display rules below swap any
   [data-price-monthly] / [data-price-annual] elements inside it. Default
   — and the no-JS state — is monthly (annual simply stays hidden and the
   toggle is inert). Markup: blocks/pricing-toggle.html. */
.hzo-price-toggle {
    display: inline-flex;
    gap: var(--space-xs);
    padding: var(--space-xs);
    background: var(--surface, #fff);
    border: 1px solid var(--surface-border, var(--border));
    border-radius: var(--radius-pill);
}
.hzo-price-toggle > button {
    min-height: 44px;
    padding: 0 var(--space-lg);
    border: 0;
    border-radius: var(--radius-pill);
    background: transparent;
    font-family: var(--font-body);
    font-size: 15px;
    font-weight: var(--fw-semibold, 600);
    color: var(--on-surface-muted, var(--text-muted));
    cursor: pointer;
    transition: background var(--transition-base), color var(--transition-base);
}
.hzo-price-toggle > button[aria-pressed="true"] {
    background: var(--brand);
    color: var(--text-on-brand);
}
.hzo-price-toggle > button:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
    .hzo-price-toggle > button { transition: none; }
}
/* Period swap — :not() keeps monthly visible before JS seeds data-period. */
[data-hzo-price-group]:not([data-period="annual"]) [data-price-annual] { display: none; }
[data-hzo-price-group][data-period="annual"] [data-price-monthly] { display: none; }

/* ─── Trust row (.hzo-trust-row) ───────────────────────────────────────
   Thin spacing wrapper for a centered strip of trust signals — the block
   (blocks/trust-row.html) composes existing icon + text patterns; this
   wrapper only owns the list reset, gap rhythm, and icon tint. */
.hzo-trust-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--space-md) var(--space-xl);
    margin: 0;
    padding: 0;
    list-style: none;
}
.hzo-trust-row > li {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    color: var(--text-body);
    font-weight: var(--fw-medium, 500);
}
.hzo-trust-row svg { width: 22px; height: 22px; flex-shrink: 0; color: var(--accent-text); }

/* ─── 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);
}

/* ─── Author bio (.hzo-author-bio) ─────────────────────────────────────
   Horizontal byline card for post footers — a modifier on .hzo-card
   (avatar left, name/meta/bio right; the card's own gap spaces them).
   Wraps to a stack when the card gets narrow via flex-wrap — intrinsic,
   no container query needed (§1.24 note at the top of this batch).
   Markup: blocks/author-bio.html. */
.hzo-author-bio {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-start;
}
.hzo-author-bio__body {
    flex: 1 1 240px;   /* below ~240px of free space the text wraps under the avatar */
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
}
.hzo-author-bio__body > p { margin: 0; }

/* ─── Share row (.hzo-share-row) ───────────────────────────────────────
   Share links as 44px icon buttons — plain sharer/intent URLs only
   (Facebook sharer, X intent, LinkedIn, mailto): no SDKs, no trackers,
   zero network cost. Each link carries an aria-label; icons are inline
   SVGs riding currentColor. !important on colors fights per-client
   a:visited (same co-located pattern as a.hzo-tag). Markup:
   blocks/share-row.html. */
.hzo-share-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
}
a.hzo-share-row__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border: 1px solid var(--surface-border, var(--border));
    border-radius: var(--radius-pill);
    background: var(--surface, transparent);
    color: var(--on-surface-muted, var(--text-muted)) !important;
    text-decoration: none;
    transition: background var(--transition-base), border-color var(--transition-base), color var(--transition-base);
}
a.hzo-share-row__btn:visited { color: var(--on-surface-muted, var(--text-muted)) !important; }
a.hzo-share-row__btn:hover,
a.hzo-share-row__btn:visited:hover {
    background: var(--brand-pale);
    border-color: var(--brand);
    color: var(--brand-text) !important;
}
a.hzo-share-row__btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.hzo-share-row__btn svg { width: 18px; height: 18px; display: block; }
@media (prefers-reduced-motion: reduce) {
    a.hzo-share-row__btn { transition: none; }
}

/* ─── 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;
}

/* ─── Equal-height cards (.hzo-grid--equal-cards) ──────────────────────
   Stretches every card in a .hzo-grid to the full row height so CTAs
   pinned with margin-top:auto (.hzo-card__link, the pricing buttons)
   bottom-align across the row.

   Subgrid was evaluated and DEFERRED — honest assessment: aligning card
   footers with subgrid requires each card to become display:grid with
   grid-template-rows:subgrid plus an agreed row count, which would
   restructure .hzo-card's flex column (its gap, align-self behaviors, and
   margin-top:auto pinning) for every existing composition. height:100% +
   the card's own flex column already achieves the visible goal (equal
   cards, bottom-aligned CTAs) with zero risk to shipped cards. Revisit
   subgrid only if MID-card rows (e.g. matching price lines across tiers)
   ever need cross-card alignment. The `> *` rule covers cards nested one
   level deep (e.g. a link wrapper); grid items themselves already stretch. */
.hzo-grid--equal-cards > * { height: 100%; }
.hzo-grid--equal-cards .hzo-card { height: 100%; }

/* ─── 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);
    }
}

/* ─── 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-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; }

    /* 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;
    }
}
