/* ── Global: Divi's #page-container overflow kills position:sticky ──────── */
/* overflow-x:clip prevents the CSS promotion rule from forcing overflow-y back
   to 'auto' when overflow-x is changed to visible. */
#page-container {
	overflow-x: clip !important;
	overflow-y: visible !important;
}

/* ── Sticky Stacking Cards: hide the trigger module wrapper (FRONTEND ONLY) ───── */
/*
 * The trigger module is a settings carrier with no visual output on the
 * frontend.  We hide the MODULE WRAPPER (.bsf_sticky_stacking_cards), not
 * the outer row — hiding the row via :has() would hide the entire column
 * including all the card rows nested inside it.
 *
 * The :has(> .bsf-stacking-trigger) selector scopes this rule to the frontend
 * context only.  On the frontend, PHP render_callback outputs a direct child
 * <div class="bsf-stacking-trigger"> inside the module wrapper.  In the Divi
 * Visual Builder, edit.tsx renders a different child (.bsf-sticky-stacking-cards__vb-notice)
 * instead — so :has() doesn't match and the module remains visible/selectable
 * for editing.
 *
 * querySelectorAll() still finds .bsf-stacking-trigger inside a display:none
 * parent, so the JS reads config data attributes just fine.
 */
.bsf_sticky_stacking_cards:has(> .bsf-stacking-trigger) {
	display: none !important;
}

/* The section itself must not clip sticky children. */
.et_pb_section:has(.bsf-stacking-trigger) {
	overflow: visible !important;
}

/* Column containing the trigger: overflow-x must be 'clip' (NOT 'hidden') to
   avoid the CSS overflow promotion rule.  When overflow-x is hidden/auto/scroll
   and we set overflow-y to visible, the spec silently promotes overflow-y back
   to 'auto', turning the element into a scroll container that traps sticky.
   'clip' clips visually without creating a scroll container. */
.et_pb_column:has(.bsf-stacking-trigger) {
	overflow-x: clip !important;
	overflow-y: visible !important;
}

/* Fix: Divi's et_cover_background sets overflow-x:hidden on body, which the
   CSS spec forces to overflow-y:auto. That makes body a non-scrolling scroll
   container, trapping position:sticky (scrollingElement = html).
   overflow-x:clip clips visually without creating a scroll container and does
   not trigger the overflow-y auto-promotion rule. */
body:has(.bsf-stacking-trigger) {
	overflow-x: clip !important;
	overflow-y: visible !important;
}

/* ── Hero Scroll Grid ─────────────────────────────────────────────────── */
/* Divi module wrapper must clip the scaled grid */
.bsf_hero_scroll_grid {
	overflow: hidden;
}

.bsf-hero-grid {
	position: relative;
	width: 100vw;
	margin-left: calc(-50vw + 50%);
	overflow: hidden;
	/* Pull the user's "Hero Background" colour (set on the parent module
	   wrapper via Divi's Background decoration) down through the inner
	   layout so it's actually visible behind the grid cells — including in
	   the gaps after the scroll animation completes. */
	background-color: inherit;
}

.bsf-hero-grid__sticky {
	position: relative;
	width: 100%;
	height: 100vh;
	overflow: hidden;
	background-color: inherit;
}

.bsf-hero-grid__images {
	position: absolute;
	/* `inset` reuses the same CSS custom property that drives `gap`, so
	   the spacing on the outer perimeter (top/right/bottom/left of the
	   grid) animates in lock-step with the spacing between cells.  This
	   gives a visually consistent gutter on every edge, including the
	   top and bottom of the hero — without that, the outer rows would
	   butt up against the section edges while the inner gaps were
	   visible.  Same var → both transitions stay in sync as GSAP
	   interpolates from 0 → user value during the scroll animation. */
	inset: var(--bsf-hero-grid-gap, 0px);
	z-index: 1;
	display: grid;
	grid-template-rows: repeat(3, 1fr);
	padding: 0;
	will-change: transform;
	/* `gap` bound to a CSS custom property so frontend.js can animate it
	   reliably via GSAP (which can interpolate custom properties cleanly,
	   unlike the `gap` shorthand which CSSPlugin handles inconsistently
	   across browsers).  Default 0 keeps the initial "compressed focal
	   cell" look; animation lerps to the user's Grid Gap value. */
	gap: var(--bsf-hero-grid-gap, 0px);
}

/* Child module wrappers must be invisible to grid */
.bsf-hero-grid__images > .et_pb_module {
	display: contents;
}

.bsf-hero-grid__cell {
	overflow: hidden;
	position: relative;
}

/* Lead Frame mode — CSS-var bridge.
   In this mode the grid container itself does NOT scale (frontend.js leaves
   .bsf-hero-grid__images at scale 1).  Instead the center cell scales 3x to
   fill the viewport while the surrounding eight cells start hidden + offset
   and animate into their grid slots.  We just expose transform-origin and
   will-change here so GSAP's per-cell transforms have a stable origin and
   the browser knows to promote each cell to its own layer.  All the actual
   transform values (scale, translate, opacity) are set inline by GSAP. */
.bsf-hero-grid--lead-frame .bsf-hero-grid__cell,
.bsf-hero-grid--center-hold .bsf-hero-grid__cell {
	transform-origin: center center;
	will-change: transform, opacity;
}

/* Center Hold mode.
   The center cell is rendered at its final 1/3-viewport slot size from the
   start — it never scales or translates.  The surrounding eight cells start
   hidden + offset (set inline by GSAP) and assemble around the center on
   scroll.  bundle.css doesn't need any positional rules beyond the shared
   transform-origin hint above; everything else lives in frontend.js. */

/* Fade In text animation — FOUC-free starting state.
   Without this rule the content container renders at full opacity for the
   first paint, then frontend.js sets it to 0 a tick later, then GSAP
   animates it back in.  The visible-then-hidden flash is the FOUC.  The
   class is added by PHP so the rule applies on the very first paint, no
   JS required.  GSAP overrides inline once it boots.  If GSAP fails to
   load, frontend.js has a defensive fallback that strips the modifier
   class so the text doesn't stay invisible. */
.bsf-hero-grid--text-fade-in .bsf-hero-grid__content {
	opacity: 0;
}

