/*
 * Vitalforce Core, component layer.
 *
 * The group bar and divisions accordion live here. This is the one component
 * that is identical on all five division sites, so it is the first thing built
 * and the thing most worth getting exactly right.
 */

/* ---------------------------------------------------------------------------
 * Supplied marks
 *
 * Icons the layouts draw rather than describe. They are here as masks rather
 * than as images so they take the colour of whatever they sit on and fade with
 * it, which is what the faded lists on White Label need. The check is the
 * designer's `checkmark.svg`, reduced to its one path on a 32 x 28 box: the
 * export carried the whole artboard and a vertical gradient of its own, and
 * the gradient would have fought the fade the list already has.
 * ------------------------------------------------------------------------ */
:root {
	--vf-icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 28'%3E%3Cpath fill='%23000' d='M31.77,3.13 L14.09,25.13 L12.29,27.38 L10.32,25.28 L0,14.28 L3.65,10.86 L12,19.76 L27.87,0 Z'/%3E%3C/svg%3E");
}

/* ---------------------------------------------------------------------------
 * Group bar
 * ------------------------------------------------------------------------ */

/*
 * Below the page, deliberately.
 *
 * The divisions panel lives inside this bar, and it used to sit at z-index 20
 * against a page at 1, so opening it painted the whole stack of division cards
 * over the hero and over the site header: the logo and the navigation
 * disappeared behind a colour block. The panel belongs *under* the page. It
 * opens in flow, so it still pushes everything down and is fully visible; the
 * hero then rides up over the last card by the tuck amount and covers its tail,
 * which is what fills the two white corners under Fulfillment.
 */
.vf-groupbar {
	position: relative;
	z-index: 0;
	background: var(--vf-color-surface);
}

.vf-groupbar__inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--vf-space-3);
	width: 100%;
	max-width: calc(var(--vf-layout-container) + (var(--vf-layout-gutter) * 2));
	margin-inline: auto;
	padding: 0 var(--vf-layout-gutter);
	min-height: var(--vf-layout-topbar-h);
}

@media (max-width: 1280px) {
	.vf-groupbar__inner {
		padding-inline: var(--vf-layout-gutter-lg);
	}
}

@media (max-width: 1024px) {
	.vf-groupbar__inner {
		padding-inline: var(--vf-layout-gutter-md);
		min-height: calc(var(--vf-layout-topbar-h) * 0.72);
	}
}

@media (max-width: 768px) {
	.vf-groupbar__inner {
		padding-inline: var(--vf-layout-gutter-sm);
	}
}

.vf-groupbar__group {
	display: inline-flex;
	align-items: center;
	gap: var(--vf-space-1);
	color: var(--vf-color-ink-500);
	font-family: var(--vf-font-display);
	font-size: var(--vf-size-body-l);
	text-decoration: none;
	white-space: nowrap;
}

.vf-groupbar__groupname {
	color: var(--vf-color-ink-700);
}

/*
 * The group mark. The one two-tone mark in the system, and the one mark that is
 * the same on all five division sites, so it is a theme asset rather than a per
 * site upload.
 */
.vf-groupbar__mark {
	width: 1.1em;
	height: 1.1em;
	object-fit: contain;
	display: block;
}

.vf-groupbar__toggle {
	display: inline-flex;
	align-items: center;
	gap: var(--vf-space-2);
	padding: var(--vf-space-1) 0;
	background: none;
	border: 0;
	cursor: pointer;
	color: var(--vf-color-ink-700);
	font-family: var(--vf-font-display);
	font-size: var(--vf-size-body-m);
	line-height: 1;
}

.vf-groupbar__toggleicon {
	display: inline-flex;
	width: 32px;
	height: 32px;
	flex: 0 0 32px;
	color: var(--vf-color-ink-700);
}

.vf-groupbar__toggleicon svg {
	width: 100%;
	height: 100%;
}

/* The plus becomes a minus when the panel is open. */
.vf-groupbar__plus-v {
	transform-origin: center;
	transition: transform var(--vf-motion-base) var(--vf-motion-ease),
		opacity var(--vf-motion-base) var(--vf-motion-ease);
}

.vf-groupbar__toggle[aria-expanded="true"] .vf-groupbar__plus-v {
	transform: rotate(90deg);
	opacity: 0;
}

@media (max-width: 768px) {
	/* Icon only on mobile, but the button keeps its accessible name. */
	.vf-groupbar__togglelabel {
		position: absolute;
		width: 1px;
		height: 1px;
		padding: 0;
		margin: -1px;
		overflow: hidden;
		clip: rect(0, 0, 0, 0);
		white-space: nowrap;
		border: 0;
	}
}

/* ---------------------------------------------------------------------------
 * Divisions accordion
 *
 * Expands in flow, pushing the header and hero down, rather than overlaying.
 * Animated on grid-template-rows so it works without knowing the content
 * height, which max-height guessing does not.
 * ------------------------------------------------------------------------ */

.vf-divisions {
	display: grid;
	grid-template-rows: 0fr;
	overflow: hidden;
	transition: grid-template-rows var(--vf-motion-slow) var(--vf-motion-ease);
}

/*
 * [hidden] is removed by the script as soon as it runs, so the panel is
 * animatable. With JS off the panel stays hidden and the toggle is not
 * rendered as interactive, which is the correct no-JS outcome.
 */
.vf-divisions[hidden] {
	display: none;
}

.vf-divisions.is-open {
	grid-template-rows: 1fr;
}

.vf-divisions__list {
	min-height: 0;
	margin: 0;
	padding: 0;
	list-style: none;
}

.vf-divisions__item {
	margin: 0;
}

/*
 * The stacked card effect.
 *
 * Each row carries an oversized bottom padding and every row after the first
 * pulls up over the one before it. The result is that each row appears to tuck
 * under its neighbour. Lose either half and the component reads as a plain
 * list of bars, which is not the same thing at all.
 */
.vf-divisions__item + .vf-divisions__item {
	margin-top: calc(var(--vf-radius-row) * -1);
}

/*
 * Row height is set by what shows, not by what the box measures.
 *
 * Each row is 56px of visible strip plus a tuck that the next row covers, and
 * the tuck is the same 24px the rows are pulled up by. Writing it that way
 * means the visible strip is `min-height - padding-bottom`, which is a fixed
 * 56, rather than whatever the tallest tagline happened to make it: the rows
 * used to measure 90, 116 and 146 at 1440 because the padding was a flat
 * 16/48 and the taglines wrap at different lengths.
 *
 * The content box therefore starts at the row's top edge and is exactly 56 tall,
 * so `align-items: center` centres the brand, the tagline and the arrow against
 * each other inside the part of the row anybody can see. Centring them in the
 * old box centred them against 48px of hidden padding as well, which is why the
 * tagline sat high.
 */
.vf-divisions__row {
	--vf-row-visible: 56px;
	--vf-row-tuck: var(--vf-radius-row);

	position: relative;
	display: grid;
	/*
	 * Equal side tracks, so the middle one is genuinely in the middle.
	 *
	 * This was `1fr 2fr auto`, which centres the tagline inside the second
	 * *column*, and that column is nowhere near the centre of the row: the two
	 * outer tracks were lopsided by design, so the text landed 281px right of
	 * centre at 1990, 229 at 1680, 189 at 1440. Centred in its own box the
	 * whole time, and visibly off centre in the row, which is the thing anybody
	 * actually looks at.
	 *
	 * With both flanking tracks at `1fr` they resolve to the same width whatever
	 * the brand and the arrow need, so the auto track between them sits on the
	 * row's centre line and the tagline centres on it. The arrow then has to be
	 * told to sit at the end of its track, since it is no longer in a track
	 * sized to it.
	 */
	grid-template-columns: minmax(0, 1fr) minmax(0, auto) minmax(0, 1fr);
	align-items: center;
	gap: var(--vf-space-3);
	min-height: calc(var(--vf-row-visible) + var(--vf-row-tuck));
	padding: 0 var(--vf-layout-gutter) var(--vf-row-tuck);
	border-radius: var(--vf-radius-row) var(--vf-radius-row) 0 0;
	background: var(--vf-row-bg, var(--vf-color-primary));
	color: var(--vf-color-on-dark);
	text-decoration: none;
	transition: filter var(--vf-motion-base) var(--vf-motion-ease);
}

/*
 * The live implementation has no hover state at all, which is odd for a row
 * that navigates to another website. A slight lift on the row plus the arrow
 * travelling right is enough to say "this goes somewhere".
 */
.vf-divisions__row:hover {
	filter: brightness(1.08);
}

.vf-divisions__row:hover .vf-divisions__arrow {
	transform: translateX(var(--vf-space-1));
}

.vf-divisions__brand {
	display: inline-flex;
	align-items: center;
	gap: var(--vf-space-1);
	min-width: 0;
}

.vf-divisions__logo {
	width: auto;
	height: 18px;
}

/*
 * The brand pair on an accordion row is its own size, not a heading size.
 *
 * Both of these used to be bound to `--vf-size-heading-m`, which is a
 * responsive clamp: the heart sized at `1em` of it and the name inherited it
 * outright, so the pair grew with the viewport and, at desktop widths, read as
 * a heading sitting in a 56px row. The confirmed sizes are 18px for the mark
 * and 20px for the name, so they are set here as fixed values on purpose. Do
 * not put a heading token back in: the row is a fixed-height strip and these
 * two have to stay put inside it.
 *
 * This is the accordion only. The Labs lockup in the header pill is a separate
 * widget with its own sizing and is deliberately untouched by these rules.
 */
.vf-divisions__name {
	font-family: var(--vf-font-display);
	font-size: 20px;
	line-height: 1;
	color: var(--vf-color-on-dark);
}

.vf-divisions__brand .vf-heart--division {
	width: 18px;
	height: 18px;
	flex: none;
	font-size: 18px;
	color: var(--vf-color-on-dark);
}

/*
 * The tagline sits on one line where it fits and two where it does not, and it
 * is centred on both axes inside the 56px strip.
 *
 * At `--vf-size-body-m` the longest of the six taglines is 91 characters and
 * runs about 819px against a column that is 805px at 1440: it wrapped, and each
 * row then set its own height from how its own copy happened to break. A fixed
 * 15px keeps every tagline on one line at desktop widths and, where a narrower
 * column does force a second, two lines at 1.35 come to 41px and still sit
 * inside the strip. The clamp is a floor under that, not a feature: no tagline
 * should reach three lines, and if a new division's copy ever does, the row
 * must not grow to swallow it.
 */
.vf-divisions__tagline {
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	line-clamp: 2;
	overflow: hidden;
	font-size: 15px;
	line-height: 1.35;
	text-align: center;
	text-wrap: balance;
	color: var(--vf-color-on-dark);
}

.vf-divisions__arrow {
	display: inline-flex;
	/* Its track is now a full 1fr rather than sized to it, so it says where in
	   that track it goes. */
	justify-self: end;
	width: 24px;
	height: 24px;
	color: var(--vf-color-on-dark);
	transition: transform var(--vf-motion-base) var(--vf-motion-ease);
}

.vf-divisions__arrow svg {
	width: 100%;
	height: 100%;
}

@media (max-width: 1280px) {
	.vf-divisions__row {
		padding-inline: var(--vf-layout-gutter-lg);
	}
}

@media (max-width: 1024px) {
	.vf-divisions__row {
		padding-inline: var(--vf-layout-gutter-md);
	}

	/*
	 * The live site drops the tagline entirely below the md breakpoint. Rather
	 * than lose it, move it under the brand: it is the only thing on the row
	 * that says what the division actually does.
	 */
	/*
	 * Two stacked areas need more than 56px, so the fixed strip is released
	 * here and the row goes back to sizing from its content. The 56 is a
	 * desktop measurement: one line of brand, tagline and arrow side by side.
	 */
	.vf-divisions__row {
		grid-template-columns: minmax(0, 1fr) auto;
		grid-template-areas:
			"brand arrow"
			"tag   tag";
		gap: var(--vf-space-1) var(--vf-space-3);
		min-height: 0;
		padding-top: var(--vf-space-2);
	}


	.vf-divisions__tagline {
		-webkit-line-clamp: 3;
		line-clamp: 3;
	}

	.vf-divisions__brand {
		grid-area: brand;
	}

	.vf-divisions__arrow {
		grid-area: arrow;
	}

	.vf-divisions__tagline {
		grid-area: tag;
		text-align: left;
	}
}

@media (max-width: 768px) {
	.vf-divisions__row {
		padding-inline: var(--vf-layout-gutter-sm);
		grid-template-columns: minmax(0, 1fr) auto;
		grid-template-areas: "brand arrow";
	}

	/*
	 * No tagline on a phone.
	 *
	 * A sentence of about ninety characters takes three lines in a 320px column
	 * and turns a 56px strip into a paragraph, and five of those stacked is most
	 * of the screen before the page has started. The division name and the
	 * arrow say what the row is and where it goes, which is what the row is for.
	 * The copy stays in the markup for anything reading the page aloud.
	 */
	.vf-divisions__tagline {
		display: none;
	}
}

/* ---------------------------------------------------------------------------
 * Wordmark
 * ------------------------------------------------------------------------ */

.vf-wordmark {
	display: inline-flex;
	align-items: center;
	gap: var(--vf-space-1);
	font-family: var(--vf-font-display);
	color: currentColor;
	text-decoration: none;
	line-height: 1;
}

.vf-wordmark--header {
	font-size: var(--vf-size-wordmark);
}

.vf-wordmark--footer {
	font-size: var(--vf-size-wordmark-xl);
}

.vf-wordmark .vf-heart--wordmark {
	width: 0.75em;
	height: auto;
	color: currentColor;
}

/* ---------------------------------------------------------------------------
 * Site header
 *
 * Sits over the hero. The hero itself carries the rounded top corners, so the
 * header is transparent and simply overlays it.
 * ------------------------------------------------------------------------ */

.vf-header {
	position: relative;
	z-index: 10;
	color: var(--vf-color-on-dark);
}

.vf-header__inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--vf-space-3);
	padding-block: var(--vf-space-4);
}

.vf-header__nav {
	display: flex;
	align-items: center;
	gap: var(--vf-space-4);
	padding: var(--vf-space-1) var(--vf-space-4);
	border-radius: var(--vf-radius-pill);
	background: rgb(255 255 255 / 0.16);
	backdrop-filter: blur(8px);
}

.vf-header__nav a {
	color: var(--vf-color-on-dark);
	font-family: var(--vf-font-display);
	font-size: var(--vf-size-body-l);
	text-decoration: none;
	opacity: 0.75;
	transition: opacity var(--vf-motion-base) var(--vf-motion-ease);
}

.vf-header__nav a:hover,
.vf-header__nav .current-menu-item > a {
	opacity: 1;
}

@media (max-width: 1024px) {
	.vf-header__nav {
		display: none;
	}
}

/* ---------------------------------------------------------------------------
 * Patterns used by the installed pages
 *
 * These back the Elementor sections the content installer generates. They are
 * here rather than as inline widget styles so that a change lands on every page
 * at once, and so the pages stay readable in the editor.
 * ------------------------------------------------------------------------ */

/*
 * Customization level meter, ten dots with n filled.
 *
 * The layout draws them at 13px on a 37px pitch, which is what makes the meter
 * read as a scale rather than as punctuation. At the inherited body size they
 * came out as a row of full stops.
 */
.vf-dots {
	display: inline-flex;
	gap: 16px;
	line-height: 1;
	vertical-align: middle;
}

.vf-dots i {
	display: inline-block;
	width: 13px;
	height: 13px;
	border-radius: 50%;
	box-shadow: inset 0 0 0 1.5px currentColor;
	background-color: transparent;
	/* Each dot takes a beat to fill and the next waits 140ms: a ten dot meter
	   fills in about two seconds, which reads as a measure rather than a flash. */
	transition: background-color var(--vf-motion-slow) var(--vf-motion-ease), transform var(--vf-motion-slow) var(--vf-motion-ease);
	transition-delay: calc(var(--i, 0) * 140ms);
}

/*
 * A lit dot is lit, always, unless the script has said it will fill on entry
 * (`will-fill`) and has not yet said it is in view (`is-in`). No JavaScript,
 * no reduced motion: the meter is simply drawn at its level.
 */
.vf-dots i.is-on {
	background-color: currentColor;
}

.vf-dots.will-fill:not(.is-in) i.is-on {
	background-color: transparent;
	transform: scale(0.85);
}

@media (prefers-reduced-motion: reduce) {
	.vf-dots.will-fill:not(.is-in) i.is-on {
		background-color: currentColor;
		transform: none;
	}
}

/*
 * Tag pill, used by the quality section and the Private Label orbit.
 *
 * White on white is not a pill. It reads as a pill on a photograph and as
 * nothing at all on the page the quality section actually sits on, so the fill
 * is the accent at low strength with the accent itself as the outline.
 *
 * Mixed from the token rather than written as the Labs blue: these were the
 * five literal colours left in the stylesheet, and on the second division
 * site they would have been blue tints on a green palette.
 */
.vf-tag {
	display: inline-block;
	padding: var(--vf-space-1) var(--vf-space-3);
	border: 1px solid color-mix(in srgb, var(--vf-color-accent) 70%, transparent);
	border-radius: var(--vf-radius-pill);
	background: color-mix(in srgb, var(--vf-color-accent) 16%, transparent);
	color: var(--vf-color-primary);
	font-size: var(--vf-size-body-m);
	white-space: nowrap;
}

/*
 * A row of tags is a row of text widgets, and an Elementor widget in a flex
 * row takes the width the container gives it. Left alone they divide the row
 * between them and the pills drift apart; each one has to be its own width.
 */
.vf-tags.vf-tags > .elementor-element {
	width: auto;
	max-width: 100%;
	flex: 0 0 auto;
}

/*
 * A place on the expansion section: the group's two tone mark, then the city.
 * The mark is the group heart at 20px rather than a plain dot, which is what
 * the layout has and what ties the row to the group bar at the top of the page.
 */
.vf-place {
	display: inline-flex;
	align-items: center;
	gap: 12px;
	font-family: var(--vf-font-display);
	font-size: var(--vf-size-heading-m);
	line-height: 1.2;
	color: var(--vf-color-ink-900);
	white-space: nowrap;
}

.vf-place__dot {
	width: 20px;
	height: 20px;
	flex: 0 0 20px;
	background-image: var(--vf-group-mark);
	background-size: contain;
	background-repeat: no-repeat;
	background-position: center;
}

.vf-place-wrap {
	display: inline-flex;
}

/*
 * Certification marks. Real artwork now, laid across the full width so the row
 * runs off both edges the way the layout has it. The marks are already the
 * right grey in the file, so nothing here recolours them.
 */
.vf-certs {
	min-width: 0;
}

/*
 * The marks keep their own proportions rather than being forced into equal
 * cells: FDA is a wide lockup, ISO is a circle, and squeezing them to the same
 * width makes the strip look like eight different logos badly resized. They
 * share a common height and the row distributes the leftover space.
 */
.vf-certs > * {
	flex: 0 1 auto;
	min-width: 0;
}

.vf-cert.vf-cert img,
.vf-cert__img.vf-cert__img img {
	/*
	 * Capped rather than fixed, and doubled so it actually applies: Elementor's
	 * image widget sizes the picture from its own settings, which outranked a
	 * single class here and left the marks rendering at their natural 224px.
	 */
	width: auto;
	height: auto;
	max-height: 140px;
	max-width: 100%;
	object-fit: contain;
	/*
	 * The layout draws these faintly, so they read as a quiet strip rather than
	 * a badge wall. 0.4 was too quiet against white: the marks are pale to begin
	 * with, and at that strength several of them were effectively invisible.
	 * 0.72 was then too heavy the other way. 0.55 is the settled value: present
	 * enough to read, quiet enough to stay a strip rather than a badge wall.
	 */
	opacity: 0.55;
}

@media (max-width: 1024px) {
	.vf-certs {
		flex-wrap: wrap !important;
		justify-content: center !important;
	}

	.vf-cert.vf-cert img,
	.vf-cert__img.vf-cert__img img {
		max-height: 96px;
	}
}

.vf-cert img {
	display: block;
}

/* ---------------------------------------------------------------------------
 * The conversation in the support section
 *
 * A question arrives, the reply is typed, the reply lands, and the next pair
 * takes over. Only one pair is on screen at a time, and they are stacked in the
 * same grid cell so the block does not change height between exchanges: a panel
 * that grows and shrinks under the copy is more distracting than the animation
 * is charming.
 * ------------------------------------------------------------------------ */

.vf-convo-wrap {
	/*
	 * `width: min()`, not `max-width`. Elementor caps every widget in a
	 * container with `.elementor.elementor .e-con > .elementor-widget
	 * { max-width: 100% }` at four class names, which beat a one class
	 * `max-width: 420px` on the site and let the panel run full width.
	 */
	width: min(100%, 420px);
	margin-left: auto;
}

/*
 * The support band carries two soft light sources, as the layout draws: a warm
 * one behind the conversation and a cooler one low and left behind the
 * headline. Radial gradients on a pseudo-element rather than an image, so they
 * scale to any width and cost nothing to load.
 *
 * ::after, because ::before on an Elementor container is its background overlay
 * and would be overwritten. The same trap that made the journey connector
 * disappear.
 */
.vf-support.vf-support {
	position: relative;
	isolation: isolate;
	/* Two class names: `.e-con { overflow: var(--overflow) }` loads after this. */
	overflow: hidden;
}

.vf-support.vf-support::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	pointer-events: none;
	background:
		radial-gradient(52% 46% at 78% 22%, rgb(255 255 255 / 0.20) 0%, rgb(255 255 255 / 0) 70%),
		radial-gradient(46% 40% at 12% 82%, rgb(140 200 255 / 0.18) 0%, rgb(140 200 255 / 0) 72%),
		radial-gradient(38% 34% at 96% 88%, rgb(255 255 255 / 0.12) 0%, rgb(255 255 255 / 0) 70%);
}

.vf-convo {
	display: grid;
}

.vf-convo__pair {
	grid-area: 1 / 1;
	display: flex;
	flex-direction: column;
	align-items: flex-end;
	gap: 14px;
	opacity: 0;
	visibility: hidden;
	transition: opacity var(--vf-motion-base) var(--vf-motion-ease);
}

.vf-convo__pair.is-current {
	opacity: 1;
	visibility: visible;
}

/* ---------------------------------------------------------------------------
 * The live thread (theme.js, initConversation)
 *
 * With JavaScript the pairs become a script and the thread plays in a fixed
 * window that clips, with a fade at the top where the older messages leave.
 *
 * The feed hangs from the bottom edge of the window, so the newest message is
 * always inside it. A message arrives by growing: it starts with a negative
 * top margin equal to its own height, so the thread is laid out as if it were
 * not there, and the margin eases to zero while the bubble fades in. The
 * older messages ride up on that margin; the new bubble stays where it is,
 * whole, at the bottom of the window.
 *
 * The earlier version moved the whole feed up with a transform after the
 * message was appended below the window's edge, so the new bubble spent the
 * length of that ease cut off at the bottom, and a long one was half visible
 * for most of a second.
 * ------------------------------------------------------------------------ */
.vf-convo.is-live {
	display: block;
	height: 440px;
	overflow: hidden;
	-webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 26%, #000 100%);
	mask-image: linear-gradient(to bottom, transparent 0, #000 26%, #000 100%);
}

.vf-convo.is-live .vf-convo__pair {
	display: none;
}

.vf-convo__window {
	position: relative;
	height: 100%;
	overflow: hidden;
}

.vf-convo__feed {
	position: absolute;
	right: 0;
	bottom: 0;
	left: 0;
	display: flex;
	flex-direction: column;
	align-items: flex-end;
	gap: 14px;
}

/* Inside the feed the show-* choreography does not apply: each item enters on its own. */
.vf-convo__feed .vf-bubble,
.vf-convo__feed .vf-convo__reply,
.vf-convo__feed .vf-chat__typing {
	opacity: 1;
	transform: none;
	transition: none;
}

.vf-convo__item {
	opacity: 0;
	transform: scale(0.94);
	transform-origin: bottom right;
	transition:
		opacity var(--vf-motion-slow) var(--vf-motion-ease),
		transform var(--vf-motion-slow) var(--vf-motion-ease-settle),
		margin-top var(--vf-motion-settle) var(--vf-motion-ease-settle);
}

.vf-convo__item.is-in {
	opacity: 1;
	transform: none;
}

@media (max-width: 767px) {
	.vf-convo.is-live {
		height: 360px;
	}
}

.vf-bubble {
	margin: 0;
	/*
	 * Scaled up from 330px and the small heading step. The block sits in a half
	 * width column against a large headline, and at the old size it read as a
	 * footnote rather than as the conversation the section is about.
	 */
	padding: 26px 30px;
	border-radius: var(--vf-radius-card);
	background: var(--vf-color-surface);
	color: var(--vf-color-ink-bubble);
	font-family: var(--vf-font-display);
	font-size: var(--vf-size-heading-m);
	line-height: 1.3;
	max-width: 430px;
	text-align: left;
	transform: translateY(10px);
	opacity: 0;
	transition: opacity var(--vf-motion-base) var(--vf-motion-ease),
		transform var(--vf-motion-base) var(--vf-motion-ease);
}

/*
 * The question waits for its own typing beat now, rather than being on screen
 * from the moment the pair becomes current.
 */
.vf-convo__pair.show-question .vf-bubble--them {
	opacity: 1;
	transform: none;
}

/* The reply is tinted, narrower, and sits in from the right, which is what
   gives the group its diagonal cascade in the layout. */
.vf-bubble--us {
	background: var(--vf-color-on-dark-muted);
	margin-right: 44px;
}

/*
 * The reply is a row: the mark, then the balloon.
 *
 * The Vitalforce mark used to live only in the typing indicator, so it vanished
 * at the exact moment the answer it belonged to arrived. Keeping it beside the
 * balloon is what makes the exchange read as Vitalforce answering rather than
 * as two anonymous bubbles.
 */
.vf-convo__reply {
	display: flex;
	align-items: flex-end;
	gap: 12px;
	align-self: flex-end;
	margin-right: 44px;
	opacity: 0;
	transition: opacity var(--vf-motion-base) var(--vf-motion-ease);
}

.vf-convo__pair.show-answer .vf-convo__reply {
	opacity: 1;
}

.vf-convo__reply .vf-bubble--us {
	margin-right: 0;
	opacity: 1;
	transform: none;
}

.vf-convo__pair.show-answer .vf-bubble--us {
	opacity: 1;
	transform: none;
}

/*
 * Two indicators. Theirs sits on the right where their question will land and
 * carries no avatar; ours sits in from the right, with the Vitalforce mark,
 * where the reply will land. Each is on screen for over a second, which is what
 * the earlier pacing did not allow.
 */
.vf-chat__typing {
	display: inline-flex;
	align-items: center;
	gap: 10px;
	opacity: 0;
	transition: opacity var(--vf-motion-base) var(--vf-motion-ease);
}

.vf-chat__typing--them {
	align-self: flex-end;
}

.vf-chat__typing--us {
	margin-right: 44px;
}

.vf-convo__pair.show-ask-typing .vf-chat__typing--them {
	opacity: 1;
}

/* Each indicator gives way to the message it was standing in for. */
.vf-convo__pair.show-question .vf-chat__typing--them {
	opacity: 0;
}

.vf-convo__pair.show-typing .vf-chat__typing--us {
	opacity: 1;
}

.vf-convo__pair.show-answer .vf-chat__typing--us {
	opacity: 0;
}

.vf-chat__avatar {
	width: 36px;
	height: 36px;
	border-radius: 50%;
	background: var(--vf-color-primary-deep);
	background-image: var(--vf-lockup-mark);
	background-size: 17px;
	background-repeat: no-repeat;
	background-position: center;
	box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.22);
	flex: 0 0 36px;
}

