:root {
	/* The Anovase palette, from the logo and the sketches. */
	--anovase-teal: #68c6dc;
	--anovase-lavender: #d0bce8;
	--anovase-violet: #a25fbe;
	--anovase-shard: #2f2354;
	--anovase-glow: #1f1747;
	--anovase-void: #050505;

	/* The sketches' teal-to-violet ramp, as gradient stops: use it as
	   linear-gradient(<angle>, var(--ano-spectrum)). */
	--ano-spectrum: var(--anovase-teal) 0%, #7da0d1 32%, #9771c3 77%, var(--anovase-violet) 100%;

	--whimsy-color-background: var(--anovase-void);
	--whimsy-color-foreground: var(--anovase-lavender);
	--whimsy-color-surface: var(--anovase-shard);
	--whimsy-color-accent: var(--anovase-teal);
	--whimsy-color-emphasis: var(--anovase-violet);
	--whimsy-border-radius: 0;

	/* Outfit Light carries the text; Space Grotesk Bold the accent lines (_Layout loads both). */
	--whimsy-font-family-body: 'Outfit', system-ui, sans-serif;
	--whimsy-font-family-heading: 'Outfit', system-ui, sans-serif;
	--ano-font-family-accent: 'Space Grotesk', system-ui, sans-serif;

	/* Page-readiness loader (<ready-loader />) in the site palette. A fragment navigation
	   only replaces the outlet: keep its overlay beneath the fixed ano-frame (z-index 100)
	   so the frame stays visible while a page loads. */
	--whimsy-loader-bg: var(--whimsy-color-background);
	--whimsy-loader-fg: var(--whimsy-color-foreground);
	--whimsy-loader-navigating-z-index: 99;

	/* One unit of the sketches' 1200 × 1200 artboard in pixels. The pages draw at this scale,
	   so a sketch's proportions hold on any viewport. */
	--ano-unit: clamp(0.45px, 100vmin / 1200, 1.2px);
	/* The frame's unit grows with the viewport's width more slowly than the pages': the width
	   counts for 758/1400 of itself, so where the height caps the frame at 758px wide, the
	   width now catches up only at 1400px. */
	--ano-frame-unit: clamp(0.45px, min(100vw * 758 / 1400, 100vh) / 1200, 1.2px);
	/* The sketches' line weight, kept at 2px whatever the scale. */
	--ano-line-width: 2px;
	/* Extra width for sector content on wide screens: 40% of the viewport beyond 900px (none
	   at 800px, 120px at 1200px), over the sketches' 1200 units. */
	--ano-stretch: max(0px, (100vw - 900px) * .4);

	/* The backdrop, fixed to the viewport: on body, and repeated by the frame's edge fades so
	   they blend in. The void, warming to the glow towards the top-right, under a coarse grain
	   of void specks that all but vanishes on the dark side. The grain is per-pixel noise
	   (turbulence at a high frequency), tiled seamlessly and blurred into clumps: the blur's
	   stdDeviation sets its size and the colour matrix's last row its strength. The filter
	   works in sRGB so the specks stay #050505 (in linear RGB they would lift to grey). */
	--ano-backdrop:
		url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n' x='-8' y='-8' width='256' height='256' filterUnits='userSpaceOnUse' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.2' stitchTiles='stitch' x='0' y='0' width='240' height='240'/%3E%3CfeTile/%3E%3CfeGaussianBlur stdDeviation='.8'/%3E%3CfeColorMatrix values='0 0 0 0 .02 0 0 0 0 .02 0 0 0 0 .02 6 0 0 -.2 -2.9'/%3E%3C/filter%3E%3Crect x='-8' y='-8' width='256' height='256' filter='url(%23n)'/%3E%3C/svg%3E"),
		linear-gradient(40deg, var(--anovase-void) 35%, var(--anovase-glow));

	color-scheme: dark;
}

