/*
 * Motley CMS — Frontend Stylesheet
 * ─────────────────────────────────
 * This file controls the public-facing site appearance.
 * Replace this file to reskin the frontend for a new client.
 *
 * Colour palette (see :root block below for values):
 *   Primary:   --color-primary (orange)
 *   Secondary: --color-secondary (green)
 *   Dark:      --color-dark (nav/footer)
 *   Error:     --color-error (red)
 */

/* ── Design Tokens ─────────────────────────────── */
/* To reskin: change the values here. One variable = one design decision. */
:root {
    /* Primary brand colour — headings, links, buttons, active nav, CTAs, stats, form focus */
    --color-primary:         #f04e23;
    --color-primary-hover:   #d4431e;
    --color-primary-tint:    #fdf5f3;
    --color-primary-rgb:     240, 78, 35;
    /* The brand colour where it must READ, not just decorate: body links, small
       labels, and button grounds carrying white text. #f04e23 is 3.6:1 against
       white — fine for headings and large text (3:1), below the 4.5:1 WCAG
       1.4.3 requires at body size. #cf3d11 is the same hue at 4.85:1. A
       published theme pins this variable to its own --color-primary (see
       ThemeTokens::cssVariablesRule), so themed installs render exactly as they
       did; only the un-themed default palette darkens. */
    --color-primary-text:    #cf3d11;

    /* Secondary accent — success states, badges, footer links, pricing checkmarks */
    --color-secondary:       #8cc14d;
    --color-secondary-hover: #7ab03e;

    /* Readable foreground for anything sitting ON the secondary colour. The
       stock secondary is a light green: white measures 2.13:1 against it, well
       under the 4.5:1 WCAG 1.4.3 requires, while #333 measures 5.93:1 and 4.88:1
       on the hover shade. `.event-badge.upcoming` already used #333 on this
       exact ground for this exact reason; the button variants did not, which is
       how the same measured fault came back on a different selector.

       Unlike --color-primary-text this is NOT pinned to a theme's own colour in
       ThemeTokens::cssVariablesRule: it is a foreground, so pinning it to its
       own background would render text invisible. A theme whose secondary is
       DARK should set this token to a light value. */
    --color-secondary-text:  #333;

    /* Navigation bar and footer background */
    --color-dark:            #494040;
    --color-dark-border:     #5a5252;

    /* Error states, draft badges */
    --color-error:           #c14e48;

    /* Backgrounds */
    --color-bg-alt:          #f5f5f5;
    --color-bg-warm:         #f9f8f6;
    --color-bg-subtle:       #f9f9f9;

    /* Borders */
    --color-border:          #e5e7eb;

    /* Font — must be loaded via a @import or <link> in the layout if changing */
    --font-family:           'Open Sans', Helvetica Neue, Helvetica, Arial, sans-serif;

    /* Max width of all page content */
    --container-width:       1140px;

    /* Vertical space between page builder blocks */
    --block-gap:             2.5rem;

    /* Corner rounding — the three tiers derive from --radius-base (Theme Studio's
       "Corner rounding" control) so the whole site rounds/squares coherently.
       sm: buttons, md: cards/images, lg: modals/large cards. */
    --radius-base:           4px;
    --radius-sm:             var(--radius-base, 4px);
    --radius-md:             calc(var(--radius-base, 4px) * 1.5);
    --radius-lg:             calc(var(--radius-base, 4px) * 2);
}

/* ── Reset ─────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { font-size: var(--font-size-base, 16px); scroll-behavior: smooth; }
/* Scrollbar colour (standard property — Firefox + Chromium 121+; older browsers
   keep the OS scrollbar). Themeable via Theme Studio; inherits to scroll panes. */