.vf-chat__dots {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 12px 16px;
	border-radius: 20px;
	background: var(--vf-color-on-dark-muted);
}

.vf-chat__dots i {
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: var(--vf-color-ink-bubble);
	opacity: 0.45;
	animation: vf-typing 1.4s infinite ease-in-out;
}

.vf-chat__dots i:nth-child(2) { animation-delay: 0.18s; }
.vf-chat__dots i:nth-child(3) { animation-delay: 0.36s; }

@keyframes vf-typing {
	0%, 60%, 100% { opacity: 0.3; transform: none; }
	30%           { opacity: 0.9; transform: translateY(-3px); }
}

/*
 * With motion reduced the script does not run at all, so the first exchange
 * stays put. Everything it would have animated is simply shown.
 */
@media (prefers-reduced-motion: reduce) {
	.vf-chat__dots i { animation: none; }

	.vf-convo__pair.is-current .vf-bubble,
	.vf-convo__pair.is-current .vf-chat__typing {
		opacity: 1;
		transform: none;
	}
}

/* ---------------------------------------------------------------------------
 * The autocomplete panel on Manufacturing
 *
 * A photograph of the blister line with a search field resting on it, the
 * typed query in the field and five suggestions below it, each one fainter
 * than the last so the list reads as continuing past the edge of the frame.
 *
 * The questions are ordinary text widgets in an ordinary container, which is
 * what keeps them editable. Everything that makes them look like a search
 * result, the glass, the magnifiers, the caret and the fade, is here, because
 * none of it is a control Elementor offers.
 * ------------------------------------------------------------------------ */
.vf-searchcard.vf-searchcard {
	position: relative;
}

.vf-searchpanel.vf-searchpanel {
	background: rgb(255 255 255 / 0.10);
	border: 1px solid rgb(255 255 255 / 0.16);
	backdrop-filter: blur(14px);
	-webkit-backdrop-filter: blur(14px);
	/*
	 * The list does not stop, it fades. Capping the panel and masking its foot
	 * is what the layout shows: the last question is half gone rather than
	 * simply dimmer, so the eye reads more of them below.
	 */
	-webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 62%, transparent 100%);
	mask-image: linear-gradient(to bottom, #000 0%, #000 62%, transparent 100%);
}

.vf-searchbar.vf-searchbar p,
.vf-suggestion.vf-suggestion p {
	display: flex;
	align-items: center;
	gap: 12px;
	margin: 0;
	font-size: 20px;
	line-height: 1.35;
}

.vf-searchbar.vf-searchbar p {
	padding: 13px 20px;
	border: 1px solid rgb(255 255 255 / 0.55);
	border-radius: var(--vf-radius-pill);
	background: rgb(255 255 255 / 0.08);
	font-weight: 700;
}

.vf-suggestion.vf-suggestion p {
	padding: 5px 20px;
}

/*
 * The magnifier. An inline mask rather than an icon font, so it takes the
 * colour of the line it sits on and dims with it.
 */
