/* Copyright (c) 2026 Web Upp Limited. Licensed under the GPL-2.0-or-later; see LICENSE. */
/*
 * WebUpp Platform Theme: shared section primitives for the parent's section blocks and
 * any child's own sections: container, section rhythm, glows and the dot
 * grid, section heads, chips, buttons, text links.
 *
 * The parent supplies the sections; a child supplies values for the --wu-*
 * slots below (the list is CONTRACT.md "--wu-* slots"). The defaults are the
 * neutral default register: the palette slots read the active theme.json
 * palette (ground, surface, surface-2, line, line-strong, text, muted,
 * accent, success, gradient "brand"), which the parent's own theme.json
 * fills with grey and white, and every other default is written for a light
 * ground. A child on a dark ground sets the slots it needs (2.0.0: the dot
 * grid, ghost faces and primary button face and text used to default to a
 * dark ground; a child that relied on that now sets them, as WebUpp,
 * VirtualCRO and WebUpp Styles do).
 *
 * The class prefix stays "wu-" so the WebUpp page's markup did not change
 * when these moved; see docs/SPIKE-LOG.md, VirtualCRO build, task 0.
 *
 * A trap for children with a second scheme: a custom property that refers
 * to another is resolved on the element that declares it, so a scheme must
 * remap the preset colours on :root (where these are declared), not on
 * body.
 *
 * Optional child slots, read with a fallback where they are used, so a child
 * that never sets them renders as before: --wu-button-glow (primary hover
 * glow), --wu-button-radius (every button the parent draws, from 3.9.0,
 * default --wu-radius read where the button is), --wu-card-shadow (glass
 * tiles and sequence panels, none),
 * --wu-stack-back-filter (the hero stack's back frames), and the block slots
 * listed in CONTRACT.md.
 */

:root {
	--wu-ground: var(--wp--preset--color--ground);
	--wu-surface: var(--wp--preset--color--surface);
	--wu-surface-2: var(--wp--preset--color--surface-2);
	--wu-line: var(--wp--preset--color--line);
	--wu-line-strong: var(--wp--preset--color--line-strong);
	--wu-text: var(--wp--preset--color--text);
	--wu-muted: var(--wp--preset--color--muted);
	--wu-success: var(--wp--preset--color--success);
	--wu-gradient: var(--wp--preset--gradient--brand);
	/* Brand slots a child sets: text accent (chips, links, focus), the
	   tint of hover glows and shadows, three backdrop glows (named after
	   WebUpp's, used as first, second and third), the chip pill. */
	--wu-accent: var(--wp--preset--color--accent, var(--wu-text));
	--wu-glow-tint: var(--wu-text);
	--wu-glow-red: transparent;
	--wu-glow-purple: transparent;
	--wu-glow-blue: transparent;
	/* From 3.12.0 (R31): two more glow slots for a block's named glows
	   (webupp_glows()), transparent until the child colours them. */
	--wu-glow-accent: transparent;
	--wu-glow-custom: transparent;
	--wu-chip-bg: var(--wu-surface);
	--wu-chip-border: var(--wu-line-strong);
	--wu-dot: color-mix(in srgb, var(--wu-text) 8%, transparent);
	--wu-ghost-face: transparent;
	--wu-ghost-face-hover: var(--wu-surface);
	--wu-ghost-border-hover: var(--wu-text);
	/* Primary button: face, text, and the gradient that fades in on hover
	   (white text on it). */
	--wu-button-face: var(--wu-text);
	--wu-button-text: var(--wu-ground);
	--wu-button-hover-face: var(--wu-gradient);
	--wu-panel-top: color-mix(in srgb, var(--wu-surface-2) 72%, transparent);
	--wu-panel-bottom: color-mix(in srgb, var(--wu-surface) 72%, transparent);
	--wu-ease: var(--wp--custom--ease--out, cubic-bezier(0.33, 1, 0.68, 1));
	--wu-radius: var(--wp--custom--radius, 10px);
	--wu-card-radius: var(--wp--custom--card-radius, 16px);
	--wu-header-h: var(--wp--custom--header-height, 72px);
	--wu-gutter: 16px;
	--wu-section-y: 64px;
	--wu-mono: var(--wp--preset--font-family--mono);
	--wu-display: var(--wp--preset--font-family--heading);
	/* From 3.2.0, the slots the customer children lacked (R17 inventory,
	   "Slots the promotions need"): the body face, the page width (the
	   container's measure without the gutters), the section heading's weight
	   and tracking, the image radius, and the three section grounds a block
	   can stand on (base is --wu-ground; alt and inverse below, with the
	   inverse ground's text). Each default is the value the parent already
	   drew, so a child that sets none of them renders as before. */
	--wu-body: var(--wp--preset--font-family--body);
	--wu-wrap: var(--wp--style--global--wide-size, 1240px);
	--wu-heading-weight: 700;
	--wu-heading-tracking: -0.02em;
	--wu-image-radius: var(--wu-radius);
	--wu-ground-alt: var(--wu-surface);
	--wu-inverse-ground: var(--wu-text);
	--wu-inverse-text: var(--wu-ground);
}

