/* BPS Lite — token utility classes
 * ===========================================================================
 * A small, flat vocabulary for picking a COLOUR or a FONT from the palette,
 * applied as a class in the markup. Every rule reads a CSS custom property the
 * Palette tab authors, so the value travels with the palette and the intent
 * travels with the markup. Nothing here depends on Elementor's global colours,
 * which live in Elementor's own kit and do NOT survive a template export.
 *
 * 🔴 PORTED FROM BASE, DELIBERATELY BYTE-FOR-BYTE ON THE NAMES.
 * `moa-bps-base/assets/scss/bps-framework.scss:706-738` already ships
 * .bps-bg-accent{,-2,-3}, .bps-text-accent{,-2,-3} and .bps-border-accent{,-2,-3}.
 * Fifteen token names are identical across the two plugins (--accent, --accent-2,
 * --accent-3, --accent-text*, --text-color*, --text-highlight*, --bg, --bg2,
 * --bg3, --border-color), so a page built with these classes on Lite paints the
 * same way after an upgrade to Base. Do not rename them on one side only.
 *
 * 🔴 A FILL CLASS MUST RE-DECLARE THE INK TOKENS, not just `color`.
 * Learned in Base 2026-08-24, and it is the whole reason the .bps-bg-* rules are
 * this long. Setting `color` alone only reaches text that INHERITS it. Anything
 * painted from a token keeps the surrounding tone band's ink, because an accent
 * band is not a tone band. Loudest case: every skin paints .section-title with
 * var(--text-highlight) at (0,3,0), so a heading on an accent fill took the light
 * band's dark ink and sat on a dark accent — measured 1.10:1 on 26 of 30 sites,
 * invisible, with nothing erroring. Any class that changes the painted GROUND
 * re-declares the ink for that ground, exactly as .light / .dark do.
 *
 * 🪤 THE FALLBACKS ARE LOAD-BEARING — this is the one place Lite improves on Base.
 * --accent / --accent-2 / --accent-3 are context-scoped to .light|.light2|.light3
 * and .dark|.dark2|.dark3. They are NOT declared at :root. So on a hand-built
 * container with no tone and no `.section` — which is exactly where you reach for
 * a utility first — a bare var(--accent) resolves to nothing and paints nothing.
 * Every rule below falls back to the light ladder, which IS declared at :root by
 * all three skins and by the palette override block.
 * ======================================================================== */

/* --- Fills. Ground + the ink that belongs on it. ------------------------- */

.bps-bg-accent {
  background-color: var(--accent, var(--light-accent));
  color:            var(--accent-text, var(--light-accent-text));
  --text-color:      var(--accent-text, var(--light-accent-text));
  --text-color2:     var(--accent-text, var(--light-accent-text));
  --text-color3:     var(--accent-text, var(--light-accent-text));
  --text-highlight:  var(--accent-text, var(--light-accent-text));
  --text-highlight2: var(--accent-text, var(--light-accent-text));
}

.bps-bg-accent-2 {
  background-color: var(--accent-2, var(--light-accent-2));
  color:            var(--accent-text-2, var(--light-accent-text-2));
  --text-color:      var(--accent-text-2, var(--light-accent-text-2));
  --text-color2:     var(--accent-text-2, var(--light-accent-text-2));
  --text-color3:     var(--accent-text-2, var(--light-accent-text-2));
  --text-highlight:  var(--accent-text-2, var(--light-accent-text-2));
  --text-highlight2: var(--accent-text-2, var(--light-accent-text-2));
}

.bps-bg-accent-3 {
  background-color: var(--accent-3, var(--light-accent-3));
  color:            var(--accent-text-3, var(--light-accent-text-3));
  --text-color:      var(--accent-text-3, var(--light-accent-text-3));
  --text-color2:     var(--accent-text-3, var(--light-accent-text-3));
  --text-color3:     var(--accent-text-3, var(--light-accent-text-3));
  --text-highlight:  var(--accent-text-3, var(--light-accent-text-3));
  --text-highlight2: var(--accent-text-3, var(--light-accent-text-3));
}

/* --- Text colour -------------------------------------------------------- */

.bps-text-accent   { color: var(--accent,   var(--light-accent)); }
.bps-text-accent-2 { color: var(--accent-2, var(--light-accent-2)); }
.bps-text-accent-3 { color: var(--accent-3, var(--light-accent-3)); }

/* The three ink roles, named for the job rather than the token. Lite-only for
 * now: Base carries the same token names, so these port whenever Base wants them. */