.vf-searchbar p::before,
.vf-suggestion p::before {
	content: "";
	flex: 0 0 19px;
	height: 19px;
	background: currentColor;
	-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='10.5' cy='10.5' r='7'/%3E%3Cpath d='M20.5 20.5l-5-5'/%3E%3C/svg%3E") center / contain no-repeat;
	mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='10.5' cy='10.5' r='7'/%3E%3Cpath d='M20.5 20.5l-5-5'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* The text caret at the end of the typed query. */
.vf-searchbar__q::after {
	content: "";
	display: inline-block;
	width: 2px;
	height: 1.05em;
	margin-left: 3px;
	background: currentColor;
	vertical-align: -0.16em;
	animation: vf-caret 1.1s step-end infinite;
}

@keyframes vf-caret {
	0%, 49% { opacity: 1; }
	50%, 100% { opacity: 0; }
}

/*
 * The ramp. Child one is the field, so the five questions are children two to
 * six, and the numbers are read off the layout rather than generated: the
 * first two are at full strength and the drop steepens from there.
 */
.vf-searchpanel .vf-suggestion:nth-child(2) { opacity: 1; }
.vf-searchpanel .vf-suggestion:nth-child(3) { opacity: 0.92; }
.vf-searchpanel .vf-suggestion:nth-child(4) { opacity: 0.72; }
.vf-searchpanel .vf-suggestion:nth-child(5) { opacity: 0.48; }
.vf-searchpanel .vf-suggestion:nth-child(6) { opacity: 0.30; }

/* ---------------------------------------------------------------------------
 * The theme's typography slots, when the kit does not resolve them
 *
 * vf_heading_m, vf_heading_s, vf_label and vf_caption are the theme's own kit
 * globals. On this site they do not resolve (docs/STATUS.md, "Typography
 * bindings"), so an element bound to one takes the widget's blanket default:
 * for a heading, the 80px display step. The builder marks every such element
 * `vf-type-<slot>`, and these rules give it the slot's own token instead.
 *
 * Three class names: above the kit's `.elementor-widget-heading
 * .elementor-heading-title` (0,2,0), below any per-component pin at four.
 * ------------------------------------------------------------------------ */
.vf-type-vf-heading-m.vf-type-vf-heading-m .elementor-heading-title {
	font-size: var(--vf-size-heading-m);
	line-height: var(--vf-leading-heading-m);
}

.vf-type-vf-heading-s.vf-type-vf-heading-s .elementor-heading-title {
	font-size: var(--vf-size-heading-s);
	line-height: var(--vf-leading-heading-m);
}

.vf-type-vf-heading-m.vf-type-vf-heading-m.elementor-widget-text-editor {
	font-size: var(--vf-size-heading-m);
	line-height: var(--vf-leading-heading-m);
}

/*
 * Size only, no weight: every label is written as <strong>, and `bolder` on
 * top of a 600 base is 900, which is what made "Best for" look like a shout.
 */
.vf-type-vf-label.vf-type-vf-label.elementor-widget-text-editor {
	font-size: var(--vf-size-body-l);
	line-height: var(--vf-leading-body-l);
}

/* The footer column titles: the display face at 24px, regular weight. */
.vf-footer-col__title.vf-footer-col__title.vf-footer-col__title.elementor-widget-text-editor {
	font-family: var(--vf-font-display);
	font-size: 24px;
	line-height: 1.2;
	font-weight: 400;
}

.vf-type-vf-caption.vf-type-vf-caption.elementor-widget-text-editor {
	font-size: var(--vf-size-caption);
	line-height: var(--vf-leading-caption);
}

/*
 * The `secondary` slot, for a site whose layouts use it.
 *
 * Every heading bound to `secondary` renders at the kit's 80px default today,
 * for the reason above, and Labs and Pharma were tuned with that. The
 * Cosmetics layouts set their carousel heads, the markets head, the
 * comparison titles and the "Already have the formula?" head at the 52px
 * step, so the Cosmetics builders mark exactly those headings
 * `vf-type-secondary` and this sizes them from the token. Nothing else
 * carries the class, so no other heading on any site changes.
 */
.vf-type-secondary.vf-type-secondary .elementor-heading-title {
	font-size: var(--vf-size-display-l);
	line-height: var(--vf-leading-display-l);
}

/*
 * The 52px step where the Cosmetics layouts set it inside components that pin
 * their own size: the timeline step titles (40 on Labs), the Blog grid titles
 * (40) and the spotlight title (44). Each is a modifier only the Cosmetics
 * builders add, one class name above the component's own pin.
 */
.vf-tl--l.vf-tl--l.vf-tl .vf-tl__step .elementor-widget-heading .elementor-heading-title,
.vf-post--l.vf-post--l.vf-post .vf-post__title .elementor-heading-title,
.vf-spotlight--l.vf-spotlight--l.vf-spotlight .vf-spotlight__copy .elementor-heading-title {
	font-size: var(--vf-size-display-l);
	line-height: var(--vf-leading-display-l);
}

/*
 * The certification strip, edge to edge.
 *
 * The Cosmetics layouts run the marks across the whole window and cut the
 * first and last at its edges, rather than inside the content column. The
 * track takes the window's width and pulls back to its left edge; the
 * section the builder puts it in clips the overhang, so the page never
 * scrolls sideways. `max-width` has to go too: Elementor caps every widget in
 * a container at 100% at four class names. Wide screens only; below 1025 the
 * strip keeps the column.
 */
@media (min-width: 1025px) {
	.vf-certs--bleed.vf-certs--bleed.vf-certs--bleed.vf-certs--bleed.vf-certs--bleed {
		width: 100vw;
		max-width: none;
		margin-left: calc(50% - 50vw);
		margin-right: calc(50% - 50vw);
	}
}

/* ---------------------------------------------------------------------------
 * "Flexible production" on Manufacturing: the four columns under the headline
 *
 * The column titles are bound to the vf_heading_m global, which the site does
 * not resolve (see docs/STATUS.md, "Typography bindings"), so without a size
 * here they take the kit's display default and "Multi-SKU Production" ran to
 * three lines of 80px over its neighbour's copy. 30px from the layout, held
 * at four class names against the kit's blanket heading rule.
 * ------------------------------------------------------------------------ */
.mf-flex__col.mf-flex__col .elementor-widget-heading .elementor-heading-title {
	font-size: 30px;
	line-height: 1.15;
}

@media (max-width: 1024px) {
	.mf-flex__col.mf-flex__col .elementor-widget-heading .elementor-heading-title {
		font-size: 26px;
	}
}

@media (max-width: 767px) {
	.mf-flex__col.mf-flex__col .elementor-widget-heading .elementor-heading-title {
		font-size: 22px;
	}
}

/* ---------------------------------------------------------------------------
 * "Already have the formula?" — the before and after on Manufacturing
 *
 * One box holds both pictures. The document is clipped at the split and the
 * bottle is not, so the seam reveals the finished product from behind the
 * paperwork. The divider runs the full height of the band, which is why it is
 * drawn from inside the artwork with the band clipping it, rather than
 * positioned separately: two hardcoded positions drift, one variable cannot.
 * ------------------------------------------------------------------------ */
.mf-existing.mf-existing {
	position: relative;
}

/* The light behind the bottle, part of the band rather than of the picture. */
.mf-existing.mf-existing::before {
	content: "";
	position: absolute;
	top: 0;
	right: 4%;
	width: 46%;
	height: 100%;
	background: radial-gradient(58% 54% at 62% 52%, color-mix(in srgb, var(--vf-color-glow) 40%, transparent), transparent 72%);
	pointer-events: none;
}

.mf-art.mf-art.mf-art {
	--mf-split: 51.5%;
	--width: auto;

	position: absolute;
	top: 14%;
	right: 14.3%;
	width: auto;
	height: 72%;
	aspect-ratio: 11 / 16;
	padding: 0;
}

.mf-art .mf-art__bottle,
.mf-art .mf-art__doc {
	position: absolute;
	inset: 0;
	width: 100%;
	margin: 0;
}

.mf-art .mf-art__bottle img,
.mf-art .mf-art__doc img {
	width: 100%;
	height: 100%;
}

.mf-art .mf-art__bottle img {
	object-fit: contain;
	object-position: center bottom;
}

.mf-art .mf-art__doc img {
	object-fit: cover;
	box-shadow: 0 22px 50px rgb(4 22 44 / 0.45);
}

.mf-art .mf-art__doc {
	clip-path: inset(0 calc(100% - var(--mf-split)) 0 0);
}

/*
 * The Pharma pair of capsules behind the sheet is a near square picture,
 * where the Labs bottle is tall: contained in the sheet's 11:16 box it
 * would sit small at the bottom. Drawn from the centre and scaled so the
 * capsules stand the height of the sheet, as on the artboard.
 */
.mf-art--capsule .mf-art__bottle img {
	object-position: center center;
	transform: translateX(16%) scale(1.35);
	transform-origin: 50% 50%;
}

/*
 * The Cosmetics cream jar is a wide, short picture: sat on the floor of the
 * sheet's 11:16 box it reads low beside the sheet, so it is centred on the
 * sheet's height instead.
 */
.mf-art--jar .mf-art__bottle img {
	object-position: center center;
}

/*
 * The divider, run past the artwork so the band clips it to its own height.
 *
 * The height is stated rather than stretched between a negative top and a
 * negative bottom, because Elementor's kit already gives every container's
 * ::before a `height: max(100%, 100%)` for the background overlay. A pair of
 * offsets cannot beat a height that is already set: the box kept the artwork's
 * own height, sat 1200px above the band, and the line simply was not there.
 */
.mf-art.mf-art::before {
	content: "";
	position: absolute;
	top: 50%;
	left: var(--mf-split);
	width: 2px;
	height: 2400px;
	margin: -1200px 0 0 -1px;
	background: rgb(255 255 255 / 0.92);
	z-index: 2;
	pointer-events: none;
}

/*
 * The control itself: a range slider over the whole picture, invisible.
 *
 * Everything the reveal shows is drawn from `--mf-split`, so moving the seam
 * is only a matter of writing that number, and a range writes it. Laying it
 * over the artwork at zero opacity gives the drag, the click to jump, the
 * touch handling and the arrow keys without writing any of them, and leaves
 * one control a keyboard and a screen reader can both reach. The visible
 * divider and handle are painted above it and take no pointer events, so the
 * grab always lands on the slider.
 */
.mf-art__range {
	position: absolute;
	inset: 0;
	z-index: 4;
	width: 100%;
	height: 100%;
	margin: 0;
	padding: 0;
	background: transparent;
	border: 0;
	opacity: 0;
	cursor: col-resize;
	-webkit-appearance: none;
	appearance: none;
}

.mf-art__range:focus-visible {
	opacity: 1;
	outline: none;
}

/* A wide thumb, so the grab target matches the handle rather than a hairline. */
.mf-art__range::-webkit-slider-thumb {
	width: 44px;
	height: 100%;
	background: transparent;
	border: 0;
	cursor: col-resize;
	-webkit-appearance: none;
	appearance: none;
}

.mf-art__range::-moz-range-thumb {
	width: 44px;
	height: 100%;
	background: transparent;
	border: 0;
	cursor: col-resize;
}

/* The focus ring goes on the handle, which is where the eye already is. */
.mf-art:focus-within::after {
	box-shadow: 0 0 0 3px var(--vf-color-focus);
}

.mf-art::after {
	content: "";
	position: absolute;
	top: 50%;
	left: var(--mf-split);
	width: 30px;
	height: 30px;
	margin: -15px 0 0 -15px;
	border-radius: 50%;
	pointer-events: none;
	background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23164a7d' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9.5 7.5L5 12l4.5 4.5M14.5 7.5L19 12l-4.5 4.5'/%3E%3C/svg%3E") center / 15px 15px no-repeat;
	z-index: 3;
}

/*
 * The handle pulses until the first touch, so the seam reads as something to
 * move rather than as a line. Base.css stills every animation under reduced
 * motion, this one included.
 */
.mf-art:not(.is-touched)::after {
	animation: mf-pulse 2.4s ease-out infinite;
}

@keyframes mf-pulse {
	0%   { box-shadow: 0 0 0 0 rgb(255 255 255 / 0.55); }
	70%  { box-shadow: 0 0 0 14px rgb(255 255 255 / 0); }
	100% { box-shadow: 0 0 0 14px rgb(255 255 255 / 0); }
}

/* ---------------------------------------------------------------------------
 * The vertical timeline
 *
 * One line down the left with a dot per step sitting on it, and the dots
 * darkening as they descend: pale at the first step, full brand blue at the
 * last, which is how the layout reads the sequence as going somewhere.
 *
 * The dot has to be centred on the line, and that is arithmetic rather than a
 * guess: the line is `--vf-tl-w` wide at `--vf-tl-line`, the dot is
 * `--vf-tl-dot` across, so its left edge sits at `line + w/2 - dot/2`. The
 * indent the steps carry is `--vf-tl-pad`, and it has to match the container's
 * padding setting, which is why the number is written once here and once
 * beside it in the page, with the reason at that call site.
 *
 * All three lengths are measured off the layout: the rail 60px into the
 * column, the step text 136px in, so the markers stand 78px clear of the copy.
 * ------------------------------------------------------------------------ */
.vf-tl.vf-tl {
	--vf-tl-pad: 136px;
	--vf-tl-line: 60px;
	--vf-tl-w: 2px;
	--vf-tl-dot: 16px;

	position: relative;
	padding-left: var(--vf-tl-pad);
}

/*
 * The rail states its height rather than stretching between a top and a
 * bottom. Elementor's kit already gives every container's ::before a
 * `height: max(100%, 100%)` for the background overlay, and a pair of offsets
 * cannot beat a height that is already set.
 */
/*
 * Two class names on every container `::before`, not one. Elementor's own
 * `.e-con::before` (the background overlay) is (0,1,1) and loads after this
 * file, so a single-class `::before` on a container ties, loses, and takes
 * `content: var(--background-overlay)`, which resolves to `none`: the rail and
 * the dots were absent from the live site while this stylesheet read
 * correctly. `tools/lint-css.js` now fails the build on that.
 */
.vf-tl.vf-tl::before {
	content: "";
	position: absolute;
	top: 16px;
	left: var(--vf-tl-line);
	width: var(--vf-tl-w);
	height: calc(100% - 32px);
	background: color-mix(in srgb, var(--vf-color-primary) 22%, transparent);
}

.vf-tl__step {
	position: relative;
}

.vf-tl__step.vf-tl__step::before {
	content: "";
	position: absolute;
	top: 14px;
	left: calc(var(--vf-tl-line) + var(--vf-tl-w) / 2 - var(--vf-tl-dot) / 2 - var(--vf-tl-pad));
	width: var(--vf-tl-dot);
	height: var(--vf-tl-dot);
	border-radius: 50%;
	background: var(--vf-color-primary);
	/* Pale at the top, solid at the bottom. Set per step below rather than
	   with an nth-child chain, so a sixth step needs no new rule. */
	opacity: var(--vf-tl-step-opacity, 1);
}

/*
 * The headline keeps the line breaks the copy carries, so its longest line has
 * to fit the column at every width. The display step alone does not guarantee
 * that: it is capped at 80px and reaches the cap well before the column is
 * wide enough to hold "An easier way to" at 80px. This ties the size to the
 * viewport the column is measured from, and stops short of the cap.
 */
.vf-tl-lead.vf-tl-lead .elementor-widget-heading .elementor-heading-title {
	font-size: clamp(28px, 5.5vw, 80px);
}

.vf-tl__step:nth-child(1) { --vf-tl-step-opacity: 0.30; }
.vf-tl__step:nth-child(2) { --vf-tl-step-opacity: 0.45; }
.vf-tl__step:nth-child(3) { --vf-tl-step-opacity: 0.65; }
.vf-tl__step:nth-child(4) { --vf-tl-step-opacity: 0.85; }
.vf-tl__step:nth-child(n + 5) { --vf-tl-step-opacity: 1; }

/*
 * The step heading is the layout's 40px, not the section heading step it was
 * inheriting. Three class names for Elementor's blanket heading rule, the same
 * shape as the capability and product cards.
 */
.vf-tl.vf-tl .vf-tl__step .elementor-widget-heading .elementor-heading-title {
	font-size: 40px;
	line-height: 1.15;
}

@media (max-width: 1024px) {
	.vf-tl {
		--vf-tl-pad: 96px;
		--vf-tl-line: 40px;
	}

	.vf-tl.vf-tl .vf-tl__step .elementor-widget-heading .elementor-heading-title {
		font-size: 32px;
	}
}

@media (max-width: 767px) {
	.vf-tl {
		--vf-tl-pad: 34px;
		--vf-tl-line: 4px;
		--vf-tl-dot: 13px;
	}

	.vf-tl.vf-tl .vf-tl__step .elementor-widget-heading .elementor-heading-title {
		font-size: 28px;
	}
}

/* ---------------------------------------------------------------------------
 * Present to a screen reader, absent to the eye.
 *
 * Not `display: none`, which removes an element from the accessibility tree as
 * well, and not `visibility: hidden` for the same reason. This is the standard
 * clip: the text is rendered, so it is announced, in a 1px box nobody sees.
 * ------------------------------------------------------------------------ */
.vf-sr {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* ---------------------------------------------------------------------------
 * Checklist on the White Label page
 *
 * The layout sets these at the section heading step, not at body size: the list
 * is the section's other half, opposite a 64px headline, and at body size it
 * read as a caption beside it.
 * ------------------------------------------------------------------------ */
.vf-check p {
	margin: 0;
	font-family: var(--vf-font-display);
	font-size: 40px;
	line-height: 1.35;
	color: var(--vf-color-primary);
}

@media (max-width: 1280px) {
	.vf-check p {
		font-size: 32px;
	}
}

@media (max-width: 767px) {
	.vf-check p {
		font-size: 26px;
	}
}

/* ---------------------------------------------------------------------------
 * The looping checklist
 *
 * The layout draws this list dissolving at the top and the bottom rather than
 * stopping, and it is meant to move: a long list of things a launch needs,
 * running past a window. So `.vf-checks` is the window, which carries the
 * height, the clipping and the fade, and `.vf-checks__track` is what travels.
 *
 * The loop is seamless because the script appends a second copy of the items
 * and the track climbs by exactly half its own height plus half a gap, which
 * lands the first clone where the first item started. Both halves of that sum
 * matter: without the gap the list jumps by one gap on every cycle.
 *
 * A mask does the fade rather than two gradient overlays, because it needs no
 * knowledge of the page's background, and it degrades to a plain list wherever
 * masks are unsupported.
 * ------------------------------------------------------------------------ */
/*
 * Three class names on the window, because Elementor's own `.e-con` rule sets
 * `height: var(--height)` and loads after this stylesheet. At one class the
 * height was declared and discarded: the window never clipped, all fourteen
 * lines showed at once, and the section ran to twice its height with the
 * duplicate half of the loop in plain view.
 */
.vf-checks.vf-checks.vf-checks {
	--vf-fade: 16%;
	--vf-mq-gap: 28px;

	height: 430px;
	overflow: hidden;
	/* Set in from the copy beside it, so the two columns read as two. */
	padding-left: 120px;
	-webkit-mask-image: linear-gradient(
		180deg,
		transparent 0%,
		#000 var(--vf-fade),
		#000 calc(100% - var(--vf-fade)),
		transparent 100%
	);
	mask-image: linear-gradient(
		180deg,
		transparent 0%,
		#000 var(--vf-fade),
		#000 calc(100% - var(--vf-fade)),
		transparent 100%
	);
}

.vf-checks__track.vf-checks__track.vf-checks__track {
	/* Three class names: `.e-con.e-flex { flex: ... }` is two and loads after this. */
	flex: 0 0 auto;
	animation: vf-marquee-y 26s linear infinite;
}

/*
 * One set, measured by theme.js into `--vf-mq-set`; half the track with the
 * old two-copy layout, which is what the fallback keeps for a page without
 * the script's measurement.
 */
@keyframes vf-marquee-y {
	to { transform: translateY(calc(-1 * var(--vf-mq-set, calc(50% + var(--vf-mq-gap) / 2)))); }
}

/*
 * The tick is the supplied mark, drawn as a mask so it takes the colour of the
 * line it sits on and dims with it as the list fades. Painted as an image it
 * would keep its own colour through the fade and sit on the text baseline
 * rather than on its centre.
 */
.vf-check.vf-check p {
	display: flex;
	align-items: center;
	gap: 16px;
	margin: 0;
	font-size: 40px;
	line-height: 1.15;
}

.vf-check__mark {
	flex: 0 0 26px;
	height: 23px;
	background: currentColor;
	-webkit-mask: var(--vf-icon-check) center / contain no-repeat;
	mask: var(--vf-icon-check) center / contain no-repeat;
}

/* ---------------------------------------------------------------------------
 * The option panels on White Label
 *
 * Five lists that drift across the band on a loop, cut off at both edges, so
 * the range reads as longer than the frame. The heading sits above its panel,
 * not inside it, and each panel's list fades out at the foot.
 *
 * `.vf-picker-vp` is the window and reaches past the section's gutter to the
 * band's own edges; `.vf-picker-row` is the track. The panel heights are set
 * per panel rather than left to the lists, because the layout staggers them
 * and equal heights read as a table.
 * ------------------------------------------------------------------------ */
/*
 * The window is the window, not the section's gutter.
 *
 * It used to reach out by exactly one gutter, which is right up to 1680 and
 * wrong above it: past that the boxed content stops growing and centres, so
 * the reach fell short by half the difference and the panels were cut a
 * hundred-odd pixels inside each edge of a wide screen. Sized to the viewport
 * instead, and centred on it, so the track always runs edge to edge and is cut
 * by the screen rather than by a margin.
 *
 * The section clips, which is what keeps the last few pixels of a 100vw box
 * from becoming a horizontal scrollbar on a page that has a vertical one.
 */
.vf-picker-vp.vf-picker-vp {
	--vf-mq-gap: 24px;

	width: 100vw;
	max-width: 100vw;
	margin-inline: calc(50% - 50vw);
	padding: 0;
	overflow: hidden;
	align-items: center;
}

.vf-picker-row.vf-picker-row.vf-picker-row {
	flex: 0 0 auto;
	width: max-content;
	max-width: none;
	flex-wrap: nowrap;
	animation: vf-marquee-x 52s linear infinite;
}

@keyframes vf-marquee-x {
	to { transform: translateX(calc(-1 * var(--vf-mq-set, calc(50% + var(--vf-mq-gap) / 2)))); }
}

.vf-picker.vf-picker.vf-picker {
	flex: 0 0 auto;
}

.vf-picker__list.vf-picker__list {
	background: rgb(255 255 255 / 0.14);
	backdrop-filter: blur(4px);
	-webkit-backdrop-filter: blur(4px);
	-webkit-mask-image: linear-gradient(180deg, #000 58%, transparent 100%);
	mask-image: linear-gradient(180deg, #000 58%, transparent 100%);
	overflow: hidden;
}

/* The staggered heights, measured off the layout rather than evened out. */
.vf-picker--1 .vf-picker__list { height: 270px; }
.vf-picker--2 .vf-picker__list { height: 400px; }
.vf-picker--3 .vf-picker__list { height: 335px; }
.vf-picker--4 .vf-picker__list { height: 300px; }
.vf-picker--5 .vf-picker__list { height: 365px; }

/* The window reaches the band's edge, and the section's gutter narrows here. */
@media (max-width: 1024px) {
	.vf-checks.vf-checks.vf-checks {
		height: 360px;
		padding-left: 64px;
	}

	.vf-check.vf-check p {
		font-size: 32px;
	}
}

/*
 * Both loops stop for anyone who has asked for less motion. The lists are
 * still complete and still faded; they simply stay where they are.
 */
@media (prefers-reduced-motion: reduce) {
	.vf-checks__track.vf-checks__track.vf-checks__track,
	.vf-picker-row.vf-picker-row.vf-picker-row {
		animation: none;
	}
}

/* ---------------------------------------------------------------------------
 * The artwork band on Private Label
 *
 * A cut out render on the brand colour. It has no background of its own, so it
 * must not be clipped, rounded or given a card's shadow; all it needs is room
 * and a maximum size, since at full width on a wide screen the bottle would
 * outgrow the copy beside it.
 * ------------------------------------------------------------------------ */
.vf-artwork__img img {
	/*
	 * No border-radius on any image widget's <img> in this file. The widget's
	 * own `image_border_radius` control writes it per element at (0,3,1), after
	 * this stylesheet, so a radius here is dead code; the installer sets the
	 * control instead (vf_image()).
	 */
	/* `.elementor img { max-width: 100% }` outranks a max-width here; the cap goes on the width. */
	width: min(100%, 660px);
	height: auto;
	box-shadow: none;
}

/* ---------------------------------------------------------------------------
 * The creative collage on Private Label
 *
 * Five stills scattered rather than lined up, the headline in the space the
 * first two leave at the top and the paragraph in the space the third leaves
 * at the bottom. That interleaving is the arrangement: a heading column beside
 * a picture column cannot put copy in a gap between pictures, and the group
 * flattened into a row of thumbnails with a title beside it.
 *
 * So it is one grid, five columns wide, and everything sits in its first row
 * and finds its place by column and by a top margin. A percentage top margin
 * on a grid item resolves against its own column width, which is the tile
 * width, so every offset here is a ratio of the tile: the arrangement holds
 * its proportions at any width rather than being a set of pixel drops measured
 * at one. The figures are taken off the artboard, tile by tile.
 * ------------------------------------------------------------------------ */
/*
 * Three class names, because Elementor writes the container's own
 * `display: flex` and `align-items` as a rule keyed to the element id, at two
 * class names and after this stylesheet. At two the grid was declared and
 * never used: the tiles came out as a flex row and the copy sat beside them.
 */
.vf-collage.vf-collage.vf-collage {
	display: grid;
	/*
	 * Five equal tiles inside the container, not five artboard-width tiles
	 * bleeding past it. The artboard's own five are wider than the column they
	 * sit in and the fifth is cut by the page edge, which reads as an export
	 * crop rather than as a decision: on the site it made the last picture a
	 * sliver of a dog. Dividing the container by five keeps the scatter and
	 * shows every tile whole.
	 */
	grid-template-columns: repeat(5, minmax(0, 1fr));
	/*
	 * Two rows, and this is what stops the headline colliding with the
	 * pictures. Everything used to sit in one row and find its place by a top
	 * margin, so how far the headline reached down was a matter of how many
	 * lines it wrapped to, and a three line title landed straight across the
	 * first two tiles.
	 *
	 * Now the headline has a row of its own. The first two tiles start below
	 * it and move down with it, while the last three span both rows and still
	 * rise past it, which is the interleave the layout is built on.
	 */
	grid-template-rows: auto auto;
	column-gap: 20px;
	row-gap: 0;
	align-items: start;
}

.vf-collage__img img {
	width: 100%;
	height: auto;
	aspect-ratio: 197 / 250;
	object-fit: cover;
}

/*
 * Two class names on every offset. Elementor maps a container's margins onto
 * `.e-con` at one class in a stylesheet that loads after this one, so a one
 * class `margin-top` ties and loses: the five tiles came out flush with each
 * other and dropped straight over the headline.
 */
/*
 * The offsets the artboard measures, with the first two taken relative to the
 * foot of the headline rather than to the top of the section, since that is
 * the row they now start in.
 */
.vf-collage__item--1.vf-collage__item--1 { grid-column: 1; grid-row: 2; margin-top: 15.7%; }
.vf-collage__item--2.vf-collage__item--2 { grid-column: 2; grid-row: 2; margin-top: 36%; }
.vf-collage__item--3.vf-collage__item--3 { grid-column: 3; grid-row: 1 / span 2; margin-top: 44.2%; }
.vf-collage__item--4.vf-collage__item--4 { grid-column: 4; grid-row: 1 / span 2; margin-top: 0; }
.vf-collage__item--5.vf-collage__item--5 { grid-column: 5; grid-row: 1 / span 2; margin-top: 56.6%; }

/*
 * The headline occupies the two columns the first two tiles have vacated.
 *
 * Above them in the stack, too. Every item in this grid shares one row and
 * finds its place by a top margin, so how far the headline reaches down is a
 * matter of how many lines it wraps to: at some widths it runs into the top of
 * the first tile, and painted underneath it, the title was unreadable. It is
 * the title. It goes on top.
 */
.vf-collage__lead.vf-collage__lead {
	position: relative;
	z-index: 2;
	grid-column: 1 / 3;
	grid-row: 1;
	align-self: start;
	/* The gap the layout leaves between the title and the first tile below it. */
	margin-bottom: 48px;
}

/*
 * And the paragraph the hole under the third.
 *
 * One column, then overflowing into the fourth, rather than an item spanning
 * two. A percentage margin on a grid item resolves against its own grid area,
 * so spanning two columns silently measured that 190.8% against a box three
 * times as wide and dropped the paragraph a page and a half down, leaving the
 * section looking empty.
 */
.vf-collage__note.vf-collage__note {
	grid-column: 3;
	grid-row: 2;
	width: 172%;
	align-self: start;
	margin-top: 125%;
}

/*
 * A tile that is a link behaves like a tile.
 *
 * Elementor wraps a linked container's children in an anchor, which brings its
 * own inline layout and, on the picture tiles, an underline under nothing.
 */
.vf-collage__item a,
.vf-card a {
	display: block;
	text-decoration: none;
	color: inherit;
}

.vf-collage__item.vf-collage__item {
	transition: transform var(--vf-motion-base) var(--vf-motion-ease);
}

.vf-collage__item:hover {
	transform: translateY(-4px);
}

/*
 * Team card. The bio is hidden until hover on a pointer device, and always
 * visible on touch, because hover does not exist there and hiding the content
 * behind an interaction nobody can perform is worse than showing it.
 */
.vf-person {
	position: relative;
}

/*
 * A scrim under the name so it stays readable over a light portrait.
 *
 * It covers the whole card and does its shaping with the gradient rather than
 * with its own height, because the open state dims the full portrait and a
 * scrim that changes size cannot animate: `height: 55%` to `height: auto` is a
 * jump, whatever duration is on it. Two gradients with the same stop structure
 * interpolate, so the dimming fades in with the rest of the card.
 */
.vf-person::after {
	content: "";
	position: absolute;
	inset: 0;
	background: linear-gradient(to top, rgb(10 20 32 / 0.72) 0%, rgb(10 20 32 / 0) 58%);
	pointer-events: none;
	z-index: 0;
}

.vf-person > * {
	position: relative;
	z-index: 1;
}

/*
 * The bio collapses rather than merely fading.
 *
 * Fading it alone left it holding its full height, which pushed the name and
 * role up into the middle of the portrait. Collapsing the row keeps the name at
 * the bottom until the card opens, and then the card switches to top alignment,
 * which is the state the layout shows on the third card.
 */
/*
 * The timing is the `settle` pair rather than the standard one, and the opacity
 * is deliberately behind the height.
 *
 * The card was opening the moment the cursor crossed it, which read as a jump
 * rather than a reveal: the standard curve leaves immediately, so the whole
 * movement happened in the first fifth of its duration and the rest was the
 * tail. The settle curve spends its time in the middle of the travel, and the
 * text waits 120ms so it fades into a gap that is already opening instead of
 * appearing on top of moving type.
 */
.vf-person__bio {
	display: grid;
	grid-template-rows: 0fr;
	opacity: 0;
	margin-top: 0;
	transition: grid-template-rows var(--vf-motion-settle) var(--vf-motion-ease-settle),
		opacity var(--vf-motion-settle) var(--vf-motion-ease-settle),
		margin-top var(--vf-motion-settle) var(--vf-motion-ease-settle);
}

.vf-person__bio > * {
	overflow: hidden;
	min-height: 0;
}

/*
 * There is no alignment switch here any more, and that is the fix.
 *
 * The card used to flip from `justify-content: flex-end` to `flex-start` on
 * hover so the opened bio had somewhere to go. `justify-content` is a discrete
 * property: it cannot be interpolated, so it snapped the instant the pointer
 * arrived and the whole card jumped, no matter how carefully the height
 * underneath it was eased. That jump was the "instant" everyone could feel, and
 * two attempts at softening the timing could not touch it.
 *
 * Bottom alignment is kept at all times instead. The bio grows downwards in the
 * flow and, because the group is anchored to the bottom, the name and role are
 * carried smoothly upwards by exactly the height that opens. One animated
 * property, nothing discrete, nothing to snap.
 */
.vf-person:hover .vf-person__bio,
.vf-person:focus-within .vf-person__bio {
	grid-template-rows: 1fr;
	opacity: 1;
	margin-top: 20px;
	transition-delay: 0s, 120ms, 0s;
}

/* An open card dims its portrait so the longer text stays readable. */
.vf-person::after {
	transition: background var(--vf-motion-settle) var(--vf-motion-ease-settle);
}

.vf-person:hover::after,
.vf-person:focus-within::after {
	background: linear-gradient(to top, rgb(10 20 32 / 0.78) 0%, rgb(10 20 32 / 0.62) 58%);
}

@media (hover: none) {
	.vf-person__bio {
		grid-template-rows: 1fr;
		opacity: 1;
		margin-top: 20px;
	}
}

/* Footer wordmark. */
.vf-footer-wordmark {
	font-size: var(--vf-size-wordmark-xl);
	line-height: 1;
}

/* Newsletter field placeholder, until the real form widget is wired up. */
.vf-field p {
	margin: 0;
	padding-bottom: var(--vf-space-1);
	border-bottom: 1px solid var(--vf-color-on-dark-muted);
}

/* ---------------------------------------------------------------------------
 * Site header
 *
 * Transparent, over the first section of the page. All four approved layouts
 * open with a full bleed hero, so the header overlays rather than stacks.
 * ------------------------------------------------------------------------ */

.vf-shell {
	position: relative;
}

.vf-header {
	position: absolute;
	inset: 0 0 auto;
	z-index: 30;
	color: var(--vf-color-on-dark);
}

.vf-header__inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--vf-space-3);
	padding-block: var(--vf-space-3);
}

.vf-header__nav {
	display: flex;
	align-items: center;
	padding: var(--vf-space-1) var(--vf-space-3);
	border-radius: var(--vf-radius-pill);
	background: rgb(255 255 255 / 0.16);
	backdrop-filter: blur(10px);
}

.vf-menu,
.vf-mobilenav__menu,
.vf-footer__links {
	display: flex;
	align-items: center;
	gap: var(--vf-space-3);
	margin: 0;
	padding: 0;
	list-style: none;
}

.vf-menu li {
	position: relative;
}

.vf-menu a {
	display: block;
	padding: var(--vf-space-1) var(--vf-space-2);
	color: var(--vf-color-on-dark);
	font-family: var(--vf-font-display);
	font-size: var(--vf-size-body-l);
	text-decoration: none;
	white-space: nowrap;
	opacity: 0.78;
	transition: opacity var(--vf-motion-base) var(--vf-motion-ease);
}

.vf-menu a:hover,
.vf-menu .current-menu-item > a,
.vf-menu .current-menu-ancestor > a {
	opacity: 1;
}

/* Solutions dropdown. */
.vf-menu .menu-item-has-children > a::after {
	content: "";
	display: inline-block;
	width: 0.5em;
	height: 0.5em;
	margin-left: 0.4em;
	border-right: 1.5px solid currentColor;
	border-bottom: 1.5px solid currentColor;
	transform: translateY(-0.15em) rotate(45deg);
}

.vf-menu .sub-menu {
	position: absolute;
	top: calc(100% + var(--vf-space-1));
	left: 0;
	min-width: 15rem;
	margin: 0;
	padding: var(--vf-space-2);
	list-style: none;
	border-radius: var(--vf-radius-card);
	background: rgb(255 255 255 / 0.16);
	backdrop-filter: blur(14px);
	opacity: 0;
	visibility: hidden;
	transform: translateY(-6px);
	transition: opacity var(--vf-motion-base) var(--vf-motion-ease),
		transform var(--vf-motion-base) var(--vf-motion-ease),
		visibility var(--vf-motion-base);
}

.vf-menu li:hover > .sub-menu,
.vf-menu li:focus-within > .sub-menu {
	opacity: 1;
	visibility: visible;
	transform: none;
}

.vf-header__actions {
	display: flex;
	align-items: center;
	gap: var(--vf-space-2);
}

.vf-header__cta {
	white-space: nowrap;
}

.vf-header__ctaicon {
	width: 1.2em;
	height: 1.2em;
	flex: 0 0 auto;
}

/* Mobile navigation. */
.vf-header__burger {
	display: none;
	align-items: center;
	justify-content: center;
	width: 48px;
	height: 48px;
	padding: 0;
	border: 0;
	border-radius: var(--vf-radius-pill);
	background: rgb(255 255 255 / 0.16);
	backdrop-filter: blur(10px);
	color: var(--vf-color-on-dark);
	cursor: pointer;
}

.vf-header__burgerbars {
	display: grid;
	gap: 5px;
	width: 20px;
}

.vf-header__burgerbars i {
	display: block;
	height: 2px;
	background: currentColor;
	border-radius: 2px;
	transition: transform var(--vf-motion-base) var(--vf-motion-ease),
		opacity var(--vf-motion-base) var(--vf-motion-ease);
}

.vf-header__burger[aria-expanded="true"] .vf-header__burgerbars i:nth-child(1) {
	transform: translateY(7px) rotate(45deg);
}

.vf-header__burger[aria-expanded="true"] .vf-header__burgerbars i:nth-child(2) {
	opacity: 0;
}

.vf-header__burger[aria-expanded="true"] .vf-header__burgerbars i:nth-child(3) {
	transform: translateY(-7px) rotate(-45deg);
}

.vf-mobilenav {
	display: grid;
	grid-template-rows: 0fr;
	overflow: hidden;
	background: var(--vf-color-primary);
	transition: grid-template-rows var(--vf-motion-base) var(--vf-motion-ease);
}

.vf-mobilenav[hidden] {
	display: none;
}

.vf-mobilenav.is-open {
	grid-template-rows: 1fr;
}

.vf-mobilenav > .vf-container {
	min-height: 0;
}

.vf-mobilenav__menu {
	flex-direction: column;
	align-items: stretch;
	gap: 0;
	padding-block: var(--vf-space-3);
}

.vf-mobilenav__menu a {
	display: block;
	padding: var(--vf-space-2) 0;
	color: var(--vf-color-on-dark);
	font-family: var(--vf-font-display);
	font-size: var(--vf-size-heading-m);
	text-decoration: none;
}

.vf-mobilenav__menu .sub-menu {
	margin: 0 0 var(--vf-space-2) var(--vf-space-3);
	padding: 0;
	list-style: none;
}

.vf-mobilenav__menu .sub-menu a {
	font-size: var(--vf-size-body-l);
	padding-block: var(--vf-space-1);
	opacity: 0.82;
}

.vf-mobilenav .vf-btn {
	margin-bottom: var(--vf-space-4);
}

@media (max-width: 1180px) {
	.vf-header__nav {
		display: none;
	}

	.vf-header__burger {
		display: inline-flex;
	}
}

@media (max-width: 560px) {
	.vf-header__cta span {
		display: none;
	}

	.vf-header__cta {
		padding-inline: var(--vf-space-3);
	}
}

/* ---------------------------------------------------------------------------
 * Site footer
 * ------------------------------------------------------------------------ */

.vf-footer {
	background: var(--vf-color-primary);
	color: var(--vf-color-on-dark);
	padding-block: var(--vf-space-12) var(--vf-space-6);
}

.vf-footer__newsletter {
	display: flex;
	flex-wrap: wrap;
	gap: var(--vf-space-6);
	align-items: flex-start;
}

.vf-footer__heading {
	flex: 1 1 20rem;
	margin: 0;
	color: var(--vf-color-on-dark);
	font-size: var(--vf-size-display-xl);
	line-height: var(--vf-leading-display-xl);
}

.vf-footer__signup {
	flex: 1 1 24rem;
}

.vf-signup__label {
	display: block;
	margin-bottom: var(--vf-space-1);
	color: var(--vf-color-on-dark-muted);
	font-size: var(--vf-size-body-l);
}

.vf-signup__row {
	display: flex;
	flex-wrap: wrap;
	gap: var(--vf-space-2);
	align-items: center;
}

.vf-signup__input {
	flex: 1 1 14rem;
	min-width: 0;
	padding: var(--vf-space-2) 0;
	border: 0;
	border-bottom: 1px solid var(--vf-color-on-dark-muted);
	background: transparent;
	color: var(--vf-color-on-dark);
	font-family: var(--vf-font-body);
	font-size: var(--vf-size-body-l);
}

.vf-signup__input:focus {
	outline: none;
	border-bottom-color: var(--vf-color-on-dark);
}

.vf-signup__input:focus-visible {
	/*
	 * The focus token, not the accent. Labs and Pharma set both to the same
	 * light colour, so nothing changes there; Cosmetics' accent is its plum,
	 * and an outline in the footer's own colour is no outline at all.
	 */
	outline: 3px solid var(--vf-color-focus);
	outline-offset: 4px;
}

.vf-footer__disclaimer {
	margin-top: var(--vf-space-2);
	color: var(--vf-color-on-dark-muted);
	font-size: var(--vf-size-caption);
	line-height: var(--vf-leading-caption);
}

.vf-footer__rule {
	margin-block: var(--vf-space-8);
	border-top-color: rgb(255 255 255 / 0.25);
}

.vf-footer__columns {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: var(--vf-space-4);
}

@media (max-width: 900px) {
	.vf-footer__columns {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

@media (max-width: 480px) {
	.vf-footer__columns {
		grid-template-columns: minmax(0, 1fr);
	}
}

.vf-footer__coltitle {
	margin: 0 0 var(--vf-space-2);
	color: var(--vf-color-on-dark);
	font-family: var(--vf-font-body);
	font-weight: var(--vf-weight-bold);
	font-size: var(--vf-size-body-l);
}

.vf-footer__links {
	flex-direction: column;
	align-items: flex-start;
	gap: var(--vf-space-1);
}

.vf-footer__links a {
	color: var(--vf-color-on-dark);
	font-size: var(--vf-size-body-l);
	text-decoration: none;
	opacity: 0.85;
}

.vf-footer__links a:hover {
	opacity: 1;
	text-decoration: underline;
}

.vf-footer__brand {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	justify-content: space-between;
	gap: var(--vf-space-4);
	margin-top: var(--vf-space-10);
}

.vf-footer__meta {
	text-align: right;
	color: var(--vf-color-on-dark);
	font-size: var(--vf-size-body-l);
}

.vf-footer__meta p {
	margin: 0 0 var(--vf-space-1);
}

.vf-footer__meta a {
	color: var(--vf-color-on-dark);
}

.vf-footer__legal {
	color: var(--vf-color-on-dark-muted);
	font-size: var(--vf-size-caption);
}

@media (max-width: 720px) {
	.vf-footer__brand {
		align-items: flex-start;
	}

	.vf-footer__meta {
		text-align: left;
	}
}

/* ---------------------------------------------------------------------------
 * Column widths
 *
 * There are no widths here any more, and that is the point.
 *
 * They used to live in this file, as `.vf-col--50-2 { width: calc(50% - 15px) }`
 * and friends, because Elementor's width control appeared to reject a calc().
 * It does not: the value simply has to sit in the control's `size` with the
 * `custom` unit. Meanwhile a single class selector is exactly as specific as
 * Elementor's own `.e-con { width: var(--width) }`, so which one won depended
 * on whether this stylesheet loaded before or after Elementor's. It loaded
 * before, and every multi column row on desktop fell back to 100% and stacked.
 * The tablet rules survived only because they used an attribute selector, one
 * step more specific, which is why the same page could be a correct two column
 * grid on a tablet and a single stack on a desktop.
 *
 * Widths are now real Elementor settings, written by vf_col() and attached in
 * vf_settings(). They land in the per element CSS Elementor generates, where
 * nothing outranks them, and they show in the panel as widths a designer can
 * change. Putting them here as well would only recreate the fight.
 *
 * What is left is the one thing a width setting cannot express.
 * ------------------------------------------------------------------------ */

.vf-col {
	/*
	 * A flex item defaults to min-width:auto, which means it refuses to shrink
	 * below its content. One long unbroken word in a column is then enough to
	 * push the whole row, and the page, wider than the viewport.
	 */
	min-width: 0;
}

/* ---------------------------------------------------------------------------
 * Container width
 *
 * A boxed Elementor container takes its maximum width from the kit, through
 * `--container-max-width`, which Elementor's own rule reads with a 1140px
 * fallback. If the kit is never seeded, every section runs at 1140 instead of
 * the design's 1520, which reads as a broken layout rather than a missing
 * setting.
 *
 * So the token layer supplies that variable as a root level default. It is a
 * default and not an override on purpose: the kit defines the same variable
 * closer to the element, so a designer changing the content width in Site
 * Settings still wins.
 * ------------------------------------------------------------------------ */

:root {
	--container-max-width: var(--vf-layout-container);
}

/*
 * Nothing makes this page scroll sideways.
 *
 * A backstop, not a substitute for fixing the element that overflows: a
 * horizontal scrollbar on a marketing site is always a bug, and the way it
 * presents to a visitor is the page sliding off centre with no way back. `clip`
 * is deliberate. `overflow-x: hidden` on the root makes it a scroll container,
 * which breaks `position: sticky` inside it and changes how the fixed header
 * behaves; `clip` clips and does nothing else.
 */
html {
	overflow-x: clip;
}

/* ---------------------------------------------------------------------------
 * Hero
 *
 * The headline sits around the middle and the paragraph plus buttons are pinned
 * near the bottom edge, as in the layout. `margin-block: auto` on the lead
 * block absorbs the free space above and below it, which centres the headline
 * and pushes the foot block to the bottom in one declaration.
 * ------------------------------------------------------------------------ */

/* The layout breaks the headline after "concept,". A 1040 measure puts the
   break in the same place without hard coding a line break into confirmed copy. */
.vf-hero__lead .elementor-heading-title {
	max-width: 1040px;
	margin-inline: auto;
}

/*
 * Nothing on the lead or the foot block here. They are centred by the hero's
 * own align-items setting; an `align-self` or `margin-block: auto` on the
 * container itself ties with Elementor's `.elementor-element { align-self:
 * var(--align-self) }` and `.e-con { margin-block-start: ... }` and loses, and
 * both did on the site while the preview showed them working.
 */

/* ---------------------------------------------------------------------------
 * Buttons
 *
 * Elementor generates the colour changes from the widget's own hover settings,
 * so what is left here is the movement: a small lift and a shadow, which is
 * what makes a button feel like a button rather than a coloured rectangle.
 * ------------------------------------------------------------------------ */

/* lint-ok: every button widget sets Elementor's own hover transition (0.3s, the same as --vf-motion-base), printed per element as `transition: all .3s`, which covers the same properties; this list is for .vf-btn and the harness */
.elementor-button,
.vf-btn {
	transition: background-color var(--vf-motion-base) var(--vf-motion-ease),
		color var(--vf-motion-base) var(--vf-motion-ease),
		border-color var(--vf-motion-base) var(--vf-motion-ease),
		transform var(--vf-motion-fast) var(--vf-motion-ease),
		box-shadow var(--vf-motion-base) var(--vf-motion-ease);
	will-change: transform;
}

.elementor-button:hover,
.vf-btn:hover {
	transform: translateY(-2px);
	box-shadow: 0 10px 24px rgb(10 10 10 / 0.18);
}

.elementor-button:active,
.vf-btn:active {
	transform: translateY(0);
	box-shadow: none;
}

@media (prefers-reduced-motion: reduce) {
	.elementor-button:hover,
	.vf-btn:hover {
		transform: none;
	}
}

/* ---------------------------------------------------------------------------
 * Carousel
 *
 * The track is Elementor Pro's Carousel widget, so almost nothing here is
 * layout: the swiper owns slide width, the gap and the arrow position, all set
 * as widget settings so a designer can change them in the panel. What is left
 * is appearance, and the one piece of layout the widget cannot know about:
 * reserving the room above the track that the arrows are lifted into.
 * ------------------------------------------------------------------------ */

.vf-carousel__head.vf-carousel__head {
	/*
	 * Two class names: `.e-con { min-height: var(--min-height) }` and the
	 * padding mapping are one, and load after this.
	 *
	 * The arrows sit above the track on the right, which is where the layout
	 * puts them. They are absolutely positioned by the widget, so they take no
	 * space of their own: the heading block keeps clear of them instead, both
	 * by leaving room to its right and by being tall enough for a 44px button.
	 */
	min-height: 44px;
	padding-inline-end: 160px;
}

@media (max-width: 767px) {
	/*
	 * The section runs to the edge on a phone, so the heading puts its own
	 * gutter back. Only the track wants the full width.
	 *
	 * Set through the padding variables, not as a longhand. Elementor maps them
	 * on the container itself:
	 *
	 *   .e-con { padding-inline-start: var(--padding-inline-start) }
	 *
	 * which is one class name in a stylesheet that loads after the theme's, so a
	 * `padding-inline-start` written here loses to it and the heading sat flush
	 * against the edge of the screen. Writing the variable it reads needs no
	 * specificity fight at all, and it is what a designer setting padding in the
	 * panel would produce.
	 */
	.vf-carousel__head.vf-carousel__head {
		--padding-left: var(--vf-layout-gutter-sm);
		--padding-right: var(--vf-layout-gutter-sm);
		--padding-inline-start: var(--vf-layout-gutter-sm);
		--padding-inline-end: var(--vf-layout-gutter-sm);
	}

	/* ---------------------------------------------------------------------
	 * The carousel runs edge to edge on a phone, with the arrows underneath.
	 *
	 * A 20px gutter either side of a one-up carousel wastes 40px of a 390px
	 * screen and makes the card look inset rather than swipeable. The section
	 * gives up its side padding, the heading takes it back for itself, and the
	 * arrows come down out of the corner into a row below the track, where
	 * there is room for them and where a thumb can reach.
	 * ------------------------------------------------------------------ */
	/*
	 * Four class names on the section, five on the arrows, both measured.
	 *
	 * The section's phone padding is Elementor's own
	 * `.elementor-192 .elementor-element.elementor-element-<id>`, three class
	 * names, in the page stylesheet that loads after this one, so a three class
	 * rule here tied and lost: the track measured 460 inside a 500 viewport with
	 * a 20px gutter still on each side.
	 *
	 * The arrows are worse, because two of Elementor's own stylesheets place
	 * them:
	 *
	 *   .elementor-element .swiper .elementor-swiper-button
	 *     { position:absolute; top:50%; transform:translateY(-50%) }     (0,3,0)
	 *   .elementor-widget-n-carousel.elementor-element :is(.swiper, .swiper-container)
	 *     ~ .elementor-swiper-button-prev { left: calc(…) }              (0,4,0)
	 *
	 * both from e-swiper.min.css, which also loads later. At three class names
	 * the whole block was ignored and the arrows stayed centred over the slides
	 * at `top: 209px, translateY(-22px)`, which is what "overlaying the slides"
	 * was.
	 */
	.vf-carousel.vf-carousel.vf-carousel.vf-carousel {
		--padding-left: 0px;
		--padding-right: 0px;
		--padding-inline-start: 0px;
		--padding-inline-end: 0px;
	}

	.vf-carousel__track.vf-carousel__track {
		position: relative;
		padding-bottom: 68px;
	}

	.vf-carousel__track.vf-carousel__track.vf-carousel__track.vf-carousel__track .elementor-swiper-button,
	.vf-carousel__track.vf-carousel__track.vf-carousel__track.vf-carousel__track .e-n-carousel-swiper-button {
		position: absolute;
		top: auto;
		bottom: 0;
		right: auto;
		inset-inline-end: auto;
		transform: none;
		margin: 0;
	}

	.vf-carousel__track.vf-carousel__track.vf-carousel__track.vf-carousel__track .elementor-swiper-button-prev,
	.vf-carousel__track.vf-carousel__track.vf-carousel__track.vf-carousel__track .e-n-carousel-swiper-button-prev {
		left: calc(50% - 52px);
	}

	.vf-carousel__track.vf-carousel__track.vf-carousel__track.vf-carousel__track .elementor-swiper-button-next,
	.vf-carousel__track.vf-carousel__track.vf-carousel__track.vf-carousel__track .e-n-carousel-swiper-button-next {
		left: calc(50% + 8px);
	}
}

/*
 * The hero's two calls to action stay on one line at every width.
 *
 * They are short labels and the row is centred, so stacking them buys nothing
 * and costs a button's height at the top of the page. The padding comes in on
 * the narrowest screens so the pair still fits inside the gutters.
 */
@media (max-width: 767px) {
	.vf-hero__ctas .elementor-button {
		padding-inline: 24px;
		white-space: nowrap;
	}
}

@media (max-width: 400px) {
	.vf-hero__ctas .elementor-button {
		padding-inline: 18px;
	}
}

/*
 * Swiper measures the track, and a flex item defaults to min-width:auto, so
 * without this the track sizes to the total width of every slide and pushes the
 * whole document sideways. A horizontal scrollbar on the page is the one
 * responsive failure everybody notices.
 */
.vf-carousel__track {
	min-width: 0;
	max-width: 100%;
}

/*
 * The track takes its full width back.
 *
 * Elementor reserves room beside the slides for arrows set to sit outside
 * them:
 *
 *   .elementor-element.elementor-arrows-position-outside .swiper
 *     { width: calc(100% - 60px) }        (0,3,0), e-swiper.min.css
 *
 * and swiper's own `margin-inline: auto` then centres the narrower track,
 * so 30px appears on each side. The arrows in this design are above the
 * track rather than beside it, so that reservation is dead space, and it
 * showed: every carousel, and the Insights spotlight, sat 30px inside the
 * content column while its own heading sat on the column edge. Measured on
 * the live sites, not guessed.
 *
 * Three class names, because Elementor's rule has three and loads later.
 */
.vf-carousel__track.vf-carousel__track.vf-carousel__track .swiper {
	width: 100%;
}

/*
 * The arrows.
 *
 * They used to be a hairline outline on white, which is almost invisible until
 * you already know where to look: a control nobody can find is not a control.
 * They now sit on the surface tint at rest, so the shape reads immediately, and
 * fill with the brand colour on hover.
 */
.vf-carousel__track .elementor-swiper-button,
.vf-carousel__track .e-n-carousel-swiper-button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	border: 1px solid transparent;
	border-radius: var(--vf-radius-pill);
	background: var(--vf-color-surface-tint);
	color: var(--vf-color-primary);
	transition: background-color var(--vf-motion-base) var(--vf-motion-ease),
		color var(--vf-motion-base) var(--vf-motion-ease),
		border-color var(--vf-motion-base) var(--vf-motion-ease),
		opacity var(--vf-motion-base) var(--vf-motion-ease);
}

.vf-carousel__track .elementor-swiper-button:hover,
.vf-carousel__track .e-n-carousel-swiper-button:hover {
	background: var(--vf-color-primary);
	border-color: var(--vf-color-primary);
	color: var(--vf-color-on-dark);
}

/*
 * And a backstop for the glyph itself.
 *
 * Elementor renders the arrow as an inline SVG and paints it with a `fill`,
 * not a `color`, so the button's own colour never reaches it. The widget
 * setting above is the real fix; this makes sure the glyph follows the button
 * in any state the setting does not cover, including hover.
 */
.vf-carousel__track {
	/*
	 * The nested carousel paints its arrow glyph from its own custom property:
	 *
	 *   .elementor-widget-n-carousel .elementor-swiper-button... svg {
	 *       fill: var(--e-n-carousel-arrow-normal-color, hsla(0,0%,93.3%,.9)) }
	 *
	 * That is four class names, so a `fill: currentColor` on a three class
	 * selector lost to it and the arrow stayed the default near white on a pale
	 * circle: visible ring, invisible arrow. Setting the property it actually
	 * reads inherits down and needs no specificity fight at all.
	 */
	--e-n-carousel-arrow-normal-color: var(--vf-color-primary);
	--e-n-carousel-arrow-hover-color: var(--vf-color-on-dark);
}

.vf-carousel__track .elementor-swiper-button svg,
.vf-carousel__track .e-n-carousel-swiper-button svg {
	width: 20px;
	height: 20px;
}

/* Dimmed rather than hidden at the ends, so the control does not jump. */
.vf-carousel__track .swiper-button-disabled {
	opacity: 0.3;
}

/* ---------------------------------------------------------------------------
 * Carousel card hover
 * ------------------------------------------------------------------------ */

/*
 * 4:5, which is what the layout draws and what every supplied photograph is
 * shot at. Expressed as a ratio rather than a height so the cards stay in
 * proportion at whatever width the carousel gives them.
 */
/*
 * The card lifts nothing and casts nothing. It zooms.
 *
 * It used to rise 8px with a soft drop shadow, and both were wrong inside a
 * carousel: the track has to clip its overflow or the off screen slides show,
 * so the shadow was cut off along the track edge and the top of a lifted card
 * was sliced clean off. Fighting that with padding on the track only moves the
 * clip somewhere else, because the clip is what makes the carousel work.
 *
 * A slow push into the photograph stays entirely inside the card, so there is
 * nothing to clip. The image is duplicated onto ::after and that copy is what
 * scales: scaling the card itself would scale the label with it, and animating
 * `background-size` is not possible from the `cover` keyword.
 *
 * ::after rather than ::before, because ::before is where Elementor paints a
 * container's background overlay, and this card has one. The negative z-index
 * puts the copy above the card's own background and below both the overlay and
 * the label, which is the order the scrim needs to stay readable.
 */
.vf-card {
	aspect-ratio: 4 / 5;
	position: relative;
	isolation: isolate;
}

/*
 * 32px and centred, which is what the layout draws. Same selector shape and
 * same reason as the capability cards: Elementor's blanket heading rule binds
 * every heading on the page to the `primary` global, so these labels were
 * rendering at 80px and wrapping across the middle of the photograph.
 */
.vf-card.vf-card .elementor-widget-heading .elementor-heading-title {
	font-size: 32px;
	text-align: center;
}

@media (max-width: 767px) {
	.vf-card.vf-card .elementor-widget-heading .elementor-heading-title {
		font-size: 26px;
	}
}

/*
 * The bento tiles in "Markets we love", 32px.
 *
 * Same selector shape and same reason as the product cards above: the label is
 * a heading widget bound to a global, and Elementor's blanket
 * `.elementor-widget-heading .elementor-heading-title` rule sends every heading
 * on the page to that global's size from a stylesheet that loads after this
 * one. Two class names ties and loses; three wins without !important.
 */
.vf-bento.vf-bento .elementor-widget-heading .elementor-heading-title {
	font-size: 32px;
	text-align: center;
}

@media (max-width: 767px) {
	.vf-bento.vf-bento .elementor-widget-heading .elementor-heading-title {
		font-size: 26px;
	}
}

.vf-card::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	background-image: inherit;
	background-size: cover;
	background-position: center;
	transform: scale(1);
	transition: transform 1600ms var(--vf-motion-ease-settle);
}

.vf-card:hover::after,
.vf-card:focus-within::after {
	transform: scale(1.09);
}

@media (prefers-reduced-motion: reduce) {
	.vf-card::after {
		transition: none;
	}

	.vf-card:hover::after,
	.vf-card:focus-within::after {
		transform: none;
	}
}

/* ---------------------------------------------------------------------------
 * Capability cards
 *
 * Three columns, two rows. Title at the top, body copy pinned to the bottom,
 * and the photograph showing from the start.
 *
 * This used to be a reveal: a tint over the picture that faded out on hover,
 * with the type turning white as it went. The team took the reveal out, so the
 * picture is the resting state and the type is white throughout, on every
 * screen size. What is left of the hover is the slow zoom the product cards
 * have: the `::after` layer inherits the container's own background image, so
 * a designer replacing the picture in the panel replaces what zooms, and the
 * container's `overflow: hidden` and radius clip it.
 * ------------------------------------------------------------------------ */

.vf-cap {
	position: relative;
	isolation: isolate;
}

.vf-cap::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	background-image: inherit;
	background-size: cover;
	background-position: center;
	transform: scale(1);
	transition: transform 1600ms var(--vf-motion-ease-settle);
}

.vf-cap:hover::after,
.vf-cap:focus-within::after {
	transform: scale(1.09);
}

@media (prefers-reduced-motion: reduce) {
	.vf-cap::after {
		transition: none;
	}

	.vf-cap:hover::after,
	.vf-cap:focus-within::after {
		transform: none;
	}
}

/*
 * The type is bound to a global so a designer can change it in the panel.
 * Elementor writes that as an inline-equivalent rule keyed to the element id,
 * which no stylesheet selector can reliably outrank, so the colour that reads
 * over a photograph is forced.
 *
 * This is the one place in the theme using !important. The alternative is
 * type in the resting blue over a photograph, which is unreadable on four of
 * the six pictures.
 */
.vf-cap :is(h1, h2, h3, h4, h5, h6, p, .elementor-heading-title) {
	color: var(--vf-color-on-dark) !important;
}

/*
 * Card titles are sized by the type scale, not by a container query.
 *
 * They used to be `clamp(1.75rem, 13.6cqi, 3.25rem)`, tied to the card's own
 * width so a long word like "documentation" could not run out of the box. The
 * idea was sound and the failure mode was not: `cqi` only means "percent of the
 * container" while a container query container actually exists, and the moment
 * `container-type` does not apply, for any reason, the unit silently falls back
 * to a percentage of the viewport. 13.6% of a 1680 viewport is 228px, which the
 * clamp caps at 52, so the title looks correct on a wide screen and then grows
 * as the card shrinks: exactly backwards, and impossible to see coming.
 *
 * The size is now the section heading step from the design system, which is
 * what the layout specifies anyway, and it is fluid on the viewport.
 *
 * Fluid on the *viewport* still has to respect the card, though, and the card
 * is not a constant fraction of it: three columns become two at 1024 and one at
 * 767, and the 52px padding takes the same bite out of a 353px card as out of a
 * 487px one. So the ceiling steps at each of those points. The numbers come
 * from measuring the longest word in the section, "documentation", against the
 * text width the card actually leaves at each breakpoint, which is what
 * tools/check-layout.js now asserts on every run.
 *
 * Set through the global's own variable, not as a font-size: Elementor writes
 * `font-size: var(--e-global-typography-secondary-font-size)` onto the widget
 * at a specificity a class selector cannot beat, and redefining the variable
 * for this subtree changes what that resolves to without a specificity fight.
 */
/*
 * Set as a font-size, on a selector that outranks Elementor's blanket rule.
 *
 * This was written twice as a redefinition of the global's variable, which is
 * the tidier mechanism and did nothing at all here. Elementor's page CSS
 * carries the heading widget's *default* typography as
 *
 *   .elementor-widget-heading .elementor-heading-title {
 *       font-size: var(--e-global-typography-primary-font-size)
 *   }
 *
 * for every heading on the page, so these titles were reading the `primary`
 * global, not the one they are bound to, and rendered at its 80px ceiling while
 * a variable nothing referenced was carefully adjusted. Adding one class to the
 * selector takes it to (0,3,0) and settles it wherever the stylesheets land.
 *
 * 52px is the design's section heading step, and it is stated as a number
 * because that is what it is: the fluid scale tops out at the same 52 and the
 * indirection bought nothing here but the bug above.
 */
/*
 * 50px, and words are never cut.
 *
 * `overflow-wrap: break-word` was here so a long word could not push past the
 * card, and it bought that by splitting the word itself: "Documentat / ion"
 * across two lines. A word broken mid-syllable reads as a broken page, so the
 * whole word moves to the next line instead and the size ladder below is what
 * guarantees it still fits. Every step in that ladder is measured, not guessed,
 * and tools/check-layout.js fails if any word in these cards ends up split or
 * wider than the box it sits in.
 */
.vf-cap.vf-cap .elementor-widget-heading .elementor-heading-title,
.vf-step.vf-step .elementor-widget-heading .elementor-heading-title {
	overflow-wrap: normal;
	word-break: normal;
	hyphens: none;
}

.vf-cap.vf-cap .elementor-widget-heading .elementor-heading-title {
	font-size: 50px;
}

/*
 * Below the design width the card narrows while the words do not, and the
 * longest one in this section is "documentation". Rather than shrink the type
 * the card gives back some of its 52px padding first, which is spacing rather
 * than typography, and only steps the size down when there is no padding left
 * to give. Both numbers are checked by tools/check-layout.js, which fails when
 * any single word breaks across two lines.
 */
@media (max-width: 1440px) {
	/*
	 * Four class names. The card's own padding setting is printed per element
	 * as these same custom properties at (0,3,0), after this stylesheet, and
	 * 1440 is not an Elementor breakpoint, so this cannot be a setting and has
	 * to outrank one instead.
	 */
	.vf-cap.vf-cap.vf-cap.vf-cap {
		--padding-left: 24px;
		--padding-right: 24px;
	}

	/* 52 is the design size and it is held as far down as the card can carry it;
	   below the design width the longest word decides. */
	.vf-cap.vf-cap .elementor-widget-heading .elementor-heading-title {
		font-size: 44px;
	}
}

@media (max-width: 1280px) {
	.vf-cap.vf-cap .elementor-widget-heading .elementor-heading-title {
		font-size: 34px;
	}
}

@media (max-width: 1024px) {
	.vf-cap.vf-cap .elementor-widget-heading .elementor-heading-title {
		font-size: 40px;
	}
}

@media (max-width: 767px) {
	.vf-cap.vf-cap .elementor-widget-heading .elementor-heading-title {
		font-size: 32px;
	}
}

/* The two column tablet card is the tightest box in the whole layout: narrow,
   and still carrying the desktop padding. */
@media (max-width: 900px) {
	.vf-cap.vf-cap .elementor-widget-heading .elementor-heading-title {
		font-size: 30px;
	}
}

/* ---------------------------------------------------------------------------
 * Media column
 * ------------------------------------------------------------------------ */

.vf-media__img img {
	width: 100%;
	height: auto;
	object-fit: cover;
}

/* ---------------------------------------------------------------------------
 * The product composition
 *
 * The render is the only thing in the flow; the plus boxes, their ticked
 * twins and the cursor sit over it at coordinates measured on the layout's
 * composition and carried over to the render as imported, which is that
 * composition cropped to the products and their shadow (1920 by 1050, from
 * 2510 by 1214, so the products fill the column rather than a third of it;
 * the shadow runs out to the right and the crop keeps all of it), as a percentage
 * of the render, so they land in the same place at every column width. Each spot is two image widgets on
 * top of each other, the plus and the tick, and `is-on` crossfades between
 * them; theme.js moves the cursor and sets `is-on`, and the resting state
 * below (first box ticked, cursor on the second) is the layout's still.
 *
 * Two class names throughout: Elementor gives every widget `position:
 * relative` and `width: 100%` at one class, from a stylesheet that loads
 * after this one.
 * ------------------------------------------------------------------------ */

.vf-compose.vf-compose {
	position: relative;
	display: block;
}

.vf-compose .vf-compose__img.vf-compose__img {
	width: 100%;
}

.vf-compose .vf-compose__spot.vf-compose__spot,
.vf-compose .vf-compose__cursor.vf-compose__cursor {
	position: absolute;
	width: 7.1%;
	margin: 0;
	pointer-events: none;
	filter: drop-shadow(0 6px 14px rgb(10 30 50 / 0.18));
}

.vf-compose .vf-compose__spot img,
.vf-compose .vf-compose__cursor img {
	width: 100%;
	height: auto;
	display: block;
}

/* Box centres, from the layout: pouch, can, tub. Placed by their centre so a
   larger box on a phone grows in place. */
.vf-compose .vf-compose__spot.vf-compose__spot { transform: translate(-50%, -50%); }
.vf-compose .vf-compose__spot--1.vf-compose__spot--1 { left: 17.5%; top: 38.6%; }
.vf-compose .vf-compose__spot--2.vf-compose__spot--2 { left: 46.1%; top: 54.8%; }
.vf-compose .vf-compose__spot--3.vf-compose__spot--3 { left: 56.2%; top: 14.7%; }

/* The plus and the tick share a spot; the tick shows once the spot is on. */
.vf-compose .vf-compose__spot.vf-compose__spot {
	transition: transform 220ms var(--vf-motion-ease), opacity 320ms var(--vf-motion-ease);
}
.vf-compose .vf-compose__tick.vf-compose__tick { opacity: 0; }
.vf-compose .vf-compose__spot.is-on.vf-compose__tick { opacity: 1; }
.vf-compose .vf-compose__spot.is-on.vf-compose__plus { opacity: 0; }
.vf-compose .vf-compose__spot.is-pressed.is-pressed { transform: translate(-50%, -50%) scale(0.88); }

/* The layout's still: the first box ticked. The script takes over from here. */
.vf-compose:not(.is-live) .vf-compose__spot--1.vf-compose__tick { opacity: 1; }
.vf-compose:not(.is-live) .vf-compose__spot--1.vf-compose__plus { opacity: 0; }

/* The cursor: its tip is the image's top left corner. Rests on the second box. */
.vf-compose .vf-compose__cursor.vf-compose__cursor {
	width: 6.4%;
	left: 47.3%;
	top: 55.7%;
	transition: left 760ms var(--vf-motion-ease-settle), top 760ms var(--vf-motion-ease-settle), transform 160ms var(--vf-motion-ease);
	transform-origin: 8% 8%;
}
.vf-compose .vf-compose__cursor.is-pressed { transform: scale(0.86); }

/*
 * The Pharma render: the ring of capsules and tablets. Three add boxes as on
 * Labs (by the white tablet, by the green capsule, at the lower right of the
 * ring), at their own coordinates; the script counts the boxes it finds.
 */
.vf-compose--pills .vf-compose__spot.vf-compose__spot { width: 8.5%; }
.vf-compose--pills .vf-compose__spot--1.vf-compose__spot--1 { left: 34.9%; top: 54.5%; }
.vf-compose--pills .vf-compose__spot--2.vf-compose__spot--2 { left: 62.5%; top: 23.7%; }
.vf-compose--pills .vf-compose__spot--3.vf-compose__spot--3 { left: 77.3%; top: 66.4%; }
.vf-compose--pills .vf-compose__cursor.vf-compose__cursor {
	width: 7.2%;
	left: 63.5%;
	top: 24.9%;
}

/*
 * The Cosmetics render: the plum range of pouch, dropper, jar, spray, tube and
 * bottle. Three add boxes: the two the layout draws (on the tall bottle, and
 * on the jar with the cursor over it) and a third on the pouch, measured off
 * the 1013 by 547 render on the 1680 artboard.
 */
.vf-compose--beauty .vf-compose__spot.vf-compose__spot { width: 6.3%; }
.vf-compose--beauty .vf-compose__spot--1.vf-compose__spot--1 { left: 81%; top: 28.2%; }
.vf-compose--beauty .vf-compose__spot--2.vf-compose__spot--2 { left: 50.9%; top: 61.6%; }
.vf-compose--beauty .vf-compose__spot--3.vf-compose__spot--3 { left: 37%; top: 39.3%; }
.vf-compose--beauty .vf-compose__cursor.vf-compose__cursor {
	width: 6.8%;
	left: 53%;
	top: 63.8%;
}

/*
 * Drawn a fifth larger than its column on desktop (Gian, 0.31.5). The render
 * is a transparent cut out, so the extra width runs left into the gap beside
 * the copy without covering anything; the boxes and the cursor are placed in
 * percentages of the render and grow with it.
 */
@media (min-width: 1025px) {
	.vf-compose--beauty.vf-compose--beauty.vf-compose--beauty {
		--width: 120%;
		width: 120%;
		max-width: none;
		margin-left: -20%;
	}
}

/* The same phone sizes as the default boxes: at 6.3% they would be 22px. */
@media (max-width: 767px) {
	.vf-compose--beauty .vf-compose__spot.vf-compose__spot { width: 11%; }
	.vf-compose--beauty .vf-compose__cursor.vf-compose__cursor { width: 10%; }
}

/* ---------------------------------------------------------------------------
 * The capsule configurator (Pharma, Capsules page)
 *
 * A capsule render with its option rows over it: the size chips and the
 * colour swatches behind the capsule, the format icons in front. Everything
 * but the capsule and the three icons is drawn here: the rows are translucent
 * panels (containers), the chips and swatches text widgets shaped into discs.
 * The box is a size container and every measure is in cqw, so the piece is
 * the same drawing at every column width; the capsule is the one thing in the
 * flow and sets the height, at a quarter of the box wide with the layout's
 * margin above and below.
 *
 * theme.js walks the selection along each row (`is-on`), and the box carries
 * the current size and tint on data attributes so the capsule can follow:
 * it steps down through the sizes and takes the swatch's colour through a
 * filter on the render. Under prefers-reduced-motion, or without the script,
 * the layout's still is what shows.
 *
 * Two and three class names throughout, for the same reason as the
 * composition: Elementor gives every widget `position: relative` and
 * `width: 100%` at one class, and a container its flex settings at two, in
 * a stylesheet that loads after this one.
 * ------------------------------------------------------------------------ */
.vf-config.vf-config.vf-config {
	position: relative;
	display: block;
	width: 100%;
	padding: 0;
	container-type: inline-size;
}

/* The light behind the capsule, the division's glow token. */
.vf-config.vf-config::before {
	content: "";
	position: absolute;
	inset: 0;
	height: 100%;
	background: radial-gradient(56% 54% at 38% 50%, color-mix(in srgb, var(--vf-color-glow) 72%, transparent), transparent 68%);
	pointer-events: none;
}

.vf-config .vf-config__img.vf-config__img.vf-config__img {
	position: relative;
	z-index: 2;
	width: 25%;
	margin: 12.5% 0 12.5% 25.5%;
	transition: transform 600ms var(--vf-motion-ease-settle), filter 600ms var(--vf-motion-ease);
	transform-origin: 50% 50%;
}

.vf-config .vf-config__img img {
	width: 100%;
	height: auto;
	display: block;
	filter: drop-shadow(0 4cqw 6cqw rgb(0 0 0 / 0.35));
	transition: filter 600ms var(--vf-motion-ease);
}

/* The size the script has chosen: the capsule steps down from 000 to 3. */
.vf-config[data-size="1"] .vf-config__img.vf-config__img.vf-config__img { transform: scale(0.93); }
.vf-config[data-size="2"] .vf-config__img.vf-config__img.vf-config__img { transform: scale(0.86); }
.vf-config[data-size="3"] .vf-config__img.vf-config__img.vf-config__img { transform: scale(0.79); }
.vf-config[data-size="4"] .vf-config__img.vf-config__img.vf-config__img { transform: scale(0.72); }
.vf-config[data-size="5"] .vf-config__img.vf-config__img.vf-config__img { transform: scale(0.66); }

/* The tint the chosen swatch gives the render (a green capsule to start). */
.vf-config[data-tint="grey"] .vf-config__img img { filter: drop-shadow(0 4cqw 6cqw rgb(0 0 0 / 0.35)) saturate(0.08) brightness(1.55); }
.vf-config[data-tint="sage"] .vf-config__img img { filter: drop-shadow(0 4cqw 6cqw rgb(0 0 0 / 0.35)) saturate(0.55) brightness(1.15); }
.vf-config[data-tint="deep"] .vf-config__img img { filter: drop-shadow(0 4cqw 6cqw rgb(0 0 0 / 0.35)) brightness(0.72); }
.vf-config[data-tint="orange"] .vf-config__img img { filter: drop-shadow(0 4cqw 6cqw rgb(0 0 0 / 0.35)) hue-rotate(-100deg) saturate(1.5) brightness(1.3); }
.vf-config[data-tint="yellow"] .vf-config__img img { filter: drop-shadow(0 4cqw 6cqw rgb(0 0 0 / 0.35)) hue-rotate(-75deg) saturate(1.5) brightness(1.45); }
.vf-config[data-tint="lime"] .vf-config__img img { filter: drop-shadow(0 4cqw 6cqw rgb(0 0 0 / 0.35)) hue-rotate(-45deg) saturate(1.3) brightness(1.3); }
.vf-config[data-tint="white"] .vf-config__img img { filter: drop-shadow(0 4cqw 6cqw rgb(0 0 0 / 0.35)) saturate(0) brightness(1.9); }

.vf-config .vf-config__panel.vf-config__panel.vf-config__panel {
	position: absolute;
	z-index: 1;
	display: flex;
	flex-direction: row;
	flex-wrap: nowrap;
	align-items: center;
	gap: 1.9cqw;
	width: auto;
	padding: 1.9cqw;
	border-radius: var(--vf-radius-pill);
	background: rgb(255 255 255 / 0.14);
	-webkit-backdrop-filter: blur(6px);
	backdrop-filter: blur(6px);
}

/* Where the rows sit, from the layout. */
.vf-config .vf-config__panel--sizes.vf-config__panel--sizes { left: 6.2%; top: 16%; }
.vf-config .vf-config__panel--colours.vf-config__panel--colours { left: 14.5%; top: 44%; }
/* The formats sit over the lower half of the capsule. */
.vf-config .vf-config__panel--formats.vf-config__panel--formats.vf-config__panel--formats { left: 40%; top: 69.6%; z-index: 3; }

/* A size chip: a text widget drawn as a disc, the chosen one in the band's
   own colour so it reads as a hole in the panel. */
.vf-config .vf-chip.vf-chip {
	width: auto;
	margin: 0;
	flex: none;
}

.vf-config .vf-chip.vf-chip p {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 7.75cqw;
	height: 7.75cqw;
	margin: 0;
	border-radius: 50%;
	background: rgb(255 255 255 / 0.16);
	font-family: var(--vf-font-display);
	font-size: 2.1cqw;
	line-height: 1;
	transition: background 320ms var(--vf-motion-ease);
}

.vf-config .vf-chip.is-on.is-on p {
	background: var(--vf-color-primary);
}

/* A colour swatch: a text widget whose Elementor background is the colour,
   drawn as a disc with its label hidden; the chosen one wears a ring. */
.vf-config .vf-swatch.vf-swatch.vf-swatch {
	flex: none;
	width: 7.75cqw;
	height: 7.75cqw;
	margin: 0;
	border-radius: 50%;
	outline: 0.35cqw solid transparent;
	outline-offset: 0.45cqw;
	transition: outline-color 320ms var(--vf-motion-ease);
}

.vf-config .vf-swatch.vf-swatch .elementor-widget-container {
	width: 100%;
	height: 100%;
	border-radius: 50%;
	background: inherit;
}

.vf-config .vf-swatch.vf-swatch p {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: 0;
	overflow: hidden;
	clip-path: inset(50%);
}

.vf-config .vf-swatch.is-on.is-on {
	outline-color: var(--vf-color-on-dark);
}

/* A format: an image widget in a disc, the icon dark on the translucent
   ones and white on the chosen one. */
.vf-config .vf-format.vf-format.vf-format {
	display: flex;
	flex: none;
	align-items: center;
	justify-content: center;
	width: 7.75cqw;
	height: 7.75cqw;
	margin: 0;
	border-radius: 50%;
	background: rgb(255 255 255 / 0.16);
	transition: background 320ms var(--vf-motion-ease);
}

/* The icon is sized by its height; the image widget's own width (set per
   element by Elementor, and inline by the preview) must not stretch it. */
.vf-config .vf-format.vf-format.vf-format img {
	display: block;
	width: auto !important;
	max-width: none;
	height: 3.6cqw !important;
	transition: filter 320ms var(--vf-motion-ease);
}

.vf-config .vf-format.vf-format .elementor-widget-container {
	display: flex;
	width: auto;
	align-items: center;
	justify-content: center;
}

.vf-config .vf-format.is-on.is-on {
	background: var(--vf-color-primary);
}

.vf-config .vf-format.is-on.is-on img {
	filter: brightness(0) invert(1);
}

/* On a phone the column is narrow and the discs would be 25px: hold the
   piece at the panel's natural scale by letting the rows overflow a little. */
@media (max-width: 767px) {
	.vf-config .vf-config__panel.vf-config__panel.vf-config__panel { gap: 1.4cqw; padding: 1.4cqw; }
	.vf-config .vf-chip.vf-chip p { font-size: 2.6cqw; }
}

@media (prefers-reduced-motion: reduce) {
	.vf-config .vf-config__img.vf-config__img.vf-config__img,
	.vf-config .vf-config__img img,
	.vf-config .vf-chip.vf-chip p,
	.vf-config .vf-swatch.vf-swatch.vf-swatch,
	.vf-config .vf-format.vf-format.vf-format,
	.vf-config .vf-format.vf-format img {
		transition: none;
	}
}

/* ---------------------------------------------------------------------------
 * The catalogue pages (Pharma, equipment page)
 *
 * Three pages fanned behind each other, the cover on top at the left, the two
 * spreads stepping down and to the right behind it. Three image widgets in
 * one box; the box keeps the cover's aspect and the pages are placed as a
 * percentage of it.
 * ------------------------------------------------------------------------ */
.vf-stack.vf-stack.vf-stack {
	position: relative;
	display: block;
	width: 100%;
	aspect-ratio: 16 / 12;
	padding: 0;
}

.vf-stack .vf-stack__page.vf-stack__page {
	position: absolute;
	width: 80%;
	margin: 0;
}

.vf-stack .vf-stack__page.vf-stack__page.vf-stack__page img {
	display: block;
	width: 100%;
	height: auto;
	border-radius: 6px;
	box-shadow: 0 24px 60px rgb(0 0 0 / 0.35);
}

.vf-stack .vf-stack__page--1.vf-stack__page--1 { left: 0; top: 0; z-index: 3; }
.vf-stack .vf-stack__page--2.vf-stack__page--2 { left: 7%; top: 16%; z-index: 2; }
.vf-stack .vf-stack__page--3.vf-stack__page--3 { left: 14%; top: 32%; z-index: 1; }

/* ---------------------------------------------------------------------------
 * A grid of checks (Pharma, "Know what you are working with")
 *
 * The same tick and line as the White Label checklist, in two rows of three
 * across a photograph instead of a looping column, and at the layout's
 * smaller size: 26px on a band, not 40px beside a headline.
 * ------------------------------------------------------------------------ */
.vf-checkgrid .vf-check.vf-check p {
	font-size: 26px;
	line-height: 1.2;
	color: inherit;
}

.vf-checkgrid .vf-check__mark {
	flex-basis: 22px;
	height: 20px;
}

@media (max-width: 767px) {
	.vf-checkgrid .vf-check.vf-check p {
		font-size: 20px;
	}
}

/*
 * The formulator section stacks below 1280px. Two columns at 42/58 leave the
 * heading a 420px column at 1150, where "Customize your idea in minutes." at
 * the display size broke a word to a line. Stacked, the render goes first
 * (the thing the section is about), capped so it does not fill a tablet
 * edge to edge, and the copy takes the full width under it. Four class
 * names: the widths are per element settings at three.
 */
@media (max-width: 1280px) {
	.vf-formulator__copy.vf-formulator__copy.vf-formulator__copy.vf-formulator__copy,
	.vf-formulator__media.vf-formulator__media.vf-formulator__media.vf-formulator__media {
		--width: 100%;
	}

	.vf-formulator__media.vf-formulator__media.vf-formulator__media.vf-formulator__media {
		order: -1;
		max-width: 760px;
		margin: 0 auto 8px;
	}

	.vf-formulator__copy.vf-formulator__copy.vf-formulator__copy.vf-formulator__copy {
		max-width: 720px;
		margin: 0 auto;
	}
}

/* On a phone the render is a third of the size and the boxes would be 19px. */
@media (max-width: 767px) {
	.vf-compose .vf-compose__spot.vf-compose__spot { width: 11%; }
	.vf-compose .vf-compose__cursor.vf-compose__cursor { width: 10%; }
}

@media (prefers-reduced-motion: reduce) {
	.vf-compose .vf-compose__spot.vf-compose__spot,
	.vf-compose .vf-compose__cursor.vf-compose__cursor {
		transition: none;
	}
}

/*
 * A boxed Elementor container puts its children inside .e-con-inner, so a
 * min-height on the outer element does not reach them. Auto margins then have
 * no free space to distribute and the hero's two blocks sit together at the
 * top instead of spreading. Letting the inner element grow is what makes the
 * layout above actually work.
 */
.vf-hero > .e-con-inner,
.vf-hero > .vf-inner {
	flex: 1 1 auto;
	display: flex;
	flex-direction: column;
	width: 100%;
}

/*
 * Hover legibility guarantee.
 *
 * The hover colours are set in the widget data so they stay editable, but the
 * outline button flips to a white fill on hover and its label is white at rest.
 * If the colour setting fails to land for any reason, the button becomes
 * invisible rather than merely off-brand, so this is worth forcing.
 */
.vf-el-btn--outline .elementor-button:hover,
.vf-el-btn--outline .elementor-button:focus,
.vf-el-btn--inverse .elementor-button:hover,
.vf-el-btn--inverse .elementor-button:focus {
	color: var(--vf-color-primary) !important;
}

.vf-el-btn--outline .elementor-button:hover,
.vf-el-btn--outline .elementor-button:focus {
	/*
	 * Forced as a pair. The outline button's hover is a white fill with a brand
	 * blue label, and half of that landing is worse than neither: white on white
	 * is invisible, blue on a dark photograph is barely better. Pinning both
	 * keeps the state legible whatever else happens.
	 */
	background-color: var(--vf-color-surface) !important;
	border-color: var(--vf-color-surface) !important;
}


/* ---------------------------------------------------------------------------
 * Journey
 *
 * Five steps on a connector line, as in the layout: a hairline across the row
 * with a ring on each step. It is drawn rather than placed, because a row of
 * five images that has to stay aligned with five text columns at every width is
 * a maintenance problem, and this is one rule.
 * ------------------------------------------------------------------------ */

/* The band clips, so the last connector can run off the right edge without
   widening the document. A stray 50vw pseudo element is one of the commonest
   causes of a page that scrolls sideways, and it is invisible until it happens. */
/*
 * Doubled, because a single class lost this fight and the failure was a page
 * that scrolled sideways.
 *
 * The last step's connector runs 50vw past its own box, which is how the line
 * reaches the edge of the screen in the layout. That only works if the band
 * clips, and the band did not: Elementor sets a container's overflow from its
 * own settings and the computed value here was `visible`, so on a 2457px screen
 * the line ended 527px beyond the document and the whole page could be dragged
 * right, with no way back except a reload. Safari let you do it; Chrome had the
 * same overflow and simply refused to scroll to it.
 *
 * `clip` rather than `hidden`: `hidden` makes the element a scroll container,
 * which changes how anything sticky inside it behaves. `clip` only clips.
 */
.vf-journey-band.vf-journey-band {
	overflow-x: clip;
	overflow-y: visible;
}

.vf-journey.vf-journey {
	position: relative;
	/* Two class names: `.e-con-full { padding-block-start: var(...) }` loads after this. */
	padding-top: 52px;
}

.vf-step {
	position: relative;
	min-width: 0;
}

/*
 * The step title has to shrink with its column, and this is the one heading on
 * the site where that is true.
 *
 * Five columns share the row, and the longest word in the section is
 * "Manufacturing": 6.72em in Mabry, 215px at the design's 32px against a 238px
 * column. Every width below the design narrows the column while a fixed size
 * does not narrow the word, and the result is "Manufacturi / ng" broken across
 * two lines, which is what `overflow-wrap` does when a word cannot fit: it is a
 * safety net, not a design.
 *
 * This used to be a ladder of fixed sizes at fixed breakpoints, which held at
 * the widths it was checked at and broke between them: 26px fits a 1440
 * column and not a 1317 one. So the size is now the column arithmetic itself,
 * per layout below: the row's width (the 1520 container, or the viewport less
 * the gutters and a scrollbar) minus the gaps, over five, over 6.9em, capped
 * at the design size. `vw` rather than `cqi` on purpose: a container query
 * unit silently falls back to a percentage of the viewport when
 * `container-type` does not apply, which is how these titles once rendered at
 * 52px on the live site while looking correct everywhere else.
 *
 * Written as a font-size on a selector that outranks Elementor's blanket
 * `.elementor-widget-heading .elementor-heading-title` rule, which binds every
 * heading on the page to the `primary` global and was rendering these at its
 * 80px ceiling.
 */
.vf-step.vf-step .elementor-widget-heading .elementor-heading-title {
	font-size: 32px;
	overflow-wrap: break-word;
}

/* Five across on the design's 82px pitch: the column is (row - 328px) / 5. */
@media (min-width: 1441px) {
	.vf-step.vf-step .elementor-widget-heading .elementor-heading-title {
		font-size: min(32px, calc((min(1520px, 100vw - 176px) - 328px) / 34.5));
	}
}

/*
 * Journey layouts by width.
 *
 * The design's five across on an 82px pitch works down to about 1560px. Below
 * that the section was five columns of two words a line, and by 1300 the
 * titles broke mid word. So the row changes shape as it narrows, and the
 * column widths, gaps and connector lines change with it:
 *
 *   to 1560   five across, the copy a step smaller
 *   to 1440   five across, pitch tightened to 40px
 *   to 1280   three across, then two: the line runs on past the row's last
 *             step to the edge, as it does past the final step, so the path
 *             reads as continuing on the next row
 *   to 1024   two across on a 30px pitch (Elementor's tablet layout)
 *   to 767    stacked, no line, no dots (below)
 *
 * The widths are Elementor settings on the steps, written by vf_col(), so
 * they are overridden here through the same `--width` custom property the
 * setting resolves to, at four class names against the setting's three. The
 * gaps likewise through `--column-gap` and `--row-gap`, at four as well,
 * since Elementor writes a tablet gap at three inside the same media range. Ranges rather than
 * ceilings, so no layout has to undo the one above it and the panel's own
 * tablet and phone widths are left alone.
 */
@media (min-width: 1025px) and (max-width: 1440px) {
	.vf-journey.vf-journey.vf-journey.vf-journey {
		--column-gap: 40px;
	}

	.vf-journey .vf-step.vf-step.vf-step {
		--width: calc((100% - 160px) / 5);
	}

	.vf-step.vf-step::before {
		width: calc(100% + 40px);
	}

	.vf-step.vf-step .elementor-widget-heading .elementor-heading-title {
		font-size: min(32px, calc((100vw - 336px) / 34.5));
	}
}

/* Five across under 200px a column: the copy comes down a step, 18 to 16. */
@media (min-width: 1281px) and (max-width: 1560px) {
	.vf-journey .vf-step.vf-step .elementor-widget-text-editor {
		font-size: 16px;
	}
}

@media (min-width: 1025px) and (max-width: 1280px) {
	.vf-journey.vf-journey.vf-journey.vf-journey {
		/* Room above the second row for its dots and line, which sit 42px up. */
		--row-gap: 72px;
	}

	.vf-journey .vf-step.vf-step.vf-step {
		--width: calc((100% - 80px) / 3);
	}

	/* A column is at least 256px here, so the design size fits again. */
	.vf-step.vf-step .elementor-widget-heading .elementor-heading-title {
		font-size: 32px;
	}

	/* The row's last step runs on to the edge, as the final one does. */
	.vf-step.vf-step:nth-child(3)::before {
		width: 100vw;
	}
}

@media (min-width: 768px) and (max-width: 1024px) {
	.vf-journey.vf-journey.vf-journey.vf-journey {
		--column-gap: 30px;
		--row-gap: 72px;
	}

	.vf-step.vf-step::before {
		width: calc(100% + 30px);
	}

	.vf-step.vf-step:nth-child(2n)::before {
		width: 100vw;
	}
}

/*
 * The line runs from each step's ring to the next one's.
 *
 * Written at two class names, and every property re-stated, because the step is
 * an Elementor container and Elementor uses `::before` on those for the
 * background overlay:
 *
 *   .e-con::before { content: var(--background-overlay);
 *                    width: max(100% + ...); height: max(100% + ...) }
 *
 * That is a single class selector, exactly as specific as `.vf-step::before`,
 * and its stylesheet loads after this one, so it won. `content` resolved to an
 * unset variable, which is `none`, and the connector between the steps simply
 * did not exist on the page: no error, no clue, just a missing line in a
 * section whose whole point is the line.
 */
.vf-step.vf-step::before {
	content: "";
	position: absolute;
	top: -37px;
	left: 5px;
	width: calc(100% + 82px);
	height: 1px;
	/*
	 * Nearly opaque, not a wash. At 45% on a sunlit running track the line was
	 * there in the markup and invisible on the page, which is the same thing as
	 * not being there.
	 */
	background: rgb(255 255 255 / 0.8);
}

.vf-step--last.vf-step--last::before {
	/* The last step's line carries on to the edge of the viewport, which is
	   what the layout shows. The band clips it, so it cannot widen the page. */
	width: 100vw;
}

.vf-step.vf-step::after {
	content: "";
	position: absolute;
	top: -42px;
	left: 0;
	width: 11px;
	height: 11px;
	border-radius: 50%;
	border: 1px solid var(--vf-color-on-dark);
	background: rgb(10 30 50 / 0.35);
}

/* The final ring is filled, which is how the layout marks the destination. */
.vf-step--last.vf-step--last::after {
	background: var(--vf-color-on-dark);
}

@media (max-width: 767px) {
	.vf-journey.vf-journey {
		padding-top: 32px;
	}

	.vf-step.vf-step::before {
		display: none;
	}

	/*
	 * No dot either.
	 *
	 * The dots and the line between them are one device: the line says the five
	 * steps are a sequence and the dots mark the stops along it. Stacked into a
	 * single column the line is gone, and a lone ring above each heading marks
	 * nothing. Dropping it takes 42px of dead space off each step as well.
	 */
	.vf-step.vf-step::after {
		display: none;
	}
}

/* ---------------------------------------------------------------------------
 * Support band
 *
 * The brand colour with the soft light behind the conversation that the layout
 * has. One radial gradient rather than a background image, so it stays crisp at
 * any width and costs nothing to download.
 * ------------------------------------------------------------------------ */

.vf-support.vf-support {
	position: relative;
	isolation: isolate;
	/* Two class names: `.e-con { overflow: var(--overflow) }` loads after this. */
	overflow: hidden;
}

.vf-support.vf-support::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	background:
		radial-gradient(60% 90% at 78% 42%, rgb(120 176 224 / 0.42), transparent 70%),
		radial-gradient(46% 70% at 62% 78%, rgb(90 150 205 / 0.28), transparent 72%);
	pointer-events: none;
}

