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

/* 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's own #id rule however specific that rule looks. 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 banner inherits from its own #id rule. 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 itself 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 partial that
   somehow publishes nothing still lands on today's colour. */
.static-hero__caption { color: var(--banner-caption, var(--banner-caption-auto, #555)); }
/* 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; }
.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; }

/* ── 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; }
.card-meta { font-size: 0.8rem; color: var(--card-meta, #989898); }

/* ── 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 h2 { margin-bottom: 1.25rem; padding-bottom: 0.5rem; border-bottom: 2px solid var(--color-primary); display: inline-block; }

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

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

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

/* 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; }
.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 "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. College Chambers, Square Club and AI Photo all 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; }
    .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 the Square Club install's site-only
   sc-stack-reverse.) */
@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-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 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; }

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

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

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

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

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