.bps-text-heading { color: var(--text-highlight, var(--light-text-highlight)); }
.bps-text-body    { color: var(--text-color,     var(--light-text)); }
.bps-text-soft    { color: var(--text-color2,    var(--light-text2)); }

/* --- Border colour ------------------------------------------------------ */

.bps-border-accent   { border-color: var(--accent,   var(--light-accent)); }
.bps-border-accent-2 { border-color: var(--accent-2, var(--light-accent-2)); }
.bps-border-accent-3 { border-color: var(--accent-3, var(--light-accent-3)); }

/* --- Font family -------------------------------------------------------- */
/* 🪤 LITE-ONLY, and the one part of this file that does NOT port yet.
 * Lite carries --font-heading / --font-body as family tokens. Base carries no
 * family token at all — its --font-* names are SIZES (--font-base, --font-lg,
 * --font-xl …). Adding the two family tokens to Base is what makes these port;
 * until then a page using them upgrades with the classes inert, not broken. */

.bps-font-heading { font-family: var(--font-heading); }
.bps-font-body    { font-family: var(--font-body); }

/* --- Buttons ------------------------------------------------------------ */
/* 🔴 A FILL CLASS ON A BUTTON WIDGET LANDS ON THE WRAPPER, NOT THE BUTTON.
 * Elementor writes _css_classes onto `.elementor-element`, so `.bps-bg-accent`
 * on a button paints a block the full width of the widget with a button-sized
 * label floating in it. Measured 2026-09-12 at 412px: wrapper 372px, button
 * 174px — a full-bleed bar on a phone. Move the fill to the button itself and
 * keep the wrapper transparent.
 *
 * 🔴 AND COVER :hover, :focus, :active. Astra ships
 *     .elementor-widget-button .elementor-button:hover{background-color:var(--ast-global-color-1)}
 * and on a BPS Lite site that variable is NEVER DEFINED, so it computes to
 * transparent and the button DISAPPEARS on hover with white text on white.
 * A base-state rule alone does not save you; Astra's hover rule still wins.
 *
 * Ruled by Mark 2026-09-12: a fill class does NOT change colour on hover. The
 * default is to hold. Opt in to a colour change with .bps-hover-accent* below,
 * so a hover is always something someone chose. */

.elementor-widget-button[class*="bps-bg-"] { background-color: transparent; }

.elementor-widget-button.bps-bg-accent   .elementor-button,
.elementor-widget-button.bps-bg-accent   .elementor-button:hover,
.elementor-widget-button.bps-bg-accent   .elementor-button:focus,
.elementor-widget-button.bps-bg-accent   .elementor-button:active {
  background-color: var(--accent,        var(--light-accent));
  color:            var(--accent-text,   var(--light-accent-text));
}
.elementor-widget-button.bps-bg-accent-2 .elementor-button,
.elementor-widget-button.bps-bg-accent-2 .elementor-button:hover,
.elementor-widget-button.bps-bg-accent-2 .elementor-button:focus,
.elementor-widget-button.bps-bg-accent-2 .elementor-button:active {
  background-color: var(--accent-2,      var(--light-accent-2));
  color:            var(--accent-text-2, var(--light-accent-text-2));
}
.elementor-widget-button.bps-bg-accent-3 .elementor-button,
.elementor-widget-button.bps-bg-accent-3 .elementor-button:hover,
.elementor-widget-button.bps-bg-accent-3 .elementor-button:focus,
.elementor-widget-button.bps-bg-accent-3 .elementor-button:active {
  background-color: var(--accent-3,      var(--light-accent-3));
  color:            var(--accent-text-3, var(--light-accent-text-3));
}

/* Opt-in hover. Pair with a fill class: `bps-bg-accent bps-hover-accent-2`.
 * Listed after the holds above so it wins on source order at equal specificity. */
.elementor-widget-button.bps-hover-accent   .elementor-button:hover,
.elementor-widget-button.bps-hover-accent   .elementor-button:focus {
  background-color: var(--accent,        var(--light-accent));
  color:            var(--accent-text,   var(--light-accent-text));
}
.elementor-widget-button.bps-hover-accent-2 .elementor-button:hover,
.elementor-widget-button.bps-hover-accent-2 .elementor-button:focus {
  background-color: var(--accent-2,      var(--light-accent-2));
  color:            var(--accent-text-2, var(--light-accent-text-2));
}
.elementor-widget-button.bps-hover-accent-3 .elementor-button:hover,
.elementor-widget-button.bps-hover-accent-3 .elementor-button:focus {
  background-color: var(--accent-3,      var(--light-accent-3));
  color:            var(--accent-text-3, var(--light-accent-text-3));
}


