/*
Theme Name: Hami Transport by Uno
Theme URI: https://hami-transport.at/
Author: Kundenmagnet
Author URI: https://kundenmagnet.at/
Description: Onepager für Hami Transport (Umzug in Wien). 1:1 aus der Vercel-Fassung übernommen, Inhalte über das Plugin Uno pflegbar, Cookie-Banner über Uno Consent.
Version: 1.0.0
Requires at least: 6.4
Requires PHP: 7.4
Text Domain: hami-transport
License: Proprietär
*/

/* Uno: Formular-Rückmeldung und Uno-Hüllen, die das Layout nicht verschieben dürfen */
.form-note--ok { color: var(--ink); font-weight: 600; }
.form-note--error { color: var(--accent-dark); font-weight: 600; }
.admin-bar .site-header { top: 32px; }
@media (max-width: 782px) { .admin-bar .site-header { top: 46px; } }
@media (max-width: 600px) { .admin-bar .site-header { top: 0; } }
.ht-hp { position: absolute !important; left: -9999px !important; width: 1px; height: 1px; overflow: hidden; }
.site-footer .uno-consent-link { background: none; border: 0; padding: 0; margin: 0; font: inherit; color: var(--ground); text-decoration: underline; text-underline-offset: .2em; cursor: pointer; }
.site-footer .uno-consent-link:hover { color: color-mix(in oklab, var(--ground) 80%, var(--ink)); }

/* number-band justified: Pitch c „Vorher nachrechnen" — der Preis (100 € je Stunde) IST das Argument der Seite, keine Rhythmus-Dekoration */
/* labels justified: Aufmaßblatt-Register aus Pitch c — Mono-Marginalien als Formularsprache des Konzepts */
/* =========================================================================
   Hami Transport – styles.css
   PALETTE RATIONALE (2026-08-27): Hamis Beklebung ist Navy und Signalrot auf
   weißem Koffer, und der Betrieb hat als einziger der zehn analysierten Wiener
   Mover seinen Satz veröffentlicht. Die Seite nimmt deshalb die zwei GEMESSENEN
   Kundenwerte (Signalrot #EA1E00 als den einen Akzent, Petrol #002A34 als
   Tinte) und stellt sie auf einen warmen Sandgrund, der zu Karton und Verpackung
   gehört und im Wiener Umzugs-SERP (4 von 5 Wettbewerbern auf reinem Weiß, einer
   auf Neutralgrau) sofort fremd ist. Das Blau (#008EFF) und der orange CTA der
   Bestandsseite fallen weg: ein Akzent, sonst keiner. Tiefe entsteht aus einer
   zweiten Sandstufe plus Haarlinie, nie aus Schatten; Radius 0 durchgehend,
   Kanten wie an einer Zurrschiene.
   cream justified: Phase-2-Whitespace (a) – 4/5 Wettbewerber auf #FFFFFF, 1 auf
   Neutralgrau; Sand statt Creme, darauf zwei gemessene Kundenwerte, kein Terracotta.
   ^ Phase 5 rule: if you cannot write that sentence without naming
     Zweitgeist, the palette is wrong (references/brand.md → FIREWALL).

   Type: DISPLAY Anybody (wdth 125) / BODY Literata / MONO Azeret Mono – strategy C.
   Anybody trägt die wdth-Achse: H1 125 %, Sektionstitel 110 %, Navigation und
   Labels 100 % – ein Profil, auf Länge geschnitten. Unter 600 px läuft die H1
   auf wdth 100 (sonst vier Zeilen bei 375 px, gemessen in Phase 4b).
   ========================================================================= */

:root {
  /* from research/03-build-brief.md – locked, gemessen, keine Defaults */
  --ground: #F1E9DC;        /* der Seitengrund selbst – nie #fff */
  --ground-deep: #E3D7C3;   /* eine Stufe tiefer: Tafelrahmen, Register, Formularfelder – nie für Text */
  --ink: #002A34;           /* Text, Haarlinien, Konturen, das eine tiefe Band – nie #000 */
  --accent: #EA1E00;        /* NUR: CTA-Fläche (weiße Schrift ≥19 px/700) + 4-px-Linie unter der Tarifzahl */
  --accent-dark: #B21500;   /* Links im Fließtext, CTA-Hover – 5,8:1 auf Sand */
  --ink-soft: color-mix(in srgb, var(--ink) 72%, var(--ground));   /* abgeleitet (≈#435F63): Nebentext, Unterschriften – 4,8:1 auf Sand tief */
  --hair: color-mix(in oklab, var(--ink) 22%, var(--ground));      /* abgeleitet: Haarlinien */
  --frame-sand: #D9C2A7;    /* Unterlage der Scroll-Bühne, gemessen am Rand des gerenderten Frames – nie für Text/UI */
  --white: #FFFFFF;         /* nur im Fahrzeugbild, in den Belegtafeln und als CTA-Schrift */

  /* scaffold aliases (structure below reads these) */
  --surface: var(--ground-deep);
  --ink-mid: var(--ink-soft);
  --line: var(--hair);
  --focus: var(--ink);
  --accent-rgb: 234,30,0;

  --font-display: 'Anybody', 'Helvetica Neue', Arial, sans-serif;
  --font-body: 'Literata', Georgia, 'Times New Roman', serif;
  --font-mono: 'Azeret Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;

  --container: 1180px;
  --gutter: clamp(1.25rem, 4vw, 3.5rem);
  --header-h: 64px;
  --radius: 0;
  --radius-sm: 0;
  --ease: cubic-bezier(.22,.61,.36,1);

  /* spacing scale – 4/8 base */
  --s-1: .25rem; --s-2: .5rem; --s-3: .75rem; --s-4: 1rem; --s-6: 1.5rem;
  --s-8: 2rem; --s-12: 3rem; --s-16: 4rem; --s-20: 5rem; --s-24: 6rem;
}

/* ---------- reset / base ---------- */
*, *::before, *::after { box-sizing: border-box; }
/* Item 10 (repair pass): background-color on <html> so the ground paints before the fonts/CSS
   finish loading – no white first frame. Google Fonts link already carries display=swap and every
   font-family list already ends in a matched system fallback, so no separate FOUT fix is needed. */
html { -webkit-text-size-adjust: 100%; scroll-padding-top: var(--header-h); background-color: var(--ground); }
/* Motion fix (client-design.md defect log): a blanket `scroll-behavior: smooth` on <html>
   does not distinguish user-clicked anchor links from programmatic/history scrolls – it
   also animates browser back/forward scroll restoration and any script's window.scrollTo(),
   turning what should be an instant jump into a multi-hundred-ms glide. Two symptoms traced
   to this: (1) the truck hero's canvas frame is a pure function of live scroll position, so a
   "jump to top" that is secretly smooth-scrolling shows the truck mid-scrub for the length of
   the glide instead of its closed default; (2) repeated close-together programmatic scrollTo()
   calls (e.g. a scripted scroll-through) each retarget the still-animating glide from the
   previous call, producing a scroll trajectory that never matches the intended positions and
   can paint a reveal element mid-transform in the wrong place for a frame. Fix: keep the
   smooth GLIDE only for user-initiated in-page anchor clicks (wired in main.js via
   scrollIntoView), leave every other scroll (load, back/forward, script, wheel, keyboard)
   at the CSS default of instant – scroll-padding-top above still keeps the sticky header from
   covering the target either way. */

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: clamp(1.0625rem, 1.02rem + .2vw, 1.125rem);
  line-height: 1.62;
  font-optical-sizing: auto;
  color: var(--ink);
  background: var(--ground);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