/* ---------------------------------------------------------------------------
 * Expansion section
 *
 * The world map is a section background rather than an image widget, because
 * the copy and the places sit on top of it and it carries on behind the cards
 * below. It is deliberately almost invisible: that is what the layout has.
 * ------------------------------------------------------------------------ */

/*
 * The map's size and position are the section's own background settings
 * (`background_size: initial` with a 1400px width, 900 on a phone, placed at
 * 40% down), not rules here. Elementor prints those settings per element at
 * four class names, so a `background-size` in this file was dead on the site,
 * which showed the map at `cover`, zoomed into Europe.
 */

/* ---------------------------------------------------------------------------
 * Group bar height
 *
 * Fixed, and not by accident. The site header is positioned over the hero and
 * offset by exactly this much, so the two have to agree on one number. Leaving
 * the bar to size itself from its own fluid type would move the header every
 * time the type scale changed.
 * ------------------------------------------------------------------------ */

.vf-groupbar__inner {
	min-height: 92px;
}

@media (max-width: 767px) {
	.vf-groupbar__inner {
		min-height: 72px;
	}
}

/* ---------------------------------------------------------------------------
 * The divisions panel, where it meets the page
 *
 * Each row is a rounded-top card tucked under the one above it. The last row
 * has nothing above it to tuck under, so its two top corners cut back to the
 * white page and left a pair of small white notches sitting on the hero.
 *
 * The fix is to let the last card carry on past the panel rather than to round
 * or square anything: it gets a deep bottom padding, the page is pulled up over
 * it by the same amount, and the hero's own rounded top corners then sit on the
 * card's colour instead of on white. Nothing is covered up, the card is simply
 * longer than the panel, which is what the notches were asking for.
 * ------------------------------------------------------------------------ */

