: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;
	/* The back button's size (.ano-back), for the pages to line it up by. */
	--ano-back-size: max(2.5rem, calc(56 * var(--ano-unit)));
	/* 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 halftone's fade (the sketches' luminance mask), from full strength to all but none:
	   use it as mask-image: linear-gradient(<direction>, var(--ano-dots-fade)). */
	--ano-dots-fade: #000, rgb(0 0 0 / .91) 6%, rgb(0 0 0 / .65) 26%, rgb(0 0 0 / .44) 45%,
		rgb(0 0 0 / .27) 62%, rgb(0 0 0 / .16) 77%, rgb(0 0 0 / .08) 91%, rgb(0 0 0 / .06);

	/* 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;
	}
}

/* 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;
	}
}

/* A pill of dark glass, a label laid over a picture (the blog's categories): Space Grotesk,
   13 units, white, in a pill 20.18 units tall, 10 in from its ends. One too long for its room
   ends in an ellipsis. */
.ano-pill {
	flex: none;
	box-sizing: border-box;
	max-width: 100%;
	overflow: hidden;
	padding: 0 .77em;
	border-radius: 1em;
	background: rgb(15 15 15 / .4);
	backdrop-filter: blur(4px);
	font: 400 max(.75rem, calc(13 * var(--ano-unit))) / 1.55 var(--ano-font-family-accent);
	color: #fff;
	white-space: nowrap;
	text-overflow: ellipsis;
}

/* A page's hero (scaff-sketch1 copy 2.svg; the page sets it up, _PageHero.cshtml renders it):
   its title laid over its icon, the two centred, then any description, and a separator, on a
   halftone band that fades out downwards from the top of the page, as the footer's fades out
   upwards.
   Measured from the sketch, by the title's 80 units (Outfit Light): the icon is 207.3 square,
   its top 175.19 down the page; the title starts 98.5 into it, its baseline 153.24 under its
   top; the separator runs 43.3 under it, and the band ends 87.12 under that (514.91 down).
   The title and icon are measured in the title's size, so they keep together where its floor
   holds it up. A description (not in the sketch) is smaller than a lede, 30 units under the
   icon. */
ano-sector.ano-page-hero {
	padding-block: calc(175.19 * var(--ano-unit)) calc(87.12 * var(--ano-unit));

	& > .ano-dots {
		position: absolute;
		inset: 0;
		z-index: -1;
		opacity: .12;
		mask-image: linear-gradient(to bottom, var(--ano-dots-fade));
	}

	& > p {
		max-width: 28em;
		margin: calc(30 * var(--ano-unit)) 0 0;
		font-size: max(1rem, calc(24 * var(--ano-unit)));
		line-height: 1.3;
		text-align: center;
		text-wrap: balance;
	}

	& > .ano-separator {
		margin-top: calc(43.3 * var(--ano-unit));
	}
}

/* The title runs over the icon's right-hand part, its first baseline 1.915em under the
   icon's top (Outfit's line box, at a line height of 1, has its baseline .87em down). */
.ano-page-hero-title {
	display: flex;
	align-items: flex-start;
	font-size: max(2.5rem, calc(80 * var(--ano-unit)));

	& > ano-hero-icon {
		flex: none;
		width: 2.591em;
		height: 2.591em;
	}

	& > h1 {
		min-width: 0;
		margin: 1.045em 0 0 -1.36em;
		font: inherit;
		line-height: 1;
		text-wrap: balance;
	}
}

/* A form card's stage (sketch-newsletter, sketch-contact): the card (FormCard.ts) centred
   between two halftone pads that reach out to the page's edges. The card is the sketches'
   895.8 units wide; the pads stop 28 units short of its sides and run from --pads-top units
   under its top to --pads-bottom under its bottom. The left pad's dots are laid from its right
   edge, so they hold still as the page's width changes. A narrow screen has no room for them. */
.ano-form-stage {
	--pads-top: 36.3;
	--pads-bottom: 42.7;
	position: relative;
	width: min(100%, calc(895.8 * var(--ano-unit)));

	& > .ano-dots {
		position: absolute;
		top: calc(var(--pads-top) * var(--ano-unit));
		bottom: calc(-1 * var(--pads-bottom) * var(--ano-unit));
		width: 100vw;
	}

	& > .ano-dots:first-child {
		--dots-x: -100vw;
		right: calc(100% + 28 * var(--ano-unit));
	}

	& > .ano-dots:nth-child(2) {
		left: calc(100% + 28 * var(--ano-unit));
	}
}

@media (max-width: 40rem) {
	.ano-form-stage > .ano-dots {
		display: none;
	}
}

/* A form card's copy, one centred column: the title across the card (48-unit type, in
   lavender), then the paragraph in a 631.2-unit measure, the form --form-width wide (the card
   sets it) and the button, 30 units apart. An error or a challenge opens up the gap over the
   button (.ano-form-status). The back's title and paragraph sit 19.9 units apart. */
