/* =====================================================================
   Home Banner block — public site
   Two modes share this file: the animated slider and the extra news-feed
   layouts (the "1 hero + 3" layout reuses news_hero.css verbatim so the
   established homepage look is untouched).

   The slider half is also loaded by the admin editor (Content → Home
   Banner) so its canvas paints layers with exactly these rules. Keep every
   layer decision here, driven by the custom properties App\Support\
   HomeBannerRender emits — never in the templates.
   ===================================================================== */

/* ---------------- feed mode ---------------------------------------- */
.sb-hbfeed { position: relative; }
.sb-hbfeed__bg { position: absolute; inset: 0; z-index: 0; overflow: hidden; }
.sb-hbfeed__bg img { width: 100%; height: 100%; object-fit: cover; }
.sb-hbfeed__bgtint { position: absolute; inset: 0; background: #0d1b0f; }
.sb-hbfeed__inner { position: relative; z-index: 1; width: 100%; }

/* ---------------------------------------------------------------------
   The grid closes up around however many cards arrived.

   A layout names a shape, but the feed cannot always fill it: a young site,
   a narrow category, or one pinned article standing in for the lot. The
   blade hands over the real counts (--hbf-side, --hbf-cols, --hbf-rows) and
   the tracks follow, so a short feed is a smaller banner rather than a
   banner with holes in it. The fallbacks are the old fixed values, so the
   full-house case is unchanged.
   --------------------------------------------------------------------- */

/* news_hero.css sizes the side column for two cards; the 1-hero-+-3 layout
   carries three, so let the rows share the height evenly. */
.sb-hbfeed--1-3 .sb-newshero__side { grid-template-rows: repeat(var(--hbf-side, 3), minmax(0, 1fr)); }

/* Only the hero came back — it takes the whole width rather than leaving a
   third of the banner as empty space beside it. */
.sb-hbfeed--1-3 .sb-newshero__grid--solo { grid-template-columns: minmax(0, 1fr); }

.sb-hbfeed__grid { display: grid; gap: 14px; height: 584px; }
.sb-hbfeed__grid--hero { grid-template-columns: 1fr; grid-template-rows: 1fr; }
.sb-hbfeed__grid--2x2 {
    grid-template-columns: repeat(var(--hbf-cols, 2), minmax(0, 1fr));
    grid-template-rows: repeat(var(--hbf-rows, 2), minmax(0, 1fr));
}
.sb-hbfeed__grid--row4 {
    grid-template-columns: repeat(var(--hbf-cols, 4), minmax(0, 1fr));
    grid-template-rows: repeat(var(--hbf-rows, 1), minmax(0, 1fr));
    height: 420px;
}

/* Cards inside the alternate layouts stretch to their grid cell. */
.sb-hbfeed__grid .sb-newshero__feature,
.sb-hbfeed__grid .sb-newshero__card { height: 100%; }

@media (max-width: 980px) {
    /* Stacked, the side cards sit in a row of their own — as many columns as
       there are cards, so two do not leave a third column empty. */
    .sb-hbfeed--1-3 .sb-newshero__side { grid-template-rows: none; grid-template-columns: repeat(var(--hbf-side, 3), minmax(0, 1fr)); }
    .sb-hbfeed__grid { height: auto; }
    /* Two across at most on a tablet. The count is worked out in PHP rather
       than with min() here: repeat() takes a literal integer, and a math
       function in that slot is not something browsers accept. */
    .sb-hbfeed__grid--2x2,
    .sb-hbfeed__grid--row4 { grid-template-columns: repeat(var(--hbf-cols-sm, 2), minmax(0, 1fr)); grid-template-rows: none; }
    .sb-hbfeed__grid .sb-newshero__card { height: 200px; }
    .sb-hbfeed__grid .sb-newshero__feature { height: 400px; }
}
@media (max-width: 600px) {
    .sb-hbfeed--1-3 .sb-newshero__side { grid-template-columns: minmax(0, 1fr); }
    .sb-hbfeed__grid--2x2,
    .sb-hbfeed__grid--row4 { grid-template-columns: 1fr; }
}


/* =====================================================================
   Slider mode
   ---------------------------------------------------------------------
   The stage is a design surface, not a pile of fixed pixels:

     --hb-ref    the design width a composition was built against
     --hb-u      one design pixel in real pixels (stage width ÷ --hb-ref)
     --hb-lu     the same, times the layer's per-device scale

   Every size inside a slide is `calc(var(--hb-lu) * <design px>)` and every
   position is a length in whichever unit the admin chose, so a slide keeps
   its proportions at any width. Dropping --hb-ref on smaller screens is what
   keeps a 60px headline readable on a phone without moving anything.

   Four breakpoints, widest to narrowest: desktop, laptop, tablet, mobile.
   Each one resolves the whole layer contract into a `--r-*` variable through
   a fallback chain — mobile falls back to tablet, tablet to laptop, laptop to
   the base value — and everything below reads only those. A layer therefore
   stores a number once and inherits it downwards, and the rules that paint it
   never mention a breakpoint at all.
   ===================================================================== */
.sb-hbslider {
    --hb-ratio: var(--hb-ratio-d, 1.7778);
    --hb-ref: var(--hb-ref-d, 1920);
    --hb-u: calc(100cqi / var(--hb-ref));
    --hb-speed: 700ms;
    --hb-dir: 1;

    /**
     * The bridge between real viewport units and the editor's scale model.
     *
     * A layer may be sized in vw / vh / rem as well as in design pixels. On
     * the live page those mean exactly what they say — but the admin canvas
     * is a *drawing* of a device, not a device, and its viewport belongs to
     * the browser chrome around it. Routing every one of them through a
     * custom property lets the editor redefine what one is worth at its own
     * size (see admin/css/home-banner.css) while the live page just uses the
     * real thing, and the same stylesheet paints both.
     */
    --hb-vw: 1vw;
    --hb-vh: 1vh;
    --hb-rem: 1rem;

    container-type: inline-size;
    position: relative;
    width: 100%;
    overflow: hidden;
    background: #1a1a1a;
    touch-action: pan-y;

    /**
     * A definite height for layouts that never resolve the stage's width.
     *
     * The page builder stacks rows with `flex-direction: column` under 768px.
     * A flex container sizes its items from their max-content contribution,
     * and in that pass the stage's width is indefinite — so *any* height
     * derived from its own width (aspect-ratio, percentage padding) counts as
     * zero, the row measures 0, and the banner disappears from the live page
     * while still measuring 500×588 itself. The viewport is the one width that
     * is always definite, so it is the only thing that can break the cycle.
     *
     * This is a floor, not a size: the spacer below is exact and already
     * matches it for the full-bleed hero the banner is designed to be. It is
     * still entirely derived — the ratio is the admin's per-device setting.
     */
    min-height: calc(100vw / var(--hb-ratio));
}

/**
 * The stage's height comes from a padding-ratio spacer, not `aspect-ratio`.
 *
 * Everything inside a slide is absolutely positioned, so the stage has no
 * in-flow content of its own. `aspect-ratio` looks like the modern answer,
 * but the height it derives does not contribute to an ancestor flex
 * container's intrinsic cross size — and the page builder wraps every block
 * in `.pb-section__row { display:flex; align-items:stretch }`. There the row
 * measured 0, stretched the column to 0, and the banner vanished off the
 * live homepage on mobile while still being 500×588 itself.
 *
 * Percentage padding resolves against the containing block's *width*, which
 * is always definite here, so this contributes real in-flow height in every
 * layout context. It is still fully derived — the ratio is the admin's
 * per-device setting, the width is whatever the page gives it.
 */
.sb-hbslider::before {
    content: '';
    display: block;
    width: 100%;
    padding-top: calc(100% / var(--hb-ratio));
}

/* Container query units need a container; without support the stage is
   full-bleed in practice, and the runtime overwrites --hb-u regardless. */
@supports not (container-type: inline-size) {
    .sb-hbslider { --hb-u: calc(100vw / var(--hb-ref)); }
}

@media (max-width: 1440px) {
    .sb-hbslider { --hb-ratio: var(--hb-ratio-l, var(--hb-ratio-d, 1.7778)); --hb-ref: var(--hb-ref-l, var(--hb-ref-d, 1920)); }
}
@media (max-width: 1024px) {
    .sb-hbslider { --hb-ratio: var(--hb-ratio-t, var(--hb-ratio-l, 1.3333)); --hb-ref: var(--hb-ref-t, var(--hb-ref-l, 1280)); }
}
@media (max-width: 640px) {
    .sb-hbslider { --hb-ratio: var(--hb-ratio-m, var(--hb-ratio-t, .85)); --hb-ref: var(--hb-ref-m, var(--hb-ref-t, 760)); }
}

.sb-hbslider__track { position: absolute; inset: 0; perspective: 1600px; }

/* ---------------- slides -------------------------------------------- */
.sb-hbslide {
    position: absolute;
    inset: 0;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    background: var(--hb-slide-bg, #1a1a1a);
    backface-visibility: hidden;
}
.sb-hbslide.is-active { opacity: 1; visibility: visible; pointer-events: auto; z-index: 2; }
.sb-hbslide.is-leaving { opacity: 1; visibility: visible; pointer-events: none; z-index: 1; }

.sb-hbslide__bg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: var(--hb-bg-fit, cover);
    object-position: var(--hb-bg-pos, 50% 50%);
}

/* ---------------- the content container ------------------------------
   A centred column inside the full-bleed stage. Layers anchored to it
   measure their percentages against the column instead of the browser,
   which is what holds a heading / description / button stack together as
   the window widens rather than letting the three drift apart.

   Two things about it are deliberate:

   · It is `position: absolute` with `z-index: auto` and nothing else that
     would make it a stacking context, so its children's z-indexes still
     compete with the stage-anchored layers in the slide's own stacking
     context. One z order covers both kinds of layer; a container that
     stacked separately would have split it in two.

   · Its width is a real pixel measure (`--hb-cw`), not a design pixel. A
     reading column is a reading column at every screen size; growing it
     with the banner would defeat the point of having one.
   ------------------------------------------------------------------ */
.sb-hbslide__content {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;

    /**
     * The gutter is taken out of the width, not added as padding.
     *
     * Every layer inside this column is absolutely positioned, and an
     * absolutely positioned box resolves its percentages against its
     * containing block's *padding* box — so `padding-inline` here would be
     * stepped straight over and the gutter would do nothing at all. Making
     * the column narrower instead is the same visual result by a route that
     * abspos children cannot ignore, and it keeps centring for free because
     * the auto margins still split whatever is left over.
     */
    width: min(calc(100% - var(--hb-u) * var(--hb-cpad, 24) * 2), var(--hb-cw, 1200px));
    margin-left: var(--hb-cml, auto);
    margin-right: var(--hb-cmr, auto);
    pointer-events: none;
    z-index: auto;
}
/* The column itself is scenery; only the layers inside it are targets. */
.sb-hbslide__content > .sb-hbl { pointer-events: auto; }

/**
 * The mask / overlay.
 *
 * Four properties describe it: the fill and the blend mode are shared by every
 * breakpoint, while the strength and the shape may be overridden per device —
 * so those two fall back through -m → -t → -l → base exactly as a layer's
 * geometry does. `mix-blend-mode` is safe here: .sb-hbslide carries an
 * opacity, which isolates the blend to the slide's own background, leaving the
 * layers painted above it untouched.
 */
.sb-hbslide__tint {
    position: absolute;
    inset: 0;
    background: var(--hb-overlay, transparent);
    opacity: var(--hb-overlay-op, .75);
    mix-blend-mode: var(--hb-overlay-blend, normal);
    clip-path: var(--hb-overlay-clip, none);

    /* Scenery, never a target. Every layer is z-index 1 or higher so the mask
       already paints behind them, and this makes sure it can never swallow a
       click meant for a linked headline or a button either. */
    pointer-events: none;
    z-index: 0;
}

@media (max-width: 1440px) {
    .sb-hbslide__tint {
        opacity: var(--hb-overlay-op-l, var(--hb-overlay-op, .75));
        clip-path: var(--hb-overlay-clip-l, var(--hb-overlay-clip, none));
    }
}
@media (max-width: 1024px) {
    .sb-hbslide__tint {
        opacity: var(--hb-overlay-op-t, var(--hb-overlay-op-l, var(--hb-overlay-op, .75)));
        clip-path: var(--hb-overlay-clip-t, var(--hb-overlay-clip-l, var(--hb-overlay-clip, none)));
    }
}
@media (max-width: 640px) {
    .sb-hbslide__tint {
        opacity: var(--hb-overlay-op-m, var(--hb-overlay-op-t, var(--hb-overlay-op-l, var(--hb-overlay-op, .75))));
        clip-path: var(--hb-overlay-clip-m, var(--hb-overlay-clip-t, var(--hb-overlay-clip-l, var(--hb-overlay-clip, none))));
    }
}

/* Slow drift on the background while its slide is on screen. */
.sb-hbslide.is-active .sb-hbslide__bg--kb { animation: hbKenBurns 14s ease-out both; }
@keyframes hbKenBurns { from { transform: scale(1) } to { transform: scale(1.09) } }

/* =====================================================================
   Layer geometry — the responsive contract
   ---------------------------------------------------------------------
   Below is the *whole* of the breakpoint logic: each device resolves every
   layer property into a `--r-*` variable, narrowest override winning, and
   every rule after this block reads only those. Adding a breakpoint means
   adding one @media here and nothing anywhere else.
   ===================================================================== */
.sb-hbl {
    --r-x: var(--hbx, 50%);
    --r-y: var(--hby, 50%);
    --r-w: var(--hbw, auto);
    --r-h: var(--hbh, auto);
    --r-tx: var(--hbtx, -50%);
    --r-ty: var(--hbty, -50%);
    --r-mw: var(--hbmw, 92%);
    --r-maxw: var(--hbmaxw, none);
    --r-sc: var(--hbsc, 1);
    --r-d: var(--hbd, block);
    --r-rot: var(--hbrot, 0deg);
    --r-op: var(--hbop, 1);
    --r-mt: var(--hbmt, 0);
    --r-mr: var(--hbmr, 0);
    --r-mb: var(--hbmb, 0);
    --r-ml: var(--hbml, 0);
    --r-fh: var(--hbfh, block);
    --r-fs: var(--hbfs, 24);
    --r-ls: var(--hbls, 0);
    --r-px: var(--hbpx, 0);
    --r-py: var(--hbpy, 0);
    --r-rad: var(--hbrad, 0);
    --r-lh: var(--hblh, 1.15);
    --r-align: var(--hbalign, left);
    --r-valign: var(--hbvalign, center);
    --r-wrap: var(--hbwrap, nowrap);
    --r-gap: var(--hbgap, 16);
    --r-dir: var(--hbdir, column);
    --r-just: var(--hbjust, flex-start);
    --r-aitems: var(--hbaitems, flex-start);
}

@media (max-width: 1440px) {
    .sb-hbl {
        --r-x: var(--hbx-l, var(--hbx, 50%));
        --r-y: var(--hby-l, var(--hby, 50%));
        --r-w: var(--hbw-l, var(--hbw, auto));
        --r-h: var(--hbh-l, var(--hbh, auto));
        --r-tx: var(--hbtx-l, var(--hbtx, -50%));
        --r-ty: var(--hbty-l, var(--hbty, -50%));
        --r-mw: var(--hbmw-l, var(--hbmw, 92%));
        --r-maxw: var(--hbmaxw-l, var(--hbmaxw, none));
        --r-sc: var(--hbsc-l, var(--hbsc, 1));
        --r-d: var(--hbd-l, var(--hbd, block));
        --r-rot: var(--hbrot-l, var(--hbrot, 0deg));
        --r-op: var(--hbop-l, var(--hbop, 1));
        --r-mt: var(--hbmt-l, var(--hbmt, 0));
        --r-mr: var(--hbmr-l, var(--hbmr, 0));
        --r-mb: var(--hbmb-l, var(--hbmb, 0));
        --r-ml: var(--hbml-l, var(--hbml, 0));
        --r-fh: var(--hbfh-l, var(--hbfh, block));
        --r-fs: var(--hbfs-l, var(--hbfs, 24));
        --r-ls: var(--hbls-l, var(--hbls, 0));
        --r-px: var(--hbpx-l, var(--hbpx, 0));
        --r-py: var(--hbpy-l, var(--hbpy, 0));
        --r-rad: var(--hbrad-l, var(--hbrad, 0));
        --r-lh: var(--hblh-l, var(--hblh, 1.15));
        --r-align: var(--hbalign-l, var(--hbalign, left));
        --r-valign: var(--hbvalign-l, var(--hbvalign, center));
        --r-wrap: var(--hbwrap-l, var(--hbwrap, nowrap));
        --r-gap: var(--hbgap-l, var(--hbgap, 16));
        --r-dir: var(--hbdir-l, var(--hbdir, column));
        --r-just: var(--hbjust-l, var(--hbjust, flex-start));
        --r-aitems: var(--hbaitems-l, var(--hbaitems, flex-start));
    }
}

@media (max-width: 1024px) {
    .sb-hbl {
        --r-x: var(--hbx-t, var(--hbx-l, var(--hbx, 50%)));
        --r-y: var(--hby-t, var(--hby-l, var(--hby, 50%)));
        --r-w: var(--hbw-t, var(--hbw-l, var(--hbw, auto)));
        --r-h: var(--hbh-t, var(--hbh-l, var(--hbh, auto)));
        --r-tx: var(--hbtx-t, var(--hbtx-l, var(--hbtx, -50%)));
        --r-ty: var(--hbty-t, var(--hbty-l, var(--hbty, -50%)));
        --r-mw: var(--hbmw-t, var(--hbmw-l, var(--hbmw, 92%)));
        --r-maxw: var(--hbmaxw-t, var(--hbmaxw-l, var(--hbmaxw, none)));
        --r-sc: var(--hbsc-t, var(--hbsc-l, var(--hbsc, 1)));
        --r-d: var(--hbd-t, var(--hbd-l, var(--hbd, block)));
        --r-rot: var(--hbrot-t, var(--hbrot-l, var(--hbrot, 0deg)));
        --r-op: var(--hbop-t, var(--hbop-l, var(--hbop, 1)));
        --r-mt: var(--hbmt-t, var(--hbmt-l, var(--hbmt, 0)));
        --r-mr: var(--hbmr-t, var(--hbmr-l, var(--hbmr, 0)));
        --r-mb: var(--hbmb-t, var(--hbmb-l, var(--hbmb, 0)));
        --r-ml: var(--hbml-t, var(--hbml-l, var(--hbml, 0)));
        --r-fh: var(--hbfh-t, var(--hbfh-l, var(--hbfh, block)));
        --r-fs: var(--hbfs-t, var(--hbfs-l, var(--hbfs, 24)));
        --r-ls: var(--hbls-t, var(--hbls-l, var(--hbls, 0)));
        --r-px: var(--hbpx-t, var(--hbpx-l, var(--hbpx, 0)));
        --r-py: var(--hbpy-t, var(--hbpy-l, var(--hbpy, 0)));
        --r-rad: var(--hbrad-t, var(--hbrad-l, var(--hbrad, 0)));
        --r-lh: var(--hblh-t, var(--hblh-l, var(--hblh, 1.15)));
        --r-align: var(--hbalign-t, var(--hbalign-l, var(--hbalign, left)));
        --r-valign: var(--hbvalign-t, var(--hbvalign-l, var(--hbvalign, center)));
        --r-wrap: var(--hbwrap-t, var(--hbwrap-l, var(--hbwrap, nowrap)));
        --r-gap: var(--hbgap-t, var(--hbgap-l, var(--hbgap, 16)));
        --r-dir: var(--hbdir-t, var(--hbdir-l, var(--hbdir, column)));
        --r-just: var(--hbjust-t, var(--hbjust-l, var(--hbjust, flex-start)));
        --r-aitems: var(--hbaitems-t, var(--hbaitems-l, var(--hbaitems, flex-start)));
    }
}

@media (max-width: 640px) {
    .sb-hbl {
        --r-x: var(--hbx-m, var(--hbx-t, var(--hbx-l, var(--hbx, 50%))));
        --r-y: var(--hby-m, var(--hby-t, var(--hby-l, var(--hby, 50%))));
        --r-w: var(--hbw-m, var(--hbw-t, var(--hbw-l, var(--hbw, auto))));
        --r-h: var(--hbh-m, var(--hbh-t, var(--hbh-l, var(--hbh, auto))));
        --r-tx: var(--hbtx-m, var(--hbtx-t, var(--hbtx-l, var(--hbtx, -50%))));
        --r-ty: var(--hbty-m, var(--hbty-t, var(--hbty-l, var(--hbty, -50%))));
        --r-mw: var(--hbmw-m, var(--hbmw-t, var(--hbmw-l, var(--hbmw, 92%))));
        --r-maxw: var(--hbmaxw-m, var(--hbmaxw-t, var(--hbmaxw-l, var(--hbmaxw, none))));
        --r-sc: var(--hbsc-m, var(--hbsc-t, var(--hbsc-l, var(--hbsc, 1))));
        --r-d: var(--hbd-m, var(--hbd-t, var(--hbd-l, var(--hbd, block))));
        --r-rot: var(--hbrot-m, var(--hbrot-t, var(--hbrot-l, var(--hbrot, 0deg))));
        --r-op: var(--hbop-m, var(--hbop-t, var(--hbop-l, var(--hbop, 1))));
        --r-mt: var(--hbmt-m, var(--hbmt-t, var(--hbmt-l, var(--hbmt, 0))));
        --r-mr: var(--hbmr-m, var(--hbmr-t, var(--hbmr-l, var(--hbmr, 0))));
        --r-mb: var(--hbmb-m, var(--hbmb-t, var(--hbmb-l, var(--hbmb, 0))));
        --r-ml: var(--hbml-m, var(--hbml-t, var(--hbml-l, var(--hbml, 0))));
        --r-fh: var(--hbfh-m, var(--hbfh-t, var(--hbfh-l, var(--hbfh, block))));
        --r-fs: var(--hbfs-m, var(--hbfs-t, var(--hbfs-l, var(--hbfs, 24))));
        --r-ls: var(--hbls-m, var(--hbls-t, var(--hbls-l, var(--hbls, 0))));
        --r-px: var(--hbpx-m, var(--hbpx-t, var(--hbpx-l, var(--hbpx, 0))));
        --r-py: var(--hbpy-m, var(--hbpy-t, var(--hbpy-l, var(--hbpy, 0))));
        --r-rad: var(--hbrad-m, var(--hbrad-t, var(--hbrad-l, var(--hbrad, 0))));
        --r-lh: var(--hblh-m, var(--hblh-t, var(--hblh-l, var(--hblh, 1.15))));
        --r-align: var(--hbalign-m, var(--hbalign-t, var(--hbalign-l, var(--hbalign, left))));
        --r-valign: var(--hbvalign-m, var(--hbvalign-t, var(--hbvalign-l, var(--hbvalign, center))));
        --r-wrap: var(--hbwrap-m, var(--hbwrap-t, var(--hbwrap-l, var(--hbwrap, nowrap))));
        --r-gap: var(--hbgap-m, var(--hbgap-t, var(--hbgap-l, var(--hbgap, 16))));
        --r-dir: var(--hbdir-m, var(--hbdir-t, var(--hbdir-l, var(--hbdir, column))));
        --r-just: var(--hbjust-m, var(--hbjust-t, var(--hbjust-l, var(--hbjust, flex-start))));
        --r-aitems: var(--hbaitems-m, var(--hbaitems-t, var(--hbaitems-l, var(--hbaitems, flex-start))));
    }
}


/* ---------------- layers --------------------------------------------
   Position and box live on .sb-hbl; the entrance animation lives on the
   inner element so it never fights the anchoring transform.
   ------------------------------------------------------------------ */
.sb-hbl {
    position: absolute;
    box-sizing: border-box;
    left: var(--r-x);
    top: var(--r-y);
    width: var(--r-w);
    height: var(--r-h);
    max-width: min(var(--r-mw), var(--r-maxw));
    display: var(--r-d);
    margin: calc(var(--hb-lu) * var(--r-mt)) calc(var(--hb-lu) * var(--r-mr))
            calc(var(--hb-lu) * var(--r-mb)) calc(var(--hb-lu) * var(--r-ml));
    transform: translate(var(--r-tx), var(--r-ty)) rotate(var(--r-rot));
    opacity: var(--r-op);
    z-index: var(--hbz, 1);
    --hb-lu: calc(var(--hb-u) * var(--r-sc));
    text-decoration: none;
}

/**
 * The inner element.
 *
 * It carries the entrance animation (so the animation's transform never
 * fights the layer's anchoring one) and, when the layer has been given a
 * height, it becomes the flex box that puts the label where --r-valign says.
 * `--r-fh` is what switches between the two, and it is a property rather
 * than a class because a device may set a height its wider neighbour had not.
 */
.sb-hbl__i {
    display: var(--r-fh);
    flex-direction: column;
    justify-content: var(--r-valign);
    width: 100%;
    height: 100%;
}
a.sb-hbl { cursor: pointer; }

/* ---------------- groups — the row / column system --------------------
   A group is placed on the stage like any other layer. What makes it a
   group is that its children lay out *in flow* inside it, so a button row
   keeps its gap and its alignment at every width instead of each button
   carrying coordinates that have to be re-solved per device.

   The flex box is the inner element, not the layer itself, for the same
   reason every other type puts its content there: the layer owns the
   anchoring transform and the group has to be free to animate.

   The children are still ordinary layers — same box, same type styling,
   same animation — they simply stop reading their own x / y.
   ------------------------------------------------------------------ */
.sb-hbl--group {
    background: var(--hbbg, transparent);
    border-radius: calc(var(--hb-lu) * var(--r-rad));
    padding: calc(var(--hb-lu) * var(--r-py)) calc(var(--hb-lu) * var(--r-px));
}
.sb-hbl--group > .sb-hbl__i {
    display: flex;
    flex-direction: var(--r-dir);
    justify-content: var(--r-just);
    align-items: var(--r-aitems);
    flex-wrap: var(--r-wrap);
    gap: calc(var(--hb-lu) * var(--r-gap));
}

/**
 * A child of a group.
 *
 * Its own coordinates are meaningless here — the flex box places it — so
 * position and the anchoring translate come off, leaving the parts of the
 * layer contract that still apply: size, margins, rotation, opacity.
 * `max-width: 100%` is the overflow guard in flow: a long label wraps inside
 * the group instead of stretching it past the stage.
 */
.sb-hbl--inflow {
    position: relative;
    left: auto;
    top: auto;
    max-width: min(100%, var(--r-maxw));
    transform: rotate(var(--r-rot));
    flex: 0 1 auto;
}

/* shared box treatment for the types that are a run of type in a box */
.sb-hbl--text,
.sb-hbl--button,
.sb-hbl--badge,
.sb-hbl--html {
    padding: calc(var(--hb-lu) * var(--r-py)) calc(var(--hb-lu) * var(--r-px));
    background: var(--hbbg, transparent);
    border-radius: calc(var(--hb-lu) * var(--r-rad));
    font-size: calc(var(--hb-lu) * var(--r-fs));
    font-weight: var(--hbfw, 700);
    letter-spacing: calc(var(--hb-lu) * var(--r-ls));
    line-height: var(--r-lh);
    color: var(--hbcolor, #fff);
    text-align: var(--r-align);
}

/**
 * The clamp() half of the type scale.
 *
 * A design pixel keeps its share of the stage forever, which is right in the
 * middle of the range and wrong at both ends — 60 design px is unreadable on
 * a watch and absurd across a 5K monitor. A floor and a ceiling in real
 * pixels bound it without flattening it: the size still scales with the
 * stage, it simply stops scaling past these. Absent bounds fall back to 0 and
 * a number nothing reaches, so an unclamped layer is untouched.
 */
.sb-hbl--fsclamp {
    font-size: clamp(
        var(--hbfsmin, 0px),
        calc(var(--hb-lu) * var(--r-fs)),
        var(--hbfsmax, 9999px));
}

.sb-hbl--text {
    white-space: pre-wrap;
    overflow-wrap: break-word;
}

/* No nowrap: with width:auto these still shrink-to-fit onto one line, so the
   common case is unchanged — but a label that would outgrow its max-width now
   wraps inside its box instead of running off the stage, and one given an
   explicit width wraps to fill it. */
.sb-hbl--button,
.sb-hbl--badge {
    text-align: center;
    line-height: 1.2;
}

.sb-hbl--button { transition: filter .15s ease; }
a.sb-hbl--button:hover { filter: brightness(1.09); }

.sb-hbl--bordered { border: calc(var(--hb-lu) * var(--hbbw, 0)) solid var(--hbbc, #fff); }
.sb-hbl--upper { text-transform: uppercase; }
.sb-hbl--italic { font-style: italic; }
.sb-hbl--underline { text-decoration: underline; }
.sb-hbl--font-heading { font-family: Georgia, "Times New Roman", serif; }
.sb-hbl--font-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

.sb-hbl--sh-soft.sb-hbl--text { text-shadow: 0 2px 14px rgba(0, 0, 0, .38); }
.sb-hbl--sh-strong.sb-hbl--text { text-shadow: 0 3px 22px rgba(0, 0, 0, .72); }
.sb-hbl--sh-soft:not(.sb-hbl--text) { box-shadow: 0 calc(var(--hb-lu) * 6) calc(var(--hb-lu) * 18) rgba(0, 0, 0, .28); }
.sb-hbl--sh-strong:not(.sb-hbl--text) { box-shadow: 0 calc(var(--hb-lu) * 12) calc(var(--hb-lu) * 34) rgba(0, 0, 0, .5); }

/* image overlay — aspect-locked by default so nothing reflows on load */
.sb-hbl--image { aspect-ratio: var(--hbar, auto); }
.sb-hbl--image img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: var(--hbfit, contain);
    border-radius: calc(var(--hb-lu) * var(--r-rad));
}

/* ---------------- icon ------------------------------------------------
   One stroked SVG from the shared set. Its size is the type scale — an
   icon beside a headline should grow with the headline, not with its box —
   so it reads --r-fs like every other glyph on the stage.
   ------------------------------------------------------------------ */
.sb-hbl--icon {
    color: var(--hbcolor, #fff);
    background: var(--hbbg, transparent);
    border-radius: calc(var(--hb-lu) * var(--r-rad));
    padding: calc(var(--hb-lu) * var(--r-py)) calc(var(--hb-lu) * var(--r-px));
    line-height: 0;
}
.sb-hbl--icon > .sb-hbl__i { display: flex; align-items: center; justify-content: center; }
.sb-hbl--icon svg {
    display: block;
    width: calc(var(--hb-lu) * var(--r-fs));
    height: calc(var(--hb-lu) * var(--r-fs));
    stroke-width: var(--hbstroke, 2);
}

/* ---------------- video ----------------------------------------------
   Either a provider embed or a plain <video>; both fill the layer's box and
   both keep a natural ratio when no height was given, so the slide never
   reflows around them as they load.
   ------------------------------------------------------------------ */
.sb-hbl--video {
    aspect-ratio: var(--hbar, 1.7778);
    overflow: hidden;
    border-radius: calc(var(--hb-lu) * var(--r-rad));
}
.sb-hbl--video video,
.sb-hbl--video iframe {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
    object-fit: var(--hbfit, cover);
    background: #000;
}

/* ---------------- custom HTML -----------------------------------------
   Whatever the admin wrote, inheriting the layer's type scale and colour so
   a fragment lands looking like part of the banner rather than like the
   browser's defaults.
   ------------------------------------------------------------------ */
.sb-hbl--html img,
.sb-hbl--html svg { max-width: 100%; height: auto; }
.sb-hbl--html a { color: inherit; }
.sb-hbl--html > .sb-hbl__i > :first-child { margin-top: 0; }
.sb-hbl--html > .sb-hbl__i > :last-child { margin-bottom: 0; }

/* shape / overlay panel */
.sb-hbl--shape {
    background: var(--hbbg, rgba(0, 0, 0, .7));
    border-radius: calc(var(--hb-lu) * var(--r-rad));
    backdrop-filter: blur(calc(var(--hb-lu) * var(--hbblur, 0)));
    -webkit-backdrop-filter: blur(calc(var(--hb-lu) * var(--hbblur, 0)));
}

/* ---------------- layer entrance animations -------------------------
   Layers stay hidden until their slide is active, then play once with the
   delay/duration the editor set.

   Every entrance that *travels* is one keyframe and one pair of numbers:
   --hb-adx / --hb-ady, where the layer starts relative to where it lands,
   in design px (the same unit as font size, so it scales with the stage).
   The named entrances below only seed that pair, which is why the editor
   can let an admin drag the start point anywhere — diagonals included —
   without a keyframe per direction, and why a start point far enough out
   simply begins outside the banner (.sb-hbslider clips it) rather than
   needing a separate "slide in from off-stage" animation.

   --hb-ease picks the curve it travels on.
   ------------------------------------------------------------------ */
.sb-hbslide .sb-hbl__i[data-anim]:not([data-anim="none"]) { opacity: 0; }

.sb-hbslide.is-active .sb-hbl__i[data-anim]:not([data-anim="none"]) {
    animation-duration: var(--hb-dur, .8s);
    animation-delay: var(--hb-delay, 0s);
    animation-fill-mode: both;
    animation-timing-function: var(--hb-ease, cubic-bezier(.22, .61, .36, 1));
}

/* The default start point per named entrance. A layer the admin has never
   dragged carries no --hb-adx/--hb-ady of its own and lands on these, which
   are the exact offsets these animations used before the start point became
   a setting. Mirrored by ANIM_START in admin/js/home-banner.js. */
.sb-hbslide .sb-hbl__i[data-anim="fade-up"] { --hb-adx-d: 0; --hb-ady-d: 44; }
.sb-hbslide .sb-hbl__i[data-anim="fade-down"] { --hb-adx-d: 0; --hb-ady-d: -44; }
.sb-hbslide .sb-hbl__i[data-anim="fade-left"],
.sb-hbslide .sb-hbl__i[data-anim="slide-left"] { --hb-adx-d: -80; --hb-ady-d: 0; }
.sb-hbslide .sb-hbl__i[data-anim="fade-right"],
.sb-hbslide .sb-hbl__i[data-anim="slide-right"] { --hb-adx-d: 80; --hb-ady-d: 0; }

.sb-hbslide.is-active .sb-hbl__i[data-anim="fade"] { animation-name: hbFade; }
.sb-hbslide.is-active .sb-hbl__i[data-anim="fade-up"],
.sb-hbslide.is-active .sb-hbl__i[data-anim="fade-down"],
.sb-hbslide.is-active .sb-hbl__i[data-anim="fade-left"],
.sb-hbslide.is-active .sb-hbl__i[data-anim="fade-right"],
.sb-hbslide.is-active .sb-hbl__i[data-anim="slide-left"],
.sb-hbslide.is-active .sb-hbl__i[data-anim="slide-right"] { animation-name: hbMoveIn; }
.sb-hbslide.is-active .sb-hbl__i[data-anim="zoom-in"] { animation-name: hbZoomIn; }
.sb-hbslide.is-active .sb-hbl__i[data-anim="zoom-out"] { animation-name: hbZoomOut; }
.sb-hbslide.is-active .sb-hbl__i[data-anim="flip-up"] { animation-name: hbFlipUp; }
.sb-hbslide.is-active .sb-hbl__i[data-anim="rotate-in"] { animation-name: hbRotateIn; }
.sb-hbslide.is-active .sb-hbl__i[data-anim="blur-in"] { animation-name: hbBlurIn; }
.sb-hbslide.is-active .sb-hbl__i[data-anim="bounce-in"] { animation-name: hbBounceIn; animation-timing-function: var(--hb-ease, cubic-bezier(.34, 1.56, .64, 1)); }

@keyframes hbFade { from { opacity: 0 } to { opacity: 1 } }

/* The one travelling entrance. --hb-adx/--hb-ady are the layer's own start
   point when the admin has dragged one; otherwise the seeds above apply. */
@keyframes hbMoveIn {
    from {
        opacity: 0;
        transform: translate(
            calc(var(--hb-lu) * var(--hb-adx, var(--hb-adx-d, 0))),
            calc(var(--hb-lu) * var(--hb-ady, var(--hb-ady-d, 0))));
    }
    to { opacity: 1; transform: none }
}

@keyframes hbZoomIn { from { opacity: 0; transform: scale(.82) } to { opacity: 1; transform: none } }
@keyframes hbZoomOut { from { opacity: 0; transform: scale(1.18) } to { opacity: 1; transform: none } }
@keyframes hbFlipUp { from { opacity: 0; transform: perspective(800px) rotateX(-70deg) } to { opacity: 1; transform: none } }
@keyframes hbRotateIn { from { opacity: 0; transform: rotate(-8deg) scale(.9) } to { opacity: 1; transform: none } }
@keyframes hbBlurIn { from { opacity: 0; filter: blur(14px) } to { opacity: 1; filter: blur(0) } }
@keyframes hbBounceIn { from { opacity: 0; transform: scale(.5) } to { opacity: 1; transform: none } }

/* ---------------- layer exit animations ------------------------------
   The mirror image of the entrance: the runtime stamps `data-out` on the
   *leaving* slide's layers, they play once, and the slide is gone.

   These are deliberately shorter and smaller than the entrances. An exit
   runs against a 700ms slide transition that is already moving the whole
   stage, so a layer that travels far on the way out reads as a glitch
   rather than as choreography — which is why the exit list is the short
   one and its default duration is half an entrance's.

   `data-out` only exists while a slide is leaving, so a layer with no exit
   configured is simply never stamped and carries on doing nothing.
   ------------------------------------------------------------------ */
.sb-hbslide.is-leaving .sb-hbl__i[data-out]:not([data-out="none"]) {
    animation-duration: var(--hb-odur, .4s);
    animation-delay: var(--hb-odelay, 0s);
    animation-fill-mode: both;
    animation-timing-function: var(--hb-oease, cubic-bezier(.4, 0, 1, 1));
}

.sb-hbslide.is-leaving .sb-hbl__i[data-out="fade"] { animation-name: hbFadeOut; }
.sb-hbslide.is-leaving .sb-hbl__i[data-out="fade-up"] { animation-name: hbOutUp; }
.sb-hbslide.is-leaving .sb-hbl__i[data-out="fade-down"] { animation-name: hbOutDown; }
.sb-hbslide.is-leaving .sb-hbl__i[data-out="fade-left"] { animation-name: hbOutLeft; }
.sb-hbslide.is-leaving .sb-hbl__i[data-out="fade-right"] { animation-name: hbOutRight; }
.sb-hbslide.is-leaving .sb-hbl__i[data-out="zoom-in"] { animation-name: hbOutZoomIn; }
.sb-hbslide.is-leaving .sb-hbl__i[data-out="zoom-out"] { animation-name: hbOutZoomOut; }
.sb-hbslide.is-leaving .sb-hbl__i[data-out="blur-out"] { animation-name: hbOutBlur; }

@keyframes hbFadeOut { from { opacity: 1 } to { opacity: 0 } }
@keyframes hbOutUp { from { opacity: 1; transform: none } to { opacity: 0; transform: translateY(calc(var(--hb-lu) * -44)) } }
@keyframes hbOutDown { from { opacity: 1; transform: none } to { opacity: 0; transform: translateY(calc(var(--hb-lu) * 44)) } }
@keyframes hbOutLeft { from { opacity: 1; transform: none } to { opacity: 0; transform: translateX(calc(var(--hb-lu) * -80)) } }
@keyframes hbOutRight { from { opacity: 1; transform: none } to { opacity: 0; transform: translateX(calc(var(--hb-lu) * 80)) } }
@keyframes hbOutZoomIn { from { opacity: 1; transform: none } to { opacity: 0; transform: scale(1.18) } }
@keyframes hbOutZoomOut { from { opacity: 1; transform: none } to { opacity: 0; transform: scale(.82) } }
@keyframes hbOutBlur { from { opacity: 1; filter: blur(0) } to { opacity: 0; filter: blur(14px) } }

/* ---------------- slide transitions ---------------------------------
   `data-transition` is configuration — which transition this slide uses.
   `data-in` / `data-leave` are the instruction to play it *now*, stamped
   on by the runtime (and by the template for the first paint). Keeping
   the two apart is what lets the admin canvas re-render a slide without
   it flashing through its entrance every time.

   The arriving slide owns the transition: the runtime copies its name
   onto the departing slide as `data-leave` so both halves always match.
   --hb-dir is 1 going forward and -1 going back, which mirrors the
   directional ones without a second set of keyframes.
   ------------------------------------------------------------------ */
.sb-hbslide.is-active[data-in],
.sb-hbslide.is-leaving[data-leave] {
    animation-duration: var(--hb-speed, 700ms);
    animation-fill-mode: both;
    animation-timing-function: cubic-bezier(.4, 0, .2, 1);
}

.sb-hbslide.is-active[data-in="fade"] { animation-name: hbsFadeIn; }
.sb-hbslide.is-leaving[data-leave="fade"] { animation-name: hbsFadeOut; }

.sb-hbslide.is-active[data-in="slide-left"] { animation-name: hbsSlideInX; }
.sb-hbslide.is-leaving[data-leave="slide-left"] { animation-name: hbsSlideOutX; }
.sb-hbslide.is-active[data-in="slide-right"] { animation-name: hbsSlideInXr; }
.sb-hbslide.is-leaving[data-leave="slide-right"] { animation-name: hbsSlideOutXr; }
.sb-hbslide.is-active[data-in="slide-up"] { animation-name: hbsSlideInY; }
.sb-hbslide.is-leaving[data-leave="slide-up"] { animation-name: hbsSlideOutY; }
.sb-hbslide.is-active[data-in="slide-down"] { animation-name: hbsSlideInYr; }
.sb-hbslide.is-leaving[data-leave="slide-down"] { animation-name: hbsSlideOutYr; }

.sb-hbslide.is-active[data-in="zoom-in"] { animation-name: hbsZoomIn; }
.sb-hbslide.is-leaving[data-leave="zoom-in"] { animation-name: hbsZoomInOut; }
.sb-hbslide.is-active[data-in="zoom-out"] { animation-name: hbsZoomOut; }
.sb-hbslide.is-leaving[data-leave="zoom-out"] { animation-name: hbsZoomOutOut; }

.sb-hbslide.is-active[data-in="flip"] { animation-name: hbsFlipIn; }
.sb-hbslide.is-leaving[data-leave="flip"] { animation-name: hbsFlipOut; }

/* "cover" slides the new panel over a stationary outgoing one */
.sb-hbslide.is-active[data-in="cover"] { animation-name: hbsSlideInX; }
.sb-hbslide.is-leaving[data-leave="cover"] { animation-name: hbsHold; }

.sb-hbslide.is-active[data-in="none"],
.sb-hbslide.is-leaving[data-leave="none"] { animation: none; }

@keyframes hbsFadeIn { from { opacity: 0 } to { opacity: 1 } }
@keyframes hbsFadeOut { from { opacity: 1 } to { opacity: 0 } }
@keyframes hbsHold { from { opacity: 1 } to { opacity: 1 } }

@keyframes hbsSlideInX { from { transform: translateX(calc(100% * var(--hb-dir, 1))) } to { transform: translateX(0) } }
@keyframes hbsSlideOutX { from { transform: translateX(0) } to { transform: translateX(calc(-100% * var(--hb-dir, 1))) } }
@keyframes hbsSlideInXr { from { transform: translateX(calc(-100% * var(--hb-dir, 1))) } to { transform: translateX(0) } }
@keyframes hbsSlideOutXr { from { transform: translateX(0) } to { transform: translateX(calc(100% * var(--hb-dir, 1))) } }
@keyframes hbsSlideInY { from { transform: translateY(calc(100% * var(--hb-dir, 1))) } to { transform: translateY(0) } }
@keyframes hbsSlideOutY { from { transform: translateY(0) } to { transform: translateY(calc(-100% * var(--hb-dir, 1))) } }
@keyframes hbsSlideInYr { from { transform: translateY(calc(-100% * var(--hb-dir, 1))) } to { transform: translateY(0) } }
@keyframes hbsSlideOutYr { from { transform: translateY(0) } to { transform: translateY(calc(100% * var(--hb-dir, 1))) } }

@keyframes hbsZoomIn { from { opacity: 0; transform: scale(.8) } to { opacity: 1; transform: scale(1) } }
@keyframes hbsZoomInOut { from { opacity: 1; transform: scale(1) } to { opacity: 0; transform: scale(1.14) } }
@keyframes hbsZoomOut { from { opacity: 0; transform: scale(1.22) } to { opacity: 1; transform: scale(1) } }
@keyframes hbsZoomOutOut { from { opacity: 1; transform: scale(1) } to { opacity: 0; transform: scale(.86) } }

@keyframes hbsFlipIn { from { opacity: 0; transform: rotateY(calc(75deg * var(--hb-dir, 1))) } to { opacity: 1; transform: rotateY(0) } }
@keyframes hbsFlipOut { from { opacity: 1; transform: rotateY(0) } to { opacity: 0; transform: rotateY(calc(-75deg * var(--hb-dir, 1))) } }

@media (prefers-reduced-motion: reduce) {
    .sb-hbslide,
    .sb-hbslide__bg--kb,
    .sb-hbslide.is-active .sb-hbl__i,
    .sb-hbslide.is-leaving .sb-hbl__i { animation: none !important; }
    .sb-hbslide .sb-hbl__i[data-anim] { opacity: 1; }
}

/* ---------------- controls ------------------------------------------ */
.sb-hbslider__dots {
    position: absolute;
    left: 50%;
    bottom: 22px;
    transform: translateX(-50%);
    display: flex;
    gap: 8px;
    z-index: 6;
}
.sb-hbslider__dot {
    width: 10px;
    height: 10px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: rgba(255, 255, 255, .45);
    cursor: pointer;
    transition: all .2s;
}
.sb-hbslider__dot:hover { background: rgba(255, 255, 255, .75); }
.sb-hbslider__dot.is-active { background: #ff9811; width: 26px; border-radius: 100px; }

.sb-hbslider__arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
    border: 0;
    border-radius: 50%;
    background: rgba(0, 0, 0, .38);
    color: #fff;
    cursor: pointer;
    z-index: 6;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background .15s;
}
.sb-hbslider__arrow:hover { background: rgba(0, 0, 0, .65); }
.sb-hbslider__arrow svg { width: 22px; height: 22px; }
.sb-hbslider__arrow--prev { left: 18px; }
.sb-hbslider__arrow--next { right: 18px; }

.sb-hbslider__progress {
    position: absolute;
    left: 0;
    bottom: 0;
    height: 3px;
    width: 100%;
    background: rgba(255, 255, 255, .18);
    z-index: 6;
}
.sb-hbslider__progress i {
    display: block;
    height: 100%;
    width: 0;
    background: #ff9811;
    transform-origin: left center;
}
.sb-hbslider__progress.is-running i { animation: hbProgress var(--hb-slide-dur, 5s) linear both; }
@keyframes hbProgress { from { width: 0 } to { width: 100% } }

@media (max-width: 768px) {
    .sb-hbslider__arrow { width: 36px; height: 36px; }
    .sb-hbslider__arrow--prev { left: 8px; }
    .sb-hbslider__arrow--next { right: 8px; }
    .sb-hbslider__dots { bottom: 14px; }
}
