/*
 * One stylesheet, one origin, no imports and no remote URL. Nothing here
 * fetches anything from another host — the page is meant to survive a
 * Content-Security-Policy of `default-src 'self'` without a single exception,
 * and that only holds as long as this file stays free of external addresses.
 * The typeface below is served from /fonts on this same origin for exactly
 * that reason.
 */

/*
 * Inter, variable weight axis, latin subset. Copied out of the npm package
 * @fontsource-variable/inter@5.3.0 — the same package the app frontend uses,
 * so both surfaces render in the same typeface without this service growing a
 * package.json. The licence sits next to the file (SIL OFL 1.1).
 *
 * `font-display: swap`: the fallback stack renders immediately and is replaced
 * when the file arrives. The alternative, a blocking period, hides the
 * headline and the form for as long as the download takes.
 */
@font-face {
	font-family: 'Inter Landing';
	font-style: normal;
	font-weight: 100 900;
	font-display: swap;
	/* Plain `woff2`, not the legacy `woff2-variations` token: the weight range
	   above is what tells the browser this is variable, and the older format
	   string is rejected outright by some engines. */
	src: url('/fonts/inter-latin-wght-normal.woff2') format('woff2');
}

:root {
	/*
	 * The ticket's table. Four of the six already existed in the app's own
	 * scale (src/index.css, Tailwind @theme) and are copied by value, so both
	 * surfaces stay the same colour without this service importing anything:
	 *
	 *   --canvas          = --color-canvas         identical
	 *   --ink             = --color-ink            identical
	 *   --ink-secondary   = --color-ink-secondary  the app's #6B6B6B, not the
	 *                                              ticket's #6B6B66 — 5.10:1
	 *                                              against the canvas either
	 *                                              way, and one value beats two
	 *                                              that differ by a digit
	 *   --hairline        = --color-hairline       the app's #E8E6E3
	 *
	 * --hairline-strong has no counterpart in the app scale and is the ticket's
	 * value. Contrast against the canvas, measured: ink 18.08:1, ink-secondary
	 * 5.10:1 — both clear AA for body text. The two hairlines are borders, not
	 * text, and are not held to it.
	 */
	--canvas: #fafaf9;

	/*
	 * The second area tone, the ticket's value, kept after measuring it: 7.9 %
	 * darker than the canvas in luminance — enough to read as a different
	 * surface across a full window, too little to read as a box drawn on top of
	 * one.
	 *
	 * What it costs, checked rather than assumed: --ink-secondary drops from
	 * 5.10:1 on the canvas to 4.72:1 on this tone. Still AA, and that is the
	 * number to watch if the tone is ever deepened.
	 */
	--surface: #f2f1ef;

	/*
	 * The one dark surface, and deliberately the same value as --ink: the
	 * section is the text colour turned into a field, which is why it needs no
	 * new hue. Still its own token — the two are the same number for a reason
	 * that could stop being true.
	 */
	--surface-dark: #111111;

	--ink: #111111;
	--ink-secondary: #6b6b6b;
	--hairline: #e8e6e3;
	--hairline-strong: #c9c9c5;

	/*
	 * For the dark section only. --ink-secondary measures 3.54:1 on
	 * --surface-dark — it is a colour for light ground, and reusing it there is
	 * exactly what the ticket warns against. These are its counterparts,
	 * measured against #111111: heading 18.08:1, body and kicker 7.78:1. The
	 * hairline is a border and is not held to text contrast.
	 */
	--ink-on-dark: #fafaf9;
	--ink-on-dark-secondary: #a9a6a1;
	--hairline-on-dark: #3a3a38;

	--radius: 8px;

	/* Two weights, as specified. Named so that nothing reaches for a third. */
	--weight-regular: 400;
	--weight-medium: 500;

	--font: 'Inter Landing', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;

	--nav-height: 4rem;
}

* {
	box-sizing: border-box;
}

html {
	/* Anchor links glide; the media query at the bottom of this file turns it
	   off for anyone who asked for less motion. */
	scroll-behavior: smooth;
	/* Section targets clear the sticky header instead of hiding under it. */
	scroll-padding-top: var(--nav-height);
}