img { max-width: 100%; height: auto; }
.site-header img, #main img, .site-footer img { display: block; }
a { color: var(--accent-dark); text-decoration-thickness: 1px; text-underline-offset: 3px; }
p { margin: 0 0 1em; }
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.tnum, [data-count-to] { font-variant-numeric: tabular-nums; }
.container { width: 100%; max-width: var(--container); margin-inline: auto; padding-inline: var(--gutter); }
:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }

.skip-link {
  position: absolute; left: 8px; top: -60px; z-index: 300;
  background: var(--ink); color: var(--ground); padding: .6rem 1rem;
  font-family: var(--font-display); text-decoration: none;
}
.skip-link:focus { top: 8px; }

/* ---------- type roles ---------- */
h1, h2, h3 { font-family: var(--font-display); margin: 0; text-wrap: balance; }
.h-section {
  font-weight: 800; font-stretch: 110%; letter-spacing: -.025em; line-height: 1.02;
  font-size: clamp(2rem, 1.25rem + 2.6vw, 3.6rem);
  margin: 0 0 .5em;
}
.h-sub { font-weight: 700; font-stretch: 100%; font-size: 1.2rem; letter-spacing: -.01em; line-height: 1.2; margin: 0 0 .45em; }
.lead { font-size: clamp(1.125rem, 1.05rem + .35vw, 1.3rem); line-height: 1.55; max-width: 58ch; margin: 0 0 1.2em; }
.lead:last-child { margin-bottom: 0; }
.soft { color: var(--ink-soft); }
.small { font-size: .94rem; line-height: 1.55; }
/* Item 11c (impeccable-audit P3, fix round 1 pass 2): .label is removed here – no longer
   referenced in index.html since the "Wer kommt" eyebrow it once styled was dropped from the
   editorial spread entirely (live round 3). Dead rule, not a hidden one. */
/* Fix round 2 pass 2 (Item 11/27): .dim's only use is the "Wien." word inside the H1, which
   sits over the scroll-stage's frame-sand scrim (#D9C2A7), not the page's sand ground
   (#F1E9DC) that --ink-soft was measured against (5,6:1 there). --ink-soft on frame-sand
   measures ~4:1 – borderline even for large text. DESIGN.md's own pairs table already
   verifies full --ink on frame-sand at 8,9:1; this derives a mid-point (85% ink) from the
   same two locked tokens ink-soft already uses (no new colour) – ~5,9:1 on frame-sand, dark
   enough to read as deliberate emphasis rather than a washed-out rendering glitch, while
   staying visibly lighter than the solid-ink words around it. */
.dim { color: color-mix(in srgb, var(--ink) 85%, var(--ground)); }
/* Item 15 (repair pass) styled "Wer kommt" as a quiet Literata lead-in instead of a mono chip.
   Live round 3 removes the line itself – the sentence it introduced (the one describing the crew
   plate) had already gone in round 2, leaving a question mark over a quote about trust. With its
   only user gone, .section-lead-in goes too. The pull-quote opens the spread on its own. */

/* ---------- buttons – Radius 0, zwei Gewichte, ein Akzent ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .6em;
  font-family: var(--font-display); font-stretch: 100%; font-weight: 700; font-size: 19px;
  line-height: 1; cursor: pointer; text-decoration: none; white-space: nowrap;
  padding: 15px 24px; border: 2px solid var(--accent);
  background: var(--accent); color: var(--white);
  transition: background .14s ease, border-color .14s ease, color .14s ease;
}
.btn:hover { background: var(--accent-dark); border-color: var(--accent-dark); }
.btn-ghost { background: transparent; color: var(--ink); border-color: var(--ink); }
.btn-ghost:hover { background: var(--ink); color: var(--ground); border-color: var(--ink); }
.btn-ink { background: var(--ink); color: var(--ground); border-color: var(--ink); }
.btn-ink:hover { background: var(--ground); color: var(--ink); }
.btn-sm { font-size: 16px; padding: 11px 16px; }
/* Item 6 (repair pass): the header CTA is .btn-ink (ground-on-ink, 12.6:1) and stays that way, but
   .btn-sm itself is a reusable size modifier – combined with the base .btn it would inherit white
   text on the accent red at 14-16 px/bold, which measures 4.48:1 (the Large-Text-AA pair the price
   band's 19 px CTA is allowed to use) and fails full AA below 18.66 px. Without touching the accent
   token, any small button that is NOT .btn-ink uses accent-dark instead (7.0:1, DESIGN.md pairs) –
   guards the component regardless of which anchor combines with it, not just the current header. */
.btn-sm:not(.btn-ink) { background: var(--accent-dark); border-color: var(--accent-dark); }
.btn-sm:not(.btn-ink):hover { background: var(--ink); border-color: var(--ink); color: var(--ground); }
.btn-block { display: flex; width: 100%; }

/* ---------- header – Kopfleiste wie die Titelzeile eines Aufmaßblatts ---------- */
.site-header {
  position: sticky; top: 0; z-index: 100;
  background: var(--ground);
  border-bottom: 2px solid var(--ink);
}
.header-inner { display: flex; align-items: center; gap: clamp(.75rem, 2vw, 1.5rem); min-height: var(--header-h); }
/* Item 3 (repair pass): at ~700–860 px the flex row (logo + full nav + full tel number + button)
   needed more width than the viewport gave it; with no min-width protection the browser shrank the
   logo – the one element with no rigid/nowrap content – all the way to 0 rather than the nav or
   button. flex-shrink:0 makes the mark load-bearing: it must show at every width. The tel number
   (below) gives way instead, the same way it already does under 520 px. */
.wordmark { display: inline-flex; align-items: center; text-decoration: none; color: var(--ink); flex-shrink: 0; }
.wordmark img { height: 44px; width: auto; flex-shrink: 0; }
.main-nav { display: none; gap: clamp(.9rem, 1.8vw, 1.6rem); margin-left: auto; }
/* Item 15 (repair pass): nav links were set in Anybody (the display face) at every weight/size step
   below the H1 – one more small-UI-chrome spot reaching for the display face by default. Small UI
   chrome (nav, form labels, footer) reads in Literata instead; Anybody stays for the CTAs it still
   owns (H1/section titles/buttons/the plate's tabular figures). */
.main-nav a {
  font-family: var(--font-body); font-weight: 600; font-size: .98rem;
  color: var(--ink); text-decoration: none; padding: .3rem 0; border-bottom: 2px solid transparent;
  transition: border-color .15s ease;
}
.main-nav a:hover { border-bottom-color: var(--accent); }
.header-right { display: flex; align-items: center; gap: .9rem; margin-left: auto; }
.tel-chip {
  display: inline-flex; align-items: center; gap: .4em;
  font-family: var(--font-mono); font-size: .86rem; font-weight: 600; color: var(--ink);
  text-decoration: none; white-space: nowrap; font-variant-numeric: tabular-nums;
}
.tel-chip .tel-label { display: none; }
.tel-icon { display: none; flex: 0 0 auto; }
/* Item 22 (Kritiker 3): bei 860 px blieb ~570 px leerer Kopfleisten-Platz zwischen Logo und
   Telefonnummer ungenutzt, während die Textnavigation schon hinter dem Hamburger verschwand. Die
   Schwelle sinkt auf 700 px, denselben Umbruchpunkt, den die Bühne (scroll-scrub) ohnehin für
   „schmal" verwendet – der Hamburger bleibt echten Telefonbreiten vorbehalten. */