@media (min-width: 768px) {
	:root {
		--wu-gutter: 32px;
		--wu-section-y: 80px;
	}
}

@media (min-width: 1025px) {
	:root {
		--wu-gutter: 40px;
		--wu-section-y: 120px;
	}
}

/* ---------- Container and section rhythm ---------- */

.wu-wrap {
	box-sizing: border-box;
	width: 100%;
	max-width: calc(var(--wu-wrap, var(--wp--style--global--wide-size, 1240px)) + 2 * var(--wu-gutter));
	margin-inline: auto;
	padding-inline: var(--wu-gutter);
}

.wu-section {
	position: relative;
	padding-block: var(--wu-section-y);
	color: var(--wu-text);
}

/* A hairline between sections, inside the container. */
.wu-section--rule::before {
	content: "";
	position: absolute;
	top: 0;
	left: 50%;
	width: min(calc(100% - 2 * var(--wu-gutter)), var(--wp--style--global--wide-size, 1240px));
	height: 1px;
	transform: translateX(-50%);
	background: linear-gradient(90deg, transparent, var(--wu-line) 20%, var(--wu-line) 80%, transparent);
}

.upp-main > .wp-block-post-content,
.upp-main > .wp-block-post-content > * {
	margin-block: 0;
}

.upp-main > .wp-block-post-content {
	padding-inline: 0;
}

/* ---------- Glows and the dot grid ---------- */

/*
 * Glows are large radial gradients at 22% (theme.json custom.glow), so they
 * are soft without a filter: blur() on a viewport-sized layer costs a full
 * repaint per frame. They drift on transform only, 20 s, and stand still
 * under reduced motion.
 */
.wu-glows {
	position: absolute;
	inset: 0;
	overflow: hidden;
	pointer-events: none;
	z-index: 0;
}

.wu-glow {
	position: absolute;
	width: 900px;
	height: 900px;
	border-radius: 50%;
	will-change: transform;
}

.wu-glow--red {
	background: radial-gradient(closest-side, var(--wu-glow-red), transparent 100%);
	animation: wu-drift-a 20s ease-in-out infinite alternate;
}

.wu-glow--purple {
	background: radial-gradient(closest-side, var(--wu-glow-purple), transparent 100%);
	animation: wu-drift-b 20s ease-in-out infinite alternate;
}

.wu-glow--blue {
	background: radial-gradient(closest-side, var(--wu-glow-blue), transparent 100%);
	animation: wu-drift-c 20s ease-in-out infinite alternate;
}

/*
 * Named glows (3.12.0, R31): the `glows` list of webupp/hero and of the
 * glows extension on the other section blocks (webupp_glows()). Each is
 * `.wu-glow.wu-glow--<colour>.wu-glow--<anchor>.wu-glow--<size>`: the
 * colour a slot (the three above, accent and custom), the size its width
 * and height (--wu-glow-size: small 600px, medium 900px, large 1200px),
 * the anchor the point of the section's box its centre sits on, moved by
 * --wu-glow-x and --wu-glow-y (0 unless the child sets them). The section
 * that draws them carries wu-has-glows: it is the glows' box and its own
 * stacking context, so they sit under its content and over its ground.
 * Drift is the three keyframes in turn by the glow's place in the list,
 * on transform only; `--still` and reduced motion hold it.
 */