body {
	margin: 0;
	background: var(--canvas);
	color: var(--ink);
	font-family: var(--font);
	font-weight: var(--weight-regular);
	font-size: 16px;
	line-height: 1.6;
	-webkit-font-smoothing: antialiased;
}

h1,
h2,
h3 {
	font-weight: var(--weight-medium);
	letter-spacing: -0.02em;
	line-height: 1.15;
	margin: 0;
	text-wrap: balance;
}

p {
	margin: 0;
}

a {
	color: inherit;
}

.plain-list {
	list-style: none;
	margin: 0;
	padding: 0;
}

:where(a, button, summary, input):focus-visible {
	outline: 2px solid var(--ink);
	outline-offset: 2px;
	border-radius: 4px;
}

/* ── Layout ─────────────────────────────────────────────────────────────── */

.container {
	width: 100%;
	max-width: 64rem; /* = max-w-5xl — grids, the feature split, the nav */
	margin: 0 auto;
	padding-inline: 1.5rem;
}

/* The feature split, 72rem = max-w-6xl. Only that one block is this wide. */
.container-wide {
	max-width: 72rem;
}

/*
 * The reading measure, 48rem = max-w-3xl. Nested inside .container rather than
 * replacing it, and left-aligned rather than centred: a centred 48rem block
 * inside a centred 64rem one starts 8rem to the right of the cards below it,
 * which reads as a mistake rather than as a narrower column. In the hero,
 * where everything is centred anyway, `margin-inline: auto` puts it back in
 * the middle.
 */
.measure {
	max-width: 48rem;
}

/*
 * One vertical rhythm for every section, no exceptions and no per-section
 * correction. Two steps, not three — the 1024px tier that used to sit here
 * made two neighbouring sections differ by 2rem for no reason a reader could
 * name.
 */
/*
 * The two light surfaces. Each tone sits on the <section> itself, which spans
 * the window — so it runs edge to edge while the content inside stays in its
 * container. Put on the container instead it would be a stripe the width of
 * the text, which reads as a box someone forgot to remove.
 *
 * No dividing lines between sections of different tone: the change of surface
 * is the division. The order in the markup never puts two toned sections next
 * to each other, so none is needed anywhere.
 */
.section-surface {
	background: var(--surface);
}

/*
 * Cards on the toned ground take the canvas, so they lift off it. On the
 * canvas they keep the hairline instead — that is .card's own rule.
 */
.section-surface .card {
	background: var(--canvas);
}

.section {
	padding-block: 3.5rem; /* = py-14 */
}

@media (min-width: 768px) {
	.section {
		padding-block: 6rem; /* = py-24 */
	}
}

/*
 * One gap from a section heading to whatever follows it, and genuinely no
 * exceptions — the two that used to sit here (.closing h2 and .feature-text h2
 * at 0) were the variation the ticket names.
 *
 * The two rules below stop it from stacking: where a heading is followed by
 * prose that carries its own margin-top, that margin would add to this one and
 * the "consistent" gap would come out at 3.4rem in exactly those two places.
 */
.section h2 {
	font-size: 1.5rem;
	margin-bottom: 2.5rem; /* = mb-10 */
}

.section h2 + .lede,
.feature-text h2 + p {
	margin-top: 0;
}

@media (min-width: 768px) {
	.section h2 {
		font-size: 1.875rem;
	}
}

/* ── 0 — Nav ────────────────────────────────────────────────────────────── */

/*
 * Three registered custom properties, because a colour only animates if the
 * browser knows it is a colour — an unregistered custom property is a string
 * and would jump rather than blend. Registering them also means the children
 * below need no keyframes of their own: they read the variable, and animating
 * it on .nav moves all of them at once.
 *
 * The initial values are the settled state, which is also what a browser
 * without scroll-driven animations lands on. See the note at the animation.
 */
@property --nav-fg {
	syntax: '<color>';
	inherits: true;
	initial-value: #111111;
}

@property --nav-fg-muted {
	syntax: '<color>';
	inherits: true;
	initial-value: #6b6b6b;
}