@media (min-width: 700px) {
  .main-nav { display: flex; }
  .header-right { margin-left: 0; }
}
/* Item 8 (impeccable-audit P1): unter 520 px blieb nur das 18×18-px SVG-Symbol klickbar, ohne
   Polsterung – unter der Mindest-Tapfläche. Fix durch Konstruktion: Polsterung plus gleich großer
   negativer Rand vergrößert die Trefferfläche auf ~44×44 px, ohne die sichtbare Chip-Größe oder
   den Kopfleisten-Rhythmus zu verändern. */
@media (max-width: 520px) {
  .tel-num { display: none; }
  .tel-icon { display: block; }
  .tel-chip { padding: 13px; margin: -13px; justify-content: center; }
  .wordmark img { height: 36px; }
}
/* Item 3 (repair pass): 700–899 px shows the full main-nav (rule above) plus the full tel number
   plus the header CTA plus the logo – measured, that content needs up to ~150 px more than the
   viewport gives it. Below 700 the hamburger already removes the nav, so there is room for the full
   number; from 900 the row itself has room. Only this middle band needs the phone number to fall
   back to icon-only (as it already does under 520 px) and the row's own gaps to tighten slightly –
   the logo (fixed above) and the nav links never lose space. */
@media (min-width: 700px) and (max-width: 899px) {
  .tel-num { display: none; }
  .tel-icon { display: block; }
  .tel-chip { padding: 13px; margin: -13px; justify-content: center; }
  .header-inner { gap: .5rem; }
  .main-nav { gap: .7rem; }
}
@media (max-width: 400px) {
  .header-right { gap: .55rem; }
  .btn-sm { padding: 10px 12px; font-size: 14px; }
}

/* ---------- mobile nav toggle – <details>/<summary>, funktioniert auch ohne JS ---------- */
.nav-toggle-wrap { display: none; position: static; }
@media (max-width: 699px) { .nav-toggle-wrap { display: block; } }
/* Item 11a (impeccable-audit P2, fix round 1 pass 2): 34x34 cleared the 24x24 WCAG 2.2 AA floor
   but sat under the 44x44 touch-target guideline already applied to .tel-chip's hit area. Same
   fix here: the box itself grows to 44x44 (bars stay the same visual weight, just re-centred). */
.nav-toggle {
  display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 4px;
  width: 44px; height: 44px; flex: 0 0 auto; cursor: pointer; list-style: none;
}
.nav-toggle::-webkit-details-marker { display: none; }
.nav-toggle-bar { display: block; width: 19px; height: 2px; background: var(--ink); transition: transform .15s ease, opacity .15s ease; }
.nav-toggle-wrap[open] .nav-toggle-bar:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav-toggle-wrap[open] .nav-toggle-bar:nth-child(2) { opacity: 0; }
.nav-toggle-wrap[open] .nav-toggle-bar:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }
.mobile-nav {
  position: absolute; left: 0; right: 0; top: 100%; z-index: 90;
  display: flex; flex-direction: column;
  background: var(--ground); border-bottom: 2px solid var(--ink);
  padding: .25rem var(--gutter) 1rem;
}
.mobile-nav a {
  font-family: var(--font-body); font-weight: 600; font-size: 1.1rem;
  color: var(--ink); text-decoration: none; padding: .8em 0; border-top: 1px solid var(--hair);
}
.mobile-nav a:first-child { border-top: 0; }

/* ---------- HERO – Scroll-Bühne, Treatment takeover ----------
   Ein 360vh-Track scrollt vorbei, .scrub-stage-pin bleibt unter der Kopfleiste
   stehen, die Canvas scrubbt die Elevation 0→1: der Wagen geht auf. Kein
   Scroll-Lock. Die .scrub-fallback trägt als src das STARTFRAME (Poster; Erstbild ist nie das Endframe); reduced-motion tauscht per data-endframe auf das aufgelöste Endframe. Sie ist, was no-JS,
   reduced-motion und langsame Verbindungen sehen; die Canvas blendet darüber. */
@property --scrub-progress { syntax: "<number>"; inherits: true; initial-value: 0; }

.scrub-stage { position: relative; height: 360vh; background: var(--frame-sand); }
.scrub-stage-pin {
  position: sticky; top: var(--header-h); height: calc(100svh - var(--header-h)); width: 100%;
  overflow: hidden; background: var(--frame-sand);
}
.scrub-fallback, .scrub-canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
/* Item 14 (Kritiker 1, Tells 27/38, fix round 1 pass 2): the render's seamless studio gradient and
   catalog lighting sat next to documentary real photos with no bridge. A slight contrast/saturation
   pull edges the clean CGI tonality toward the real photos' register – no new colour, same image. */
.scrub-fallback, .scrub-canvas { filter: contrast(1.04) saturate(.92); }
.scrub-fallback { object-fit: cover; object-position: center top; }
.scrub-canvas { opacity: 0; transition: opacity .45s var(--ease); }
.scrub-canvas.scrub-ready { opacity: 1; }
/* A low-opacity SVG-noise overlay (feTurbulence, generated – no new asset, no new colour) blended
   over the image only, never over the scrim/overlay text above it (z-index 1, below the scrim's 2). */
.scrub-grain {
  position: absolute; inset: 0; z-index: 1; pointer-events: none; opacity: .05; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 140px 140px;
}
/* Hochformat / schmal: die 16:9-Elevation wird eingepasst statt beschnitten (scroll-scrub.js
   liest data-fit-narrow), der Beschnitt würde sonst nur das mittlere Drittel des Wagens zeigen. */