.bsf-hero-grid__image,
.bsf-hero-grid__cell video {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

.bsf-hero-grid__hero-image {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

.bsf-hero-grid__content {
	position: absolute;
	inset: 0;
	z-index: 2;
	display: flex;
	align-items: center;
	justify-content: center;
	/* Padding so corner placements don't touch the viewport edge.
	   Scales with viewport so it stays comfortable on phone, tablet,
	   and desktop without a separate responsive control. */
	padding: clamp(24px, 5vw, 80px);
}

/* Hero Text Placement modifier classes.
   The wrapper element gets one bsf-hero-grid--placement-{position} class
   from PHP based on the user's selection.  Default middle-center matches
   the original centered layout exactly so existing instances don't shift.
   Each rule sets flex alignment AND text-align so the text reads
   correctly for its position; per-element overrides via Divi's font
   alignment win on specificity if authors want a different combo. */
.bsf-hero-grid--placement-top-left .bsf-hero-grid__content {
	align-items: flex-start;
	justify-content: flex-start;
}
.bsf-hero-grid--placement-top-center .bsf-hero-grid__content {
	align-items: flex-start;
	justify-content: center;
}
.bsf-hero-grid--placement-top-right .bsf-hero-grid__content {
	align-items: flex-start;
	justify-content: flex-end;
}
.bsf-hero-grid--placement-middle-left .bsf-hero-grid__content {
	align-items: center;
	justify-content: flex-start;
}
.bsf-hero-grid--placement-middle-center .bsf-hero-grid__content {
	align-items: center;
	justify-content: center;
}
.bsf-hero-grid--placement-middle-right .bsf-hero-grid__content {
	align-items: center;
	justify-content: flex-end;
}
.bsf-hero-grid--placement-bottom-left .bsf-hero-grid__content {
	align-items: flex-end;
	justify-content: flex-start;
}
.bsf-hero-grid--placement-bottom-center .bsf-hero-grid__content {
	align-items: flex-end;
	justify-content: center;
}
.bsf-hero-grid--placement-bottom-right .bsf-hero-grid__content {
	align-items: flex-end;
	justify-content: flex-end;
}

/* Auto text-align based on the horizontal half of the placement. */
.bsf-hero-grid--placement-top-left .bsf-hero-grid__text,
.bsf-hero-grid--placement-middle-left .bsf-hero-grid__text,
.bsf-hero-grid--placement-bottom-left .bsf-hero-grid__text {
	text-align: left;
}
.bsf-hero-grid--placement-top-right .bsf-hero-grid__text,
.bsf-hero-grid--placement-middle-right .bsf-hero-grid__text,
.bsf-hero-grid--placement-bottom-right .bsf-hero-grid__text {
	text-align: right;
}
/* Top/middle/bottom-center inherit the default text-align: center on
   .bsf-hero-grid__text — no explicit rule needed. */

/* Buttons row needs the same horizontal alignment treatment.  The
   default .bsf-hero-grid__buttons rule sets justify-content: center,
   which leaves the buttons stranded in the middle even when the text
   block is left- or right-aligned.  Override per placement to keep
   the button row visually anchored to the same edge as the text. */
.bsf-hero-grid--placement-top-left .bsf-hero-grid__buttons,
.bsf-hero-grid--placement-middle-left .bsf-hero-grid__buttons,
.bsf-hero-grid--placement-bottom-left .bsf-hero-grid__buttons {
	justify-content: flex-start;
}
.bsf-hero-grid--placement-top-right .bsf-hero-grid__buttons,
.bsf-hero-grid--placement-middle-right .bsf-hero-grid__buttons,
.bsf-hero-grid--placement-bottom-right .bsf-hero-grid__buttons {
	justify-content: flex-end;
}

.bsf-hero-grid__overlay {
	position: absolute;
	inset: 0;
	z-index: 1;
	background-color: rgba(0, 0, 0, 0.55);
}

.bsf-hero-grid__text {
	position: relative;
	z-index: 2;
	text-align: center;
	/* Max-width is bound to a CSS var set inline by PHP from the
	   user's "Text Block Max Width" range field.  Default 900px
	   matches the original behavior so existing instances render
	   identically without re-saving. */
	max-width: var(--bsf-hero-grid-content-max-width, 900px);
	width: 100%;
}

/* Default text colour is white over the grid imagery + dark overlay.
   Authors can override per-element via Divi's font color picker — these
   are intentionally low-specificity (single class) so user overrides win. */
.bsf-hero-grid__subtitle,
.bsf-hero-grid__title,
.bsf-hero-grid__description {
	color: #ffffff;
}

.bsf-hero-grid__buttons {
	display: flex;
	gap: 12px;
	flex-wrap: wrap;
	justify-content: center;
	margin-top: 16px;
}

.et_pb_button.bsf-hero-grid__btn {
	display: inline-block;
	text-decoration: none;
	transition: all 0.3s ease;
	cursor: pointer;
	padding: 10px 24px;
	border-radius: 6px;
	font-weight: 600;
	font-size: 14px;
	line-height: 1.7em;
}

.et_pb_button.bsf-hero-grid__btn:hover {
	opacity: 0.85;
}

.et_pb_button.bsf-hero-grid__btn--primary {
	background-color: #2ea3f2;
	color: #ffffff;
	border: 2px solid #2ea3f2;
}

.et_pb_button.bsf-hero-grid__btn--secondary {
	background-color: transparent;
	color: #2ea3f2;
	border: 2px solid #2ea3f2;
}

.bsf-hero-grid__cell--hide-mobile {
	display: block;
}

@media (max-width: 767px) {
	.bsf-hero-grid {
		width: 100%;
		margin-left: 0;
	}

	.bsf-hero-grid__cell--hide-mobile {
		display: none;
	}
}

/* Prevent horizontal scroll from hero grid on any device.
   Use clip, NOT hidden — hidden forces overflow-y:auto via CSS spec,
   which creates a non-scrolling scroll container that traps position:sticky. */
html:has(.bsf-hero-grid),
body:has(.bsf-hero-grid) {
	overflow-x: clip;
}

/* ── Card Carousel ───────────────────────────────────────────────────── */
/* Module wrapper — renders the full Swiper structure. */
.bsf_card_carousel {
	position: relative;
	width: 100%;
}

/* Carousel root — holds nav, swiper, pagination, progress. */
.bsf-carousel {
	position: relative;
	width: 100%;
	max-width: 100%;
}

/* Swiper container — masks overflow so partial slides clip at the edge. */
.bsf-carousel .bsf-carousel-swiper {
	width: 100%;
	overflow: hidden;
}

/* Track: horizontal flex row of slides. */
.bsf-carousel-track {
	display: flex;
	flex-wrap: nowrap;
	align-items: stretch;
}

/* Each card wrapper (applied to the child module wrapper).
   Dual-targeted (.bsf_card_carousel_item is added by Divi via moduleClassName;
   .bsf-card-wrap is added by our classnamesFunction) so styles apply even if
   one source fails.  Divi's decoration.background paints directly on this
   element — overflow: hidden clips oversized bg media to card bounds. */
.bsf_card_carousel_item,
.bsf-card-wrap {
	flex-shrink: 0;
	box-sizing: border-box;
	overflow: hidden;
	position: relative;
	/* Visible fallback height so cards show BEFORE Swiper or our scoped
	   sizing CSS applies an explicit dimension.  Any aspect-ratio or
	   height set via the injected <style> tag overrides this. */
	min-height: 240px;
}

/* Inner card — fixed visual structure.  Transparent so the module
   wrapper's native Divi decoration.background (image / video / color /
   gradient) shows through behind the logo / content / icon. */
.bsf-card {
	position: relative;
	width: 100%;
	height: 100%;
	overflow: hidden;
	box-sizing: border-box;
	background: transparent;
}

/* Optional link wrapper covers the whole card without affecting layout. */
.bsf-card-link {
	display: block;
	width: 100%;
	height: 100%;
	text-decoration: none;
	color: inherit;
}

/* Logo layer — anchor-based positioning driven by .bsf-card--logo-<pos>
   class on the card root.  Defaults set in .bsf-card--logo-top-center. */
.bsf-card-logo {
	position: absolute;
	z-index: 2;
	padding: 0;
	max-width: 60%;
}
.bsf-card-logo img {
	display: block;
	max-width: var(--bsf-logo-max-w, 120px);
	width: auto;
	height: auto;
}
/* Per-anchor positioning — 24px inset from the corresponding edge(s).
   `transform` re-centers when an axis is centered. */
.bsf-card--logo-top-left      .bsf-card-logo { top: 24px;    left: 24px;   }
.bsf-card--logo-top-center    .bsf-card-logo { top: 24px;    left: 50%; transform: translateX(-50%); }
.bsf-card--logo-top-right     .bsf-card-logo { top: 24px;    right: 24px;  }
.bsf-card--logo-center-left   .bsf-card-logo { top: 50%;     left: 24px;  transform: translateY(-50%); }
.bsf-card--logo-center        .bsf-card-logo { top: 50%;     left: 50%;   transform: translate(-50%, -50%); }
.bsf-card--logo-center-right  .bsf-card-logo { top: 50%;     right: 24px; transform: translateY(-50%); }
.bsf-card--logo-bottom-left   .bsf-card-logo { bottom: 24px; left: 24px;   }
.bsf-card--logo-bottom-center .bsf-card-logo { bottom: 24px; left: 50%;   transform: translateX(-50%); }
.bsf-card--logo-bottom-right  .bsf-card-logo { bottom: 24px; right: 24px;  }

/* Content stack — bottom-left by default. */
.bsf-card-content {
	position: absolute;
	left: 24px;
	right: 24px;
	bottom: 24px;
	z-index: 2;
	max-width: 80%;
	display: flex;
	flex-direction: column;
	gap: 12px;
}

/* Title + body text — neutral defaults so Divi's font decoration controls
   actually take effect.  No hardcoded colours / sizes here. */
.bsf-card-title {
	margin: 0;
	padding: 0;
}
.bsf-card-text {
	margin: 0;
	padding: 0;
}

/* Button row — sits below title + content.  Wraps if both buttons + a
   narrow card. */
.bsf-card-buttons {
	display: flex;
	gap: 12px;
	flex-wrap: wrap;
	margin-top: 4px;
}

/* Base button styling — minimal so Divi's button-decoration group can
   override colour, font, padding, border, shadow. */
.et_pb_button.bsf-card-btn {
	display: inline-block;
	text-decoration: none;
	cursor: pointer;
	transition: opacity 0.2s ease, transform 0.2s ease;
}
.et_pb_button.bsf-card-btn:hover {
	opacity: 0.9;
}

/* Card Button (Link Cue) — Webflow-style circle button in a corner of the
   card.  Acts as a visual cue that the whole card is a link; the anchor
   wrapping is on the card itself, so this element is decorative (a <span>
   when the parent is an <a>, an <a> otherwise — see render).  All values
   driven by CSS custom properties from the Card Button design group. */
.bsf-card-button {
	position: absolute;
	z-index: 3;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width:  var(--bsf-cardbtn-size, 56px);
	height: var(--bsf-cardbtn-size, 56px);
	border-radius: var(--bsf-cardbtn-radius, 50%);
	background: var(--bsf-cardbtn-bg, #ffffff);
	color:      var(--bsf-cardbtn-color, #1a1a1a);
	border: var(--bsf-cardbtn-border-w, 0) solid var(--bsf-cardbtn-border-c, rgba(0,0,0,0.1));
	box-sizing: border-box;
	pointer-events: none;
	text-decoration: none;
	transition: background 0.25s ease, color 0.25s ease, transform 0.25s ease;
}
.bsf-card-button svg { display: block; }
/* Hover swaps colours when the ENTIRE CARD is hovered — Webflow pattern.
   Targets .bsf-card:hover (which is also true when the surrounding
   .bsf-card-link anchor is hovered, since the card sits inside it). */
.bsf-card:hover .bsf-card-button {
	background: var(--bsf-cardbtn-bg-hover, #ffffff);
	color:      var(--bsf-cardbtn-color-hover, #000000);
}
/* Defeat any theme ::before / ::after that might sneak in. */
.bsf-card-button::before,
.bsf-card-button::after { content: none !important; display: none !important; }

.bsf-card--btn-top-left    .bsf-card-button { top: 20px;    left: 20px;   }
.bsf-card--btn-top-right   .bsf-card-button { top: 20px;    right: 20px;  }
.bsf-card--btn-bottom-left .bsf-card-button { bottom: 20px; left: 20px;   }
.bsf-card--btn-bottom-right .bsf-card-button { bottom: 20px; right: 20px; }

/* Active slide hook — no default style; authors can target via Advanced CSS. */
.bsf-card-wrap--active {
	/* no default styling */
}

/* ── Card overlay — improves legibility of text over image / video ──── */
/* Sits between the bg layer (z-index 0) and content (z-index 2). */
.bsf-card-overlay {
	position: absolute;
	left: 0;
	right: 0;
	/* Height + blur strength + edge anchor are all controlled by CSS
	   variables / data-attributes on the parent .bsf-carousel.  Authors
	   tune them via Carousel Settings → Overlay Height / Blur Intensity /
	   Overlay Position. */
	bottom: 0;
	top: auto;
	height: var(--bsf-overlay-height, 70%);
	z-index: 1;
	pointer-events: none;
	transform: translateZ(0);  /* compositing — required for backdrop-filter */
	will-change: transform;
}

/* Overlay anchored to the top instead of the bottom. */
.bsf-carousel[data-overlay-position="top"] .bsf-card-overlay {
	top: 0;
	bottom: auto;
}

/* Hidden by default — only one of the variants below is shown when the
   parent carousel sets data-overlay-effect="..." */
.bsf-card-overlay {
	display: none;
}
.bsf-carousel[data-overlay-effect="blur"] .bsf-card-overlay,
.bsf-carousel[data-overlay-effect="gradient-black"] .bsf-card-overlay,
.bsf-carousel[data-overlay-effect="gradient-white"] .bsf-card-overlay {
	display: block;
}

/* Blur — backdrop-filter strength is controlled by --bsf-overlay-blur
   (default 10px).  Mask fades the blur in from the FAR edge toward the
   anchor edge.  When position=bottom (default) the blur is strongest at
   the bottom; when position=top the mask flips. */
.bsf-carousel[data-overlay-effect="blur"] .bsf-card-overlay {
	-webkit-backdrop-filter: blur(var(--bsf-overlay-blur, 10px));
	backdrop-filter: blur(var(--bsf-overlay-blur, 10px));
	background-image: linear-gradient(rgba(255,255,255,0.16), rgba(0,0,0,0.25) 59%, rgba(0,0,0,0.5));
	-webkit-mask-image: linear-gradient(rgba(0,0,0,0), rgba(0,0,0,1), rgba(0,0,0,1));
	mask-image: linear-gradient(rgba(0,0,0,0), rgba(0,0,0,1), rgba(0,0,0,1));
	backface-visibility: hidden;
}
.bsf-carousel[data-overlay-effect="blur"][data-overlay-position="top"] .bsf-card-overlay {
	background-image: linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.25) 41%, rgba(255,255,255,0.16));
	-webkit-mask-image: linear-gradient(rgba(0,0,0,1), rgba(0,0,0,1), rgba(0,0,0,0));
	mask-image: linear-gradient(rgba(0,0,0,1), rgba(0,0,0,1), rgba(0,0,0,0));
}

/* Black gradient — fades transparent at the FAR edge → black at the anchor. */
.bsf-carousel[data-overlay-effect="gradient-black"] .bsf-card-overlay {
	background-image: linear-gradient(to top, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.6) 35%, rgba(0,0,0,0) 100%);
}
.bsf-carousel[data-overlay-effect="gradient-black"][data-overlay-position="top"] .bsf-card-overlay {
	background-image: linear-gradient(to bottom, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.6) 35%, rgba(0,0,0,0) 100%);
}

/* White gradient — same shape, white tones. */
.bsf-carousel[data-overlay-effect="gradient-white"] .bsf-card-overlay {
	background-image: linear-gradient(to top, rgba(255,255,255,0.92) 0%, rgba(255,255,255,0.7) 35%, rgba(255,255,255,0) 100%);
}
.bsf-carousel[data-overlay-effect="gradient-white"][data-overlay-position="top"] .bsf-card-overlay {
	background-image: linear-gradient(to bottom, rgba(255,255,255,0.92) 0%, rgba(255,255,255,0.7) 35%, rgba(255,255,255,0) 100%);
}

/* ── Nav arrows ──────────────────────────────────────────────────────── */
/*
 * Nav lives OUTSIDE .bsf-carousel as a sibling inside .bsf_card_carousel
 * (the Divi module wrapper).  The module wrapper is constrained to the row
 * width regardless of the carousel's bleed setting, so the nav stays user-
 * friendly even when the carousel itself extends to the viewport edge.
 *
 * Defaults are deliberately neutral — Divi's button decoration on the
 * `navButton` element override colour, font, padding, border, shadow.
 */
.bsf-carousel-nav {
	display: flex;
	gap: var(--bsf-nav-gap, 8px);
	pointer-events: none;
	position: relative;
	z-index: 4;
	/* Author-controlled spacing.  Defaults match the prior hardcoded values
	   (only the top-right variant had a 12px bottom-margin previously). */
	margin: var(--bsf-nav-mt, 0) var(--bsf-nav-mr, 0) var(--bsf-nav-mb, 0) var(--bsf-nav-ml, 0);
}
/* Nav buttons — driven entirely by CSS variables emitted on .bsf-carousel-nav
   from the parent module's "Nav Buttons" Design tab settings.  Specificity
   kept LOW so author overrides via Advanced CSS still win, but high enough
   to defeat any inherited button styles from the page theme. */
.bsf-carousel-nav .bsf-carousel-prev,
.bsf-carousel-nav .bsf-carousel-next {
	pointer-events: auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width:  var(--bsf-nav-size, 40px);
	height: var(--bsf-nav-size, 40px);
	padding: 0;
	min-width: 0;
	background: var(--bsf-nav-bg, rgba(255, 255, 255, 0.9));
	color:      var(--bsf-nav-color, #1a1a1a);
	border: var(--bsf-nav-border-w, 1px) solid var(--bsf-nav-border-c, rgba(0, 0, 0, 0.1));
	border-radius: var(--bsf-nav-radius, 50%);
	cursor: pointer;
	line-height: 1;
	text-decoration: none;
	font: inherit;
	box-sizing: border-box;
	transition: background 0.2s ease, color 0.2s ease, opacity 0.2s ease;
}
.bsf-carousel-nav .bsf-carousel-prev:hover,
.bsf-carousel-nav .bsf-carousel-next:hover {
	background: var(--bsf-nav-bg-hover, #1a1a1a);
	color:      var(--bsf-nav-color-hover, #ffffff);
}
/* Defeat any theme / Divi default that injects content via ::after on
   buttons in this carousel (e.g. Divi's anchor-arrow on .et_pb_button — not
   that we use that class anymore, but some themes target [class*="-prev"]). */
.bsf-carousel-nav .bsf-carousel-prev::after,
.bsf-carousel-nav .bsf-carousel-next::after,
.bsf-carousel-nav .bsf-carousel-prev::before,
.bsf-carousel-nav .bsf-carousel-next::before {
	content: none !important;
	display: none !important;
}
.bsf-carousel-prev.swiper-button-disabled,
.bsf-carousel-next.swiper-button-disabled {
	opacity: 0.35;
	pointer-events: none;
	cursor: default;
}

/* Nav position variants — only handle alignment within the row.  Vertical
   spacing (the gap between nav and carousel) is now driven by the user's
   margin values in the Nav Buttons design group. */
.bsf-carousel-nav--top-right {
	justify-content: flex-end;
}
.bsf-carousel-nav--below-center {
	justify-content: center;
}
/* Overlay-sides positions the nav OVER the carousel.  Anchored to the
   module wrapper (which stays at row width) so the buttons sit at the
   row's left/right edges, not the viewport edges. */
.bsf-carousel-nav--overlay-sides {
	position: absolute;
	top: 50%;
	left: 0;
	right: 0;
	transform: translateY(-50%);
	z-index: 4;
	justify-content: space-between;
	padding: 0 12px;
	pointer-events: none;
}
.bsf-carousel-nav--overlay-sides .bsf-carousel-prev,
.bsf-carousel-nav--overlay-sides .bsf-carousel-next {
	pointer-events: auto;
}
.bsf-carousel-nav--hidden {
	display: none;
}

/* Pinned mode is driven entirely by page scroll — nav arrows would be
   redundant and confusing (they'd just page-scroll, not advance one card).
   Hide the nav chrome whenever the carousel is in pinned mode. */
.bsf-carousel--mode-pinned .bsf-carousel-nav,
.bsf-carousel--pinned .bsf-carousel-nav {
	display: none !important;
}

/* ── Card Bleed — escape the row width to the viewport edge ─────────── */
/*
 * The carousel base rule has `width: 100%; max-width: 100%`, so a negative
 * margin alone won't widen the element — it just gets eaten by the width
 * cap.  Instead we explicitly set the carousel WIDTH to a value larger
 * than the parent, plus override the max-width.
 *
 * 50% resolves against the parent's width (the Divi module wrapper, which
 * tracks the column width).  50vw is half the viewport.  The difference is
 * how far the parent's edge sits from the viewport's edge.
 *
 *   Right bleed:  carousel width = parent (50% + 50% = 100%) + extra-on-right (50vw - 50%)
 *               = 50% + 50vw
 *   Full bleed:   carousel width = 100vw, shifted left by (50vw - 50%)
 *
 * Parents (module wrapper, column, row, section) are forced to NOT clip
 * horizontal overflow when a bleed is active, and the body gets
 * `overflow-x: clip` so the bleed doesn't trigger a horizontal scrollbar.
 */
.bsf-carousel[data-bleed="right"] {
	width: calc(50% + 50vw);
	max-width: none;
}
.bsf-carousel[data-bleed="both"] {
	margin-left: calc(50% - 50vw);
	width: 100vw;
	max-width: none;
}

/* When ANY bleed is active, let the swiper clip plane open up so slides
   that translate past the carousel's column-side boundary stay visible
   until the body's `overflow-x: clip` (set further down) trims them at
   the actual viewport edge.  Without this, clicking next on a "right"
   bleed makes prior slides snap-disappear at the column's left edge
   instead of bleeding off the viewport like the right side does — a
   one-sided clip that breaks visual symmetry after advancing.

   Slide widths are unchanged because the swiper container width is
   unchanged; only its overflow rule changes. */
.bsf-carousel[data-bleed="right"] .bsf-carousel-swiper,
.bsf-carousel[data-bleed="both"]  .bsf-carousel-swiper {
	overflow: visible;
}

.bsf_card_carousel:has(.bsf-carousel[data-bleed="right"]),
.bsf_card_carousel:has(.bsf-carousel[data-bleed="both"]),
.et_pb_section:has(.bsf-carousel[data-bleed="right"]),
.et_pb_section:has(.bsf-carousel[data-bleed="both"]),
.et_pb_row:has(.bsf-carousel[data-bleed="right"]),
.et_pb_row:has(.bsf-carousel[data-bleed="both"]),
.et_pb_column:has(.bsf-carousel[data-bleed="right"]),
.et_pb_column:has(.bsf-carousel[data-bleed="both"]) {
	overflow-x: visible !important;
	overflow-y: visible !important;
}

body:has(.bsf-carousel[data-bleed="right"]),
body:has(.bsf-carousel[data-bleed="both"]),
html:has(.bsf-carousel[data-bleed="right"]),
html:has(.bsf-carousel[data-bleed="both"]) {
	overflow-x: clip !important;
}

/* In the VB the bleed widths / shifts would push the preview off the
   editor canvas; reset them so the stacked block layout stays in frame. */
.bsf-carousel--vb[data-bleed="right"],
.bsf-carousel--vb[data-bleed="both"] {
	margin-left:  0 !important;
	margin-right: 0 !important;
	width: 100% !important;
	max-width: 100% !important;
}

/* ── Pagination ──────────────────────────────────────────────────────── */
.bsf-carousel-pagination {
	display: flex;
	justify-content: center;
	align-items: center;
	gap: 8px;
	margin-top: 14px;
	padding: 0;
}
.bsf-carousel-bullet {
	width: 8px;
	height: 8px;
	padding: 0;
	border: 0;
	background: rgba(0, 0, 0, 0.25);
	border-radius: 50%;
	cursor: pointer;
	transition: background 0.2s ease, transform 0.2s ease, opacity 0.2s ease;
}
.bsf-carousel-bullet:hover {
	background: rgba(0, 0, 0, 0.5);
}
.bsf-carousel-bullet.is-active {
	background: #1a1a1a;
	transform: scale(1.15);
}

/* ── Progress bar (pinned mode) ──────────────────────────────────────── */
.bsf-carousel-progress {
	width: 100%;
	height: 3px;
	margin-top: 14px;
	background: rgba(0, 0, 0, 0.08);
	border-radius: 2px;
	overflow: hidden;
}
.bsf-carousel-progress-bar {
	width: 0%;
	height: 100%;
	background: #1a1a1a;
	transition: width 60ms linear;
}

/* Pinned mode: prevent native horizontal touch interference — GSAP drives
   translateX directly. */
.bsf-carousel--pinned {
	touch-action: pan-y;
}
.bsf-carousel--pinned .bsf-carousel-track {
	transition: none !important;
}

/* VB mode: edit.tsx hard-codes bsf-carousel--vb into the className so
   these overrides apply BEFORE any JS runs.  Cards stack vertically
   with a fixed preview height so aspect-ratio / height settings don't
   balloon the VB canvas. */
.bsf-carousel--vb .bsf-carousel-track,
.bsf-carousel--vb .swiper-wrapper {
	display: block !important;
	transform: none !important;
}
.bsf-carousel--vb .bsf_card_carousel_item,
.bsf-carousel--vb .bsf-card-wrap {
	width: 100% !important;
	margin: 0 0 16px 0 !important;
	aspect-ratio: auto !important;
	height: 400px !important;
	max-width: 100%;
	display: block !important;
}
.bsf-carousel--vb .bsf-card {
	aspect-ratio: auto !important;
	height: 100% !important;
	width: 100% !important;
}
/* Hide overlay-sides nav in VB (positioned absolutely, would cover the
   block-stacked cards). */
.bsf-carousel--vb .bsf-carousel-nav--overlay-sides {
	position: static;
	transform: none;
	justify-content: flex-end;
	margin-bottom: 12px;
}

/* Layered card background: image (always visible) under the video.
   The image is the poster / fallback; the video sits on top and is
   shown/hidden by the videoBehavior setting below. */
.bsf-card-bg {
	position: absolute;
	inset: 0;
	z-index: 0;
	overflow: hidden;
	pointer-events: none;
	border-radius: var(--bsf-media-radius, 0);
}
.bsf-card-bg-img,
.bsf-card-bg-video {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: var(--bsf-media-fit, cover);
	object-position: var(--bsf-media-pos, center);
	display: block;
}
/* When the user sets a Media Corner Radius the overlay should follow so the
   blur / gradient clip neatly inside the rounded media. */
.bsf-card-overlay {
	border-radius: var(--bsf-media-radius, 0);
}

/* When no image has been uploaded yet, PHP / edit.tsx render the bundled
   Divi-style placeholder SVG (assets/divi-placeholder.svg) directly into
   .bsf-card-bg-img.  No separate placeholder element needed. */
.bsf-card-bg-video {
	z-index: 1;  /* above image */
}

/* Autoplay (default): video plays on top of the image continuously. */
.bsf-carousel[data-video-behavior="autoplay"] .bsf-card-bg-video {
	opacity: 1;
}

/* Hoverplay: video hidden by default — image shows through.  On card
   hover, video fades in and plays (JS handles play/pause). */
.bsf-carousel[data-video-behavior="hoverplay"] .bsf-card-bg-video {
	opacity: 0;
	transition: opacity 0.25s ease;
}
.bsf-carousel[data-video-behavior="hoverplay"] .bsf_card_carousel_item:hover .bsf-card-bg-video,
.bsf-carousel[data-video-behavior="hoverplay"] .bsf-card-wrap:hover .bsf-card-bg-video {
	opacity: 1;
}

/* Reduced motion — no transitions on nav or progress. */
@media (prefers-reduced-motion: reduce) {
	.bsf-carousel-nav .bsf-carousel-prev,
	.bsf-carousel-nav .bsf-carousel-next,
	.bsf-carousel-bullet,
	.bsf-carousel-progress-bar {
		transition-duration: 0s !important;
	}
}

/* ════════════════════════════════════════════════════════════════════════
 *  Sticky Feature Block
 *
 *  Three-column editorial layout: optional eyebrow column, feature items
 *  column, sticky visual column.  Hover an item → the parent's Default
 *  Visual cross-fades to that item's Hover Visual.  All colour, sizing,
 *  and layout values come from CSS custom properties emitted on the
 *  `.bsf-sfb` root in PHP / edit.tsx — single source of truth.
 *
 *  Math note: the column-width controls (eyebrow / links / visual / offset)
 *  are token counts out of a 12-track layout.  We use `grid-template-columns`
 *  with `fr` units so authors get the familiar Bootstrap-ish span model
 *  without needing to compute pixel widths.  Gaps are `--bsf-sfb-col-gap`.
 * ════════════════════════════════════════════════════════════════════════ */
.bsf-sfb {
	display: grid;
	gap: var(--bsf-sfb-col-gap, 32px);
	/* `stretch` (not `start`) so the visual column stretches to the full
	   row height — without this the visual column shrinks to fit only the
	   sticky's intrinsic height, leaving zero scroll travel for sticky to
	   do its thing. */
	align-items: stretch;
	/* Author-controlled vertical breathing room below the module. */
	margin-bottom: var(--bsf-sfb-bottom-offset, 0px);
}

/* 3-column layouts — eyebrow + links + (empty offset) + visual.
   Gap-aware percentage math: subtract the total column-gap allowance
   from 100% BEFORE distributing proportionally, so columns + gaps
   sum to exactly the row width.  Combines the strengths of the two
   prior attempts:
     - v0.53 (calc(N / 12 * 100%)): proportions held but overflowed
       by total gap (3 gaps × 32px = 96px past row edge).
     - v0.54 (calc(N * 1fr)):       gap-aware but `fr` collapses to
       0 under content pressure (visual column → 0px).
   This form uses explicit percentages (no fr-collapse) AND subtracts
   gap allowance before distribution (no overflow).  3-column variants
   have 3 internal gaps; 2-column variants have 1. */
.bsf-sfb--3-column.bsf-sfb--visual-right {
	grid-template-columns:
		calc((100% - 3 * var(--bsf-sfb-col-gap, 32px)) * var(--bsf-sfb-eyebrow-cols, 2) / 12)
		calc((100% - 3 * var(--bsf-sfb-col-gap, 32px)) * var(--bsf-sfb-links-cols, 4) / 12)
		calc((100% - 3 * var(--bsf-sfb-col-gap, 32px)) * var(--bsf-sfb-visual-offset, 1) / 12)
		calc((100% - 3 * var(--bsf-sfb-col-gap, 32px)) * var(--bsf-sfb-visual-cols, 5) / 12);
	grid-template-areas: "eyebrow links . visual";
}
.bsf-sfb--3-column.bsf-sfb--visual-left {
	grid-template-columns:
		calc((100% - 3 * var(--bsf-sfb-col-gap, 32px)) * var(--bsf-sfb-visual-cols, 5) / 12)
		calc((100% - 3 * var(--bsf-sfb-col-gap, 32px)) * var(--bsf-sfb-visual-offset, 1) / 12)
		calc((100% - 3 * var(--bsf-sfb-col-gap, 32px)) * var(--bsf-sfb-links-cols, 4) / 12)
		calc((100% - 3 * var(--bsf-sfb-col-gap, 32px)) * var(--bsf-sfb-eyebrow-cols, 2) / 12);
	grid-template-areas: "visual . links eyebrow";
}

/* 2-column layouts — links + visual (eyebrow folds above description).
   Single gap between the two columns; denominator /9 because the
   default links (4) + visual (5) = 9 column units. */
.bsf-sfb--2-column.bsf-sfb--visual-right {
	grid-template-columns:
		calc((100% - 1 * var(--bsf-sfb-col-gap, 32px)) * var(--bsf-sfb-links-cols, 4) / 9)
		calc((100% - 1 * var(--bsf-sfb-col-gap, 32px)) * var(--bsf-sfb-visual-cols, 5) / 9);
	grid-template-areas: "links visual";
}
.bsf-sfb--2-column.bsf-sfb--visual-left {
	grid-template-columns:
		calc((100% - 1 * var(--bsf-sfb-col-gap, 32px)) * var(--bsf-sfb-visual-cols, 5) / 9)
		calc((100% - 1 * var(--bsf-sfb-col-gap, 32px)) * var(--bsf-sfb-links-cols, 4) / 9);
	grid-template-areas: "visual links";
}

/* min-width: 0 on flex / grid children lets long single-word headings
   wrap inside the column instead of stretching the column wider — needed
   now that we dropped the implicit `minmax(0, ...)` content-min from the
   percentage layout above. */
.bsf-sfb__links-col,
.bsf-sfb__items,
.bsf-sfb-item-wrap {
	min-width: 0;
}

.bsf-sfb__eyebrow-col { grid-area: eyebrow; }
.bsf-sfb__visual-col  { grid-area: visual; }

/* Links column — `min-height` is now author-controlled via the `Group
   Min Height` setting (parent → Sticky Behavior).  Default `auto` lets
   the natural item-stack height drive the column.  Authors who want
   meaningful sticky scroll travel can set e.g. `120vh` to force a
   taller group regardless of item count.  Mobile @media block resets
   to `auto`. */
.bsf-sfb__links-col {
	grid-area: links;
	min-height: var(--bsf-sfb-group-min-height, auto);
}

/* Eyebrow + description — neutral defaults so Divi font decoration wins. */
.bsf-sfb__eyebrow-text {
	font-size: 0.75rem;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: rgba(0, 0, 0, 0.55);
	margin: 0 0 1rem;
}
.bsf-sfb__description {
	font-size: 1.5rem;
	line-height: 1.4;
	margin: 0 0 2rem;
}

.bsf-sfb__items {
	display: flex;
	flex-direction: column;
}

/* ─── Sticky Visual ──────────────────────────────────────────────────────
 * Native `position: sticky` driven entirely by CSS — no JS involvement.
 * The `data-enable-sticky="off"` author override flips it to `static` so
 * the visual scrolls naturally inline with the items column.  In the VB
 * (.bsf-sfb--vb) we always force relative so the editor canvas stays
 * scrollable without weird sticky behavior.
 */
.bsf-sfb__visual-sticky {
	position: sticky;
	top: var(--bsf-sfb-sticky-top, 80px);
	width: 100%;
	/* Aspect ratio + height resolution chain:
	     - When `--bsf-sfb-visual-h-desktop` is `auto` (empty author input),
	       `aspect-ratio` drives the size from width.
	     - When the author sets a real height (e.g. "600px", "80vh"),
	       `height` wins over `aspect-ratio` per CSS spec. */
	aspect-ratio: var(--bsf-sfb-visual-aspect, 4 / 5);
	height: var(--bsf-sfb-visual-h-desktop, auto);
	/* Clamp to viewport (minus sticky offset + breathing room) so the
	   visual never grows taller than the screen AND doesn't dominate the
	   group's total height — both required for sticky travel. */
	max-height: calc(100vh - var(--bsf-sfb-sticky-top, 80px) - 2rem);
	border-radius: 12px;
	overflow: hidden;
}

/* Tablet — switch to the tablet-specific height var. */
@media (min-width: 768px) and (max-width: 1024px) {
	.bsf-sfb__visual-sticky {
		height: var(--bsf-sfb-visual-h-tablet, auto);
	}
}
.bsf-sfb[data-enable-sticky="off"] .bsf-sfb__visual-sticky {
	position: relative;
	top: auto;
}
.bsf-sfb--vb .bsf-sfb__visual-sticky {
	position: relative !important;
	top: auto !important;
}

.bsf-sfb__default-visual,
.bsf-sfb__hover-visuals {
	position: absolute;
	inset: 0;
	transition: opacity 0.3s ease;
	pointer-events: none;
}
.bsf-sfb__default-visual { opacity: 1; }
.bsf-sfb__hover-visuals  { opacity: 0; }
.bsf-sfb--hovering .bsf-sfb__default-visual { opacity: 0; }
.bsf-sfb--hovering .bsf-sfb__hover-visuals  { opacity: 1; }

/* Default Visual Behavior — `hover-only` mode.  The default visual is
   suppressed at all times; only the hovered item's hover visual fades
   in while that item is hovered.  Useful when chaining multiple
   Sticky Feature Block modules — eliminates the jarring per-module
   "default image" snap between hovers. */
.bsf-sfb[data-default-visual="hover-only"] .bsf-sfb__default-visual {
	opacity: 0;
}
.bsf-sfb[data-default-visual="hover-only"].bsf-sfb--hovering .bsf-sfb__default-visual {
	opacity: 0;
}
.bsf-sfb[data-default-visual="hover-only"].bsf-sfb--hovering .bsf-sfb__hover-visuals {
	opacity: 1;
}

.bsf-sfb__default-visual img,
.bsf-sfb__hover-visuals img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* ─── Feature Item ────────────────────────────────────────────────────── */
.bsf-sfb-item-wrap {
	position: relative;
	display: block;
}
.bsf-sfb-item {
	position: relative;
}
.bsf-sfb-item__clickable {
	position: relative;
	display: flex;
	flex-direction: row;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: 1.5rem 0;
	cursor: pointer;
	transition: color 0.3s ease;
}
/* Mirrored layout — visual on left, so each item flips its arrow to
   the LEFT side of the heading.  `row-reverse` keeps the same DOM order
   (text first, arrow second in markup) and just inverts visual order. */
.bsf-sfb--visual-left .bsf-sfb-item__clickable {
	flex-direction: row-reverse;
}
.bsf-sfb-item__text {
	font-size: 1.25rem;
	font-weight: 500;
	line-height: 1.4;
	transition: color 0.3s ease;
	flex: 1;
}
.bsf-sfb-item__arrow {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: none;
	transition: transform 0.3s ease, color 0.3s ease;
	color: inherit;
}
/* Divi's icon element renders as a font glyph by default.  Default size
   so it's readable; authors can override via Arrow Icon → Decoration. */
.bsf-sfb-item__arrow .et-pb-icon,
.bsf-sfb-item__arrow [data-icon] {
	font-size: var(--bsf-sfb-arrow-size, 1.25rem);
	color: var(--bsf-sfb-arrow-color, inherit);
}
.bsf-sfb-item__border {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 1px;
	background-color: var(--bsf-sfb-item-border, rgba(0, 0, 0, 0.15));
	transition: background-color 0.3s ease;
}
/* Show Item Bottom Border toggle — driven by the parent's data attribute
   so toggling it in the VB updates instantly without re-rendering every
   child item. */
.bsf-sfb[data-show-border="off"] .bsf-sfb-item__border {
	display: none;
}
/* Full-row anchor that turns the entire item into a link target without
   nesting interactive elements (heading + arrow stay non-anchor). */
.bsf-sfb-item__link {
	position: absolute;
	inset: 0;
	z-index: 2;
	text-decoration: none;
	color: transparent;
}

/* Hover state — only on hover-capable pointer devices (skips touch). */
@media (hover: hover) and (pointer: fine) {
	.bsf-sfb-item-wrap:hover .bsf-sfb-item__text,
	.bsf-sfb-item-wrap:hover .bsf-sfb-item__arrow,
	.bsf-sfb-item-wrap:hover .bsf-sfb-item__arrow .et-pb-icon {
		color: var(--bsf-sfb-accent, #5B6CFF);
	}
	.bsf-sfb-item-wrap:hover .bsf-sfb-item__arrow {
		transform: translateX(6px);
	}
	/* Mirrored layout — arrow sits on the LEFT, so it should translate
	   LEFT on hover (away from the right-side text) to keep the
	   "moving away from the text" feel.  The `.bsf-sfb--visual-left`
	   ancestor selector gives this rule higher specificity than the
	   default above, so no !important needed. */
	.bsf-sfb--visual-left .bsf-sfb-item-wrap:hover .bsf-sfb-item__arrow {
		transform: translateX(-6px);
	}
	.bsf-sfb-item-wrap:hover .bsf-sfb-item__border {
		background-color: var(--bsf-sfb-accent, #5B6CFF);
	}
}

/* ─── Mobile ─────────────────────────────────────────────────────────────
 * Single column stack.  Sticky is meaningless on a phone — the visual just
 * sits below the items.  Per-item hover visuals are hidden because the
 * whole interaction model assumes hover, which doesn't exist on touch.
 */
@media (max-width: 767px) {
	.bsf-sfb,
	.bsf-sfb--3-column,
	.bsf-sfb--2-column,
	.bsf-sfb--visual-right,
	.bsf-sfb--visual-left {
		grid-template-columns: 1fr !important;
		grid-template-areas:
			"eyebrow"
			"links"
			"visual" !important;
	}
	.bsf-sfb__visual-sticky {
		position: static !important;
		aspect-ratio: 16 / 10;
		max-height: none;
		height: var(--bsf-sfb-visual-h-phone, auto);
	}
	.bsf-sfb__links-col {
		min-height: auto;
	}
	.bsf-sfb__hover-visuals {
		display: none;
	}
}

/* Reduced motion — instant swap, no transitions. */
@media (prefers-reduced-motion: reduce) {
	.bsf-sfb-item__arrow,
	.bsf-sfb-item__text,
	.bsf-sfb-item__border,
	.bsf-sfb__default-visual,
	.bsf-sfb__hover-visuals,
	.bsf-sfb-item__clickable {
		transition: none !important;
	}
}

/* ════════════════════════════════════════════════════════════════════════
 *  Sticky Feature Stack  (.bsf-sfs)
 *
 *  Wrapper module — renders ONE shared sticky visual that travels across
 *  every nested Sticky Feature Block child.  Each nested block's own
 *  visual column is suppressed (.bsf-sfs .bsf-sfb__visual-col display:
 *  none) so the wrapper's visual is the only one shown.  Hovering any
 *  item in any nested block fades the wrapper's visual to that item's
 *  Hover Visual via JS class toggling on the stack root.
 * ════════════════════════════════════════════════════════════════════════ */
.bsf-sfs {
	margin-bottom: var(--bsf-sfs-bottom-offset, 0px);
}

.bsf-sfs__inner {
	display: grid;
	gap: var(--bsf-sfs-col-gap, 32px);
	align-items: stretch;
}

.bsf-sfs--visual-right .bsf-sfs__inner {
	grid-template-columns:
		calc((100% - 2 * var(--bsf-sfs-col-gap, 32px)) * var(--bsf-sfs-content-cols, 6) / 12)
		calc((100% - 2 * var(--bsf-sfs-col-gap, 32px)) * var(--bsf-sfs-visual-offset, 1) / 12)
		calc((100% - 2 * var(--bsf-sfs-col-gap, 32px)) * var(--bsf-sfs-visual-cols, 5) / 12);
	grid-template-areas: "content . visual";
}
.bsf-sfs--visual-left .bsf-sfs__inner {
	grid-template-columns:
		calc((100% - 2 * var(--bsf-sfs-col-gap, 32px)) * var(--bsf-sfs-visual-cols, 5) / 12)
		calc((100% - 2 * var(--bsf-sfs-col-gap, 32px)) * var(--bsf-sfs-visual-offset, 1) / 12)
		calc((100% - 2 * var(--bsf-sfs-col-gap, 32px)) * var(--bsf-sfs-content-cols, 6) / 12);
	grid-template-areas: "visual . content";
}
/* Content column — flex column with gap between stacked Blocks.  4rem
   default.  Setting `margin-bottom: 0` on nested Blocks prevents
   double-spacing (Block's own Sticky Bottom Offset + Stack's gap). */
.bsf-sfs__content-col {
	grid-area: content;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 4rem;
}
.bsf-sfs .bsf-sfb {
	margin-bottom: 0;
}

/* Empty-state placeholder shown in the VB only (frontend never has an
   empty Stack — saved pages always have ≥1 child).  Dashed outline +
   centred copy gives authors a clear call-to-action. */
.bsf-sfs__empty {
	padding: 2.5rem 2rem;
	border: 2px dashed rgba(0, 0, 0, 0.18);
	border-radius: 8px;
	text-align: center;
	background: rgba(0, 0, 0, 0.02);
}
.bsf-sfs__visual-col  { grid-area: visual; }

.bsf-sfs__visual-sticky {
	position: sticky;
	top: var(--bsf-sfs-sticky-top, 80px);
	width: 100%;
	aspect-ratio: var(--bsf-sfs-visual-aspect, 4 / 5);
	height: var(--bsf-sfs-visual-h-desktop, auto);
	max-height: calc(100vh - var(--bsf-sfs-sticky-top, 80px) - 2rem);
	border-radius: 12px;
	overflow: hidden;
}
@media (min-width: 768px) and (max-width: 1024px) {
	.bsf-sfs__visual-sticky { height: var(--bsf-sfs-visual-h-tablet, auto); }
}
.bsf-sfs[data-enable-sticky="off"] .bsf-sfs__visual-sticky {
	position: relative;
	top: auto;
}
/* VB context — disable native sticky so the editor canvas stays scrollable. */
.bsf-sfs--vb .bsf-sfs__visual-sticky {
	position: relative !important;
	top: auto !important;
}

.bsf-sfs__default-visual,
.bsf-sfs__hover-visuals {
	position: absolute;
	inset: 0;
	transition: opacity 0.3s ease;
	pointer-events: none;
}
.bsf-sfs__default-visual { opacity: 1; }
.bsf-sfs__hover-visuals  { opacity: 0; }
.bsf-sfs--hovering .bsf-sfs__default-visual { opacity: 0; }
.bsf-sfs--hovering .bsf-sfs__hover-visuals  { opacity: 1; }
.bsf-sfs__default-visual img,
.bsf-sfs__hover-visuals img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* Default Visual Behavior — `hover-only` mode. */
.bsf-sfs[data-default-visual="hover-only"] .bsf-sfs__default-visual { opacity: 0; }
.bsf-sfs[data-default-visual="hover-only"].bsf-sfs--hovering .bsf-sfs__default-visual { opacity: 0; }
.bsf-sfs[data-default-visual="hover-only"].bsf-sfs--hovering .bsf-sfs__hover-visuals  { opacity: 1; }

/* ─── Nested Sticky Feature Block overrides ──────────────────────────────
 * When a Block lives inside a Stack, the Stack owns the sticky visual.
 * Hide each block's own visual column and collapse its 3- or 2-column
 * grid down to just eyebrow + links / links so the block content stacks
 * cleanly inside the Stack's content column. */
.bsf-sfs .bsf-sfb__visual-col {
	display: none;
}
.bsf-sfs .bsf-sfb--3-column.bsf-sfb--visual-right {
	grid-template-columns:
		calc((100% - 1 * var(--bsf-sfb-col-gap, 32px)) * var(--bsf-sfb-eyebrow-cols, 2) / 6)
		calc((100% - 1 * var(--bsf-sfb-col-gap, 32px)) * var(--bsf-sfb-links-cols, 4) / 6);
	grid-template-areas: "eyebrow links";
}
.bsf-sfs .bsf-sfb--3-column.bsf-sfb--visual-left {
	grid-template-columns:
		calc((100% - 1 * var(--bsf-sfb-col-gap, 32px)) * var(--bsf-sfb-links-cols, 4) / 6)
		calc((100% - 1 * var(--bsf-sfb-col-gap, 32px)) * var(--bsf-sfb-eyebrow-cols, 2) / 6);
	grid-template-areas: "links eyebrow";
}
.bsf-sfs .bsf-sfb--2-column.bsf-sfb--visual-right,
.bsf-sfs .bsf-sfb--2-column.bsf-sfb--visual-left {
	grid-template-columns: 1fr;
	grid-template-areas: "links";
}
.bsf-sfs .bsf-sfb__links-col {
	min-height: auto;
}

/* Mobile — Webflow-style per-block layout.
 *
 * On mobile each nested Block surfaces its OWN Default Visual full-width
 * above its content; the Stack's shared sticky visual is hidden because
 * each Block now carries its own image inline.  Per-item Hover Visuals
 * are hidden — no hover model on touch.
 *
 * The desktop nested-block override above (`display: none` on
 * `.bsf-sfs .bsf-sfb__visual-col`) is the rule we have to undo here, so
 * `display: block !important` is necessary.
 */
@media (max-width: 767px) {
	/* Stack collapses to a single column.  Shared visual area is
	   hidden — each nested Block surfaces its own image instead. */
	.bsf-sfs__inner {
		grid-template-columns: 1fr !important;
		grid-template-areas: "content" !important;
	}
	.bsf-sfs__visual-col {
		display: none !important;
	}

	/* Re-show each nested Block's own visual column (hidden on desktop
	   when the Stack's shared visual replaces it). */
	.bsf-sfs .bsf-sfb__visual-col {
		display: block !important;
	}

	/* Force each nested Block to a single-column stack with its visual
	   ABOVE eyebrow + items.  Override every layout-direction +
	   column-count combo since users may have set any of them at
	   desktop. */
	.bsf-sfs .bsf-sfb,
	.bsf-sfs .bsf-sfb--3-column,
	.bsf-sfs .bsf-sfb--2-column,
	.bsf-sfs .bsf-sfb--visual-right,
	.bsf-sfs .bsf-sfb--visual-left {
		grid-template-columns: 1fr !important;
		grid-template-areas:
			"visual"
			"eyebrow"
			"links" !important;
	}

	/* Each nested Block's visual: full-width, in-flow (no longer
	   sticky), with a reasonable mobile aspect ratio.  Drop desktop's
	   max-height cap and per-breakpoint heights so aspect-ratio drives
	   the height predictably regardless of what the author set.
	   `position: relative` (not `static`) is critical — the
	   `.bsf-sfb__default-visual` and `.bsf-sfb__hover-visuals` children
	   are `position: absolute; inset: 0`; with `static` they escape to
	   the nearest positioned ancestor and stretch to fill it (measured
	   896px tall, dominating the column).  Relative keeps the children
	   contained within the 16:10 box. */
	.bsf-sfs .bsf-sfb__visual-sticky {
		position: relative !important;
		width: 100% !important;
		height: auto !important;
		aspect-ratio: 16 / 10 !important;
		max-height: none !important;
		border-radius: 12px;
	}

	/* Hover visuals — hidden on mobile (no hover on touch). */
	.bsf-sfs .bsf-sfb__hover-visuals {
		display: none !important;
	}

	/* Free up the nested Block's links column from any min-height
	   constraints sized for desktop sticky travel. */
	.bsf-sfs .bsf-sfb__links-col {
		min-height: auto !important;
	}
}