@property --nav-edge {
	syntax: '<color>';
	inherits: true;
	initial-value: #c9c9c5;
}

/*
 * fixed, not sticky: the picture starts at the very top of the document and
 * the nav has to sit on it rather than above it.
 */
.nav {
	position: fixed;
	inset-inline: 0;
	top: 0;
	z-index: 50;
	background: var(--canvas);
	border-bottom: 1px solid var(--hairline);
	color: var(--nav-fg);
}

/*
 * Only a `from` block. The settled state is what the rules above already say,
 * so there is nothing to repeat in a `to` — and a browser that ignores
 * animation-timeline runs this on the document timeline with a 0s duration,
 * lands on the end state, and therefore on exactly those rules. Which is the
 * right place for it to land.
 *
 * Same timeline and same range as the dissolve, so the two cannot drift.
 */
/*
 * The change is compressed into the middle of the range instead of being
 * spread across all of it, and that is a legibility decision with a
 * measurement behind it.
 *
 * Spread over the full 70svh, the nav spends hundreds of pixels of scrolling
 * with half-grey text on a half-transparent background: measured 1.57:1 at
 * 10% and 1.54:1 at 50%. Neither state is unreadable — the crossfade between
 * them is. Compressed to 40–55% of the range, the same crossfade takes about
 * 95px of scrolling, the endpoints are unchanged, and there is still no jump.
 */
@keyframes nav-settle {
	from,
	40% {
		background-color: transparent;
		border-bottom-color: transparent;
		--nav-fg: #ffffff;
		--nav-fg-muted: rgb(255 255 255 / 0.88);
		--nav-edge: rgb(255 255 255 / 0.7);
	}

	55% {
		background-color: var(--canvas);
		border-bottom-color: var(--hairline);
		--nav-fg: #111111;
		--nav-fg-muted: #6b6b6b;
		--nav-edge: #c9c9c5;
	}
}

.nav {
	animation: nav-settle linear both;
	animation-timeline: scroll(root block);
	animation-range: 0 70svh;
}

.nav-inner {
	display: flex;
	align-items: center;
	gap: 1.5rem;
	height: var(--nav-height);
}

.logo {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	text-decoration: none;
	margin-right: auto;
}

.logo-mark {
	width: 1.25rem;
	height: 1.25rem;
	border-radius: 4px;
	background: var(--nav-fg);
}

.logo-word {
	font-weight: var(--weight-medium);
	letter-spacing: -0.01em;
}

.nav-links {
	display: none;
	gap: 1.5rem;
	font-size: 0.9375rem;
	color: var(--nav-fg-muted);
}

.nav-links a {
	text-decoration: none;
}

.nav-links a:hover {
	color: var(--nav-fg);
}

@media (min-width: 768px) {
	.nav-links {
		display: flex;
	}
}

/* ── Buttons ────────────────────────────────────────────────────────────── */

/*
 * One filled style on the page, and it is the waitlist CTA. Everything else is
 * outline or plain text — .button-outline and .link-button below.
 */
.button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	height: 2.75rem; /* 44px, same as the input beside it */
	padding-inline: 1.125rem;
	border: 1px solid var(--ink);
	border-radius: var(--radius);
	background: var(--ink);
	color: var(--canvas);
	font: inherit;
	font-weight: var(--weight-medium);
	white-space: nowrap;
	text-decoration: none;
	cursor: pointer;
}

.button[disabled] {
	cursor: default;
	opacity: 0.55;
}

.button-outline {
	height: 2.25rem;
	padding-inline: 0.875rem;
	background: transparent;
	color: var(--nav-fg);
	border-color: var(--nav-edge);
	font-size: 0.9375rem;
}

.button-outline:hover {
	border-color: var(--nav-fg);
}

.link-button {
	padding: 0;
	border: 0;
	background: none;
	color: inherit;
	font: inherit;
	cursor: pointer;
	text-decoration: none;
}

/* ── 1 — Hero, 7 — Closing ──────────────────────────────────────────────── */

/* ── Hero stage ─────────────────────────────────────────────────────────── */

