/* CLEO Website Core — base stylesheet. Theme-token driven; do not hard-code
   brand colors here. Every generated site loads this file plus the inline
   :root token blocks core/theme.php and core/motion.php produce. Container
   width, section rhythm, shape language, and shadow strength all come from
   the site's Design Profile (see core/design-personalities.php) — this file
   should read as "how tokens are applied", not "what the tokens are". */

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
    margin: 0;
    background: var(--cleo-bg);
    color: var(--cleo-text);
    font-family: var(--cleo-font-body);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4 {
    font-family: var(--cleo-font-heading);
    letter-spacing: var(--cleo-heading-tracking, 0);
    line-height: 1.15;
    margin: 0 0 .5em;
    color: var(--cleo-primary);
}
h1 { font-size: clamp(2.1rem, 1.4rem + 3vw, 3.4rem); }
h2 { font-size: clamp(1.6rem, 1.2rem + 1.6vw, 2.3rem); }
h3 { font-size: clamp(1.15rem, 1rem + .6vw, 1.4rem); }
p { margin: 0 0 1em; color: var(--cleo-text); }
p.lead { font-size: clamp(1.05rem, 1rem + .3vw, 1.2rem); color: var(--cleo-muted); }
a { color: var(--cleo-primary); text-decoration: none; }
a:hover { text-decoration: underline; }
img { max-width: 100%; height: auto; display: block; }
.wrap { max-width: var(--cleo-container, 1140px); margin: 0 auto; padding: 0 20px; }