@media (max-width: 700px), (max-aspect-ratio: 1/1) {
  .scrub-fallback { object-fit: contain; object-position: center; }
  .scrub-fallback, .scrub-canvas {
    -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 9%, #000 91%, transparent 100%);
            mask-image: linear-gradient(180deg, transparent 0, #000 9%, #000 91%, transparent 100%);
  }
  /* Defekt (Runde 1): in "contain" füllt der 16:9-Wagen nur die Breite, nicht die Höhe – bei
     voller 100svh-Bühnenhöhe blieb er auf schmalen/hohen Screens ~120 px hoch in ~900 px Sand.
     Die Bühne bekommt deshalb nur so viel Höhe, wie H1-Block + Wagen + Caption/CTA tatsächlich
     brauchen (aus den Schriftgrößen der 375-px-Spalte gerechnet), gedeckelt auf die Viewporthöhe. */
  .scrub-stage-pin { height: min(calc(100svh - var(--header-h)), calc(56.25vw + 26rem)); }
}

/* gradients justified: zwei Lesbarkeits-Schutzflächen über der ruhigen Sandzone der Bühne (scroll-canvas scrim) –
   oben für H1/Stützsatz, unten für Caption/CTA/Anruf-Link. impeccable-audit P1 (Item 9): vorher schützte nur
   die obere Fläche die H1-Zone, der untere Rand verließ sich darauf, dass der jeweilige Frame dort hell bleibt.
   Fix durch Konstruktion statt Verlass auf Bildinhalt: eine zweite, gespiegelte Wäsche am unteren Rand erzwingt
   den Kontrast-Sockel unabhängig vom Frame darunter (Reifen, Schatten, Ladefläche). */
/* impeccable-audit P1 (Item 9), zweiter Durchgang: eine reine Prozent-Ausblendung ab 0 % Deckkraft
   ist keine Kontrast-UNTERGRENZE, nur ein Trend – an einem dunklen Bilddetail (Gurt, Rad, Schatten)
   genau an der Stelle bleibt zu wenig Deckkraft übrig, wie der 08:47-Nachweis am Cargo-Gurt zeigte.
   Fix durch Konstruktion: eine GEHALTENE Deckfläche über die dokumentierte Ruhezone (H1-Block ≤ 27 %
   Bühnenhöhe aus dem Brief; Caption/CTA-Zone unten), erst danach Ausblendung – der Text steht damit
   auf einer garantiert hellen Fläche, unabhängig vom Frame-Inhalt darunter. */
.scrub-scrim {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background:
    linear-gradient(180deg, rgba(217,194,167,.85) 0%, rgba(217,194,167,.85) 24%, rgba(217,194,167,0) 40%),
    linear-gradient(0deg, rgba(217,194,167,.82) 0%, rgba(217,194,167,.82) 18%, rgba(217,194,167,0) 32%);
}
/* Item 16 (repair pass): a hairline plate frame + one corner label + one dimension hairline, so the
   render reads as a drawing in a survey document rather than a floating product shot. The dimension
   line sits at 15% from the bottom – inside the scrim's held-opaque zone (0–18% from bottom, rule
   above), so it always has contrast, and roughly where the van's wheels meet the ground (same camera,
   same silhouette on every frame, per the brief). The label sits bottom-centre, the one calm spot the
   caption (bottom-left) and CTA group (bottom-right) both leave empty. Both hide below 701 px / in
   "contain" mode – same threshold .scrub-notes already uses – where the crop leaves no room and the
   375/768 layouts are otherwise untouched. */
.scrub-plate-frame {
  position: absolute; inset: 10px; z-index: 3; pointer-events: none;
  border: 1px solid color-mix(in oklab, var(--ink) 30%, transparent);
}
.scrub-dim-line {
  position: absolute; left: var(--gutter); right: var(--gutter); bottom: 15%; height: 1px;
  z-index: 3; pointer-events: none; background: color-mix(in oklab, var(--ink) 35%, transparent);
}
.scrub-dim-line::before, .scrub-dim-line::after {
  content: ""; position: absolute; top: -4px; width: 1px; height: 9px;
  background: color-mix(in oklab, var(--ink) 35%, transparent);
}
.scrub-dim-line::before { left: 0; }
.scrub-dim-line::after { right: 0; }
/* Item 20 (Kritiker 2): one size step up (.72rem → .8rem) – already full-ink/600-weight (8.9:1
   per DESIGN.md), the fine print just read small against a busy frame. */
.scrub-plate-label {
  position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%); z-index: 3;
  font-size: .8rem; font-weight: 600; letter-spacing: .06em; color: var(--ink); white-space: nowrap;
}
/* Fix round 2 pass 2 (Item 1/14): the plate frame is a hairline drawn 10px inset from the
   STAGE edges, tuned for "cover" mode where the image fills every pixel of that stage – the
   line then sits directly on continuous photo tone and reads as a deliberate survey-plate
   border. In "contain" mode (this query) the fallback/canvas are ALSO mask-image-faded top
   and bottom, but the frame itself is a separate sibling layer that is never masked – it kept
   drawing a full-opacity rectangle straight across the fading zone, showing as a boxed-photo
   seam (hard vertical lines + a tonal break at the top) instead of a bleed. Same reasoning
   that already hides the dimension line/label here (the crop no longer matches a fixed
   inset), extended to the frame itself: below the "contain" threshold there is no frame. */
@media (max-width: 700px), (max-aspect-ratio: 1/1) { .scrub-dim-line, .scrub-plate-label, .scrub-plate-frame { display: none; } }
.scrub-overlay { position: absolute; inset: 0; z-index: 3; pointer-events: none; }
.scrub-overlay .container { height: 100%; position: relative; }
.hero-top { position: absolute; left: var(--gutter); right: var(--gutter); top: clamp(.9rem, 2.2vh, 1.4rem); pointer-events: auto; }
.hero-h1 {
  font-weight: 800; font-stretch: 125%; letter-spacing: -.03em; line-height: .95;
  font-size: min(clamp(2.5rem, 1.1rem + 5.4vw, 5.4rem), 8.8svh);
  margin: 0 0 .28em;
}
/* Item 14 (repair pass): the H1 used to split into two whole-line colours (ink / ink-soft), reading
   as a navy line over a sage line – a template two-tone headline. It is one ink now; .dim tints only
   the single word "Wien." inline, no forced line break. */
@media (max-width: 599px) { .hero-h1 { font-stretch: 100%; letter-spacing: -.02em; } }
.hero-subline {
  font-size: clamp(1rem, .95rem + .3vw, 1.15rem); line-height: 1.45; max-width: 64ch; margin: .5em 0 0;
}
/* Runde 2 Defekt (1, 2, 3, 5, 6, 10, 15, 16, 20, 25, 31, 39): der Stützsatz stand bei breiten
   Querformaten absolut rechts unten und legte sich über den Reifen-/Schattenbereich des Wagens
   (dunkle Tinte auf dunklem Bildgrund, kaum lesbar) – identisch in der geschlossenen ("Zu.") und
   der aufgeschnittenen ("Offen.") Aufnahme, weil es dieselbe fest stehende Overlay-Ebene ist, nur
   mit wechselndem Frame darunter. Fix durch Umbau statt Patch: der Satz bleibt in JEDER Breite im
   normalen Fluss direkt unter der H1 in der ruhigen Sandzone oben links – exakt wie bei 375/768.
   Auf sehr flachen Bühnen (<860 px Höhe) entfällt er weiterhin (H1-Zone hält die 27%-Grenze aus
   dem Brief), der Satz steht wortgleich im Preisband darunter. */
@media (min-width: 701px) and (max-height: 859px) { .hero-subline { display: none; } }
/* Tarifzeile (Live-Runde 27.08.): die zwei Zahlen als Display-Ziffern in der H1-Familie, Etiketten in Literata,
   rote 4-px-Linie = die eine erlaubte Akzentlinie unter Tarifzahlen (DESIGN.md). Steht unter dem Titel, nicht auf dem Dach. */
.hero-tarif { display: flex; flex-wrap: wrap; align-items: baseline; gap: .35em 1.6rem; margin: 1.1rem 0 2.6rem; max-width: none; font-size: 1.05rem; line-height: 1.3; color: var(--ink); }
.hero-tarif .tarif { display: inline-flex; align-items: baseline; gap: .45em; padding-bottom: .3rem; border-bottom: 4px solid var(--accent); }
/* Kundenrunde 02.09.: mit 95 € passte die Tarifzahl bei 375 px noch neben das Literata-Etikett;
   die dreistellige 100 € brach zwischen Ziffer und Zeichen um. nowrap wie bei .fig b im
   Preisband – die Zahl ist eine Einheit, das Etikett daneben darf umbrechen. */