/* A column at least as tall as the screen, whose outlet takes up what a short page leaves, so
   the footer always ends the screen rather than leaving a gap under it. As a flex container,
   it also keeps a page's outer margins inside the outlet. */
body {
	margin: 0;
	min-height: 100dvh;
	display: flex;
	flex-direction: column;
	background: var(--ano-backdrop);
	color: var(--whimsy-color-foreground);
	font-family: var(--whimsy-font-family-body);
	font-weight: 300;
	background-attachment: fixed;
}

[data-whimsy-outlet] {
	flex-grow: 1;
}

/* The frame shows only once it has upgraded. */
ano-frame:not(:defined) {
	display: none;
}

/* The headline pair: a light lead line over a bold, uppercase accent line
   (Outfit Light 60.77 and Space Grotesk Bold 77.58 units in the sketch). */
.ano-headline {
	margin: 0;
	font-weight: inherit;
	line-height: 1;

	& > .lead,
	& > .accent {
		display: block;
	}

	& > .lead {
		font-family: var(--whimsy-font-family-heading);
		font-size: max(1.75rem, calc(60.77 * var(--ano-unit)));
		font-weight: 300;
		color: var(--whimsy-color-foreground);
	}

	& > .accent {
		margin-top: .12em;
		font-family: var(--ano-font-family-accent);
		font-size: max(2.25rem, calc(77.58 * var(--ano-unit)));
		font-weight: 700;
		text-transform: uppercase;
		color: var(--whimsy-color-accent);
	}
}

/* Body copy under a headline (Outfit Light 29 units on 34.8). */
.ano-lede {
	margin: 0;
	font-size: max(1rem, calc(29 * var(--ano-unit)));
	line-height: 1.2;
}

/* A sector's title, in the accent face and colour (Space Grotesk Bold, 58 units, teal). */
.ano-title {
	margin: 0;
	font-family: var(--ano-font-family-accent);
	font-size: max(2rem, calc(58 * var(--ano-unit)));
	font-weight: 700;
	line-height: 1.2;
	color: var(--whimsy-color-accent);
}

/* A highlighter's stroke over words of a title (sketch6-fig2): violet (#716bc1) behind them as
   they turn lavender. It's laid on from the inline start as --sweep runs from 0% to 100%, the
   words changing colour at its edge; until then they keep the title's colour. Something sets
   --sweep to light it (an artwork's change: Toolkit.ts). The stroke is drawn behind the words
   at z-index -1, so the highlight must not be a stacking context of its own. */
.ano-highlight {
	--sweep: 0%;
	position: relative;
	display: inline-block;
	background: linear-gradient(to right, var(--anovase-lavender) var(--sweep), var(--whimsy-color-accent) var(--sweep));
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;

	&::before {
		content: '';
		position: absolute;
		inset: .06em -.1em -.04em -.06em;
		z-index: -1;
		border-radius: .14em;
		background: #716bc1;
		clip-path: inset(0 calc(100% - var(--sweep)) 0 0 round .14em);
	}
}

/* The text field, the theme's default (the newsletter sketch's): the text in Space Grotesk and
   teal on no background, over a spectrum underline (30 units, its line 12.35 units under
   the text's box). Never under 1rem, so phones don't zoom in on focus. Errors go in a
   .ano-field-error after it. */
:where(input:is(:not([type]), [type=text], [type=email], [type=search], [type=tel], [type=url], [type=password]), textarea) {
	box-sizing: border-box;
	width: 100%;
	margin: 0;
	padding: calc(8 * var(--ano-unit)) 0 calc(12.35 * var(--ano-unit));
	border: 0 solid;
	border-bottom-width: var(--ano-line-width);
	border-image: linear-gradient(90deg, var(--ano-spectrum)) 1;
	border-radius: 0;
	background: none;
	color: var(--anovase-teal);
	font: 400 max(1rem, calc(30 * var(--ano-unit))) / 1.2 var(--ano-font-family-accent);
	caret-color: var(--anovase-lavender);
	outline: none;
	transition: background-color .25s ease;

	&::placeholder {
		color: var(--anovase-teal);
		opacity: .45;
	}

	/* Focused, a faint teal haze rises off the line. */
	&:focus-visible {
		background: linear-gradient(to top, rgb(104 198 220 / .12), transparent 70%);
	}

	&[aria-invalid=true] {
		border-image: linear-gradient(90deg, var(--anovase-violet), var(--anovase-lavender)) 1;
	}

	/* Keep the browser's autofill from painting the field. */
	&:autofill {
		-webkit-text-fill-color: var(--anovase-teal);
		transition: background-color 600000s 0s;
	}
}