.skip-link { position: absolute; left: -999px; top: 0; background: var(--cleo-primary); color: #fff; padding: 10px 16px; z-index: 100; }
.skip-link:focus { left: 12px; top: 12px; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }

:where(a, button, input, select, textarea):focus-visible {
    outline: 3px solid var(--cleo-accent);
    outline-offset: 2px;
}

.site-header { border-bottom: 1px solid var(--cleo-border); background: var(--cleo-bg); position: sticky; top: 0; z-index: 20; transition: background-color .4s ease, border-color .4s ease; }

/* Transparent-over-hero navigation (only on pages whose first component is
   an immersive hero — see core/layout.php). The header leaves normal flow
   entirely so the hero can start at the true top of the viewport, reads
   light-on-dark until assets/js/site.js adds `.is-scrolled`, and only then
   becomes the same solid/sticky header every other page already uses. */
body.nav-transparent .site-header {
    position: fixed;
    top: 0; left: 0; right: 0;
    background: transparent;
    border-bottom-color: transparent;
}
body.nav-transparent .site-header .brand,
body.nav-transparent .site-header .primary-nav a { color: #fff; }
body.nav-transparent .site-header .primary-nav a[aria-current="page"] { color: #fff; text-decoration: underline; text-underline-offset: 4px; }
body.nav-transparent .site-header .nav-toggle { border-color: rgba(255,255,255,.6); color: #fff; }
/* No backdrop-filter here on purpose (2026-08-17 scroll-performance pass):
   this header is `position: fixed` once scrolled, so a blur here would
   recompute continuously for the ENTIRE rest of the page's scroll, not a
   bounded moment — by far the most expensive place blur could go. A
   near-solid background reads just as clean without that cost, and now
   matches every other page's plain (never-blurred) header exactly. */
body.nav-transparent .site-header.is-scrolled {
    background: color-mix(in srgb, var(--cleo-bg), transparent 3%);
    border-bottom-color: var(--cleo-border);
}
body.nav-transparent .site-header.is-scrolled .brand,
body.nav-transparent .site-header.is-scrolled .primary-nav a { color: var(--cleo-text); }
body.nav-transparent .site-header.is-scrolled .primary-nav a[aria-current="page"] { color: var(--cleo-primary); }
body.nav-transparent .site-header.is-scrolled .nav-toggle { border-color: var(--cleo-border); color: inherit; }
/* The mobile dropdown panel is always solid regardless of header state
   (readability first) — unaffected by the transparent treatment above. */
body.nav-transparent .primary-nav.open { background: var(--cleo-bg); }
body.nav-transparent .primary-nav.open a { color: var(--cleo-text); }
.nav-wrap { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; gap: 16px; }
.brand { font-family: var(--cleo-font-heading); font-weight: 700; font-size: 1.2rem; color: var(--cleo-primary); }
.nav-toggle { display: none; background: none; border: 1px solid var(--cleo-border); border-radius: var(--cleo-radius-sm); padding: 8px 10px; font-size: 1.1rem; }
.primary-nav { display: flex; gap: 22px; align-items: center; }
.primary-nav a { color: var(--cleo-text); font-weight: 600; font-size: .95rem; }
.primary-nav a[aria-current="page"] { color: var(--cleo-primary); }
.primary-nav a:hover { text-decoration: none; color: var(--cleo-primary); }

main { min-height: 60vh; }
section.cleo-section { padding: var(--cleo-section-space, 64px) 20px; }
section.cleo-section.alt { background: var(--cleo-surface); }
section.cleo-section.alt-2 { background: var(--cleo-surface-alt); }
/* A dedicated, warm-off-white section surface (derived from the brand's
   own secondary color, not the alternating .alt/.alt-2 rhythm) — available
   to any component/client for whom a warm-off-white "moment" matters more
   than the generic alternation, without touching cleo_surface_class(). */
section.cleo-section.cleo-surface-warm { background: var(--cleo-surface-alt); }
section.cleo-section.full-bleed { padding: 0; }
section.cleo-section.full-bleed .section-inner { max-width: none; padding: 0; }
.section-inner { max-width: var(--cleo-container, 1140px); margin: 0 auto; }
.section-inner.narrow { max-width: min(720px, 100%); }
/* --cleo-accent-text (optional, added 2026-08-28): a client's accent color
   isn't guaranteed to clear WCAG AA at small/bold text sizes against a
   light section background — found on The Squeegee Method's gold accent
   (2.4:1 against white, needs 4.5:1). Falls back to --cleo-accent itself
   when unset, so this is a no-op for every existing client; a client
   whose accent fails contrast at this weight/size defines this token
   (see clients/squeegee-method/assets/site/material.css) with an
   accessible variant of the SAME color family, not a palette change. */
.section-eyebrow { text-transform: uppercase; letter-spacing: .08em; font-size: .75rem; font-weight: 700; color: var(--cleo-accent-text, var(--cleo-accent)); }

.button {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 12px 22px;
    border-radius: var(--cleo-radius-lg);
    background: var(--cleo-primary);
    color: #fff;
    font-weight: 700;
    border: 0;
    cursor: pointer;
    box-shadow: var(--cleo-shadow);
}
.button:hover { text-decoration: none; opacity: .92; }
.button.outline { background: transparent; color: var(--cleo-primary); border: 2px solid var(--cleo-primary); box-shadow: none; }
.button.accent { background: var(--cleo-accent); color: #23180a; }

.grid { display: grid; gap: 24px; }
.grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
.grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.card {
    display: block;
    background: var(--cleo-bg);
    border: 1px solid var(--cleo-border);
    border-radius: var(--cleo-radius);
    padding: 24px;
    box-shadow: var(--cleo-shadow);
    text-decoration: none;
    color: inherit;
}

form.cleo-form { display: grid; gap: 14px; max-width: 520px; }
form.cleo-form label { display: grid; gap: 6px; font-weight: 600; font-size: .9rem; }
form.cleo-form input, form.cleo-form textarea, form.cleo-form select {
    padding: 11px 13px; border: 1px solid var(--cleo-border); border-radius: var(--cleo-radius-sm); font: inherit;
}

.faq-list { display: grid; gap: 12px; max-width: 760px; }
.faq-item { border: 1px solid var(--cleo-border); border-radius: var(--cleo-radius); padding: 16px 18px; }
.faq-item summary { cursor: pointer; font-weight: 700; }
.faq-item p { margin-top: 10px; margin-bottom: 0; }

.gallery-grid .cleo-media { border-radius: var(--cleo-radius); aspect-ratio: 4/3; object-fit: cover; }

.split-reversed .section-inner { direction: rtl; }
.split-reversed .split-media, .split-reversed .split-text { direction: ltr; }
.split-media { background: var(--cleo-surface); border-radius: var(--cleo-radius-lg); min-height: 200px; overflow: hidden; }
.split-media img { width: 100%; height: 100%; object-fit: cover; }

.team-card img { border-radius: var(--cleo-radius); aspect-ratio: 1; object-fit: cover; margin-bottom: 12px; }

/* Shared "plain numbered list" visual language (docs/DESIGN-SYSTEM.md's
   "Cinematic & immersive" section: card grids are not the default) — used
   by `service-grid`'s optional `layout: 'editorial-list'` and by
   `hotspot-map`'s zone list, so a service breakdown reads the same way
   wherever it appears on a site, without either component depending on
   the other or duplicating this CSS. */
.cleo-editorial-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.cleo-editorial-list-item {
    display: flex;
    gap: 16px;
    padding: 18px 10px;
    border-bottom: 1px solid var(--cleo-border);
    border-radius: var(--cleo-radius-sm);
    transition: background-color .3s ease;
    scroll-margin-top: 90px;
}
/* An item whose only child is itself a padded interactive element (the
   expandable <details>, or the "link elsewhere" <a>) shouldn't ALSO be
   flex/padded at the <li> level — that doubled the padding. The <li>
   becomes a plain wrapper; the child owns the whole row's layout. */
.cleo-editorial-list-item:has(> details),
.cleo-editorial-list-item:has(> a) {
    display: block;
    gap: 0;
    padding: 0;
}
.cleo-editorial-list-item:last-child { border-bottom: 0; }
.cleo-editorial-list-index {
    font-family: var(--cleo-font-heading);
    color: var(--cleo-accent);
    font-weight: 700;
    font-size: .85rem;
    min-width: 1.6em;
    line-height: 1.5;
}
.cleo-editorial-list-label { margin: 0 0 4px; font-size: 1.05rem; }
.cleo-editorial-list-desc { margin: 0; color: var(--cleo-muted); font-size: .92rem; }
.service-grid-editorial .cleo-editorial-list { max-width: 760px; margin-top: 20px; }

/* "A homepage service preview is never a dead end" (2026-08-22): a
   service-grid item with `link_items_to` set renders as this link
   instead of plain text — same flex layout the plain item already had
   (the item <li> itself stops laying anything out once it holds a link),
   plus a small "Learn more" hint so the affordance reads as intentional,
   not an accidental unstyled link. */
.cleo-editorial-list-link {
    display: flex;
    align-items: flex-start;
    gap: 16px;
    padding: 18px 10px;
    border-radius: var(--cleo-radius-sm);
    text-decoration: none;
    color: inherit;
    transition: background-color .3s ease;
}
.cleo-editorial-list-link:hover,
.cleo-editorial-list-link:focus-visible { background-color: var(--cleo-surface); }
.cleo-editorial-list-more,
.card .cleo-editorial-list-more {
    margin-left: auto;
    align-self: center;
    font-size: .82rem;
    font-weight: 600;
    color: var(--cleo-accent);
    white-space: nowrap;
    padding-left: 12px;
}
.cleo-editorial-list-more::after { content: 'Learn more →'; }
.card .cleo-editorial-list-more { display: block; margin: 12px 0 0; padding-left: 0; }

/* Service explorability (docs/DESIGN-SYSTEM.md's "Visual coverage across
   the whole site", 2026-08-22): an editorial-list service with a `detail`
   field renders as a native <details>/<summary> disclosure — the exact
   accessible pattern .faq-item already established, reused rather than
   duplicated. `.service-grid-item-expandable` widens the list's max-width
   slightly (media needs more room than plain text) and owns the border
   the plain .cleo-editorial-list-item rule would otherwise supply. */
.service-grid-item-expandable { border-bottom: 1px solid var(--cleo-border); }
.service-grid-item-expandable:last-child { border-bottom: 0; }
.service-detail-summary {
    display: flex;
    align-items: flex-start;
    gap: 16px;
    padding: 18px 10px;
    border-radius: var(--cleo-radius-sm);
    cursor: pointer;
    list-style: none;
    transition: background-color .3s ease;
}
.service-detail-summary::-webkit-details-marker { display: none; }
.service-detail-summary::marker { content: ''; }
.service-detail-summary:hover { background-color: var(--cleo-surface); }
.service-detail-toggle {
    margin-left: auto;
    align-self: center;
    font-size: .82rem;
    font-weight: 600;
    color: var(--cleo-accent);
    white-space: nowrap;
    padding-left: 12px;
}
.service-detail-toggle::after { content: 'Learn more ↓'; }
.service-detail[open] .service-detail-toggle::after { content: 'Show less ↑'; }
.service-detail-expanded { padding: 4px 10px 24px calc(1.6em + 16px + 10px); display: grid; gap: 16px; max-width: 640px; }
.service-detail-media { width: 100%; max-height: 320px; object-fit: cover; border-radius: var(--cleo-radius); }
.service-detail-copy p { margin: 0 0 12px; color: var(--cleo-muted); }
.service-detail-copy p:last-child { margin-bottom: 0; }
@media (max-width: 640px) {
    .service-detail-expanded { padding-left: 10px; max-width: none; }
}

/* Trust/proof: full-bleed image stage with a floating panel (right, on
   desktop) — the mirror image of hotspot-map's left-floating panel, for
   compositional variety across the two full-bleed/overlay moments on a
   homepage rather than a repeated pattern. */
.trust-proof-stage { position: relative; min-height: min(78vh, 720px); overflow: hidden; }
.trust-proof-media.cleo-media,
.trust-proof-media.hero-immersive-cinematic-fallback {
    position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 0; border-radius: 0;
}
.trust-proof-stage::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(260deg, rgba(0,0,0,.35) 0%, rgba(0,0,0,0) 45%);
    pointer-events: none;
}
.trust-proof-panel {
    position: relative;
    z-index: 1;
    background: var(--cleo-bg);
    padding: 40px clamp(20px, 6vw, 48px);
}
@media (min-width: 760px) {
    .trust-proof-panel {
        position: absolute;
        right: 0;
        top: 0;
        bottom: 0;
        display: flex;
        flex-direction: column;
        justify-content: center;
        width: min(440px, 92%);
        background: color-mix(in srgb, var(--cleo-bg), transparent 10%);
        backdrop-filter: blur(16px) saturate(1.1);
        -webkit-backdrop-filter: blur(16px) saturate(1.1);
        padding: 48px clamp(28px, 4vw, 48px);
    }
}
.trust-proof-points { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; }
.trust-proof-points li { padding-left: 18px; border-left: 2px solid var(--cleo-accent); font-size: 1.02rem; }
.trust-proof-below { max-width: var(--cleo-container, 1140px); margin: 0 auto; padding: var(--cleo-section-space, 64px) 20px 0; }
.trust-proof-preview { margin-top: 8px; }
.trust-proof-preview-grid { margin-bottom: 16px; }
.trust-proof-preview-grid .cleo-media { aspect-ratio: 4/3; border-radius: var(--cleo-radius); }
.trust-proof-preview-link { font-weight: 700; }
.trust-proof-reviews { margin-top: 40px; padding-bottom: var(--cleo-section-space, 64px); }
.trust-proof-reviews-placeholder { color: var(--cleo-muted); font-style: italic; margin: 0; }
/* Optional end_cta (components/trust-proof.php V3): closes the section in
   normal typographic flow — a divider and centered text — instead of a
   separate coloured banner block underneath it. */
.trust-proof-end-cta {
    margin-top: 8px;
    padding: 40px 0 var(--cleo-section-space, 64px);
    border-top: 1px solid var(--cleo-border);
    text-align: center;
}
.trust-proof-end-cta h2 { margin-bottom: .3em; font-size: clamp(1.8rem, 1.3rem + 2.4vw, 3rem); }
.trust-proof-end-cta .lead { max-width: 46ch; margin: 0 auto 20px; }

.hero-minimal .lead { max-width: 46ch; }

.hero-editorial-headline { max-width: 16ch; }
.hero-editorial-row { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 24px; margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--cleo-border); }
.hero-editorial-row .lead { margin: 0; max-width: 42ch; }

.hero-split .hero-split-text { display: flex; flex-direction: column; justify-content: center; }
.hero-split-media { min-height: 320px; }

.hero-immersive {
    position: relative;
    background: linear-gradient(135deg, var(--cleo-primary), color-mix(in srgb, var(--cleo-primary), var(--cleo-accent) 45%));
    color: #fff;
    overflow: hidden;
    min-height: min(88vh, 780px);
    display: flex;
    align-items: flex-end;
}
/* Photography-backed variant: the media layer replaces the gradient as the
   visible background; the gradient stays as the loading/fallback color
   behind it via .has-media so nothing ever flashes unstyled. */
.hero-immersive.has-media { background: var(--cleo-primary); }
.hero-immersive-media.cleo-media,
.hero-immersive-media.hero-immersive-video,
.hero-immersive-media.hero-immersive-cinematic-fallback {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: 0;
    border-radius: 0;
}
/* Tier 3 of the video-forward hierarchy (docs/DESIGN-SYSTEM.md): when a
   hero has neither real/placeholder video nor a real/placeholder image, it
   still isn't a flat static color — a "motion composition built from
   stills" (an animated gradient, see assets/css/motion.css) stands in.
   This rule is the static base (visible even with motion disabled); the
   drift animation itself lives in motion.css so a motion_personality of
   `minimal` ships none of it. */
.hero-immersive-cinematic-fallback {
    background:
        radial-gradient(120% 90% at 20% 15%, color-mix(in srgb, var(--cleo-accent), transparent 72%), transparent 60%),
        linear-gradient(135deg, var(--cleo-primary), color-mix(in srgb, var(--cleo-primary), var(--cleo-accent) 40%));
    background-size: 160% 160%, 100% 100%;
}
/* Even the zero-asset gradient fallback identifies itself as a placeholder
   (docs/DESIGN-SYSTEM.md's placeholder-media policy: never presented as
   final approved content), via the same small corner-badge pattern the
   image placeholder uses. */
.cleo-cinematic-fallback-caption {
    position: absolute;
    top: 16px;
    left: 20px;
    z-index: 1;
    max-width: min(46ch, calc(100% - 40px));
    font-size: .72rem;
    line-height: 1.4;
    color: rgba(255,255,255,.75);
    background: rgba(0,0,0,.35);
    padding: 8px 12px;
    border-radius: var(--cleo-radius-sm);
}
/* Found 2026-08-18 (Fenwick & Rowe Build-Spec stress test): a hero with no
   media at all falls to the zero-asset gradient fallback above, whose
   caption sits at the same fixed top:16px every corner-badge caption uses —
   but an immersive hero's `nav-transparent` header (above) is ALSO fixed at
   the true top of the viewport, so the two collide/overlap illegibly. Every
   real client site so far has always supplied at least a placeholder image
   or video for its hero, so this exact combination (immersive hero + zero
   real/placeholder media + transparent nav) was never previously exercised.
   Scoped narrowly to that one combination — has no effect wherever a hero
   already has media (the current state of every real client site). */
body.nav-transparent .hero-immersive-cinematic-fallback .cleo-cinematic-fallback-caption {
    top: 76px;
}
/* A dark scrim under the text, not just the raw gradient — guarantees readable contrast
   for white text regardless of how light a given brand's primary/accent colors are. */
.hero-immersive::before {
    content: "";
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, .45);
    z-index: 1;
}
/* V3: editorial, not centered — a large left-weighted text block near the
   bottom of the frame, so the footage carries most of the composition
   instead of a small centered card of text floating over it. */
.hero-immersive-inner {
    position: relative;
    z-index: 2;
    max-width: var(--cleo-container, 1140px);
    width: 100%;
    margin: 0 auto;
    /* Top padding is load-bearing, not decorative: this page's nav is
       `position: fixed` and transparent over the hero (body.nav-transparent),
       so nothing else reserves this space — without it, a tall wrapped
       headline can grow the section (min-height, not height) enough to
       collide with the nav bar sitting on top of it. */
    padding: 130px 24px calc(var(--cleo-section-space, 64px) * 1.3);
    text-align: left;
}
.hero-immersive h1, .hero-immersive p { color: #fff; }
/* The hero eyebrow sits over dark, scrimmed hero media, not a light
   section surface — it already clears contrast at full accent strength
   (measured 7.68:1 for Squeegee's gold), so it's deliberately exempted
   from the --cleo-accent-text swap above rather than inheriting it. */
.hero-immersive-inner .hero-immersive-eyebrow { color: var(--cleo-accent); }
.hero-immersive-inner h1 {
    max-width: 15ch;
    font-size: clamp(2.3rem, 1.3rem + 4.6vw, 5.4rem);
    margin-bottom: .28em;
}
.hero-immersive .lead { margin: 0 0 28px; max-width: 42ch; opacity: .92; text-align: left; }
.hero-immersive .button:not(.outline) { background: #fff; color: var(--cleo-primary); }
.hero-immersive .button.outline { border-color: rgba(255,255,255,.7); color: #fff; }

/* Ambient scroll parallax on the hero text (assets/js/motion.js), gated
   the same way as every other motion effect — a static `transform: none`
   is the fully correct state whenever JS hasn't set the custom property. */
html.motion-ready .hero-immersive-inner {
    transform: translateY(calc(var(--hero-parallax, 0) * -70px));
    opacity: calc(1 - var(--hero-parallax, 0) * .7);
}

/* Hero -> next-section hand-off: the bottom of the hero fades toward
   near-black so it dissolves into whatever dark-scrimmed full-bleed
   section follows (see `editorial-band`'s matching top fade), instead of
   cutting hard into a new rectangle. Layered above the media, below the
   text (z-index 1, same stacking context as the existing uniform scrim). */
.hero-immersive-handoff {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 38%;
    background: linear-gradient(to bottom, transparent, rgba(0,0,0,.94) 92%);
    z-index: 1;
    pointer-events: none;
}

/* Shared "real photo or clearly-labeled placeholder" media pattern
   (core/helpers.php's cleo_placeholder_media()) — used anywhere a component
   needs to go into a first build before final client photography exists. */
.cleo-media { width: 100%; height: 100%; object-fit: cover; }
.cleo-media-placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    min-height: 160px;
    background: repeating-linear-gradient(135deg, var(--cleo-surface-alt), var(--cleo-surface-alt) 10px, var(--cleo-surface) 10px, var(--cleo-surface) 20px);
    border: 2px dashed var(--cleo-border);
    border-radius: inherit;
    color: var(--cleo-muted);
    text-align: center;
    padding: 20px;
}
.cleo-media-placeholder-icon { font-size: 1.8rem; opacity: .6; }
.cleo-media-placeholder-caption { font-size: .82rem; line-height: 1.4; max-width: 34ch; }
/* Any placeholder used as a full-bleed background BEHIND real overlaid
   text (hero, editorial-band, trust-proof, hotspot-map — anywhere
   `cleo_placeholder_media()` is called with the shared `cleo-media-overlay`
   marker class) needs its caption out of the way of that text, not
   centered in the middle of the box where it would collide with headings,
   body copy, or a floating panel positioned anywhere in the section. This
   pins it to a small, consistent top-left corner badge instead — still
   clearly a placeholder, never mistaken for real content. */
.cleo-media-overlay.cleo-media-placeholder {
    border: 0;
    border-radius: 0;
    background: repeating-linear-gradient(135deg, rgba(255,255,255,.05), rgba(255,255,255,.05) 10px, rgba(255,255,255,.015) 10px, rgba(255,255,255,.015) 20px);
    align-items: flex-start;
    justify-content: flex-start;
}
.cleo-media-overlay .cleo-media-placeholder-icon,
.cleo-media-overlay .cleo-media-placeholder-caption {
    display: inline;
}
.cleo-media-overlay .cleo-media-placeholder-caption,
.cleo-media-overlay .cleo-media-placeholder-icon {
    position: absolute;
    top: 16px;
    left: 20px;
    z-index: 1;
    max-width: min(46ch, calc(100% - 40px));
    font-size: .72rem;
    line-height: 1.4;
    color: rgba(255,255,255,.75);
    background: rgba(0,0,0,.35);
    padding: 8px 12px;
    border-radius: var(--cleo-radius-sm);
}
.cleo-media-overlay .cleo-media-placeholder-icon { padding: 8px 10px; }
.cleo-media-overlay .cleo-media-placeholder-caption { top: 44px; }

/* Editorial band (components/editorial-band.php) — full-bleed connective
   moment between two bigger moments (typically hero -> signature). Text is
   asymmetrically positioned (align-left/center/right), never centered like
   the hero, so the two full-bleed sections read as distinct beats. */
.cleo-editorial-band {
    position: relative;
    min-height: min(62vh, 560px);
    display: flex;
    align-items: center;
    color: #fff;
    overflow: hidden;
    padding: 0;
}
.cleo-editorial-band .editorial-band-media.cleo-media,
.cleo-editorial-band .editorial-band-media.hero-immersive-cinematic-fallback {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: 0;
    border-radius: 0;
}
.cleo-editorial-band::before {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(100deg, rgba(0,0,0,.55) 0%, rgba(0,0,0,.2) 55%, rgba(0,0,0,.1) 100%);
    z-index: 1;
}
.cleo-editorial-band.align-right::before { background: linear-gradient(260deg, rgba(0,0,0,.55) 0%, rgba(0,0,0,.2) 55%, rgba(0,0,0,.1) 100%); }
.cleo-editorial-band.align-center::before { background: rgba(0,0,0,.4); }
/* Matches the hero's bottom hand-off fade (.hero-immersive-handoff) so a
   band placed directly after a hero reads as one continuous dissolve
   through black, not a hard cut into a new rectangle. Harmless when a
   band follows something else — it's a small top-edge darkening only. */
.cleo-editorial-band::after {
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 22%;
    background: linear-gradient(to bottom, rgba(0,0,0,.75), transparent);
    z-index: 1;
    pointer-events: none;
}
.editorial-band-inner {
    position: relative;
    z-index: 2;
    max-width: var(--cleo-container, 1140px);
    width: 100%;
    margin: 0 auto;
    padding: 56px 24px;
}
.editorial-band-inner h1, .editorial-band-inner h2, .editorial-band-inner p { color: #fff; }
/* V3: a full-bleed narrative moment earns display-scale type, not the
   default boxed h2 size used inside cards/forms/grids elsewhere. */
.editorial-band-inner h1, .editorial-band-inner h2 { font-size: clamp(2rem, 1.3rem + 3.2vw, 3.8rem); max-width: 16ch; }
.editorial-band-inner .lead { max-width: 46ch; margin: 0; opacity: .92; }
.cleo-editorial-band.align-left .editorial-band-inner { text-align: left; }
.cleo-editorial-band.align-right .editorial-band-inner { text-align: right; margin-left: auto; }
.cleo-editorial-band.align-right .editorial-band-inner .lead,
.cleo-editorial-band.align-right .editorial-band-inner h1,
.cleo-editorial-band.align-right .editorial-band-inner h2 { margin-left: auto; }
.cleo-editorial-band.align-center .editorial-band-inner { text-align: center; }
.cleo-editorial-band.align-center .editorial-band-inner .lead,
.cleo-editorial-band.align-center .editorial-band-inner h1,
.cleo-editorial-band.align-center .editorial-band-inner h2 { margin-left: auto; margin-right: auto; }
.cleo-editorial-band .button { margin-top: 8px; }
.cleo-editorial-band .button:not(.outline) { background: #fff; color: var(--cleo-primary); }
.cleo-editorial-band .button.outline { border-color: rgba(255,255,255,.7); color: #fff; }

/* Media sequence (components/media-sequence.php) — a paced full-bleed
   visual story: one slide per item, alternating left/right, an oversized
   faint index number behind the heading (typography as composition, not
   just a label). See that file's doc comment for why this is deliberately
   NOT scroll-jacked. */
.media-sequence-intro {
    max-width: var(--cleo-container, 1140px);
    margin: 0 auto;
    padding: var(--cleo-section-space, 64px) 20px 8px;
}
.media-sequence-slide {
    position: relative;
    min-height: min(78vh, 720px);
    display: flex;
    align-items: center;
    color: #fff;
    overflow: hidden;
}
.media-sequence-media.cleo-media,
.media-sequence-media.hero-immersive-video,
.media-sequence-media.hero-immersive-cinematic-fallback {
    position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 0; border-radius: 0;
}
.media-sequence-slide::before {
    content: "";
    position: absolute;
    inset: 0;
    /* Flat and uniform, not a directional fade (2026-08-23). A gradient
       that fades toward one side left a corner of the frame under-covered
       for whatever media happened to sit there -- found by actually
       rendering a real slide, not from the gradient values alone. A flat
       scrim gives consistent white-text-on-photo contrast everywhere in
       the frame. (A screenshot's own baked-in UI text is a different,
       harder problem no scrim opacity actually solves -- see
       cleo_component_media_sequence()'s 'inset' media style, which
       sidesteps it instead of fighting it with more darkness.) */
    background: rgba(0,0,0,.5);
    z-index: 1;
}
.media-sequence-slide-inner {
    position: relative;
    z-index: 2;
    max-width: var(--cleo-container, 1140px);
    width: 100%;
    margin: 0 auto;
    padding: 48px 24px;
}
.media-sequence-slide.align-left .media-sequence-slide-inner { text-align: left; }
.media-sequence-slide.align-right .media-sequence-slide-inner { text-align: right; margin-left: auto; }
.media-sequence-index {
    display: block;
    font-family: var(--cleo-font-heading);
    font-size: clamp(4.5rem, 3rem + 8vw, 11rem);
    font-weight: 700;
    line-height: .8;
    color: var(--cleo-accent);
    opacity: .5;
    margin-bottom: -.15em;
}
.media-sequence-slide h3 { color: #fff; font-size: clamp(1.8rem, 1.2rem + 3vw, 3.2rem); max-width: 18ch; margin: 0 0 .3em; }
.media-sequence-slide.align-right h3 { margin-left: auto; }
.media-sequence-slide p { color: rgba(255,255,255,.9); max-width: 46ch; font-size: 1.05rem; margin: 0; }
.media-sequence-slide.align-right p { margin-left: auto; }
.media-sequence-slide:not(:last-child) { border-bottom: 1px solid rgba(255,255,255,.08); }

/* 'inset' media style (components/media-sequence.php) — the image sits in
   its own frame next to the text instead of behind it as a full-bleed
   cover. See that file's comment: a scrim can't fix a screenshot's own
   internal text contrast, so this sidesteps the collision instead of
   trying to darken past it. */
.media-sequence-slide.media-inset { background: var(--cleo-primary); padding: 64px 0; }
.media-sequence-slide.media-inset::before { display: none; }
.media-sequence-slide.media-inset .media-sequence-slide-inner { padding: 0 24px; }
.media-sequence-inset-figure { margin: 28px 0 0; max-width: 640px; }
.media-sequence-slide.align-right.media-inset .media-sequence-inset-figure { margin-left: auto; }
.media-sequence-inset-image {
    display: block;
    width: 100%;
    height: auto;
    max-height: 360px;
    object-fit: cover;
    object-position: top;
    border-radius: var(--cleo-radius);
    box-shadow: 0 30px 60px -20px rgba(0,0,0,.6);
}
.media-sequence-inset-figure .hero-immersive-cinematic-fallback { position: static; height: 220px; }

@media (max-width: 760px) {
    .media-sequence-slide { min-height: 62vh; }
    .media-sequence-slide.align-right .media-sequence-slide-inner,
    .media-sequence-slide.align-right h3,
    .media-sequence-slide.align-right p { text-align: left; margin-left: 0; }
}

/* Before/after (components/before-after.php) — a range-input-driven
   transformation reveal. The frame's aspect-ratio is fixed so the layout
   never jumps as the slider moves; both images are absolutely stacked at
   full size, and only the "after" layer is clipped via the --reveal custom
   property the range input writes to.

   V3.1 fix (2026-08-17): the visible handle IS the real <input
   type="range"> now, not a decorative div next to a separate control below
   the frame. The input is stretched to cover the whole frame with a
   transparent track and a custom-styled (but real) thumb, so the entire
   image is the drag surface and mouse/touch/keyboard all come from the one
   native control — no bespoke pointer-event code, nothing for a drag to
   miss. `.before-after-divider`/`.before-after-handle` stay as a purely
   decorative (`pointer-events:none`) visual layer under the input, so the
   thumb's real position and the drawn line/circle always match exactly. */
.cleo-before-after .before-after-demo-note {
    font-size: .85rem;
    color: var(--cleo-muted);
    margin: -8px 0 20px;
}
.before-after-frame {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 10;
    max-height: 78vh;
    border-radius: var(--cleo-radius, 12px);
    overflow: hidden;
    background: #111;
}
.before-after-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 0;
}
.before-after-before-wrap {
    position: absolute;
    inset: 0;
    clip-path: inset(0 calc(100% - var(--reveal)) 0 0);
}
.before-after-before-wrap .before-after-img { position: absolute; inset: 0; }
.before-after-tag {
    position: absolute;
    top: 16px;
    z-index: 2;
    pointer-events: none;
    font-size: .75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: #fff;
    background: rgba(0,0,0,.45);
    padding: 6px 12px;
    border-radius: var(--cleo-radius-sm, 6px);
}
.before-after-tag-before { left: 16px; }
.before-after-tag-after { right: 16px; }
.before-after-divider {
    position: absolute;
    top: 0;
    bottom: 0;
    left: var(--reveal);
    width: 3px;
    margin-left: -1.5px;
    background: rgba(255,255,255,.9);
    box-shadow: 0 0 0 1px rgba(0,0,0,.25);
    pointer-events: none;
    z-index: 2;
}
.before-after-handle {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: #fff;
    color: var(--cleo-primary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.1rem;
    box-shadow: 0 2px 10px rgba(0,0,0,.35);
}
/* The real control: full-frame, transparent track, invisible thumb sized
   to match the decorative handle above so hit-testing feels obvious even
   though nothing about the thumb itself is drawn — the decorative circle
   underneath is what renders, this is what's actually draggable/focusable.

   A custom squeegee-shaped cursor was tried here (Clarity Engine Phase 1,
   2026-08-18) and removed the same day after visual review — at cursor
   size it read as a generic tool/hammer shape rather than a squeegee, so
   it was working against the polish it was meant to add. Plain `ew-resize`
   is the correct, honest cursor for a horizontal drag control. */
.before-after-range {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    z-index: 3;
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    background: transparent;
    cursor: ew-resize;
}
.before-after-range::-webkit-slider-runnable-track { background: transparent; }
.before-after-range::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: transparent;
    border: 0;
    cursor: ew-resize;
}
.before-after-range::-moz-range-track { background: transparent; border: 0; height: 100%; }
.before-after-range::-moz-range-thumb {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: transparent;
    border: 0;
    cursor: ew-resize;
}
.before-after-range:focus-visible { outline: none; }
.before-after-range:focus-visible ~ .before-after-divider .before-after-handle {
    outline: 3px solid var(--cleo-accent);
    outline-offset: 2px;
}

@media (max-width: 760px) {
    .before-after-frame { aspect-ratio: 4 / 5; max-height: none; }
}

/* Before/after multi-pair tabs (2026-08-27) — see components/before-after.php
   and assets/js/before-after.js. Empty/absent with 0-1 pairs (nothing to
   switch between); a real service tag on the photo itself always shows
   which result is on screen, tabs or not. */
.before-after-tabs { display: flex; flex-wrap: wrap; gap: 10px; margin: 0 0 16px; }
.before-after-tab {
    font: inherit;
    font-size: .85rem;
    color: var(--cleo-muted);
    background: none;
    border: 1px solid var(--cleo-border);
    border-radius: var(--cleo-radius-sm, 6px);
    padding: 8px 16px;
    cursor: pointer;
}
.before-after-tab.active { color: var(--cleo-text); border-color: var(--cleo-accent); }
.before-after-service-tag {
    position: absolute;
    top: 16px;
    left: 16px;
    z-index: 3;
    font-size: .72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--cleo-bg);
    background: var(--cleo-accent);
    padding: 6px 12px;
    border-radius: var(--cleo-radius-sm, 6px);
}

/* Stat row (components/stat-row.php) — boxless numerals over a hairline
   row, column-divided. No card backgrounds/borders by design; see that
   component's doc comment for why (restraint over boxes). */
.cleo-stat-row .stat-row-head { max-width: 640px; margin: 0 auto 40px; }
.stat-row-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    border-top: 1px solid var(--cleo-border);
}
.stat-row-item {
    padding: 32px 24px 0;
    border-left: 1px solid var(--cleo-border);
}
.stat-row-item:first-child { border-left: none; padding-left: 0; }
.stat-row-value {
    font-family: var(--cleo-font-heading);
    font-weight: 600;
    font-size: clamp(2.2rem, 1.6rem + 2.6vw, 3.2rem);
    color: var(--cleo-accent);
}
.stat-row-label { margin-top: 10px; font-size: .92rem; color: var(--cleo-muted); max-width: 28ch; line-height: 1.5; }
@media (max-width: 760px) {
    .stat-row-grid { grid-template-columns: 1fr; border-top: none; }
    .stat-row-item { border-left: none; border-top: 1px solid var(--cleo-border); padding: 22px 0 0; }
    .stat-row-item:first-child { border-top: none; }
}

/* Service swatches (components/service-swatches.php) — a bordered "material
   sample" tile grid: photo, numbered tag, name, short description. The
   photo carries [data-reveal-focus] (blur-to-sharp) independently of the
   tile's own [data-reveal] fade+rise — two coexisting entrance moments on
   purpose, see that component's doc comment. */
.cleo-service-swatches .section-head { max-width: 640px; margin: 0 auto 40px; text-align: center; }
.service-swatches-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 24px;
}
.service-swatch { background: var(--cleo-bg); border: 1px solid var(--cleo-border); }
.service-swatch-media { position: relative; aspect-ratio: 1 / 1; overflow: hidden; }
.service-swatch-media .cleo-media,
.service-swatch-media .service-swatch-media-img { width: 100%; height: 100%; object-fit: cover; }
.service-swatch-tag {
    position: absolute;
    top: 10px;
    left: 10px;
    font-family: var(--cleo-font-heading);
    font-size: .75rem;
    padding: 4px 9px;
    background: var(--cleo-primary);
    color: var(--cleo-accent);
    letter-spacing: .04em;
}
.service-swatch-body { padding: 18px 20px 22px; }
.service-swatch-name { font-family: var(--cleo-font-heading); font-weight: 600; font-size: 1rem; margin-bottom: 6px; }
.service-swatch-desc { font-size: .85rem; line-height: 1.55; color: var(--cleo-muted); margin: 0; }
.service-swatches-note { max-width: 640px; margin: 28px auto 0; text-align: center; font-size: .9rem; color: var(--cleo-muted); line-height: 1.6; }

/* Cinematic band (components/cinematic-band.php) — a wide, full-bleed
   "film strip" video moment. object-fit:cover on a fixed wide aspect
   ratio (default 21:9, overridable per instance via --cinematic-band-ratio)
   keeps it reading as an editorial frame rather than a standard embedded
   video card; no controls are ever rendered (muted/loop/playsinline only,
   see the component's own doc comment for the autoplay/pause mechanism). */
.cleo-cinematic-band { background: var(--cleo-primary); }
.cinematic-band-frame {
    position: relative;
    width: 100%;
    aspect-ratio: var(--cinematic-band-ratio, 21 / 9);
    overflow: hidden;
}
.cinematic-band-media {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.cinematic-band-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--cleo-surface);
    color: var(--cleo-muted);
    font-size: .9rem;
    text-align: center;
    padding: 20px;
}
.cinematic-band-overlay {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    padding: 40px clamp(20px, 5vw, 64px);
    background: linear-gradient(to top, rgba(0,0,0,.55), transparent);
}
.cinematic-band-overlay .section-eyebrow { color: #fff; opacity: .85; }
.cinematic-band-overlay h2 { color: #fff; margin: 0; max-width: 28ch; }
.cinematic-band-caption {
    position: absolute;
    right: 16px;
    bottom: 16px;
    font-size: .7rem;
    color: rgba(255,255,255,.65);
    letter-spacing: .03em;
}
@media (max-width: 760px) {
    .cinematic-band-frame { aspect-ratio: 4 / 5; }
    .cinematic-band-overlay { padding: 24px 20px; }
}

.cta-banner {
    position: relative;
    background: linear-gradient(120deg, var(--cleo-primary), color-mix(in srgb, var(--cleo-primary), var(--cleo-accent) 40%));
    color: #fff;
}
.cta-banner::before { content: ""; position: absolute; inset: 0; background: rgba(0, 0, 0, .45); }
.cta-banner .section-inner { position: relative; }
.cta-banner h2, .cta-banner p { color: #fff; }
.cta-banner .button:not(.outline) { background: #fff; color: var(--cleo-primary); }

@media (max-width: 760px) {
    .hero-editorial-row { flex-direction: column; align-items: flex-start; }
    .hero-split-media { min-height: 220px; order: -1; }
}

.site-footer { background: var(--cleo-primary); color: #fff; margin-top: 40px; }
.footer-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; padding: 48px 20px; }
.footer-grid h2, .footer-grid h3 { color: #fff; }
.footer-grid p { color: rgba(255,255,255,.85); }
.footer-grid a { color: #fff; display: block; margin-bottom: 6px; }
.footer-bottom { display: flex; justify-content: space-between; padding: 16px 20px; border-top: 1px solid rgba(255,255,255,.15); font-size: .85rem; opacity: .85; }
.footer-credit { opacity: .8; }

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    * { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

/* Nav's own breakpoint (920px), deliberately wider than the 760px content
   breakpoint below (2026-08-28 tablet QA finding): a brand name plus 7 nav
   links needs ~870px to lay out on one line without wrapping (measured via
   scrollWidth) — using the same 760px threshold as the content grid meant
   a ~768-920px tablet fell into the "desktop nav" branch and got mid-word
   wraps ("Service" / "Areas") instead of the clean hamburger menu it
   actually needed. Everything else (grids, footer, section padding)
   genuinely doesn't need extra room until much narrower, so that content
   breakpoint stays at 760px unchanged. */
@media (max-width: 920px) {
    .nav-toggle { display: inline-flex; }
    /* `fixed` + `bottom:0`, not `absolute` + auto height (pre-2026-08-28
       bug): an auto-height dropdown only covers its own link list, so
       whatever's underneath (hero text/CTAs on a nav-transparent page)
       shows through in a hard-edged strip right below the last link.
       `--cleo-mobile-nav-top` is measured from the header's real rendered
       height by site.js on open, so this needs no assumption about
       header height across clients/pages; 73px only covers the instant
       before that JS has run. */
    .primary-nav { position: fixed; top: var(--cleo-mobile-nav-top, 73px); left: 0; right: 0; bottom: 0; overflow-y: auto; background: var(--cleo-bg); flex-direction: column; align-items: flex-start; padding: 16px 20px; border-bottom: 1px solid var(--cleo-border); display: none; }
    .primary-nav.open { display: flex; }
}

@media (max-width: 760px) {
    .grid.cols-2, .grid.cols-3 { grid-template-columns: 1fr; }
    .footer-grid { grid-template-columns: 1fr; }
    section.cleo-section { padding: calc(var(--cleo-section-space, 64px) * .65) 18px; }
}
