@charset "utf-8";

/* Autohaus Bertram — stylesheet
   Single file, no build step, no external requests.
   Layer order fixes specificity once, so nothing here needs !important.

   Browser floor: iOS 15 / Safari 15. Deliberately avoided:
   - light-dark()      — Safari 17.5+, would blank every colour below that
   - @media (width > ) — Safari 16.4+, would drop the whole mobile layout
   Both are replaced with long-standing equivalents. Anything newer than the
   floor is used only where its absence costs nothing (see @supports below). */

@layer reset, tokens, base, layout, components, utilities;

/* ------------------------------------------------------------------ fonts */

/* woff2 only. The .woff fallbacks were dropped: every browser at or above the
   iOS 15 / Safari 15 floor supports woff2 (Safari has since 10, 2016), so the
   .woff files were 216 KB that nothing would ever request. */

@font-face {
	font-family: "Zilla Slab";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("../fonts/ZillaSlab-Regular.woff2") format("woff2");
}

@font-face {
	font-family: "Montserrat";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("../fonts/Montserrat-Regular.woff2") format("woff2");
}

/* ------------------------------------------------------------------ reset */

@layer reset {
	*, *::before, *::after { box-sizing: border-box; }

	* { margin: 0; }

	html {
		/* 100%, never "none": "none" blocks iOS text inflation, which people
		   with low vision rely on. */
		-webkit-text-size-adjust: 100%;
		text-size-adjust: 100%;
		scroll-padding-block-start: 6rem;
	}

	body {
		min-block-size: 100vh;
		/* Rotated figures on /historie/ poke past the viewport edge; clip stops
		   that becoming a sideways scroll on a phone. */
		overflow-x: clip;
	}

	@supports (min-block-size: 100svh) {
		body { min-block-size: 100svh; }
	}

	img, picture, svg, video {
		display: block;
		max-inline-size: 100%;
		block-size: auto;
	}

	input, button, textarea, select { font: inherit; color: inherit; }

	p, h1, h2, h3, h4, li { overflow-wrap: break-word; }

	:target { scroll-margin-block-start: 6rem; }

	@media (prefers-reduced-motion: no-preference) {
		html { scroll-behavior: smooth; }
	}
}

/* ----------------------------------------------------------------- tokens */

@layer tokens {
	:root {
		color-scheme: light dark;

		/* Brand, carried over from the previous site. Scheme-independent. */
		--red: #a31521;
		--red-bright: #c81c2a;
		--blue: #1b5d7a;
		--blue-deep: #1d475a;

		/* Text on a dark fill — hero, callout band, footer, history header.
		   Those grounds are dark in BOTH schemes, so this token must not
		   switch with the scheme. It replaces three near-identical literals
		   (#eaf1f6, #dce8ef, #cfe0ea) that did the same job in three places.

		   Deliberately an alpha, not a hex: it then sits correctly on any
		   dark ground, including the red fact band, without a fourth value.
		   Worst case is the light end of the band gradient, --blue, at
		   5.84:1; --blue-deep 7.86:1, --red 6.07:1, the history header
		   12.98:1. audit.py composites text alpha against the backdrop, so
		   these stay machine-checkable rather than a promise. */
		--on-dark: rgba(255, 255, 255, 0.86);

		/* Palette values live here exactly once. The two switch blocks below
		   only re-point the active tokens, so there is nothing to keep in
		   sync by hand. */
		--l-bg: #ffffff;       --d-bg: #14181c;
		--l-sunken: #edf0f4;   --d-sunken: #1b2026;
		--l-raised: #ffffff;   --d-raised: #212831;
		--l-ink: #1a1f24;      --d-ink: #e8edf2;
		--l-muted: #5a6672;    --d-muted: #a4b0bd;
		--l-line: #dfe4e9;     --d-line: #303842;
		--l-accent: #a31521;   --d-accent: #ff6b78;
		--l-heading: #1b5d7a;  --d-heading: #7fc4e8;
		--l-sub: #565e66;      --d-sub: #10141a;
		--l-shadow: 15 23 42;  --d-shadow: 0 0 0;

		/* A SECOND line colour, and the reason is not taste.

		   --line is 1.28:1 against white. As the edge of a card or a rule
		   between paragraphs that is fine — it is decoration. But it is also
		   the only visible boundary of every control on the site: the text
		   inputs, the radio tiles, the ghost buttons, the burger, the
		   language switch, the theme switch, the carousel buttons and the
		   back-to-top. WCAG 2.1 SC 1.4.11 asks 3:1 for those, so at 1.28:1
		   they fail in both schemes.

		   deploy/audit.py cannot catch this: its contrast walker only visits
		   text nodes. It was found by reading, and it has to stay fixed by
		   hand — so keep controls on --line-strong and decoration on --line.

		   Light 3.79:1 on white, 3.31:1 on --l-sunken.
		   Dark  3.87:1 on --d-bg, 3.56:1 on --d-sunken, 3.23:1 on --d-raised. */
		--l-line-strong: #7c848d;  --d-line-strong: #6b7686;

		/* The faint red wash behind chips and notices. Was five hardcoded
		   rgba(163, 21, 33, …) with three different alphas, none of which
		   switched with the scheme — so in dark mode they all kept mixing the
		   LIGHT red over a dark card and came out a muddy plum. As a pair the
		   ground follows the scheme and the red text on it stays legible:
		   --accent on --l-tint 6.31:1, --d-accent on --d-tint 5.27:1. */
		--l-tint: #f4e3e4;     --d-tint: #441d24;

		/* Active scheme — light by default. */
		--bg: var(--l-bg);
		--bg-sunken: var(--l-sunken);
		--bg-raised: var(--l-raised);
		--ink: var(--l-ink);
		--ink-muted: var(--l-muted);
		--line: var(--l-line);
		--line-strong: var(--l-line-strong);
		--tint: var(--l-tint);
		--accent: var(--l-accent);
		--heading: var(--l-heading);
		--sub: var(--l-sub);
		--shadow-rgb: var(--l-shadow);

		--shadow-sm: 0 1px 2px rgba(var(--shadow-rgb), 0.1);
		--shadow-md: 0 4px 12px -2px rgba(var(--shadow-rgb), 0.14);
		--shadow-lg: 0 18px 40px -12px rgba(var(--shadow-rgb), 0.2);

		/* Fluid scale: 360px → 1280px viewport. */
		--step--1: clamp(0.88rem, 0.85rem + 0.14vw, 0.96rem);
		--step-0:  clamp(1.05rem, 0.99rem + 0.26vw, 1.2rem);
		--step-1:  clamp(1.25rem, 1.13rem + 0.52vw, 1.55rem);
		--step-2:  clamp(1.5rem,  1.28rem + 0.96vw, 2.05rem);
		--step-3:  clamp(1.75rem, 1.4rem + 1.6vw, 2.85rem);
		--step-4:  clamp(2rem,    1.4rem + 2.7vw, 3.95rem);

		--space:    clamp(1rem, 0.83rem + 0.7vw, 1.4rem);
		--space-lg: clamp(2rem, 1.48rem + 2.26vw, 3.3rem);
		--space-xl: clamp(2.5rem, 1.6rem + 3.9vw, 5.6rem);

		--measure: 62ch;
		--page: 78rem;
		--radius: 0.75rem;
		--radius-lg: 1.25rem;

		--ease: cubic-bezier(0.2, 0.7, 0.3, 1);

		--font-body: "Montserrat", system-ui, -apple-system, "Segoe UI", sans-serif;
		--font-display: "Zilla Slab", Georgia, "Times New Roman", serif;
	}

	/* Dark, because the OS asks for it — unless the visitor pinned light. */
	@media (prefers-color-scheme: dark) {
		:root:not([data-theme="light"]) {
			--bg: var(--d-bg);
			--bg-sunken: var(--d-sunken);
			--bg-raised: var(--d-raised);
			--ink: var(--d-ink);
			--ink-muted: var(--d-muted);
			--line: var(--d-line);
			--line-strong: var(--d-line-strong);
			--tint: var(--d-tint);
			--accent: var(--d-accent);
			--heading: var(--d-heading);
			--sub: var(--d-sub);
			--shadow-rgb: var(--d-shadow);
		}
	}

	/* Dark, because the visitor pinned it. */
	:root[data-theme="dark"] {
		--bg: var(--d-bg);
		--bg-sunken: var(--d-sunken);
		--bg-raised: var(--d-raised);
		--ink: var(--d-ink);
		--ink-muted: var(--d-muted);
		--line: var(--d-line);
		--line-strong: var(--d-line-strong);
		--tint: var(--d-tint);
		--accent: var(--d-accent);
		--heading: var(--d-heading);
		--sub: var(--d-sub);
		--shadow-rgb: var(--d-shadow);
	}

	:root[data-theme="light"] { color-scheme: light; }
	:root[data-theme="dark"]  { color-scheme: dark; }

	/* Windows high-contrast and "increase contrast": drop the muted greys to
	   full-strength ink and firm up every border. */
	@media (prefers-contrast: more) {
		:root {
			--l-muted: #33393f;   --d-muted: #dfe6ec;
			--l-line: #6c7681;    --d-line: #8b97a3;
			--l-heading: #123d51; --d-heading: #b3ddf2;
			--l-accent: #8b101b;  --d-accent: #ff9aa4;

			/* The control outlines have to move too, or "more contrast"
			   would leave them at the 3:1 floor while everything around
			   them firms up. */
			--l-line-strong: #4a525b;  --d-line-strong: #9aa6b3;

			/* Deepen the section ground so the alternation stays visible
			   once the greys around it get stronger, and take the muted
			   text on a dark fill to full white. */
			--l-sunken: #e5e9ee;  --d-sunken: #10151a;
			--on-dark: #ffffff;
		}
	}
}