.hero-tarif .tarif b { font-family: var(--font-display); font-variation-settings: 'wdth' 125; font-weight: 800; font-size: 1.9rem; line-height: 1; letter-spacing: -.01em; font-variant-numeric: tabular-nums; white-space: nowrap; }
.hero-tarif .tarif small { font-family: var(--font-body); font-size: 1rem; color: var(--ink); }
@media (max-width: 600px) { .hero-tarif { gap: .5rem 1.2rem; margin-bottom: 1.4rem; } .hero-tarif .tarif b { font-size: 1.5rem; } }
.hero-bottom {
  position: absolute; left: var(--gutter); right: var(--gutter); bottom: clamp(.9rem, 2.4vh, 1.6rem);
  display: flex; align-items: flex-end; justify-content: space-between; gap: 1.5rem; pointer-events: auto;
}
.hero-cta-row { display: flex; flex-wrap: wrap; gap: .75rem; justify-content: flex-end; }
.hero-cta-group { display: flex; flex-direction: column; align-items: flex-end; gap: .85rem; }
/* Item 4/17 (Runde 2): auf 375 standen drei gleichgewichtige Elemente (Preis ansehen / Anfrage
   senden / Oder anrufen) dicht gedrängt. Der Anruf-Link ist die dritte, leiseste Option, nicht ein
   drittes CTA – er verliert deshalb Gewicht (400 statt 600, ink-soft statt ink, keine permanente
   Unterstreichung) und bekommt mehr Abstand zur Button-Zeile, damit "Anfrage senden" die einzige
   klare Primärhandlung bleibt. */
.hero-call {
  font-family: var(--font-mono); font-size: .8rem; font-weight: 400; line-height: 1.3; color: var(--ink-soft);
  text-decoration: none;
}
.hero-call:hover { color: var(--ink); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }

/* Item 13/18/31/34 (fix round 1 pass 2): das gezeichnete Rolltor-Glyph (Rahmen + Lamellen) neben
   "Zu."/"Offen." las sich als leere, nicht klickbare Checkbox direkt neben zwei echten Buttons.
   Entfernt statt umgestylt – die Tür-Mechanik bleibt im Wagenbild selbst, das Wort trägt den Beat
   allein weiter. */

/* Annotation-Beats – drei belegte Zahlen, nacheinander an derselben ruhigen Stelle oben rechts
   (die Sandfläche über dem Koffer). Ohne Engine (no-JS, reduced-motion) sind sie ausgeblendet:
   dieselben Zahlen stehen direkt darunter im Preisband, nichts geht verloren. */
.scrub-notes { position: absolute; inset: 0; z-index: 3; pointer-events: none; display: none; }
.scrub-js .scrub-notes { display: block; }
/* Fix (motion review, frame_016 of the reverse sweep): leaving-view used "ease-out" – a decelerate
   curve, correct for something arriving, wrong for something leaving. Decelerating INTO opacity 0
   means the last stretch of the fade crawls through low-but-nonzero values, so a note caught
   mid-exit (e.g. scrolling back up out of its .is-on band) sits at a faint, blurry partial-opacity
   smear for longer than the .18s duration suggests. "ease-in" accelerates toward 0 instead – it
   holds near full opacity briefly, then clears the near-invisible band quickly – so a note interrupted
   mid-fade reads as "still fading" rather than "stuck ghosting" in the beige gap above the truck. */
.scrub-note { position: absolute; right: var(--gutter); top: clamp(1rem, 3vh, 2rem); text-align: right;
  opacity: 0; transform: translateY(.8rem); transition: opacity .18s ease-in, transform .18s ease-in; }
.scrub-note.is-on { opacity: 1; transform: none; transition: opacity .45s var(--ease), transform .45s var(--ease); }
.scrub-note-fig { display: inline-block; font-family: var(--font-display); font-weight: 800; font-stretch: 125%;
  font-size: clamp(2.4rem, 1rem + 4.2vw, 4.8rem); line-height: 1; letter-spacing: -.03em;
  color: var(--ink); font-variant-numeric: tabular-nums;
  border-bottom: 4px solid var(--accent); padding-bottom: .06em; } /* Tarifzahl: Tinte + die rote Linie, wie im Preisband */
.scrub-note-cap { display: block; margin-top: .45rem; font-family: var(--font-mono); font-size: .86rem;
  font-weight: 600; color: var(--ink); max-width: 22ch; margin-left: auto; }
@media (max-width: 700px), (max-aspect-ratio: 1/1) { .scrub-notes { display: none !important; } }

/* Ohne Engine (no-JS, reduced-motion, Frames noch nicht verdrahtet) fällt der Track auf eine
   Viewporthöhe zusammen – niemand scrollt durch 2,6 leere Bildschirme Sand. */
@media (prefers-reduced-motion: reduce) { .scrub-stage { height: auto; } .scrub-stage-pin { position: relative; top: 0; } }
.no-js .scrub-stage, .scrub-stage:not(.scrub-armed) { height: auto; }
.no-js .scrub-stage-pin, .scrub-stage:not(.scrub-armed) .scrub-stage-pin { position: relative; top: 0; }
@media (max-width: 700px) {
  .scrub-stage { height: 300vh; }
  /* Item 4 (Runde 2 Defekt): drei CTA-gewichtige Elemente standen auf 375 zu dicht gedrängt am
     Fuß der ersten anderthalb Bildschirme. Mehr Luft zwischen Caption, Button-Zeile und dem jetzt
     leiseren Anruf-Link (siehe .hero-call oben) schafft eine klare Reihenfolge statt eines Blocks. */
  .hero-bottom { flex-direction: column; align-items: flex-start; gap: 1.5rem; }
  .hero-cta-row { justify-content: flex-start; }
  .hero-cta-group { align-items: flex-start; }
}

/* ---------- page-load: eine orchestrierte Staffel (H1 → Subline → Bühne) ---------- */
@keyframes rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
.hero-h1, .hero-subline, .hero-bottom { animation: rise .7s var(--ease) both; }
.hero-subline { animation-delay: .12s; }
.hero-bottom { animation-delay: .24s; }
@media (prefers-reduced-motion: reduce) { .hero-h1, .hero-subline, .hero-bottom { animation: none; } }

/* Item 2/5/7/22/37 (fix round 1 pass 2): the separate .scrub-nojs-plate that used to sit here
   (a small boxed CLOSED-truck thumbnail above the H1) is removed by construction – it duplicated
   the hero and read as a broken/contradictory truck state next to the full-width OPEN end-frame
   the section below already shows under no-JS/reduced-motion (the immutable "reduced-motion shows
   end-frame static" is unchanged: .scrub-fallback alone still carries that, single image). */

/* ---------- sections ---------- */
.section { padding: clamp(3.5rem, 8vw, 7rem) 0; }
.section + .section { border-top: 1px solid var(--hair); }
.section-head { max-width: 62ch; margin-bottom: clamp(2rem, 4vw, 3.25rem); }

/* ---------- Belegtafeln – jede echte Aufnahme ist eine gerahmte Tafel ---------- */
.plate { margin: 0; background: var(--ground-deep); border: 1px solid var(--hair); padding: 12px; }
.plate img { width: 100%; height: auto; background: var(--white); }
.plate figcaption {
  font-family: var(--font-mono); font-size: .74rem; line-height: 1.5; letter-spacing: .04em;
  color: var(--ink-soft); margin-top: 10px; display: flex; gap: .8em; align-items: baseline;
}
.plate figcaption b { color: var(--ink); font-weight: 600; flex: 0 0 auto; }
/* Item 13 (repair pass): the bold-word + mono-description caption is the plate/Aufmaßblatt device –
   it stays on the Flotte and Standort plates, where it IS the concept. Every other plate's caption
   (step photos, the no-JS hero plate, the Karton photo) used the identical construction; that is the
   monotonous-caption tell. .cap-plain is one flowing Literata sentence instead: no bold lead word,
   no mono, so no more than two images on the page still share the plate-label construction. */