html { scrollbar-color: var(--scrollbar-thumb, #c1c1c1) var(--scrollbar-track, #f1f1f1); }

/* ── Accessibility ─────────────────────────────── */
/* Skip link (WCAG 2.4.1). Off-screen rather than display:none, which would drop
   it from the tab order and defeat the point. It must out-rank any sticky header
   or announcement bar when focused, hence the z-index. */
.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 10000;
    padding: 0.75rem 1.25rem;
    background: var(--color-dark, #222);
    color: #fff;
    font-weight: 600;
    text-decoration: none;
    border-radius: 0 0 4px 0;
}
.skip-link:focus {
    left: 0;
    outline: 3px solid var(--color-primary, #f04e23);
    outline-offset: 2px;
}
/* The skip target is tabindex="-1" purely to receive programmatic focus; it must
   never draw a focus ring, or the whole page outlines after a skip. */
main:focus { outline: none; }

/* ── Base ──────────────────────────────────────── */
body {
    font-family: var(--font-family);
    font-size: 1rem;
    font-weight: var(--weight-body, 400);
    line-height: var(--lh-body, 1.6);
    color: var(--color-text, #333);
    background: var(--color-page-bg, #fff);
    /* Stops the u-full-bleed layout helper's 100vw width triggering a
       horizontal scrollbar. `clip` (not `hidden`) so it never turns body into
       a scroll container and breaks position:sticky elements. */
    overflow-x: clip;
}
@supports not (overflow-x: clip) {
    body { overflow-x: hidden; }
}
a { color: var(--color-link, var(--color-primary-text)); text-decoration: none; }
a:hover { color: var(--color-primary-hover); text-decoration: underline; }
img { max-width: 100%; height: auto; display: block; }

/* ── Container ─────────────────────────────────── */
.container { max-width: var(--container-width); margin: 0 auto; padding: 0 1.25rem; }

/* ── Preview Banner ────────────────────────────── */
.preview-banner {
    background: var(--color-error);
    color: #fff;
    text-align: center;
    padding: 0.5rem 1rem;
    font-weight: 700;
    font-size: 0.875rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}
.draft-badge {
    display: inline-block;
    background: var(--color-error);
    color: #fff;
    font-size: 0.7rem;
    font-weight: 700;
    padding: 0.15rem 0.5rem;
    border-radius: 3px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    vertical-align: middle;
    margin-left: 0.5rem;
}

/* ── Admin Bar ─────────────────────────────────── */
.admin-bar {
    background: #000;
    color: #fff;
    padding: 0.4rem 1.25rem;
    font-size: 0.8rem;
    display: flex;
    justify-content: flex-end;
    gap: 1rem;
}
.admin-bar a { color: #fff; }
.admin-bar a:hover { color: var(--color-secondary); text-decoration: none; }

/* ── Header ────────────────────────────────────── */
.site-header {
    background: #fff;
    border-bottom: 3px solid var(--color-primary);
    padding: 1rem 0;
}
.site-header .container {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 1rem;
}
.site-name { font-size: 1.5rem; font-weight: 700; color: #333; }
.site-name a { color: inherit; }
.site-name a:hover { text-decoration: none; }
.site-strapline { font-size: 0.85rem; color: #707070; font-weight: 400; }

/* ── Navigation ────────────────────────────────── */
.site-nav { background: var(--nav-bg, var(--color-dark)); }
.nav-main { list-style: none; display: flex; flex-wrap: wrap; gap: 0; }
.nav-item { position: relative; }
.nav-item > a {
    display: block;
    padding: 0.75rem 1.15rem;
    color: var(--nav-link-color, #fff);
    font-family: var(--font-nav, inherit);
    font-size: var(--nav-link-size, 0.9rem);
    font-weight: 700;
    transition: background 0.2s ease, color 0.2s ease;
}
.nav-item > a:hover { background: var(--nav-hover-bg, var(--color-primary-text)); color: var(--nav-hover-color, #fff); text-decoration: none; }
.nav-item.active > a { background: var(--nav-active-bg, var(--color-primary-text)); color: var(--nav-active-color, #fff); text-decoration: none; }
/* The dropdown chevron button on a LINKED parent exists only below 768px: above
   it the submenu opens on hover and :focus-within, so a second visible control
   would change every install's desktop nav for no gain (26.10.15). */
.nav-sub-toggle { display: none; }
.nav-sub {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    background: var(--nav-dropdown-bg, #fff);
    min-width: 220px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.12);
    z-index: 100;
    list-style: none;
}
/* :focus-within is load-bearing, not decorative: the submenu is display:none, so
   without it every dropdown child is out of the tab order and unreachable by
   keyboard (WCAG 2.1.1). Keep it paired with :hover on every rule that reveals a
   .nav-sub. Separate selectors, not a :is() group — an unsupported pseudo-class
   inside a comma list invalidates the whole rule in older browsers. */
.nav-item:hover .nav-sub { display: block; }
.nav-item:focus-within .nav-sub { display: block; }
.nav-sub a {
    display: block;
    padding: 0.6rem 1.15rem;
    color: var(--nav-dropdown-color, #333);
    font-family: var(--font-nav, inherit);
    font-size: 0.85rem;
    border-bottom: 1px solid #f0f0f0;
    transition: background 0.15s ease, color 0.15s ease;
}
.nav-sub a:hover { background: var(--nav-hover-bg, var(--color-primary-text)); color: var(--nav-hover-color, #fff); text-decoration: none; }

/* Nav item icons (ICON-PICKER) */
.nav-icon-wrap { display: inline-flex; align-items: center; vertical-align: -0.125em; margin-right: 0.4rem; }
.nav-icon { width: 1.05em; height: 1.05em; }

/* Nav heading (dropdown parent only) */
.nav-heading .nav-label {
    display: block;
    padding: 0.75rem 1.15rem;
    color: var(--nav-link-color, #fff);
    font-family: var(--font-nav, inherit);
    font-size: var(--nav-link-size, 0.9rem);
    font-weight: 700;
    cursor: default;
    transition: background 0.2s ease, color 0.2s ease;
}
/* A heading that owns a dropdown renders as <button> so it is focusable and
   keyboard-operable; this strips the UA chrome so it still looks like a label. */
button.nav-label {
    width: 100%;
    text-align: left;
    background: none;
    border: 0;
    cursor: pointer;
    line-height: inherit;
}
.nav-heading:hover .nav-label { background: var(--nav-hover-bg, var(--color-primary-text)); color: var(--nav-hover-color, #fff); }
.nav-heading:focus-within .nav-label { background: var(--nav-hover-bg, var(--color-primary-text)); color: var(--nav-hover-color, #fff); }
.nav-heading:hover .nav-sub { display: block; }
.nav-heading:focus-within .nav-sub { display: block; }

/* The main menu's third level flies out beside its dropdown item. The hide rule
   must come after every rule above that reveals a .nav-sub: those match any
   .nav-sub inside the row, so without it the third level opens together with
   the second. Its chevron exists only below 768px, like the top level's. */
.nav-sub-parent { position: relative; }
.nav-main .nav-sub .nav-sub { display: none; top: 0; left: 100%; }
.nav-main .nav-sub-parent:hover > .nav-sub { display: block; }
.nav-main .nav-sub-parent:focus-within > .nav-sub { display: block; }
/* Under the last top-level item it opens to the left, or it runs off the page. */
.nav-main > .nav-item:not(:has(~ .nav-item)) .nav-sub .nav-sub { left: auto; right: 100%; }
.nav-sub-parent > a { position: relative; padding-right: 2rem; }
.nav-sub-parent > a::after {
    content: "";
    position: absolute;
    right: 0.9rem;
    top: 50%;
    margin-top: -0.25rem;
    width: 0.4rem;
    height: 0.4rem;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(-45deg);
    pointer-events: none;
}
.nav-sub-parent-toggle { display: none; }

/* Inactive nav items (admins only) */
.nav-item.inactive > a { opacity: 0.5; border-bottom: 2px dashed var(--color-error); }
.nav-item.inactive > a::after { content: ' [draft]'; font-size: 0.7rem; text-transform: uppercase; color: var(--color-error); }
.nav-sub .inactive a { opacity: 0.5; font-style: italic; }

/* Mobile nav toggle */
.nav-toggle {
    display: none;
    background: var(--color-primary-text, var(--color-primary));
    color: #fff;
    border: none;
    padding: 0.6rem 1rem;
    font-size: 1rem;
    cursor: pointer;
    font-weight: 700;
}

/* ── Typography ────────────────────────────────── */
/* Each heading level takes its OWN font / colour / size token (Theme Studio
   Typography tab) so H1, H2 and H3 can be styled independently. Every token
   falls back to the previous hard-coded value (colour falls back to
   --color-primary), so an untouched site is pixel-identical. H4-H6 are
   unchanged (they inherit the body font + colour as before). */
h1 { font-family: var(--font-h1, inherit); font-size: var(--font-size-h1, 1.75rem); font-weight: var(--weight-h1, 700); color: var(--color-h1, var(--color-primary)); margin-bottom: 0.75rem; line-height: var(--lh-h1, 1.25); }
h2 { font-family: var(--font-h2, inherit); font-size: var(--font-size-h2, 1.45rem); font-weight: var(--weight-h2, 700); color: var(--color-h2, var(--color-primary)); margin-bottom: 0.6rem; line-height: var(--lh-h2, 1.3); }
h3 { font-family: var(--font-h3, inherit); font-size: var(--font-size-h3, 1.2rem); font-weight: var(--weight-h3, 700); color: var(--color-h3, var(--color-primary)); margin-bottom: 0.5rem; line-height: var(--lh-h3, 1.35); }
p { margin-bottom: 1rem; }

/* ── Page Content ──────────────────────────────── */
.page-hero { background: var(--color-bg-alt); padding: 2.5rem 0; margin-bottom: 2rem; border-bottom: 1px solid #e0e0e0; }
/* The wrapper heading sizes below are RATIOS of the Theme Studio h1/h2/h3 tokens,
   not literals. A literal here outranks the token by specificity, so `h1` above
   would honour a themed type scale and every real page would quietly ignore it —
   the h1 a visitor actually sees lives inside one of these wrappers.
   Each multiplier is the old literal divided by that token's default, so a site
   that has never opened Theme Studio renders pixel-identically. */
.page-hero h1, .page-hero-heading { margin: 0 0 0.25rem 0; font-size: calc(var(--font-size-h1, 1.75rem) * 1.143); font-weight: 700; line-height: 1.2; }
.page-hero .excerpt { font-size: 1.05rem; }
/* The banner caption is excluded from this grey, and the exclusion is the fix,
   not tidying: the caption carries `.excerpt`, so this rule matched it DIRECTLY,
   and a directly-matched declaration beats the white the caption INHERITS from
   the banner however specific the banner's own rule is. The caption came
   out #555 over a dark photo while the heading beside it came out white. Same
   trap as `.static-hero__heading` below, through a second door.
   `:not()` rather than deleting the rule, because it still colours every other
   `.excerpt` under `.page-hero`. The caption's own muted grey now arrives via
   --banner-caption-auto instead, which is the same colour by a route a theme
   can override. */
.page-hero .excerpt:not(.static-hero__caption) { color: #555; }
/* The static banner's heading is a <p> until `banner_heading_is_h1` promotes it
   to an <h1>, and the promotion must not be visible. The rule above already
   out-specifies the base `h1` on size, weight, line-height and margin — colour
   and family are the two it leaves behind, and both would then apply directly to
   the element and beat the colour the heading inherits from the banner. So
   a white heading over a photo would turn brand-coloured the moment the setting
   was switched on, and only there. No-op while it is a <p>, which already
   inherits both. */
.static-hero__heading { color: inherit; font-family: inherit; }
/* The banner caption's colour, deliberately at ONE class of specificity so an
   install's own `.page-hero.static-hero .static-hero__caption { color: … }`
   (three classes, and two installs have one) still wins. --banner-caption is the
   Theme Studio token, unset on every install until someone picks a colour;
   --banner-caption-auto is published by the banner rules below and holds the
   answer for THAT banner (white over a photo, the muted grey otherwise), which is
   why the per-banner value travels as an inherited custom property rather than as
   a declaration that would have to out-specify this one. The final #555 is the
   value this element rendered before the token existed, so a banner that somehow
   publishes nothing still lands on today's colour. */
.static-hero__caption { color: var(--banner-caption, var(--banner-caption-auto, #555)); }
/* The static page banner. Every declaration lives here and the banner's markup
   carries only classes plus its own image as custom properties, so a site
   stylesheet loaded after this file restyles a banner with an ordinary rule.
   Never move a declaration back into the markup or into a per-banner `#id` rule:
   either one beats every site rule that is not `!important`.
   The base is TWO classes on purpose: it must beat the one-class `.page-hero`
   above and stay level with an install's own `.page-hero.static-hero` rule, which
   then wins on source order. `color: inherit` keeps a site's `.page-hero { color }`
   off static banners, which have never taken it; drop it and that colour lands
   on every banner without a photo. */
.page-hero.static-hero {
    position: relative;
    min-height: 400px;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    color: inherit;
    --banner-caption-auto: #555;
    overflow: hidden;
}
.page-hero.static-hero--align-left { justify-content: flex-start; text-align: left; }
.page-hero.static-hero--align-right { justify-content: flex-end; text-align: right; }
.page-hero.static-hero--height-compact { min-height: 300px; }
.page-hero.static-hero--height-tall { min-height: 550px; }
.page-hero.static-hero--height-hero { min-height: 700px; }
.page-hero.static-hero--height-full { min-height: 100vh; }
.page-hero.static-hero--light-text { color: #fff; --banner-caption-auto: #fff; }
.page-hero.static-hero--image {
    background-image: var(--static-hero-bg);
    background-size: var(--static-hero-bg-size, cover);
    background-position: var(--static-hero-bg-position, center center);
    background-repeat: no-repeat;
}
.static-hero[class*="static-hero--tint-"]::before { content: ''; position: absolute; inset: 0; pointer-events: none; }
.static-hero--tint-dark-light::before { background: rgba(0,0,0,0.15); }
.static-hero--tint-dark-medium::before { background: rgba(0,0,0,0.35); }
.static-hero--tint-dark-heavy::before { background: rgba(0,0,0,0.6); }
.static-hero--tint-light-light::before { background: rgba(255,255,255,0.2); }
.static-hero--tint-light-medium::before { background: rgba(255,255,255,0.4); }
.static-hero--tint-light-heavy::before { background: rgba(255,255,255,0.65); }
.static-hero > .container { position: relative; z-index: 1; width: 100%; }
@media (max-width: 768px) {
    .page-hero.static-hero { min-height: 280px; }
    .page-hero.static-hero--height-compact { min-height: 220px; }
    .page-hero.static-hero--height-tall { min-height: 360px; }
    .page-hero.static-hero--height-hero { min-height: 440px; }
    .page-hero.static-hero--height-full { min-height: 70vh; }
    .page-hero.static-hero--image { background-image: var(--static-hero-bg-mobile, var(--static-hero-bg)); }
}
/* Two classes, so core's own `a:hover` (and a site's `.container a`) cannot
   recolour the buttons. The secondary rules sit after the :hover and match its
   weight: the secondary button wears both classes, and the outline button would
   flip to solid under a pointer if the plain hover outranked it. */
.static-hero .static-hero__cta {
    display: inline-block;
    padding: 0.6rem 1.5rem;
    background: var(--color-primary, #f04e23);
    color: #fff;
    text-decoration: none;
    border-radius: 4px;
    font-weight: 600;
    transition: background 0.2s;
}
.static-hero .static-hero__cta:hover { background: var(--color-primary-hover, #d4431e); }
.static-hero .static-hero__cta--secondary {
    background: transparent;
    border: 2px solid currentColor;
    padding: calc(0.6rem - 2px) calc(1.5rem - 2px);
}
.static-hero .static-hero__cta--secondary:hover { background: rgba(255, 255, 255, 0.15); }
.static-hero .static-hero__cta-wrap { margin-top: 1rem; }
.static-hero .static-hero__cta-wrap .static-hero__cta + .static-hero__cta { margin-left: 0.75rem; }
/* The slideshow and video banner. Keep these here, never in a <style> block in
   the banner's markup: that prints in the <body> after a site stylesheet and
   beats any equally-specific site rule on source order alone. The one
   per-banner value, the video tint, arrives as --slideshow-tint on the overlay;
   each slide's own photo stays inline because it is content, like an img src. */
.slideshow-hero {
    position: relative;
    overflow: hidden;
    margin-bottom: 2rem;
    height: 400px;
}
.slideshow-hero--height-compact { height: 300px; }
.slideshow-hero--height-tall { height: 550px; }
.slideshow-hero--height-hero { height: 700px; }
.slideshow-hero--height-full { height: 100vh; }
.slideshow-slides {
    position: absolute;
    inset: 0;
}
.slideshow-slide {
    position: absolute;
    inset: 0;
    background-size: cover;
    background-position: center;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    color: #fff;
    opacity: 0;
    transition: opacity 0.6s ease;
    z-index: 0;
}
/* Alignment: applied at the hero level, scoped down to slide containers
   and the video-banner overlay so both types share one rule. */
.slideshow-align-left  .slideshow-slide,
.slideshow-align-left  .slideshow-overlay-content { text-align: left;  justify-content: flex-start; }
.slideshow-align-right .slideshow-slide,
.slideshow-align-right .slideshow-overlay-content { text-align: right; justify-content: flex-end; }
.slideshow-align-left  .slideshow-slide .container,
.slideshow-align-right .slideshow-slide .container { width: 100%; }
.slideshow-slide.active {
    opacity: 1;
    z-index: 1;
}
/* Video banner — video layer fills the hero, dark overlay matches the
   brightness of the static-image gradient so titles/captions stay readable. */
.slideshow-hero--video {
    background: #000;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    color: #fff;
}
.slideshow-video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: 0;
}
.slideshow-video-overlay {
    position: absolute;
    inset: 0;
    background: var(--slideshow-tint, rgba(0,0,0,0.35));
    z-index: 1;
    pointer-events: none;
}
.slideshow-overlay-content { position: relative; z-index: 2; }
.slideshow-slide h2,
.slideshow-overlay-content h2,
.slideshow-overlay-heading {
    font-size: 2rem;
    margin: 0 0 0.5rem 0;
    font-weight: 700;
    line-height: 1.2;
    text-shadow: 0 2px 8px rgba(0,0,0,0.4);
}
/* The caption's colour is set above, beside the static banner's, from the
   --banner-caption theme token. */
.slideshow-caption {
    font-size: 1.1rem;
    margin-bottom: 1rem;
    text-shadow: 0 1px 4px rgba(0,0,0,0.3);
}
.slideshow-cta {
    display: inline-block;
    padding: 0.6rem 1.5rem;
    /* --color-primary-text, not --color-primary: the CTA label is white at
       button size, and the stock orange is 3.6:1 against white — below the
       4.5:1 WCAG 1.4.3 needs. Un-themed installs resolve the darker orange
       from frontend.css :root; a published theme pins --color-primary-text
       to its own primary, so themed banners render exactly as before. */
    background: var(--color-primary-text, #cf3d11);
    color: #fff;
    text-decoration: none;
    border-radius: 4px;
    font-weight: 600;
    transition: background 0.2s;
}
.slideshow-cta:hover { background: var(--color-primary-hover, #d4431e); }

.slideshow-prev, .slideshow-next {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 5;
    background: rgba(0,0,0,0.4);
    color: #fff;
    border: none;
    font-size: 1.5rem;
    padding: 0.75rem 1rem;
    cursor: pointer;
    transition: background 0.2s;
}
.slideshow-prev:hover, .slideshow-next:hover { background: rgba(0,0,0,0.7); }
.slideshow-prev { left: 0; border-radius: 0 4px 4px 0; }
.slideshow-next { right: 0; border-radius: 4px 0 0 4px; }

/* Pause/play (WCAG 2.2.2). Bottom-right so it never collides with the
   centred dots; 44px square clears the 24px target minimum with room. */
.slideshow-pause {
    position: absolute;
    bottom: 0.6rem;
    right: 0.6rem;
    z-index: 5;
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0,0,0,0.4);
    color: #fff;
    border: none;
    border-radius: 50%;
    font-size: 0.85rem;
    line-height: 1;
    cursor: pointer;
    transition: background 0.2s;
}
.slideshow-pause:hover { background: rgba(0,0,0,0.7); }

.slideshow-dots {
    position: absolute;
    bottom: 1rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 5;
    display: flex;
    /* 0.875rem, not 0.5rem: the dots' 26px hit areas (see ::before) must not
       overlap or WCAG 2.5.8's 24px minimum is defeated — 12px dot + 14px gap
       puts the centres 26px apart. */
    gap: 0.875rem;
}
.slideshow-dot {
    position: relative;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    border: 2px solid #fff;
    background: transparent;
    cursor: pointer;
    transition: background 0.2s;
}
/* Invisible hit-area extension (WCAG 2.5.8 Target Size): the visual stays a
   12px dot, the target becomes 26x26. A pseudo-element on a button still
   receives its clicks. */
.slideshow-dot::before { content: ''; position: absolute; inset: -7px; }
.slideshow-dot.active, .slideshow-dot:hover { background: #fff; }

.slideshow-page-heading {
    position: absolute;
    bottom: 2.5rem;
    left: 0;
    right: 0;
    z-index: 5;
    pointer-events: none;
}
/* The overlay is pointer-events:none so the carousel's own controls stay
   reachable through it. Every interactive child must opt back in, or it is
   visible and unclickable — which the page CTA was. */
.slideshow-page-heading .slideshow-cta { pointer-events: auto; }
.slideshow-page-heading .slideshow-cta--secondary,
.slideshow-overlay-content .slideshow-cta--secondary {
    background: transparent;
    border: 2px solid currentColor;
    margin-left: 0.75rem;
}
.slideshow-page-heading .slideshow-overlay-heading-accent,
.slideshow-overlay-content .slideshow-overlay-heading-accent {
    color: var(--heading-accent-color, var(--color-primary));
}
.slideshow-page-heading .slideshow-overlay-heading {
    color: #fff;
    text-shadow: 0 2px 8px rgba(0,0,0,0.5);
    margin: 0;
}

@media (max-width: 768px) {
    .slideshow-hero { height: 280px; }
    .slideshow-hero--height-compact { height: 220px; }
    .slideshow-hero--height-tall { height: 360px; }
    .slideshow-hero--height-hero { height: 440px; }
    .slideshow-hero--height-full { height: 70vh; }
    .slideshow-slide h2,
    .slideshow-overlay-content h2,
    .slideshow-overlay-heading { font-size: 1.4rem; }
    .slideshow-prev, .slideshow-next { font-size: 1.2rem; padding: 0.5rem 0.75rem; }
}
/* The slideshow / video banner's caption takes the same token. Its fallback is
   `inherit` because that side was never broken: a slide and the video overlay
   both set white on the container and nothing matches this element directly. A
   literal #fff would render the same today and freeze the caption white the day
   a banner wants dark text over a pale image. */
.slideshow-caption { color: var(--banner-caption, inherit); }
.page-body-heading { padding: 1.5rem 0 0.5rem; }
.page-body-heading h1 { margin: 0; font-size: var(--font-size-h1, 1.75rem); line-height: 1.2; }
/* The "Updated <date> · Reviewed <date>" line under a heading (SEO-FRESHNESS-CORE).
   Shares .card-meta's size and grey so it reads as the article date line does. */
.page-body-heading .content-dates { margin: 0.35rem 0 0; }

.page-body { padding: 2rem 0 3rem; }
/* A section page's own intro text, sitting straight above its list. Two classes
   so a site's general .page-body padding does not reach it, as the inline style
   it replaced did not. */
.page-body.page-body--intro { padding-bottom: 0; }
/* The Back links above and below an item page. */
.back-top { margin-bottom: 1.5rem; }
.back-bottom { margin-top: 2rem; }
/* An item's picture above its body text, on a service or case study saved before blocks. */
.item-lead-image { margin-bottom: 1.5rem; border-radius: 6px; }
.page-body .cms-content { font-size: 1rem; line-height: 1.7; }
.page-body .cms-content h1 { font-size: calc(var(--font-size-h1, 1.75rem) * 0.914); }
.page-body .cms-content h2 { font-size: calc(var(--font-size-h2, 1.45rem) * 0.931); }
.page-body .cms-content h3 { font-size: calc(var(--font-size-h3, 1.2rem) * 0.958); }
.page-body .cms-content img { margin: 1rem 0; border-radius: var(--radius-sm); }
.page-body .cms-content img.img-left { float: left; display: inline; margin: 0 1.25em 0.75em 0; }
.page-body .cms-content img.img-right { float: right; display: inline; margin: 0 0 0.75em 1.25em; }
.page-body .cms-content img.img-centre { display: block; margin-left: auto; margin-right: auto; }
/* Media-library images are wrapped in <picture> by ResponsiveImage's responsive
   rewrite, which copies the alignment class onto the wrapper. Float the wrapper
   (the layout box) and neutralise the inner <img> float so they don't double up.
   Bare <img> (no variants) still uses the rules above. */
.page-body .cms-content picture.img-left { float: left; margin: 0 1.25em 0.75em 0; }
.page-body .cms-content picture.img-right { float: right; margin: 0 0 0.75em 1.25em; }
.page-body .cms-content picture.img-centre { display: block; margin-left: auto; margin-right: auto; width: max-content; max-width: 100%; }
.page-body .cms-content picture.img-left > img,
.page-body .cms-content picture.img-right > img,
.page-body .cms-content picture.img-centre > img { float: none; margin: 0; }
.page-body .cms-content ul,
.page-body .cms-content ol { margin: 0.75rem 0 1rem 1.5rem; }
.page-body .cms-content li { margin-bottom: 0.3rem; }
.page-body .cms-content blockquote {
    border-left: 4px solid var(--color-primary);
    padding: 0.75rem 1.25rem;
    margin: 1rem 0;
    background: var(--color-primary-tint);
    color: #555;
}

/* ── Rich-text editor classes ──────────────────────
   The content editor's Callout and Button toolbar items write these classes
   into saved content, so the front end has to know them: a class the editor
   offers and the stylesheet does not define renders as a plain paragraph on
   the live page, with nothing anywhere saying why. Kept honest by
   RichTextEditorClassesTest, which reads the class list out of the editor's
   own insertContent calls rather than restating it.

   Deliberately NOT scoped to .page-body .cms-content, unlike the image
   alignment rules above: the editor styles these regardless of context, and
   rich text also renders outside .page-body (staff bios, feed intros), so a
   scoped rule would leave the same promise broken in those places.

   Themed off the brand tokens, so a callout follows Theme Studio instead of
   being one fixed colour on every client's site — the blockquote directly
   above is the same idea and the two are deliberately near-siblings. */
.callout {
    border-left: 4px solid var(--color-primary);
    background: var(--color-primary-tint);
    padding: 1rem 1.25rem;
    margin: 1rem 0;
    border-radius: var(--radius-sm);
}
/* p carries a 1rem bottom margin, which would otherwise stack on the box's
   own padding and make the callout look bottom-heavy. */
.callout > :last-child { margin-bottom: 0; }
/* The amber variant is the CONTRAST one and stays a fixed warning colour on
   purpose: theming it off the brand as well would make the editor's two menu
   entries produce an identical box. An install wanting its own amber sets
   these two properties in its site.css. */
.callout-amber { border-left-color: #f59e0b; background: #fffbeb; }

/* Video transcript: kept in the HTML behind a native disclosure. */
.builder-video__transcript { margin-top: .75rem; }
.builder-video__transcript summary { cursor: pointer; font-weight: 600; }
.builder-video__transcript > div { margin-top: .5rem; line-height: 1.6; }

/* Key Points block: the callout's look, so Theme Studio colours it the same. */
.builder-key-points {
    border-left: 4px solid var(--color-primary);
    background: var(--color-primary-tint);
    padding: 1.25rem 1.5rem;
    border-radius: var(--radius-sm);
}
.builder-key-points__heading { margin: 0 0 .75rem; font-size: 1.25rem; }
.builder-key-points__list { margin: 0; padding-left: 1.25rem; }
.builder-key-points__list li { margin-bottom: .4rem; }
.builder-key-points__list li:last-child { margin-bottom: 0; }
.builder-key-points__note { margin: .75rem 0 0; font-size: .9rem; opacity: .85; }

/* Text + Links: the row is .content-row-grid, so u-stack-reverse and
   u-align-middle work on it; the box wears the Key Points look. */
.content-row-grid.builder-links-text { grid-template-columns: minmax(0, 2fr) minmax(220px, 1fr); }
.content-row-grid.builder-links-text--box-left { grid-template-columns: minmax(220px, 1fr) minmax(0, 2fr); }
.builder-links-text__box {
    border-left: 4px solid var(--color-primary);
    background: var(--color-primary-tint);
    padding: 1.25rem 1.5rem;
    border-radius: var(--radius-sm);
}
.builder-links-text__heading { margin: 0 0 .75rem; font-size: 1.25rem; }
.builder-links-text__list { margin: 0; padding: 0; list-style: none; }
.builder-links-text__list li { margin-bottom: .5rem; }
.builder-links-text__list li:last-child { margin-bottom: 0; }
/* The modifier outranks core's own stacking rule, so it is restated. */
@media (max-width: 768px) {
    .content-row-grid.builder-links-text,
    .content-row-grid.builder-links-text--box-left { grid-template-columns: minmax(0, 1fr); }
}

/* Business Facts: labelled rows, the label a fixed column beside the value
   from tablet width up and stacked above it on a phone. */
.builder-business-facts__intro { margin: 0 0 1rem; }
.builder-business-facts__list { margin: 0; border-top: 1px solid var(--color-border, #e5e7eb); }
.builder-business-facts__row {
    display: grid;
    grid-template-columns: minmax(9rem, 14rem) 1fr;
    gap: .25rem 1.5rem;
    padding: .7rem 0;
    border-bottom: 1px solid var(--color-border, #e5e7eb);
}
.builder-business-facts__row dt { font-weight: 600; margin: 0; }
.builder-business-facts__row dd { margin: 0; overflow-wrap: anywhere; }
.builder-business-facts__links { margin: 0; padding: 0; list-style: none; }
.builder-business-facts__links li { margin: 0 0 .2rem; }
.builder-business-facts__subheading { margin: 1.5rem 0 .5rem; font-size: 1.15rem; }
.builder-business-facts__history { margin: 0; }
.builder-business-facts__checked { margin: 1rem 0 0; font-size: .9rem; opacity: .85; }
@media (max-width: 600px) {
    .builder-business-facts__row { grid-template-columns: 1fr; }
}

/* ── Content Rows ──────────────────────────────── */
.content-row { margin-bottom: 2rem; padding: 1.5rem 0; border-bottom: 1px solid #eee; }
.content-row:last-child { border-bottom: none; }
.content-row h2 { font-size: 1.3rem; }
.content-row-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; align-items: start; }
.content-row-grid.has-image { grid-template-columns: 1fr 300px; }

/* ── Cards (listings) ──────────────────────────── */
.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 1.5rem;
    margin: 1.5rem 0;
}
.card {
    background: var(--card-bg, #fff);
    border: 1px solid var(--card-border, #e8e8e8);
    border-radius: var(--radius-md);
    overflow: hidden;
    transition: box-shadow 0.2s ease, transform 0.2s ease;
}
.card:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.1); transform: translateY(-2px); }
.card-image { width: 100%; height: 200px; object-fit: cover; }
.card-body { padding: 1.25rem; }
.card-body h3 { font-size: 1.1rem; margin-bottom: 0.4rem; }
.card-body h3 a { color: var(--card-title, var(--color-primary)); }
.card-body p { font-size: 0.9rem; color: var(--card-text, #555); margin-bottom: 0.75rem; }
/* A team member in a Content List. These sit above .staff-card's rules on
   purpose: the team page's own cards match them too, and .staff-card's colour
   wins there by coming later. */
.card-body .feed-avatar { width: 80px; height: 80px; border-radius: 50%; object-fit: cover; margin-bottom: 0.5rem; }
.card-body > .job-title, .feed-item > .job-title { color: var(--color-primary, #f04e23); font-size: 0.85rem; font-weight: 700; }
.card-body > .bio, .feed-item > .bio { font-size: 0.85rem; color: #555; }
.card-meta { font-size: 0.8rem; color: var(--card-meta, #989898); }

/* Related links under an article or a case study (App\Support\RelatedLinks):
   the cards are the listing's own .card-grid; these only space the strip and
   the older / newer pair. One class each, so a site's own rule wins. Only the
   related-links-* names are styled: the partials also carry the names an
   install already prints from its own markup (related-articles,
   article-neighbours, more-case-studies), and a rule here on those would
   restyle that install's live pages on upgrade. */
.related-links { margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--color-border, #e5e7eb); }
.related-links__heading { font-size: clamp(1.35rem, 2vw, 1.75rem); margin: 0 0 1.25rem; }
.related-links__list { margin: 0 0 1.25rem; }
.related-links__service { max-width: 720px; margin: 0; }
.related-links-pair { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.5rem 1.5rem; font-size: 0.95rem; }
.related-links-pair--top { margin-top: 0.9rem; font-size: 0.9rem; }
.related-links-pair--foot { margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--color-border, #e5e7eb); }
.related-links-pair__newer { margin-left: auto; text-align: right; }

/* Profile cards (staff feed): the photo across the top at card size, then the
   name, role, phone, an email button and the whole bio. Three across from a
   laptop, as the card grid lays out every other card. */
.staff-profiles { grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); }
.staff-profile { display: flex; flex-direction: column; }
.staff-profile__photo { line-height: 0; }
.staff-profile__photo picture,
.staff-profile__image { display: block; width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; object-position: center 20%; }
.staff-profile .card-body { flex: 1; }
.staff-profile__name { margin: 0 0 0.25rem; }
.staff-profile .job-title { margin: 0 0 0.75rem; color: var(--color-primary-text, var(--color-primary)); font-size: 0.85rem; font-weight: 700; }
.staff-profile__contact { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.5rem 1rem; margin: 0 0 1rem; }
.staff-profile__phone { font-weight: 700; }
.staff-profile__email.btn { padding: 0.5rem 1.1rem; font-size: 0.85rem; }
.staff-profile__bio { padding-top: 1rem; border-top: 1px solid var(--color-border, #e5e7eb); font-size: 0.95rem; }

/* ── Buttons ───────────────────────────────────── */
/* `.cta-button` is what the content editor's Button toolbar item inserts into
   rich text. It SHARES these selectors rather than carrying its own copy of
   the values, so an editor-inserted button cannot drift from the site's real
   buttons, including when a theme moves them.

   THIS IS THE ONLY PLACE `.btn` AND ITS MODIFIERS ARE DECLARED, and that is
   load-bearing rather than tidiness. The file used to declare `.btn` twice and
   `.btn-secondary` / `.btn-outline` twice each, with the later copies winning on
   source order. The duplication itself was harmless; what it hid was not. The
   winning `.btn-secondary` hardcoded a grey, so the brand's own secondary colour
   and Theme Studio's control for it silently did nothing on every install, and
   the winning `.btn` hardcoded a radius, so the corner token never reached a
   button either. Declare a button modifier HERE. A second declaration further
   down the file wins, and the one you are reading becomes dead code with nothing
   anywhere to say so. */
.btn,
.cta-button {
    display: inline-block;
    padding: 0.7rem 2rem;
    border: none;
    border-radius: var(--radius-sm);
    font-family: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    text-decoration: none;
    transition: background 0.2s, transform 0.1s;
    cursor: pointer;
}
.btn:active,
.cta-button:active { transform: scale(0.98); }
.btn-primary,
.cta-button { background: var(--color-primary-text, var(--color-primary)); color: #fff; }
.btn-primary:hover,
.cta-button:hover { background: var(--color-primary-hover); color: #fff; text-decoration: none; }
.btn-secondary { background: var(--color-secondary); color: var(--color-secondary-text, #333); }
.btn-secondary:hover { background: var(--color-secondary-hover); color: var(--color-secondary-text, #333); text-decoration: none; }
/* The 2px border would otherwise make an outline button 4px taller and wider
   than a solid one sitting next to it, so the padding gives the border its space
   back and all three variants share one outer box. */
.btn-outline {
    background: transparent;
    color: var(--color-primary-text, var(--color-primary));
    border: 2px solid var(--color-primary);
    padding: calc(0.7rem - 2px) calc(2rem - 2px);
}
.btn-outline:hover { background: var(--color-primary-text, var(--color-primary)); color: #fff; text-decoration: none; }
/* Buttons with an icon (ICON-PICKER) */
.btn--has-icon { display: inline-flex; align-items: center; gap: 0.5rem; }
.btn__icon { width: 1.1em; height: 1.1em; flex: 0 0 auto; display: inline-block; }

/* ── Content Sections (feeds) ──────────────────── */
.content-section { padding: 2rem 0; border-top: 2px solid #f0f0f0; }
.content-section .feed-view-all { margin-top: 1rem; }
.content-section h2 { margin-bottom: 1.25rem; padding-bottom: 0.5rem; border-bottom: 2px solid var(--color-primary); display: inline-block; }

/* Meetings list (MEETING-PAPERS): a year picker, then one card per meeting
   with a button per paper. Plain on purpose; an install styles it to its
   design from these class names. */
.meeting-year-form { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem 1rem; margin-bottom: 1.5rem; }
.meeting-year-form label { font-weight: 600; }
.meeting-year-form select { min-width: 9rem; padding: 0.55rem 0.75rem; font: inherit; color: inherit; background: #fff; border: 1px solid #767676; border-radius: var(--radius-sm); }
.meeting-cards { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 14rem), 1fr)); gap: 1rem; }
.meeting-card { padding: 1.25rem; background: var(--color-primary-tint); border-radius: var(--radius-sm); }
.meeting-card__date { margin: 0 0 0.75rem; font-size: 1.4rem; text-align: center; }
.meeting-card__note,
.meeting-card__status { margin: 0 0 0.75rem; text-align: center; }
.meeting-card__papers { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
.meeting-card__paper { display: block; padding: 0.6rem 1rem; text-align: center; font-weight: 600; text-decoration: none; color: #fff; background: var(--color-primary-text, var(--color-primary)); border-radius: var(--radius-sm); }
.meeting-card__paper:hover,
.meeting-card__paper:focus-visible { color: #fff; background: var(--color-primary-hover); text-decoration: none; }

/* Document lists (DOCUMENT-LISTS): a list of links per folder, under the
   folder's heading when the block gives one. Grouped by year, the years are
   the accordion's own markup (.faq-item) and take its rules. */
.document-list { list-style: none; margin: 0 0 1.25rem; padding: 0; }
.document-list li { padding: 0.6rem 0; border-bottom: 1px solid var(--color-border, #e5e7eb); }
.document-list li:last-child { border-bottom: 0; }
.document-list__link { font-weight: 600; }
.document-list__heading { margin: 1rem 0 0.25rem; font-size: 1.1rem; }

/* Feed display styles */
/* FEED-RANDOM-ROTATE: hidden pool items must stay hidden even where an item
   class sets its own display (author display rules beat the UA [hidden] rule,
   e.g. a flexed .feed-compact row or a gridded card). */
.content-section [data-feed-item][hidden] { display: none !important; }
.feed-list .feed-item { padding: 0.75rem 0; border-bottom: 1px solid #eee; }
.feed-list .feed-item:last-child { border-bottom: none; }
.feed-list .feed-item h3 { font-size: 1rem; margin-bottom: 0.2rem; }
.feed-list .feed-item p { font-size: 0.85rem; color: #555; margin: 0; }
.feed-list .feed-item .card-meta { font-size: 0.75rem; }

.feed-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; }
.feed-grid .feed-item img { width: 100%; height: 150px; object-fit: cover; border-radius: var(--radius-sm); }
.feed-grid .feed-item h3 { font-size: 0.9rem; margin-top: 0.4rem; }

.feed-compact .feed-item { display: flex; gap: 0.75rem; align-items: center; padding: 0.4rem 0; border-bottom: 1px solid #f0f0f0; }
.feed-compact .feed-item:last-child { border-bottom: none; }
.feed-compact .feed-item h3 { font-size: 0.9rem; margin: 0; }
.feed-compact .feed-item .card-meta { font-size: 0.7rem; }
.feed-compact .feed-item p { display: none; }

/* Inline icon used in list / compact rows for downloads (↓) and links (↗) */
.feed-item .feed-icon,
.feed-compact .feed-item .feed-icon { display: inline-block; margin-right: 0.4rem; color: var(--color-primary); font-weight: 700; }

/* Card-mode row for downloads / links / videos that have no thumbnail.
   Keep card body padding and a subtle accent so they do not look empty. */
.card-grid .card .card-body > .card-meta { display: inline-block; margin-top: 0.35rem; color: #777; font-size: 0.8rem; }

/* Action button rendered at the end of card-mode rows for downloads. */
.card-grid .card .card-body .feed-action { display: inline-block; margin-top: 0.6rem; }

/* ── Blog feed filters (FEED-BLOG-FILTERS) ───────
   Rendered only by a content_feed block whose feed_filterable toggle is on —
   see frontend/partials/blog-category-filter.blade.php. Solid grounds carrying
   white text use --color-primary-text, the readable orange, per the standing
   contrast rule; muted text follows --card-meta like every other supporting
   grey since v15.26. */
.blog-filters { margin: 0 0 1.75rem; }
.blog-search { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: 0 0 1rem; }
.blog-search__input { flex: 0 1 280px; padding: 0.5rem 0.9rem; font-size: 0.9rem;
    border: 1px solid var(--color-border, #e5e7eb); border-radius: 999px;
    background: #fff; color: var(--color-text, #333); }
.blog-search__input:focus { border-color: var(--color-primary); }
.blog-search__btn { padding: 0.5rem 1.15rem; font-size: 0.8rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.03em; cursor: pointer;
    border: 1px solid var(--color-primary-text, var(--color-primary));
    border-radius: 999px;
    background: var(--color-primary-text, var(--color-primary)); color: #fff;
    transition: background .15s, border-color .15s; }
.blog-search__btn:hover { background: var(--color-primary-hover); border-color: var(--color-primary-hover); }
.blog-search__clear { font-size: 0.85rem; color: var(--card-meta, #989898); }
.blog-search__clear:hover { color: var(--color-primary-text, var(--color-primary)); }
.blog-filter { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.blog-filter__label { font-size: 0.8rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-text, #333); margin-right: 0.15rem; }
.blog-filter__chip { display: inline-block; padding: 0.35rem 0.85rem; font-size: 0.85rem; line-height: 1;
    border: 1px solid var(--color-border, #e5e7eb); border-radius: 999px; color: var(--color-text, #333);
    background: #fff; transition: background .15s, color .15s, border-color .15s; }
.blog-filter__chip:hover { background: var(--color-primary-tint); border-color: var(--color-primary); color: var(--color-primary-text, var(--color-primary)); text-decoration: none; }
.blog-filter__chip.is-active { background: var(--color-primary-text, var(--color-primary)); border-color: var(--color-primary-text, var(--color-primary)); color: #fff; }
.blog-filter__chip.is-active:hover { color: #fff; }
.blog-filter__empty { color: var(--card-meta, #989898); font-style: italic; padding: 1rem 0; }

/* ── Testimonials ──────────────────────────────── */
.testimonial-card {
    background: var(--color-primary-tint);
    border-left: 4px solid var(--color-primary);
    padding: 1.5rem;
    margin-bottom: 1.25rem;
    border-radius: 0 var(--radius-md) var(--radius-md) 0;
}
.testimonial-card .quote { font-style: italic; font-size: 1rem; margin-bottom: 0.75rem; color: #444; }
.testimonial-card .author { font-weight: 700; color: #333; }
.testimonial-card .role { font-size: 0.85rem; color: #777; }
.testimonial-card .rating { color: var(--color-primary); margin-bottom: 0.5rem; }
.testimonial-card__avatar,
.testimonial-card__avatar img { width: 52px; height: 52px; border-radius: 50%; object-fit: cover; display: block; margin-bottom: 0.6rem; }

/* Quote marks for a testimonial written in paragraphs. Plain text keeps its
   curly quotes as text either side (QuoteMarks::wrap), but a text node either
   side of a paragraph sits on a line of its own, so for paragraphs the template
   leaves them out and the marks are drawn inside the first and last paragraph.

   The size is the quote box's own font size, made absolute where it is declared
   (a registered <length> computes 1em there, not at the mark). Never 1em of the
   paragraph: installs hide core's marks by setting the quote box to font-size 0
   and giving the size back to its paragraphs, and a mark sized from the
   paragraph reappears on every one of them. line-height: 0 stops a zero-size
   mark adding height to the line it sits on. */
@property --cms-quote-mark-size {
    syntax: '<length>';
    inherits: true;
    initial-value: 0px;
}
[data-quote-marks] { --cms-quote-mark-size: 1em; }
[data-quote-marks] > p:first-child::before,
[data-quote-marks] > p:last-child::after {
    font-size: var(--cms-quote-mark-size);
    line-height: 0;
    letter-spacing: 0;
}
[data-quote-marks] > p:first-child::before { content: '\201C'; }
[data-quote-marks] > p:last-child::after { content: '\201D'; }

/* ── Feed cards, equal height + Read more (feed_display_style = cards_modal) ──
   Used by the testimonials and staff feeds. The point of the style is a tidy
   line where the content length is unpredictable: a 60-character review and a
   760-character one have to sit side by side without one of them dictating the
   height of the row.

   The clamp is CSS only. The full text stays in the markup so the dialog can
   clone it and so search engines read all of it; nothing is cut server-side. */
.rm-grid { align-items: stretch; }
.rm-card {
    display: flex;
    flex-direction: column;
    height: 100%;
    background: var(--card-bg, #fff);
    border: 1px solid var(--card-border, #e8e8e8);
    border-radius: var(--radius-md);
    padding: 1.25rem;
}
.rm-card .rating { color: var(--color-primary); margin-bottom: 0.5rem; }
.rm-card__avatar,
.rm-card__avatar img { width: 64px; height: 64px; border-radius: 50%; object-fit: cover; display: block; margin-bottom: 0.6rem; }
.rm-card h3 { font-size: 1.05rem; margin-bottom: 0.2rem; }
.rm-card .job-title { color: var(--color-primary); font-size: 0.85rem; font-weight: 700; margin-bottom: 0.5rem; }
/* -webkit-line-clamp needs all three of display, orientation and overflow to do
   anything, and the standard `line-clamp` is listed alongside so the rule stops
   being prefix-only as browsers adopt it. min-height:0 is load-bearing inside
   the flex column: without it the clamped box refuses to shrink below its
   content and every card grows to its own text again, which is the one thing
   this style exists to prevent. */
.rm-card__text {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 6;
    line-clamp: 6;
    overflow: hidden;
    min-height: 0;
    font-size: 0.95rem;
    line-height: 1.6;
    color: #444;
}
.rm-card__text p { margin: 0 0 0.6rem; }
.rm-card__text p:last-child { margin-bottom: 0; }
/* margin-top:auto pins the byline and the button to the bottom of every card,
   so the buttons line up across a row whatever each card's text does. */
.rm-card__foot { margin-top: auto; padding-top: 0.9rem; }
.rm-card__foot .author { font-weight: 700; color: #333; font-size: 0.9rem; }
.rm-card__foot .role { display: block; font-weight: 400; font-size: 0.8rem; color: var(--card-meta, #777); }
.rm-card__more {
    display: inline-block;
    margin-top: 0.5rem;
    padding: 0;
    background: none;
    border: 0;
    font: inherit;
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--color-primary-text, var(--color-primary));
    cursor: pointer;
    text-decoration: underline;
}
.rm-card__more:hover { text-decoration: none; }
.rm-card__more[hidden] { display: none; }

/* The dialog. One per page, filled from whichever card was clicked. */
.feed-readmore {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 1000;
    background: rgba(0,0,0,0.6);
    padding: 1.5rem;
    overflow-y: auto;
}
.feed-readmore.is-open { display: flex; align-items: flex-start; justify-content: center; }
.feed-readmore__panel {
    position: relative;
    background: var(--card-bg, #fff);
    border-radius: var(--radius-md);
    padding: 2rem;
    max-width: 640px;
    width: 100%;
    margin: auto;
}
.feed-readmore__close {
    position: absolute;
    top: 0.5rem;
    right: 0.75rem;
    background: none;
    border: 0;
    font-size: 1.8rem;
    line-height: 1;
    color: #666;
    cursor: pointer;
}
.feed-readmore__close:hover { color: #000; }
.feed-readmore__rating { color: var(--color-primary); margin-bottom: 0.4rem; }
.feed-readmore__title { font-size: 1.25rem; margin: 0 0 0.15rem; }
.feed-readmore__meta { color: var(--card-meta, #777); font-size: 0.85rem; margin: 0 0 1rem; }
.feed-readmore__text { font-size: 1rem; line-height: 1.7; color: #333; }
.feed-readmore__text p { margin: 0 0 0.8rem; }
.feed-readmore__text p:last-child { margin-bottom: 0; }
@media (max-width: 600px) {
    .feed-readmore { padding: 0.75rem; }
    .feed-readmore__panel { padding: 1.5rem 1.25rem; }
}

/* ── FAQ ────────────────────────────────────────── */
.faq-item { border-bottom: 1px solid #e8e8e8; padding: 0; }
.faq-item > summary { font-weight: 700; font-size: 1.05rem; color: var(--accordion-question, #333); cursor: pointer; padding: 1rem 1.5rem 1rem 0; position: relative; user-select: none; list-style: none; }
.faq-item > summary::-webkit-details-marker { display: none; }
.faq-item > summary::after { content: '+'; position: absolute; right: 0; top: 50%; transform: translateY(-50%); font-size: 1.4rem; font-weight: 300; color: var(--accordion-icon, var(--color-primary)); }
.faq-item[open] > summary::after { content: '−'; }
.faq-item > summary:hover { color: var(--accordion-icon, var(--color-primary-text)); }
.faq-answer { display: none; padding: 0.75rem 0 1rem 0; color: var(--accordion-answer, #555); line-height: 1.65; }
.faq-item[open] .faq-answer { display: block; }

/* Open/close animation for the native <details> FAQ (section page + faqs feed).
   <details> has no animatable state of its own: the browser flips the content
   between rendered and not, so ::details-content is what gives it a box to
   animate, and allow-discrete defers content-visibility to the END of a close
   instead of hiding everything before it can move.

   The box is collapsed with a GRID TRACK, 0fr → 1fr. Animating block-size to
   `auto` instead needs `interpolate-size: allow-keywords`, which is
   Chromium-only, so an @supports condition naming it drops Firefox and Safari
   and the animation exists in one engine. A flexible track needs no keyword.

   The gate is load-bearing beyond the animation: inside it .faq-answer's
   `display: none` above is cancelled, because a discrete display flip collapses
   the content on the first frame of a close and no transition can outrun it.
   Ungated, a browser without the pseudo would render every answer permanently
   open. Reduced-motion is the inner guard rather than a later override, so the
   animation is never registered for anyone who asked not to see it.

   Scoped to details.faq-item: the page-builder accordion below shares the class
   on a <div> and is driven by inline display from JS, which no CSS can animate. */
@supports selector(details::details-content) {
    @media (prefers-reduced-motion: no-preference) {
        /* min-height pairs with the 0fr track: a grid item's automatic minimum
           size is its min-content height, so without it the row will not
           collapse below one line of the answer. Padding cannot compress
           either, so it is transitioned away with the track — leave it and the
           close stops ~1.75rem short, then drops the rest in one frame when
           content-visibility hides the box. A margin is counted the same way. */
        details.faq-item .faq-answer {
            display: block;
            min-height: 0;
            overflow: hidden;
            transition:
                padding-block-start 380ms cubic-bezier(0.33, 0, 0.15, 1),
                padding-block-end 380ms cubic-bezier(0.33, 0, 0.15, 1);
        }

        details.faq-item:not([open]) .faq-answer {
            padding-block-start: 0;
            padding-block-end: 0;
        }

        /* The easing spreads the travel rather than front-loading it. A one-line
           answer moves the row about 50px, and a curve whose second control
           point sits at x: 0 covers most of that inside 40ms — the row reads as
           a snap however long the duration says it is. */
        details.faq-item::details-content {
            display: grid;
            grid-template-rows: 0fr;
            overflow: hidden;
            opacity: 0;
            transition:
                grid-template-rows 380ms cubic-bezier(0.33, 0, 0.15, 1),
                opacity 160ms ease-out,
                content-visibility 380ms allow-discrete;
        }

        /* Opacity runs later than the box on the way in, so the answer fades in
           behind the row opening rather than arriving before there is room. */
        details.faq-item[open]::details-content {
            grid-template-rows: 1fr;
            opacity: 1;
            transition:
                grid-template-rows 380ms cubic-bezier(0.33, 0, 0.15, 1),
                opacity 260ms ease-in 90ms,
                content-visibility 380ms allow-discrete;
        }
    }
}

/* The page-builder accordion block renders the same details.faq-item as the FAQ
   page, so it is styled and animated by the rules above and needs nothing here.
   It previously had its own .faq-question button; no install styled that class,
   so it was removed rather than left as a dead selector. */
.faq-category { font-size: 1.2rem; color: var(--color-primary); margin: 1.5rem 0 0.75rem; font-weight: 700; }

/* ── Staff Grid ────────────────────────────────── */
.staff-card { text-align: center; }
.staff-card img { width: 160px; height: 160px; border-radius: 50%; object-fit: cover; margin: 0 auto 1rem; border: 3px solid var(--color-primary); }
.staff-card h3 { color: #333; font-size: 1.05rem; margin-bottom: 0.2rem; }
.staff-card .job-title { color: var(--color-primary-text, var(--color-primary)); font-size: 0.85rem; font-weight: 700; margin-bottom: 0.5rem; }
.staff-card .bio { font-size: 0.85rem; color: #555; }
.staff-card .staff-email { margin-top: 0.5rem; font-size: 0.85rem; }
.staff-card .staff-phone { font-size: 0.85rem; }

/* ── Gallery ───────────────────────────────────── */
.gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 0.75rem; }
.gallery-grid img { width: 100%; height: 180px; object-fit: cover; border-radius: var(--radius-sm); cursor: pointer; transition: opacity 0.2s; }
.gallery-grid img:hover { opacity: 0.85; }
.gallery-caption { display: block; font-size: 0.8rem; color: #777; margin-top: 0.3rem; text-align: center; }

/* ── Downloads / Links / Videos ────────────────── */
.list-item { display: flex; align-items: center; gap: 1rem; padding: 1rem 0; border-bottom: 1px solid #eee; }
.list-item:last-child { border-bottom: none; }
.list-item > .list-body { flex: 1; }
.list-item h3 { font-size: 1rem; margin-bottom: 0.15rem; }
.list-item p { font-size: 0.85rem; color: #555; margin-bottom: 0; }
.list-icon {
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    background: var(--color-primary-text, var(--color-primary));
    color: #fff;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 0.8rem;
}

/* ── Event Meta ────────────────────────────────── */
.event-meta { font-size: 0.85rem; color: #777; margin-bottom: 0.5rem; }
.event-meta strong { color: #333; }
.event-badge { display: inline-block; font-size: 0.7rem; font-weight: 700; padding: 0.15rem 0.5rem; border-radius: 3px; text-transform: uppercase; }
/* Dark labels, not white: white on the stock green is 2.1:1 and on the grey 2.0:1,
   both far below WCAG 1.4.3 at badge size. #333 clears 4.5:1 on both grounds. */
.event-badge.upcoming { background: var(--color-secondary); color: #333; }
.event-badge.past { background: #b6b6b6; color: #333; }

/* ── Pagination ──────────────────────────────────
 * Connected square-box pager rendered by resources/views/pagination/motley.blade.php
 * (« first · ‹ prev · numbers · next › · last »). Colours are token-driven, so
 * each site's palette applies automatically; structure/format are shared. */
.pagination-wrap { padding: 2rem 0; display: flex; justify-content: center; }
.pagination-wrap--top { padding: 0 0 1.5rem; }
.cms-pagination { display: inline-flex; flex-wrap: wrap; justify-content: center; }
.cms-pagination__item {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 46px; height: 46px; padding: 0 0.6rem;
    margin-left: -1px;                        /* collapse adjacent borders → connected */
    font-size: 0.95rem; font-weight: 600; line-height: 1;
    border: 1px solid var(--color-primary); border-radius: 0;
    color: var(--color-primary-text, var(--color-primary)); background: #fff; text-decoration: none;
    transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}
.cms-pagination__item:first-child { margin-left: 0; }
a.cms-pagination__item:hover {
    background: var(--color-secondary); border-color: var(--color-secondary);
    /* #333, not the brand colour: orange on the stock green is 1.7:1 (illegible);
       dark text clears 4.5:1 on the green and on any theme's mid secondary. */
    color: #333; position: relative; z-index: 1;
}
.cms-pagination__item.is-current {
    background: var(--color-primary-text, var(--color-primary)); border-color: var(--color-primary-text, var(--color-primary));
    color: #fff; position: relative; z-index: 1;
}
.cms-pagination__item.is-disabled,
.cms-pagination__item.is-gap { color: #c2c3d0; border-color: #dcdce5; cursor: default; }

/* ── Contact Form ─────────────────────────────── */
.contact-form-section { padding: 2.5rem 0 1rem; }
/* A submission redirects back to #form-<key> / #contact-form, so the reply lands on the form; the
   margin keeps it off the viewport edge. A site with a sticky header widens it in its own stylesheet. */
.cms-form-wrap, .contact-form-section { scroll-margin-top: 1.5rem; }
.contact-layout { display: grid; grid-template-columns: 1fr 1fr; gap: 3rem; align-items: start; }

/* Form side */
.contact-form-wrap { background: var(--color-bg-warm); border-radius: var(--radius-lg); padding: 2rem; }
.contact-form-wrap h2 { font-size: 1.2rem; color: var(--color-dark); margin-bottom: 1.5rem; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
/* A half-width field left alone at the end of a run of halves takes the whole
   line (FormRenderer adds the class; a "half, even when on its own" field never
   gets it). */
.form-row--fill-last > :last-child { grid-column: 1 / -1; }
.form-group { margin-bottom: 1.15rem; }
.form-group label { display: block; font-weight: 600; font-size: 0.85rem; margin-bottom: 0.3rem; color: var(--color-dark); }
.form-group .required { color: var(--color-error); }
/* Resting border follows the theme; the :focus rules below already follow
   --color-primary, so leaving this literal made one control theme its focus
   state and not its edge. --color-border is declared in this file's :root, so
   the literal here can never fire in the normal case and is the floor for a
   site that resets :root. Every input surface must use the same token or the
   fields on one form stop matching each other. */
.form-group input,
.form-group textarea {
    width: 100%; padding: 0.65rem 0.85rem; border: 1px solid var(--color-border, #ddd); border-radius: 5px;
    font-family: inherit; font-size: 0.9rem; background: #fff; transition: border-color 0.2s, box-shadow 0.2s;
}
.form-group input:focus,
.form-group textarea:focus { border-color: var(--color-primary); outline: none; box-shadow: 0 0 0 3px rgba(var(--color-primary-rgb), 0.1); }
.form-group textarea { resize: vertical; min-height: 140px; }
.form-error { display: block; color: var(--color-error); font-size: 0.78rem; margin-top: 0.25rem; }
.form-actions { margin-top: 0.5rem; }
/* Buttons are declared once, in the Buttons section near the top of this file,
   and deliberately NOT redeclared here. A Forms-section copy used to win on
   source order and quietly turned that section into dead code. */

/* Alerts */
.form-alert { padding: 1rem 1.25rem; border-radius: var(--radius-md); margin-bottom: 1.5rem; font-size: 0.9rem; line-height: 1.5; }
.form-alert-success { background: #f0faf0; border-left: 4px solid var(--color-secondary); color: #2d5a2d; }
.form-alert-error { background: #fef2f2; border-left: 4px solid var(--color-error); color: #991b1b; }
/* After a send the form waits behind a link (FormSendOnce). !important because
   an install's display rule on the form outranks the UA rule for [hidden], and
   a form that stays visible under its thank-you gets sent again. */
.cms-form-wrap form[hidden],
.cms-form-again[hidden] { display: none !important; }
.cms-form-again { margin: 0; font-size: 0.9rem; }
.cms-form-review { margin: 0 0 1rem; }

/* Case study subtitle and headline stats */
.card-subtitle { font-weight: 700; margin: 0 0 0.5rem; }
.page-body-subtitle { font-weight: 700; margin: 0.35rem 0 0; }
.case-study-stats { margin: 0 0 1rem; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.case-study-stat { display: flex; align-items: baseline; gap: 0.75rem; }
.case-study-stat__value { font-weight: 700; }
.case-study-stat__label { margin: 0; }
/* Two columns only when there is a second column to make. :has() keeps a study
   with no stats from leaving an empty rail beside its title. */
@media (min-width: 900px) {
    .page-body-heading--case-study > .container:has(.case-study-stats) { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 3rem; align-items: start; }
}

/* Form builder rendered forms */
.cms-form-wrap { margin-top: 1.5rem; background: var(--color-bg-warm); border-radius: var(--radius-lg); padding: 2rem; }
/* The top margin is for a form that FOLLOWS text at full width. Beside text
   (Form + Text, Form + Image in left/right mode) the grid is top-aligned and the
   global reset gives the copy's first heading no margin, so the same 1.5rem
   drops the box 24px below the words it sits next to. Two classes on purpose:
   an install's flat .cms-form-wrap override (Motley's site.css restates the
   1.5rem) must not win this back. */
.content-row-grid .cms-form-wrap { margin-top: 0; }
.cms-form__intro { margin: 0 0 1.25rem; font-weight: 600; line-height: 1.4; }
/* A "Text & instructions" field sitting between the questions. Its own bottom
   margin matches .form-group so the wording keeps the rhythm of the fields
   around it; the first/last child rules stop a paragraph's own margin doubling
   the gap at either end. Unstyled otherwise, so an install's typography wins. */
.form-text { margin-bottom: 1.15rem; line-height: 1.6; }
.form-text > :first-child { margin-top: 0; }
.form-text > :last-child { margin-bottom: 0; }
/* A form that remembers answers says so above its first question, shown by its
   script only once the browser has let it save. */
.cms-form-remember-note { margin: 0 0 1.25rem; font-size: 0.85rem; line-height: 1.5; }
/* "Your answers are saved on this device" under the submit button. The line is
   in the page from the start and empty (its status span is the live region),
   so it takes no space until the script fills it and adds .is-on. Colour is
   inherited, so it reads in the form's own text colour on any theme. */
.cms-form-remember { margin: 0; font-size: 0.85rem; line-height: 1.5; }
.cms-form-remember.is-on { margin-top: 0.75rem; }
.cms-form-remember__text:focus { outline: none; }
.cms-form-remember__clear {
    background: none; border: 0; padding: 0; margin-left: 0.4rem; color: inherit;
    font: inherit; text-decoration: underline; cursor: pointer;
}
.cms-form-remember__clear:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
.form-group select {
    width: 100%; padding: 0.65rem 0.85rem; border: 1px solid var(--color-border, #ddd); border-radius: 5px;
    font-family: inherit; font-size: 0.9rem; background: #fff; transition: border-color 0.2s, box-shadow 0.2s;
    appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='%23666'%3E%3Cpath fill-rule='evenodd' d='M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 1rem; padding-right: 2.5rem;
}
.form-group select:focus { border-color: var(--color-primary); outline: none; box-shadow: 0 0 0 3px rgba(var(--color-primary-rgb), 0.1); }
.form-group input[type="checkbox"] { width: auto; accent-color: var(--color-primary); }
.form-group input[type="radio"] { width: auto; accent-color: var(--color-primary); margin: 0; }
.form-group .form-radio-group { display: flex; flex-direction: column; gap: 0.4rem; padding: 0.25rem 0; }
.form-group .form-radio-option { display: flex; align-items: center; gap: 0.5rem; font-weight: normal; cursor: pointer; }
.form-group input[type="date"] { width: 100%; padding: 0.65rem 0.85rem; border: 1px solid var(--color-border, #ddd); border-radius: 5px; font-family: inherit; font-size: 0.9rem; }

/* Details side */
.contact-details-wrap { padding: 1rem 0; }
.contact-details-wrap h2 { font-size: 1.2rem; color: var(--color-dark); margin-bottom: 1.5rem; }
.contact-detail { margin-bottom: 1.25rem; padding-left: 1rem; border-left: 3px solid var(--color-primary); }
.contact-detail strong { display: block; font-size: 0.75rem; color: #999; text-transform: uppercase; letter-spacing: 0.6px; margin-bottom: 0.2rem; font-weight: 600; }
.contact-detail a { color: #333; font-weight: 500; }
.contact-detail a:hover { color: var(--color-primary-text, var(--color-primary)); }
.contact-detail span { color: #555; line-height: 1.7; }

/* ── Footer ────────────────────────────────────── */
.site-footer { background: var(--footer-bg, var(--color-dark)); color: var(--footer-color, #ccc); padding: 2.5rem 0 1.5rem; margin-top: 3rem; font-size: 0.85rem; }
.footer-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 2rem; margin-bottom: 2rem; }
.footer-col h4 { color: var(--footer-heading-color, #fff); font-size: 0.95rem; margin-bottom: 0.75rem; padding-bottom: 0.4rem; border-bottom: 2px solid var(--color-primary); display: inline-block; }
.footer-col ul { list-style: none; }
.footer-col li { margin-bottom: 0.35rem; }
.footer-col a { color: var(--footer-link-color, #ccc); }
.footer-col a:hover { color: var(--footer-link-hover, #fff); }
/* A footer column whose parent is a real link (not a heading) keeps its
   destination in the heading. Must out-rank .footer-col a or the heading
   drops to link grey instead of the heading colour. */
.footer-col h4 a { color: inherit; text-decoration: none; }
.footer-col h4 a:hover { color: var(--footer-link-hover, #fff); }
.footer-bottom { border-top: 1px solid var(--color-dark-border); padding-top: 1rem; text-align: center; color: #999; font-size: 0.8rem; }
.footer-bottom a { color: var(--color-secondary); }

/* The "Cookie settings" button (frontend/partials/cookie-settings-link): a
   button, because it acts on the page rather than going anywhere, dressed as a
   link. It inherits its colour so it suits an install's own footer, and takes
   the link colour in the core footer. [hidden] is restated because a site's
   footer rule may set display, and the button must stay hidden until the
   banner's script is there to answer it. */
.cms-cookie-settings { background: none; border: 0; padding: 0; margin: 0; font: inherit; color: inherit; text-decoration: underline; cursor: pointer; }
.cms-cookie-settings:hover { text-decoration: none; }
.cms-cookie-settings[hidden] { display: none; }
.footer-bottom .cms-cookie-settings { margin-left: 0.5rem; color: var(--color-secondary); }

/* The "site by Motley" bar (App\Support\AgencyCredit). Site Settings sets the
   three colours as custom properties on the bar itself, and each var() fallback
   is the default, so a blank setting leaves black with white. The link rules
   repeat the colour on :hover because the global a:hover outranks one class. */
.site-footer--credit { padding-bottom: 0; }
.site-footer--credit .agency-credit { margin-top: 1.5rem; }
.agency-credit { background: var(--agency-credit-bg, #000); }
.agency-credit__inner { display: flex; align-items: center; min-height: 49px; }
.agency-credit--center .agency-credit__inner { justify-content: center; }
.agency-credit--right .agency-credit__inner { justify-content: flex-end; }
.agency-credit__link,
.agency-credit__link:hover,
.agency-credit__link:focus-visible { display: inline-flex; align-items: center; gap: 1rem; color: var(--agency-credit-text, #fff); font-size: 0.75rem; line-height: 1.5; text-decoration: none; }
.agency-credit__link:hover .agency-credit__label,
.agency-credit__link:focus-visible .agency-credit__label { text-decoration: underline; }
.agency-credit__mark { flex-shrink: 0; color: var(--agency-credit-mark, #fff); }

/* ── Responsive ────────────────────────────────── */
@media (max-width: 768px) {
    .nav-toggle { display: block; }
    .nav-main { display: none; flex-direction: column; }
    .nav-main.open { display: flex; }
    .nav-sub { position: static; box-shadow: none; }
    /* On mobile the submenu is driven solely by the explicit .sub-open toggle, so
       both reveal-on-pointer rules are cancelled. :focus-within must be cancelled
       too, or focusing the trigger would open the panel behind the toggle's back
       and leave aria-expanded lying about the state. */
    .nav-item:hover .nav-sub { display: none; }
    .nav-item:focus-within .nav-sub { display: none; }
    .nav-heading:focus-within .nav-sub { display: none; }
    .nav-item.sub-open .nav-sub { display: block; }
    /* Dropdown affordance — chevron on any nav item that expands a sub-menu,
       so on mobile (tap-to-open) it's clear the item reveals more (P3-4).
       :has() keeps it markup-driven (no extra class needed); :not(.inactive)
       leaves the admin-only "[draft]" ::after marker alone. */
    .nav-item:has(> .nav-sub):not(.inactive) > a,
    .nav-heading:has(> .nav-sub) > .nav-label { position: relative; padding-right: 2.6rem; }
    /* The arrow is drawn on whatever OPENS the submenu, never on two things:
       on a linked parent with a chevron button (core's own menu since
       26.10.15) that is the button, so the link draws none; on a heading it is
       the label; and on a linked parent with NO button it is still the link.
       That last case is load-bearing, not legacy tidying: an install rendering
       its own copy of the menu (a header override written before 26.10.15)
       has no button and still opens the submenu from the link, so drawing no
       arrow there would leave a row that opens a dropdown with nothing on
       screen to say so. Several sites carry such a copy. */
    .nav-item > .nav-sub-toggle {
        display: block;
        position: absolute;
        right: 0;
        top: 0;
        width: 3rem;
        height: 2.9rem;
        padding: 0;
        border: 0;
        background: none;
        color: var(--nav-link-color, #fff);
        cursor: pointer;
    }
    .nav-item > .nav-sub-toggle::after,
    .nav-item:has(> .nav-sub):not(:has(> .nav-sub-toggle)):not(.inactive) > a::after,
    .nav-heading:has(> .nav-sub) > .nav-label::after {
        content: "";
        position: absolute;
        right: 1.2rem;
        top: 50%;
        margin-top: -0.35rem;
        width: 0.5rem;
        height: 0.5rem;
        border-right: 2px solid currentColor;
        border-bottom: 2px solid currentColor;
        transform: rotate(45deg);
        transition: transform 0.2s ease;
        pointer-events: none;
    }
    .nav-item.sub-open > .nav-sub-toggle::after,
    .nav-item.sub-open:has(> .nav-sub):not(:has(> .nav-sub-toggle)):not(.inactive) > a::after,
    .nav-heading.sub-open:has(> .nav-sub) > .nav-label::after { transform: rotate(225deg); margin-top: -0.15rem; }
    /* The row has to be the positioning context for the overlaid button, and the
       link must not run under it. A heading is unaffected: its label IS the
       trigger and keeps the ::after. */
    .nav-item:has(> .nav-sub-toggle) { position: relative; }
    /* The third level is an accordion inside the accordion: closed until its
       own chevron opens it, whatever the row above it is doing, so the
       pointer and descendant reveals above are cancelled before it opens. */
    .nav-main .nav-sub-parent:hover > .nav-sub { display: none; }
    .nav-main .nav-sub-parent:focus-within > .nav-sub { display: none; }
    .nav-item.sub-open .nav-sub .nav-sub { display: none; }
    .nav-main .nav-sub-parent.sub-open > .nav-sub { display: block; }
    .nav-sub .nav-sub a { padding-left: 2.15rem; }
    .nav-sub-parent > a { padding-right: 3rem; }
    .nav-sub-parent > a::after { content: none; }
    .nav-sub-parent > .nav-sub-parent-toggle {
        display: block;
        position: absolute;
        right: 0;
        top: 0;
        width: 3rem;
        height: 2.4rem;
        padding: 0;
        border: 0;
        background: none;
        color: var(--nav-dropdown-color, #333);
        cursor: pointer;
    }
    .nav-sub-parent > .nav-sub-parent-toggle::after {
        content: "";
        position: absolute;
        right: 1.2rem;
        top: 50%;
        margin-top: -0.35rem;
        width: 0.5rem;
        height: 0.5rem;
        border-right: 2px solid currentColor;
        border-bottom: 2px solid currentColor;
        transform: rotate(45deg);
        transition: transform 0.2s ease;
        pointer-events: none;
    }
    .nav-sub-parent.sub-open > .nav-sub-parent-toggle::after { transform: rotate(225deg); margin-top: -0.15rem; }
    .content-row-grid,
    .content-row-grid.has-image { grid-template-columns: 1fr; }
    .card-grid { grid-template-columns: 1fr; }
    .footer-grid { grid-template-columns: 1fr; }
    .contact-layout { grid-template-columns: 1fr; }
    .contact-details-wrap { order: -1; }
    .form-row { grid-template-columns: 1fr; }
    h1 { font-size: calc(var(--font-size-h1, 1.75rem) * 0.857); }
    .builder-cta { padding: 2rem 1.5rem; }
}

/* ── Breadcrumbs ───────────────────────────────── */
.breadcrumbs-wrap { padding-top: 0.9rem; padding-bottom: 0.2rem; }
.breadcrumbs { font-size: 0.85rem; margin: 0 0 0.5rem; color: var(--breadcrumb-color, #6b7280); }
.breadcrumbs ol { list-style: none; padding: 0; margin: 0; display: flex; flex-wrap: wrap; gap: 0.4rem; }
.breadcrumbs li { display: flex; align-items: center; }
.breadcrumbs li:not(:last-child)::after { content: "›"; margin-left: 0.4rem; color: #9ca3af; }
.breadcrumbs a { color: inherit; text-decoration: none; }
.breadcrumbs a:hover { color: var(--color-primary-text, var(--color-primary)); text-decoration: underline; }
.breadcrumbs li[aria-current="page"] { color: var(--breadcrumb-current, #1f2937); font-weight: 500; }

/* ── Page Builder Blocks ───────────────────────── */
.builder-block { margin-bottom: var(--block-gap); }
.builder-block:last-child { margin-bottom: 0; }
/* The section label above a heading. Uppercased in CSS, not in the field, so
   the editor's own capitalisation survives into exports and search. */
.builder-block__eyebrow {
    margin: 0 0 0.4rem;
    font-size: 0.85rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--eyebrow-color, var(--color-primary-text, var(--color-primary)));
}
.builder-block__heading { margin-top: 0; margin-bottom: 1rem; }
/* Two-tone headings. Defaults to the site accent; a site restyles the span
   rather than needing markup in the heading field. */
.builder-block__heading-accent,
.builder-cta__heading-accent,
.static-hero__heading-accent { color: var(--heading-accent-color, var(--color-primary)); }
.builder-cta--banner .builder-cta__heading-accent { color: var(--cta-accent-color, inherit); }

.builder-block__image img { width: 100%; height: auto; border-radius: var(--radius-md); display: block; }

/* Stacked image pair — the second image overlaps the bottom-right of the first.
   The white ring is what separates the two photos; without it they read as one
   image when their edges happen to be a similar tone. Padding on the wrapper
   reserves the overhang, so the inset never collides with the next block. */
.builder-block__image--stacked { position: relative; padding-bottom: 3.5rem; padding-right: 2rem; }
.builder-block__image--stacked .builder-block__image-inset,
.builder-block__image--stacked picture.builder-block__image-inset {
    position: absolute;
    right: 0;
    bottom: 0;
    width: 58%;
    display: block;
    border-radius: var(--radius-md);
    border: 5px solid #fff;
    box-sizing: border-box;
    overflow: hidden;
}
@media (max-width: 768px) {
    /* Below this the inset is more clutter than composition. */
    .builder-block__image--stacked { padding: 0; }
    .builder-block__image--stacked .builder-block__image-inset,
    .builder-block__image--stacked picture.builder-block__image-inset { display: none; }
}

/* Image pair — the "text full width, images below" layout of Image + Text. The
   two photos share the row; a lone photo spans both columns rather than leaving
   half the row empty. The adjacent-sibling margin is on the pair rather than the
   text, so an imageless block emits no stray gap. */
.builder-block__image--pair { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; align-items: start; }
.builder-block__image--pair > :only-child { grid-column: 1 / -1; }
.cms-content + .builder-block__image--pair { margin-top: 1.5rem; }
@media (max-width: 768px) {
    .builder-block__image--pair { grid-template-columns: 1fr; }
}

/* Map blocks — full-width, map+text, map+image */
.builder-map { width: 100%; border-radius: var(--radius-md); overflow: hidden; }
.builder-map iframe { width: 100%; height: 100%; border: 0; display: block; }
.builder-map__popup { font: 14px/1.5 'Open Sans', system-ui, sans-serif; max-width: 260px; }
.builder-map__popup strong { display: block; margin-bottom: 4px; }
.builder-map__popup a { color: var(--color-primary-text, var(--color-primary)); text-decoration: none; }
.builder-map__popup a:hover { text-decoration: underline; }

/* Admin-preview-only warning shown inside empty/invalid blocks. Public visitors never see this.
   .builder-toc__empty is the contents block's version, injected by script rather
   than rendered. It is a SEPARATE class on purpose: that script ships on every
   page, so writing the name below into it would put the string in the HTML of
   pages carrying no warning at all, and several tests assert its absence to
   prove a block rendered cleanly. */
.builder-toc__empty,
.builder-block__warning {
    background: #fffbeb;
    border: 1px dashed #f59e0b;
    color: #92400e;
    padding: 0.85rem 1rem;
    border-radius: var(--radius-md);
    font-size: 0.875rem;
    line-height: 1.5;
}

/* Block-catalogue-only tier chip on blocks above the site's current mode.
   The catalogue page is signed-in-only, so this never faces the public. */
.builder-block__tier-note {
    display: inline-block;
    background: #fffbeb;
    border: 1px solid #f59e0b;
    color: #92400e;
    padding: 0.3rem 0.7rem;
    border-radius: 999px;
    font-size: 0.78rem;
    line-height: 1.4;
    margin-bottom: 0.75rem;
}

/* Multi-column layouts (expert mode) */
.builder-grid { display: grid; gap: 2rem; align-items: start; }
.builder-grid--3 { grid-template-columns: 1fr 1fr 1fr; }
.builder-grid--4 { grid-template-columns: 1fr 1fr 1fr 1fr; }
.builder-grid--1-2 { grid-template-columns: 1fr 2fr; }
.builder-grid--2-1 { grid-template-columns: 2fr 1fr; }
/* Responsive — declared AFTER the base column rules so they win (equal specificity). */
@media (min-width: 769px) and (max-width: 1024px) {
    /* Tablet: 4-col → 2-col grid; 3-col stays as 3 but tighter */
    .builder-grid--4 { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 768px) {
    .builder-grid--3,
    .builder-grid--4,
    .builder-grid--1-2,
    .builder-grid--2-1 { grid-template-columns: 1fr; }
}

/* ── Block layout helpers (config: cms.block_utilities) ─────────────────────
   Discoverable design tweaks ticked on from a block's Design → Layout helpers
   picker and emitted onto the .builder-block wrapper. Core helpers use the `u-`
   prefix so they never collide with per-site custom classes.

   u-stack-reverse — reverse the column order ONLY when the block has stacked
   (≤768px). Works on any multi-column block: .content-row-grid (two_column /
   image_text / map_text / map_image / form_text + form_image in left-right mode)
   and .builder-grid (3/4-col + asymmetric). Switches the collapsed grid to flex
   column-reverse so it reverses whatever column count the block has. Desktop
   layout is untouched. (Graduated from one site's own stylesheet into core.) */
@media (max-width: 768px) {
    .u-stack-reverse .content-row-grid,
    .u-stack-reverse .builder-grid,
    .u-stack-reverse .builder-block__image--pair {
        display: flex;
        flex-direction: column-reverse;
    }
}

/* u-align-middle — vertically centre a multi-column block's columns (the grids
   default to top-aligned). Pairs well with Image + Text when the copy is
   shorter than the image. Targets the same grid containers as u-stack-reverse. */
.u-align-middle .content-row-grid,
.u-align-middle .builder-grid,
.u-align-middle .builder-block__image--pair { align-items: center; }

/* u-full-bleed — break the block out of the centred page container so its
   background spans the full viewport width, edge to edge. Pairs with the Design
   tab's background colour / image. box-sizing keeps the inner gutter inside the
   100vw (no overflow); the gutter stops content hitting the screen edge and is
   overridden if the editor sets padding on the Design tab. The horizontal
   scrollbar this width could cause is absorbed by body { overflow-x: clip }. */
.u-full-bleed {
    box-sizing: border-box;
    width: 100vw;
    max-width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    padding-left: 1.25rem;
    padding-right: 1.25rem;
}

/* u-band — the block's background runs edge to edge while the block itself, and so
   its content, keeps its normal width. The background is repainted on a ::before
   that inherits the inline Design-tab background (colour, image, size, position,
   overlay) and reaches out to the viewport edges; the block box never moves, so the
   text lines up with the column on every layout, a site's own column rules included.
   Like u-full-bleed it assumes the block is centred on the page, and its overhang is
   absorbed by body { overflow-x: clip }. isolation keeps z-index:-1 behind this
   block's content but in front of whatever sits behind the block. A block type that
   clips its own overflow would cut the band back to its box. */
.u-band {
    position: relative;
    isolation: isolate;
}
.u-band::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: calc(50% - 50vw);
    right: calc(50% - 50vw);
    background: inherit;
    z-index: -1;
    pointer-events: none;
}

/* u-elevate — soft drop shadow to lift a block off the page (card style).
   Combine with the Design tab's corner radius for a rounded card. */
.u-elevate { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.10); }

/* u-hide-mobile / u-hide-desktop — breakpoint visibility. !important so they win
   over any block-type display rule. */
@media (max-width: 768px) { .u-hide-mobile { display: none !important; } }
@media (min-width: 769px) { .u-hide-desktop { display: none !important; } }

/* Stacked layout (form_text/form_image with above/below position) */
.builder-stack { display: flex; flex-direction: column; gap: 2rem; }

/* Split Hero — copy on one side, photo bleeding off the other. The photo is
   positioned rather than a grid column so it can run to the edge of the band
   while the copy stays on the container grid. Sites restyle via .builder-hero
   in site.css; these are working defaults, not a house look. */
.builder-hero { position: relative; overflow: hidden; background: var(--hero-bg, var(--color-primary)); color: var(--hero-text, #fff); }
.builder-hero__media { position: absolute; top: 0; bottom: 0; right: 0; width: 60%; z-index: 1; }
.builder-hero--photo-left .builder-hero__media { right: auto; left: 0; }
.builder-hero__media img,
.builder-hero__media picture { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The scrim is what lets light copy sit over an arbitrary photo. Without it the
   heading is unreadable the moment a client uploads a pale image. */
.builder-hero__media::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 2;
    background: linear-gradient(to right, var(--hero-bg, var(--color-primary)) 0%, rgba(0, 0, 0, 0) 55%);
}
.builder-hero--photo-left .builder-hero__media::before { background: linear-gradient(to left, var(--hero-bg, var(--color-primary)) 0%, rgba(0, 0, 0, 0) 55%); }

/* A hero is full-bleed by definition — a boxed one is just an Image + Text
   block. The wrapper breaks out (same technique as u-full-bleed, absorbed by
   body { overflow-x: clip }) and the gutter moves to __inner, so the photo
   still runs to the screen edge while the copy stays on the container grid. */
.builder-block--split_hero {
    box-sizing: border-box;
    width: 100vw;
    max-width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
}

.builder-hero__inner {
    position: relative;
    z-index: 3;
    display: flex;
    align-items: center;
    max-width: var(--container-width);
    margin: 0 auto;
    padding: 4rem 1.25rem;
    min-height: 420px;
}
.builder-hero--photo-left .builder-hero__inner { justify-content: flex-end; }
.builder-hero__text { flex: 0 0 50%; max-width: 34rem; }

.builder-hero__badge { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 1.5rem; }
.builder-hero__badge-photo { width: 38px; height: 38px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
.builder-hero__badge-stars { color: var(--hero-stars, #f5b301); letter-spacing: 2px; white-space: nowrap; }
.builder-hero__badge-text { font-size: 0.78rem; line-height: 1.4; margin: 0; opacity: 0.9; }

.builder-hero__heading { font-size: clamp(2.5rem, 6vw, 5rem); line-height: 1; margin: 0 0 1.25rem; color: inherit; }
.builder-hero__tagline { font-weight: 700; text-transform: uppercase; margin: 0 0 1rem; }
.builder-hero__body { margin: 0 0 1.75rem; opacity: 0.9; }
.builder-hero__actions { display: flex; flex-wrap: wrap; gap: 0.75rem; }

@media (max-width: 768px) {
    /* The photo goes behind the copy rather than beside it — at this width a
       60% column leaves neither the text nor the image enough room. */
    .builder-hero__media,
    .builder-hero--photo-left .builder-hero__media { width: 100%; left: 0; right: 0; }
    .builder-hero__media::before,
    .builder-hero--photo-left .builder-hero__media::before {
        background: linear-gradient(to bottom, rgba(0, 0, 0, 0.25) 0%, var(--hero-bg, var(--color-primary)) 78%);
    }
    .builder-hero__inner { padding: 2.5rem 1.25rem; min-height: 0; }
    .builder-hero__text { flex: none; width: 100%; max-width: 100%; }
}

/* Expanding Panels: a row of photo panels, one open and the rest folded into
   strips. Working defaults, not a house look: every colour, size and timing an
   install is likely to change is a custom property on .builder-panels, so a
   restyle sets variables rather than fighting these selectors.

   Full-bleed for the same reason as the split hero above: it opens a page, and
   a boxed one leaves the folded strips no room. */
.builder-block--panels {
    box-sizing: border-box;
    width: 100vw;
    max-width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
}
/* The shared wrapper prints the eyebrow and heading INSIDE the bled block, so
   they are put back on the container grid rather than left against the edge. */
.builder-block--panels > .builder-block__heading,
.builder-block--panels > .builder-block__eyebrow { padding-inline: max(20px, calc(50% - var(--container-width, 1140px) / 2)); }

.builder-panels {
    --panels-collapsed: clamp(3.5rem, 8vw, 150px);
    --panels-height: clamp(26rem, 49vw, 59rem);
    --panels-mobile-height: 26rem;
    --panels-tab-height: 50%;
    --panels-tab-bg: var(--color-dark, #494040);
    --panels-tab-hover-bg: var(--color-primary, #f04e23);
    --panels-tab-text: #fff;
    --panels-text: #fff;
    --panels-scrim: linear-gradient(90deg, rgba(0, 0, 0, 0.65) 0%, rgba(0, 0, 0, 0.4) 55%, rgba(0, 0, 0, 0.2) 100%);
    /* The slide, then the fade. panels-script.blade.php reads the slide's
       duration back off the panel to know when to start the fade, so changing
       it here is enough: the two cannot drift apart. */
    --panels-duration: 0.35s;
    --panels-fade-duration: 0.3s;
    /* Load-bearing: 100cqw below is this element's width. Without a container
       the open panel's photo and copy are sized to whatever the panel happens
       to be mid-slide, so the photo rescales and the copy reflows on every
       frame of it. */
    container-type: inline-size;
    display: flex;
    min-height: var(--panels-height);
    background: var(--panels-tab-bg);
}
.builder-panels__panel {
    position: relative;
    /* Hidden overflow is also what lets a panel narrower than its copy shrink:
       a flex item's automatic minimum width is its content unless it clips. */
    overflow: hidden;
    display: flex;
    align-items: center;
    flex: 0 0 var(--panels-collapsed);
}
/* The slide, switched on only once the script has bound. Before that nothing
   can change, and a transition declared from the first paint can run as the
   page loads, sliding the server-opened panel open in front of the visitor.
   For a visitor who asked for less motion, the reduced-motion rule at the
   foot of this file takes this and the copy's fade to 0.01ms, so the swap is
   instant, and the script reads that back as nothing to wait for. */
.builder-panels[data-bound] .builder-panels__panel { transition: flex-grow var(--panels-duration) ease; }
/* flex-grow, not a width, is what slides: it interpolates smoothly through
   zero, so the panel opening and the one closing trade space frame by frame
   and the row never changes width between them. */
.builder-panels__panel.is-open { flex-grow: 1; }
/* The script opens a folded panel from a click anywhere on its strip, so the
   whole strip says so. Only once the script is running: with none, the strip
   does nothing and must not look as if it would. */
.builder-panels[data-bound] .builder-panels__panel:not(.is-open) { cursor: pointer; }
.builder-panels[data-bound] .builder-panels__panel:not(.is-open):hover .builder-panels__tab { background: var(--panels-tab-hover-bg); }

/* The photo is always the OPEN panel's width, centred on its panel, and the
   panel's overflow crops it. Sized to the panel instead, object-fit would
   rescale it on every frame of the slide and the picture would visibly zoom.
   The first width is the fallback where container units are not supported. */
.builder-panels__media {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    z-index: 1;
    width: 100%;
    width: calc(100cqw - (var(--panels-count, 2) - 1) * var(--panels-collapsed));
    transform: translateX(-50%);
}
.builder-panels__media picture,
.builder-panels__media img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: center; }
/* The scrim is what lets light copy sit over an arbitrary photo. Only the open
   panel carries copy, so only it is darkened. */
.builder-panels__panel.is-open .builder-panels__media::after {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--panels-scrim);
}

.builder-panels__tab {
    position: absolute;
    left: 0;
    bottom: 0;
    z-index: 3;
    box-sizing: border-box;
    width: var(--panels-collapsed);
    height: var(--panels-tab-height);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem 0.5rem;
    border: 0;
    border-radius: 0;
    background: var(--panels-tab-bg);
    color: var(--panels-tab-text);
    font: inherit;
    font-weight: 700;
    line-height: 1.2;
    cursor: pointer;
}
.builder-panels__panel:not(.is-open) .builder-panels__tab:hover { background: var(--panels-tab-hover-bg); }
.builder-panels__panel.is-open .builder-panels__tab { cursor: default; }
/* Set in core rather than left to the theme: a hover colour on the tab of
   the panel already open would offer a click that does nothing. */
.builder-panels__panel.is-open .builder-panels__tab:hover { background: var(--panels-tab-bg); }
/* Inset, because the panel clips its overflow: an outline drawn outside the
   button would be cut off and the focused tab would show nothing. */
.builder-panels__tab:focus-visible { outline: 3px solid var(--panels-tab-text); outline-offset: -6px; }
/* Reads bottom to top, the way a book spine on a shelf does. */
.builder-panels__label {
    display: block;
    max-height: 100%;
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    text-align: center;
}

.builder-panels__body {
    position: relative;
    z-index: 2;
    flex: 0 0 auto;
    box-sizing: border-box;
    /* The width the panel has once open, so the copy is only ever laid out at
       its final width and never wraps across a half-open strip, even when a
       quick second click lands mid-slide. The first line is the fallback
       where container units are not supported. */
    width: 100%;
    width: calc(100cqw - (var(--panels-count, 2) - 1) * var(--panels-collapsed));
    padding: 3rem clamp(1.5rem, 4vw, 4rem) 3rem calc(var(--panels-collapsed) + clamp(1.5rem, 4vw, 4rem));
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    color: var(--panels-text);
}
/* The display above outranks the UA rule for [hidden], so this restates it. */
.builder-panels__body[hidden] { display: none; }
/* Set by the script when it shows a panel's copy after the slide. A body is
   display:none while folded, and showing it again restarts the animation, so
   every opening fades in. The panel open on page load never carries it: a
   fade there would only delay the page's largest paint. */
.builder-panels__body.is-entering { animation: builder-panels-fade var(--panels-fade-duration) ease both; }
@keyframes builder-panels-fade { from { opacity: 0; } to { opacity: 1; } }
.builder-panels__eyebrow { margin: 0 0 0.75rem; font-size: 1.1rem; color: inherit; }
.builder-panels__heading { margin: 0 0 1.25rem; font-size: clamp(2.25rem, 5vw, 4.5rem); line-height: 1.05; color: inherit; }
.builder-panels__text { margin: 0 0 1.75rem; max-width: 40rem; color: inherit; }
.builder-panels .builder-panels__button { color: var(--panels-text); border-color: var(--panels-text); }
.builder-panels .builder-panels__button:hover { background: var(--panels-text); color: var(--panels-tab-bg); }

@media (max-width: 768px) {
    /* A vertical accordion: folded panels become bars carrying their label,
       and the open one shows its photo with the copy over it. */
    .builder-panels { flex-direction: column; min-height: 0; }
    /* No slide on a phone: the swap is instant. transition: none is also what
       tells the script there is no slide to wait for before the copy shows. */
    .builder-panels__panel,
    .builder-panels__panel.is-open { flex: none; flex-direction: column; align-items: stretch; }
    .builder-panels[data-bound] .builder-panels__panel { transition: none; }
    .builder-panels__panel:not(.is-open) .builder-panels__media { display: none; }
    .builder-panels__media { left: 0; width: 100%; transform: none; }
    .builder-panels__tab {
        position: relative;
        width: 100%;
        height: auto;
        min-height: 3.5rem;
        justify-content: flex-start;
        padding: 1rem 1.25rem;
    }
    .builder-panels__label { writing-mode: horizontal-tb; transform: none; text-align: left; max-height: none; }
    .builder-panels__body {
        width: auto;
        min-height: var(--panels-mobile-height);
        justify-content: center;
        padding: 2.5rem 1.25rem;
    }
    .builder-panels__panel + .builder-panels__panel { border-top: 1px solid rgba(255, 255, 255, 0.2); }
}

/* CTA */
.builder-cta { padding: 3.5rem 2rem; text-align: center; border-radius: var(--radius-md); }
.builder-cta--banner { background: var(--cta-banner-bg, var(--color-primary)); color: var(--cta-banner-text, #fff); }
.builder-cta--banner h2 { color: var(--cta-banner-text, #fff); }
.builder-cta--card { background: var(--color-bg-subtle); border: 1px solid var(--color-border); max-width: 640px; margin: 0 auto; }
.builder-cta--minimal { text-align: left; padding: 1.5rem 0; }
.builder-cta__inner { max-width: 720px; margin: 0 auto; }
.builder-cta h2 { margin-bottom: 0.75rem; }
.builder-cta p { margin-bottom: 1.25rem; opacity: 0.9; }

/* Gallery — two layouts: grid (uniform squares) or masonry (staggered, natural shape) */
.builder-gallery { gap: 0.75rem; }

/* Grid: equal-sized square tiles, images cropped via object-fit */
.builder-gallery--grid { display: grid; }
.builder-gallery--grid.builder-gallery--2col { grid-template-columns: repeat(2, 1fr); }
.builder-gallery--grid.builder-gallery--3col { grid-template-columns: repeat(3, 1fr); }
.builder-gallery--grid.builder-gallery--4col { grid-template-columns: repeat(4, 1fr); }
.builder-gallery--grid .builder-gallery__item { aspect-ratio: 1; overflow: hidden; border-radius: var(--radius-sm); }
.builder-gallery--grid .builder-gallery__item img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Masonry: CSS column-count for staggered, preserve-aspect-ratio layout */
.builder-gallery--masonry { column-gap: 0.75rem; }
.builder-gallery--masonry.builder-gallery--2col { column-count: 2; }
.builder-gallery--masonry.builder-gallery--3col { column-count: 3; }
.builder-gallery--masonry.builder-gallery--4col { column-count: 4; }
.builder-gallery--masonry .builder-gallery__item {
    break-inside: avoid;
    margin: 0 0 0.75rem;
    border-radius: var(--radius-sm);
    overflow: hidden;
    display: block;
}
.builder-gallery--masonry .builder-gallery__item img {
    width: 100%;
    height: auto;
    display: block;
}

/* Legacy fallback: any old gallery markup without --grid/--masonry class behaves
   like the previous grid implementation. */
.builder-gallery:not(.builder-gallery--grid):not(.builder-gallery--masonry) {
    display: grid;
}
.builder-gallery:not(.builder-gallery--grid):not(.builder-gallery--masonry).builder-gallery--2col { grid-template-columns: repeat(2, 1fr); }
.builder-gallery:not(.builder-gallery--grid):not(.builder-gallery--masonry).builder-gallery--3col { grid-template-columns: repeat(3, 1fr); }
.builder-gallery:not(.builder-gallery--grid):not(.builder-gallery--masonry).builder-gallery--4col { grid-template-columns: repeat(4, 1fr); }
.builder-gallery:not(.builder-gallery--grid):not(.builder-gallery--masonry) .builder-gallery__item { aspect-ratio: 1; overflow: hidden; border-radius: var(--radius-sm); }
.builder-gallery:not(.builder-gallery--grid):not(.builder-gallery--masonry) .builder-gallery__item img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Responsive — declared AFTER the base column rules so they win (equal specificity). */
@media (max-width: 768px) {
    .builder-gallery--grid.builder-gallery--3col,
    .builder-gallery--grid.builder-gallery--4col { grid-template-columns: repeat(2, 1fr); }
    .builder-gallery--masonry.builder-gallery--3col,
    .builder-gallery--masonry.builder-gallery--4col { column-count: 2; }
}

/* Video */
.builder-video { margin: 0 auto; max-width: 900px; }
.builder-video__frame { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; border-radius: var(--radius-md); }
.builder-video__frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; }
.builder-video__caption { text-align: center; color: #666; font-size: 0.85rem; margin-top: 0.6rem; }

/* Poster facade — what shows before the player is asked for. The button fills
   the 16:9 frame absolutely so the ratio survives an image of any shape. */
.builder-video__facade { position: absolute; inset: 0; display: block; width: 100%; height: 100%; padding: 0; border: 0; background: #000; cursor: pointer; }
.builder-video__poster { display: block; width: 100%; height: 100%; object-fit: cover; }
.builder-video__play { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: clamp(56px, 12%, 96px); aspect-ratio: 1; border-radius: 50%; border: 4px solid #fff; background: rgba(0, 0, 0, 0.25); transition: background-color 0.2s ease, transform 0.2s ease; }
/* The triangle is drawn, not typed: a glyph would inherit the page font and
   land differently in every theme. Nudged right of centre because an optical
   centre for a triangle is not its bounding box centre. */
.builder-video__play::after { content: ''; position: absolute; left: 54%; top: 50%; transform: translate(-50%, -50%); border-style: solid; border-width: 0.9em 0 0.9em 1.4em; border-color: transparent transparent transparent #fff; }
.builder-video__facade:hover .builder-video__play { background: rgba(0, 0, 0, 0.5); transform: translate(-50%, -50%) scale(1.06); }
.builder-video__facade:focus-visible { outline: 3px solid var(--color-primary); outline-offset: 3px; }
.builder-video__facade:focus-visible .builder-video__play { background: rgba(0, 0, 0, 0.5); }
@media (prefers-reduced-motion: reduce) {
    .builder-video__play { transition: none; }
    .builder-video__facade:hover .builder-video__play { transform: translate(-50%, -50%); }
}

/* Video + Text — the row reuses .content-row-grid, so only the player needs
   telling to stop centring itself inside its own half. */
.builder-block--video_text .builder-video { max-width: none; margin: 0; }

/* Accordion */
.builder-accordion { display: flex; flex-direction: column; gap: 0.5rem; }
/* Intro column beside the questions. One block, one row: the FAQ arrangement
   (copy on one side, the questions on the other) without asking two sibling
   blocks to fake it with a float, which orphans the moment an editor deletes
   or reorders either one. Same shape as the icon grid and mini gallery rows. */
.builder-accordion-row { display: grid; grid-template-columns: 4fr 6fr; gap: 2.5rem; align-items: start; }
.builder-accordion-row--intro-right { grid-template-columns: 6fr 4fr; }
/* Order, not DOM order: the intro stays first in the markup so it is read and
   indexed before the questions regardless of which side it is shown on. */
.builder-accordion-row--intro-right .builder-accordion__intro { order: 2; }
/* min-width: 0 is load-bearing: a grid item's automatic minimum size is its
   content, so one long unbroken word in a question could push the 6fr track
   wider than its share. */
.builder-accordion-row .builder-accordion { min-width: 0; }

@media (max-width: 768px) {
    /* Stacks intro-first in both arrangements: on a phone the copy introduces
       the questions, so leading with them reads as a non sequitur. */
    .builder-accordion-row,
    .builder-accordion-row--intro-right { grid-template-columns: 1fr; gap: 1.5rem; }
    .builder-accordion-row--intro-right .builder-accordion__intro { order: 0; }
}

/* Audio embed (Spotify, SoundCloud, Apple Podcasts) — fixed-height, not 16:9 */
.builder-video--audio { max-width: 900px; }
.builder-video--audio iframe { display: block; border-radius: var(--radius-lg); }

/* Pull Quote */
.builder-quote { margin: 1.5rem 0; padding: 1.5rem; border-left: 4px solid var(--quote-accent, var(--color-primary)); }
.builder-quote--centred { text-align: center; border-left: 0; padding: 2rem 1rem; }
.builder-quote--card { border-left: 0; background: var(--quote-card-bg, #f8f8f8); border-radius: var(--radius-lg); padding: 2rem; position: relative; }
.builder-quote--card::before { content: '\201C'; position: absolute; top: -0.5rem; left: 1rem; font-size: 5rem; color: var(--quote-accent, var(--color-primary)); line-height: 1; opacity: 0.3; font-family: Georgia, serif; }
.builder-quote__text { font-size: 1.25rem; font-style: italic; line-height: 1.5; margin: 0 0 1rem; }
.builder-quote__cite { font-style: normal; font-size: 0.9rem; color: #555; }
.builder-quote__cite cite { font-style: normal; font-weight: 600; color: var(--quote-author, #222); }
.builder-quote__role { display: block; color: var(--quote-role, var(--card-meta, #888)); font-size: 0.85rem; margin-top: 0.15rem; }

/* Buttons block */
.builder-buttons { display: flex; flex-wrap: wrap; gap: 0.75rem; margin: 1rem 0; }
.builder-buttons--left { justify-content: flex-start; }
.builder-buttons--center { justify-content: center; }
.builder-buttons--right { justify-content: flex-end; }
/* `.btn-secondary` / `.btn-outline` are declared once, in the Buttons section.
   The copies that used to sit here hardcoded a grey over the brand's secondary
   colour and carried their own padding, which is why a secondary button never
   matched a primary one beside it. */

/* Stats / Numbers */
.builder-stats { display: grid; gap: 1.5rem; margin: 1.5rem 0; text-align: center; }
.builder-stats--2col { grid-template-columns: repeat(2, 1fr); }
.builder-stats--3col { grid-template-columns: repeat(3, 1fr); }
.builder-stats--4col { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 768px) { .builder-stats--3col, .builder-stats--4col { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 480px) { .builder-stats { grid-template-columns: 1fr !important; } }
.builder-stat__value { font-size: 2.5rem; font-weight: 700; color: var(--stat-value, var(--color-primary)); line-height: 1.1; }
.builder-stat__label { font-size: 1rem; color: var(--stat-label, #222); margin-top: 0.3rem; }
.builder-stat__subtext { font-size: 0.85rem; color: var(--stat-subtext, var(--card-meta, #666)); margin-top: 0.25rem; }

/* Logo Strip */
.builder-logo-strip { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 2rem; margin: 1.5rem 0; }
.builder-logo-strip__item { display: inline-flex; align-items: center; }
.builder-logo-strip__item img { max-height: 60px; max-width: 160px; width: auto; height: auto; object-fit: contain; }
.builder-logo-strip--grayscale img { filter: grayscale(100%); opacity: 0.7; transition: filter 0.2s, opacity 0.2s; }
.builder-logo-strip--grayscale .builder-logo-strip__item:hover img { filter: grayscale(0%); opacity: 1; }

/* Logo Strip, continuous scroll (the block's "Scroll automatically" toggle).
   Three elements: the scroller clips, the track moves, and inside it sits the
   ordinary static strip with nothing done to it. The strip is left alone on
   purpose, so an install's own logo styling applies whether the strip scrolls
   or not.

   EVERYTHING here is inert until the script adds .is-scrolling. That is what
   makes the no-JavaScript case safe: without it the scroller does not clip, the
   track does not move, and what renders is the static strip a visitor would
   have got before this feature existed, rather than a clipped one. */
.builder-logo-strip-scroller { position: relative; margin: 1.5rem 0; }
.builder-logo-strip-scroller .builder-logo-strip { margin: 0; }
.builder-logo-strip-scroller.is-scrolling { overflow: hidden; }
.builder-logo-strip__track { display: flex; align-items: center; width: max-content; }

/* What turns the wrapping strip into one long row. An install that restyles
   .builder-logo-strip's own layout (a grid, a fixed column count) will
   out-specify this and the marquee will have nothing to scroll: scope that
   install rule with :not(.builder-logo-strip--scroll). */
.builder-logo-strip-scroller.is-scrolling .builder-logo-strip--scroll {
    flex-wrap: nowrap;
    width: max-content;
    justify-content: flex-start;
}

/* TWO classes, not one, and the split is load-bearing. .is-scrolling is the
   layout: nowrap, max-content, clipped. .is-running is the motion. The script
   has to turn the layout on BEFORE it measures how wide one lap is, because
   the width it needs is the width of the single nowrap row, and it must not be
   animating against a stale distance while it does so. So it adds the first,
   measures, writes the distance, then adds the second.

   Duration and distance are both set by the script, because both depend on the
   rendered width of the logos and no stylesheet can know it. The fallback of 0px
   is deliberate: if the script never runs, the animation moves nothing rather
   than sliding the strip somewhere arbitrary. */
.builder-logo-strip-scroller.is-running .builder-logo-strip__track {
    animation: builder-logo-strip-marquee linear infinite;
}

@keyframes builder-logo-strip-marquee {
    from { transform: translateX(0); }
    to   { transform: translateX(calc(-1 * var(--logo-strip-shift, 0px))); }
}

/* The button is the one that counts for WCAG 2.2.2, because hover does not
   exist on a touch screen. Hover and focus-within are here so that a visitor
   reaching for a logo is not chasing it, and so tabbing to a linked logo does
   not scroll it out from under the focus ring. */
.builder-logo-strip-scroller.is-running:hover .builder-logo-strip__track,
.builder-logo-strip-scroller.is-running:focus-within .builder-logo-strip__track,
.builder-logo-strip-scroller.is-paused .builder-logo-strip__track { animation-play-state: paused; }

.builder-logo-strip__pause {
    background: none; border: 0; cursor: pointer; padding: 0;
    min-width: 44px; min-height: 44px;
    color: inherit; font-size: 1rem; line-height: 1;
}
.builder-logo-strip__pause:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }
/* The UA rule for [hidden] only wins while nothing here declares display on the
   button. Stated so a later restyle adding one cannot bring the button back on
   strips that never scroll. */
.builder-logo-strip__pause[hidden] { display: none; }

/* Belt and braces with the script's own check: reduced motion never reaches the
   marquee, whichever of the two runs first. */
@media (prefers-reduced-motion: reduce) {
    .builder-logo-strip-scroller .builder-logo-strip__track { animation: none; }
}

/* Tabs */
.builder-tabs { margin: 1.5rem 0; }
.builder-tabs__nav { display: flex; flex-wrap: wrap; border-bottom: 2px solid #eee; margin-bottom: 1rem; }
.builder-tabs__tab { background: none; border: 0; padding: 0.75rem 1.25rem; font: inherit; color: var(--tab-inactive, #555); cursor: pointer; border-bottom: 3px solid transparent; margin-bottom: -2px; }
.builder-tabs__tab:hover { color: var(--tab-active, var(--color-primary-text)); }
.builder-tabs__tab.is-active { color: var(--tab-active, var(--color-primary-text)); border-bottom-color: var(--tab-active, var(--color-primary)); font-weight: 600; }
.builder-tabs__panel { display: none; }
.builder-tabs__panel.is-active { display: block; }

/* ── Carousel ──────────────────────────────────────────────── */
/* Geometry lives here rather than inline, unlike the slideshow hero whose size
   depends on per-record settings. That means an install can restyle it. */
.builder-carousel { position: relative; margin: 1.25rem 0; }

/* Edge-to-edge. --cms-viewport-width is set on :root by the carousel script and
   is the viewport MINUS the scrollbar; 100vw includes it, which is why core's
   own u-full-bleed sits half a scrollbar off centre. It is deliberately NOT
   declared on this element: an inherited custom property always loses to a
   local declaration, so a local fallback would pin the value and the script
   could never move it. With no JS the var() fallback gives 100% and the block
   simply stays inside its container. */
.builder-carousel--bleed {
    width: var(--cms-viewport-width, 100%);
    margin-left: calc(50% - var(--cms-viewport-width, 100%) / 2);
}
/* The shared block wrapper emits the heading and the preview warning INSIDE the
   block, so bleeding the block alone would push the editor's own heading hard
   against the screen edge. Restore the grid for them as well as the track.

   Half of --container-width, never a literal: an install that widens the
   container is the only reason to bleed a carousel in the first place, and a
   fixed half-of-1140 there lands the slides on a grid narrower than every
   block above and below them. The four values must stay identical — the script
   reads the viewport's scroll padding to decide which slides the track can
   reach, and falls back to the track's own offset when it cannot resolve it. */
.builder-carousel--bleed .builder-carousel__viewport { scroll-padding-inline-start: max(20px, calc(50% - var(--container-width, 1140px) / 2)); }
.builder-carousel--bleed .builder-carousel__track { padding-inline: max(20px, calc(50% - var(--container-width, 1140px) / 2)); }
.builder-carousel--bleed .builder-carousel__controls { padding-inline: max(20px, calc(50% - var(--container-width, 1140px) / 2)); }
.builder-block--carousel:has(.builder-carousel--bleed) > .builder-block__heading,
.builder-block--carousel:has(.builder-carousel--bleed) > .builder-block__eyebrow { padding-inline: max(20px, calc(50% - var(--container-width, 1140px) / 2)); }

.builder-carousel__viewport { overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; }
.builder-carousel__viewport::-webkit-scrollbar { display: none; }
.builder-carousel__viewport:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }
.builder-carousel__track { display: flex; gap: 1rem; list-style: none; margin: 0; padding: 0; }
/* start, not center: it is the layout the in-core strip gallery already uses,
   and it is the only one scroll-padding can be reasoned about. */
.builder-carousel__slide {
    flex: 0 0 calc((100% - (var(--carousel-cols, 3) - 1) * 1rem) / var(--carousel-cols, 3));
    scroll-snap-align: start;
    position: relative;
}
/* Owned here, unconditionally. The mini gallery's tile rules live inside the
   lightbox partial, so a block that does not include it loses all image sizing
   and falls back to intrinsic ratio. */
.builder-carousel__slide picture { display: block; }
/* Not on a carousel of cards: there the slide holds a card's photo and a
   reviewer's avatar, which this square crop would blow up to the slide's full
   width. The exclusion sits inside :where() so the selector still weighs 0,1,1,
   exactly what it weighed before, and an install restyling
   `.builder-carousel__slide img` keeps winning on source order. */
:where(.builder-carousel:not(.builder-carousel--cards)) .builder-carousel__slide img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; display: block; border-radius: var(--radius-md); }
.builder-carousel__caption { display: block; margin-top: 0.5rem; font-size: 0.85rem; color: var(--card-meta, #989898); }
/* Click to enlarge, when the block turns it on: the picture becomes the link.
   Block-level, or the anchor takes the inline box's line height with it and the
   focus ring sits a few pixels clear of the image on every side. */
.builder-carousel__zoom { display: block; cursor: zoom-in; border-radius: var(--radius-md); }
.builder-carousel__zoom:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }

.builder-carousel__controls { display: flex; align-items: center; gap: 0.875rem; margin-top: 1rem; }
/* The script hides the controls when every slide already fits. The display
   above outranks the UA rule for [hidden], so without this they stay. */
.builder-carousel__controls[hidden] { display: none; }
.builder-carousel__prev,
.builder-carousel__next,
.builder-carousel__pause {
    background: none; border: 0; cursor: pointer; padding: 0;
    min-width: 44px; min-height: 44px;
    color: inherit; font-size: 1rem; line-height: 1;
}
/* Reduced motion leaves nothing to pause, so the script hides the button. The
   UA rule for [hidden] only wins while nothing here declares display on it:
   stated so a later restyle adding one cannot bring back a control whose only
   effect would be to start the movement the visitor asked not to see. */
.builder-carousel__pause[hidden] { display: none; }
.builder-carousel__dots { display: flex; align-items: center; gap: 0.875rem; }
/* The 24px target rule is THREE coupled values: a 12px dot, a ::before inset of
   -7px giving a 26x26 hit area, and the 0.875rem gap that keeps neighbouring
   centres 26px apart so those areas cannot overlap. Shrink the gap and WCAG
   2.5.8 is defeated with nothing to see. */
.builder-carousel__dot {
    position: relative;
    width: 12px; height: 12px; padding: 0; border: 0; border-radius: 50%;
    background: var(--color-border, #d8d8d8); cursor: pointer;
}
.builder-carousel__dot::before { content: ''; position: absolute; inset: -7px; }
.builder-carousel__dot.is-active,
.builder-carousel__dot:hover { background: var(--color-primary); }
.builder-carousel__dot:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }
/* The script hides the dots the track cannot scroll to. The UA rule for the
   hidden attribute only wins while nothing here declares display on the dot, so
   this states it: without it, a later restyle adding one brings the dead dots
   back looking exactly as they did before they were fixed. */
.builder-carousel__dot[hidden] { display: none; }

@media (max-width: 900px) {
    /* Overriding the resolved flex-basis, NOT --carousel-cols: that arrives as
       an inline style attribute, and inline declarations outrank every author
       stylesheet rule, so redefining the variable here would do nothing. */
    .builder-carousel__slide { flex-basis: calc((100% - 1rem) / 2); }
}
@media (max-width: 600px) {
    .builder-carousel__slide { flex-basis: 80%; }
}

/* A Content List set to Carousel (feed_display_style = carousel). The slides
   hold the list's ordinary cards, so the card rules above style them and this
   only fits them to the row.

   Every card in the row is the height of the tallest. The slides already
   stretch, as flex items do; the card inside has to fill its slide. */
.builder-carousel--cards .builder-carousel__slide > .card,
.builder-carousel--cards .builder-carousel__slide > .feed-item,
.builder-carousel--cards .builder-carousel__slide > .testimonial-card { height: 100%; box-sizing: border-box; }
/* Spacing between cards is the track's gap here, not the card's margin. */
.builder-carousel--cards .testimonial-card { margin-bottom: 0; }
/* An overflow-x container clips on both axes, which would slice off the card
   hover lift and its shadow. The viewport's own padding is inside the clip, so
   it gives them room, and the matching negative margin keeps the row exactly
   where it would have sat. */
.builder-carousel--cards .builder-carousel__viewport { padding-block: 0.75rem 1.25rem; margin-block: -0.75rem -1.25rem; }

/* Content List intro column. The same row as the accordion, mini gallery and
   icon grid intros: copy down one side, the list beside it, one block. */
.builder-feed-row { display: grid; grid-template-columns: 4fr 6fr; gap: 2.5rem; align-items: start; }
.builder-feed-row--intro-right { grid-template-columns: 6fr 4fr; }
/* Order, not DOM order: the intro stays first in the markup so it is read and
   indexed before the list regardless of which side it is shown on. */
.builder-feed-row--intro-right .builder-feed__intro { order: 2; }
/* min-width: 0 is load-bearing: a grid item's automatic minimum size is its
   content, and a carousel's track is as wide as all its slides together, so
   without it the list column grows to fit every card and the row overflows
   the page instead of the carousel scrolling. */
.builder-feed-row .builder-feed__list { min-width: 0; }
/* The feed opens its own .content-section and .container, chrome for a list
   dropped into a text page. Inside the row it would push the cards below the
   top of the intro and inset them from their column, so the row drops it.
   Lists without an intro keep it untouched. */
.builder-feed-row .content-section { padding: 0; border-top: 0; }
.builder-feed-row .content-section > .container { max-width: none; padding: 0; }
.builder-feed-row .builder-carousel { margin-top: 0; }

@media (max-width: 768px) {
    /* Stacks intro-first in both arrangements: on a phone the copy introduces
       the list, so leading with the cards reads as a non sequitur. */
    .builder-feed-row,
    .builder-feed-row--intro-right { grid-template-columns: 1fr; gap: 1.5rem; }
    .builder-feed-row--intro-right .builder-feed__intro { order: 0; }
}

/* Mini Gallery block and its click-to-enlarge overlay. Keep them here, never in
   a <style> block in the partial: that prints in the <body> after a site
   stylesheet and beats any equally-specific site rule on source order alone. */
.mini-gallery { gap: 0.75rem; margin: 1.25rem 0; }
/* Grid — uniform square tiles, images cropped via object-fit. */
.mini-gallery--grid { display: grid; grid-template-columns: repeat(var(--mg-cols, 3), 1fr); }
/* Strip — single horizontally-scrolling row, natural-width thumbnails. */
.mini-gallery--strip { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; padding-bottom: 0.5rem; }
.mini-gallery--strip .mini-gallery__item { flex: 0 0 auto; width: clamp(150px, 32vw, 260px); scroll-snap-align: start; }
/* Masonry — CSS columns; tiles keep their natural aspect ratio and stagger. */
.mini-gallery--masonry { display: block; column-count: var(--mg-cols, 3); column-gap: 0.75rem; }
.mini-gallery--masonry .mini-gallery__item { break-inside: avoid; margin: 0 0 0.75rem; aspect-ratio: auto; }
.mini-gallery--masonry .mini-gallery__item img { height: auto; }
.mini-gallery__item { position: relative; display: block; overflow: hidden; border-radius: 6px; cursor: zoom-in; background: #f2f2f2; aspect-ratio: 1 / 1; }
.mini-gallery__item img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .35s ease; }
.mini-gallery__item:hover img { transform: scale(1.05); }
.mini-gallery__caption { position: absolute; left: 0; right: 0; bottom: 0; padding: 0.5rem 0.6rem; font-size: 0.8rem; line-height: 1.3; color: #fff; background: linear-gradient(to top, rgba(0,0,0,0.72), rgba(0,0,0,0)); }
@media (max-width: 768px) {
    .mini-gallery--grid { grid-template-columns: repeat(2, 1fr); }
    .mini-gallery--masonry { column-count: 2; }
}
@media (prefers-reduced-motion: reduce) { .mini-gallery__item img { transition: none; } .mini-gallery__item:hover img { transform: none; } }

.mini-gallery-lightbox { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.92); z-index: 9999; align-items: center; justify-content: center; cursor: zoom-out; }
.mini-gallery-lightbox.is-open { display: flex; }
.mini-gallery-lightbox img { max-width: 92vw; max-height: 82vh; object-fit: contain; cursor: default; box-shadow: 0 6px 40px rgba(0,0,0,0.5); }
.mini-gallery-lightbox__caption { position: absolute; left: 0; right: 0; bottom: 1.25rem; text-align: center; color: #fff; font-size: 0.95rem; padding: 0 1.5rem; pointer-events: none; }
.mini-gallery-lightbox__btn { position: absolute; background: none; border: 0; color: #fff; cursor: pointer; line-height: 1; padding: 0.25rem 0.75rem; }
.mini-gallery-lightbox__close { top: 1rem; right: 1.5rem; font-size: 2.25rem; }
.mini-gallery-lightbox__prev { left: 1rem; top: 50%; transform: translateY(-50%); font-size: 2.75rem; }
.mini-gallery-lightbox__next { right: 1rem; top: 50%; transform: translateY(-50%); font-size: 2.75rem; }

/* Mini Gallery intro column. Same arrangement as the Features Grid row below,
   with one difference: the margin is 1.25rem to match `.mini-gallery`'s own,
   not the 1.5rem the icon grid uses. Aligning the two would change the spacing
   of every existing gallery. */
.mini-gallery-row { display: grid; grid-template-columns: 4fr 6fr; gap: 2.5rem; align-items: start; margin: 1.25rem 0; }
.mini-gallery-row--intro-right { grid-template-columns: 6fr 4fr; }
/* Order, not DOM order: the intro stays first in the markup so it is read and
   indexed before the photos regardless of which side it is shown on. */
.mini-gallery-row--intro-right .mini-gallery__intro { order: 2; }
/* Load-bearing: a grid item's automatic minimum size is its content, so the
   masonry layout (display:block + column-count, overflow visible) can push the
   6fr track wider than its share. The strip resolves it already via
   overflow-x, but one rule covers both. */
.mini-gallery-row .mini-gallery { margin: 0; min-width: 0; }

@media (max-width: 768px) {
    .mini-gallery-row,
    .mini-gallery-row--intro-right { grid-template-columns: 1fr; gap: 1.5rem; }
    .mini-gallery-row--intro-right .mini-gallery__intro { order: 0; }
}

/* Icon + Text Grid (Features) */
/* Intro column beside the cards. One block, one row — the arrangement the
   pattern needs, without asking two sibling blocks to fake it with a float
   (which orphans the moment an editor deletes or reorders either one). */
.builder-icon-grid-row { display: grid; grid-template-columns: 4fr 6fr; gap: 2.5rem; align-items: start; margin: 1.5rem 0; }
.builder-icon-grid-row--intro-right { grid-template-columns: 6fr 4fr; }
/* Order, not DOM order: the intro stays first in the markup so it is read and
   indexed before the cards regardless of which side it is shown on. */
.builder-icon-grid-row--intro-right .builder-icon-grid__intro { order: 2; }
.builder-icon-grid-row .builder-icon-grid { margin: 0; }

@media (max-width: 768px) {
    /* Stacks intro-first in both arrangements: on a phone the copy introduces
       the cards, so leading with them reads as a non sequitur. */
    .builder-icon-grid-row,
    .builder-icon-grid-row--intro-right { grid-template-columns: 1fr; gap: 1.5rem; }
    .builder-icon-grid-row--intro-right .builder-icon-grid__intro { order: 0; }
}

.builder-icon-grid { display: grid; gap: 1.5rem; margin: 1.5rem 0; }
.builder-icon-grid--2col { grid-template-columns: repeat(2, 1fr); }
.builder-icon-grid--3col { grid-template-columns: repeat(3, 1fr); }
.builder-icon-grid--4col { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 768px) { .builder-icon-grid--3col, .builder-icon-grid--4col { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 480px) { .builder-icon-grid { grid-template-columns: 1fr !important; } }
/* A linked card is an <a>, so the UA underline and link colour have to go or
   every card title turns blue. display:block because an inline <a> would not
   take the card's box. */
.builder-icon-grid__item--link { display: block; color: inherit; text-decoration: none; transition: opacity 0.2s; }
.builder-icon-grid__item--link:hover,
.builder-icon-grid__item--link:focus-visible { color: inherit; text-decoration: none; }
.builder-icon-grid__item--link:hover { opacity: 0.85; }
.builder-icon-grid__item--link .builder-icon-grid__title { color: inherit; }

.builder-icon-grid__icon img { width: 48px; height: 48px; object-fit: contain; }
.builder-icon-grid__icon--svg { color: var(--icon-color, var(--color-primary)); line-height: 0; }
.builder-icon-grid__svg { width: 48px; height: 48px; display: inline-block; vertical-align: middle; }
.builder-icon-grid__title { margin: 0.6rem 0 0.4rem; font-size: 1.1rem; }
.builder-icon-grid__desc { margin: 0; color: var(--icon-desc, #555); font-size: 0.95rem; line-height: 1.5; }

/* Photo tiles: the card's custom image fills the card and the text sits on a
   wash at the foot. The wash is what keeps white text readable on whatever
   photo an editor picks, so a restyle changes --icon-tile-wash, never removes
   it. Restyle through the custom properties rather than the geometry. */
.builder-icon-grid--photo {
    --icon-tile-height: 20rem;
    --icon-tile-pad: 1.75rem;
    --icon-tile-text: #fff;
    --icon-tile-bg: var(--color-dark, #222);
    --icon-tile-wash: linear-gradient(to bottom, rgba(0, 0, 0, 0) 30%, rgba(0, 0, 0, 0.7) 100%);
}
.builder-icon-grid--photo .builder-icon-grid__item {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    min-height: var(--icon-tile-height);
    padding: var(--icon-tile-pad);
    border-radius: var(--radius-md, 6px);
    background: var(--icon-tile-bg);
    color: var(--icon-tile-text);
}
.builder-icon-grid__photo { position: absolute; inset: 0; z-index: -1; }
.builder-icon-grid__photo::after { content: ""; position: absolute; inset: 0; background: var(--icon-tile-wash); }
.builder-icon-grid__photo picture,
.builder-icon-grid__photo img { display: block; width: 100%; height: 100%; object-fit: cover; }
.builder-icon-grid--photo .builder-icon-grid__title { margin: 0 0 0.35rem; color: inherit; font-size: 1.6rem; }
.builder-icon-grid--photo .builder-icon-grid__desc { color: inherit; }
.builder-icon-grid--photo .builder-icon-grid__item--link:hover { opacity: 1; }
.builder-icon-grid--photo .builder-icon-grid__item--link:hover .builder-icon-grid__photo img { transform: scale(1.03); }
.builder-icon-grid--photo .builder-icon-grid__photo img { transition: transform 0.4s ease; }
@media (prefers-reduced-motion: reduce) {
    .builder-icon-grid--photo .builder-icon-grid__photo img { transition: none; }
    .builder-icon-grid--photo .builder-icon-grid__item--link:hover .builder-icon-grid__photo img { transform: none; }
}

/* Pricing */
.builder-pricing { display: grid; gap: 1.5rem; margin: 1.5rem 0; }
.builder-pricing--2col { grid-template-columns: repeat(2, 1fr); }
.builder-pricing--3col { grid-template-columns: repeat(3, 1fr); }
.builder-pricing--4col { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 768px) { .builder-pricing--3col, .builder-pricing--4col { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .builder-pricing { grid-template-columns: 1fr !important; } }
.builder-pricing__plan { background: var(--pricing-bg, #fff); border: 1px solid var(--pricing-border, #e5e5e5); border-radius: var(--radius-lg); padding: 2rem 1.5rem; text-align: center; display: flex; flex-direction: column; position: relative; }
.builder-pricing__plan.is-featured { border-color: var(--pricing-accent, var(--color-primary)); border-width: 2px; transform: scale(1.02); box-shadow: 0 4px 20px rgba(var(--color-primary-rgb), 0.15); }
.builder-pricing__badge { position: absolute; top: -0.75rem; left: 50%; transform: translateX(-50%); background: var(--pricing-accent, var(--color-primary-text)); color: #fff; padding: 0.25rem 0.9rem; border-radius: 999px; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }
.builder-pricing__name { font-size: 1.1rem; font-weight: 600; color: var(--pricing-name, #222); margin-bottom: 0.5rem; }
.builder-pricing__price { margin: 0.5rem 0 1rem; }
.builder-pricing__amount { font-size: 2.5rem; font-weight: 700; color: var(--pricing-accent, var(--color-primary)); }
.builder-pricing__period { font-size: 1rem; color: var(--pricing-text, #666); margin-left: 0.25rem; }
.builder-pricing__desc { color: var(--pricing-text, #666); font-size: 0.9rem; margin: 0 0 1rem; }
.builder-pricing__features { list-style: none; padding: 0; margin: 1rem 0; text-align: left; flex-grow: 1; }
.builder-pricing__features li { padding: 0.4rem 0; border-bottom: 1px solid #f0f0f0; font-size: 0.95rem; }
.builder-pricing__features li:last-child { border-bottom: 0; }
.builder-pricing__features li::before { content: '\2713'; color: var(--pricing-check, var(--color-secondary)); font-weight: 700; margin-right: 0.5rem; }
.builder-pricing__plan .btn { margin-top: auto; }

/* Block-picker tile alignment for new types */
.block-picker-modal .filament-forms-toggle-buttons-component label { min-height: 86px; }

/* Countdown */
.builder-countdown { text-align: center; padding: 1rem 0; }
.builder-countdown__units { display: flex; justify-content: center; align-items: flex-start; gap: 0.25rem; flex-wrap: wrap; }
.builder-countdown__unit { display: flex; flex-direction: column; align-items: center; min-width: 72px; }
.builder-countdown__value { font-size: 3rem; font-weight: 700; color: var(--countdown-color, var(--color-primary)); line-height: 1; font-variant-numeric: tabular-nums; }
.builder-countdown__label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--countdown-label, #888); margin-top: 0.3rem; }
.builder-countdown__sep { font-size: 2.5rem; font-weight: 700; color: var(--countdown-color, var(--color-primary)); line-height: 1; padding-top: 0.1rem; }
.builder-countdown__sublabel { margin-top: 1.25rem; color: var(--countdown-sublabel, #444); font-size: 1rem; }
.builder-countdown__expired { font-size: 1.5rem; font-weight: 600; color: var(--countdown-color, var(--color-primary)); }
@media (max-width: 480px) { .builder-countdown__value { font-size: 2rem; } .builder-countdown__unit { min-width: 52px; } }

/* Info Card (block_type `flip_box`)
   The 3D flip was removed, not made keyboard-operable: it hid half its content,
   including the CTA, behind a hover or tap that no keyboard user could trigger.
   See Documentation/ACCESSIBILITY-PLAN.md §2.2.11a.
   The four --flip-* Theme Studio tokens are DELIBERATELY retained and repurposed
   rather than orphaned — front = the card, back = its call to action — so every
   existing Theme Studio control stays live and meaningful. Renaming them would
   invalidate every saved theme's stored token keys. */
.builder-card-grid { display: grid; gap: 1.5rem; margin: 1.5rem 0; width: 100%; }
.builder-card-grid--2col { grid-template-columns: repeat(2, 1fr); }
.builder-card-grid--3col { grid-template-columns: repeat(3, 1fr); }
.builder-card-grid--4col { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 768px) { .builder-card-grid--3col, .builder-card-grid--4col { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 480px) { .builder-card-grid { grid-template-columns: 1fr !important; } }

.builder-info-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    /* min-height, never height: a fixed height clips the card's own content at
       larger text sizes, which browser zoom or a text-resize preference exposes
       immediately (WCAG 1.4.4 / 1.4.10). */
    min-height: 260px;
    padding: 1.5rem;
    background: var(--flip-front-bg, var(--color-bg-subtle));
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
}
.builder-info-card__image { margin-bottom: 0.75rem; }
.builder-info-card__image img { width: 56px; height: 56px; object-fit: cover; border-radius: 50%; }
.builder-info-card__heading { margin: 0 0 0.4rem; font-size: 1.05rem; font-weight: 600; }
.builder-info-card__subheading { margin: 0.85rem 0 0.3rem; font-size: 0.95rem; font-weight: 600; }
.builder-info-card__text { margin: 0 0 0.5rem; font-size: 0.88rem; line-height: 1.55; color: var(--flip-front-text, #555); }
.builder-info-card__text:last-of-type { margin-bottom: 0; }
/* margin-top:auto pins the CTA to the card foot so a grid of uneven cards still
   lines its buttons up. */
.builder-info-card__cta {
    margin-top: auto;
    padding-top: 0.25rem;
    align-self: center;
    background: var(--flip-back-bg, transparent);
    border-color: var(--flip-back-bg, var(--color-primary));
    color: var(--flip-back-text, var(--color-primary-text));
}
.builder-info-card__cta:hover { filter: brightness(0.92); }

/* Share / Follow Buttons */
.builder-share { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.builder-share--center { justify-content: center; }
.builder-share--right { justify-content: flex-end; }
.builder-share__btn { display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.45rem; border-radius: var(--radius-md); text-decoration: none; border: none; background: none; cursor: pointer; transition: opacity 0.15s, transform 0.15s; line-height: 1; }
.builder-share__btn:hover { opacity: 0.75; transform: translateY(-1px); text-decoration: none; }
.builder-share--icons_labels .builder-share__btn { padding: 0.4rem 0.8rem; border-radius: var(--radius-md); border: 1px solid currentColor; }
.builder-share--pills .builder-share__btn { padding: 0.4rem 1rem; border-radius: 999px; color: #fff !important; }
.builder-share__icon { width: 20px; height: 20px; display: block; flex-shrink: 0; }
.builder-share__icon svg { width: 100%; height: 100%; display: block; }
.builder-share__label { font-size: 0.85rem; font-weight: 500; white-space: nowrap; }
/* Brand colours keyed on data-platform, so a site can retint one network or
   all of them. The colour rule is two classes so core's `a:hover` (0,1,1) and a
   site's `.page-body a` cannot recolour the icons. */
.builder-share__btn[data-platform="facebook"]  { --share-color: #1877F2; }
.builder-share__btn[data-platform="twitter"]   { --share-color: #000000; }
.builder-share__btn[data-platform="linkedin"]  { --share-color: #0A66C2; }
.builder-share__btn[data-platform="pinterest"] { --share-color: #E60023; }
.builder-share__btn[data-platform="instagram"] { --share-color: #E4405F; }
.builder-share__btn[data-platform="youtube"]   { --share-color: #FF0000; }
.builder-share__btn[data-platform="tiktok"]    { --share-color: #010101; }
.builder-share__btn[data-platform="whatsapp"]  { --share-color: #25D366; }
.builder-share__btn[data-platform="email"]     { --share-color: #555555; }
.builder-share .builder-share__btn { color: var(--share-color, inherit); }
.builder-share--pills .builder-share__btn { background: var(--share-color, #555555); }

/* Flip box: contain hover-flip within block wrapper on desktop.
   width:100% stops flow-root shrink-wrapping the wrapper to the cards'
   content width, which collapsed the grid's 1fr columns at wide breakpoints. */
@media (hover: hover) { .builder-block--flip_box { display: flow-root; width: 100%; } }

/* TOC: contain its float within the builder-block wrapper */
.builder-block--table_of_contents { display: flow-root; }

/* Table of Contents */
.builder-toc { background: var(--color-bg-subtle); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 1.1rem 1.4rem; }
.builder-toc--float_right { float: right; margin: 0 0 1.5rem 2rem; max-width: 270px; width: 100%; }
.builder-toc--float_left { float: left; margin: 0 2rem 1.5rem 0; max-width: 270px; width: 100%; }
.builder-toc__heading { font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #888; margin: 0 0 0.7rem; }
.builder-toc ul { margin: 0; padding: 0; list-style: none; }
.builder-toc__item { margin: 0; padding: 0; }
.builder-toc__item a { display: block; padding: 0.22rem 0; color: var(--color-primary-text, var(--color-primary)); text-decoration: none; font-size: 0.88rem; line-height: 1.4; }
.builder-toc__item a:hover { text-decoration: underline; }
.builder-toc__item--h3 { padding-left: 1rem; }
.builder-toc__item--h4 { padding-left: 2rem; }
.builder-toc__item--h3 a, .builder-toc__item--h4 a { font-size: 0.82rem; color: #666; }
.builder-toc__item--h3 a:hover, .builder-toc__item--h4 a:hover { color: var(--color-primary-text, var(--color-primary)); }
@media (max-width: 600px) { .builder-toc--float_right, .builder-toc--float_left { float: none; max-width: 100%; margin: 0 0 1.5rem; } }
.builder-toc__noscript { font-size: 0.85rem; color: #666; }
/* Spacer and divider. Sizes are classes; an editor's own line width, style and
   colour arrive as --divider-* custom properties only where they differ from
   the default, so a site can set its own default divider (`--divider-color` and
   friends) without fighting each block. Scoped to the block so a site's
   `.page-body hr` does not restyle it. */
.builder-block--spacer .builder-spacer--small  { height: 20px; }
.builder-block--spacer .builder-spacer--medium { height: 40px; }
.builder-block--spacer .builder-spacer--large  { height: 80px; }
.builder-block--spacer .builder-divider {
    margin: 40px auto;
    border: 0;
    border-top: var(--divider-width, 1px) var(--divider-style, solid) var(--divider-color, #dddddd);
    max-width: 100%;
}
.builder-block--spacer .builder-divider--space-small { margin: 20px auto; }
.builder-block--spacer .builder-divider--space-large { margin: 80px auto; }
.builder-block--spacer .builder-divider--max-narrow { max-width: 200px; }
.builder-block--spacer .builder-divider--max-medium { max-width: 500px; }

/* ===== Cookie consent banner =====
   Scoped by the banner's own id so a site's general rules (`button`, `a:hover`,
   `label`) cannot reach in and restyle it by accident; a site that means to
   restyle it sets the --cookie-banner-* properties below, or writes its rule
   with the same id. The root ships hidden (inline display:none) and the
   partial's script reveals it. */
#cms-cookie-banner {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 9990;
    background: var(--cookie-banner-bg, #1f2937);
    color: var(--cookie-banner-text, #fff);
    padding: 18px 22px;
    box-shadow: 0 -4px 20px rgba(0,0,0,0.18);
    font-family: inherit;
    border-radius: 0;
}
#cms-cookie-banner.cms-cookie-banner--top { top: 0; bottom: auto; }
#cms-cookie-banner.cms-cookie-banner--bottom-left,
#cms-cookie-banner.cms-cookie-banner--bottom-right { bottom: 20px; max-width: 420px; border-radius: var(--cookie-banner-radius, 10px); }
#cms-cookie-banner.cms-cookie-banner--bottom-left { left: 20px; right: auto; }
#cms-cookie-banner.cms-cookie-banner--bottom-right { right: 20px; left: auto; }
#cms-cookie-banner [hidden] { display: none; }
#cms-cookie-banner .cms-cookie-banner__inner { max-width: 1200px; margin: 0 auto; display: flex; flex-wrap: wrap; align-items: center; gap: 14px 20px; }
#cms-cookie-banner.cms-cookie-banner--granular .cms-cookie-banner__inner { align-items: flex-start; }
#cms-cookie-banner .cms-cookie-banner__text { flex: 1 1 320px; min-width: 0; font-size: 14px; line-height: 1.5; }
#cms-cookie-banner .cms-cookie-banner__heading { display: block; margin-bottom: 4px; font-size: 15px; }
#cms-cookie-banner .cms-cookie-banner__message { color: var(--cookie-banner-muted, #d1d5db); }
#cms-cookie-banner .cms-cookie-banner__link { color: var(--cookie-banner-link, #fff); text-decoration: underline; margin-left: 6px; }
/* Reopened from "Cookie settings", the banner takes focus so a screen reader
   lands in it and Tab reaches its controls next. It is a container, focused
   only by the script, and its appearing is the visible sign, as with main:focus. */
#cms-cookie-banner:focus { outline: none; }
#cms-cookie-banner .cms-cookie-banner__current { margin: 6px 0 0; font-size: 13px; color: var(--cookie-banner-muted, #d1d5db); }
#cms-cookie-banner .cms-cookie-banner__categories { margin-top: 14px; display: grid; gap: 10px; }
#cms-cookie-banner .cms-cookie-banner__category { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; }
#cms-cookie-banner .cms-cookie-banner__category--locked { cursor: not-allowed; opacity: 0.85; }
#cms-cookie-banner .cms-cookie-banner__category input { margin-top: 3px; flex-shrink: 0; }
#cms-cookie-banner .cms-cookie-banner__category-text,
#cms-cookie-banner .cms-cookie-banner__category-text strong { font-size: 13px; line-height: 1.4; }
#cms-cookie-banner .cms-cookie-banner__category-desc { color: var(--cookie-banner-faint, #9ca3af); }
#cms-cookie-banner .cms-cookie-banner__actions { display: flex; gap: 8px; flex-wrap: wrap; flex-shrink: 0; }
#cms-cookie-banner.cms-cookie-banner--granular .cms-cookie-banner__actions { align-self: center; }
#cms-cookie-banner .cms-cookie-banner__btn {
    padding: 9px 16px;
    background: var(--cookie-banner-button-bg, rgba(255,255,255,0.12));
    color: var(--cookie-banner-button-text, #fff);
    border: 1px solid var(--cookie-banner-button-border, rgba(255,255,255,0.7));
    border-radius: 6px;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
}
#cms-cookie-banner .cms-cookie-banner__btn--accept {
    background: var(--cookie-banner-accept-bg, #fff);
    color: var(--cookie-banner-accept-text, #111827);
    border: 0;
}

/* ===== Gallery lightbox =====
   Scoped by the dialog's id so a site's general `button` and `img` rules cannot
   reach in; a site restyles it with the same id or the --lightbox-* properties.
   The root ships hidden (inline display:none) and the script sets it to flex. */
#lightbox { position: fixed; inset: 0; background: var(--lightbox-bg, rgba(0,0,0,0.9)); z-index: 9999; cursor: pointer; align-items: center; justify-content: center; flex-direction: column; }
#lightbox .cms-lightbox__close,
#lightbox .cms-lightbox__arrow { position: absolute; background: none; border: none; color: var(--lightbox-text, #fff); cursor: pointer; z-index: 10000; }
#lightbox .cms-lightbox__close { top: 1rem; right: 1.5rem; font-size: 2rem; }
#lightbox .cms-lightbox__arrow { top: 50%; transform: translateY(-50%); font-size: 2.5rem; }
#lightbox .cms-lightbox__arrow--prev { left: 1rem; }
#lightbox .cms-lightbox__arrow--next { right: 1rem; }
#lightbox .cms-lightbox__img { max-height: 80vh; max-width: 90vw; object-fit: contain; border-radius: 4px; }
#lightbox .cms-lightbox__caption { color: var(--lightbox-text, #fff); text-align: center; margin-top: 1rem; font-size: 0.95rem; max-width: 600px; }

/* ===== Site announcements (banner / popup modal / full-screen takeover) =====
   Colours come in as inline --ann-bg / --ann-fg custom properties (sanitised to
   hex server-side). The root element ships hidden (inline display:none); the
   partial's JS reveals it, so layout is handed back to these classes. */
.cms-announcement, .cms-announcement *, .cms-announcement *::before, .cms-announcement *::after { box-sizing: border-box; }

/* --- Top banner --- */
.cms-announcement--banner { display: block; position: relative; background: var(--ann-bg, #1f2937); color: var(--ann-fg, #fff); z-index: 9970; }
.cms-announcement--banner .cms-announcement__inner { max-width: 1120px; margin: 0 auto; padding: 0.7rem 3rem 0.7rem 1.25rem; }
.cms-announcement--banner-full .cms-announcement__inner { max-width: none; }
.cms-announcement--banner .cms-announcement__content { font-size: 0.95rem; line-height: 1.5; }
.cms-announcement--banner .cms-announcement__content p { margin: 0; display: inline; }
.cms-announcement--banner .cms-announcement__title { margin-right: 0.45rem; }
.cms-announcement--banner .cms-announcement__content a { color: inherit; text-decoration: underline; }
/* A banner is a thin bar — images would break it, so they're not shown in this
   style (kept in the content, so they reappear if switched to popup/full-screen).
   !important so it always beats the img-left/right/centre alignment rules, which
   have equal specificity and are declared later in this file. */
.cms-announcement--banner .cms-announcement__content img { display: none !important; }
.cms-announcement--banner .cms-announcement__close { position: absolute; top: 50%; right: 0.6rem; transform: translateY(-50%); }
/* No × to make room for, so the right gutter matches the left. */
.cms-announcement--banner.cms-announcement--no-close .cms-announcement__inner { padding-right: 1.25rem; }

/* Banner overflow: scroll (ticker). Wrap is the default and needs no extra rules. */
.cms-announcement--overflow-scroll .cms-announcement__inner { overflow: hidden; }
.cms-announcement--overflow-scroll .cms-announcement__content { display: inline-block; white-space: nowrap; padding-left: 100%; animation: cms-ann-marquee var(--ann-scroll, 18s) linear infinite; }
.cms-announcement--overflow-scroll .cms-announcement__content p { display: inline; }
.cms-announcement--overflow-scroll .cms-announcement__inner:hover .cms-announcement__content { animation-play-state: paused; }
@keyframes cms-ann-marquee { from { transform: translateX(0); } to { transform: translateX(-100%); } }
@media (prefers-reduced-motion: reduce) { .cms-announcement--overflow-scroll .cms-announcement__content { animation: none; padding-left: 0; white-space: normal; display: block; } .cms-announcement--overflow-scroll .cms-announcement__content p { display: inline; } }

/* --- Shared overlay (modal + full-screen) --- */
.cms-announcement--modal, .cms-announcement--fullscreen { position: fixed; inset: 0; z-index: 9980; display: flex; align-items: center; justify-content: center; padding: 1.5rem; }
.cms-announcement--modal { background: rgba(0, 0, 0, 0.55); }
.cms-announcement--fullscreen { background: var(--ann-bg, #1f2937); color: var(--ann-fg, #fff); }
.cms-announcement__box { position: relative; max-width: 560px; width: 100%; border-radius: 10px; padding: 2rem 2.2rem; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35); }
.cms-announcement--modal .cms-announcement__box { background: var(--ann-bg, #1f2937); color: var(--ann-fg, #fff); }
.cms-announcement--fullscreen .cms-announcement__box { background: transparent; box-shadow: none; max-width: 720px; text-align: center; padding: 1rem; }
.cms-announcement--modal .cms-announcement__title, .cms-announcement--fullscreen .cms-announcement__title { margin: 0 0 0.8rem; font-size: 1.6rem; line-height: 1.2; }
.cms-announcement--fullscreen .cms-announcement__title { font-size: clamp(1.8rem, 5vw, 2.6rem); }
.cms-announcement__content a { color: inherit; text-decoration: underline; }
.cms-announcement__content p { margin: 0 0 0.6rem; }
.cms-announcement__content p:last-child { margin-bottom: 0; }
/* Title + all body text use the chosen text colour. Override the global heading
   and link colours (h2 etc. default to the brand colour) so the whole
   announcement reads as one colour on its background. */
.cms-announcement__title, .cms-announcement__content,
.cms-announcement__content :where(h1, h2, h3, h4, h5, h6, p, ul, ol, li, strong, em, span, a, blockquote) { color: inherit; }
/* Rich-text image alignment (TinyMCE "Class" dropdown: img-left / img-right /
   img-centre). The page-body equivalents are scoped to .page-body .cms-content,
   which the announcement isn't inside, so mirror them here. */
.cms-announcement__content img { margin: 0.6rem 0; border-radius: var(--radius-sm); }
.cms-announcement__content img.img-left { float: left; margin: 0 1.25em 0.75em 0; }
.cms-announcement__content img.img-right { float: right; margin: 0 0 0.75em 1.25em; }
.cms-announcement__content img.img-centre { display: block; margin-left: auto; margin-right: auto; }
.cms-announcement__content::after { content: ''; display: table; clear: both; }
.cms-announcement--modal .cms-announcement__close { position: absolute; top: 0.5rem; right: 0.6rem; }
.cms-announcement--fullscreen .cms-announcement__close { position: fixed; top: 1rem; right: 1.2rem; }

/* --- Close button (shared) --- */
.cms-announcement__close { appearance: none; background: transparent; border: 0; cursor: pointer; color: inherit; opacity: 0.8; font-size: 1.6rem; line-height: 1; width: 2rem; height: 2rem; display: inline-flex; align-items: center; justify-content: center; border-radius: var(--radius-sm); }
.cms-announcement__close:hover { opacity: 1; background: rgba(127, 127, 127, 0.22); }

@media (max-width: 600px) { .cms-announcement--banner .cms-announcement__content { font-size: 0.9rem; } .cms-announcement__box { padding: 1.5rem 1.4rem; } }

/* ============================================================
   Shop (SHOP P3) — storefront catalogue
   Token-first: cards/buttons inherit the existing Theme Studio
   kit (--card-*, buttons); shop-specific hooks carry their own
   --shop-* custom properties with hard-coded fallbacks so an
   un-themed install renders exactly these values.
   ============================================================ */

.shop-category-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 1.25rem;
    margin: 0 0 2rem;
}
.shop-category-card { text-decoration: none; display: flex; flex-direction: column; overflow: hidden; }
.shop-category-card__image img { width: 100%; height: 140px; object-fit: cover; display: block; }
.shop-category-card__name { display: block; font-weight: 700; color: var(--card-title, #333); }
.shop-category-card__count { display: block; font-size: 0.85rem; color: var(--card-meta, #777); margin-top: 0.25rem; }

.shop-product-grid { margin-top: 1rem; }
.shop-product-card { display: flex; flex-direction: column; overflow: hidden; }
.shop-product-card__image { display: block; }
.shop-product-card__image img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; }
.shop-product-card__title a { color: var(--card-title, #333); text-decoration: none; }
.shop-product-card__meta { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; margin: 0.35rem 0 0.6rem; }
.shop-product-card__excerpt { font-size: 0.92rem; color: var(--card-text, #555); }

.shop-price { font-weight: 700; color: var(--shop-price-color, var(--card-title, #333)); }
.shop-price__was { color: var(--card-meta, #888); font-weight: 400; margin-right: 0.4rem; }
.shop-price__now { color: var(--shop-sale-color, #c0392b); }
.shop-price--poa { font-weight: 600; font-style: italic; }

.shop-stock-badge {
    display: inline-block;
    font-size: 0.78rem;
    font-weight: 700;
    padding: 0.15rem 0.55rem;
    border-radius: 999px;
    background: var(--shop-stock-out-bg, #fdecea);
    color: var(--shop-stock-out-color, #c0392b);
}
.shop-stock-badge--backorder {
    background: var(--shop-stock-backorder-bg, #fef5e7);
    color: var(--shop-stock-backorder-color, #9a6a12);
}

.shop-product-layout {
    display: grid;
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
    gap: 2.5rem;
    margin-bottom: 2rem;
}
.shop-product-gallery__main img { width: 100%; border-radius: var(--card-radius, 8px); display: block; }
.shop-product-gallery__thumbs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.75rem; margin-top: 0.75rem; }
.shop-product-gallery__thumbs img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; border-radius: var(--card-radius, 6px); display: block; }

.shop-product-info__price { font-size: 1.5rem; margin-bottom: 0.75rem; }
.shop-product-info__summary { margin-bottom: 1.25rem; }
.shop-product-info__meta { list-style: none; padding: 0; margin: 1.25rem 0 0; font-size: 0.92rem; color: var(--card-meta, #666); }
.shop-product-info__meta li { margin-bottom: 0.35rem; }

.shop-variant-label { display: block; font-weight: 700; margin-bottom: 0.35rem; }
.shop-variant-select {
    width: 100%;
    max-width: 420px;
    padding: 0.55rem 0.75rem;
    border: 1px solid var(--card-border, #ddd);
    border-radius: 6px;
    font-size: 1rem;
    background: #fff;
    margin-bottom: 1rem;
}
.shop-variant-select option:disabled { color: #aaa; }

.shop-category-intro { max-width: 60ch; margin-bottom: 1.5rem; }

@media (max-width: 900px) {
    .shop-category-grid { grid-template-columns: repeat(2, 1fr); }
    .shop-product-layout { grid-template-columns: 1fr; gap: 1.5rem; }
}

/* ── Shop basket (SHOP P4a) ── */
.shop-basket-bar { display: flex; justify-content: flex-end; margin-bottom: 1rem; }
.shop-add-to-basket { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; margin: 1rem 0; }
.shop-add-to-basket .shop-cart-qty { width: 5rem; }
.shop-add-feedback a { font-weight: 700; }
.shop-cart-flash { background: var(--shop-flash-bg, #eef7ea); border: 1px solid var(--shop-flash-border, #cfe6c6); padding: 0.6rem 1rem; border-radius: 6px; }
.shop-cart-table { width: 100%; border-collapse: collapse; margin-bottom: 1rem; }
.shop-cart-table th { text-align: left; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--card-meta, #777); padding: 0.5rem 0.6rem; border-bottom: 2px solid var(--card-border, #ddd); }
.shop-cart-table td { padding: 0.75rem 0.6rem; border-bottom: 1px solid var(--card-border, #e5e5e5); vertical-align: middle; }
.shop-cart-item { display: flex; align-items: center; gap: 0.9rem; }
.shop-cart-item__image img { width: 64px; height: 64px; object-fit: cover; border-radius: 6px; display: block; }
.shop-cart-item__variant { font-size: 0.85rem; color: var(--card-meta, #777); }
.shop-cart-qty { width: 4.5rem; padding: 0.4rem 0.5rem; border: 1px solid var(--card-border, #ccc); border-radius: 6px; }
.shop-cart-remove { background: none; border: none; font-size: 1.4rem; line-height: 1; cursor: pointer; color: var(--card-meta, #999); }
.shop-cart-remove:hover { color: #c0392b; }
.shop-cart-actions { display: flex; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 1.5rem; }
.shop-cart-totals { max-width: 380px; margin-left: auto; background: var(--card-bg, #fafafa); border: 1px solid var(--card-border, #e5e5e5); border-radius: var(--card-radius, 8px); padding: 1.25rem; }
.shop-cart-totals > div { display: flex; justify-content: space-between; margin-bottom: 0.5rem; }
.shop-cart-totals__grand { font-weight: 700; font-size: 1.15rem; border-top: 1px solid var(--card-border, #e5e5e5); padding-top: 0.6rem; }
.shop-cart-totals__note { font-size: 0.82rem; color: var(--card-meta, #888); }
.shop-cart-totals .btn { width: 100%; margin-top: 0.5rem; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
@media (max-width: 700px) {
    /* sr-only positioning, NOT display:none: hiding the thead removes the header
       cells from the accessibility tree, and `td { display: block }` already
       strips the implicit table semantics — which is why the templates carry
       explicit role="table"/"row"/"columnheader"/"cell" attributes (immune to
       CSS display) and each td a data-label. WCAG 1.3.1. */
    .shop-cart-table thead { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
    .shop-cart-table td { display: block; border: none; padding: 0.25rem 0; }
    .shop-cart-table tr { display: block; border-bottom: 1px solid var(--card-border, #e5e5e5); padding: 0.75rem 0; }
    /* Visible label above each stacked value, from the cell's own data-label —
       the sighted-mobile half of the same fix (an unlabelled stack of price /
       qty / total was guesswork for everyone). The `/ ""` form hides the label
       from screen readers (they already get the real header cells); the plain
       form is the fallback for engines without alt-text content support, where
       a doubled label beats a missing one. */
    .shop-cart-table td[data-label]::before { content: attr(data-label); content: attr(data-label) / ""; display: block; font-size: 0.72rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--card-meta, #777); }
}

/* ── Shop checkout (SHOP P4b) ── */
.shop-checkout-layout { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); gap: 2.5rem; align-items: start; }
.shop-checkout-form h2 { font-size: 1.15rem; margin: 1.75rem 0 0.75rem; }
.shop-checkout-form h2:first-child { margin-top: 0; }
.shop-checkout-note { font-size: 0.85rem; color: var(--card-meta, #777); margin: 0.35rem 0 0.75rem; }
.shop-checkout-errors { margin-bottom: 1.5rem; }
.form-group--checkbox label { display: flex; gap: 0.5rem; align-items: flex-start; font-weight: 400; }
.form-group--checkbox input { margin-top: 0.25rem; }
.shop-billing-details { margin: 0.5rem 0 1rem; }
.shop-billing-details summary { cursor: pointer; font-weight: 600; margin-bottom: 0.5rem; }
.shop-payment-method { border: 1px solid var(--card-border, #ddd); border-radius: 8px; padding: 0.9rem 1rem; margin-bottom: 1rem; }
.shop-age-gate { background: rgba(240, 173, 78, 0.12); border: 1px solid rgba(240, 173, 78, 0.5); border-radius: 8px; padding: 0.9rem 1rem; }
.shop-place-order { width: 100%; margin-top: 1rem; }
.shop-checkout-summary { background: var(--card-bg, #fafafa); border: 1px solid var(--card-border, #e5e5e5); border-radius: var(--card-radius, 8px); padding: 1.25rem; }
.shop-checkout-summary h2 { margin-top: 0; font-size: 1.1rem; }
.shop-summary-lines { list-style: none; margin: 0 0 1rem; padding: 0; }
.shop-summary-lines li { display: flex; justify-content: space-between; gap: 1rem; padding: 0.3rem 0; border-bottom: 1px solid var(--card-border, #eee); font-size: 0.92rem; }
.shop-checkout-summary .shop-cart-totals { max-width: none; margin: 0; padding: 0; border: none; background: transparent; }
@media (max-width: 900px) {
    .shop-checkout-layout { grid-template-columns: 1fr; }
    .shop-checkout-summary { order: -1; }
}

/* ── Shop coupons (SHOP P6B) ── */
.shop-cart-coupon { margin: 0 0 1rem; }
.shop-cart-coupon form { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.shop-cart-coupon input[type="text"] { padding: 0.5rem 0.75rem; border: 1px solid var(--card-border, #ccc); border-radius: 6px; max-width: 220px; }

/* ── Quantity discounts (SHOP P6F) ── */
.shop-tier-list { list-style: none; margin: 0 0 1rem; padding: 0.5rem 0.9rem; background: var(--card-bg, #fafafa); border: 1px dashed var(--card-border, #ddd); border-radius: 8px; font-size: 0.9rem; }
.shop-tier-list li { padding: 0.15rem 0; }

/* ── Shop quick wins: quick add, related strip, checkout policy links ── */
.shop-product-card__actions { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; }
.shop-quick-add { white-space: nowrap; }
.shop-related { margin-top: 2.5rem; }
.shop-related h2 { font-size: 1.3rem; margin-bottom: 1rem; }
.shop-checkout-policies { margin-top: 0.75rem; font-size: 0.85rem; text-align: center; color: var(--card-meta, #777); }

/* ── Shop customer accounts (SHOP P4A) ── */
.shop-account-narrow { max-width: 480px; }
.shop-account-form .form-group { margin-bottom: 1rem; }
.shop-account-form label { display: block; font-weight: 600; margin-bottom: 0.3rem; }
.shop-account-form input { width: 100%; padding: 0.55rem 0.75rem; border: 1px solid var(--card-border, #ccc); border-radius: 6px; }
.shop-account-hint { font-weight: 400; font-size: 0.82rem; color: var(--card-meta, #777); }
.shop-account-links { margin-top: 1.25rem; font-size: 0.9rem; }
.shop-account-flash { background: var(--card-bg, #eef7ea); border: 1px solid var(--card-border, #cfe6c6); border-radius: 8px; padding: 0.75rem 1rem; }
.shop-account-bar { display: flex; justify-content: space-between; align-items: center; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.25rem; font-size: 0.9rem; color: var(--card-meta, #777); }
.shop-account-shipment { background: var(--card-bg, #fafafa); border: 1px solid var(--card-border, #e5e5e5); border-radius: 8px; padding: 0.7rem 1rem; margin-bottom: 0.75rem; font-size: 0.92rem; }
/* Spacing the account and confirmation pages used to carry inline. */
.shop-account-section { margin-top: 2rem; }
.shop-account-subhead { margin-top: 2.5rem; }
.shop-return-reason { margin-top: 1.25rem; }
.shop-item-list { margin: 0.4rem 0 0; padding-left: 1.1rem; }
.shop-cart-totals.shop-cart-totals--confirmation { margin-left: 0; margin-top: 1.5rem; }
.shop-account-shipment.shop-account-shipment--spaced,
.shop-account-flash.shop-account-flash--spaced { margin-top: 1.5rem; }

/* ── Shop account self-serve (v14.6): sub-nav, address book, order actions ── */
.shop-account-nav { display: flex; gap: 1.25rem; flex-wrap: wrap; }
.shop-account-nav a { text-decoration: none; font-weight: 600; color: var(--link, #2a6e2a); padding-bottom: 0.2rem; border-bottom: 2px solid transparent; }
.shop-account-nav a.is-active { border-bottom-color: currentColor; }
.shop-account-check { font-weight: 400 !important; display: flex; align-items: center; gap: 0.5rem; }
.shop-account-check input[type="checkbox"] { width: auto !important; }
.shop-order-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; margin: 0.75rem 0 1.5rem; }
.shop-address-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 1rem; margin: 1rem 0; }
.shop-address-card { border: 1px solid var(--card-border, #e5e5e5); border-radius: 8px; padding: 1rem; display: flex; flex-direction: column; justify-content: space-between; gap: 0.75rem; background: var(--card-bg, #fff); }
.shop-address-card__body { font-size: 0.92rem; line-height: 1.45; }
.shop-address-card__actions { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: flex-start; }
.shop-address-card__actions form { margin: 0; }
.shop-address-default { display: inline-block; background: var(--card-accent, #8cc14d); color: #fff; font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; padding: 0.1rem 0.45rem; border-radius: 4px; margin-bottom: 0.4rem; }
.shop-address-card__actions details { width: 100%; }
.shop-address-card__actions summary { display: inline-block; list-style: none; cursor: pointer; }
.shop-address-card__actions summary::-webkit-details-marker { display: none; }
.shop-address-form { margin-top: 0.75rem; }
.btn-small { padding: 0.3rem 0.6rem !important; font-size: 0.82rem !important; }

/* ── Shop ops essentials (v14.1): holiday banner, free-delivery nudge, dispatch time ── */
.shop-holiday-banner { background: rgba(240, 173, 78, 0.14); border: 1px solid rgba(240, 173, 78, 0.55); border-radius: 8px; padding: 0.8rem 1.1rem; margin-bottom: 1.25rem; font-size: 0.95rem; }
.shop-free-delivery-nudge { background: var(--card-bg, #eef7ea); border: 1px dashed var(--card-border, #8cc14d); border-radius: 8px; padding: 0.6rem 0.9rem; font-size: 0.92rem; max-width: 380px; margin-left: auto; }
.shop-dispatch-time { font-size: 0.88rem; color: var(--card-meta, #777); margin-top: 0.5rem; }

/* ── Shop growth batch (v14.2): back-in-stock, cross-sell strips ── */
.shop-stock-alert { margin-top: 1rem; padding: 0.9rem 1rem; background: var(--card-bg, #fafafa); border: 1px solid var(--card-border, #e5e5e5); border-radius: 8px; max-width: 420px; }
.shop-stock-alert__row { display: flex; gap: 0.5rem; margin-top: 0.5rem; flex-wrap: wrap; }
.shop-stock-alert__row input { flex: 1; min-width: 180px; padding: 0.5rem 0.75rem; border: 1px solid var(--card-border, #ccc); border-radius: 6px; }
.shop-stock-alert__feedback { display: block; margin-top: 0.4rem; font-size: 0.85rem; color: var(--card-meta, #777); }
.shop-cross-sell { margin-top: 2rem; }
.shop-cross-sell h2 { font-size: 1.2rem; margin-bottom: 0.9rem; }
.shop-recent-strip { display: flex; gap: 1rem; flex-wrap: wrap; }
.shop-recent-item { display: flex; flex-direction: column; gap: 0.35rem; width: 130px; text-decoration: none; font-size: 0.85rem; }
.shop-recent-item img { width: 130px; height: 130px; object-fit: cover; border-radius: 8px; border: 1px solid var(--card-border, #e5e5e5); }

/* ── Shop search + facets (SHOP-SEARCH, v14.3) ── */
.shop-filter-bar { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; margin-bottom: 1.5rem; padding: 0.9rem 1rem; background: var(--card-bg, #fafafa); border: 1px solid var(--card-border, #e5e5e5); border-radius: 8px; }
.shop-filter-bar input[type="search"] { flex: 2; min-width: 170px; }
.shop-filter-bar input, .shop-filter-bar select { padding: 0.45rem 0.6rem; border: 1px solid var(--card-border, #ccc); border-radius: 6px; }
.shop-filter-bar input[type="number"] { width: 90px; }
.shop-filter-bar__stock { display: flex; gap: 0.35rem; align-items: center; font-size: 0.9rem; white-space: nowrap; }

/* ── Native reviews (SHOP-REVIEWS, v14.3) ── */
.shop-reviews { margin-top: 2.5rem; }
.shop-reviews h2 { font-size: 1.3rem; margin-bottom: 1rem; }
.shop-review { border-bottom: 1px solid var(--card-border, #eee); padding: 0.9rem 0; }
.shop-review__head { display: flex; gap: 0.6rem; align-items: baseline; flex-wrap: wrap; font-size: 0.92rem; }
.shop-review__stars { color: #e8a33d; letter-spacing: 2px; }
.shop-review__verified { color: #15803d; font-size: 0.82rem; font-weight: 700; }
.shop-review__date { color: var(--card-meta, #999); font-size: 0.82rem; }
.shop-review__title { font-weight: 700; margin: 0.35rem 0 0.15rem; }
.shop-review__body { margin: 0.25rem 0 0; }
.shop-review-form-wrap { margin-top: 1.25rem; }
.shop-review-form-wrap summary { display: inline-block; cursor: pointer; list-style: none; }
.shop-review-form { margin-top: 1rem; max-width: 480px; }
.shop-review-form .form-group { margin-bottom: 0.9rem; }
.shop-review-form label { display: block; font-weight: 600; margin-bottom: 0.3rem; }
.shop-review-form input, .shop-review-form select, .shop-review-form textarea { width: 100%; padding: 0.5rem 0.7rem; border: 1px solid var(--card-border, #ccc); border-radius: 6px; }
.shop-review-feedback { display: block; margin-top: 0.5rem; font-size: 0.88rem; color: var(--card-meta, #777); }

/* ── Focus visibility + form field naming (A11Y Phase 2: G1 + G9) ──
   2.4.7: a keyboard user must always be able to see where focus is. Form
   fields set outline:none for a 10%-alpha shadow (invisible on checkboxes
   and radios), and nothing else declared a ring beyond the browser default. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible {
    outline: 3px solid var(--color-primary, #f04e23);
    outline-offset: 2px;
}
/* These outrank the earlier .form-group :focus rules (which set outline:none)
   only by coming LATER at equal specificity — this block must stay below them
   in the file, or checkbox/radio keyboard focus goes invisible again. */
.form-group input:focus-visible,
.form-group textarea:focus-visible,
.form-group select:focus-visible {
    outline: 3px solid var(--color-primary, #f04e23);
    outline-offset: 2px;
}

/* Radio groups render as native <fieldset>+<legend> (their accessible name).
   min-width:0 is load-bearing: a fieldset's min-content sizing otherwise blows
   out the .form-row grid the moment an option label is long. */
.form-group.form-fieldset { border: 0; padding: 0; margin: 0 0 1.15rem; min-width: 0; }
.form-group.form-fieldset legend { display: block; width: 100%; padding: 0; margin-bottom: 0.3rem; font-weight: 600; font-size: 0.85rem; color: var(--color-dark); }
/* The checkbox field label when it differs from the checkbox statement — a
   heading, deliberately NOT a <label>, or the control gets two names. */
.form-group .form-label-heading { display: block; font-weight: 600; font-size: 0.85rem; margin-bottom: 0.3rem; color: var(--color-dark); }

/* ── Site search (SITE-SEARCH) ─────────────────── */
/* The header box lives in the nav bar as a flex item; margin-left:auto packs it
   against the right edge on desktop. Token-driven so themed installs restyle it
   from Theme Studio's nav colours without touching CSS. */
.nav-search { margin-left: auto; display: flex; align-items: center; padding: 0.35rem 0.75rem; }
.site-search-form { display: flex; align-items: stretch; gap: 0.4rem; margin: 0.5rem 0 1.5rem; }
.site-search-form input[type="search"] {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--color-border, #ccc);
    border-radius: var(--radius-sm, 4px);
    font-size: 0.95rem;
    font-family: inherit;
}
.site-search-form__submit {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 0.9rem;
    background: var(--color-primary-text, var(--color-primary));
    color: #fff;
    border: none;
    border-radius: var(--radius-sm, 4px);
    font-size: 0.95rem;
    font-family: inherit;
    cursor: pointer;
}
/* Mirrors .btn-primary, and the token names are load-bearing. --btn-bg,
   --btn-text and --btn-hover-bg are emitted by nothing, and --color-primary-dark
   is not a token either (the real one is --color-primary-hover), so a chain
   built on them resolves hover and resting to the same colour and the button
   has no hover state at all. */
.site-search-form__submit:hover { background: var(--color-primary-hover); }
/* Compact header variant: smaller controls, no page-form margins. */
.site-search-form--compact { margin: 0; }
.site-search-form--compact input[type="search"] { width: 11rem; flex: 0 1 auto; padding: 0.35rem 0.6rem; font-size: 0.85rem; border-color: transparent; }
.site-search-form--compact .site-search-form__submit { padding: 0.35rem 0.6rem; }

/* Results list */
/* Every colour here keeps its original literal as the innermost fallback, but
   the two kinds of token behave differently and the difference is load-bearing.
   --card-meta and --card-text are only ever DEFINED by an injected theme, so
   their literal is what an un-themed install still renders and nothing moves.
   --color-border and --color-bg-alt are declared in this file's own :root, so
   their fallback can never fire and the un-themed value shifts to the token.
   Keep the literals either way: they are the floor if a site's own stylesheet
   resets :root. */
/* The summary is a heading for navigation, not for size — reset it to read
   as the supporting line it is. */
.search-summary { color: var(--card-meta, #555); margin: 0 0 1.5rem; font-size: 1rem; font-weight: 400; line-height: 1.5; }
.search-result { padding: 1rem 0; border-bottom: 1px solid var(--color-border, #e5e5e5); }
.search-result:last-child { border-bottom: none; }
.search-result__type {
    display: inline-block;
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--card-meta, #666);
    background: var(--color-bg-alt, #f2f2f2);
    border-radius: 3px;
    padding: 0.15rem 0.5rem;
    margin-bottom: 0.3rem;
}
.search-result__title { margin: 0 0 0.3rem; font-size: 1.15rem; line-height: 1.3; }
/* --color-link, matching the global `a` rule. The token name is load-bearing:
   there is no --link-color in the catalogue, so that spelling is never emitted
   and the fallback fires every time, pinning these titles to the brand colour
   while every other link on the site follows the theme's link colour. */
.search-result__title a { color: var(--color-link, var(--color-primary-text)); text-decoration: none; }
.search-result__title a:hover { text-decoration: underline; }
.search-result__excerpt { margin: 0; color: var(--card-text, #444); font-size: 0.95rem; line-height: 1.6; }
.search-result__excerpt mark { background: color-mix(in srgb, var(--color-primary, #f04e23) 18%, transparent); color: inherit; padding: 0 0.1em; border-radius: 2px; }

@media (max-width: 768px) {
    /* Inside the collapsed mobile menu the box becomes a full-width row. */
    .nav-search { margin-left: 0; padding: 0.5rem 1.15rem; }
    .nav-search .site-search-form { width: 100%; }
    .nav-search .site-search-form input[type="search"] { flex: 1 1 auto; width: auto; }
}

/* == Reduced motion (WCAG 2.3.3) ================================
   Site-wide: visitors who ask their OS for less motion get instant state
   changes instead of animation - hover lifts/zooms snap, smooth scrolling
   (html { scroll-behavior } above, plus the TOC and back-to-top scripts)
   becomes a jump. Near-zero duration rather than `none` so any code waiting
   on animationend/transitionend still fires. Surfaces with their own richer
   handling keep it: the announcement marquee unwraps to static text (its own
   block above) and the slideshow/video-banner start paused (script-driven,
   in slideshow-hero.blade.php). Declared last so it wins every equal-
   specificity transition rule earlier in the file. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
    html { scroll-behavior: auto !important; }
}

/* ── Back to top ───────────────────────────────────────────────────────────
   Off by default site-wide (Global Settings), always on for the block
   catalogue. Hidden until the visitor is a screen down — the partial toggles
   the `hidden` attribute rather than a class, so with no JavaScript it never
   appears at all rather than appearing and doing nothing.

   56px, comfortably past the 24px target minimum and past the 44px most touch
   guidance asks for. */
.back-to-top {
    position: fixed;
    right: 1.5rem;
    bottom: 1.5rem;
    z-index: 60;
    width: 56px;
    height: 56px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: var(--color-primary);
    color: #fff;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25);
    transition: background 0.2s, transform 0.2s;
}

/* The mark is an SVG rather than a character because flex centres a glyph's line
   box, not its ink, and a caret is drawn near cap height — so a typed one sits
   visibly high. `display: block` drops the inline baseline gap under it. */
.back-to-top svg {
    display: block;
    width: 22px;
    height: 22px;
}

.back-to-top:hover,
.back-to-top:focus-visible {
    background: var(--color-primary-hover, var(--color-primary));
    transform: translateY(-2px);
}

/* Its own ring: the button sits on whatever the page happens to scroll past, so
   it cannot rely on contrast with a known background. */
.back-to-top:focus-visible {
    outline: 3px solid var(--color-primary);
    outline-offset: 3px;
}

/* `hidden` is an attribute, and `display: flex` above would otherwise beat the
   browser's default `[hidden] { display: none }`. */
.back-to-top[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
    .back-to-top { transition: none; }
    .back-to-top:hover,
    .back-to-top:focus-visible { transform: none; }
}

@media (max-width: 600px) {
    .back-to-top { right: 1rem; bottom: 1rem; width: 48px; height: 48px; }
}