.ano-field-error {
	margin: .5em 0 0;
	font-size: max(.875rem, calc(20 * var(--ano-unit)));
	line-height: 1.3;
	color: var(--anovase-lavender);

	&:empty {
		display: none;
	}
}

/* The sketches' halftone: faint lavender dots, 8 units across on a 16-unit grid turned by
   5.28°. Place and size it (it must be positioned) and shape it with border-radius;
   --dots-unit sets its scale.
   Each is a window onto one field of dots, laid out and turned around an origin it can
   share with its neighbours: give it its offset from that origin as --dots-x and --dots-y
   (by default the origin is its own top-left), and overlapping halftones line their dots up
   instead of sifting through each other. */
.ano-dots {
	--dots-reach: 150vmax;
	overflow: hidden;
	opacity: .08;
	pointer-events: none;

	&::before {
		content: '';
		position: absolute;
		left: calc(-1 * var(--dots-x, 0px) - var(--dots-reach));
		top: calc(-1 * var(--dots-y, 0px) - var(--dots-reach));
		width: calc(2 * var(--dots-reach));
		aspect-ratio: 1;
		rotate: -5.28deg;
		background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='4' cy='4' r='4' fill='%23d0bce8'/%3E%3C/svg%3E") 0 0 / calc(16 * var(--dots-unit, var(--ano-unit)));
	}
}

/* A dashed streak in the spectrum, teal at its foot and violet at its head (the sketches'
   dashes of 20, 7, 30 and 13 units), its colours rising through the dashes: the spectrum
   runs up and back down over twice the streak's height, shown from its lower half, and
   climbs a whole round every --streak-duration. Give it a height; --streak-unit sets the
   dashes' scale. */
.ano-streak {
	--streak-rise: ano-streak-rise var(--streak-duration, 4s) linear var(--streak-delay, 0s) infinite;
	width: var(--ano-line-width);
	background:
		linear-gradient(0deg,
			var(--anovase-teal), #7da0d1 16%, #9771c3 38.5%, var(--anovase-violet) 50%,
			#9771c3 61.5%, #7da0d1 84%, var(--anovase-teal))
		0 100% / 100% 200%;
	mask-image: repeating-linear-gradient(
		#000 0 calc(20 * var(--streak-unit, var(--ano-unit))),
		transparent 0 calc(27 * var(--streak-unit, var(--ano-unit))),
		#000 0 calc(57 * var(--streak-unit, var(--ano-unit))),
		transparent 0 calc(70 * var(--streak-unit, var(--ano-unit)))
	);
	animation: var(--streak-rise);
}

@keyframes ano-streak-rise {
	from { background-position-y: 100%; }
	to { background-position-y: 300%; }
}

/* A slow, subtle bob up and down by --float-distance; vary --float-duration and
   --float-delay to set neighbours apart. */
.ano-float {
	--float: ano-float var(--float-duration, 6s) ease-in-out var(--float-delay, 0s) infinite alternate both;
	animation: var(--float);
}

.ano-float.ano-streak {
	animation: var(--float), var(--streak-rise);
}

@keyframes ano-float {
	from { translate: 0 calc(-1 * var(--float-distance, 6px)); }
	to { translate: 0 var(--float-distance, 6px); }
}

@media (prefers-reduced-motion: reduce) {
	.ano-float,
	.ano-streak {
		animation: none;
	}
}