/* ============================================================================
   TYPE SCALE — .bps-size-1 … -6                                    (0.6.3)
   ============================================================================
   Why this exists. 0.5.8 retired bps-text-xs/-sm/-lg because each named a
   MEASUREMENT that no skin could re-cut, and across 2,023 published documents
   they were used once between them. Retiring them was right; what it left
   behind was not. Measured 2026-09-12: between .hero-subtitle (1.25rem) and
   .section-title (2.5rem) there was NOTHING an author could reach — every rung
   in that band is a role welded to a job (hero, stat, section head) or a
   tutorial class that is not offered in the picker. For ordinary copy the
   choice was down to two, so the largest general size sat 1.25x above body
   text. A scale that flat gives the eye no hierarchy to read, and a page with
   no size hierarchy reads as badly ALIGNED even when every edge is true —
   contrast failing takes alignment down with it. (Mark, CRAP, 2026-09-12.)

   Why this one is not the old one back again. The class names a STEP; the skin
   owns what a step is worth, via --step-1..6 in its own :root. So modern-flat
   runs the scale tighter (1.0625–3rem) than warm-organic (1.125–4rem), exactly
   as it already cuts .section-title tighter. A class that names a step can be
   re-cut; a class called bps-text-lg never could.

   Fluid by construction: clamp() between a proportional floor and the skin's
   step, so a display size cannot blow out a 390px screen.

   🪤 SELECTOR DEPTH IS LOAD-BEARING. Elementor's per-element typography lands
   at (0,3,0). A bare .bps-size-3 is (0,1,0) and loses — that is precisely how
   bps-text-lg came to be applied in the DOM and paint nothing. The body skin
   class takes these to (0,3,1), the same depth .lede uses to win. Do not
   "simplify" these selectors.

   🪤 rem, never em. em compounds against the parent, so a step inside a step
   multiplies. Ruled 2026-09-12.
   ========================================================================= */

body[class*="bps-lite-skin-"] .bps-size-1 .elementor-heading-title,
body[class*="bps-lite-skin-"] .bps-size-1 .elementor-widget-text-editor p,
body[class*="bps-lite-skin-"] .bps-size-1 > p,
body[class*="bps-lite-skin-"] .bps-size-1 > h1, body[class*="bps-lite-skin-"] .bps-size-1 > h2,
body[class*="bps-lite-skin-"] .bps-size-1 > h3, body[class*="bps-lite-skin-"] .bps-size-1 > h4,
body[class*="bps-lite-skin-"] .bps-size-1 > h5, body[class*="bps-lite-skin-"] .bps-size-1 > h6,
body[class*="bps-lite-skin-"] p.bps-size-1,
body[class*="bps-lite-skin-"] h1.bps-size-1, body[class*="bps-lite-skin-"] h2.bps-size-1,
body[class*="bps-lite-skin-"] h3.bps-size-1, body[class*="bps-lite-skin-"] h4.bps-size-1,
body[class*="bps-lite-skin-"] h5.bps-size-1, body[class*="bps-lite-skin-"] h6.bps-size-1 {
  font-size: clamp(calc(var(--step-1) * 0.94), 1.5vw, var(--step-1));
  line-height: 1.55;
}

body[class*="bps-lite-skin-"] .bps-size-2 .elementor-heading-title,
body[class*="bps-lite-skin-"] .bps-size-2 .elementor-widget-text-editor p,
body[class*="bps-lite-skin-"] .bps-size-2 > p,
body[class*="bps-lite-skin-"] .bps-size-2 > h1, body[class*="bps-lite-skin-"] .bps-size-2 > h2,
body[class*="bps-lite-skin-"] .bps-size-2 > h3, body[class*="bps-lite-skin-"] .bps-size-2 > h4,
body[class*="bps-lite-skin-"] .bps-size-2 > h5, body[class*="bps-lite-skin-"] .bps-size-2 > h6,
body[class*="bps-lite-skin-"] p.bps-size-2,
body[class*="bps-lite-skin-"] h1.bps-size-2, body[class*="bps-lite-skin-"] h2.bps-size-2,
body[class*="bps-lite-skin-"] h3.bps-size-2, body[class*="bps-lite-skin-"] h4.bps-size-2,
body[class*="bps-lite-skin-"] h5.bps-size-2, body[class*="bps-lite-skin-"] h6.bps-size-2 {
  font-size: clamp(calc(var(--step-2) * 0.88), 1.8vw, var(--step-2));
  line-height: 1.45;
}

