/**
 * Curved Reveal Hero
 *
 * Read the header comment in curved-reveal-hero.php first — it records
 * the measurements behind every default, taken frame by frame from the
 * reference recording.
 *
 * The base state here is the FINISHED state. Nothing is hidden by
 * default: the inline script in the PHP adds .cl-crh--armed before first
 * paint to set the pre-animation state, so a visitor with no JavaScript,
 * or with reduced motion, just gets the settled hero rather than an
 * invisible heading behind a coloured screen.
 */

/* Prevents scrolling the real page while the load-triggered timeline is
   still playing — the reference this element was measured from never
   moves the page during the reveal, and scrolling mid-reveal was
   breaking the animation's own geometry. Same class name and rule as
   Hero Intro Animation's identical scroll lock; toggled by
   lockScroll()/unlockScroll() in script.js, never for the scroll
   trigger (see that function for why). */
html.cl-crh-scroll-lock,
body.cl-crh-scroll-lock {
	overflow: hidden;
	height: 100%;
}

.cl-crh {
	position: relative;
	display: flex;
	min-height: var(--cl-crh-height, 100vh);
	padding: 0 5vw;
	overflow: hidden;
	background-color: var(--cl-crh-bg, #ebe8e7);
}

.cl-crh__inner {
	display: flex;
	width: 100%;
	flex-direction: column;
	align-items: center;
	justify-content: center;
}

.cl-crh__heading {
	margin: 0;
	color: var(--cl-crh-heading-color, #000);
	font-family: var(--cl-crh-heading-font-family, inherit);
	/**
	 * No font-size declared here — deliberately. Neither
	 * `var(--x, 13vw)` nor a bare `var(--x)` can express "let the heading
	 * tag's own size apply": an empty custom property resolves to empty and
	 * an unset one makes the declaration invalid, both of which reset
	 * font-size to `unset` and leave the heading inheriting its parent's
	 * size. The size, when set, is emitted as a scoped rule from the PHP.
	 */
	line-height: var(--cl-crh-heading-lh, 1);
	text-align: center;
	text-wrap: balance;
}

/* One block per hard line break in the setting. Not a clipping box. */
.cl-crh__block {
	display: block;
}

/**
 * Each VISUAL line is its own clipping box — this is the edge the
 * characters slide up through — and script.js inserts these once the
 * browser has decided where the text wraps. Clipping per hard break
 * instead only works for headings that never wrap: a character on the
 * first wrapped row of a two-row block has no clip edge beneath it, so it
 * isn't hidden at all, it just sits 0.85em lower and overlaps the row
 * below.
 *
 * The padding/negative-margin pair is not cosmetic: with line-height 1
 * the box bottom sits on the baseline, so overflow: hidden alone would
 * shear the descender off every g, y and p in the finished heading. The
 * padding gives the descender room inside the box and the matching
 * negative margin takes that room back out of the layout, so line
 * spacing is unaffected.
 */
.cl-crh__line {
	display: block;
	padding-bottom: 0.2em;
	margin-bottom: -0.2em;
	overflow: hidden;
}

/**
 * Words are inline-block with nowrap so that line breaking can only
 * happen at the real space text nodes between them. Without this
 * wrapper, a row of inline-block characters is a row of independent
 * inline-level boxes and the browser will break a word in half.
 */
.cl-crh__word {
	display: inline-block;
	white-space: nowrap;
}

.cl-crh__char {
	display: inline-block;
	white-space: pre;
	will-change: transform;
}

/**
 * The card that expands inside the heading. Its width is animated;
 * .cl-crh__media-inner keeps a FIXED width, so as the outer box narrows
 * the image is cropped equally on both sides rather than squashed —
 * which is what the reference does. A scaleX would distort it.
 */
.cl-crh__media {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	/* Size is the HEIGHT; aspect gives the width. */
	width: calc(var(--cl-crh-media-size, 0.82em) * var(--cl-crh-media-aspect, 1));
	height: var(--cl-crh-media-size, 0.82em);
	margin: 0 var(--cl-crh-media-gap, 18px);
	border-radius: var(--cl-crh-media-radius, 14px);
	overflow: hidden;
	vertical-align: middle;
	will-change: width;
}

.cl-crh__media-inner {
	width: calc(var(--cl-crh-media-size, 0.82em) * var(--cl-crh-media-aspect, 1));
	height: 100%;
	flex: 0 0 auto;
	object-fit: cover;
	display: block;
}

/**
 * Handoff placeholder. Holds the card's space open while the words close
 * over it, because going position: fixed takes the card out of the flow
 * instantly and the heading would otherwise snap shut in a single frame.
 */
.cl-crh__media-ghost {
	display: inline-block;
	width: calc(var(--cl-crh-media-size, 0.82em) * var(--cl-crh-media-aspect, 1));
	height: var(--cl-crh-media-size, 0.82em);
	margin: 0 var(--cl-crh-media-gap, 18px);
	vertical-align: middle;
}

/**
 * These states live on the CARD, not on the section.
 *
 * They were originally written as .cl-crh--flying .cl-crh__media, and
 * they could never match: the first thing detach() does is move the card
 * to <body> so it stops inheriting the fading heading's opacity, and from
 * that moment it is no longer a descendant of the section carrying the
 * class. The card kept its inline reveal width and got no stacking
 * context, so instead of flying it sat at its natural size with a tiny
 * image inside it. Any rule that has to apply DURING the flight has to be
 * scoped to the card itself.
 */
.cl-crh__media.is-flying {
	position: fixed;
	/* Out-ranks the site header it travels over. */
	z-index: 999998;
	margin: 0;
	overflow: hidden;
}

/* Fills the flying box rather than keeping the fixed reveal width, so the
   photo grows with the box instead of staying a small centred crop. */
.cl-crh__media.is-flying .cl-crh__media-inner {
	width: 100%;
	height: 100%;
}

/**
 * Docked: the card has landed and now lives INSIDE the receiving slot.
 *
 * It is the same element that flew, not a swap — an earlier version faded
 * this one out and revealed the slot's own copy underneath, which is what
 * produced the flicker and the visible change of photo on landing. There
 * is nothing to cross-fade if the thing that arrives is the thing that
 * travelled.
 */
.cl-crh__media.is-docked {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	margin: 0;
	border-radius: 0;
	overflow: hidden;
}

.cl-crh__media.is-docked .cl-crh__media-inner {
	width: 100%;
	height: 100%;
}

/**
 * The copy of the receiving element's heading, painted over the flying
 * card so the card reads as passing behind the text. Lives at body level
 * with a z-index above the card, which is the whole point — the real
 * heading's z-index is capped inside whatever stacking context its row
 * wrapper creates, so it can never win against a body-level element.
 * Position, type and colour are copied from the original each frame.
 */
.cl-crh-heading-echo {
	position: fixed;
	margin: 0;
	z-index: 999999;
	pointer-events: none;
	user-select: none;
}

.cl-crh__tagline {
	position: absolute;
	bottom: var(--cl-crh-tagline-bottom, 44px);
	left: 50%;
	max-width: var(--cl-crh-tagline-max-width, 360px);
	margin: 0;
	color: var(--cl-crh-tagline-color, #000);
	font-size: var(--cl-crh-tagline-size, 19px);
	line-height: 1.35;
	text-align: center;
	transform: translateX(-50%);
}

/**
 * The curtain. Created by script.js and appended to <body>, not to the
 * section, because on the page-load trigger it has to cover the site
 * header too — nesting it in the section would leave it trapped inside
 * the section's own overflow: hidden and stacking context.
 *
 * It's an SVG rather than a clip-path on a div: the boundary is a
 * circular arc that has to be recomputed every frame, and clip-path:
 * path() would mean re-parsing a fresh path string through the CSS
 * parser 60 times a second, with weaker browser support behind it.
 */
.cl-crh-curtain {
	position: fixed;
	inset: 0;
	width: 100%;
	height: 100%;
	z-index: 999999;
	/* Never intercepts a click, even mid-animation. */
	pointer-events: none;
}

.cl-crh-curtain--scoped {
	position: absolute;
	z-index: 5;
}

/**
 * -----------------------------------------------------------------
 * Armed (pre-animation) state — added by the inline script in the PHP
 * -----------------------------------------------------------------
 */
/**
 * Until the clipping boxes exist, an offset character is a VISIBLE offset
 * character — so the heading is hidden outright and only revealed once
 * script.js has split it into visual lines. Doing it the other way round
 * shows one frame of overlapping text on the scroll trigger, where no
 * curtain is covering the section yet.
 */
.cl-crh--armed .cl-crh__heading {
	visibility: hidden;
}

.cl-crh--armed.cl-crh--split .cl-crh__heading {
	visibility: visible;
}

.cl-crh--armed .cl-crh__char {
	transform: translateY(var(--cl-crh-char-travel, 0.85em));
}

/**
 * The armed pre-state, at every width: the card starts closed, because the
 * width reveal is what runs above 600px and is the element's original
 * behaviour. The phone reveals override this inside the media query — the
 * pre-state has to match whichever animation is about to run, or the card
 * jumps on the first frame.
 */
.cl-crh--armed .cl-crh__media {
	width: 0;
	/* Not zero: a closed card still has to read as the word space it
	   stands in for, and it has to do so symmetrically. */
	margin-left: var(--cl-crh-media-collapsed-gap, 0.13em);
	margin-right: var(--cl-crh-media-collapsed-gap, 0.13em);
}

/**
 * Small screens.
 *
 * The heading reveal is per-character out of per-visual-line clip boxes, so
 * the only thing that has to change is the type: everything else is measured
 * at runtime and follows whatever the text does. What does NOT survive is
 * 5vw side padding — at 390px that is 19px a side, and with a large heading
 * the words break awkwardly, so the padding goes up as the viewport comes
 * down.
 *
 * The inline media card is sized in em, so it scales with the heading on its
 * own — that was the right call and needs nothing here.
 */
@media (max-width: 900px) {
	.cl-crh__inner { padding: 0 6vw; }
}

@media (max-width: 600px) {
	/**
	 * Phone-only armed states. "Card reveal" and "Tagline reveal" are
	 * phone settings (script.js ignores them above 600px), so their
	 * pre-states live here too — otherwise a fade-up card would still
	 * start at width 0 and snap open on the first frame.
	 */
	.cl-crh--armed[data-media-reveal="fadeup"] .cl-crh__media,
	.cl-crh--armed[data-media-reveal="fade"] .cl-crh__media,
	.cl-crh--armed[data-media-reveal="none"] .cl-crh__media {
		width: calc(var(--cl-crh-media-size, 0.82em) * var(--cl-crh-media-aspect, 1));
		margin-left: var(--cl-crh-media-gap, 18px);
		margin-right: var(--cl-crh-media-gap, 18px);
	}

	.cl-crh--armed[data-media-reveal="fadeup"] .cl-crh__media,
	.cl-crh--armed[data-media-reveal="fade"] .cl-crh__media {
		opacity: 0;
	}

	.cl-crh--armed[data-tagline-reveal="1"] .cl-crh__tagline {
		opacity: 0;
	}

	/* 100svh minus whatever sits above the section (notification bar,
	   site header) — script.js measures that and sets the property; it
	   is 0 for a hero that is not at the top of the page. Without it the
	   section runs off the bottom of the screen by exactly that amount
	   and its centred contents sit half of it too low. */
	.cl-crh {
		min-height: 100vh;
		min-height: calc(100svh - var(--cl-crh-fill-offset, 0px));
	}

	.cl-crh__inner { padding: 0 22px; }

	/* The curtain is a full-viewport fixed SVG, and on iOS Safari 100vh is
	   the URL-bar-hidden height — so it would leave a strip of page visible
	   beneath itself at rest. */
	.cl-crh-curtain { height: 100vh; height: 100svh; }

	.cl-crh__tagline { bottom: 28px; padding: 0 22px; }

	/**
	 * "Own row under the heading". script.js moves the card into
	 * .cl-crh__media-slot placed directly after the <h1>, in the
	 * centring flex column — it cannot stay in the sentence, because a
	 * block-level card inside the text breaks the line at that word and
	 * puts the card between "we" and "provide" rather than under the
	 * whole phrase.
	 *
	 * The slot carries the size (the card fills it), so the card's own
	 * em sizing and the animated side margins are both overridden. See
	 * drawMedia: once relocated the reveal is a scaleX wipe, not an
	 * animated width.
	 */

	/**
	 * The tagline is absolutely positioned against the section's bottom
	 * edge by default. On a phone the card is large enough that the
	 * centred block reaches down to meet it, so it was overlapping the
	 * card and sitting under the browser's own toolbar. In flow it is
	 * simply the last item in the centred column and can never collide.
	 */
	.cl-crh--m-centred .cl-crh__tagline {
		position: static;
		transform: none;
		margin-top: 1.1em;
		max-width: 100%;
	}

	.cl-crh--media-below-m.cl-crh--relocated .cl-crh__media-slot {
		width: calc(var(--cl-crh-media-size, 44vw) * var(--cl-crh-media-aspect, 1));
		max-width: 100%;
		height: var(--cl-crh-media-size, 44vw);
		margin: 0.5em auto 0;
	}

	/**
	 * "Inline, scaled to the text". The card stays a word in the
	 * sentence and stays em-sized — it is simply a larger multiple of
	 * the heading, emitted as --cl-crh-media-size by PHP. Being taller
	 * than the strut, it sets the height of its own line box, which is
	 * what the line's clipping edge is measured from, so the character
	 * reveal keeps working with no changes.
	 */
	.cl-crh--media-inline_scaled-m .cl-crh__media {
		vertical-align: middle;
	}

	.cl-crh--media-hidden-m .cl-crh__media,
	.cl-crh--media-hidden-m .cl-crh__media-ghost {
		display: none;
	}

	/**
	 * Shared by both placements that take the card out of the sentence:
	 * the floor layout, and "own row under the heading".
	 *
	 * script.js moves the node into .cl-crh__media-slot — it cannot stay
	 * in the heading, because every visual line is a clipping box and
	 * would crop it. Once relocated, the card's size belongs to the slot,
	 * so the em sizing and the animated side margins are both overridden
	 * here, and the reveal becomes a scaleX wipe (see drawMedia).
	 *
	 * Positioning is NOT set here: the floor slot is absolute against the
	 * section, while the own-row slot stays in normal flow after the
	 * heading. Putting position: absolute on the shared rule took the
	 * own-row slot out of the flex column and stacked it over the type.
	 */
	.cl-crh--relocated .cl-crh__media-slot {
		z-index: 0;
	}

	.cl-crh--relocated .cl-crh__media {
		display: block;
		width: 100% !important;
		height: 100% !important;
		margin: 0 !important;
		/* Never scaleX — see drawMedia. A scaleX squashes the picture and
		   lets it stretch back out as it opens, which is exactly what
		   .cl-crh__media's own comment warns against for the inline
		   reveal. Fade up moves it; the wipe clips it. Neither distorts. */
		will-change: transform, opacity, clip-path;
	}

	.cl-crh--relocated .cl-crh__media-inner {
		width: 100%;
		height: 100%;
	}

	.cl-crh--relocated .cl-crh__inner {
		position: relative;
		z-index: 1;
	}

	/**
	 * Floor — type block at the top, card a fixed distance beneath it.
	 *
	 * The card is the last item in the text column, not an absolutely
	 * positioned box on the section's bottom edge. Anchored to the floor,
	 * the gap under the tagline was whatever the section had left over,
	 * so it changed with the heading's wrap and with the screen height.
	 */
	.cl-crh--m-floor .cl-crh__inner {
		justify-content: flex-start;
		padding-top: 52px;
		padding-bottom: 40px;
	}

	.cl-crh--m-floor.cl-crh--relocated .cl-crh__media-slot {
		width: auto;
		align-self: stretch;
		margin: var(--cl-crh-m-card-gap, 50px) var(--cl-crh-m-card-inset, 0px) 0;
		height: var(--cl-crh-m-card-h, 44vh);
	}

	.cl-crh--m-floor.cl-crh--relocated .cl-crh__media {
		border-radius: var(--cl-crh-media-radius, 14px);
	}

	/* The tagline is absolutely positioned against the section's bottom
	   edge by default, which under this layout is where the card now is. */
	.cl-crh--m-floor .cl-crh__tagline {
		position: static;
		transform: none;
		margin-top: 14px;
		max-width: 100%;
	}

}

@media (prefers-reduced-motion: reduce) {
	.cl-crh--armed .cl-crh__heading {
		visibility: visible;
	}

	.cl-crh__char,
	.cl-crh--armed .cl-crh__char {
		transform: none;
	}

	.cl-crh__media,
	.cl-crh--armed .cl-crh__media {
		width: var(--cl-crh-media-size, 160px);
		margin: 0 var(--cl-crh-media-gap, 18px);
	}

	.cl-crh-curtain {
		display: none;
	}
}


/* ------------------------------------------------------------------ */
/* No image or video set (18 Sep 2026)                                 */
/* ------------------------------------------------------------------ */
/*
 * The section is a full-screen hero because it has to hold the media that
 * flies into it. With the media removed there is nothing to hold, so it stood
 * a whole screen tall around a heading and a tagline — 744px around ~190px of
 * content on a 390x844 phone, and 1024px with the tagline stranded 373px
 * below the heading on a tablet. Size it to its content on both instead.
 * Desktop keeps the full-height hero, where the space still reads as one.
 */
@media (max-width: 999px) {
	.cl-crh.cl-crh--no-media {
		min-height: 0;
	}

	/* Anchored to the bottom of the section when there is a full screen to
	   anchor to; with the section now only as tall as its text, it follows
	   the heading instead. */
	.cl-crh--no-media .cl-crh__tagline {
		position: static;
		transform: none;
		margin-top: 14px;
		max-width: 100%;
	}
}

/* Phones: the inner box already carries 52px above the heading and 40px below
   the tagline, so 16px here evens the two ends up at ~56px. */
@media (max-width: 690px) {
	.cl-crh.cl-crh--no-media {
		padding-bottom: 16px;
	}
}

/* Tablets: the inner box has no padding of its own at this width, and the
   section starts at the very top of the page — with the full-screen height
   gone, the heading sat behind the fixed 135px header. This is the whole of
   the section's spacing here. */
@media (min-width: 691px) and (max-width: 999px) {
	.cl-crh.cl-crh--no-media {
		padding-top: 176px;
		padding-bottom: 56px;
	}
}