.wu-glow--accent {
	background: radial-gradient(closest-side, var(--wu-glow-accent), transparent 100%);
	animation: wu-drift-a 20s ease-in-out infinite alternate;
}

.wu-glow--custom {
	background: radial-gradient(closest-side, var(--wu-glow-custom), transparent 100%);
	animation: wu-drift-b 20s ease-in-out infinite alternate;
}

.wu-glow--small {
	--wu-glow-size: 600px;
}

.wu-glow--medium {
	--wu-glow-size: 900px;
}

.wu-glow--large {
	--wu-glow-size: 1200px;
}

.wu-glow:is(.wu-glow--small, .wu-glow--medium, .wu-glow--large) {
	top: calc(var(--wu-glow-ay, 50%) - var(--wu-glow-size) / 2 + var(--wu-glow-y, 0px));
	left: calc(var(--wu-glow-ax, 50%) - var(--wu-glow-size) / 2 + var(--wu-glow-x, 0px));
	width: var(--wu-glow-size);
	height: var(--wu-glow-size);
}

.wu-glow--top-left { --wu-glow-ax: 0%; --wu-glow-ay: 0%; }
.wu-glow--top { --wu-glow-ax: 50%; --wu-glow-ay: 0%; }
.wu-glow--top-right { --wu-glow-ax: 100%; --wu-glow-ay: 0%; }
.wu-glow--left { --wu-glow-ax: 0%; --wu-glow-ay: 50%; }
.wu-glow--centre { --wu-glow-ax: 50%; --wu-glow-ay: 50%; }
.wu-glow--right { --wu-glow-ax: 100%; --wu-glow-ay: 50%; }
.wu-glow--bottom-left { --wu-glow-ax: 0%; --wu-glow-ay: 100%; }
.wu-glow--bottom { --wu-glow-ax: 50%; --wu-glow-ay: 100%; }
.wu-glow--bottom-right { --wu-glow-ax: 100%; --wu-glow-ay: 100%; }

.wu-glow--still {
	animation: none;
}

@media (prefers-reduced-motion: no-preference) {
	.wu-glow:is(.wu-glow--small, .wu-glow--medium, .wu-glow--large):not(.wu-glow--still):nth-child(3n + 1) {
		animation-name: wu-drift-a;
	}

	.wu-glow:is(.wu-glow--small, .wu-glow--medium, .wu-glow--large):not(.wu-glow--still):nth-child(3n + 2) {
		animation-name: wu-drift-b;
	}

	.wu-glow:is(.wu-glow--small, .wu-glow--medium, .wu-glow--large):not(.wu-glow--still):nth-child(3n) {
		animation-name: wu-drift-c;
	}
}

:where(.wu-has-glows) {
	position: relative;
	isolation: isolate;
}

.wu-has-glows > .wu-glows {
	z-index: -1;
}

@keyframes wu-drift-a {
	0% { transform: translate3d(0, 0, 0) scale(1); }
	50% { transform: translate3d(80px, 40px, 0) scale(1.08); }
	100% { transform: translate3d(20px, 100px, 0) scale(0.96); }
}

@keyframes wu-drift-b {
	0% { transform: translate3d(0, 0, 0) scale(1); }
	50% { transform: translate3d(-90px, 50px, 0) scale(1.1); }
	100% { transform: translate3d(60px, -40px, 0) scale(1); }
}

@keyframes wu-drift-c {
	0% { transform: translate3d(0, 0, 0) scale(1.04); }
	50% { transform: translate3d(-60px, -70px, 0) scale(0.95); }
	100% { transform: translate3d(-120px, 30px, 0) scale(1.06); }
}