body[class*="bps-lite-skin-"] .bps-size-3 .elementor-heading-title,
body[class*="bps-lite-skin-"] .bps-size-3 .elementor-widget-text-editor p,
body[class*="bps-lite-skin-"] .bps-size-3 > p,
body[class*="bps-lite-skin-"] .bps-size-3 > h1, body[class*="bps-lite-skin-"] .bps-size-3 > h2,
body[class*="bps-lite-skin-"] .bps-size-3 > h3, body[class*="bps-lite-skin-"] .bps-size-3 > h4,
body[class*="bps-lite-skin-"] .bps-size-3 > h5, body[class*="bps-lite-skin-"] .bps-size-3 > h6,
body[class*="bps-lite-skin-"] p.bps-size-3,
body[class*="bps-lite-skin-"] h1.bps-size-3, body[class*="bps-lite-skin-"] h2.bps-size-3,
body[class*="bps-lite-skin-"] h3.bps-size-3, body[class*="bps-lite-skin-"] h4.bps-size-3,
body[class*="bps-lite-skin-"] h5.bps-size-3, body[class*="bps-lite-skin-"] h6.bps-size-3 {
  font-size: clamp(calc(var(--step-3) * 0.82), 2.6vw, var(--step-3));
  line-height: 1.3;
}

body[class*="bps-lite-skin-"] .bps-size-4 .elementor-heading-title,
body[class*="bps-lite-skin-"] .bps-size-4 .elementor-widget-text-editor p,
body[class*="bps-lite-skin-"] .bps-size-4 > p,
body[class*="bps-lite-skin-"] .bps-size-4 > h1, body[class*="bps-lite-skin-"] .bps-size-4 > h2,
body[class*="bps-lite-skin-"] .bps-size-4 > h3, body[class*="bps-lite-skin-"] .bps-size-4 > h4,
body[class*="bps-lite-skin-"] .bps-size-4 > h5, body[class*="bps-lite-skin-"] .bps-size-4 > h6,
body[class*="bps-lite-skin-"] p.bps-size-4,
body[class*="bps-lite-skin-"] h1.bps-size-4, body[class*="bps-lite-skin-"] h2.bps-size-4,
body[class*="bps-lite-skin-"] h3.bps-size-4, body[class*="bps-lite-skin-"] h4.bps-size-4,
body[class*="bps-lite-skin-"] h5.bps-size-4, body[class*="bps-lite-skin-"] h6.bps-size-4 {
  font-size: clamp(calc(var(--step-4) * 0.76), 3.4vw, var(--step-4));
  line-height: 1.2;
}

body[class*="bps-lite-skin-"] .bps-size-5 .elementor-heading-title,
body[class*="bps-lite-skin-"] .bps-size-5 .elementor-widget-text-editor p,
body[class*="bps-lite-skin-"] .bps-size-5 > p,
body[class*="bps-lite-skin-"] .bps-size-5 > h1, body[class*="bps-lite-skin-"] .bps-size-5 > h2,
body[class*="bps-lite-skin-"] .bps-size-5 > h3, body[class*="bps-lite-skin-"] .bps-size-5 > h4,
body[class*="bps-lite-skin-"] .bps-size-5 > h5, body[class*="bps-lite-skin-"] .bps-size-5 > h6,
body[class*="bps-lite-skin-"] p.bps-size-5,
body[class*="bps-lite-skin-"] h1.bps-size-5, body[class*="bps-lite-skin-"] h2.bps-size-5,
body[class*="bps-lite-skin-"] h3.bps-size-5, body[class*="bps-lite-skin-"] h4.bps-size-5,
body[class*="bps-lite-skin-"] h5.bps-size-5, body[class*="bps-lite-skin-"] h6.bps-size-5 {
  font-size: clamp(calc(var(--step-5) * 0.70), 4.4vw, var(--step-5));
  line-height: 1.12;
}