/*
 * Two viewport heights stacked: the media sticks to the top through the first
 * one while the panel travels up over it. The panel's own height is the scroll
 * distance, so nothing artificial is added to the page.
 *
 * No max-width and no horizontal padding here — the stage sits directly in
 * <main>, outside any container, which is why the picture reaches both window
 * edges without a negative margin.
 */
.hero-stage {
	position: relative;
}

.hero-media {
	position: sticky;
	top: 0;
	z-index: 0;
	height: 100svh;
	overflow: hidden;
}

.hero-image {
	width: 100%;
	height: 100%;
	object-fit: cover;
	/* Slightly above centre: the motif's subject sits in the upper half, and
	   50% 50% cuts its heads off on a short window. */
	object-position: 50% 45%;
}

/*
 * The nav sits on the picture, and the picture is bright in places. The
 * gradient darkens the top band, and the shape of it was decided by measuring
 * rather than by taste.
 *
 * A plain 0.55-to-0 ramp is not enough: it has already thinned to ~0.37 by the
 * lower edge of the nav, and white on the brightest pixel there came out at
 * 3.02:1. So the band holds full strength across the nav's own height and only
 * then fades. 0.62 puts the worst case at 96/255 under white text, which is
 * 6.31:1 measured.
 */
.hero-scrim {
	position: absolute;
	inset: 0;
	background: linear-gradient(
		to bottom,
		rgb(0 0 0 / 0.62) 0,
		rgb(0 0 0 / 0.62) 4.5rem,
		rgb(0 0 0 / 0) 26%
	);
	pointer-events: none;
}

/*
 * On first load the picture is all there is, and nothing says the page
 * continues. Decorative, so it carries no text — and it leaves faster than the
 * picture does, because it has done its job as soon as the reader moves.
 */
.hero-cue {
	position: absolute;
	bottom: 1.75rem;
	left: 50%;
	translate: -50% 0;
	pointer-events: none;
}

.hero-cue svg {
	display: block;
	width: 28px;
	height: 28px;
	fill: none;
	stroke: rgb(255 255 255 / 0.85);
	stroke-width: 1.5;
	stroke-linecap: round;
	stroke-linejoin: round;
}

/*
 * The panel is what covers the picture, and the opaque background is the whole
 * mechanism — a transparent panel would let the motif show through the text no
 * matter what the dissolve does.
 */
.hero-panel {
	position: relative;
	z-index: 10;
	min-height: 100svh;
	background: var(--canvas);
	display: flex;
	flex-direction: column;
	justify-content: center;
	/*
	 * Block padding only where it can matter. With min-height and centred
	 * content the panel is exactly one viewport and the padding is inert — but
	 * on a short window the content does exceed it, and then this is what keeps
	 * the form off the edge. Nothing here adds to the panel's height in the
	 * normal case; measured 900px on a 900px viewport.
	 */
	padding-block: 3.5rem;
}

.hero-inner {
	display: flex;
	flex-direction: column;
	align-items: center;
	text-align: center;
	margin-inline: auto;
}

/*
 * The dissolve, and the nav's change of state, on one timeline so they cannot
 * drift apart. Native scroll-driven animation: no library, no scroll listener,
 * no IntersectionObserver.
 *
 * `both` is not decoration — without the fill mode the picture snaps back to
 * its unanimated state outside the range, which shows as a flash on the way
 * back up.
 *
 * opacity and transform only. A blur over a viewport-sized surface leaves the
 * compositor and costs real frames on a weak device.
 */
@keyframes hero-dissolve {
	to {
		opacity: 0;
		transform: scale(1.06);
	}
}

.hero-image,
.hero-scrim {
	animation: hero-dissolve linear both;
	animation-timeline: scroll(root block);
	animation-range: 0 70svh;
}

/* Same timeline, shorter range: the hint has done its job at the first move. */
.hero-cue {
	animation: hero-dissolve linear both;
	animation-timeline: scroll(root block);
	animation-range: 0 20svh;
}