.cap-plain { font-family: var(--font-body); font-style: italic; font-size: .92rem; line-height: 1.5; color: var(--ink-soft); margin-top: 10px; }

/* ---------- (6) PREISBAND – Full-bleed in Petrol, der einzige tiefe Block ---------- */
/* Defekt (fix round 1 pass 2, Item 4/17): the sticky header (opaque, ground-coloured) sits at a
   fixed --header-h regardless of scroll position; on a normal (non-anchor) scroll the section's
   own top padding is the only thing that ever keeps the "25 €" figure and this heading clear of
   it. The vw-based clamp's floor (3.5rem = 56px) can fall under --header-h (64px) on short
   viewports; max() guarantees the padding never drops below header height + a real margin, so the
   heading/figure never scroll under the header no matter the offset. scroll-margin-top covers the
   anchor-jump path (nav "Preis" link) the same way scroll-padding-top already does site-wide. */
.band { background: var(--ink); color: var(--ground); padding: max(var(--header-h) + 2rem, clamp(3.5rem, 8vw, 7rem)) 0; scroll-margin-top: var(--header-h); }
/* impeccable-audit P1 (Item 12): --focus is var(--ink) site-wide, so a keyboard-focused link
   inside a var(--ink) SECTION (this band, the footer below) drew an ink outline on an ink
   background – contrast(ink, ink) = 1:1, invisible. Overriding --focus locally to --ground
   (already the verified 12,6:1 pair against ink per DESIGN.md) keeps the same
   :focus-visible rule and outline width working everywhere, just pointed at a colour that
   actually shows on this section's own background – no new colour, no new rule. */
.band { --focus: var(--ground); }
.band .h-section { color: var(--ground); }
.band .lead { color: color-mix(in oklab, var(--ground) 86%, var(--ink)); }
/* Item 23 (Kritiker 3, historical): a CTA inside the band itself, so the strongest argument
   converts on the spot; first shipped as a ghost button (Sandgrund-Rand/-Schrift statt Tinte).
   Fix round 2 pass 2 (Item 26): reverted to the plain red .btn (see index.html) so all three
   "Anfrage senden" targets share one colour; only the spacing rule below still applies. */
.band .btn { margin-top: 1.5rem; }
.band-grid { display: grid; gap: clamp(2rem, 5vw, 4rem); }
/* Fix round 2 pass 2 (Item 8/24): 900px left a 768px tablet viewport single-column, where the
   figs (die Tarifzahl) are narrow inline blocks that never need the row's full measure – the section
   read as ~35-40% empty dark space on the right while hero/services/footer run full-bleed at
   the same width. Lowered to the site's own existing "narrow" threshold (700px, already used
   by the header nav and the hero's cover/contain switch) so 768 gets the same two-column
   balance already used above 900. */
@media (min-width: 700px) { .band-grid { grid-template-columns: 1.25fr .75fr; align-items: end; } }
.figs { display: grid; gap: clamp(1.5rem, 3vw, 2.5rem); grid-template-columns: 1fr; }
.fig { min-width: 0; }
.fig b {
  display: inline-block; font-family: var(--font-display); font-stretch: 125%; font-weight: 800;
  font-size: clamp(2.8rem, 1.4rem + 5.6vw, 6.4rem); line-height: .94; letter-spacing: -.035em;
  font-variant-numeric: tabular-nums; color: var(--ground);
  border-bottom: 4px solid var(--accent); padding-bottom: .08em; white-space: nowrap;
}
/* Item 20 (Kritiker 2): this is exactly the fine print a price-skeptical reader leans in to check;
   already an AA-compliant pair (7.8:1, DESIGN.md) but one weight step (mono's other loaded cut,
   400→600) makes it easier to read at a glance rather than merely passable. */
.fig > span { display: block; margin-top: .75em; font-family: var(--font-mono); font-weight: 600; font-size: .84rem; line-height: 1.45;
  letter-spacing: .03em; color: color-mix(in oklab, var(--ground) 76%, var(--ink)); max-width: 24ch; }
.fig-small b { font-size: clamp(2rem, 1rem + 3vw, 3.6rem); }
.band a { color: var(--ground); }
/* Fix round 2 pass 2 (Item 26): ".band a" (for the plain lead-paragraph links, if any) beats
   ".btn" on specificity and was about to repaint the new red CTA's text in --ground instead of
   the verified white-on-red pairing (4,48:1, DESIGN.md pairs table) the same button uses in the
   hero and the form. Restored explicitly rather than relying on source order. */
.band .btn { color: var(--white); }

/* ---------- (3) LEISTUNGSREGISTER – asymmetrisches Bento, die Karton-Tafel führt ----------
   Item 11 (Kritiker 1): die vier umrandeten Kacheln (bento-a/b/c/d) waren ein generisches
   2-spaltiges SaaS-Kartenraster – Bild + Label/Wert-Zeilen, austauschbar gegen jeden
   Leistungs-Baukasten. Fix durch Umbau statt Patch: eine einzige durchlaufende Frachtliste
   (.ledger) statt vier Kästchen, in derselben Grammatik wie .spec in Die Flotte (Mono-Großbuchstaben-
   Spalte links, 11ch/1fr-Raster, Haarlinien statt Kastenrand) – ein System, keine vier Karten. Das
   dominante Bild bleibt die eine große Zelle, die Liste die andere: weiterhin asymmetrisches Bento
   mit einer führenden Zelle, nur ohne die Kartenoptik. */
/* Item 1 (repair pass): every `1fr`/`Nfr` track below defaults to an automatic MINIMUM of the
   content's own natural width, not 0 – a grid track never shrinks below that unless told to with
   minmax(0, …). The register rows' text (reg-name + reg-note, kept on one line by the flex row
   below) were wide enough to force the whole .ledger, .bento, and its containing .container out to
   482 px at 375 px wide, which overflow-x:hidden on body only hid rather than fixed. minmax(0, …)
   lets every track shrink to the container's real width so content wraps inside it instead. */
.bento { display: grid; gap: 1rem; grid-template-columns: minmax(0, 1fr); }
@media (min-width: 900px) {
  .bento { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); align-items: stretch; }
}
.ledger { display: flex; flex-direction: column; min-width: 0; }
.ledger-row { display: grid; grid-template-columns: 11ch minmax(0, 1fr); gap: 1rem; padding: 1.1rem 0; border-top: 1px solid var(--hair); }
.ledger-row:first-child { border-top: 0; }
.ledger-row:last-child { border-bottom: 1px solid var(--hair); }
.ledger-k { margin: 0; font-family: var(--font-mono); font-weight: 600; font-size: .74rem; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-soft); padding-top: .25em; overflow-wrap: break-word; }
.register { list-style: none; margin: 0; padding: 0; min-width: 0; }
.register li { display: flex; flex-wrap: wrap; justify-content: space-between; gap: .3rem 1rem; align-items: baseline;
  padding: .5em 0; border-top: 1px solid var(--hair); }
.register li:first-child { border-top: 0; }
/* Item 1, continued: minmax(0, …) alone was not enough – "Transportdienstleistungen" is one
   unbreakable 25-character compound noun with no space to wrap at. Its own scrollWidth (232 px)
   still exceeded its clientWidth (210 px) even after the row correctly shrank to the container,
   and that internal overflow (never visible in a screenshot – the box paints outside itself rather
   than expanding it) was what document.documentElement.scrollWidth was reporting. overflow-wrap
   lets the browser break inside a word only when there is truly nowhere else to go. */