body[class*="bps-lite-skin-"] .bps-size-6 .elementor-heading-title,
body[class*="bps-lite-skin-"] .bps-size-6 .elementor-widget-text-editor p,
body[class*="bps-lite-skin-"] .bps-size-6 > p,
body[class*="bps-lite-skin-"] .bps-size-6 > h1, body[class*="bps-lite-skin-"] .bps-size-6 > h2,
body[class*="bps-lite-skin-"] .bps-size-6 > h3, body[class*="bps-lite-skin-"] .bps-size-6 > h4,
body[class*="bps-lite-skin-"] .bps-size-6 > h5, body[class*="bps-lite-skin-"] .bps-size-6 > h6,
body[class*="bps-lite-skin-"] p.bps-size-6,
body[class*="bps-lite-skin-"] h1.bps-size-6, body[class*="bps-lite-skin-"] h2.bps-size-6,
body[class*="bps-lite-skin-"] h3.bps-size-6, body[class*="bps-lite-skin-"] h4.bps-size-6,
body[class*="bps-lite-skin-"] h5.bps-size-6, body[class*="bps-lite-skin-"] h6.bps-size-6 {
  font-size: clamp(calc(var(--step-6) * 0.64), 5.4vw, var(--step-6));
  line-height: 1.06;
}


/* A step above 3 is display type; give it the tightening a headline needs
   without asking the author for a second decision. */
body[class*="bps-lite-skin-"] .bps-size-4,
body[class*="bps-lite-skin-"] .bps-size-5,
body[class*="bps-lite-skin-"] .bps-size-6 {
  letter-spacing: -0.015em;
  text-wrap: balance;
}


/* ---------------------------------------------------------------------------
   bps-card-flush — a card whose CONTENT reaches its edges.
   Added 2026-09-14, and it is not a nicety: without it `bps-card` cannot be put
   on a card that holds a photograph.

   `body.bps-lite .bps-card` sets `padding: 1.75rem 1.625rem` at (0,2,1). Elementor
   emits an authored padding as `.elementor-element-<id>` at (0,1,0). So a card
   authored `padding: 0` — which is exactly what the OWED-489 recipe requires so
   the photograph reaches the card's edges — computes 28px 26px instead, and the
   image floats inset with a border of card colour around it.

   Measured 2026-09-14 on b-team-01. The two rules were in direct conflict and
   the hook class won. This resolves it at the same specificity the hook class
   uses, rather than asking every section to fight it with an override.

   Use it whenever the card's first child is an image, a map or a video. The
   inner text box keeps its own bps_size, which is where the padding belongs.
   ------------------------------------------------------------------------ */
body.bps-lite .bps-card.bps-card-flush {
    padding: 0;
    overflow: hidden;
}


/* ---------------------------------------------------------------------------
   bps-card-row — a card that lays its content out HORIZONTALLY.
   Added 2026-09-14, and like bps-card-flush it is a prerequisite rather than a
   nicety.

   `body.bps-lite .bps-card` declares `display:flex; flex-direction:column` at (0,2,1).
   Elementor emits a container's authored direction as
   `.elementor-element-<id>{flex-direction:row}` at (0,1,0). So a card authored as
   a ROW silently renders as a COLUMN: the second cell drops underneath the first
   and the layout is simply wrong, while the settings read correctly.

   Measured 2026-09-14 on b-services-07, whose rows are a wide prose cell beside a
   narrow figure cell. Caught by align.py, which was right to fire — the stacked
   cell's text no longer shared an edge with anything.

   🔴 THIS IS THE THIRD PROPERTY bps-card takes from the element it is put on:
   padding (see bps-card-flush), flex-direction (here), and background/border.
   Anyone retrofitting the hook class onto 505 existing sections must check all
   three, not just look at whether the card still has a surface.
   ------------------------------------------------------------------------ */
body.bps-lite .bps-card.bps-card-row {
    flex-direction: row;
}

@media (max-width: 767px) {
    /* A two-cell card has nowhere to put the second cell on a phone. */
    body.bps-lite .bps-card.bps-card-row {
        flex-direction: column;
    }
}