/*
 * The last row's tuck is the page overlap rather than the next row's 24px, so
 * its padding is `--vf-tuck` on its own. It used to carry that *plus* the 48px
 * every row had, which put 24px more of it on screen than the rows above and is
 * why the bottom card read as taller than the rest.
 */
.vf-divisions__item:last-child .vf-divisions__row {
	min-height: calc(var(--vf-row-visible) + var(--vf-tuck, 56px));
	padding-bottom: var(--vf-tuck, 56px);
}

/*
 * Below the desktop layout the rows are content sized, so the last one has to
 * be too, and this has to sit *here* rather than up with the other tablet
 * rules: a media query adds no specificity, so a `min-height: 0` written
 * earlier in the file loses to the three class name rule directly above. The
 * symptom was the bottom card standing 16px prouder than the four above it on a
 * phone, which is the same asymmetry the fixed strip was added to remove.
 */
@media (max-width: 1024px) {
	.vf-divisions__item:last-child .vf-divisions__row {
		min-height: 0;
	}
}

/*
 * Keyed off the root element rather than off a sibling.
 *
 * The panel and the page are siblings in the theme's own header markup and are
 * not siblings at all under the Theme Builder, where the panel sits several
 * levels inside the header location. The sibling selectors are kept for a page
 * rendered before the script runs; the root class is what actually holds.
 */
.vf-divisions.is-open + *,
.vf-divisions.is-open ~ .vf-shell,
.vf-divisions-open .vf-shell,
.vf-divisions-open .elementor-location-header + * {
	margin-top: calc(var(--vf-tuck, 56px) * -1);
}

/*
 * The header travels with the page.
 *
 * Everything else moves because it is in flow; the header is positioned over
 * the hero and would sit still while the panel opened across it. It is moved by
 * the panel's measured height less the tuck, which is exactly how far the page
 * below it moved, so the header stays in the same place relative to the hero it
 * belongs to. Not while the header is stuck to the top of the viewport: there
 * the panel is scrolled away and has nothing to say about it.
 */
html.vf-divisions-open:not(.vf-header-stuck) .vf-elheader.vf-elheader.vf-elheader {
	transform: translateY( calc( var(--vf-divisions-h, 0px) - var(--vf-tuck, 56px) ) );
	transition: transform var(--vf-motion-slow) var(--vf-motion-ease);
}

.vf-elheader {
	transition: transform var(--vf-motion-slow) var(--vf-motion-ease);
}

/* ---------------------------------------------------------------------------
 * The header clears the group bar by measurement, not by a typed number
 *
 * The header is an absolutely positioned container whose vertical offset is an
 * Elementor control: 92px on desktop, 72px on a phone. That is two numbers that
 * have to stay in step with a third they cannot see, which is how tall the
 * group bar happens to be at that width. On a narrow viewport they fell out of
 * step and the logo and the pill came down on top of the bar.
 *
 * `--vf-groupbar-h` is the bar's real height, written to the root by
 * `initStickyHeader()` on load, on scroll and on resize. Taking the offset from
 * it means the header sits under the bar whatever the bar does, including the
 * label wrapping to a second line on a small phone.
 *
 * Scoped to tablet and phone on purpose. On desktop the typed 92 is a design
 * decision about how far the header floats down over the hero, not a clearance,
 * and it stays in the designer's hands. Four class names, because the offset
 * arrives as per element CSS at two and that sheet loads later. The fallback
 * keeps the typed value if the script has not run yet.
 * ------------------------------------------------------------------------ */