/*
 * This block is needed, and the ticket says it is not — so it is worth being
 * precise about why. Measured in Chromium with animation-timeline forced to
 * `auto`, which is what a browser that does not know the property does: the
 * picture comes out at opacity 0, not at its resting state.
 *
 * The cause is the shorthand. `animation: hero-dissolve linear both` leaves the
 * duration at its initial 0s; on the document timeline the animation is
 * therefore over before it starts, and `both` pins the element to the last
 * keyframe. So the fallback is not "the image stands still and gets covered" —
 * it is "there is no image".
 *
 * The nav needs no such guard, and for a reason worth copying: its keyframes
 * carry only a `from` block, so its end state is whatever the ordinary rules
 * already say. A 0s animation lands exactly there.
 */
@supports not (animation-timeline: scroll()) {
	.hero-image,
	.hero-scrim,
	.hero-cue {
		animation: none;
	}
}

.pill {
	display: inline-block;
	margin-bottom: 1.5rem;
	padding: 0.25rem 0.75rem;
	border: 1px solid var(--hairline-strong);
	border-radius: 999px;
	color: var(--ink-secondary);
	font-size: 0.8125rem;
}

.lede {
	margin-top: 1rem;
	max-width: 48ch;
	color: var(--ink-secondary);
	font-size: 1.125rem;
	/* Stops the last line from being a single word. */
	text-wrap: pretty;
}

.trust-line {
	margin-top: 0.875rem;
	color: var(--ink-secondary);
	font-size: 0.75rem;
}

/*
 * No border. The FAQ above is canvas and this is the toned surface, so the
 * change of ground is the division — a line on top of it would be saying the
 * same thing twice.
 */
.closing h2 {
	font-size: 1.75rem;
}

/* ── Waitlist form ──────────────────────────────────────────────────────── */

.waitlist {
	width: 100%;
	max-width: 30rem;
	margin-top: 2rem;
	text-align: left;
}

.waitlist-row {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
}

@media (min-width: 640px) {
	.waitlist-row {
		flex-direction: row;
	}
}

.waitlist-input {
	flex: 1 1 auto;
	min-width: 0;
	height: 2.75rem;
	padding-inline: 0.75rem;
	border: 1px solid var(--hairline-strong);
	border-radius: var(--radius);
	background: var(--canvas);
	color: var(--ink);
	font: inherit;
}

.waitlist-input::placeholder {
	color: var(--ink-secondary);
}

.waitlist-input:read-only {
	color: var(--ink-secondary);
}

.visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

/*
 * The honeypot. Moved off-screen rather than display:none or hidden, because
 * a field that is not rendered at all is one a form-filler can spot as cheaply
 * as we can. Also out of the tab order and out of the accessibility tree, so
 * nobody using a keyboard or a screen reader ever meets it.
 */
.honeypot {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	opacity: 0;
}

.waitlist-message {
	display: flex;
	align-items: flex-start;
	gap: 0.5rem;
	margin-top: 0.625rem;
	font-size: 0.875rem;
	color: var(--ink-secondary);
}

.waitlist-message .icon {
	flex: none;
	margin-top: 0.15rem;
	color: var(--ink);
}

.waitlist-error {
	margin-top: 0.5rem;
	font-size: 0.875rem;
	color: var(--ink);
}

/* ── 2 — Cards ──────────────────────────────────────────────────────────── */

/*
 * gap-4 and a floor on the height. Grid stretches its items by default, so the
 * cards in a row already match — the min-height is for the row that has only
 * short cards in it, which otherwise collapses to two lines of text in a lot
 * of border.
 */
.cards {
	display: grid;
	gap: 1rem;
}