/* Dot grid (--wu-dot) that fades out towards the content it sits under. */
.wu-dots {
	position: absolute;
	inset: 0;
	z-index: 0;
	pointer-events: none;
	background-image: radial-gradient(var(--wu-dot) 1px, transparent 1.5px);
	background-size: 28px 28px;
	background-position: -14px -14px;
	-webkit-mask-image: radial-gradient(ellipse 70% 60% at 50% 30%, #000 0%, rgba(0, 0, 0, 0.5) 45%, transparent 80%);
	mask-image: radial-gradient(ellipse 70% 60% at 50% 30%, #000 0%, rgba(0, 0, 0, 0.5) 45%, transparent 80%);
}

/* ---------- Section head: chip, h2, standfirst ---------- */

.wu-head {
	max-width: 760px;
	margin-bottom: 48px;
}

@media (min-width: 1025px) {
	.wu-head {
		margin-bottom: 64px;
	}
}

/* From 3.17.0, the questions' head measure (--wu-faq-head-width, 760px as
   every head): read here for a site that loads this file, and in the
   block's own stylesheet at no weight for a site that draws .wu-head
   itself (CONTRACT.md, "Slots on the shared head parts"). */
:where(.wu-faq) .wu-head {
	max-width: var(--wu-faq-head-width, 760px);
}

/* From 3.18.0, the reviews head's gap (--wu-reviews-head-gap, the shared
   head's 48px and 64px from 1025px): read here at the shared head's weight
   for a site that loads this file, and in the block's own stylesheet at no
   weight for a site that draws .wu-head itself (CONTRACT.md, "Slots on the
   shared head parts"; 3.17.0 read it in the block alone). */
:where(.wu-reviews) .wu-head {
	margin-bottom: var(--wu-reviews-head-gap, 48px);
}

@media (min-width: 1025px) {
	:where(.wu-reviews) .wu-head {
		margin-bottom: var(--wu-reviews-head-gap, 64px);
	}
}

.wu-head--center {
	margin-inline: auto;
	text-align: center;
}

.wu-head > * {
	margin-block: 0;
}

.wu-head > * + * {
	margin-top: 18px;
}

.wu-head > .wu-chip + * {
	margin-top: 20px;
}

.wu-h2 {
	font-family: var(--wu-display);
	font-size: var(--wp--preset--font-size--heading-2);
	font-weight: var(--wu-heading-weight, 700);
	line-height: 1.1;
	letter-spacing: var(--wu-heading-tracking, -0.02em);
	text-wrap: balance;
}

.wu-standfirst {
	max-width: 60ch;
	color: var(--wu-muted);
	font-size: var(--wp--preset--font-size--standfirst);
	line-height: 1.5;
	text-wrap: pretty;
}

/* From 3.17.0, two block-scoped sizes of the standfirst, read where the
   shared rule above sets them, with its values as the fallbacks: the
   section head's (--wu-shead-standfirst-size) and the split and full
   heroes' (--wu-hero-standfirst-size, --wu-hero-standfirst-line). Each
   block's own stylesheet reads the same slots at no weight for a site that
   draws .wu-standfirst itself. */
:where(.wu-shead) .wu-standfirst {
	font-size: var(--wu-shead-standfirst-size, var(--wp--preset--font-size--standfirst));
}

.wu-hero__standfirst {
	font-size: var(--wu-hero-standfirst-size, var(--wp--preset--font-size--standfirst));
	line-height: var(--wu-hero-standfirst-line, 1.5);
}

.wu-head--center .wu-standfirst {
	margin-inline: auto;
}

/* Chips and small data: the mono family, uppercase, tracked. */
.wu-chip {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	padding: 6px 12px;
	border: 1px solid var(--wu-chip-border);
	border-radius: 999px;
	/* A child keeps --wu-chip-bg opaque, so a glow behind never lowers the
	   chip text's contrast. */
	background: var(--wu-chip-bg);
	color: var(--wu-accent);
	font-family: var(--wu-mono);
	font-size: var(--wp--preset--font-size--chip);
	font-weight: 500;
	line-height: 1.4;
	letter-spacing: 0.12em;
	text-transform: uppercase;
}

/* Numbered label: a number, a drawn rule and the section name (a real rule, not typed dashes).
   Set by a block's "Section number" field instead of the pill chip. */
.wu-label {
	display: flex;
	align-items: center;
	gap: var(--wu-label-gap, 14px);
	margin: 0;
	color: var(--wu-accent);
	font-family: var(--wu-mono);
	font-size: var(--wp--preset--font-size--chip, 12px);
	font-weight: 500;
	line-height: 1.4;
	letter-spacing: 0.12em;
	text-transform: uppercase;
}

.wu-label__num {
	font-variant-numeric: tabular-nums;
}

/* The rule of the "rule" and "numbered" eyebrows. From 3.17.0 its length,
   thickness (its width, as --wu-steps-rule-width and --wu-hero-rule-width
   name a rule's), colour, opacity and corners, and the gap between the
   eyebrow's parts above, are slots with the values drawn before as their
   fallbacks; every block that draws an eyebrow reads them again at no
   weight in its own stylesheet, for a site that dequeues this file
   (CONTRACT.md, "Slots on the shared head parts"). */
.wu-label__rule {
	flex: none;
	width: var(--wu-label-rule-length, 48px);
	height: var(--wu-label-rule-width, 1px);
	border-radius: var(--wu-label-rule-radius, 0);
	background: var(--wu-label-rule, currentColor);
	opacity: var(--wu-label-rule-opacity, 0.6);
}

.wu-head--center .wu-label {
	justify-content: center;
}

/* Eyebrow variants (3.2.0, webupp_eyebrow()). "pill" is .wu-chip and
   "numbered" the .wu-label above; "rule" is .wu-label without the number;
   "plain", the neutral default, is the text alone in the accent colour. */
.wu-eyebrow {
	margin: 0;
	color: var(--wu-accent);
	font-family: var(--wu-body);
	font-size: var(--wp--preset--font-size--small, 15px);
	font-weight: 600;
	line-height: 1.4;
}

/* Section grounds (3.2.0): a child's own block stands on the alternate or
   the inverse ground through these classes and slots. */
.wu-ground--alt {
	background: var(--wu-ground-alt);
}

.wu-ground--inverse {
	background: var(--wu-inverse-ground);
	color: var(--wu-inverse-text);
	/* From 3.17.0: the ghost button on the inverse ground. Its words and
	   edge read --wu-text and --wu-line-strong, made for the page's ground,
	   so on this ground (--wu-inverse-ground, which is --wu-text unless a
	   child sets it) they vanished. The ground gives the ghost button its
	   own pair, as the hero's and the split's remaps already did: the words
	   in the inverse text, the edge at 55% of it, the hover face at 10%. A
	   child sets these on .wu-ground--inverse to choose otherwise. */
	--wu-ghost-text: var(--wu-inverse-text);
	--wu-ghost-border: color-mix(in srgb, var(--wu-inverse-text) 55%, transparent);
	--wu-ghost-border-hover: var(--wu-inverse-text);
	--wu-ghost-face-hover: color-mix(in srgb, var(--wu-inverse-text) 10%, transparent);
}

.wu-mono {
	font-family: var(--wu-mono);
	font-size: 13px;
	font-weight: 500;
	letter-spacing: 0.02em;
	color: var(--wu-muted);
}

/* Gradient wash on a phrase (h1 key phrase). Large text only. */
.wu-wash {
	background-image: var(--wu-gradient);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
	-webkit-box-decoration-break: clone;
	box-decoration-break: clone;
}

/* ---------- Buttons ---------- */

.wu-button {
	box-sizing: border-box;
	position: relative;
	isolation: isolate;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	padding: 14px 22px;
	border: 1px solid transparent;
	border-radius: var(--wu-button-radius, var(--wu-radius));
	font-family: var(--wp--preset--font-family--body);
	font-size: 15px;
	font-weight: 500;
	line-height: 1;
	text-decoration: none;
	white-space: nowrap;
	cursor: pointer;
	transition: transform 0.25s var(--wu-ease), border-color 0.2s, background-color 0.2s, box-shadow 0.25s var(--wu-ease), color 0.2s;
}

.wu-button:hover {
	transform: translateY(-1px);
	text-decoration: none;
}

/*
 * Primary: a 1px gradient hairline (two backgrounds, padding-box and
 * border-box) round the button face (--wu-button-face, --wu-button-text).
 * Hover fades a gradient face in on a pseudo-element, white text on it; the
 * face is sized 220% and centred so the text sits on the gradient's middle.
 */
.wu-button--primary,
.wu-button--primary:visited {
	background: linear-gradient(var(--wu-button-face), var(--wu-button-face)) padding-box, var(--wu-gradient) border-box;
	color: var(--wu-button-text);
	box-shadow: 0 0 0 0 transparent;
}

.wu-button--primary::before {
	content: "";
	position: absolute;
	inset: -1px;
	z-index: -1;
	border-radius: inherit;
	background-image: var(--wu-button-hover-face);
	background-size: 220% 100%;
	background-position: 50% 50%;
	opacity: 0;
	transition: opacity 0.3s var(--wu-ease);
}

/* --wu-button-glow: optional child token (a second scheme may soften it);
   unset, the glow is the tint at 70% as before. */
.wu-button--primary:hover {
	color: #fff;
	box-shadow: 0 10px 40px -10px var(--wu-button-glow, color-mix(in srgb, var(--wu-glow-tint) 70%, transparent));
}

.wu-button--primary:hover::before {
	opacity: 1;
}

.wu-button--ghost,
.wu-button--ghost:visited {
	border-color: var(--wu-ghost-border, var(--wu-line-strong));
	background: var(--wu-ghost-face);
	color: var(--wu-ghost-text, var(--wu-text));
}

.wu-button--ghost:hover {
	border-color: var(--wu-ghost-border-hover);
	background: var(--wu-ghost-face-hover);
	color: var(--wu-ghost-text, var(--wu-text));
}

.wu-button svg {
	width: 16px;
	height: 16px;
	transition: transform 0.2s var(--wu-ease);
}

.wu-button:hover svg {
	transform: translateX(3px);
}

.wu-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 12px;
}

.wu-textlink {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	color: var(--wu-accent);
	font-weight: 500;
	text-decoration: none;
}

.wu-textlink svg {
	width: 16px;
	height: 16px;
	transition: transform 0.2s var(--wu-ease);
}

.wu-textlink:hover {
	color: var(--wu-accent);
	text-decoration: underline;
	text-underline-offset: 4px;
}

.wu-textlink:hover svg {
	transform: translateX(3px);
}


/* ---------- Reduced motion: glows still, no hover travel ---------- */

@media (prefers-reduced-motion: reduce) {
	.wu-glow {
		animation: none;
	}

	.wu-button,
	.wu-button:hover,
	.wu-button svg,
	.wu-textlink svg {
		transition: none;
		transform: none;
	}
}

/* ---------- The email-only form (3.15.0) ---------- */

/* webupp_signup_form(): webupp/contact with `aside` email and webupp/cta
   with `signup` draw it. The label above, the address and the button on
   one line from 480px, the consent line and the status under them. The
   trap field is out of sight and out of the tab order. A child that
   dequeues this stylesheet styles wu-signup in its own. */
.wu-signup {
	width: 100%;
	max-width: var(--wu-signup-max, 520px);
}

.wu-cta .wu-signup {
	margin: 24px auto 0;
}

.wu-signup__form {
	display: grid;
	gap: 8px;
	margin: 0;
}

.wu-signup__label {
	color: inherit;
	font: var(--wu-signup-label-font, 600 15px/1.4 var(--wu-body));
	text-align: left;
}

.wu-signup__row {
	display: flex;
	flex-direction: column;
	gap: 8px;
}

@media (min-width: 480px) {
	.wu-signup__row {
		flex-direction: row;
	}
}

.wu-signup__input {
	box-sizing: border-box;
	flex: 1 1 auto;
	min-width: 0;
	padding: 12px 14px;
	border: 1px solid var(--wu-signup-border, var(--wu-line-strong));
	border-radius: var(--wu-button-radius, var(--wu-radius));
	background: var(--wu-signup-face, var(--wu-ground));
	color: var(--wu-signup-text, var(--wu-text));
	font: inherit;
	font-size: 16px;
}

.wu-signup__input:focus-visible {
	outline: 2px solid var(--wu-accent);
	outline-offset: 2px;
}

.wu-signup__trap {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

.wu-signup__consent,
.wu-signup__status {
	margin: 10px 0 0;
	color: var(--wu-signup-note-colour, var(--wu-muted));
	font-size: 14px;
	line-height: 1.45;
	text-align: left;
}

.wu-signup__status--thanks {
	color: var(--wu-signup-thanks, var(--wu-success, inherit));
	font-weight: 600;
}

.wu-ground--inverse .wu-signup__consent,
.wu-ground--inverse .wu-signup__status {
	color: var(--wu-inverse-text);
}