@media (max-width: 1024px) {
	html:not(.vf-header-stuck) .vf-elheader.vf-elheader.vf-elheader.vf-elheader {
		top: var(--vf-groupbar-h, 72px);
	}
}

/*
 * The page has to sit above the panel for that overlap to read correctly, and
 * the panel above the group bar for the accordion to open over nothing.
 */
.vf-shell,
.elementor-location-header + * {
	position: relative;
	z-index: 1;
}

/* ---------------------------------------------------------------------------
 * The header navigation pill, in the Theme Builder header
 *
 * The pill sits on photography, and a translucent white on a busy photograph is
 * a legibility problem rather than a style. Blurring what is behind it gives the
 * labels a stable ground to sit on whatever the image happens to be doing.
 * ------------------------------------------------------------------------ */

/*
 * The blur is on a layer of its own under the pill, not on the pill.
 *
 * An element with a backdrop-filter is a backdrop root: anything inside it
 * that asks for a backdrop-filter of its own samples only what that element
 * paints, not the page behind it. The Solutions dropdown lives inside the
 * pill and asked for frosted glass; on the site it got a flat translucent box
 * with the photograph sharp behind it, because the pill's own blur was in the
 * way. With the pill's blur on a pseudo-element the pill is no longer a
 * backdrop root, and the panel's glass sees the photograph. The pseudo sits at
 * z-index -1 inside the header's stacking context (the header carries
 * z-index 30 from its settings), under the pill's fill and above the page.
 *
 * The same move ends the pill being a containing block for the fixed sheet
 * on a phone, which is why the toggle circle below had to switch its blur off.
 */
.vf-elnav::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	border-radius: inherit;
	pointer-events: none;
	-webkit-backdrop-filter: blur(14px) saturate(1.2);
	backdrop-filter: blur(14px) saturate(1.2);
}

/*
 * The pill's size, stated here because Elementor's kit will not.
 *
 * The kit carries a *default typography* for the nav menu widget and binds it
 * to the `primary` global, and writes it into every page's CSS as
 *
 *   .elementor-widget-nav-menu .elementor-nav-menu .elementor-item {
 *       font-size: var(--e-global-typography-primary-font-size) }
 *
 * `primary` is the display step, which tops out at 80px, so the five menu
 * labels rendered at headline size and the pill grew to 374px tall and covered
 * the hero. This is the third widget hit by the same mechanism, after the
 * heading on the page and the card titles; the fix is the same, one class more
 * than Elementor's three so the theme wins wherever the stylesheets land.
 *
 * 20px labels in a 60px pill, both from the layout.
 */
.vf-elnav.vf-elnav {
	min-height: 60px;
	align-items: center;
	/* The pill's padding is its own setting (4/16, header template); the
	   custom properties it prints per element outrank anything written here. */
}

.vf-elnav .elementor-widget-nav-menu .elementor-nav-menu .elementor-item {
	font-size: 20px;
	line-height: 1.2;
	padding-block: 6px;
}

/*
 * Every button at medium.
 *
 * Written at three class names, and the count is the whole point: Elementor
 * emits `.elementor-widget-button .elementor-button { font-weight: var(...) }`
 * into every page's own stylesheet, bound to the `accent` global, which is 400.
 * The first attempt at this used exactly that selector, so the two tied on
 * specificity and Elementor won on load order. Nothing changed on the page and
 * the rule looked correct in the file.
 *
 * Worth recording why 500 rather than "whatever the pill uses": the pill's
 * labels are Mabry Pro at 400 and the buttons are Montserrat, so matching the
 * number would have made them lighter still. They read as the same weight
 * because the two families are drawn differently, not because the values match.
 */
.elementor-widget-button.elementor-widget-button .elementor-button,
.elementor-widget-form.elementor-widget-form .elementor-button,
.vf-elheader .elementor-button.elementor-button,
.vf-btn.vf-btn {
	font-weight: var(--vf-weight-medium);
}

/* ---------------------------------------------------------------------------
 * The Solutions dropdown
 *
 * A floating white panel, so its items take the brand blue rather than the
 * white the labels on the bar above them use. The widget settings say the same
 * thing and are what a designer edits; these rules are the backstop, written at
 * one class more than Elementor's own per element selector, because a binding
 * that silently fails to emit has cost this build a day at a time.
 *
 * The panel itself gets the card language: rounded, a hairline, and a soft
 * shadow so it reads as sitting above the photograph rather than cut into it.
 * ------------------------------------------------------------------------ */

/*
 * The panel is the pill's material. Over a photograph that is frosted glass
 * with a deep blue cast and a hairline, the same recipe as the bar it hangs
 * from, so the two read as one object rather than a white card dropped onto
 * the page. The rows carry no fill; the row under the cursor and the current
 * page take a light wash, from the widget's own settings.
 *
 * Six class names on the panel: Elementor prints the dropdown's colour and
 * radius per element at (0,4,0) from the item settings, and on a white page
 * the rules further down have to turn the same panel into a white card.
 */
.vf-elnav.vf-elnav.vf-elnav.vf-elnav nav.elementor-nav-menu--main .sub-menu,
.vf-elnav.vf-elnav.vf-elnav.vf-elnav .elementor-nav-menu--main .sub-menu {
	min-width: 236px;
	padding: 8px;
	list-style: none;
	border-radius: var(--vf-radius-card);
	background-color: rgb(12 42 74 / 0.42);
	-webkit-backdrop-filter: blur(18px) saturate(1.25);
	backdrop-filter: blur(18px) saturate(1.25);
	box-shadow:
		inset 0 0 0 1px rgb(255 255 255 / 0.22),
		0 18px 44px rgb(4 22 44 / 0.28);
}

.vf-elnav.vf-elnav.vf-elnav.vf-elnav .elementor-nav-menu--dropdown a.elementor-sub-item {
	color: var(--vf-color-on-dark);
}

.vf-elnav.vf-elnav .elementor-nav-menu--main .elementor-sub-item {
	display: block;
	font-family: var(--vf-font-body);
	font-size: 17px;
	line-height: 1.35;
	white-space: nowrap;
	transition: background-color var(--vf-motion-fast) var(--vf-motion-ease),
		color var(--vf-motion-fast) var(--vf-motion-ease);
}

/*
 * Every row is the same pill. The widget's dropdown radius lands on three
 * selectors, not one: besides the panel, Elementor rounds the top corners of
 * the first row's link and the bottom corners of the last, per element at
 * (0,5,2), so a panel with square rows looks right out of the box. With pill
 * rows that gave White Label a square-topped hover and Manufacturing a
 * square-bottomed one while Private Label, in the middle, was the pill. The
 * harness never printed those two rules, so it showed three pills; it prints
 * them now, and this rule beats them on every row.
 */
.vf-elnav.vf-elnav.vf-elnav.vf-elnav .elementor-nav-menu--dropdown li > a.elementor-sub-item {
	border-radius: var(--vf-radius-pill);
}

/*
 * On a white page the glass has nothing to frost, so the panel becomes the
 * white card the pill on white is: a hairline, a soft float, labels in the
 * brand blue, and the hover and the current page as the tinted surface. Six
 * class names plus the body class, above the widget's per element colours.
 */
body.vf-header-solid .vf-elnav.vf-elnav.vf-elnav.vf-elnav nav.elementor-nav-menu--main .sub-menu,
body.vf-header-solid .vf-elnav.vf-elnav.vf-elnav.vf-elnav .elementor-nav-menu--main .sub-menu {
	background-color: var(--vf-color-surface);
	-webkit-backdrop-filter: none;
	backdrop-filter: none;
	box-shadow:
		inset 0 0 0 1px color-mix(in srgb, var(--vf-color-primary) 14%, transparent),
		0 12px 32px rgb(10 20 32 / 0.12);
}

body.vf-header-solid .vf-elnav.vf-elnav.vf-elnav.vf-elnav .elementor-nav-menu--dropdown a.elementor-sub-item {
	color: var(--vf-color-primary);
	background-color: transparent;
}

body.vf-header-solid .vf-elnav.vf-elnav.vf-elnav.vf-elnav .elementor-nav-menu--dropdown a.elementor-sub-item:hover,
body.vf-header-solid .vf-elnav.vf-elnav.vf-elnav.vf-elnav .elementor-nav-menu--dropdown a.elementor-sub-item:focus-visible,
body.vf-header-solid .vf-elnav.vf-elnav.vf-elnav.vf-elnav .elementor-nav-menu--dropdown a.elementor-sub-item.elementor-item-active {
	color: var(--vf-color-primary);
	background-color: var(--vf-color-surface-tint);
}

/*
 * The panel arrives from just under the label rather than appearing, and the
 * caret on Solutions turns to point at what it opened.
 */
.vf-elnav.vf-elnav .elementor-nav-menu--main .menu-item-has-children:hover > .sub-menu {
	animation: vf-dropdown-in var(--vf-motion-fast) var(--vf-motion-ease-settle) both;
}

@keyframes vf-dropdown-in {
	from {
		opacity: 0;
		transform: translateY(-6px);
	}

	to {
		opacity: 1;
		transform: none;
	}
}

/*
 * The caret flips, it does not turn, and the box it sits in does not move.
 *
 * Two things were wrong and they compounded. The rotation was applied to the
 * `.sub-arrow` span *and* to the `<i>` inside it, so the glyph turned twice and
 * came back where it started; and `.sub-arrow` carries Elementor's own
 * `padding: 10px; padding-inline-end: 0` with negative block margins, which is
 * a deliberately lopsided box, so turning that box swung the glyph across to
 * the other side of it. Together they read as the caret spinning and landing
 * somewhere else.
 *
 * So the span is squared off and pinned, and exactly one element rotates: the
 * glyph. No transition on either, because a 180 degree turn of a small solid
 * triangle has no readable in-between and the motion is just noise.
 */
.vf-elnav.vf-elnav .elementor-nav-menu--main .elementor-item .sub-arrow {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 1em;
	height: 1em;
	margin: 0 0 0 6px;
	padding: 0;
	transform: none;
	transition: none;
}

.vf-elnav.vf-elnav .elementor-nav-menu--main .elementor-item .sub-arrow i,
.vf-elnav.vf-elnav .elementor-nav-menu--main .elementor-item .sub-arrow svg,
.vf-elnav.vf-elnav .elementor-nav-menu--main .elementor-item .sub-arrow .e-font-icon-svg {
	display: block;
	transform: rotate(0deg);
	transition: none;
}

.vf-elnav.vf-elnav .elementor-nav-menu--main .menu-item-has-children:hover > .elementor-item .sub-arrow i,
.vf-elnav.vf-elnav .elementor-nav-menu--main .menu-item-has-children:hover > .elementor-item .sub-arrow svg,
.vf-elnav.vf-elnav .elementor-nav-menu--main .menu-item-has-children:hover > .elementor-item .sub-arrow .e-font-icon-svg {
	transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
	.vf-elnav.vf-elnav .elementor-nav-menu--main .menu-item-has-children:hover > .sub-menu {
		animation: none;
	}
}

/* ---------------------------------------------------------------------------
 * The header on a phone
 *
 * Elementor collapses the menu into a toggle here, and the desktop styling made
 * three separate messes of that: the pill kept the width it needs for five
 * labels and wrapped a 20px hamburger in a 500px lozenge; the panel it opens is
 * the same `--dropdown` element as the Solutions submenu, so it inherited the
 * white on white; and Book a Call, sized for a wide bar and rounded to a pill,
 * wrapped onto three lines and became a circle.
 * ------------------------------------------------------------------------ */

@media (max-width: 1024px) {
	/*
	 * Three class names on every selector in this block, not two.
	 *
	 * Elementor writes this widget's own padding and its dropdown colours into
	 * the page stylesheet at two class names, and that sheet loads after the
	 * theme's. Two here therefore ties and loses, which showed up twice: the
	 * pill kept its 16px side padding so the 52px toggle overflowed its own
	 * circle by exactly that much and sat right of centre, and the full screen
	 * menu's labels kept the brand blue meant for the white desktop dropdown,
	 * which on a brand blue sheet is invisible.
	 *
	 * The toggle is a circle, and getting a circle takes more than a radius.
	 *
	 * The button was a flex item in the header row, so it was free to stretch:
	 * `border-radius: 999px` on a box that is 52 wide and 58 tall draws a
	 * lozenge, not a circle, and the glyph inside inherited the row's alignment
	 * rather than centring. Fixed size on both axes, `aspect-ratio` as a
	 * guarantee, `flex: 0 0 auto` so nothing stretches it, and the toggle itself
	 * made a grid whose only cell is centred.
	 */
	.vf-elnav.vf-elnav.vf-elnav.vf-elnav {
		width: 52px;
		height: 52px;
		min-height: 0;
		aspect-ratio: 1;
		flex: 0 0 52px;
		align-self: center;
		--width: 52px;
		--padding-top: 0px;
		--padding-bottom: 0px;
		--padding-left: 0px;
		--padding-right: 0px;
		border-radius: 50%;
		overflow: visible;
		/* The fill is the container's own background setting (white at 16%). */
		box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.30);
		/*
		 * No blur here, and it is not a style decision.
		 *
		 * A `backdrop-filter` makes an element a containing block for any
		 * `position: fixed` descendant, and the full screen menu lives inside
		 * this pill. With the blur on, `inset: 0` resolved against a 52px circle
		 * and the sheet opened as a 390px panel hanging off the pill's corner,
		 * halfway down the screen and off the right edge. On a 52px circle the
		 * blur was worth almost nothing anyway.
		 */
		-webkit-backdrop-filter: none;
		backdrop-filter: none;
	}

	.vf-elnav::after {
		display: none;
	}

	/*
	 * The toggle stays a flex box, and that is the whole of the centring.
	 *
	 * This rule used to say `display: block`, which threw away Elementor's own
	 *
	 *   .elementor-menu-toggle { display:flex; align-items:center; justify-content:center }
	 *
	 * and left the glyph to sit wherever a block box put it. Everything that
	 * followed was an attempt to put it back: absolute positioning on the
	 * glyphs, then `display: block` on those glyphs to kill the baseline gap.
	 * That last one is what stacked the hamburger and the cross on top of each
	 * other, because the swap classes are on the glyphs themselves and a rule
	 * here outranks the swap.
	 *
	 * Giving the button back its flex centring on a fixed 52 square box centres
	 * the one visible glyph on both axes with no further help, and means nothing
	 * inside the toggle has to be positioned or given a `display` at all.
	 */
	/*
	 * The column the toggle lives in, and why it has to be told two things.
	 *
	 * Pro puts `display: flex; flex-direction: column` on
	 * `.elementor-widget-nav-menu .elementor-widget-container`, so inside the
	 * 52px pill the toggle is a flex item sharing that column with the closed
	 * dropdown. The dropdown is 0 tall but still carries Elementor's
	 * `margin-top: 10px`, so the column is asked to fit 62px of content into 52
	 * and does what a flex column does: it shrinks the item that can shrink and
	 * pins the rest to the top. The hamburger ended up above the middle of its
	 * own circle, which is what "not centred, sits at the top" was.
	 *
	 * Centring the column would not have been enough on its own; the toggle also
	 * has to refuse to shrink, and the 10px has to go. It is spacing for a small
	 * panel hanging under a button, and this dropdown is a full screen sheet
	 * pinned to the viewport, so it was never doing anything here anyway.
	 */
	/*
	 * Measured on the site rather than reasoned about, because the markup is not
	 * what it looks like from Pro's stylesheet.
	 *
	 *   .elementor-widget-nav-menu .elementor-widget-container,
	 *   .elementor-widget-nav-menu:not(:has(.elementor-widget-container)):not([class*=elementor-hidden-])
	 *     { display: flex; flex-direction: column }
	 *
	 * This widget renders *without* a `.elementor-widget-container`, so it is
	 * the second branch that applies and the flex column is the widget element
	 * itself. A rule written for the container matched nothing at all.
	 *
	 * In that column the toggle is 52px and the closed dropdown, though 0 tall,
	 * still contributes its own margin. The widget measured 84px inside a 52px
	 * pill whose `align-items: center` then hung it 16px over each end, so the
	 * toggle, perfectly centred within itself, sat 16px above the middle of the
	 * circle. Live measurement: pill centre y 118, toggle centre y 102.
	 *
	 * Centring the column does not fix that: the toggle is one of two items and
	 * the dropdown's margin sits under it, so the *pair* centres and the toggle
	 * still rides high. Taking the closed panel out of the flow leaves the
	 * column with exactly one item, and 52px of content in a 52px box has
	 * nowhere to go. The open panel is already `position: fixed`, so this only
	 * ever describes the closed one.
	 */
	.vf-elnav.vf-elnav.vf-elnav .elementor-widget-nav-menu,
	.vf-elnav.vf-elnav.vf-elnav .elementor-widget-container {
		justify-content: center;
		min-height: 0;
	}

	.vf-elnav.vf-elnav.vf-elnav .elementor-menu-toggle:not(.elementor-active)
		+ nav.elementor-nav-menu--dropdown {
		position: absolute;
		margin: 0;
	}

	.vf-elnav.vf-elnav.vf-elnav .elementor-menu-toggle {
		position: relative;
		display: flex;
		align-items: center;
		justify-content: center;
		flex: 0 0 52px;
		width: 52px;
		height: 52px;
		margin: 0;
		padding: 0;
		border: 0;
		border-radius: 50%;
		background: transparent;
		line-height: 0;
	}

	/*
	 * The toggle's colour, at five class names and setting `fill` as well.
	 *
	 * The nav widget's "dropdown item colour" is set to the brand blue, because
	 * that is what the white desktop dropdown needs. Elementor applies that one
	 * control to two different things at once:
	 *
	 *   {{WRAPPER}} .elementor-nav-menu--dropdown a,
	 *   {{WRAPPER}} .elementor-menu-toggle  =>  color: {{VALUE}}; fill: {{VALUE}};
	 *
	 * so the setting that makes the desktop dropdown readable also paints the
	 * phone hamburger. That lands at four class names in the page stylesheet,
	 * which ties with a four class rule here and wins on load order: the toggle
	 * rendered rgb(16, 76, 122) over a near black hero, measured on the site.
	 * Five settles it. `fill` is named too, because that rule sets both and the
	 * glyph is an inline SVG.
	 */
	.vf-elnav.vf-elnav.vf-elnav.vf-elnav .elementor-menu-toggle {
		/* White over photography; the solid and stuck headers redefine the
		   variable, so this follows the header rather than fighting it. */
		color: var(--e-global-color-vf_on_dark, var(--vf-color-on-dark));
		fill: currentColor;
	}

	/*
	 * Size and colour only. Never `display`, and never `position`.
	 *
	 * Elementor swaps the hamburger for the cross by hiding one of them:
	 *
	 *   .elementor-menu-toggle.elementor-active .elementor-menu-toggle__icon--open,
	 *   .elementor-menu-toggle:not(.elementor-active) .elementor-menu-toggle__icon--close
	 *     { display: none }
	 *
	 * and those classes sit on the glyph elements themselves. `nav-menu.php`
	 * only wraps a glyph in a span when the icon is a custom uploaded SVG:
	 *
	 *   $is_normal_icon_svg = 'svg' === $normal_icon['library'];
	 *   if ( $is_normal_icon_svg ) { echo '<span class="' . $open_class . '">'; }
	 *
	 * This site sets no toggle icon, so the default eicons are used, no span is
	 * written, and the swap class is on the `<svg>`. Any `display` declared here
	 * is four class names against the swap's three, so it wins and both glyphs
	 * render at once. That is the X laid over the bars, and it shipped twice.
	 *
	 * A flex item is blockified, so the svg needs no `display` to lose its
	 * baseline gap: being a child of the flex toggle above is enough.
	 */
	.vf-elnav.vf-elnav.vf-elnav .elementor-menu-toggle svg,
	.vf-elnav.vf-elnav.vf-elnav .elementor-menu-toggle i {
		width: 22px;
		height: 22px;
		margin: 0;
		padding: 0;
		font-size: 22px;
		line-height: 1;
		color: inherit;
		fill: currentColor;
	}

	/*
	 * The call to action comes off the phone header entirely, as asked. It is
	 * the widest thing in a 350px row, and it reappears at the foot of the
	 * full screen menu, which is a better place for it anyway.
	 */
	.vf-elheader .elementor-widget-button {
		display: none;
	}

	/* ---------------------------------------------------------------------
	 * The full screen menu
	 *
	 * Elementor's own mobile dropdown is a small panel hanging off the toggle.
	 * At this width that is a lot of white for five links, and it leaves the
	 * page showing round the edges. This takes the same element, which is the
	 * markup Elementor already renders and already opens and closes, and gives
	 * it the whole viewport: brand ground, the primary links at a size worth
	 * tapping, the Solutions children indented beneath their parent rather than
	 * hidden behind a second toggle, and the call to action at the bottom.
	 * ------------------------------------------------------------------ */

	/*
	 * Everything below hangs off `.elementor-active`, and that is the whole
	 * point of this block rather than a detail of it.
	 *
	 * Elementor does not hide a closed dropdown with `display: none`. It hides
	 * it with three properties together, from
	 * `widget-nav-menu.min.css`:
	 *
	 *   .elementor-nav-menu--toggle .elementor-menu-toggle:not(.elementor-active)
	 *     + .elementor-nav-menu__container { max-height:0; overflow:hidden; transform:scaleY(0) }
	 *
	 * The full screen sheet used to be written at the unqualified nav, so it
	 * set `transform: none` and `overflow-y: auto` on the closed panel too and
	 * cancelled two of those three. What was left was `max-height: 0` on a
	 * border-box element carrying 104px of top padding and 40px of bottom, and
	 * a box cannot be shorter than its own padding: the closed menu painted its
	 * brand gradient as a 144px block across the top of every phone page, over
	 * the group bar and the top of the hero. Measured in the preview at
	 * 390x844: 390x144 at 0,0, transform none, overflow-y auto.
	 *
	 * So: the sheet styling applies only while the toggle is active, and the
	 * closed panel gets its padding taken away and nothing else, leaving
	 * Elementor's own hide intact.
	 *
	 * `nav.` still matters. Elementor puts `elementor-nav-menu--dropdown` on two
	 * different things: the <nav> that holds the mobile menu, and every
	 * <ul class="sub-menu"> inside any menu. Without the element in the
	 * selector these rules turn the Solutions submenu into a second full screen
	 * sheet, which the preview showed the moment it started rendering both.
	 */
	.vf-elnav.vf-elnav.vf-elnav .elementor-menu-toggle:not(.elementor-active)
		+ nav.elementor-nav-menu--dropdown {
		padding: 0;
		border: 0;
	}

	/*
	 * `margin-top` is the one declaration here that needs `!important`, and it
	 * is not a shortcut.
	 *
	 * The gap under the desktop Solutions dropdown is the nav widget's own
	 * "Distance" control, and Elementor hardcodes the flag into the control's
	 * selector template:
	 *
	 *   '{{WRAPPER}} .elementor-nav-menu--main > … > .elementor-nav-menu--dropdown,
	 *    {{WRAPPER}} .elementor-nav-menu__container.elementor-nav-menu--dropdown'
	 *      => 'margin-top: {{SIZE}}{{UNIT}} !important'
	 *
	 * One control, two targets: the same 12px that offsets the desktop dropdown
	 * from the pill also pushed the full screen sheet 12px down the phone
	 * screen, leaving a strip of page above it. Measured on the site: sheet top
	 * 12, margin-top 12px !important from post-260.css.
	 *
	 * No specificity beats `!important`, and zeroing the control would take the
	 * desktop gap with it, since the flag is on that selector too. So the sheet
	 * says `!important` back, scoped to the open state alone.
	 */
	.vf-elnav.vf-elnav.vf-elnav .elementor-menu-toggle.elementor-active
		+ nav.elementor-nav-menu--dropdown {
		position: fixed;
		inset: 0;
		width: 100vw;
		height: 100dvh;
		max-width: none;
		min-width: 0;
		margin: 0 !important;
		padding: 28px 28px 40px;
		border-radius: 0;
		transform: none;
		overflow-y: auto;
		overscroll-behavior: contain;
		background: linear-gradient(
			160deg,
			var(--vf-color-primary) 0%,
			var(--vf-color-primary-deep) 62%,
			#0B3557 100%
		);
		box-shadow: none;
		z-index: 40;
	}

	/*
	 * The close control is taken out of the header while the sheet is open.
	 *
	 * It used to be `position: relative` inside the pill, which is inside the
	 * header, and the header moves: it translates when the group accordion is
	 * open and it is replaced by a fixed bar once the page is scrolled. The X
	 * went with it, which is why it was there sometimes and gone others. Fixed
	 * to the viewport, it is in the same place whatever the header is doing.
	 *
	 * `position: fixed` still resolves against an ancestor that has a
	 * transform, so `.vf-menu-open` below takes the header's transform away for
	 * as long as the sheet is open.
	 */
	.vf-elnav.vf-elnav.vf-elnav .elementor-menu-toggle {
		position: relative;
		z-index: 50;
	}

	.vf-elnav.vf-elnav.vf-elnav .elementor-menu-toggle.elementor-active {
		position: fixed;
		top: 26px;
		right: 20px;
		z-index: 60;
		width: 52px;
		height: 52px;
		border-radius: 50%;
		background: rgb(255 255 255 / 0.12);
	}

	/*
	 * The lockup at the head of the sheet, cloned from the header by
	 * `initMenuState()`. It sits on the same line as the close control, which is
	 * fixed at the top right, so it takes the left of that line and the menu
	 * starts underneath.
	 */
	.vf-elnav.vf-elnav.vf-elnav .elementor-active + nav.elementor-nav-menu--dropdown .vf-menu-brand {
		display: flex;
		align-items: center;
		min-height: 52px;
		margin-bottom: 34px;
		padding-right: 72px;
	}

	.vf-elnav.vf-elnav.vf-elnav .elementor-active + nav.elementor-nav-menu--dropdown .vf-menu-brand a {
		display: block;
		line-height: 0;
	}

	.vf-elnav.vf-elnav.vf-elnav .elementor-active + nav.elementor-nav-menu--dropdown .vf-menu-brand img {
		display: block;
		width: auto;
		height: 34px;
		max-width: none;
	}

	.vf-elnav.vf-elnav.vf-elnav .elementor-active + nav.elementor-nav-menu--dropdown .elementor-nav-menu {
		display: flex;
		flex-direction: column;
		gap: 0;
		/*
		 * The list fills what is left under the lockup rather than the whole
		 * sheet, or the auto margin that pins the call to action to the foot
		 * would push it a lockup's height past the bottom edge.
		 */
		min-height: calc(100% - 86px);
	}

	/*
	 * No rules between the links.
	 *
	 * Every item carried a hairline and every child carried a second one down
	 * its left edge, which on a sheet holding four links and three children is
	 * eight lines drawn to separate seven things that the type already
	 * separates. Spacing and the drop from 34px display to 18px body do that
	 * work on their own. The only line left in the sheet is the outline of the
	 * call to action at the foot, which is a button rather than a divider.
	 */
	.vf-elnav.vf-elnav.vf-elnav .elementor-active + nav.elementor-nav-menu--dropdown .elementor-item {
		font-family: var(--vf-font-display);
		font-size: 32px;
		line-height: 1.2;
		font-weight: var(--vf-weight-regular);
		color: var(--vf-color-on-dark);
		background: transparent;
		padding: 13px 0;
		border: 0;
		border-radius: 0;
	}

	.vf-elnav.vf-elnav.vf-elnav .elementor-active + nav.elementor-nav-menu--dropdown .elementor-item:hover,
	.vf-elnav.vf-elnav.vf-elnav .elementor-active + nav.elementor-nav-menu--dropdown .elementor-item.elementor-item-active {
		color: var(--vf-color-on-dark);
		background: transparent;
		opacity: 0.72;
	}

	/*
	 * The Solutions children are always visible here. A submenu behind a second
	 * tap, inside a menu that is already behind one tap, is two taps to reach a
	 * page: the whole point of this sheet is that everything is one reach away.
	 *
	 * They are set in from the left with padding rather than a border, so the
	 * indent reads without drawing anything.
	 */
	.vf-elnav.vf-elnav.vf-elnav .elementor-active + nav.elementor-nav-menu--dropdown .sub-menu {
		display: block;
		background: transparent;
		box-shadow: none;
		padding: 2px 0 14px;
		margin: 0;
	}

	.vf-elnav.vf-elnav.vf-elnav .elementor-active + nav.elementor-nav-menu--dropdown .elementor-sub-item {
		font-family: var(--vf-font-body);
		font-size: 17px;
		line-height: 1.5;
		color: rgb(255 255 255 / 0.72);
		background: transparent;
		padding: 8px 0 8px 20px;
		border: 0;
		margin: 0;
		border-radius: 0;
	}

	.vf-elnav.vf-elnav.vf-elnav .elementor-active + nav.elementor-nav-menu--dropdown .elementor-sub-item:hover {
		color: var(--vf-color-on-dark);
		background: transparent;
	}

	/*
	 * The call to action, rebuilt at the foot of the sheet from the menu's own
	 * last item rather than added as markup, so it stays in step with whatever
	 * the menu holds.
	 */
	.vf-elnav.vf-elnav.vf-elnav .elementor-active + nav.elementor-nav-menu--dropdown .elementor-nav-menu > li:last-child {
		margin-top: auto;
		padding-top: 32px;
	}

	.vf-elnav.vf-elnav.vf-elnav .elementor-active + nav.elementor-nav-menu--dropdown .elementor-nav-menu > li:last-child .elementor-item {
		font-family: var(--vf-font-body);
		font-size: 17px;
		text-align: center;
		padding: 17px 28px;
		border: 1px solid rgb(255 255 255 / 0.5);
		border-radius: var(--vf-radius-pill);
	}
}