/* ===========================================================================
   SCRIM — a directional gradient veil over a background photograph.
   Added 2026-09-14. Mark's call: "add new classes to both base and lite".

   WHY THIS EXISTS, when `.bps-gradient-overlay` was already here.
   That one is vertical-only, fixed at transparent -> rgba(0,0,0,0.55), with no
   angle, colour or stop control, and its black belongs to no palette. It cannot
   express a hero whose copy sits on the LEFT and whose subject must stay legible
   on the right. It is left in place for anything already using it; new work uses
   this family.

   WHY A CLASS AND NOT ELEMENTOR'S OWN OVERLAY CONTROL.
   The Elementor route works — measured 2026-09-14, and its colour field passes a
   `color-mix()` through verbatim, so it is NOT restricted to a hardcoded colour.
   But it is seven settings keys per hero, written per site, and it does not port:
   the same mould has to build a BPS Base site (PC-70 section 0, OWED-501), and
   seven Elementor keys are exactly the kind of thing that does not survive that
   trip. One class token does. That, not the colour, is the argument.

   🔴 THE ONE RULE APPLIES HERE TOO: no bare `.bps-scrim { }` anywhere. Every
   rule below is scoped to the anchor body class, so the paint can be overridden
   back off and a flavour has something to beat.

   🪤 A MOULD REACHES THIS THROUGH `css_classes`, NOT `_css_classes`.
   Containers take `css_classes`; widgets take `_css_classes` with the leading
   underscore. A section root is a container. An earlier note in this estate
   recorded "a mould cannot apply a decorator class at all, because _css_classes
   is inert on a container in Elementor 4.2.4" — that finding was correct about
   the key and wrong about the conclusion. `css_classes` on a container works.

   HOW IT COMPOSES
     bps-scrim              the mechanism. Tint follows the band's own --bg.
     bps-scrim-left         copy left, subject right   (linear 100deg)
     bps-scrim-right        copy right, subject left   (linear 260deg)
     bps-scrim-centre       copy centred               (radial from centre)
     bps-scrim-dark | -light | -grey    force the tint instead of the palette

   The bare class takes `var(--bg)`, which is the tone the band already carries —
   so a dark band scrims dark and a light band scrims light without anyone
   choosing. The three tint classes are for when the band's own ground is the
   wrong colour to sit over that particular photograph.

   Tunable per element without touching this file:
     --bps-scrim-alpha   opacity at the copy edge      (default .95)
     --bps-scrim-hold    where the veil starts to fade (default 55%)
     --bps-scrim-fade    where it reaches clear        (default 92%)
   ======================================================================== */

body.bps-lite .bps-scrim {
    position: relative;
    --bps-scrim-color: var(--bg, #101418);
    --bps-scrim-alpha: .95;
    --bps-scrim-hold: 55%;
    --bps-scrim-fade: 92%;
    --bps-scrim-angle: 100deg;
}

body.bps-lite .bps-scrim::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-image: linear-gradient(
        var(--bps-scrim-angle),
        color-mix(in srgb, var(--bps-scrim-color) calc(var(--bps-scrim-alpha) * 100%), transparent) var(--bps-scrim-hold),
        transparent var(--bps-scrim-fade)
    );
}

/* 🔴 The veil is a sibling of the content, so the content has to be lifted above
   it. Without this the copy sits UNDER the scrim and every contrast reading is
   taken against the wrong layer — which still passes, because the numbers are
   computed from a stack that happens to be equivalent. It shows as text that
   looks slightly washed and measures fine. */
body.bps-lite .bps-scrim > * {
    position: relative;
    z-index: 1;
}

body.bps-lite .bps-scrim-left   { --bps-scrim-angle: 100deg; }
body.bps-lite .bps-scrim-right  { --bps-scrim-angle: 260deg; }

/* Centre is radial, NOT a linear veil at 180deg. A two-stop linear that is
   opaque at both ends is a flat scrim, which greys the whole photograph — the
   thing PC-70 section 4 bans. Radial is opaque where the copy sits and lets the
   corners of the frame stay legible. */
body.bps-lite .bps-scrim-centre::after {
    background-image: radial-gradient(
        color-mix(in srgb, var(--bps-scrim-color) calc(var(--bps-scrim-alpha) * 100%), transparent) var(--bps-scrim-hold),
        color-mix(in srgb, var(--bps-scrim-color) 15%, transparent) 100%
    );
}