.register .reg-name { font-family: var(--font-display); font-weight: 600; font-size: 1.02rem; min-width: 0; flex: 1 1 auto; overflow-wrap: break-word; }
.register .reg-note { font-family: var(--font-mono); font-size: .76rem; color: var(--ink-soft); text-align: right; letter-spacing: .02em; overflow-wrap: break-word; }
/* Defekt (fix round 1 pass 2, Item 1/12): at ~375 px "Transportdienstleistungen" broke mid-word
   (now shortened to "Transportleistungen", see index.html) and its right-aligned .reg-note wrapped
   into two ragged-right lines under it – the most crowded block on the page at this width. Below
   420 px the value drops right-alignment and moves to its own left-aligned line instead of fighting
   for space on the name's line; construction fix, not a truncation. */
@media (max-width: 420px) {
  .register li { flex-direction: column; align-items: flex-start; gap: .15rem; }
  .register .reg-note { text-align: left; }
}
.bento-lead.plate { display: flex; flex-direction: column; }
.bento-lead .plate-img { position: relative; flex: 1 1 auto; min-height: 440px; background: var(--white); }
.bento-lead .plate-img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: center 30%; }
/* Bildregel aus dem Brief: genau ein Bild ist groß (die Bühne); jede echte Aufnahme läuft als
   gerahmte Tafel von 320–440 px, wie Abbildungen in einer Betriebsanleitung. */
.plate-cap { max-width: 440px; width: 100%; }
@media (min-width: 700px) {
  .zigzag-row .plate-cap { justify-self: end; }
  .zigzag-row:nth-child(even) .plate-cap { justify-self: start; }
  .split .plate-cap { justify-self: end; }
  .editorial .plate-cap { justify-self: end; }
}

/* ---------- (1) SPLIT 60/40 – Flotte / Anfrage (gespiegelt) ---------- */
.split { display: grid; gap: clamp(1.75rem, 4vw, 3.5rem); align-items: center; }
/* Fix round 2 pass 2 (Item 9/25): same 768px dead-space defect as .band-grid below – the
   Flotte spec table (and the Anfrage form/map split) stayed single-column until 880px, so at
   768 the text column capped at 46ch and the plate capped at 440px both sat left, leaving the
   right ~260-300px of the 768 viewport empty. Lowered to 700px, the breakpoint already used
   elsewhere on this page. Trade-off: at exactly 768 the plate column runs a little under the
   320px floor of the plate-width rule (≈290px) rather than well over it – a real image is
   better a touch narrow than the site looking like a broken two-column grid at a named width. */
@media (min-width: 700px) {
  .split { grid-template-columns: 1.3fr 1fr; }
  .split.split-flip { grid-template-columns: 1.3fr 1fr; }
  .split.split-flip .split-media { order: 1; }
}
/* Fix round 2 pass 2 (Item 17/30): .spec used to be the identical mono-label/bold-value
   hairline row as .ledger-row (Leistungen) and .contact-lines – three sections stamped from
   one component. The Flotte facts are exactly four fixed fields (Wagen/Beklebung/An
   Bord/Besatzung, no list-of-N register), which is what a nameplate/spec-sheet actually looks
   like: a bordered plate with fields in a fixed grid, not a scrolling ledger. Restyled as a
   2×2 bordered card (Sand tief, Tinte-Rahmen – the same plate-surface grammar the page's real
   photos use, applied to data instead of an image) with the label riding above its own value
   inside each cell, rather than a left-hand label column. Same tokens, same type roles,
   genuinely different layout. */
.spec {
  list-style: none; margin: 1.5rem 0 0; padding: 0; max-width: 480px;
  display: grid; grid-template-columns: 1fr 1fr; border: 1px solid var(--ink); background: var(--ground-deep);
}
.spec li { display: flex; flex-direction: column; gap: .4rem; padding: 1rem 1.15rem;
  border-right: 1px solid var(--hair); border-bottom: 1px solid var(--hair); }
.spec li:nth-child(2n) { border-right: 0; }
.spec li:nth-last-child(-n+2) { border-bottom: 0; }
.spec .k { order: -1; font-family: var(--font-mono); font-size: .7rem; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-soft); overflow-wrap: break-word; }
.spec li span:last-child { font-weight: 600; color: var(--ink); font-size: .96rem; line-height: 1.4; }
@media (max-width: 559px) {
  .spec { grid-template-columns: 1fr; }
  .spec li { border-right: 0; }
}

/* ---------- (2) ZICKZACK – der Ablauf, drei Schritte, Bild wechselt die Seite ---------- */
.zigzag { display: grid; gap: clamp(2.5rem, 6vw, 5rem); }
.zigzag-row { display: grid; gap: clamp(1.25rem, 3vw, 2.5rem); align-items: center; }
/* Fix round 2 pass 2 (Item 5/20): "Drei Schritte bis zum Einzug" was single-column below
   820px – at 768 the .step text (max-width 46ch) sat alone in a much wider container, the
   same dead-space defect as .split/.band-grid. Lowered to 700px to match. */
@media (min-width: 700px) {
  .zigzag-row { grid-template-columns: 1fr 1fr; }
  .zigzag-row--solo { grid-template-columns: 1fr; }
  .zigzag-row--solo .step { max-width: 56ch; }
  .zigzag-row:nth-child(even) .zigzag-media { order: -1; }
}
/* Item 6/12/21/28/40 (Runde 2, historical): "01/02/03" mit roter Unterstreichung war dieselbe
   Kennzahl-Masche wie im Preisband; ersetzt damals durch eine umrandete "Pos. 0n"-Marke in Tinte.
   Item 11 (repair pass): die Marke selbst war der numerierte Scaffold, den Kritiker als Bau-
   kasten-Signal lasen (Eyebrow-Pattern über jedem Schritt). Ersetzt durch eine Haarlinie über dem
   Schritt (derselbe Trenner wie im Register/Spec) und den echten deutschen Namen (Anfrage · Termin
   · Umzugstag) als Hierarchie, mit einer einzelnen belegten Tatsache am Fuß statt eines Zählers. */
.step { max-width: 46ch; padding-top: 1.2rem; border-top: 1px solid var(--hair); }
.step .h-sub { font-size: clamp(1.4rem, 1.1rem + 1vw, 1.9rem); }
.step-fact { margin: .9em 0 0; font-size: .9rem; color: var(--ink-soft); }
.step-fact.mono { font-family: var(--font-mono); letter-spacing: .02em; font-variant-numeric: tabular-nums; }
/* Item 20: die Ladeliste war das vierte Register mit identischer Label:Wert-Haarlinien-Optik
   (nach Leistungen, Flotte-Spec, Kontaktzeilen). Hier stattdessen als Tafel aus einzeln
   umrandeten Positionen – gleiche Schrift, gleiche Farben, andere Form, sonst nichts geändert. */
/* Fix round 2 pass 2 (Item 6/21): seven individually-bordered boxes tiled inside the outer
   bordered .plate card doubled the frame (a frame around a grid of frames) and read as
   crowded at 375 next to the page's otherwise generous spacing. Construction fix: one flowing
   list inside the single outer card, rows separated by the same hairline the register/spec
   lists already use elsewhere – no per-item border, so exactly one frame remains (the plate). */