/* ---------------------------------------------------------------------------
 * The homepage on a phone
 *
 * A phone has no hover and no patience for a 19,000px page. Everything in this
 * block is one of those two facts: a reveal nobody can trigger is shown
 * outright, and the sections that were tallest are compressed.
 * ------------------------------------------------------------------------ */

@media (max-width: 767px) {
	/* The card's phone padding is its own `padding_mobile` setting (32/28). A
	   value written here at two class names lost to it, per element, on the site. */

	/*
	 * The journey band gets a scrim back, and only here.
	 *
	 * The overlays came off site wide so the photography reads at its own
	 * brightness, which is right everywhere the copy sits on its own ground.
	 * This band is the exception: on a phone the five steps stack down the whole
	 * frame, including the bright track through the middle of the picture, and
	 * white on white is not a design decision. It is a gradient under the copy
	 * rather than a flat dim over the picture, so the photograph still reads.
	 */
	.vf-journey-band {
		position: relative;
		isolation: isolate;
	}

	.vf-journey-band.vf-journey-band::before {
		content: "";
		position: absolute;
		inset: 0;
		z-index: -1;
		/*
		 * Flat, not a gradient. The gradient kept the top of the band bright,
		 * which is where the section heading sits, so the part that most needed
		 * help got the least of it. One even tone reads as a deliberate dark
		 * panel rather than as a photograph someone has half covered.
		 */
		background: rgb(8 20 32 / 0.58);
		pointer-events: none;
	}

	/* And it is shorter: 82px between five stacked steps is a screen of nothing. */
	.vf-journey.vf-journey.vf-journey {
		gap: 40px;
	}

	/*
	 * The conversation goes above the copy.
	 *
	 * It is what the section is about, and a stacked row follows source order,
	 * which put a heading, a paragraph and two buttons in front of it. The two
	 * columns are siblings, so one `order` moves it.
	 */
	/*
	 * Two class names on every `order` in this block, and the reason is one
	 * rule in Elementor's frontend.min.css:
	 *
	 *   .elementor-element { order: var(--order) }
	 *
	 * One class, and that stylesheet loads after this one, so every ordering
	 * written here at a single class tied and lost. The conversation stayed
	 * under its own copy and the FAQ box stayed above the questions, while both
	 * rules sat in the stylesheet looking correct. Measured on the site:
	 * computed `order: 0` on all three.
	 */
	.vf-chat.vf-chat {
		order: -1;
	}

	/*
	 * The product render goes above the copy in the formulator section, for the
	 * same reason the conversation does in the support band: it is the thing the
	 * section is about, and a stacked row follows source order, which buried it
	 * under a heading, a paragraph and a button.
	 */
	.vf-formulator__media.vf-formulator__media {
		order: -1;
	}


	/*
	 * The location names centre over the map rather than inside their own boxes.
	 *
	 * Each pill is an inline span in a full width HTML widget, so the row
	 * centred the widgets and the text sat wherever it fell within them.
	 */
	.vf-places.vf-places .vf-place-wrap {
		width: 100%;
		text-align: center;
	}

	/* The pill is a flex box, so it centres with an auto margin rather than
	   with the text alignment above; both are set because the label inside it
	   needs the alignment and the box needs the margin. */
	.vf-places.vf-places .vf-place {
		margin-inline: auto;
		justify-content: center;
	}

	/* ---------------------------------------------------------------------
	 * White Label and Private Label on a phone
	 * ------------------------------------------------------------------ */

	/*
	 * The panels narrow and shorten rather than stacking. They are a moving
	 * row now, so the phone shows the same range in a window a third of the
	 * height the stacked lists took.
	 */
	.vf-picker-vp.vf-picker-vp {
		--vf-mq-gap: 16px;
	}

	.vf-picker--1 .vf-picker__list { height: 210px; }
	.vf-picker--2 .vf-picker__list { height: 290px; }
	.vf-picker--3 .vf-picker__list { height: 245px; }
	.vf-picker--4 .vf-picker__list { height: 225px; }
	.vf-picker--5 .vf-picker__list { height: 265px; }

	/* The checklist window shortens with the type it holds. */
	.vf-checks.vf-checks.vf-checks {
		--vf-mq-gap: 18px;

		height: 300px;
		padding-left: 0;
	}

	.vf-checks__track.vf-checks__track {
		row-gap: 18px;
	}

	.vf-check.vf-check p {
		gap: 12px;
		font-size: 26px;
	}

	.vf-check__mark {
		flex: 0 0 18px;
		height: 16px;
	}

	/*
	 * The collage stops scattering and becomes a grid.
	 *
	 * The offsets are what makes it read as samples on a desk across a wide
	 * row; in three narrow columns they are just gaps, and the headline and
	 * the paragraph have no holes left to sit in. Three across, square, no
	 * offsets, copy above and below: the same five samples in a quarter of the
	 * space.
	 */
	.vf-collage.vf-collage.vf-collage {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		column-gap: 16px;
		row-gap: 16px;
	}

	.vf-collage.vf-collage.vf-collage {
		grid-template-rows: none;
	}

	.vf-collage > .elementor-element,
	.vf-collage [class*="vf-collage__item--"] {
		grid-row: auto;
		grid-column: auto;
		margin-top: 0;
	}

	.vf-collage__lead.vf-collage__lead,
	.vf-collage__note.vf-collage__note {
		grid-column: 1 / -1;
		grid-row: auto;
		width: auto;
		margin-top: 0;
	}

	.vf-collage__lead.vf-collage__lead {
		margin-bottom: 0;
	}

	.vf-collage__img img {
		aspect-ratio: 1;
	}

	/*
	 * The bottle artwork goes above the copy, for the same reason the
	 * conversation and the formulator render do: it is what the section is
	 * about, and a stacked row otherwise buries it under a heading, a
	 * paragraph and a button.
	 */
	.vf-artwork.vf-artwork {
		order: -1;
	}

	/*
	 * The "let's talk" box goes after the questions.
	 *
	 * It is nested in the left column beside the heading, not a sibling of the
	 * questions, so `order` on its own cannot reach across. `display: contents`
	 * takes the column out of the box tree and promotes its two children into
	 * the row, where all three can be ordered together.
	 */
	.vf-faq-aside.vf-faq-aside.vf-faq-aside {
		display: contents;
	}

	.vf-faq-aside.vf-faq-aside > .elementor-element:first-child {
		order: 1;
	}

	.vf-faq-questions.vf-faq-questions {
		order: 2;
	}

	.vf-faq-cta.vf-faq-cta {
		order: 3;
		margin-top: 8px;
	}
}

/* ---------------------------------------------------------------------------
 * While the full screen menu is open
 *
 * The page behind it does not scroll, and the header does not move.
 *
 * The second half is what keeps the close control on screen. A `position:
 * fixed` element resolves against the nearest ancestor with a transform, not
 * against the viewport, and the header has two: it translates while the group
 * accordion is open, and it runs a drop animation when it becomes the stuck
 * bar. Either one would capture the fixed X and carry it off with the header,
 * which is why it was there sometimes and gone others. Taking both away for as
 * long as the sheet is open leaves the X pinned to the viewport, where it was
 * asked to be.
 *
 * `vf-menu-open` is set on the root by `initMenuState()` in theme.js, which
 * watches the toggle's own class rather than binding a second click handler.
 * ------------------------------------------------------------------------ */
.vf-menu-open,
.vf-menu-open body {
	overflow: hidden;
}

/*
 * Written at six class names because it has to outrank two other rules that
 * both put a transform on this same element:
 *
 *   html.vf-divisions-open:not(.vf-header-stuck) .vf-elheader×3   (0,5,1)
 *   .vf-header-stuck body .vf-elheader×3                          (0,4,1)
 *
 * At four it lost to the first of those, so opening the group accordion and
 * then the menu left the header translated, the sheet's `inset: 0` resolved
 * against the translated header instead of the viewport, and the menu opened
 * 463px down the page with the page showing above it. Measured at 390x844:
 * sheet top 555 with the header at translateY(463px).
 */
html.vf-menu-open.vf-menu-open.vf-menu-open .vf-elheader.vf-elheader.vf-elheader,
html.vf-menu-open.vf-menu-open.vf-menu-open body .vf-elheader.vf-elheader.vf-elheader {
	transform: none;
	animation: none;
	transition: none;
}

/*
 * A phone has 350px of usable width, and the logo, the pill and the button have
 * to share it. Holding the desktop gap and button padding put Book a Call 10px
 * past the right edge and gave every page a sideways scroll.
 */
@media (max-width: 767px) {
	.vf-elheader.vf-elheader {
		gap: 12px;
	}

}

/* ---------------------------------------------------------------------------
 * Two grounds for the header: photography, and white
 *
 * The header is drawn for a hero: a white lockup and white labels on a
 * photograph. Insights, Privacy and every other page that opens on the page
 * background put that same white artwork on white, and it disappears.
 *
 * So the header has two states, and the page says which one it needs. The
 * installer stamps `_vitalforce_header_ground` on each page from whether its
 * first section is a hero, and a body_class filter turns that into
 * `vf-header-over` or `vf-header-solid`. Anything the theme did not install is
 * solid, because a page nobody has designed is far more likely to be white.
 *
 * The recolouring is done by redefining the global colour inside the pill
 * rather than by out-specifying Elementor's generated rules. Elementor writes
 * `color: var(--e-global-color-vf_on_dark)` onto the menu at a specificity that
 * a stylesheet can only beat by stacking class names or shouting !important,
 * and both of those break the moment a designer changes the colour in the
 * panel. Redefining the variable for the subtree leaves the panel in charge of
 * everything else and changes only what is inherited here.
 * ------------------------------------------------------------------------ */

/*
 * An article on a site that puts the header inside the article band
 * (`vitalforce/article_header_ground`, Cosmetics): the band is the brand
 * colour, so the Book a Call button, also the brand colour, would vanish
 * into it. The layout draws it one step deeper. Redefining the global the
 * button reads, for this one widget, keeps the panel in charge of the rest.
 */
body.single-vf_insight.vf-header-over .vf-elheader .elementor-widget-button,
body.vf-header-over:has(.vf-article__head) .vf-elheader .elementor-widget-button {
	--e-global-color-primary: var(--vf-color-primary-deep);
}

/* The brand lockup is the exception rather than the default. */
.vf-logo--solid {
	display: none;
}

.vf-elheader .vf-sitelogo {
	transition: opacity var(--vf-motion-base) var(--vf-motion-ease);
}

body.vf-header-solid .vf-logo--over,
.vf-header-stuck body .vf-logo--over {
	display: none;
}

body.vf-header-solid .vf-logo--solid,
.vf-header-stuck body .vf-logo--solid {
	display: block;
}

/*
 * On a white page the header still floats, but the page makes room for it.
 *
 * Over a hero the header is positioned absolutely so the photograph runs up
 * behind it, which is the whole point of that layout. A page that opens on the
 * page background has nothing to run behind, so the same absolute header lands
 * on top of the first heading.
 *
 * The fix is clearance rather than putting the header back in flow, because a
 * header in flow has to leave flow again the moment it sticks, and the page
 * jumps by its own height when it does. It stays out of flow in both states,
 * and the header location carries a matching pad. The height is measured in
 * JavaScript into --vf-header-h so it cannot drift from the padding and type
 * the header actually has; the fallback is the height it has at the shipped
 * settings.
 */
body.vf-header-solid .elementor-location-header {
	padding-bottom: var(--vf-header-h, 148px);
}

/*
 * The white "hero" behind the header on a page without one.
 *
 * Opening the divisions accordion tucks the page 56px under the last card. On
 * a designed page the hero, full width with 30px top corners, is what covers
 * that strip. A white page has nothing there: the card ran on under the logo
 * and the pill, half on blue and half on white. This paints the same shape the
 * hero has, on the header container itself, so it travels with the header
 * when the panel opens and covers the card's tail exactly as the hero does.
 *
 * `::before` on a container is Elementor's background overlay at (0,1,1);
 * three class names plus the body class outrank it. Behind the header's own
 * content: the container is positioned with a z-index, so it is a stacking
 * context and -1 stays inside it.
 */
body.vf-header-solid .vf-elheader.vf-elheader.vf-elheader::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	background-color: var(--vf-color-surface);
	border-radius: var(--vf-radius-card, 30px) var(--vf-radius-card, 30px) 0 0;
	pointer-events: none;
}

/* Not while stuck to the viewport: there the bar is a strip, not a hero. */
.vf-header-stuck body.vf-header-solid .vf-elheader.vf-elheader.vf-elheader::before {
	display: none;
}

/*
 * The pill on white: no fill, a hairline, and the labels in the Vitalforce
 * Labs blue. The translucent white it wears over photography is invisible on
 * a white page, and a tinted surface was tried and preferred less; the outline
 * alone is what the site had while the tint was silently losing to the pill's
 * own background setting, and it is what the designer asked to keep.
 *
 * Three class names plus the body class, because that background setting is
 * printed per element at (0,4,0) and this has to outrank it to turn it off.
 */
body.vf-header-solid .vf-elnav.vf-elnav.vf-elnav,
.vf-header-stuck body .vf-elnav.vf-elnav.vf-elnav {
	--e-global-color-vf_on_dark: var(--vf-color-primary);
	background-color: transparent;
	box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--vf-color-primary) 16%, transparent);
}

/* ---------------------------------------------------------------------------
 * The sticky header
 *
 * Scrolling takes the group bar and the divisions accordion with it and leaves
 * the logo, the pill and the Book a Call button pinned to the top. Coming back
 * to the top of the page puts the group bar back above them.
 *
 * The position has to be written at four class names because Elementor emits
 * `position: absolute` and `top: 92px` from the container's own Position
 * controls, onto a selector built from two class names, and per-element CSS is
 * printed after the theme's stylesheet. Repeating the class is the cheap way to
 * win that without !important, which would also block the panel.
 *
 * It slides in rather than appearing, because a bar that materialises over the
 * content mid-scroll reads as a glitch.
 * ------------------------------------------------------------------------ */

.vf-header-stuck body .vf-elheader.vf-elheader.vf-elheader {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	/*
	 * Elementor containers take their padding from these two custom properties,
	 * so the stuck bar sets them rather than the padding itself: a designer who
	 * changes the header padding in the panel changes the resting header, and
	 * this stays the tighter bar it is meant to be.
	 */
	--padding-top: 10px;
	--padding-bottom: 10px;
	background: none;
	animation: vf-header-drop var(--vf-motion-base) var(--vf-motion-ease-settle) both;
}

/*
 * The glass sits on a pseudo-element, and that is what makes it smooth.
 *
 * The first version put the gradient and the `backdrop-filter` on the header
 * itself. A backdrop-filter has no falloff: it applies at full strength right
 * up to the element's edge and then stops dead, so however carefully the
 * background colour faded, the blur underneath it ended on a hard horizontal
 * line a third of the way down. That line is what read as "not smooth", and no
 * amount of adjusting the colour stops could have fixed it.
 *
 * On its own layer the blur can be masked, and a masked backdrop-filter fades
 * out properly. The mask cannot go on the header, because it would take the
 * logo and the navigation with it.
 *
 * The stops are many and close together on purpose. Three stops across 100px of
 * near-black alpha is where banding shows on an ordinary screen; stepping it
 * finer costs nothing and the edges disappear.
 */
.vf-header-stuck body .vf-elheader.vf-elheader.vf-elheader::after {
	content: "";
	position: absolute;
	/*
	 * 16px below the bar is the whole fade. The bar itself is 80px, so the
	 * darkened band tops out at 96px: under the 100px ceiling, and about a third
	 * of what the first attempt covered.
	 */
	inset: 0 0 -16px 0;
	z-index: -1;
	pointer-events: none;
	background: linear-gradient(
		to bottom,
		rgb(9 20 32 / 0.50) 0%,
		rgb(9 20 32 / 0.47) 18%,
		rgb(9 20 32 / 0.41) 34%,
		rgb(9 20 32 / 0.32) 50%,
		rgb(9 20 32 / 0.22) 64%,
		rgb(9 20 32 / 0.13) 76%,
		rgb(9 20 32 / 0.06) 87%,
		rgb(9 20 32 / 0.02) 95%,
		rgb(9 20 32 / 0) 100%
	);
	-webkit-backdrop-filter: blur(14px) saturate(1.15);
	backdrop-filter: blur(14px) saturate(1.15);
	-webkit-mask-image: linear-gradient(
		to bottom,
		#000 0%,
		#000 42%,
		rgb(0 0 0 / 0.86) 58%,
		rgb(0 0 0 / 0.62) 72%,
		rgb(0 0 0 / 0.34) 84%,
		rgb(0 0 0 / 0.12) 93%,
		transparent 100%
	);
	mask-image: linear-gradient(
		to bottom,
		#000 0%,
		#000 42%,
		rgb(0 0 0 / 0.86) 58%,
		rgb(0 0 0 / 0.62) 72%,
		rgb(0 0 0 / 0.34) 84%,
		rgb(0 0 0 / 0.12) 93%,
		transparent 100%
	);
}

/*
 * And because the stuck bar is dark, the header keeps its "over photography"
 * artwork while it is stuck, on a white page as much as on the homepage.
 */
.vf-header-stuck body .vf-logo--over {
	display: block;
}

.vf-header-stuck body .vf-logo--solid {
	display: none;
}

.vf-header-stuck body .vf-elnav.vf-elnav.vf-elnav {
	--e-global-color-vf_on_dark: var(--vf-color-on-dark);
	background-color: rgb(255 255 255 / 0.16);
	box-shadow: none;
}

/* The pill sets the bar's height, so it comes down a little while stuck. */
.vf-header-stuck body .vf-elnav.vf-elnav {
	min-height: 52px;
}

@keyframes vf-header-drop {
	from {
		transform: translateY(-100%);
	}

	to {
		transform: translateY(0);
	}
}

@media (prefers-reduced-motion: reduce) {
	.vf-header-stuck body .vf-elheader.vf-elheader.vf-elheader {
		animation: none;
	}
}

/* ---------------------------------------------------------------------------
 * The FAQ list
 *
 * The widget's own settings do the work: one hairline above each question, the
 * plus on the right, no card. What is left here is the two things a setting
 * cannot say, which are how the plus should behave and how the answer should
 * read against the question.
 * ------------------------------------------------------------------------ */

/* The group heading: General, Formulation. 32px, like every other h3 in the
   design, rather than the 80px the blanket heading rule was giving it. */
.vf-faq-heading.vf-faq-heading .elementor-heading-title,
.elementor-widget-heading.vf-faq-heading .elementor-heading-title {
	font-size: 32px;
	line-height: 1.3;
}

/*
 * Every selector here is doubled, and that is deliberate.
 *
 * Elementor styles this widget at `.elementor-widget-n-accordion .e-n-...`,
 * which is two class names, from a stylesheet that loads after the theme's. The
 * first version of this block used two as well, so it tied and lost: the title
 * kept Elementor's own layout, `justify-content` never applied, and the rule
 * meant to hide Elementor's chevron never ran, which is why the supplied plus
 * appeared on top of Elementor's minus instead of replacing it.
 *
 * The markup, for reference, because it is not obvious:
 *
 *   <summary class="e-n-accordion-item-title">
 *     <span class="e-n-accordion-item-title-header">
 *       <div class="e-n-accordion-item-title-text">…</div>
 *     </span>
 *     <span class="e-n-accordion-item-title-icon">
 *       <span class="e-opened">…</span><span class="e-closed">…</span>
 *     </span>
 *   </summary>
 *
 * The question is two elements deep, so the thing that has to grow is the
 * header span, not the text inside it. Growing the text did nothing, which is
 * the other half of why the icon sat beside the question rather than at the far
 * right of the row.
 */
.vf-faq.vf-faq .e-n-accordion-item-title {
	cursor: pointer;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 24px;
	width: 100%;
}

.vf-faq.vf-faq .e-n-accordion-item-title-header {
	flex: 1 1 auto;
	min-width: 0;
}

.vf-faq.vf-faq .e-n-accordion-item-title-text {
	font-family: var(--vf-font-body);
	font-size: 20px;
	line-height: 1.4;
	font-weight: var(--vf-weight-regular);
	color: var(--vf-color-ink-500);
}

/*
 * The supplied plus, drawn as a mask so it is the exact artwork from the design
 * file and takes its colour from the text. Elementor ships two glyphs in here,
 * one for each state, and both are hidden: leaving them visible is what put an
 * asterisk on the page where a plus belongs.
 */
.vf-faq.vf-faq .e-n-accordion-item-title-icon {
	flex: 0 0 16px;
	width: 16px;
	height: 16px;
	position: relative;
	margin-left: auto;
}

/*
 * Elementor's own glyphs are hidden with `visibility`, not `display`.
 *
 * There are two of them, one per state, and the widget switches between them by
 * setting `display` on whichever should show. A `display: none` from here is
 * therefore in a fight it can lose, and did: the closed rows took the supplied
 * plus correctly while every open row drew Elementor's minus straight through
 * it, which is where the asterisk came from.
 *
 * `visibility` is not a property the widget touches, and it inherits, so one
 * declaration on the container hides both glyphs whatever `display` they are
 * given. The mask below turns itself back on explicitly.
 */
.vf-faq.vf-faq .e-n-accordion-item-title-icon {
	visibility: hidden;
}

.vf-faq.vf-faq .e-n-accordion-item-title-icon > span,
.vf-faq.vf-faq .e-n-accordion-item-title-icon svg,
.vf-faq.vf-faq .e-n-accordion-item-title-icon i {
	display: none;
}

.vf-faq.vf-faq .e-n-accordion-item-title-icon::after {
	content: "";
	position: absolute;
	inset: 0;
	visibility: visible;
	background-color: var(--vf-color-ink-700);
	-webkit-mask: var(--vf-plus-icon) center / 16px 16px no-repeat;
	mask: var(--vf-plus-icon) center / 16px 16px no-repeat;
	transition: transform var(--vf-motion-base) var(--vf-motion-ease);
}