body.bps-lite .bps-scrim-dark  { --bps-scrim-color: #101418; }
body.bps-lite .bps-scrim-light { --bps-scrim-color: #ffffff; }
body.bps-lite .bps-scrim-grey  { --bps-scrim-color: #5d646b; }

/* A light or grey scrim needs dark copy on top of it; the band's tone cannot know
   that a scrim was added. Opt in with the tone classes as usual — these two only
   soften the veil, because white at .95 over a photograph is a white box. */
body.bps-lite .bps-scrim-light { --bps-scrim-alpha: .88; }
body.bps-lite .bps-scrim-grey  { --bps-scrim-alpha: .82; }

@media (max-width: 768px) {
    /* On a phone the copy fills the width, so a directional veil has nowhere to
       fade to. Square it up rather than leaving half the copy off the scrim. */
    body.bps-lite .bps-scrim::after {
        background-image: linear-gradient(
            180deg,
            color-mix(in srgb, var(--bps-scrim-color) calc(var(--bps-scrim-alpha) * 100%), transparent) 0%,
            color-mix(in srgb, var(--bps-scrim-color) calc(var(--bps-scrim-alpha) * 85%), transparent) 100%
        );
    }
}

/* ============================================================================
   ASPECT RATIO — .bps-aspect-*                                      (0.7.29)
   ============================================================================
   Why this exists. The house pattern for a square image was a hardcoded width
   AND height plus object-fit, because Elementor's own crop tool conflicts with
   Imagify. That works until the image is replaced at a different size, and it
   cannot be re-cut per skin. A ratio class states the SHAPE and lets the image
   fill it, so swapping the source changes nothing.

   🔴 THE NAMES ARE BASE'S, DELIBERATELY. moa-bps-base already ships
   .bps-aspect-video / -square / -portrait (bps-framework.scss:1271) and offers
   them in its `layout` bucket. Renaming them would break every page already
   using them, and Mark's ruling is that Base and Lite must carry the SAME
   classes or we maintain two vocabularies. So this extends that family rather
   than opening a `moa-ratio-*` one beside it.

   🪤 Base's three set `aspect-ratio` and NOTHING else — no object-fit, no
   overflow, nothing reaching the child <img>. That is why the hardcoded
   width/height was still needed. The image rules below are the missing half.

   🪤 SELECTOR DEPTH. Elementor's image widget lands its own width/height at
   (0,2,0). `body[class]` is an attribute selector that is always true on a
   WordPress body, so it buys the extra point unconditionally. Same device the
   type scale above uses. Do not "simplify" it away.
   ------------------------------------------------------------------------- */

.bps-aspect-square   { aspect-ratio: 1 / 1;  }
.bps-aspect-photo    { aspect-ratio: 4 / 3;  }
.bps-aspect-wide     { aspect-ratio: 3 / 2;  }
.bps-aspect-video    { aspect-ratio: 16 / 9; }
.bps-aspect-portrait { aspect-ratio: 3 / 4;  }
.bps-aspect-story    { aspect-ratio: 9 / 16; }

/* A circle is a square that is fully rounded — Mark's ruling 2026-09-16. No
   separate crop step: aspect-ratio squares the box, object-fit fills it without
   distortion, border-radius does the rest. Named in the aspect family rather
   than as `.bps-circle`, which is already a fixed 64px numeral ring in Base. */
.bps-aspect-circle   { aspect-ratio: 1 / 1; border-radius: 50%; }

[class*="bps-aspect-"] { overflow: hidden; }

/* Fill the shape. Covers the class on a container, on the image widget, or on
   the <img> itself. `object-fit: cover` is what stops a non-square photograph
   distorting inside a square box — it is not an extra crop step, it is what
   makes the ratio honest. */
body[class] [class*="bps-aspect-"] img,
body[class] img[class*="bps-aspect-"] {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
body[class] .bps-aspect-circle img { border-radius: 50%; }

/* An Elementor image widget wraps the <img> in a figure/div that would otherwise
   collapse the ratio box. */
[class*="bps-aspect-"] .elementor-widget-container,
[class*="bps-aspect-"] figure,
[class*="bps-aspect-"] .elementor-image { height: 100%; }


/* ============================================================================
   VIEWPORT HEIGHT — .moa-vh-*                                       (0.7.29)
   ============================================================================
   `bps_size` is vertical PADDING ("Size (vertical padding)"), so nothing in the
   vocabulary could ever say "this band is a screenful". Authors set Elementor's
   Min Height per element instead, which cannot be re-cut and cannot express the
   one thing that actually matters on a phone.

   🔴 svh, NOT vh. On iOS and Android `100vh` is measured against the viewport
   with the browser chrome RETRACTED, so a 100vh hero is taller than what you can
   see and its CTA sits below the fold — the opposite of the point. `100svh` is
   the visible height. The plain `vh` line stays as the fallback for engines
   without svh; a later declaration of the same property simply wins where it is
   understood. An Elementor control takes ONE value and so cannot express this
   pair — which is the whole argument for a class.

   🔴 `moa-`, not `bps-`. These paint, and Mark's 2026-09-13 ruling puts painting
   spacing classes in the `moa-` family beside moa-sm/sd/lg/xl/xxl. The aspect
   classes above are the deliberate exception, for Base parity.

   🪤 PRECEDENCE IS DELIBERATE. The class sets `--moa-vh` and the min-height reads
   `var(--min-height, var(--moa-vh))`. Elementor's own Min Height control writes
   `--min-height` on the element, so an explicit author value still wins and the
   class is only the default. Setting `min-height` directly would invert that.
   ------------------------------------------------------------------------- */

.moa-vh-full { --moa-vh: 100vh; }
.moa-vh-3q   { --moa-vh: 75vh;  }
.moa-vh-half { --moa-vh: 50vh;  }
/* Full height MINUS a sticky bar. 15 of the 17 funnel shapes carry sp-nav-01/02/03,
   and under a sticky bar a true 100vh hero loses its top ~70px behind it.
   --nav-h is overridable per site from the Palette tab's head code. */
.moa-vh-nav  { --moa-vh: calc(100vh - var(--nav-h, 72px)); }

@supports (height: 100svh) {
  .moa-vh-full { --moa-vh: 100svh; }
  .moa-vh-3q   { --moa-vh: 75svh;  }
  .moa-vh-half { --moa-vh: 50svh;  }
  .moa-vh-nav  { --moa-vh: calc(100svh - var(--nav-h, 72px)); }
}

[class*="moa-vh-"] { min-height: var(--min-height, var(--moa-vh)); }

/* ---------------------------------------------------------------------------
   MARQUEE — a continuously scrolling strip of items. CSS only.
   Added 0.7.31, 2026-09-17. Proven on `designutions` before it shipped here:
   the mould carried it as site head_code for a day, then this replaced it.

   🔴 THE AUTHOR MUST LIST THE ITEMS TWICE. The loop is `translateX(-50%)`, so
   the strip has to contain exactly two identical halves or it jumps on repeat.
   There is no CSS that can duplicate content, and no JS ships with this — so
   this is a real constraint of the class, not an oversight. Twelve words? List
   all twelve, then list them again.

   🪤 `max-width: none` is load-bearing. On a BOXED container `.e-con-inner`
   carries the theme measure (1140px is typical), and `width: max-content` alone
   is clamped by it — the items are then squeezed until they overlap. Measured on
   `designutions` 2026-09-17; gate.py reported it as five NARROW findings reading
   "text overlaps a sibling", which is exactly what it was.

   Speed is a custom property so a site can retune without touching this file:
     .my-section { --bps-marquee-speed: 45s; }
   ------------------------------------------------------------------------- */

/* 🔴 EACH CLASS STANDS ALONE. A chip option is ONE class — `build/verify-vocabulary.php`
   reads the option key as a single selector, so an option written
   `'bps-marquee bps-marquee-reverse'` is reported as "offered but never painted".
   It caught exactly that here before this shipped. So `-reverse` carries the whole
   rule set itself rather than expecting `.bps-marquee` beside it. */
.bps-marquee,
.bps-marquee-reverse { overflow: hidden; width: 100%; }

.bps-marquee > .e-con-inner,
.bps-marquee.e-con-full,
.bps-marquee-reverse > .e-con-inner,
.bps-marquee-reverse.e-con-full {
  display: flex !important;
  flex-wrap: nowrap !important;
  width: max-content !important;
  max-width: none !important;
  gap: 0 !important;
  animation: bps-marquee var(--bps-marquee-speed, 28s) linear infinite;
}

.bps-marquee-reverse > .e-con-inner,
.bps-marquee-reverse.e-con-full { animation-direction: reverse; }

/* Breathing room between items without fighting the section's own gap token. */
.bps-marquee > .e-con-inner > *,
.bps-marquee.e-con-full > *,
.bps-marquee-reverse > .e-con-inner > *,
.bps-marquee-reverse.e-con-full > * { white-space: nowrap; padding: 0 var(--bps-marquee-gap, 1.6rem); }

@keyframes bps-marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* 🔴 Motion is opt-out for anyone who has asked the OS to reduce it. A marquee is
   decoration; it must never be the only way information is presented. */
@media (prefers-reduced-motion: reduce) {
  .bps-marquee > .e-con-inner,
  .bps-marquee.e-con-full,
  .bps-marquee-reverse > .e-con-inner,
  .bps-marquee-reverse.e-con-full { animation: none; flex-wrap: wrap !important; width: 100% !important; justify-content: center; }
}