@media (min-width: 640px) {
	.cards-4 {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

@media (min-width: 768px) {
	.cards-3 {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

@media (min-width: 1024px) {
	.cards-4 {
		grid-template-columns: repeat(4, minmax(0, 1fr));
	}
}

.card {
	padding: 1.25rem; /* = p-5 */
	min-height: 9.5rem;
	border: 1px solid var(--hairline);
	border-radius: var(--radius);
}

@media (min-width: 640px) {
	.card {
		padding: 1.5rem; /* = p-6 */
	}
}

.card p {
	color: var(--ink-secondary);
	font-size: 0.9375rem;
}

.card-label {
	margin-bottom: 0.5rem;
	font-size: 1rem;
}

/*
 * The three-card row makes its point by not looking uniform: the first two are
 * dashed and set in secondary text, the third is solid and in the ink colour.
 * Anyone tempted to tidy this into three identical cards would be deleting the
 * argument the section exists to make.
 */
.card-muted {
	border-style: dashed;
	color: var(--ink-secondary);
}

.card-muted .card-label {
	color: var(--ink-secondary);
}

.card-primary {
	border-color: var(--ink);
}

.card-primary p {
	color: var(--ink);
}

.icon {
	display: block;
	width: 20px;
	height: 20px;
	margin-bottom: 0.75rem;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.5;
	stroke-linecap: round;
	stroke-linejoin: round;
	color: var(--ink);
}

/* ── 3 — Steps ──────────────────────────────────────────────────────────── */

.steps {
	display: grid;
	gap: 2rem;
}

@media (min-width: 768px) {
	.steps {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		gap: 2.5rem;
	}
}

.step-number {
	display: block;
	margin-bottom: 0.75rem;
	color: var(--ink-secondary);
	font-variant-numeric: tabular-nums;
	font-size: 0.875rem;
}

.step-title {
	margin-bottom: 0.375rem;
	font-size: 1.0625rem;
}

.step p {
	color: var(--ink-secondary);
}

/* ── 4 — Kernfeature, die dunkle Sektion ────────────────────────────────── */

/*
 * The one place the page raises its voice. Edge to edge, no radius, no border,
 * no shadow — the field itself is the statement, and a rounded dark box would
 * be a card instead.
 *
 * More vertical room than the light sections (py-20 / py-32 against 14 / 24):
 * a dark field needs the air, or it reads as a bar rather than as a section.
 */
.section-dark {
	background: var(--surface-dark);
	color: var(--ink-on-dark);
	padding-block: 5rem;
}

@media (min-width: 768px) {
	.section-dark {
		padding-block: 8rem;
	}
}

.section-dark .feature-text p,
.section-dark .kicker {
	color: var(--ink-on-dark-secondary);
}

/*
 * The placeholder on dark ground: a light hairline instead of the dark one,
 * and a fill a shade above the field rather than white — white here would be a
 * hole in the page.
 *
 * When the real screenshot arrives: take it from a dark variant of the product
 * UI if there is one. A bright screenshot on this field looks like something
 * pasted on.
 */
.section-dark .feature-shot {
	border-color: var(--hairline-on-dark);
}

.section-dark .feature-shot-empty {
	background: #191918;
	color: var(--ink-on-dark-secondary);
}

/* ── 4 — Kernfeature ────────────────────────────────────────────────────── */

/*
 * The only asymmetric block on the page. Below the breakpoint it stacks with
 * the text first, which is also the source order — no order: property, so the
 * reading order and the visual order cannot drift apart.
 */
.feature {
	display: grid;
	gap: 2rem;
	align-items: center;
}

@media (min-width: 900px) {
	.feature {
		grid-template-columns: 45fr 55fr;
		gap: 3rem;
	}
}

.feature-text p {
	margin-top: 0.875rem;
	color: var(--ink-secondary);
}

.kicker {
	margin-bottom: 0.75rem;
	color: var(--ink-secondary);
	font-size: 0.8125rem;
}

.feature-shot {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 16 / 10;
	border: 1px solid var(--hairline);
	border-radius: var(--radius);
	object-fit: cover;
}

/* Holds the ratio until the real screenshot replaces the element. */
.feature-shot-empty {
	display: grid;
	place-items: center;
	color: var(--ink-secondary);
	font-size: 0.875rem;
}

/* ── 6 — FAQ ────────────────────────────────────────────────────────────── */

/*
 * Native <details>. The chevron rotates on [open]; nothing here animates the
 * panel itself, so there is no height to measure and no script to load.
 */
/*
 * Full container width, like every other section, so the heading above it
 * lines up with the rest of the page. The answers keep their own 60ch measure
 * further down — that is a reading-width limit, not a layout one.
 */
.faq {
	border-top: 1px solid var(--hairline);
}

.faq details {
	border-bottom: 1px solid var(--hairline);
}

.faq summary {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding-block: 1rem;
	font-weight: var(--weight-medium);
	cursor: pointer;
	list-style: none;
}

.faq summary::-webkit-details-marker {
	display: none;
}

.faq p {
	padding-bottom: 1rem;
	max-width: 60ch;
	color: var(--ink-secondary);
}

.chevron {
	flex: none;
	width: 20px;
	height: 20px;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.5;
	stroke-linecap: round;
	stroke-linejoin: round;
	color: var(--ink-secondary);
	transition: transform 150ms ease;
}

.faq details[open] .chevron {
	transform: rotate(180deg);
}

/* ── Impressum / Datenschutz ────────────────────────────────────────────── */

.legal h1 {
	margin-bottom: 1.5rem;
	font-size: 1.875rem;
}

/* The placeholder the two pages carry until their text is written. */
.todo {
	padding: 1rem;
	border: 1px dashed var(--hairline-strong);
	border-radius: var(--radius);
	color: var(--ink-secondary);
	font-size: 0.9375rem;
}

/* ── 8 — Footer ─────────────────────────────────────────────────────────── */

.footer {
	border-top: 1px solid var(--hairline);
	padding-block: 2rem;
}

.footer-inner {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
}

.footer-links {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 1.25rem;
	color: var(--ink-secondary);
	font-size: 0.875rem;
}

.footer-links a {
	text-decoration: none;
}

.footer-links a:hover {
	color: var(--ink);
}

/* ── The gate ───────────────────────────────────────────────────────────── */

/*
 * The whole viewport until the question is answered. Not an overlay on top of
 * the page — the page is not in the layout at all while this is here (#page
 * carries `hidden`), so there is nothing underneath to scroll, read or tab
 * into, and no picture being fetched behind it.
 */
.gate {
	min-height: 100svh;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 3rem;
	padding: 2rem 1.5rem;
}

.gate-inner {
	width: 100%;
	max-width: 34rem;
}

.gate-question {
	display: block;
	margin-bottom: 1rem;
	font-size: 1.125rem;
	text-wrap: pretty;
}

.gate-row {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
}

@media (min-width: 640px) {
	.gate-row {
		flex-direction: row;
	}
}

.gate-row input {
	flex: 1 1 auto;
	min-width: 0;
	height: 2.75rem;
	padding-inline: 0.75rem;
	border: 1px solid var(--hairline-strong);
	border-radius: var(--radius);
	background: var(--canvas);
	color: var(--ink);
	font: inherit;
}

.gate-hint {
	margin-top: 0.625rem;
	color: var(--ink-secondary);
	font-size: 0.875rem;
}

/*
 * Impressum and Datenschutz have to be reachable without answering anything —
 * they are the two pages the law requires to be available directly, and every
 * other route to them is behind the question.
 */
.gate-footer {
	display: flex;
	gap: 1.25rem;
	color: var(--ink-secondary);
	font-size: 0.875rem;
}

.gate-footer a {
	text-decoration: none;
}

.gate-footer a:hover {
	color: var(--ink);
}

/* ── Reduced motion ─────────────────────────────────────────────────────── */

/*
 * Covers both movers on the page: the anchor glide declared on html, and the
 * chevron. The scroll the nav button performs asks matchMedia separately —
 * scrollIntoView takes its behaviour from an argument, not from this rule.
 */
@media (prefers-reduced-motion: reduce) {
	html {
		scroll-behavior: auto;
	}

	.chevron {
		transition: none;
	}

	/*
	 * Everything stays where it is, at full opacity — the movement goes, the
	 * layout does not. The panel still covers the picture by scrolling over it,
	 * which is geometry rather than animation, so the page still works exactly
	 * the same way; it just does not fade on the way.
	 */
	.hero-image,
	.hero-scrim,
	.hero-cue,
	.nav {
		animation: none;
	}
}