/* ------------------------------------------------------------------- base */

@layer base {
	body {
		background-color: var(--bg);
		color: var(--ink);
		font-family: var(--font-body);
		font-size: var(--step-0);
		line-height: 1.65;
		display: flex;
		flex-direction: column;
		-webkit-font-smoothing: antialiased;
	}

	h1, h2, h3, h4 {
		font-family: var(--font-display);
		font-weight: 400;
		line-height: 1.15;
		text-wrap: balance;
		color: var(--heading);
	}

	h1 { font-size: var(--step-4); }
	h2 { font-size: var(--step-3); }
	h3 { font-size: var(--step-2); }
	h4 { font-size: var(--step-1); }

	p, li { text-wrap: pretty; }

	/* --- Silbentrennung ------------------------------------------------ */

	/* German compounds are what tears the ragged edge open, not the ragged
	   edge itself: "Windschutzscheibenaustausch" is 27 characters in a column
	   that carries 40, and "Gebrauchtwagen" pushed one line 131 px short of
	   its neighbour on the start page. Until now `hyphens` sat at its initial
	   value `manual` everywhere, so nothing was ever hyphenated; the only
	   guard was `overflow-wrap: break-word` in the reset, which snaps a word
	   mid-letter WITHOUT a hyphen — worse than any break this makes.

	   Justified text was considered and rejected. Measured on the running
	   preview, the widest reading column carries 71 characters at 1280 px and
	   34-38 on a phone, and only ONE paragraph on the whole site (the Anfahrt
	   intro, ~92) is wide enough for justification to sit still. There is also
	   no bold cut of Montserrat, so a stretched word space has no stroke
	   variation to hide behind.

	   No markup change was needed: build.py already writes <html lang="…">,
	   so the German and English patterns are picked correctly. */
	p, li, dd, figcaption, blockquote {
		/* Safari only dropped the prefix in 17; the floor here is 15. */
		-webkit-hyphens: auto;
		        hyphens: auto;
	}

	/* NEVER hyphenate, and the first group is a hard technical reason rather
	   than taste: THE DISPLAY FACE HAS NO HYPHEN. Zilla Slab was subset to 337
	   characters and carries neither U+2010 HYPHEN nor U+00AD SOFT HYPHEN, so
	   a break in one of these would pull its hyphen from the Georgia fallback
	   — a different typeface, mid-word. (Montserrat does carry both, which is
	   why body copy above is safe.) Everything using var(--font-display) is
	   therefore listed here; if a new selector takes that font, it belongs
	   here too.

	   The second group is judgement: names, addresses, phone numbers, control
	   labels and display-sized lead-ins. "Ber-tram", "Wies-baden" or a broken
	   e-mail address would be worse than any gap at the margin. */
	h1, h2, h3, h4,
	.fact__num, .person__name, .timeline__year,
	.car__name, .car__price, .stop__text h2, .stop__street,
	.lead, .hero__lead, .hero__eyebrow,
	.person__role, .person__contact, .car__facts, .stop__years,
	.details li, .footer li, .subfooter,
	.btn, .nav__link, .langswitch, .ilink, .field label, .radios legend,
	address {
		-webkit-hyphens: manual;
		        hyphens: manual;
	}

	/* How eagerly to break. hyphenate-limit-chars is Chrome 109+ / Safari 17+
	   and is ignored in silence below that; the -webkit- pair covers Safari
	   15 and 16, which is the floor. Both are pure improvement — without
	   either, the browser still only breaks a word when the break saves the
	   line, because the text is ragged-right and not justified.

	   German: at least 7 characters in the word, 4 before the break, 3 after.
	   Four before is what keeps "Ab-wicklung" from happening. */
	:lang(de) {
		-webkit-hyphenate-limit-before: 4;
		-webkit-hyphenate-limit-after: 3;
		hyphenate-limit-chars: 7 4 3;
	}

	/* English barely needs it — short words, no compounds — and tolerates it
	   worse: a break every second line is conspicuous at English word
	   lengths. Nine characters minimum means /en/ hyphenates almost never,
	   which is the right amount. The patterns follow lang, so this needs no
	   second rule anywhere else. */
	:lang(en) {
		-webkit-hyphenate-limit-before: 4;
		-webkit-hyphenate-limit-after: 3;
		hyphenate-limit-chars: 9 4 3;
	}

	a { color: var(--accent); text-underline-offset: 0.2em; }
	a:hover { text-decoration-thickness: 2px; }

	:focus-visible {
		outline: 3px solid var(--heading);
		outline-offset: 3px;
		border-radius: 2px;
	}

	/* The hero, band, footer and history header sit on dark fills where the
	   blue ring all but vanishes. */
	.hero :focus-visible,
	.band :focus-visible,
	.footer :focus-visible,
	.hist-hero :focus-visible { outline-color: #fff; }

	strong { font-weight: 700; }

	hr {
		border: 0;
		block-size: 1px;
		background-color: var(--line);
		margin-block: var(--space-lg);
	}

	::selection { background-color: var(--red); color: #fff; }
}

/* ----------------------------------------------------------------- layout */

@layer layout {
	.wrap {
		inline-size: min(100% - 2 * var(--space), var(--page));
		margin-inline: auto;
	}

	.wrap--narrow { --page: 52rem; }

	.stack > * + * { margin-block-start: var(--flow, 1em); }
	.stack > * + :is(h2, h3) { --flow: 1.8em; }
	.stack > :is(h2, h3) + * { --flow: 0.6em; }

	.section { padding-block: var(--space-xl); }
	.section--sunken { background-color: var(--bg-sunken); }

	main { flex: 1; }

	.prose { max-inline-size: var(--measure); }
}

/* ------------------------------------------------------------- components */

@layer components {

	/* --- skip link --- */

	.skip {
		position: absolute;
		inset-block-start: 0;
		inset-inline-start: 0;
		transform: translateY(-120%);
		z-index: 100;
		margin: 0.5rem;
		padding: 0.75rem 1.25rem;
		background-color: var(--bg-raised);
		color: var(--ink);
		border-radius: var(--radius);
		box-shadow: var(--shadow-lg);
		text-decoration: none;
		transition: transform 0.2s var(--ease);
	}

	.skip:focus { transform: translateY(0); }

	/* --- header --- */

	.header {
		position: sticky;
		inset-block-start: 0;
		z-index: 50;
		border-block-end: 1px solid var(--line);
		/* Deliberately NO backdrop-filter on this element. A filtered element
		   becomes the containing block for its position:fixed descendants,
		   which would trap the mobile menu panel and its scrim inside this
		   72px bar instead of covering the viewport. The blur lives on the
		   pseudo-element below, which is a child and so harmless. */
		background-color: transparent;
	}

	.header::before {
		content: "";
		position: absolute;
		inset: 0;
		z-index: -1;
		background-color: var(--bg);
	}

	/* -webkit- prefix required: Safari only dropped it in version 18.
	   If color-mix is unsupported (Safari < 16.2) that one declaration is
	   simply dropped and the opaque fill above stands — no fallback needed. */
	@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
		.header::before {
			background-color: color-mix(in srgb, var(--bg) 82%, transparent);
			-webkit-backdrop-filter: blur(14px) saturate(1.4);
			backdrop-filter: blur(14px) saturate(1.4);
		}
	}

	.header__inner {
		display: flex;
		align-items: center;
		gap: clamp(0.4rem, 0.1rem + 1.2vw, 1rem);
		min-block-size: 4.5rem;
		padding-block: 0.6rem;
	}

	.header__logo {
		flex-shrink: 1;
		min-inline-size: 0;
		margin-inline-end: auto;
		display: inline-flex;
	}

	/* Small phones have to fit logo + theme switch + burger on one line. */
	.header__logo img {
		inline-size: clamp(8.5rem, 5.5rem + 14vw, 16.2rem);
	}

	/* Two real artworks, not a CSS filter. The mark is red on black, so
	   brightness(0) invert(1) would flatten the whole thing to white and throw
	   the brand red away; the dark variant recolours only the neutral parts.
	   Swapped with display so it follows the manual switch, which a
	   <picture media="(prefers-color-scheme: dark)"> could not do.
	   Whichever is display:none also leaves the accessibility tree, so both
	   carry the same alt text and exactly one is ever announced. */
	.header__logo .is-dark { display: none; }

	@media (prefers-color-scheme: dark) {
		:root:not([data-theme="light"]) .header__logo .is-light { display: none; }
		:root:not([data-theme="light"]) .header__logo .is-dark { display: block; }
	}

	:root[data-theme="dark"] .header__logo .is-light { display: none; }
	:root[data-theme="dark"] .header__logo .is-dark { display: block; }
	:root[data-theme="light"] .header__logo .is-light { display: block; }
	:root[data-theme="light"] .header__logo .is-dark { display: none; }

	/* --- navigation --- */

	.nav__list {
		display: flex;
		align-items: center;
		/* Wrapping matters only when JS is unavailable: the list then stays a
		   plain row and must not overflow a narrow screen. */
		flex-wrap: wrap;
		justify-content: flex-end;
		gap: clamp(0.2rem, -0.4rem + 1.6vw, 1rem);
		list-style: none;
		margin: 0;
		padding: 0;
	}

	.nav__link {
		position: relative;
		display: block;
		padding: 0.5rem 0.35rem;
		color: var(--ink);
		font-size: var(--step--1);
		text-decoration: none;
		white-space: nowrap;
		transition: color 0.2s var(--ease);
	}

	.nav__link::after {
		content: "";
		position: absolute;
		inset-block-end: 0.15rem;
		inset-inline: 0.35rem;
		block-size: 3px;
		border-radius: 2px;
		background-color: var(--accent);
		transform: scaleX(0);
		transform-origin: center;
		transition: transform 0.25s var(--ease);
	}

	.nav__link:hover,
	.nav__link[aria-current="page"] { color: var(--accent); }

	.nav__link:hover::after,
	.nav__link[aria-current="page"]::after { transform: scaleX(1); }

	.nav__toggle {
		display: none;
		align-items: center;
		gap: 0.5rem;
		min-inline-size: 2.75rem;
		min-block-size: 2.75rem;
		padding: 0.5rem 0.7rem;
		background-color: transparent;
		color: var(--ink);
		border: 1px solid var(--line-strong);
		border-radius: var(--radius);
		font-size: var(--step--1);
		cursor: pointer;
	}

	/* --- language switch --- */

	.langswitch {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		gap: 0.45rem;
		min-inline-size: 2.75rem;
		min-block-size: 2.75rem;
		padding-inline: 0.6rem;
		flex-shrink: 0;
		color: var(--ink);
		border: 1px solid var(--line-strong);
		border-radius: var(--radius);
		font-size: var(--step--1);
		text-decoration: none;
		white-space: nowrap;
		transition: background-color 0.2s var(--ease), color 0.2s var(--ease),
		            border-color 0.2s var(--ease);
	}

	.langswitch:hover {
		background-color: var(--bg-sunken);
		color: var(--accent);
		border-color: var(--accent);
	}

	/* Icon only on phones — logo, language, scheme and burger have to share
	   a 375px line. The accessible name comes from aria-label. */
	@media (max-width: 61.99rem) {
		.langswitch { padding-inline: 0; }
		.langswitch span { display: none; }
	}

	/* Auch eine Fussnote ist Fliesstext. Ohne Begrenzung lief der Hinweis
	   unter den Fahrzeugen ueber die vollen 1236 px, rund 160 Zeichen je
	   Zeile bei 15 px — das Auge findet den Zeilenanfang nicht mehr wieder.
	   Die Fussnote der Anfahrt ueberschreibt das weiter unten bewusst, weil
	   sie mit den Spalten darueber buendig stehen soll. */
	.note {
		font-size: var(--step--1);
		color: var(--ink-muted);
		max-inline-size: var(--measure);
	}

	/* --- colour scheme switch --- */

	.theme {
		display: none;
		place-items: center;
		inline-size: 2.75rem;
		block-size: 2.75rem;
		flex-shrink: 0;
		background-color: transparent;
		color: var(--ink);
		border: 1px solid var(--line-strong);
		border-radius: var(--radius);
		cursor: pointer;
		transition: background-color 0.2s var(--ease), color 0.2s var(--ease),
		            border-color 0.2s var(--ease);
	}

	.js .theme { display: inline-grid; }

	.theme:hover {
		background-color: var(--bg-sunken);
		color: var(--accent);
		border-color: var(--accent);
	}

	.theme .icon { inline-size: 1.3rem; block-size: 1.3rem; }

	/* --- mobile navigation panel --- */

	@media (max-width: 61.99rem) {
		.js .nav .nav__list {
			position: fixed;
			inset: 0 0 0 auto;
			z-index: 55;
			inline-size: min(22rem, 86vw);
			flex-direction: column;
			align-items: stretch;
			justify-content: center;
			gap: 0.25rem;
			padding: var(--space-lg) var(--space);
			background-color: var(--bg-raised);
			box-shadow: var(--shadow-lg);
			transform: translateX(101%);
			visibility: hidden;
			overflow-y: auto;
			overscroll-behavior: contain;
			transition: transform 0.35s var(--ease), visibility 0.35s;
		}

		.js .nav[data-open] .nav__list {
			transform: translateX(0);
			visibility: visible;
		}

		.js .nav .nav__link {
			padding: 0.85rem 1rem;
			font-size: var(--step-1);
			border-radius: var(--radius);
		}

		.js .nav .nav__link::after { display: none; }

		.js .nav .nav__link[aria-current="page"] {
			background-color: var(--tint);
		}

		.js .nav .nav__cta { margin-block-start: var(--space); }
		/* Zweiter Portal-Knopf schliesst dicht an den ersten an, statt noch
		   einmal den vollen Abstand zu nehmen. */
		.js .nav .nav__cta + .nav__cta { margin-block-start: 0.5rem; }

		.js .nav ~ .nav__toggle { display: inline-flex; }

		.nav__scrim {
			position: fixed;
			inset: 0;
			z-index: 54;
			background-color: rgba(0, 0, 0, 0.6);
			opacity: 0;
			visibility: hidden;
			transition: opacity 0.35s var(--ease), visibility 0.35s;
		}

		.nav[data-open] .nav__scrim { opacity: 1; visibility: visible; }

		.nav__toggle { position: relative; z-index: 56; }
		.theme { order: 1; }
	}

	/* Below ~400px the word costs more than it gives; the button keeps its
	   accessible name from aria-label. */
	@media (max-width: 25rem) {
		.nav__toggle__text {
			position: absolute;
			inline-size: 1px;
			block-size: 1px;
			overflow: hidden;
			clip-path: inset(50%);
			white-space: nowrap;
		}

		.nav__toggle { padding: 0.5rem; justify-content: center; }
	}

	@media (min-width: 62rem) {
		.nav__scrim { display: none; }
	}

	/* Schmales Desktopfenster: Logo, Links und die zwei Portal-Knoepfe teilen
	   sich eine Zeile. Ohne diese Straffung rutscht der zweite Knopf ab rund
	   1000 px in eine zweite Zeile und die Kopfleiste wird doppelt hoch. */
	@media (min-width: 62rem) and (max-width: 75rem) {
		.header__logo img { inline-size: 10rem; }
		.nav__link { padding-inline: 0.25rem; }
		.nav__cta .btn { padding-inline: 0.85rem; }
		.header__inner { gap: 0.5rem; }
	}

	/* --- buttons --- */

	.btn {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		gap: 0.6rem;
		min-block-size: 2.75rem;
		padding: 0.7rem 1.35rem;
		border: 1px solid transparent;
		border-radius: var(--radius);
		background-color: var(--red);
		color: #fff;
		font-size: var(--step--1);
		line-height: 1.3;
		text-align: center;
		text-decoration: none;
		cursor: pointer;
		box-shadow: var(--shadow-sm);
		transition: background-color 0.2s var(--ease), transform 0.2s var(--ease),
		            box-shadow 0.2s var(--ease);
	}

	.btn:hover {
		background-color: var(--red-bright);
		transform: translateY(-2px);
		box-shadow: var(--shadow-md);
	}

	.btn:active { transform: translateY(0); }

	.btn--ghost {
		background-color: transparent;
		color: var(--ink);
		border-color: var(--line-strong);
		box-shadow: none;
	}

	.btn--ghost:hover {
		background-color: var(--bg-sunken);
		border-color: var(--accent);
		color: var(--accent);
	}

	.btn--light {
		background-color: #fff;
		color: var(--blue-deep);
	}

	.btn--light:hover { background-color: #f1f5f8; }

	.btn:disabled {
		opacity: 0.55;
		cursor: not-allowed;
		transform: none;
		box-shadow: none;
	}

	.btn .icon { inline-size: 1.1em; block-size: 1.1em; }

	.btn-row {
		display: flex;
		flex-wrap: wrap;
		gap: 0.85rem;
	}

	.btn-row--center { justify-content: center; }

	/* Full-width buttons on small phones read better than a ragged wrap. */
	@media (max-width: 26rem) {
		.btn-row { flex-direction: column; align-items: stretch; }
		.btn-row .btn { inline-size: 100%; }
	}

	/* --- hero --- */

	.hero {
		position: relative;
		isolation: isolate;
		display: grid;
		align-items: end;
		min-block-size: clamp(20rem, 14rem + 30vw, 40rem);
		padding-block: var(--space-xl);
		color: #fff;
		overflow: hidden;
	}

	.hero__media {
		position: absolute;
		inset: 0;
		z-index: -2;
		inline-size: 100%;
		block-size: 100%;
		object-fit: cover;
	}

	/* On a phone the frame is taller than it is wide, so `cover` crops away
	   roughly two thirds of a 16:9 photograph — and a centred crop lands on
	   the canopy roof. The building, the forecourt and the painted sign are
	   all in the left half of von-oben-1040.jpg, which is the half worth
	   keeping. Shifting the crop left is what makes lifting the veil worth
	   anything on the screen most of this audience actually uses.

	   max-width, never the range syntax: Safari 16.4 is above the floor. */
	@media (max-width: 47.99rem) {
		.hero__media { object-position: 8% center; }
	}

	/* The veil, and why it is shaped the way it is.

	   It used to start at 0.55 across the WHOLE frame, which put the only
	   photograph of the building under 55-92 % blue: the forecourt, the cars
	   and, worst of all, the "Auto haus BERTRAM" sign painted on the wall all
	   disappeared into one flat blue-grey. That sign is the single most
	   Bertram thing on the site, and the page was covering it up.

	   Note where things actually are in von-oben-1040.jpg: the sign sits in
	   the UPPER LEFT (roughly 8-28 % across, 22-32 % down) and the forecourt
	   fills the bottom right. A left-to-right gradient — the obvious move when
	   the text is bottom-left — would therefore darken exactly the corner
	   worth showing. It has to stay vertical: clear at the top where the
	   building is, heavy at the bottom where the type sits.

	   The hero text is NOT covered by deploy/audit.py — it skips any element
	   with a picture in its paint stack rather than invent a number, which is
	   the right call but leaves this the one place on the site without a
	   safety net. So the bottom stop is deliberately generous. */
	.hero::after {
		content: "";
		position: absolute;
		inset: 0;
		z-index: -1;
		background: linear-gradient(
			177deg,
			rgba(5, 18, 26, 0.14) 0%,
			rgba(5, 18, 26, 0.22) 30%,
			rgba(29, 71, 90, 0.66) 55%,
			rgba(5, 18, 26, 0.93) 80%,
			rgba(5, 18, 26, 0.96) 100%
		);
	}

	.hero h1, .hero h2 { color: #fff; }

	/* The chip is used twice, over two very different grounds, and it needs a
	   different fill for each.

	   Here on /historie/ it sits on .hist-hero, a known dark fill: white on
	   10 % white over #1d475a composites to 7.55:1, which is legible and
	   stays legible because the ground cannot change. */
	.hero__eyebrow {
		display: inline-flex;
		align-items: center;
		gap: 0.55rem;
		padding: 0.4rem 0.9rem;
		border: 1px solid rgba(255, 255, 255, 0.25);
		border-radius: 999px;
		background-color: rgba(255, 255, 255, 0.1);
		font-size: var(--step--1);
		letter-spacing: 0.06em;
		text-transform: uppercase;
	}


	.hero__lead {
		max-inline-size: 46ch;
		font-size: var(--step-1);
		color: var(--on-dark);
	}

	/* --- cards --- */

	/* Sechs Karten, deshalb 1, 2 oder 3 Spalten — alle drei teilen sechs ohne
	   Rest. auto-fit landete im Grossbild auf vier und liess zwei Karten in
	   einer angebrochenen zweiten Reihe stehen. Gleiche Ueberlegung wie beim
	   Team-Raster; wird die Zahl der Karten geaendert, gehoert das hier mit
	   nachgezogen. */
	.grid {
		display: grid;
		gap: var(--space);
		grid-template-columns: 1fr;
	}

	@media (min-width: 38rem) {
		.grid { grid-template-columns: repeat(2, 1fr); }
	}

	@media (min-width: 58rem) {
		.grid { grid-template-columns: repeat(3, 1fr); }
	}

	.card {
		display: flex;
		flex-direction: column;
		gap: 0.6rem;
		padding: var(--space);
		background-color: var(--bg-raised);
		border: 1px solid var(--line);
		border-radius: var(--radius-lg);
		box-shadow: var(--shadow-sm);
		transition: transform 0.25s var(--ease), box-shadow 0.25s var(--ease),
		            border-color 0.25s var(--ease);
	}

	.card:hover {
		transform: translateY(-4px);
		border-color: var(--accent);
		box-shadow: var(--shadow-md);
	}

	.card h3 { font-size: var(--step-1); }
	.card p { font-size: var(--step--1); color: var(--ink-muted); margin: 0; }

	/* A solid disc, not a wash. The wash was rgba(163, 21, 33, 0.12): on white
	   that composites to #f6e8e9, which is 1.19:1 — six invisible squares. In
	   dark mode it was worse and muddier, because the LIGHT red was being
	   mixed over a dark card.

	   var(--red), never var(--accent): --accent flips to #ff6b78 in dark, and
	   that is a text tone for a dark ground. As a fill under white type it is
	   2.75:1 — a straight fail. As --red it is 7.82:1 in both schemes and
	   needs no second value. */
	.card__icon {
		display: grid;
		place-items: center;
		inline-size: 3rem;
		block-size: 3rem;
		border-radius: var(--radius);
		background-color: var(--red);
		color: #fff;
	}

	.card__icon .icon { inline-size: 1.6rem; block-size: 1.6rem; }

	/* --- facts strip --- */

	.facts {
		display: grid;
		gap: var(--space);
		grid-template-columns: repeat(2, 1fr);
		list-style: none;
		padding: 0;
	}

	@media (min-width: 52rem) {
		.facts { grid-template-columns: repeat(4, 1fr); }
	}

	/* The fact strip is a red band, and that is the whole point of it.

	   It used to be four boxes of --bg-raised on --bg. Those are the same
	   value in the light scheme (#ffffff both), so it was four white boxes on
	   white, held together by a border at 1.28:1 — a box you cannot see is not
	   a box. Meanwhile the only large red on the page was the footer, 4700 px
	   further down, where nobody is still looking.

	   These four lines — seit 1972, alle Marken, HU & AU, Sa 9-13 Uhr — are
	   also the four claims that belong to Bertram and to nobody else. Giving
	   them the surface puts the brand colour 700 px from the top instead of
	   4700, and it carries content rather than decoration.

	   A flat colour, never a gradient: audit.py abandons its contrast check as
	   soon as any ancestor has a background-image, so a gradient here would be
	   UNCHECKED rather than passed. --red and not --accent, because --accent
	   is #ff6b78 in dark mode and would be 2.75:1 under white type. */
	.factband {
		background-color: var(--red);
		color: #fff;
		padding-block: var(--space-lg);
	}

	.fact {
		display: flex;
		flex-direction: column;
		gap: 0.15rem;
	}

	.fact__num {
		font-family: var(--font-display);
		font-size: var(--step-3);
		line-height: 1.05;
		color: #fff;                    /* 7.82:1 on --red, large type */
	}

	.fact__label {
		font-size: var(--step--1);
		color: var(--on-dark);          /* 6.07:1 on --red */
	}

	/* --- team --- */

	/* Six people, so the column count is pinned to 1, 2 or 3 — all of which
	   divide evenly. auto-fit would land on 5 across on a wide screen and
	   leave a single orphan on the second row. */
	/* Flex, not grid, and that is a correction rather than a preference.

	   This was a grid pinned to 1 / 2 / 3 columns because six people divide
	   evenly by all three. The moment the team went from six to five, the
	   three-column row left two portraits stranded under the left edge with an
	   empty third column beside them — exactly the orphan problem the pinning
	   was meant to avoid, arriving from the other direction.

	   A staff list changes. Wrapping with justify-content: center centres
	   whatever is left over on the last row, so five, seven or four all look
	   deliberate, and nobody has to remember to retune a column count when
	   somebody joins or leaves. Full rows still reach both edges because the
	   basis is computed to fill them exactly. */
	.team {
		display: flex;
		flex-wrap: wrap;
		justify-content: center;
		gap: var(--space-lg) var(--space);
	}

	.team > * { flex: 0 1 100%; }

	@media (min-width: 34rem) {
		.team > * { flex: 0 1 calc((100% - var(--space)) / 2); }
	}

	@media (min-width: 58rem) {
		.team > * { flex: 0 1 calc((100% - 2 * var(--space)) / 3); }
	}

	.person {
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 0.3rem;
		text-align: center;
	}

	.person__photo {
		inline-size: min(11rem, 52vw);
		aspect-ratio: 1;
		border-radius: 50%;
		object-fit: cover;
		padding: 5px;
		border: 4px solid var(--line);
		background-color: var(--bg);
		transition: border-color 0.3s var(--ease), transform 0.3s var(--ease);
	}

	.person:hover .person__photo {
		border-color: var(--accent);
		transform: scale(1.04);
	}

	.person__name {
		margin-block-start: 0.6rem;
		font-family: var(--font-display);
		font-size: var(--step-1);
		color: var(--heading);
	}

	.person__role { font-size: var(--step--1); color: var(--ink-muted); }

	.person__contact {
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 0.15rem;
		margin-block-start: 0.4rem;
		font-size: var(--step--1);
	}

	/* WCAG 2.2 asks for a 24px minimum target; bare text links fall just
	   under it, so pad them out. */
	.person__contact .ilink {
		min-block-size: 1.75rem;
		padding-block: 0.25rem;
	}

	/* --- inline icon links --- */

	.ilink {
		display: inline-flex;
		align-items: center;
		gap: 0.45rem;
		text-decoration: none;
		color: inherit;
	}

	.ilink span { text-decoration: underline; text-underline-offset: 0.2em; }
	.ilink:hover span { color: var(--accent); }

	/* On the red footer and the blue band, --accent is the background colour
	   itself — hovering would paint the text invisible. Keep it white there
	   and give the feedback through the underline instead. */
	.footer .ilink:hover span,
	.band .ilink:hover span,
	.hero .ilink:hover span {
		color: #fff;
		text-decoration-thickness: 2px;
	}
	.ilink .icon { inline-size: 1.05em; block-size: 1.05em; color: var(--accent); }

	/* An inline <svg> with a viewBox but no width/height defaults to filling
	   its container — which is how the contact headings ended up with icons
	   the size of the column. Every icon gets a sane default here; the
	   context-specific rules still override it. */
	.icon {
		inline-size: 1.25em;
		block-size: 1.25em;
		flex-shrink: 0;
		fill: none;
		stroke: currentColor;
		stroke-width: 1.9;
	}

	/* --- feature list --- */

	.checklist {
		list-style: none;
		padding: 0;
		display: grid;
		gap: 0.55rem;
		grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr));
	}

	.checklist li {
		display: flex;
		align-items: flex-start;
		gap: 0.7rem;
		font-size: var(--step--1);
	}

	.checklist .icon {
		inline-size: 1.35em;
		block-size: 1.35em;
		margin-block-start: 0.15em;
		color: var(--accent);
	}

	/* --- timeline --- */

	.timeline {
		list-style: none;
		padding: 0;
		display: grid;
		gap: var(--space-lg);
		position: relative;
		max-inline-size: var(--measure);
	}

	.timeline::before {
		content: "";
		position: absolute;
		inset-block: 0.6rem 0.6rem;
		inset-inline-start: 0.55rem;
		inline-size: 2px;
		background-color: var(--accent);
	}

	.timeline li {
		position: relative;
		padding-inline-start: 2.6rem;
	}

	.timeline li::before {
		content: "";
		position: absolute;
		inset-block-start: 0.5rem;
		inset-inline-start: 0;
		inline-size: 1.15rem;
		block-size: 1.15rem;
		border-radius: 50%;
		background-color: var(--bg);
		border: 3px solid var(--accent);
	}

	.timeline__year {
		display: block;
		font-family: var(--font-display);
		font-size: var(--step-1);
		color: var(--heading);
	}

	.timeline p { margin-block-start: 0.25rem; }

	/* --- callout band --- */

	.band {
		background: linear-gradient(135deg, var(--blue-deep), var(--blue));
		color: #fff;
	}

	.band :is(h2, h3) { color: #fff; }
	.band p { color: var(--on-dark); }

	/* :not(.btn) matters — a bare `.band a` beats .btn--light on specificity
	   and would paint the white button's label white on white. */
	.band a:not(.btn) { color: #fff; }

	.band__inner {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		justify-content: space-between;
		gap: var(--space);
		padding-block: var(--space-lg);
	}

	.band__inner h2 { font-size: var(--step-2); }

	/* Die Textspalte ist eine Lesespalte. Ohne Begrenzung zog der laengere
	   Satz auf /kontakt/ den Block auf 1158 px auf, schob die Knoepfe in eine
	   eigene Zeile und liess dasselbe Band auf zwei Seiten verschieden
	   aussehen. */
	.band__inner > .stack { max-inline-size: var(--measure); }

	/* Icon sprite host — carries no layout of its own. */
	.sprite {
		position: absolute;
		inline-size: 0;
		block-size: 0;
		overflow: hidden;
	}

	/* --- figure --- */

	.shot {
		border-radius: var(--radius-lg);
		overflow: hidden;
		box-shadow: var(--shadow-lg);
		background-color: var(--bg-raised);
	}

	.shot img { inline-size: 100%; }

	.shot figcaption {
		padding: 0.7rem var(--space) 0.9rem;
		font-size: var(--step--1);
		color: var(--ink-muted);
	}

	/* --- vehicle slider --- */

	/* Native scroll-snap rather than a JS carousel: it scrolls with a finger,
	   a trackpad, a scrollbar and the keyboard before any script runs, and the
	   arrow buttons below are only a convenience on top. */
	.cars {
		display: grid;
		grid-auto-flow: column;
		grid-auto-columns: min(19rem, 78vw);
		gap: var(--space);
		overflow-x: auto;
		overscroll-behavior-x: contain;
		scroll-snap-type: x mandatory;
		scroll-padding-inline: var(--space);
		padding-block-end: 0.75rem;
		margin: 0;
		padding-inline: 0;
		list-style: none;
		scrollbar-width: thin;
	}

	.cars > li { scroll-snap-align: start; }

	/* --- Bestandsseite /fahrzeuge/ ----------------------------------------- */

	/* Erzeugt vom Importer aus der Vorlage in build.py; ein Artikel je
	   Fahrzeug, angesprungen ueber #fz-<id>. Die Galerie ist dieselbe
	   Scroll-Snap-Leiste wie der Slider, nur breiter -- ohne Skript
	   bedienbar, mit Finger, Trackpad und Tastatur. */
	.stock {
		padding-block: var(--space-lg);
		border-block-start: 1px solid var(--line);
		scroll-margin-block-start: 6rem;
	}

	.stock__head {
		display: flex;
		flex-wrap: wrap;
		align-items: baseline;
		gap: 0.2rem var(--space);
	}

	.stock__name { font-size: var(--step-2); flex: 1 1 100%; }
	.stock__variant { color: var(--ink-muted); flex: 1 1 auto; }

	.stock__price {
		font-family: var(--font-display);
		font-size: var(--step-2);
		color: var(--accent);
		line-height: 1.1;
	}

	.stock__price small {
		display: block;
		font-family: var(--font-body);
		font-size: var(--step--1);
		color: var(--ink-muted);
	}

	.stock__gallery {
		display: grid;
		grid-auto-flow: column;
		grid-auto-columns: min(34rem, 88vw);
		gap: var(--space);
		overflow-x: auto;
		overscroll-behavior-x: contain;
		scroll-snap-type: x mandatory;
		scroll-padding-inline: var(--space);
		padding-block: var(--space) 0.75rem;
		margin: 0;
		padding-inline: 0;
		list-style: none;
		scrollbar-width: thin;
	}

	.stock__gallery > li { scroll-snap-align: start; }

	.stock__gallery a {
		display: block;
		border-radius: var(--radius);
		overflow: hidden;
		background-color: var(--bg-sunken);
	}

	.stock__gallery img { inline-size: 100%; block-size: auto; }

	.stock__facts {
		display: grid;
		gap: 0.6rem var(--space);
		grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
		margin-block-start: var(--space);
	}

	.stock__facts dt { font-size: var(--step--1); color: var(--ink-muted); }
	.stock__facts dd { margin: 0; }

	.stock__ask {
		margin-block-start: var(--space);
		display: grid;
		gap: var(--space);
	}

	@media (min-width: 56rem) {
		.stock__ask { grid-template-columns: 1fr auto; align-items: center; }
	}

	/* --- "Ich bin ein Mensch" (Kontaktformular) ---------------------------- */

	/* Built by site.js from an empty [data-altcha] box; hidden without
	   scripting, where the noscript question takes over. Looks like a form
	   field on purpose -- it is one. */
	.human {
		display: grid;
		grid-template-columns: auto 1fr;
		align-items: center;
		gap: 0.3rem 0.7rem;
		padding: 0.8rem 1rem;
		border: 1px solid var(--line-strong);
		border-radius: var(--radius);
		background-color: var(--bg-raised);
	}

	.human input { inline-size: 1.4rem; block-size: 1.4rem; margin: 0; accent-color: var(--red); cursor: pointer; }
	.human label { cursor: pointer; }
	.human__state { grid-column: 2; font-size: var(--step--1); color: var(--ink-muted); min-block-size: 1.2em; }
	.human[data-state="done"] { border-color: #1c8a4a; }
	.human[data-state="done"] .human__state { color: #1c8a4a; }
	.human[data-state="fail"] { border-color: var(--red); }
	.human[data-state="fail"] .human__state { color: var(--accent); }

	/* --- Vollbild-Betrachter (Bestandsseite) ------------------------------ */

	/* Built by site.js on first use; absent without scripting, where the
	   thumbnails stay plain links to the large file. z-index above the header
	   (100) and the mobile menu (55). The ground is a flat colour, no blur:
	   a backdrop-filter here would make this the containing block of nothing
	   we care about, but the README's warning about filters near fixed
	   children is worth keeping to the letter.

	   The ground is OPAQUE, and that was learned the hard way: at 94 % alpha
	   the sticky header showed through almost undimmed — its ::before carries
	   backdrop-filter: blur(14px) saturate(1.4), and Chromium composites that
	   layer in a way that the overlay's 6 % transmission does not tame. Two
	   fixes, both kept: a flat opaque colour here, and the header hidden while
	   the viewer is open (html.lightbox-open below). A photo viewer wants a
	   black ground anyway; the page comes back untouched on close. */
	.lightbox {
		position: fixed;
		inset: 0;
		z-index: 200;
		display: grid;
		grid-template-rows: auto 1fr auto;
		grid-template-columns: 1fr;
		gap: var(--space);
		padding: var(--space);
		padding-block-end: max(var(--space), env(safe-area-inset-bottom));
		background-color: #05121a;
		color: #fff;
	}

	.lightbox-open .header { visibility: hidden; }

	.lightbox__figure {
		grid-row: 2;
		margin: 0;
		display: grid;
		grid-template-rows: minmax(0, 1fr) auto;
		place-items: center;
		gap: 0.6rem;
		min-block-size: 0;
	}

	/* Modell, Erstzulassung, Preis -- direkt unter dem Bild. */
	.lightbox__caption {
		font-size: var(--step-0);
		color: #fff;
		text-align: center;
	}

	.lightbox__img {
		max-inline-size: 100%;
		max-block-size: 100%;
		object-fit: contain;
		border-radius: var(--radius);
	}

	.lightbox__btn {
		position: absolute;
		display: grid;
		place-items: center;
		inline-size: 3rem;
		block-size: 3rem;
		border-radius: 50%;
		border: 1px solid rgba(255, 255, 255, 0.45);
		background-color: rgba(255, 255, 255, 0.12);
		color: #fff;
		font-size: 2rem;
		line-height: 1;
		cursor: pointer;
	}

	.lightbox__btn:hover { background-color: rgba(255, 255, 255, 0.24); }
	.lightbox :focus-visible { outline: 3px solid #fff; outline-offset: 3px; }

	.lightbox__close { inset-block-start: var(--space); inset-inline-end: var(--space); }
	.lightbox__prev  { inset-block-start: 50%; inset-inline-start: var(--space); transform: translateY(-50%); }
	.lightbox__next  { inset-block-start: 50%; inset-inline-end: var(--space); transform: translateY(-50%); }

	.lightbox__count {
		grid-row: 3;
		justify-self: center;
		font-size: var(--step--1);
		color: var(--on-dark);
	}

	/* On a phone the arrows would sit on the picture; swiping does the job
	   there, and the buttons move under it so they never cover a face. */
	@media (max-width: 47.99rem) {
		.lightbox__prev, .lightbox__next {
			position: static;
			transform: none;
			grid-row: 3;
		}
		.lightbox__prev { justify-self: start; }
		.lightbox__next { justify-self: end; }
		.lightbox { grid-template-columns: auto 1fr auto; }
		.lightbox__figure, .lightbox__close { grid-column: 1 / -1; }
		.lightbox__count { grid-column: 2; }
	}

	/* --- Bestands-Teaser neben dem Willkommen-Text ---------------------- */

	/* The vehicles used to start at 46 % scroll depth on a desktop and 44 % on
	   a phone — nearly three screen heights down. The obvious fix, hauling the
	   whole section upwards, turns the page's argument around: the copy is
	   deliberately aimed at the workshop, not at sales.

	   Measured instead: .prose is capped at --measure (62ch = 744 px) inside a
	   --page of 78rem, so on a 1280 px window there are 492 px of empty column
	   sitting beside the welcome paragraph for its whole height. That is where
	   this goes. It costs 39 px of page height at 1280 and nothing at all
	   between 1100 and 1200, because the column was already there.

	   (The margin outside .wrap was measured too and is not usable: 22 px at
	   1280 px, and a tile needs 326. That only becomes an option above about
	   1900 px, which is not the screen to design for.) */
	/* minmax(0, 1fr) already at the narrow end, not only in the two-column
	   layout below: a grid item's automatic minimum size is its min-content,
	   and the teaser contains a scroll-snap rail whose min-content is the sum
	   of all its tiles. Without this the single column grows to ~960 px inside
	   a 390 px viewport and the whole PAGE scrolls sideways — 570 px of it,
	   measured. The rail is supposed to scroll inside itself; this is what
	   makes it. */
	.welcome {
		display: grid;
		grid-template-columns: minmax(0, 1fr);
		gap: var(--space-lg);
	}

	.teaser { min-inline-size: 0; }

	@media (min-width: 62rem) {
		/* minmax(0, …) on the text column, not 1fr: a grid track's automatic
		   minimum is min-content, and the fact line inside a tile does not
		   wrap — without this the track refuses to shrink and the page gains
		   a horizontal scrollbar between 992 and 1440 px. That is exactly the
		   failure deploy/audit.py checks for at 1280, 768 and 390. */
		.welcome {
			grid-template-columns: minmax(0, 1fr) 21rem;
			gap: var(--space-lg);
			align-items: start;
		}

		.welcome .teaser { grid-column: 2; grid-row: 1; }
		.welcome .prose  { grid-column: 1; grid-row: 1; }
	}

	/* Between 62 and 75 rem the two columns are genuinely tight, so the tile
	   narrows and drops its photo rather than pushing the text into a gutter. */
	@media (min-width: 62rem) and (max-width: 74.99rem) {
		.welcome { grid-template-columns: minmax(0, 1fr) 16rem; gap: var(--space); }
	}

	.teaser__h {
		font-size: var(--step-1);
		color: var(--heading);
		margin-block-end: 0.6rem;
	}

	.teaser__all { margin-block-start: 0.6rem; font-size: var(--step--1); }

	/* On a desktop the teaser is a short vertical list — a rail that scrolls
	   sideways inside a 21rem column would be a puzzle, not a convenience.
	   Below 62 rem it keeps the inherited scroll-snap rail, so the phone gets
	   a swipe and the block costs 178 px instead of the 938 px the full
	   section costs down the page. */
	@media (min-width: 62rem) {
		.cars--teaser {
			grid-auto-flow: row;
			grid-auto-columns: auto;
			grid-template-columns: minmax(0, 1fr);
			gap: 0.6rem;
			overflow-x: visible;
			scroll-snap-type: none;
			padding-block-end: 0;
		}

		/* Two tiles beside a paragraph; the rest are in the section below,
		   which is what the link under them is for. */
		.cars--teaser > li:nth-child(n + 3) { display: none; }
	}

	@media (min-width: 62rem) and (max-width: 74.99rem) {
		.cars--teaser > li:nth-child(n + 2) { display: none; }
	}

	/* The tile: photo left, name and price right. No variant and no fact line
	   — at 98 px tall they would be clipped, and somebody scanning for a car
	   wants to know what it is and what it costs. */
	.cars--teaser .car {
		flex-direction: row;
		align-items: stretch;
		block-size: auto;
		border-radius: var(--radius);
	}

	.cars--teaser .car:hover { transform: none; box-shadow: var(--shadow-sm); }

	/* A fixed square, not aspect-ratio alone: the tile is a flex row with
	   align-items: stretch, and stretch overrides the automatic height of a
	   replaced element -- measured 96 x 110 px, a portrait crop of a
	   landscape photo. object-fit: cover keeps the pixels honest either way;
	   this keeps the frame honest too. */
	.cars--teaser .car__media {
		inline-size: 6rem;
		block-size: 6rem;
		flex: 0 0 6rem;
		align-self: center;
		aspect-ratio: auto;
	}

	.cars--teaser .car__body {
		gap: 0.15rem;
		padding: 0.6rem 0.8rem;
		/* Same reason as the grid track above: without this the flex item
		   refuses to shrink below its content and the tile overflows. */
		min-inline-size: 0;
	}

	.cars--teaser .car__name { font-size: var(--step-0); }
	.cars--teaser .car__price { font-size: var(--step-0); }

	.car {
		display: flex;
		flex-direction: column;
		block-size: 100%;
		background-color: var(--bg-raised);
		border: 1px solid var(--line);
		border-radius: var(--radius-lg);
		overflow: hidden;
		text-decoration: none;
		color: inherit;
		box-shadow: var(--shadow-sm);
		transition: transform 0.25s var(--ease), box-shadow 0.25s var(--ease),
		            border-color 0.25s var(--ease);
	}

	.car:hover {
		transform: translateY(-4px);
		border-color: var(--accent);
		box-shadow: var(--shadow-md);
	}

	.car__media {
		aspect-ratio: 4 / 3;
		inline-size: 100%;
		object-fit: cover;
		background-color: var(--bg-sunken);
	}

	.car__body {
		display: flex;
		flex-direction: column;
		gap: 0.3rem;
		padding: var(--space);
		flex: 1;
	}

	.car__name {
		font-family: var(--font-display);
		font-size: var(--step-1);
		color: var(--heading);
		line-height: 1.2;
	}

	.car__facts {
		font-size: var(--step--1);
		color: var(--ink-muted);
		margin: 0;
	}

	.car__price {
		margin-block-start: auto;
		padding-block-start: 0.5rem;
		font-family: var(--font-display);
		font-size: var(--step-2);
		color: var(--accent);
	}

	.car__price small {
		display: block;
		font-family: var(--font-body);
		font-size: var(--step--1);
		color: var(--ink-muted);
	}

	.cars__nav {
		display: flex;
		gap: 0.5rem;
		justify-content: flex-end;
		margin-block-start: var(--space);
	}

	.cars__btn {
		display: grid;
		place-items: center;
		inline-size: 2.75rem;
		block-size: 2.75rem;
		border: 1px solid var(--line-strong);
		border-radius: var(--radius);
		background-color: var(--bg-raised);
		color: var(--ink);
		cursor: pointer;
		transition: background-color 0.2s var(--ease), color 0.2s var(--ease),
		            border-color 0.2s var(--ease);
	}

	.cars__btn:hover:not(:disabled) {
		background-color: var(--red);
		border-color: var(--red);
		color: #fff;
	}

	.cars__btn:disabled { opacity: 0.4; cursor: default; }
	.cars__btn .icon { inline-size: 1.2rem; block-size: 1.2rem; }
	.cars__btn--prev .icon { transform: rotate(180deg); }

	/* --- contact form --- */

	.form {
		display: grid;
		gap: var(--space);
		max-inline-size: 42rem;
	}

	.field { display: grid; gap: 0.35rem; }

	.field label { font-size: var(--step--1); font-weight: 700; }

	.field :is(input, textarea) {
		padding: 0.7rem 0.9rem;
		min-block-size: 2.75rem;
		background-color: var(--bg-raised);
		color: var(--ink);
		border: 1px solid var(--line-strong);
		border-radius: var(--radius);
		font-size: var(--step--1);
		transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
	}

	.field :is(input, textarea):focus-visible {
		border-color: var(--heading);
		box-shadow: 0 0 0 3px rgba(27, 93, 122, 0.25);
		outline: none;
	}

	.field textarea { resize: vertical; min-block-size: 9rem; line-height: 1.5; }

	.radios {
		display: grid;
		gap: 0.5rem;
		padding: 0;
		border: 0;
		margin: 0;
		min-inline-size: 0;
	}

	.radios legend { font-size: var(--step--1); font-weight: 700; padding: 0; }

	.radios label {
		display: flex;
		align-items: center;
		gap: 0.6rem;
		padding: 0.6rem 0.9rem;
		min-block-size: 2.75rem;
		border: 1px solid var(--line-strong);
		border-radius: var(--radius);
		font-size: var(--step--1);
		cursor: pointer;
		transition: border-color 0.2s var(--ease), background-color 0.2s var(--ease);
	}

	.radios label:has(:checked) {
		border-color: var(--accent);
		background-color: var(--tint);
	}

	.radios input { accent-color: var(--red); inline-size: 1.1rem; block-size: 1.1rem; }

	/* Honeypot — out of sight for people, irresistible to naive bots.
	   Not display:none: some bots skip hidden fields, and this one is meant
	   to be filled in. */
	.trap {
		position: absolute;
		inline-size: 1px;
		block-size: 1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}

	.notice {
		display: flex;
		gap: 0.75rem;
		padding: var(--space);
		border-radius: var(--radius);
		border: 1px solid var(--line);
		background-color: var(--bg-sunken);
		font-size: var(--step--1);
	}

	.notice--ok {
		border-color: #1c8a4a;
		background-color: rgba(28, 138, 74, 0.1);
	}

	.notice--bad {
		border-color: var(--red);
		background-color: var(--tint);
	}

	[hidden] { display: none !important; }

	/* --- contact detail block --- */

	.details {
		display: grid;
		gap: var(--space);
		grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
		list-style: none;
		padding: 0;
	}

	/* Gemeinsame Breite fuer Einleitung, Spalten, Knopf und Fussnote der
	   Anfahrt, damit der Block links UND rechts buendig steht. Bewusst
	   breiter als --measure: die drei Spalten brauchen Luft und "Aus
	   Erbenheim und Wiesbaden" soll in eine Zeile passen. Die Einleitung
	   laeuft damit auf rund 100 Zeichen je Zeile — der Preis dafuer, dass
	   die rechte Kante nicht zufaellig wirkt. */
	.route-width { max-inline-size: 60rem; }

	/* Hier bewusst KEIN text-wrap: balance. Balance gleicht die Zeilen an,
	   indem es sie verkuerzt — der Satz blieb damit schmal, obwohl sein Kasten
	   die vollen 60rem hat. Gewuenscht ist das Gegenteil: die Zeilen sollen
	   die Breite der Spalten darunter ausnutzen. Es gilt also das voreinge-
	   stellte text-wrap: pretty, das nur Schusterjungen vermeidet. */

	.details--aligned { grid-template-columns: 1fr; }

	@media (min-width: 44rem) {
		.details--aligned { grid-template-columns: repeat(3, 1fr); }
	}

	.details :is(h2, h3) {
		display: flex;
		align-items: center;
		gap: 0.5rem;
		font-size: var(--step-1);
		margin-block-end: 0.3rem;
	}

	.details :is(h2, h3) .icon { color: var(--accent); }
	.details ul { list-style: none; padding: 0; }
	.details li { font-size: var(--step--1); }
	.details .ilink { min-block-size: 1.75rem; padding-block: 0.25rem; }

	/* --- historie --- */

	.hist-hero {
		position: relative;
		padding-block: var(--space-xl) var(--space-lg);
		background-color: var(--blue-deep);
		background-image: linear-gradient(160deg, var(--blue-deep), #0d1c26);
		color: #fff;
		overflow: hidden;
	}

	.hist-hero h1 { color: #fff; }
	.hist-hero .hero__lead { color: var(--on-dark); max-inline-size: 54ch; }

	.hist-hero__rule {
		position: absolute;
		inset-inline: 0;
		inset-block-end: 0;
		block-size: 0.5rem;
		color: rgba(255, 255, 255, 0.25);
	}

	.stop {
		padding-block: var(--space-xl);
		border-block-end: 1px solid var(--line);
	}

	.stop:nth-child(even) { background-color: var(--bg-sunken); }
	.stop--last { border-block-end: 0; }

	.stop__inner {
		display: grid;
		gap: var(--space-lg);
		align-items: center;
	}

	.stop__figure {
		margin: 0;
		border-radius: var(--radius-lg);
		overflow: hidden;
		box-shadow: var(--shadow-lg);
	}

	.stop__figure img { inline-size: 100%; }

	.stop__years {
		display: inline-block;
		padding: 0.25rem 0.75rem;
		border-radius: 999px;
		background-color: var(--tint);
		color: var(--accent);
		font-size: var(--step--1);
		letter-spacing: 0.04em;
	}

	.stop__text h2 { font-size: var(--step-3); margin-block-start: 0.4rem; }

	.stop__street {
		font-family: var(--font-display);
		font-size: var(--step-1);
		color: var(--ink-muted);
	}

	@media (min-width: 56rem) {
		.stop__inner { grid-template-columns: 1.1fr 0.9fr; }
		/* Alternate the photo left/right down the page. */
		.stop:nth-child(even) .stop__figure { order: 2; }

		/* The tilt is a desktop flourish; on a phone it only risks overflow. */
		.stop__figure {
			transform: rotate(-0.8deg);
			transition: transform 0.4s var(--ease);
		}

		.stop:nth-child(even) .stop__figure { transform: rotate(0.8deg); }
		.stop__figure:hover { transform: rotate(0deg) scale(1.015); }
	}

	/* --- footer --- */

	.footer {
		background-color: var(--red);
		color: #fff;
		padding-block: var(--space-lg);
	}

	.footer :is(h3, h4) { color: #fff; font-size: var(--step-1); }
	.footer a:not(.btn) { color: #fff; }

	.footer__cols {
		display: grid;
		gap: var(--space-lg) var(--space);
		grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
	}

	.footer ul { list-style: none; padding: 0; }
	.footer li { font-size: var(--step--1); }
	.footer .ilink { min-block-size: 1.75rem; padding-block: 0.25rem; }
	.footer .ilink .icon { color: #fff; }

	.subfooter {
		display: block;
		background-color: var(--sub);
		color: #fff;
		padding-block: 0.5rem;
	}

	.subfooter__inner {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		justify-content: center;
		gap: 0.25rem var(--space);
		font-size: var(--step--1);
	}

	.subfooter a {
		display: inline-flex;
		align-items: center;
		min-block-size: 2.25rem;
		padding-inline: 0.25rem;
		color: #fff;
		text-decoration: none;
	}

	.subfooter a:hover { text-decoration: underline; }

	@media (min-width: 48rem) {
		.subfooter__inner { justify-content: flex-end; }
	}

	/* --- back to top --- */

	.totop {
		position: fixed;
		inset-block-end: 1.1rem;
		inset-inline-end: 1.1rem;
		z-index: 40;
		display: grid;
		place-items: center;
		inline-size: 3rem;
		block-size: 3rem;
		border: 1px solid var(--line-strong);
		border-radius: 50%;
		background-color: var(--bg-raised);
		color: var(--ink);
		cursor: pointer;
		box-shadow: var(--shadow-md);
		opacity: 0;
		transform: translateY(140%);
		/* visibility, not just opacity: an invisible button that still takes
		   focus is a dead stop in the tab order. */
		visibility: hidden;
		transition: opacity 0.3s var(--ease), transform 0.3s var(--ease),
		            visibility 0.3s, background-color 0.2s var(--ease);
	}

	.totop[data-visible] { opacity: 1; transform: translateY(0); visibility: visible; }
	.totop:hover { background-color: var(--red); color: #fff; border-color: var(--red); }
	.totop .icon { inline-size: 1.35rem; block-size: 1.35rem; }

	/* Keep it clear of the home indicator on modern iPhones. */
	@supports (padding: max(0px)) {
		.totop { inset-block-end: max(1.1rem, env(safe-area-inset-bottom)); }
	}
}

/* -------------------------------------------------------------- utilities */

@layer utilities {
	.visually-hidden:not(:focus):not(:active) {
		position: absolute;
		inline-size: 1px;
		block-size: 1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}

	.center { text-align: center; }
	.muted { color: var(--ink-muted); }
	.lead { font-size: var(--step-1); }
	.mt { margin-block-start: var(--space); }
	.mt-lg { margin-block-start: var(--space-lg); }

	.split {
		display: grid;
		gap: var(--space-lg);
		align-items: center;
	}

	@media (min-width: 56rem) {
		.split { grid-template-columns: 1fr 1fr; }
		.split--wide-start { grid-template-columns: 1.15fr 0.85fr; }
	}

	/* .split centres its columns, which is right when both are about as tall.
	   Beside the timeline on /ueber-uns/ it is not: the column runs some
	   1600 px and the photograph about 530, so centring floats the picture in
	   the middle of a tall empty space. Align to the top and it reads as a
	   caption to the first years of the list. */
	.split--top { align-items: start; }

	/* Reveal-on-scroll, opt-in and motion-safe.
	   Scoped to .js — the class is set by site.js. Without it these elements
	   would start at opacity 0 and, with scripting blocked, never come back. */
	@media (prefers-reduced-motion: no-preference) {
		.js .reveal {
			opacity: 0;
			transform: translateY(1.5rem);
			transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
		}

		.js .reveal[data-shown] { opacity: 1; transform: translateY(0); }
	}

	@media (prefers-reduced-motion: reduce) {
		*, *::before, *::after {
			animation-duration: 0.01ms !important;
			animation-iteration-count: 1 !important;
			transition-duration: 0.01ms !important;
		}
	}
}

/* ------------------------------------------------------------------ print */

@media print {
	.header, .footer, .subfooter, .totop, .nav, .hero__media, .band { display: none; }
	body { color: #000; background: #fff; font-size: 11pt; }
	a::after { content: " (" attr(href) ")"; font-size: 9pt; }
}