:is(ano-newsletter, ano-contact) {
	& .ano-title {
		font-size: max(1.75rem, calc(48 * var(--ano-unit)));
		color: var(--anovase-lavender);
		text-align: center;

		&:focus {
			outline: none;
		}
	}

	& .ano-lede {
		align-self: center;
		width: min(100%, calc(631.2 * var(--ano-unit)));
		margin-top: calc(37.4 * var(--ano-unit));
		color: #d3d3d3;
	}

	& form {
		display: flex;
		flex-direction: column;
		align-items: center;
		margin-top: calc(30 * var(--ano-unit));
	}

	& [slot=done] {
		display: flex;
		flex-direction: column;

		& > .ano-lede {
			margin-top: calc(19.9 * var(--ano-unit));
		}
	}
}

.ano-form-status {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: calc(12 * var(--ano-unit));
	width: min(100%, var(--form-width));
	padding-block: calc(15 * var(--ano-unit));

	& > .ano-field-error {
		align-self: stretch;
		margin: 0;
	}

	& > [data-challenge]:empty {
		display: none;
	}
}

/* View transitions between pages (the router runs each fragment swap in one): a portfolio work's
   name, kind and two strongest shards (view-transition-class work) move between the portfolio
   and the work's own page, and a blog post's title, banner, categories and date (post) between
   the Blog page and the post's, a little slower than the pages cross-fade, on the sketches'
   ease. The banner's old and new pictures both fill its box as it changes shape, cropped
   rather than stretched; the categories keep their size, held to the box's top right corner.
   The title and date fly over the banner, which they cross on the way: on a card they lie on
   its foot, on the post's page above and under it. */
::view-transition-group(*.work),
::view-transition-old(*.work),
::view-transition-new(*.work),
::view-transition-group(*.post),
::view-transition-old(*.post),
::view-transition-new(*.post) {
	animation-duration: .5s;
	animation-timing-function: cubic-bezier(.65, 0, .35, 1);
}

::view-transition-old(post-banner),
::view-transition-new(post-banner) {
	height: 100%;
	object-fit: cover;
}

::view-transition-old(post-categories),
::view-transition-new(post-categories) {
	height: 100%;
	object-fit: none;
	object-position: right top;
}

::view-transition-group(post-title),
::view-transition-group(post-date) {
	z-index: 1;
}

/* Carrying: the frame's own view transitions, as a view opens in its open state or closes
   (FrameHost.ts), the root marked data-frame-carrying meanwhile. The frame's layers then travel
   as themselves, so they keep their stacking over whatever the page shares in its own
   transitions: the scrim over the page, the view over the scrim, and the corner fragments over
   all. Each shows live, its old picture dropped, so it plays its own fades and the mark's
   unfolding as it would without a transition. */
:root[data-frame-carrying] {
	& ano-frame::part(scrim) {
		view-transition-name: frame-scrim;
	}

	& ano-frame::part(view) {
		view-transition-name: frame-view;
	}

	& ano-frame::part(head) {
		view-transition-name: frame-head;
	}

	& ano-frame::part(foot) {
		view-transition-name: frame-foot;
	}

	&::view-transition-group(frame-scrim) {
		z-index: 1;
	}

	&::view-transition-group(frame-view) {
		z-index: 2;
	}

	&::view-transition-group(*.carried) {
		z-index: 3;
	}

	&::view-transition-group(frame-head),
	&::view-transition-group(frame-foot) {
		z-index: 4;
	}

	&::view-transition-old(frame-scrim),
	&::view-transition-old(frame-view),
	&::view-transition-old(frame-head),
	&::view-transition-old(frame-foot) {
		display: none;
	}

	&::view-transition-new(frame-scrim),
	&::view-transition-new(frame-view),
	&::view-transition-new(frame-head),
	&::view-transition-new(frame-foot) {
		animation: none;
	}
}

/* What a view carries out of the page and back (view-transition-class carried: a gallery's
   picture, into the viewer) travels between the view and the corner fragments, on the
   sketches' ease; its old and new pictures both fill its box as it changes shape, cropped
   rather than stretched. The viewer names its current picture here, by its part, so the name
   meets the page's picture's in the document's scope; the page's picture is named inline. */
ano-gallery-viewer::part(carried) {
	view-transition-name: gallery-medium;
	view-transition-class: carried;
}

::view-transition-group(*.carried),
::view-transition-old(*.carried),
::view-transition-new(*.carried) {
	animation-duration: .5s;
	animation-timing-function: cubic-bezier(.65, 0, .35, 1);
}

::view-transition-old(*.carried),
::view-transition-new(*.carried) {
	height: 100%;
	object-fit: cover;
}