.ladeliste { list-style: none; margin: 0; padding: .3rem 1rem; background: var(--white); }
.ladeliste li { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; gap: .3rem 1rem;
  padding: .65em 0; border-top: 1px solid var(--hair);
  font-family: var(--font-mono); font-size: .82rem; }
.ladeliste li:first-child { border-top: 0; }
.ladeliste .q { color: var(--ink-soft); font-size: .72rem; font-variant-numeric: tabular-nums; text-align: right; }

/* ---------- (4) EDITORIAL – „Vertrauen bewegt." ---------- */
.editorial { display: grid; gap: clamp(1.75rem, 4vw, 3.5rem); }
/* Fix round 2 pass 2 (Item 5/20): "Wer kommt" / Vertrauen bewegt had the same single-column
   dead-space defect at 768 as the sections above – lowered to match. */
@media (min-width: 700px) { .editorial { grid-template-columns: 1.45fr 1fr; align-items: start; } }
/* Item 26 (Kritiker 3, fix round 1 pass 2): the standalone „ mark (formerly a hanging .quote-mark
   glyph) read as a stray character glued to the V's cap-height. Dropped rather than redrawn – the
   <cite> line already carries the quotation, no punctuation needed to signal it (see index.html). */
.pull-quote {
  font-family: var(--font-display); font-weight: 800; font-stretch: 125%; letter-spacing: -.03em; line-height: 1;
  font-size: clamp(2.4rem, 1rem + 5.2vw, 5.8rem); margin: 0 0 .35em; color: var(--ink); text-wrap: balance;
}
.pull-quote cite { display: block; margin-top: .9rem; font: 600 .78rem/1.5 var(--font-mono); letter-spacing: .06em; font-style: normal; color: var(--ink-soft); }
.editorial-body { max-width: 58ch; }

/* ---------- ANFRAGE – Formular, Vorwahl fix auf +43 ---------- */
.form { display: grid; gap: 1rem; }
.row2 { display: grid; gap: 1rem; }
@media (min-width: 560px) { .row2 { grid-template-columns: 1fr 1fr; } }
.field { display: grid; gap: .35rem; }
.field label { font-family: var(--font-body); font-weight: 600; font-size: .95rem; }
.field input, .field select, .field textarea {
  width: 100%; font: inherit; font-size: 1rem; color: var(--ink); background: var(--ground-deep);
  border: 1px solid var(--ink); border-radius: 0; padding: .75rem .85rem; -webkit-appearance: none; appearance: none;
}
.field select { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='9' viewBox='0 0 14 9'%3E%3Cpath d='M1 1l6 6 6-6' fill='none' stroke='%23002A34' stroke-width='2'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right .85rem center; padding-right: 2.4rem; }
.field textarea { min-height: 7.5rem; resize: vertical; }
/* impeccable-audit P1: 45 % ink auf Sand tief maß 2,71:1. --ink-soft ist bereits das im
   Brief gemessene, freigegebene Paar auf Sand tief (4,8:1) – keine neue Farbe, nur wiederverwendet. */
.field input::placeholder, .field textarea::placeholder { color: var(--ink-soft); }
.tel-row { display: grid; grid-template-columns: auto 1fr; }
.tel-row .vorwahl {
  display: flex; align-items: center; padding: 0 .85rem; font-family: var(--font-mono); font-weight: 600;
  background: var(--ink); color: var(--ground); border: 1px solid var(--ink); border-right: 0;
}
.form-note { font-size: .92rem; color: var(--ink-soft); margin: .25rem 0 0; }
.contact-lines { list-style: none; margin: 1.25rem 0 0; padding: 0; }
.contact-lines li { display: grid; grid-template-columns: 8ch minmax(0, 1fr); gap: 1rem; padding: .55em 0; border-top: 1px solid var(--hair); align-items: baseline; }
.contact-lines li:last-child { border-bottom: 1px solid var(--hair); }
.contact-lines .k { font-family: var(--font-mono); font-size: .74rem; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-soft); overflow-wrap: break-word; }
.contact-lines a { font-family: var(--font-mono); font-weight: 600; color: var(--ink); text-decoration: none; }
.contact-lines a:hover { color: var(--accent-dark); }
.map.plate img { background: var(--ground); }

/* ---------- footer – ruhiges Band in Petrol ---------- */
.site-footer { background: var(--ink); color: color-mix(in oklab, var(--ground) 76%, var(--ink)); padding: clamp(2.5rem, 5vw, 4rem) 0 1.5rem; }
/* impeccable-audit P1 (Item 12): same ink-on-ink invisible focus outline as .band above –
   every footer link (phone, email, Impressum, Datenschutz, the wordmark) is on this ink
   background. Same fix, same reused --ground pairing. */
.site-footer { --focus: var(--ground); }
.site-footer a { color: var(--ground); }
.site-footer a:hover { color: color-mix(in oklab, var(--ground) 80%, var(--ink)); }
.footer-mark { font-family: var(--font-display); font-stretch: 125%; font-weight: 800; font-size: 1.35rem; letter-spacing: -.02em; color: var(--ground); text-decoration: none; }
.footer-inner { display: grid; gap: 2rem; margin-bottom: 2.25rem; }
@media (min-width: 720px) { .footer-inner { grid-template-columns: 1.6fr 1fr 1fr 1fr; } }
.footer-tagline { margin: .9rem 0 0; max-width: 34ch; font-size: .96rem; }
.footer-col-label { font-family: var(--font-body); font-style: italic; color: var(--ground); font-size: .92rem; margin: 0 0 .7rem; font-weight: 600; }
.footer-col p { margin: 0 0 .6rem; font-size: .94rem; line-height: 1.6; }
.footer-bottom { border-top: 1px solid color-mix(in oklab, var(--ground) 18%, var(--ink)); padding-top: 1.2rem;
  display: flex; flex-wrap: wrap; gap: .5rem 1.5rem; justify-content: space-between; }
.footer-bottom p { margin: 0; font-size: .78rem; font-family: var(--font-mono); letter-spacing: .04em; }

/* ---------- Impressum (Unterseite) ---------- */
main.page { padding: clamp(3rem, 6vw, 5rem) 0; }
main.page .prose { max-width: 66ch; }
main.page h2 { font-family: var(--font-display); font-weight: 700; font-stretch: 100%; font-size: 1.35rem; margin: 2rem 0 .6rem; }
main.page dl { margin: 0 0 1.5rem; display: grid; grid-template-columns: 12ch 1fr; gap: .4rem 1rem; }
main.page dt { font-family: var(--font-mono); font-size: .74rem; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-soft); padding-top: .3em; }
main.page dd { margin: 0; }

/* ---------- reveal (IO-getrieben, ohne JS sichtbar) ---------- */
/* Item 11b (impeccable-audit P2, fix round 1 pass 2): will-change used to sit on every .reveal
   element permanently – a standing compositing-layer hint for elements that may be nowhere near
   transitioning yet, and never released once they had. Scoped to :not(.in) only: the hint is live
   while an element is still waiting to transition in, and drops the moment .in lands (main.js),
   letting the browser release the layer once the transform/opacity have settled at their finals. */
.reveal { opacity: 0; transform: translateY(20px); transition: opacity .6s var(--ease), transform .6s var(--ease); }
.reveal:not(.in) { will-change: opacity, transform; }
.reveal.in { opacity: 1; transform: none; }
.no-js .reveal, .reduce-motion .reveal { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .reveal { opacity: 1; transform: none; transition: none; } }