/* Open turns the plus into a cross, which is the design's own behaviour. */
.vf-faq.vf-faq details[open] > .e-n-accordion-item-title .e-n-accordion-item-title-icon::after {
	transform: rotate(45deg);
}

/*
 * The answer reads at the same size as its question, with no box around it.
 * The widget's default is a bordered panel at a larger size than the question,
 * which put the answer above its own question in the hierarchy.
 */
.vf-faq.vf-faq .e-n-accordion-item > .e-con,
.vf-faq.vf-faq .e-n-accordion-item-content,
.vf-faq.vf-faq .e-n-accordion-item-content > .e-con {
	border: 0;
	box-shadow: none;
	background: transparent;
}

.vf-faq.vf-faq .e-n-accordion-item-content,
.vf-faq.vf-faq .e-n-accordion-item-content p,
.vf-faq.vf-faq .e-n-accordion-item-content .elementor-widget-text-editor,
.vf-faq.vf-faq .e-n-accordion-item-content .elementor-widget-container {
	font-family: var(--vf-font-body);
	font-size: 20px;
	line-height: 1.55;
	color: var(--vf-color-ink-500);
}

.vf-faq-group {
	width: 100%;
}

/* ---------------------------------------------------------------------------
 * Footer links
 *
 * A link inside a text widget takes the theme's link colour, which is the brand
 * blue, and the footer is the brand blue: the links were there, clickable, and
 * completely invisible. They inherit the surrounding colour instead, and say so
 * by underlining on hover.
 * ------------------------------------------------------------------------ */

.vf-footer-link a {
	color: inherit;
	text-decoration: none;
	transition: opacity var(--vf-motion-base) var(--vf-motion-ease);
}

.vf-footer-link a:hover,
.vf-footer-link a:focus-visible {
	text-decoration: underline;
	text-underline-offset: 4px;
}

/* The newsletter field, which is a real form rather than a styled label. */
/*
 * The subscribe field, on the brand blue footer.
 *
 * The colours are widget settings now, not rules here: Elementor writes them
 * into the template's per element CSS, which no theme stylesheet outranks, so
 * fighting them from here never worked. What is left is the two things a
 * setting cannot express, which are the placeholder and the browser's autofill
 * styling, and autofill matters because a password manager repaints the field
 * solid white the moment it touches it.
 */
.vf-newsletter input[type="email"] {
	background: transparent;
	border: 0;
	border-bottom: 1px solid rgb(255 255 255 / 0.45);
	border-radius: 0;
	padding: 10px 0;
	color: var(--vf-color-on-dark);
	font-family: var(--vf-font-body);
	font-size: var(--vf-size-body-l);
}

.vf-newsletter input[type="email"]::placeholder {
	color: rgb(255 255 255 / 0.7);
}

.vf-newsletter input[type="email"]:focus {
	outline: none;
	border-bottom-color: var(--vf-color-on-dark);
}

/*
 * No focus ring around a field, on any of the site's forms. Elementor draws
 * one for every focused field:
 *
 *   .elementor-field-group .elementor-field-textual:focus {
 *       box-shadow: 0 0 0 1px rgba(0,0,0,.1) inset; outline: 0 }
 *
 * a faint box around a field the layout draws as a single underline. Four
 * class names, because the underline itself is the form's own field border
 * setting, which Elementor prints per element at (0,6,0); this has to outrank
 * that too or the focus weight would never apply. Focus is still visible: the
 * underline doubles in weight and, on the contact form, takes the brand blue,
 * which is a clearer indicator than the ring was.
 */
.vf-newsletter.vf-newsletter.vf-newsletter.vf-newsletter .elementor-field-group .elementor-field-textual:focus,
.vf-contactform.vf-contactform.vf-contactform.vf-contactform .elementor-field-group .elementor-field-textual:focus {
	box-shadow: none;
	outline: none;
	border-bottom-width: 2px;
}

.vf-contactform.vf-contactform.vf-contactform.vf-contactform .elementor-field-group .elementor-field-textual:focus {
	border-bottom-color: var(--vf-color-primary);
}

/*
 * Nothing for the button here. Its shape and colours are the form widget's own
 * button settings (installer.php and pages.php), printed per element after
 * this stylesheet at a specificity a theme rule does not reach.
 */

/* ---------------------------------------------------------------------------
 * The newsletter layout: field on top, button beneath it, small print beside
 * the button.
 *
 * This is a grid with named areas rather than the flex row Elementor builds,
 * and that is a correction, not a preference.
 *
 * `.elementor-form-fields-wrapper` is `display: flex; flex-wrap: wrap`, the
 * fields are printed in array order and the submit group is always the last
 * child. The previous attempt at this layout put `order: -1` on the submit
 * group to bring the button left of the note. But `order` sorts the whole flex
 * container, not the row an item happens to land in: it moved the button ahead
 * of *every* field, so the wrap became button, then field, then note, each on
 * its own line. That is exactly what shipped.
 *
 * The other half of it is that `width: 65` and `button_width: 35` are not
 * values the widget offers. Elementor writes `elementor-col-65` and
 * `elementor-col-35`, and its stylesheet only carries rules for the widths in
 * its own dropdown, so neither class had any width at all and both groups sized
 * to their content. Those are 66 and 33 now, which are real.
 *
 * Grid placement is immune to both problems: areas decide where things sit,
 * whatever order the widget prints them in and whatever widths it does or does
 * not apply.
 * ------------------------------------------------------------------------ */
.vf-newsletter.vf-newsletter .elementor-form-fields-wrapper {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	grid-template-areas:
		"field  field"
		"button note";
	align-items: center;
	column-gap: 24px;
	row-gap: 28px;
	/*
	 * Elementor writes the form's column and row gaps as negative margins on
	 * this wrapper and matching padding on each group, per element and after
	 * this stylesheet. Zeroing them here lost; the form's own `column_gap` and
	 * `row_gap` settings are 0 instead, so the grid's gaps are the only ones.
	 */
}

.vf-newsletter.vf-newsletter .elementor-field-group {
	width: auto;
}

.vf-newsletter.vf-newsletter .elementor-field-type-email {
	grid-area: field;
}

.vf-newsletter.vf-newsletter .e-form__buttons {
	grid-area: button;
	display: flex;
	align-items: center;
	justify-content: flex-start;
}

.vf-newsletter.vf-newsletter .elementor-field-group-note {
	grid-area: note;
	display: flex;
	align-items: center;
}

.vf-newsletter__note {
	margin: 0;
	font-family: var(--vf-font-body);
	font-size: 13px;
	line-height: 1.45;
	color: var(--vf-color-on-dark-muted);
	opacity: 0.85;
}

/* On a phone the three stack, and the note goes under the button. */
@media (max-width: 767px) {
	.vf-newsletter.vf-newsletter .elementor-form-fields-wrapper {
		grid-template-columns: minmax(0, 1fr);
		grid-template-areas:
			"field"
			"button"
			"note";
		row-gap: 20px;
	}

	.vf-newsletter.vf-newsletter .e-form__buttons {
		justify-content: stretch;
	}
}

/* ---------------------------------------------------------------------------
 * Editorial: Insights, Contact and the article template
 *
 * Three pages, one card and two bands between them. Everything here is either
 * a shape Elementor has no control for, or a size that has to be pinned
 * because Elementor's blanket heading rule would otherwise set it at the
 * display step.
 * ------------------------------------------------------------------------ */

/* The Insights and Contact heroes set their copy left, not centred. */
.vf-hero--left .elementor-heading-title {
	max-width: none;
	margin-inline: 0;
}

/* ---------------------------------------------------------------------------
 * The article card
 *
 * A square still, the title, the byline and a text link. Used in the archive
 * grid on Insights and again as a carousel slide under an article.
 * ------------------------------------------------------------------------ */
.vf-post__img img {
	width: 100%;
	height: auto;
	aspect-ratio: 1;
	object-fit: cover;
	transition: transform var(--vf-motion-settle) var(--vf-motion-ease);
}

/*
 * The still eases in a touch while the card is hovered, the same gesture the
 * product cards make. The widget clips it, so the card's rounded corner holds.
 */
.vf-post .vf-post__img.vf-post__img {
	overflow: hidden;
	/* The same radius the image's own corner setting gives it (vf_image()),
	   so the clip is the picture's shape and not a square around it. */
	border-radius: var(--vf-radius-card);
}

.vf-post:hover .vf-post__img img,
.vf-post:focus-within .vf-post__img img {
	transform: scale(1.035);
}

@media (prefers-reduced-motion: reduce) {
	.vf-post:hover .vf-post__img img,
	.vf-post:focus-within .vf-post__img img {
		transform: none;
	}
}

.vf-post.vf-post .vf-post__title .elementor-heading-title {
	font-size: 40px;
	line-height: 1.15;
}

.vf-post--slide.vf-post--slide .vf-post__title .elementor-heading-title {
	font-size: 28px;
}

.vf-post__meta p {
	margin: 0;
	color: var(--vf-color-ink-700);
}

.vf-post__meta strong {
	color: var(--vf-color-ink-900);
	font-weight: var(--vf-weight-semibold);
}

/*
 * "Read article" with the arrow that follows it. The glyph is a mask so it
 * takes the label's colour in both the ink and the on-dark version, and it
 * slides on hover, which is the only motion the layout asks of this link.
 */
/* lint-ok: the widget's hover background is set to rgba(0,0,0,0) by the installer, so the per element hover rule that outranks this one asks for the same transparent fill */
.vf-el-btn--link .elementor-button,
.vf-el-btn--link-inverse .elementor-button {
	display: inline-flex;
	align-items: center;
	gap: 10px;
	padding: 0;
	background: transparent;
	box-shadow: none;
}

.vf-el-btn--link .elementor-button:hover,
.vf-el-btn--link-inverse .elementor-button:hover {
	transform: none;
	box-shadow: none;
}

/*
 * The same cue inside a card that is itself a link.
 *
 * A card whose whole box is the anchor cannot hold a button widget: an anchor
 * inside an anchor is invalid markup, and Elementor says so on the control.
 * So "Read article" is a span there, and it takes the same arrow.
 */
.vf-post__more p {
	margin: 0;
	font-weight: var(--vf-weight-semibold);
}

.vf-more::after,
.vf-el-btn--link .elementor-button-text::after,
.vf-el-btn--link-inverse .elementor-button-text::after {
	content: "";
	display: inline-block;
	width: 18px;
	height: 13px;
	margin-left: 10px;
	vertical-align: -1px;
	background: currentColor;
	transition: transform var(--vf-motion-fast) var(--vf-motion-ease);
	-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 14' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M0 7h18M12.5 1.5L19 7l-6.5 5.5'/%3E%3C/svg%3E") center / contain no-repeat;
	mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 14' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M0 7h18M12.5 1.5L19 7l-6.5 5.5'/%3E%3C/svg%3E") center / contain no-repeat;
}

.vf-el-btn--link .elementor-button:hover .elementor-button-text::after,
.vf-el-btn--link-inverse .elementor-button:hover .elementor-button-text::after,
.vf-post:hover .vf-more::after,
.vf-spotlight:hover .vf-more::after {
	transform: translateX(4px);
}

/* A card that is a link is still a card. */
.vf-post.vf-post,
.vf-spotlight.vf-spotlight {
	text-decoration: none;
	color: inherit;
}

/* ---------------------------------------------------------------------------
 * The spotlight card
 *
 * Half brand blue and half photograph, in one rounded box. The two halves have
 * to meet with no gap and no wrap, so the row carrying them says so, and the
 * media half has no content of its own: it is a background, and its height
 * comes from the copy beside it.
 * ------------------------------------------------------------------------ */
.vf-spotlight.vf-spotlight .vf-spotlight__copy .elementor-heading-title {
	font-size: 44px;
	line-height: 1.15;
}

/*
 * The spotlight runs one slide across the whole width, so the arrows belong
 * beside the heading rather than over the card.
 */
.vf-carousel .vf-spotlight {
	height: 100%;
}

/* ---------------------------------------------------------------------------
 * The newsletter photograph band
 *
 * The same form as the footer's, on a photograph, with the layout's filled
 * button. Only the note needs saying here: on this band it sits beside the
 * button rather than under the whole form, which is the grid the footer
 * newsletter already uses.
 * ------------------------------------------------------------------------ */
.vf-newsletter--band.vf-newsletter--band .elementor-field-group-note {
	align-self: center;
}

/* ---------------------------------------------------------------------------
 * The contact form
 *
 * Four fields two across, then the message, then the button with its note
 * beside it. The widths are the widget's own; what is here is the vertical
 * rhythm, which Elementor sets far tighter than the layout draws.
 * ------------------------------------------------------------------------ */
.vf-contactform.vf-contactform .elementor-form-fields-wrapper {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
}

/*
 * The gutter between the half width fields and the 44px rhythm between rows
 * are the form's own `column_gap` (30) and `row_gap` (44, 32 on a phone)
 * settings: Elementor writes them as padding on each group and negative
 * margins on the wrapper, per element and after this stylesheet, so the same
 * values written here were dead and the site showed the widget's defaults.
 */

.vf-contactform.vf-contactform .elementor-field-group-message {
	padding-top: 24px;
}

.vf-contactform.vf-contactform textarea {
	resize: vertical;
	min-height: 96px;
}

/*
 * The button before the note.
 *
 * Elementor always prints the submit group last, and the note is a field, so
 * left alone the note comes first and the button lands to the right of it,
 * which is the wrong way round. Ordering the note after the submit is enough:
 * every other group keeps its document order at the default.
 */
.vf-contactform.vf-contactform .elementor-field-group-note {
	order: 1;
	align-self: center;
	padding-bottom: 0;
}

.vf-contactform.vf-contactform .e-form__buttons {
	padding-bottom: 0;
}

/*
 * The note is shared markup with the footer newsletter, which sits on the
 * brand blue and takes a near white. On this page it is on white paper.
 */
.vf-contactform .vf-newsletter__note {
	color: var(--vf-color-ink-500);
	opacity: 1;
}

/* ---------------------------------------------------------------------------
 * The article template
 * ------------------------------------------------------------------------ */

/*
 * The lead photograph hangs below the band.
 *
 * The band's own colour still runs the full width behind the title, which is
 * why the overhang is a negative margin on the picture rather than a shorter
 * band, and why the band does not clip its overflow.
 */
.vf-article__head.vf-article__head {
	overflow: visible;
}

.vf-article__media.vf-article__media {
	margin-bottom: -116px;
}

/*
 * The title at the layout's 52px display step, not the 80px one. It is bound
 * to the `secondary` global, which the site does not apply (docs/STATUS.md,
 * "Typography bindings"), so it took the kit's blanket default: three lines of
 * 80px made the copy column taller than the photograph, and the photograph's
 * overhang, which only exists while the picture is the taller column, was
 * lost. Four class names, above the kit rule and the per-element colour.
 */
.vf-article__head.vf-article__head .elementor-widget-theme-post-title .elementor-heading-title {
	font-size: var(--vf-size-display-l);
	line-height: var(--vf-leading-display-l);
}

.vf-article__img img {
	width: 100%;
	height: auto;
	aspect-ratio: 1;
	object-fit: cover;
}

.vf-article__still img {
	width: 100%;
	height: auto;
	aspect-ratio: 1;
	object-fit: cover;
	border-radius: var(--vf-radius-card);
}

/* The article's own section headings are the 52px step, not the display one. */
.vf-article__aside .elementor-widget-heading .elementor-heading-title {
	font-size: 40px;
	line-height: 1.2;
}

.vf-article__body p {
	margin: 0;
}

/* ---------------------------------------------------------------------------
 * The social marks
 *
 * Drawn here rather than fetched from an icon font. Elementor's Social Icons
 * widget rendered one of the two as an empty circle and painted the other in
 * the network's own brand blue, because the widget colours each icon from its
 * own palette and the glyph depends on which Font Awesome build the site has.
 * Two inline shapes in a button that owns its own colour are smaller than
 * either problem.
 * ------------------------------------------------------------------------ */
.vf-social {
	display: flex;
	gap: 12px;
	margin: 0;
	padding: 0;
	list-style: none;
}

.vf-social__link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	border: 1px solid rgb(255 255 255 / 0.55);
	border-radius: 50%;
	color: var(--vf-color-on-dark);
	text-decoration: none;
	transition: background-color var(--vf-motion-fast) var(--vf-motion-ease),
		color var(--vf-motion-fast) var(--vf-motion-ease),
		border-color var(--vf-motion-fast) var(--vf-motion-ease);
}

.vf-social__link svg {
	width: 21px;
	height: 21px;
	display: block;
}

.vf-social__link:hover,
.vf-social__link:focus-visible {
	background: var(--vf-color-on-dark);
	border-color: var(--vf-color-on-dark);
	color: var(--vf-color-primary);
}

/* The same marks on a white page. */
.vf-social-wrap--ink .vf-social__link {
	border-color: var(--vf-color-primary);
	color: var(--vf-color-primary);
}

.vf-social-wrap--ink .vf-social__link:hover,
.vf-social-wrap--ink .vf-social__link:focus-visible {
	background: var(--vf-color-primary);
	border-color: var(--vf-color-primary);
	color: var(--vf-color-on-dark);
}

@media (max-width: 1024px) {
	.vf-post.vf-post .vf-post__title .elementor-heading-title {
		font-size: 32px;
	}

	.vf-spotlight.vf-spotlight .vf-spotlight__copy .elementor-heading-title,
	.vf-article__aside .elementor-widget-heading .elementor-heading-title {
		font-size: 34px;
	}

	.vf-article__media.vf-article__media {
		margin-bottom: -72px;
	}
}

@media (max-width: 767px) {
	.vf-post.vf-post .vf-post__title .elementor-heading-title {
		font-size: 28px;
	}

	.vf-spotlight.vf-spotlight .vf-spotlight__copy .elementor-heading-title,
	.vf-article__aside .elementor-widget-heading .elementor-heading-title {
		font-size: 28px;
	}

	/*
	 * The overhang goes. On a phone the picture is above the title rather than
	 * beside it, so hanging it past the band would leave a notch of brand blue
	 * with nothing in it.
	 */
	.vf-article__media.vf-article__media {
		margin-bottom: 0;
	}

	/*
	 * Every field full width. Three class names, because the widget's own
	 * `elementor-col-50` is two and a half width field on a 390px screen is a
	 * 150px box with a placeholder that does not fit in it.
	 */
	.vf-contactform.vf-contactform.vf-contactform .elementor-field-group {
		width: 100%;
	}
}

/* ---------------------------------------------------------------------------
 * The article body
 *
 * The layout sets each section as a heading on the left and its paragraphs on
 * the right. The article template renders the post's content as one block, so
 * the two columns come from the content's own markup: the installer wraps each
 * section in `.vf-art-sec` and this lays that wrapper out.
 *
 * Auto-placement does the work. The heading is put in column one and the
 * paragraphs in column two, and each one takes the next free row in its own
 * column, so a section with a heading and three paragraphs comes out as one
 * heading beside three stacked paragraphs rather than four staggered rows.
 *
 * Anything the wrapper does not contain, a stray paragraph or a block someone
 * adds in the editor, still renders: it simply runs the measure of the column
 * it lands in.
 * ------------------------------------------------------------------------ */
.vf-article__content .vf-art-lead {
	max-width: 60ch;
	margin: 0 0 var(--vf-space-10);
	font-size: var(--vf-size-body-l);
	color: var(--vf-color-ink-700);
}

.vf-article__content .vf-art-sec {
	display: grid;
	grid-template-columns: calc(45% - 15px) calc(55% - 15px);
	column-gap: 30px;
	row-gap: var(--vf-space-3);
	align-items: start;
	margin-bottom: var(--vf-space-12);
}

.vf-article__content .vf-art-sec > h2 {
	grid-column: 1;
	margin: 0;
	font-family: var(--vf-font-display);
	font-size: 40px;
	font-weight: var(--vf-weight-regular);
	line-height: 1.2;
	color: var(--vf-color-ink-900);
}

.vf-article__content .vf-art-sec > .vf-art-fig {
	grid-column: 1;
	margin: var(--vf-space-3) 0 0;
}

.vf-article__content .vf-art-sec > .vf-art-fig img {
	width: 100%;
	height: auto;
	aspect-ratio: 1;
	object-fit: cover;
	border-radius: var(--vf-radius-card);
}

.vf-article__content .vf-art-sec > p {
	grid-column: 2;
	margin: 0;
	font-size: var(--vf-size-body-l);
	line-height: var(--vf-leading-body-l);
	color: var(--vf-color-ink-500);
}

/* The byline reads as a sentence, not as a list of facts. */
.vf-byline .elementor-icon-list-items {
	margin: 0;
	padding: 0;
	list-style: none;
}

.vf-byline .elementor-icon-list-item {
	color: var(--vf-color-ink-700);
}

@media (max-width: 767px) {
	/*
	 * One column. At this width the heading and its paragraphs are two narrow
	 * measures side by side, which is worse than the sequence they describe.
	 */
	.vf-article__content .vf-art-sec {
		grid-template-columns: minmax(0, 1fr);
		row-gap: var(--vf-space-2);
		margin-bottom: var(--vf-space-10);
	}

	.vf-article__content .vf-art-sec > h2,
	.vf-article__content .vf-art-sec > .vf-art-fig,
	.vf-article__content .vf-art-sec > p {
		grid-column: 1;
	}

	.vf-article__content .vf-art-sec > h2 {
		font-size: 28px;
	}
}

/* ---------------------------------------------------------------------------
 * The legal pages
 *
 * One measure, read front to back by someone looking for one clause. So a
 * readable line length, headings that can be scanned, and space between the
 * sections rather than rules.
 * ------------------------------------------------------------------------ */
.vf-legal.vf-legal,
.vf-legal__head.vf-legal__head {
	max-width: 76ch;
}

.vf-legal__date p {
	margin: 0;
	color: var(--vf-color-ink-700);
}

.vf-legal__intro p {
	margin: 0;
	font-size: var(--vf-size-body-l);
	color: var(--vf-color-ink-700);
}

.vf-legal .vf-legal__h2 .elementor-heading-title {
	font-size: 28px;
	line-height: 1.25;
}

/* The gap belongs above a heading, not between every paragraph. */
.vf-legal .vf-legal__h2 {
	margin-top: var(--vf-space-8);
}

.vf-legal .vf-legal__h2:first-child {
	margin-top: 0;
}

.vf-legal p {
	margin: 0;
	line-height: var(--vf-leading-body-l);
}

.vf-legal strong {
	color: var(--vf-color-ink-900);
	font-weight: var(--vf-weight-semibold);
}

@media (max-width: 767px) {
	.vf-legal .vf-legal__h2 .elementor-heading-title {
		font-size: 24px;
	}

	.vf-legal .vf-legal__h2 {
		margin-top: var(--vf-space-6);
	}
}

/* ---------------------------------------------------------------------------
 * The first section of a page that has no hero
 *
 * Opening the divisions accordion pushes the page down and pulls it back up by
 * the tuck, so the page overlaps the last division card by that much. On the
 * designed pages the thing doing the overlapping is a hero on a photograph,
 * which is opaque and covers the card's tail; that overlap is deliberate and is
 * what fills the two corner notches under Fulfillment.
 *
 * A page that starts with ordinary content has nothing opaque there, so the
 * card ran on under the header and the logo and navigation ended up sitting on
 * a strip of Fulfillment blue. The cover has to be the section rather than the
 * page wrapper: painting the wrapper would put white behind the hero's rounded
 * top corners on every other page and undo the notch fill.
 * ------------------------------------------------------------------------ */
.vf-page-top.vf-page-top {
	position: relative;
	z-index: 1;
	background-color: var(--vf-color-surface);
}

/* ---------------------------------------------------------------------------
 * The 404 page
 *
 * The legal pages' measure, with the label set small and spaced so the error
 * code reads as a caption over the headline rather than as a second title.
 * ------------------------------------------------------------------------ */
.vf-error__label.vf-error__label.vf-error__label {
	font-size: 14px;
	font-weight: var(--vf-weight-medium);
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

.vf-error__label.vf-error__label.vf-error__label p {
	margin: 0;
}

/* ---------------------------------------------------------------------------
 * Entrances
 *
 * theme.js marks section headings, pictures and the card-like blocks with
 * `vf-reveal` and, the first time each enters the viewport, `is-in`. Until
 * then a block sits 18px low and invisible, and it settles into place over
 * half a second; siblings follow one another 70ms apart. Whatever is on
 * screen at load plays at once, which is the only intro the hero gets.
 *
 * Nothing here is hidden without the script: the classes are added by it, so
 * a page without JavaScript, a crawler or a print sees everything in place.
 * Four class names, because Elementor gives every widget a `transition` of
 * its own at three, and a transition that lost would jump rather than fade.
 *
 * `--fade` is for blocks with a transform of their own on hover (the collage
 * tiles, the pickers, the reveal artwork): opacity only, so the two never
 * fight over `transform`.
 * ------------------------------------------------------------------------ */
.vf-reveal.vf-reveal.vf-reveal.vf-reveal {
	opacity: 0;
	transition:
		opacity var(--vf-motion-settle) var(--vf-motion-ease),
		transform var(--vf-motion-settle) var(--vf-motion-ease-settle);
	transition-delay: calc(var(--vf-reveal-i, 0) * 70ms);
	will-change: opacity, transform;
}

.vf-reveal--lift.vf-reveal--lift.vf-reveal--lift.vf-reveal--lift {
	transform: translateY(18px);
}

.vf-reveal.vf-reveal.vf-reveal.vf-reveal.is-in {
	opacity: 1;
	will-change: auto;
}

.vf-reveal--lift.vf-reveal--lift.vf-reveal--lift.vf-reveal--lift.is-in {
	transform: none;
}

/* Scrolled past before the script ran: in place, no entrance. */
.vf-reveal.vf-reveal.vf-reveal.vf-reveal.is-instant {
	transition: none;
}

@media (prefers-reduced-motion: reduce) {
	.vf-reveal.vf-reveal.vf-reveal.vf-reveal {
		opacity: 1;
		transform: none;
		transition: none;
	}
}
