.cl-sfh {
	position: relative;
	background-color: var(--cl-sfh-bg, #f5f3f1);
	overflow: hidden;
}

/* ---------------------------------------------------------------------
 * Intro (load-in) — heading, gallery
 * ---------------------------------------------------------------------
 * Normal in-flow content. Plays a one-time reveal automatically (not
 * scroll-tied) when the page loads, then just scrolls away normally
 * like any other hero content — it is NOT part of the sticky/pinned
 * phase below.
 *
 * The gallery is full-width edge-to-edge (confirmed from the reference
 * recording: 5 equal-width columns spanning the entire viewport width
 * with tight gaps, not a smaller centred cluster), so only the heading
 * sits inside a padded, centred text column — the gallery
 * itself breaks out to the section's full width.
 */
.cl-sfh__intro {
	position: relative;
	z-index: 1;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 32px;
	padding-top: 100px;
	padding-bottom: 90px;
}

.cl-sfh__heading {
	margin: 0;
	max-width: var(--cl-sfh-heading-max-width, 900px);
	padding: 0 24px;
	/**
	 * No font-size here at all — deliberately.
	 *
	 * It cannot be `var(--x, 64px)`: with the setting cleared, the property
	 * is emitted empty and var() resolves to empty, not to the fallback.
	 * It cannot be a bare `var(--x)` either: an unset custom property makes
	 * the declaration invalid at computed-value time, which resets
	 * font-size to `unset` — and font-size is inherited, so the heading
	 * takes its PARENT's size rather than the theme's h1. Both were
	 * measured; both gave a 17px heading.
	 *
	 * Only the complete absence of a font-size declaration lets the chosen
	 * heading tag's own rule apply. The size, when set, is emitted as a
	 * scoped rule from the PHP instead.
	 */
	font-family: var(--cl-sfh-heading-font-family, inherit);
	font-weight: 500;
	line-height: 1.1;
	text-align: center;
	color: var(--cl-sfh-heading-color, #1a1814);
	opacity: 0;
	transform: translateY(20px);
	transition: opacity 0.7s ease, transform 0.7s ease;
}

.cl-sfh__heading.is-revealed {
	opacity: 1 !important;
	transform: translateY(0) !important;
}

.cl-sfh__heading em {
	font-style: italic;
}

/* Full section width, equal-width columns — grid (rather than a fixed
   item width in a centred flex row) is what makes N images always
   span edge-to-edge evenly regardless of how many there are.
   translateX here is the load-in's step 2 ("cards scroll/move right"),
   applied to the whole row at once. */
.cl-sfh__gallery {
	display: grid;
	grid-template-columns: repeat(var(--cl-sfh-gallery-count, 5), 1fr);
	gap: var(--cl-sfh-gallery-gap, 12px);
	width: 100%;
	padding: 0 var(--cl-sfh-gallery-gap, 12px);
	opacity: 0;
	/* margin-left, not transform: translateX() — a transform on this
	   ancestor (even the resting translateX(0) it would end up at)
	   creates a new containing block for any position: fixed
	   descendant. Once script.js promotes the video tile to position:
	   fixed for the grow effect, its top/left would then resolve
	   relative to THIS element's box instead of the real viewport,
	   throwing it to the wrong place entirely. margin-left achieves
	   the same visual shift with no such side effect. */
	/* The load-in slide is expressed as two custom properties rather than
	   two literal margin values, because the mobile track needs its own
	   resting offset (see the max-width: 600px block) and the .is-shifted
	   rule below carries !important — a media query cannot outrank that,
	   so a literal `margin-left: 0` there wiped the centring out the
	   moment the load-in finished and left the 190% track flush left,
	   running off the right-hand edge. Overriding the PROPERTY instead
	   works at any width, and keeps the 40px slide-in on mobile too. */
	margin-left: var(--cl-sfh-track-start, -40px);
	transition: opacity 0.6s ease, margin-left 0.9s ease;
}

.cl-sfh__gallery.is-visible {
	opacity: 1 !important;
}

.cl-sfh__gallery.is-shifted {
	margin-left: var(--cl-sfh-track-rest, 0px) !important;
}

.cl-sfh__gallery-item {
	position: relative;
	width: 100%;
	min-width: 0;
	height: var(--cl-sfh-gallery-h, 260px);
	border-radius: var(--cl-sfh-gallery-radius, 12px);
	overflow: hidden;
	background: #00000010;
	transform: translateY(0);
	transition: transform 0.6s ease;
}

/* Step 3 of the load-in: the two items adjacent to centre drop down
   slightly, and the centre item drops down further still — confirmed
   from the reference as happening at the same time as the heading
   reveal, not before/after it. Distance-from-centre is computed in PHP
   per item so this still makes sense with a gallery count other than
   five (only the centre and its immediate neighbours move at all). */
.cl-sfh__gallery-item.is-arced.cl-sfh__gallery-item--offset-center {
	transform: translateY(var(--cl-sfh-arc-center, 28px));
}

.cl-sfh__gallery-item.is-arced.cl-sfh__gallery-item--offset-near {
	transform: translateY(var(--cl-sfh-arc-near, 14px));
}

.cl-sfh__gallery-item img,
.cl-sfh__gallery-item .cl-sfh__thumb-video {
	display: block;
	width: 100% !important;
	height: 100% !important;
	object-fit: cover !important;
}

/* ---------------------------------------------------------------------
 * Scroll-driven phase — the video tile grows, phrase 1 with it, then
 * phrases 2/3 stage in once it's full-screen
 * ---------------------------------------------------------------------
 * .cl-sfh__stage IS the intro's own wrapper — once its top reaches the
 * viewport top, script.js pins it in place (position: fixed) so the
 * whole gallery stays exactly where it already was on screen, then
 * promotes the one real video tile (not a separate decoupled clone)
 * to grow from its own measured position to full-screen. Nothing
 * "swaps" to a different element and nothing jumps, because it's
 * always the same DOM node throughout.
 */
.cl-sfh__spacer {
	position: relative;
	/* Real height set by script.js: grow range + stage range + 100vh,
	   matching the video-hero-overlay spacer pattern exactly. */
}

/* The same height from the first paint. Until script.js ran, the spacer
   was only as tall as its content (~690px on a phone), then jumped to
   ~2,000px and pushed everything below it down: a 0.17 layout shift
   on Medical cannabis. The inline height script.js writes still wins
   over this; it's only what applies before that. svh for the same
   reason as the stage rules below (clVh() is the URL-bar-visible
   height). Not applied when the scroll effects are off or motion is
   reduced, because the spacer stays content-height then. */
.cl-sfh[data-scroll-effects="1"] .cl-sfh__spacer {
	height: calc(var(--cl-sfh-flip-scroll-range, 0px) + var(--cl-sfh-phrase-scroll-range, 0px) + 100vh - var(--cl-sfh-bottom-spacing, 0px));
	height: calc(var(--cl-sfh-flip-scroll-range, 0px) + var(--cl-sfh-phrase-scroll-range, 0px) + 100svh - var(--cl-sfh-bottom-spacing, 0px));
}

@media (prefers-reduced-motion: reduce) {
	.cl-sfh[data-scroll-effects="1"] .cl-sfh__spacer {
		height: auto;
	}
}

.cl-sfh__stage {
	position: relative;
	width: 100%;
}

/* svh, not vh, with a vh line first as the fallback. On iOS Safari
   100vh means the viewport with the URL bar HIDDEN, so a 100vh box is
   taller than what is actually on screen at rest — and script.js sizes
   the same box from clVh(), which is innerHeight, i.e. the bar-VISIBLE
   height. The two disagreeing by 60-90px is what put a band of
   background under the video and clipped its bottom edge through
   overflow: hidden. (script.js writes an inline height over both of
   these while pinned/released; these values are what applies in the
   frames before it does, and they must not disagree with it.) */
.cl-sfh__stage.cl-sfh--pinned {
	height: 100vh;
	height: 100svh;
	overflow: hidden;
	background-color: var(--cl-sfh-bg, #f5f3f1);
}

.cl-sfh__stage.cl-sfh--released {
	height: 100vh;
	height: 100svh;
	overflow: hidden;
	background-color: var(--cl-sfh-bg, #f5f3f1);
}

/* Heading and every gallery tile EXCEPT the video one fade out as the
   video tile grows — script.js sets their opacity directly every
   frame, tied to scroll progress (1 - flipProgress), rather than a
   CSS transition playing out on its own timer once triggered. */

.cl-sfh__gallery-item--video {
	z-index: 5;
	will-change: width, height, top, left, border-radius;
}

.cl-sfh__tile-overlay {
	position: absolute;
	inset: 0;
	z-index: 1;
	background-color: var(--cl-sfh-overlay-color, rgba(26, 24, 20, 1));
	opacity: 0;
	pointer-events: none;
	will-change: opacity;
}

/* Phrase 1 used to live inside the growing tile itself (inset: 0 +
   flex-centering), on the theory that centering-within-the-tile would
   always look centered. It doesn't: the tile isn't necessarily
   horizontally centered while mid-grow (that depends on which column
   the video happens to be in, and how the grow interpolates from that
   starting position toward the true centre) — so the text visibly
   drifted as the tile's own bounds shifted. It now sits in the same
   viewport-centred .cl-sfh__phrases overlay as phrases 2/3 below,
   tied to the true centre of the screen throughout, regardless of
   where the tile itself currently is. */
.cl-sfh__phrase--first {
	will-change: opacity, transform;
}

/* All three phrases sit in one viewport-centred overlay — tied to the
   true centre of the screen throughout, not to the growing tile's own
   (not necessarily centred mid-grow) bounds. */
.cl-sfh__phrases {
	position: absolute;
	inset: 0;
	z-index: 6;
	display: flex;
	align-items: center;
	justify-content: center;
	pointer-events: none;
}

.cl-sfh__phrase {
	position: absolute;
	margin: 0;
	max-width: var(--cl-sfh-phrase-max-width, 760px);
	padding: 0 32px;
	font-size: var(--cl-sfh-phrase-size, 40px);
	line-height: 1.3;
	text-align: center;
	color: var(--cl-sfh-phrase-color, #f5f3f1);
	opacity: 0;
	will-change: opacity;
}

/* Phrase 3 and its button, centred as one block. inset:0 + flex rather
   than relying on the parent flex container's alignment of an
   absolutely-positioned child, so the pair is centred the same way
   regardless of how tall it gets. */
.cl-sfh__phrase-group {
	position: absolute;
	inset: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--cl-sfh-button-gap, 32px);
}

/* Inside the group the phrase is a normal flow item — it's the group
   that's positioned now, not the phrase. */
.cl-sfh__phrase-group .cl-sfh__phrase {
	position: static;
}

/* Selector is doubled up (.cl-sfh .cl-sfh__button) and the visual
   properties are forced, because Salient styles anchors and
   button-shaped anchors aggressively through long compound selectors
   that outrank a single class — the same specificity trap that made the
   accordion's collapsed-strip height and the row slider's flex rules
   silently do nothing. Anything the theme is likely to have an opinion
   about (colour, background, font, padding, radius) is defended here;
   layout properties it has no rule for are left plain. */
.cl-sfh .cl-sfh__button {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	padding: 12px 24px !important;
	border-radius: 8px !important;
	background-color: var(--cl-sfh-button-bg, #f5f3f1) !important;
	color: var(--cl-sfh-button-text, #1a1814) !important;
	text-decoration: none !important;
	font-size: var(--cl-sfh-button-size, 16px) !important;
	font-weight: 400 !important;
	line-height: 1;
	opacity: 0;
	/* The phrases overlay is pointer-events: none so it never eats
	   clicks meant for the video's own play/pause toggle underneath.
	   The button has to opt back in — and script.js withdraws it again
	   whenever the button is faded out, so an invisible button is never
	   clickable. */
	pointer-events: auto;
	transition: filter 0.2s ease;
	will-change: opacity, transform;
}

/* Hover can't use opacity: script.js writes an inline opacity with
   !important every frame to drive the reveal, so no stylesheet rule can
   ever win against it. filter is untouched by the reveal, so it's free
   to carry the hover state. */
.cl-sfh .cl-sfh__button:hover,
.cl-sfh .cl-sfh__button:focus-visible {
	filter: brightness(0.92);
}

/* Arc reveal pieces (letter/word styles only — the fade style leaves
   the phrase as a single unsplit text node).

   Each WORD is its own inline-block with nowrap, and real space text
   nodes sit between words: that keeps line breaking at word boundaries
   only. Making every letter a bare inline-block instead would let the
   browser wrap mid-word, since each one becomes an independent
   inline-level box. white-space: pre on the pieces themselves keeps any
   space that ends up inside a piece from collapsing away. */
.cl-sfh__phrase-word {
	display: inline-block;
	white-space: nowrap;
}

.cl-sfh__phrase-piece {
	display: inline-block;
	white-space: pre;
	transform-origin: 50% 55%;
	will-change: opacity, transform;
}

.cl-sfh__gallery-item .cl-sfh__thumb-toggle {
	position: absolute;
	bottom: 10px;
	right: 10px;
	z-index: 7;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 28px;
	height: 28px;
	border-radius: 50%;
	border: none;
	background: rgba(0, 0, 0, 0.45);
	color: #fff;
	cursor: pointer;
	padding: 0;
}

.cl-sfh__gallery-item .cl-sfh__thumb-toggle svg {
	width: 12px;
	height: 12px;
}

.cl-sfh__icon-pause {
	display: none;
}

.cl-sfh__thumb-toggle.is-playing .cl-sfh__icon-play {
	display: none;
}

.cl-sfh__thumb-toggle.is-playing .cl-sfh__icon-pause {
	display: block;
}

/* ---------------------------------------------------------------------
 * Settled fallback — reduced motion, or scroll effects disabled
 * ---------------------------------------------------------------------
 */
.cl-sfh.cl-sfh--settled .cl-sfh__stage {
	position: relative;
	height: auto;
	min-height: 100vh;
	min-height: 100svh;
}

.cl-sfh.cl-sfh--settled .cl-sfh__heading,
.cl-sfh.cl-sfh--settled .cl-sfh__gallery-item:not(.cl-sfh__gallery-item--video) {
	display: none;
}

/* Settled fallback shows the sequence's END state, which means the last
   phrase and its button — not nothing. Hiding the overlay wholesale (as
   this did) would mean a real, clickable CTA never rendered at all for
   anyone with reduced motion or scroll effects switched off. */
.cl-sfh.cl-sfh--settled .cl-sfh__phrase--first,
.cl-sfh.cl-sfh--settled .cl-sfh__phrase[data-phrase="2"] {
	display: none;
}

.cl-sfh.cl-sfh--settled .cl-sfh__phrase[data-phrase="3"],
.cl-sfh.cl-sfh--settled .cl-sfh__button {
	opacity: 1 !important;
}

.cl-sfh.cl-sfh--settled .cl-sfh__gallery {
	display: block;
}

.cl-sfh.cl-sfh--settled .cl-sfh__gallery-item--video {
	position: relative;
	width: auto;
	height: 100vh;
	height: 100svh;
	margin: 0 var(--cl-sfh-h-padding, 0px);
	border-radius: var(--cl-sfh-expanded-radius, 0px);
}

.cl-sfh.cl-sfh--settled .cl-sfh__tile-overlay {
	opacity: var(--cl-sfh-overlay-target-opacity, 0.55);
}

@media (prefers-reduced-motion: reduce) {
	.cl-sfh__heading,
	.cl-sfh__gallery-item,
	.cl-sfh__tile-overlay,
	.cl-sfh__phrase {
		transition: none !important;
	}
}

@media (max-width: 600px) {
	.cl-sfh__intro {
		padding: 80px 20px 60px;
		gap: 20px;
	}

	.cl-sfh__gallery-item .cl-sfh__thumb-toggle {
		bottom: 8px;
		right: 8px;
	}

	/**
	 * Tiles overflow the screen instead of being squeezed into it.
	 *
	 * Five equal columns across a phone gives roughly 68px each, which is
	 * narrower than it is tall by a long way and reads as a row of slivers.
	 * The track is widened past the viewport and pulled left by half the
	 * excess, so the middle tiles are centred at a usable size and the outer
	 * two run off both edges — the composition is the same, it is simply
	 * cropped by the screen rather than compressed into it.
	 *
	 * Width and margin are set here rather than by changing the column count
	 * because nothing has to decide WHICH tiles to drop: the video tile that
	 * script.js promotes and grows could be any of them, and hiding it would
	 * break the whole scroll phase.
	 *
	 * margin-left, not transform — see the note on .cl-sfh__gallery above:
	 * a transform on this element would become the containing block for the
	 * position: fixed video tile and throw it across the screen.
	 */
	/**
	 * Tiles overflow the screen instead of being squeezed into it.
	 *
	 * Five equal columns across a phone gives roughly 68px each, which is
	 * narrower than it is tall by a long way and reads as a row of slivers.
	 * So the row is built to a real tile width and then slid left until the
	 * MIDDLE tile lands on the centre of the screen — the outer tiles run
	 * off both edges and the composition is cropped by the screen rather
	 * than compressed into it.
	 *
	 * The tile width is a vw value, not a percentage of this element's
	 * container. A percentage here resolves against .cl-sfh__intro's
	 * content box, which is the screen minus its own side padding — so the
	 * same setting produced a different tile depending on the padding, and
	 * the number that produced the right result also changed with the image
	 * count. Against vw it is simply "this much of the screen", and the
	 * total comes out of the count instead of being entered by hand.
	 *
	 * width: max-content lets the row be exactly its columns plus gaps.
	 * The lead count (tiles before the middle) comes from PHP because
	 * calc() has no floor().
	 *
	 * margin-left, not transform — see the note on .cl-sfh__gallery above:
	 * a transform on this element would become the containing block for the
	 * position: fixed video tile and throw it across the screen.
	 */
	.cl-sfh__gallery {
		--cl-sfh-tile: var(--cl-sfh-mobile-tile, 65vw);
		--cl-sfh-step: calc(var(--cl-sfh-tile) + var(--cl-sfh-gallery-gap, 8px));

		/* The intro is a flex column with align-items: center, and cross-axis
		   centring positions this item's MARGIN BOX — so it composes with the
		   margin below and the row lands half its overflow further left than
		   the margin alone says (measured: exactly overflow/2). Opting out
		   here makes the margin the only thing positioning the row, so the
		   maths below is the whole story. */
		align-self: flex-start;

		grid-template-columns: repeat(var(--cl-sfh-gallery-count, 5), var(--cl-sfh-tile));
		width: max-content;
		padding: 0;

		/* 50% resolves against the container, whose centre IS the screen's
		   centre — the intro's side padding is symmetrical. Both offsets as
		   properties, so .is-shifted's !important resolves to the centred
		   value instead of zero. */
		--cl-sfh-track-rest: calc(50% - (var(--cl-sfh-tile) / 2) - (var(--cl-sfh-mobile-lead, 2) * var(--cl-sfh-step)));
		--cl-sfh-track-start: calc(50% - (var(--cl-sfh-tile) / 2) - (var(--cl-sfh-mobile-lead, 2) * var(--cl-sfh-step)) - 40px);
	}
}
