/* base.css — technisches Grundgerüst für alle /redesign-Vorschauen.
   Themes überschreiben NUR die :root-Variablen + eigene Sektions-Styles. */
:root {
  --bg: #ffffff;
  --bg-alt: #f4f4f5;
  --text: #18181b;
  --text-dim: #52525b;
  --accent: #2563eb;
  --accent-contrast: #ffffff;
  --display: system-ui, 'Segoe UI', Roboto, sans-serif;
  --body: system-ui, 'Segoe UI', Roboto, sans-serif;
  --radius: 1rem;
  --pad: clamp(1rem, 4vw, 2.5rem);

  /* PFLICHT — nicht entfernen, und bei dunklem Theme auf "dark" setzen.
     Ohne diese Angabe haelt der Browser die Seite fuer hell und faerbt sie
     bei aktivem Farbmodus (Windows-Farbfilter, Nachtmodus, erzwungener
     Dunkelmodus von Chrome/Edge) SELBST um. Auf einer bereits dunklen Seite
     hellt er dabei die Flaechen auf, laesst die vom Theme gesetzte helle
     Schrift aber stehen — helle Schrift auf hellem Grund, unlesbar. Genau
     dieser Fehler ist bei HD Vision aufgetreten.
     Mit der Deklaration weiss der Browser, dass die Seite ihre Farben selbst
     verwaltet, und laesst die Finger davon. */
  color-scheme: light;

  /* Breakpoint-System — VERBINDLICH. Vorher lagen 16 Layout-Media-Queries auf
     sieben willkürlichen Werten (700/720/820/840/880/900/1000); jede neue
     Sektion war dadurch Handarbeit. Es gibt jetzt genau drei Stufen. CSS kann
     Variablen nicht in @media auswerten, die Werte stehen dort als Literal —
     diese Liste ist die Quelle der Wahrheit, an der sie sich ausrichten:
       sm  720px  Handy -> Tablet   (Spalten aufklappen, Karussell -> Grid)
       md  900px  Tablet -> Desktop (Burger -> Kopfzeilen-Navigation)
       lg 1000px  breit             (Seitenleisten-Layouts) */
  --bp-sm: 720px;
  --bp-md: 900px;
  --bp-lg: 1000px;

  /* Höhe der mobilen Aktionsleiste; wird von .action-bar gesetzt und hält
     unten Platz frei, damit nichts fixiert Positioniertes darauf liegt. */
  --action-bar-h: 0px;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  font-family: var(--body);
  background: var(--bg);
  color: var(--text);
  line-height: 1.6;
  overflow-x: hidden;
}
img { max-width: 100%; height: auto; display: block; }
/* Das <picture> um ein Bild ist nur eine Huelle fuer den WebP-Rueckfall und
   soll im Layout nicht mitzaehlen: sonst sitzt in jedem Grid und jeder
   Flex-Reihe eine zusaetzliche Box zwischen Container und Bild. Die
   Selektoren, die ein Foto als direktes Kind erwarten, decken die Huelle
   zusaetzlich per "> picture > img" ab. */
picture { display: contents; }

/* ---------- A11y ---------- */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--accent); color: var(--accent-contrast);
  padding: .75rem 1.25rem; font-weight: 700;
}
.skip-link:focus { left: 0; }
a:focus-visible, button:focus-visible {
  outline: 3px solid var(--accent); outline-offset: 3px; border-radius: 2px;
}
.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* ---------- Layout ---------- */
.container { max-width: 72rem; margin-inline: auto; padding-inline: var(--pad); }
.section { padding-block: clamp(3.5rem, 8vw, 6.5rem); }
.section--alt { background: var(--bg-alt); }
.section-kicker {
  font-weight: 800; letter-spacing: .2em; text-transform: uppercase;
  font-size: .8rem; margin-bottom: .5rem; color: var(--accent);
}
.section-title { font-family: var(--display); font-size: clamp(2.2rem, 6vw, 4rem); line-height: 1.05; margin-bottom: 1rem; }

.grid { display: grid; gap: 1.5rem; }
@media (min-width: 700px) {
  .grid--2 { grid-template-columns: 1fr 1fr; }
  .grid--3 { grid-template-columns: repeat(3, 1fr); }
}

/* ---------- Header ---------- */
/* Der Weichzeichner liegt bewusst auf einem PSEUDOELEMENT, nicht auf der
   Kopfzeile selbst. Ein Element mit backdrop-filter wird zum Bezugsrahmen fuer
   position:fixed seiner Kinder — das Burger-Panel steckt darin und war deshalb
   auf 375x104 statt 375x667 eingesperrt. Auf iOS kommt dazu, dass ein Menue in
   einem Element mit backdrop-filter durchsichtige Flaechen bekommt
   (bekannter WebKit-Fehler, siehe small-business-website/references).
   Auf ::before wirkt der Filter optisch identisch, erzeugt aber keinen
   Bezugsrahmen — damit ist die ganze Fehlerklasse weg, ohne JS-Umschaltung. */
.site-header {
  position: sticky; top: 0; z-index: 50;
  background: transparent;
  border-bottom: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
}
.site-header::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: color-mix(in srgb, var(--bg) 85%, transparent);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
}
.header-inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; padding-block: .6rem;
}
/* Die Wortmarke muss neben dem Burger auf EINE Zeile passen. Gemessen:
   "Farbwerk Wolfsburg" brach bei 375px auf zwei Zeilen um und machte die
   Kopfzeile 97px statt 68px hoch. Deshalb mitwachsende Schriftgroesse und
   ein harter Umbruchstopp; laenger als der Platz wird sie beschnitten statt
   die Kopfzeile zu verdoppeln. */
.logo {
  font-family: var(--display); font-size: clamp(1.05rem, 4.4vw, 1.5rem);
  color: var(--text); white-space: nowrap; min-width: 0;
  overflow: hidden; text-overflow: ellipsis;
  text-decoration: none; display: inline-flex; align-items: center;
  gap: .5rem; min-height: 44px; font-weight: 800;
}
.site-nav ul { display: flex; list-style: none; gap: .25rem; flex-wrap: wrap; }
.site-nav a {
  display: inline-flex; align-items: center; min-height: 44px;
  padding: .35rem .8rem; color: var(--text-dim); text-decoration: none;
  font-weight: 600; font-size: .95rem; border-radius: 999px;
  transition: color .2s, background .2s;
}
.site-nav a:hover { color: var(--text); background: color-mix(in srgb, var(--text) 8%, transparent); }
.site-nav a.nav-cta { background: var(--accent); color: var(--accent-contrast); }

/* ---------- Burger-Menü (mobile Navigation, VERBINDLICH) ----------
   Regel: unterhalb von md läuft die Navigation IMMER über das Burger-Menü —
   in Vorschauen wie in echten Kundenwebsites, in jeder Architektur. Vorher
   hatte .site-nav gar keine Mobilregel: bei fünf Menüpunkten brach die
   Kopfzeile auf zwei Zeilen um und war dann 172px hoch, also 26% eines
   375x667-Viewports, dauerhaft klebend.

   Progressive Enhancement wie bei .reveal: der Umschalter ist voreingestellt
   unsichtbar und die Navigation normal sichtbar. Erst wenn base.js die Klasse
   .js-nav auf <html> setzt, greift das Panel-Layout. Ohne JS bleibt also eine
   funktionierende (umbrechende) Navigation statt eines toten Knopfes. */
.nav-toggle {
  display: none; /* wird unter .js-nav unterhalb von md eingeblendet */
  position: relative; z-index: 2;
  width: 48px; height: 48px; flex: 0 0 auto;
  align-items: center; justify-content: center;
  background: none; border: 0; cursor: pointer; color: var(--text);
  border-radius: 999px;
}
.nav-toggle__bars, .nav-toggle__bars::before, .nav-toggle__bars::after {
  content: ""; position: absolute; left: 50%; width: 22px; height: 2px;
  background: currentColor; border-radius: 2px;
  transition: transform .28s cubic-bezier(.2,.8,.2,1), opacity .18s;
}
.nav-toggle__bars { transform: translateX(-50%); }
.nav-toggle__bars::before { transform: translateX(-50%) translateY(-7px); }
.nav-toggle__bars::after  { transform: translateX(-50%) translateY(7px); }
/* Geöffnet: die mittlere Linie verschwindet, die äußeren bilden ein X. */
.nav-toggle[aria-expanded="true"] .nav-toggle__bars { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars::before {
  opacity: 1; transform: translateX(-50%) rotate(45deg);
}
.nav-toggle[aria-expanded="true"] .nav-toggle__bars::after {
  opacity: 1; transform: translateX(-50%) rotate(-45deg);
}

/* Abdunklung hinter dem offenen Panel. Liegt als eigenes Element vor der
   Seite, damit ein Tipp daneben schließt, ohne dass Links darunter feuern. */
.nav-scrim {
  position: fixed; inset: 0; z-index: 70; display: none;
  background: color-mix(in srgb, var(--text) 45%, transparent);
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
  opacity: 0; transition: opacity .25s;
}

@media (max-width: 899.98px) {
  .js-nav .nav-toggle { display: inline-flex; }

  /* Das Panel deckt den Bildschirm ab statt die Kopfzeile zu verlängern.
     display:none hält es zugleich aus der Tab-Reihenfolge — deshalb braucht
     es zusätzlich zu inert kein aria-hidden. */
  .js-nav .site-nav {
    position: fixed; inset: 0; z-index: 80; display: none;
    background: var(--bg); color: var(--text);
    padding: 5.5rem var(--pad) calc(var(--pad) + env(safe-area-inset-bottom));
    overflow-y: auto; overscroll-behavior: contain;
  }
  .js-nav .site-nav[data-open="true"] { display: block; }
  .js-nav .site-nav[data-open="true"] ~ .nav-scrim,
  .js-nav .nav-scrim[data-open="true"] { display: block; opacity: 1; }

  .js-nav .site-nav ul {
    display: flex; flex-direction: column; gap: .25rem;
    max-width: 30rem; margin-inline: auto;
  }
  /* Grosse, ruhige Zeilen statt Pillen: auf dem Handy ist die Navigation ein
     eigener Bildschirm, keine Kopfzeilenleiste. */
  .js-nav .site-nav a {
    min-height: 56px; width: 100%; font-size: 1.35rem; font-weight: 700;
    font-family: var(--display); padding: .6rem .25rem; border-radius: var(--radius);
    border-bottom: 1px solid color-mix(in srgb, var(--text) 10%, transparent);
  }
  .js-nav .site-nav a.nav-cta {
    justify-content: center; margin-top: 1.25rem; border-bottom: 0;
    min-height: 60px; border-radius: 999px;
  }
  /* Jeder Eintrag setzt beim Öffnen kurz nach — gestaffelt, damit das Panel
     nicht als eine tote Fläche erscheint. --i wird im Markup gesetzt. */
  .js-nav .site-nav[data-open="true"] li {
    animation: nav-in .34s cubic-bezier(.2,.8,.2,1) both;
    animation-delay: calc(var(--i, 0) * 45ms + 60ms);
  }
}
@keyframes nav-in { from { opacity: 0; transform: translateY(12px); } }

/* Ab md ist die Kopfzeilen-Navigation wieder normal und der Burger weg. */
@media (min-width: 900px) {
  .nav-toggle, .nav-scrim { display: none !important; }
}

/* Sperrt das Scrollen der Seite, solange das Panel offen ist. Ohne das
   scrollt unter dem Panel der Seiteninhalt mit. */
/* overflow:hidden genuegt, um die Seite dahinter festzuhalten. Zusaetzlich
   touch-action:none zu setzen war ein Fehler: es blockiert die Wischgeste auch
   INNERHALB des Panels, ein Menue mit vielen Punkten liesse sich auf dem Handy
   nicht mehr scrollen. Das Panel selbst haelt seinen Scroll ueber
   overscroll-behavior:contain bei sich. */
.nav-locked { overflow: hidden; }

/* Sicherheitsnetz: Sollte ein Theme den Weichzeichner doch wieder direkt auf
   .site-header setzen, nimmt diese Regel ihn waehrend des offenen Panels raus.
   Die eigentliche Loesung ist das ::before oben. */
.nav-locked .site-header {
  backdrop-filter: none; -webkit-backdrop-filter: none;
}

/* ---------- Mobile Aktionsleiste (Achse: Mobil-Muster) ----------
   Mobil hat der Besucher eine Absicht, und die ist je Branche eine andere.
   Die Leiste liegt daumenerreichbar am unteren Rand und traegt genau die
   ein bis drei Handlungen, die fuer DIESE Branche zaehlen. Vier Varianten,
   Markup dazu in blocks.html:
     handwerk -> Anrufen + WhatsApp        (niemand fuellt hier ein Formular)
     praxis   -> Termin + Anfahrt          (groessere Schrift, hoeherer Kontrast)
     gastro   -> Karte + Reservieren       (abends unterwegs, dunkle Fassung)
     verein   -> Mitglied werden + Termin  (Anschluss statt Verkauf)
   Nur unterhalb von md sichtbar — am Desktop uebernehmen Kopfzeile und CTA. */
.action-bar { display: none; }
@media (max-width: 899.98px) {
  .action-bar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 65;
    display: flex; gap: .5rem;
    padding: .55rem var(--pad) calc(.55rem + env(safe-area-inset-bottom));
    background: color-mix(in srgb, var(--bg) 92%, transparent);
    backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
    border-top: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
  }
  .nav-locked .action-bar { display: none; }
  .action-bar__btn {
    flex: 1 1 0; min-height: 52px;
    display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
    border-radius: 999px; text-decoration: none; font-weight: 800;
    font-size: 1rem; line-height: 1.1; text-align: center;
    background: color-mix(in srgb, var(--text) 8%, transparent); color: var(--text);
    border: 1px solid color-mix(in srgb, var(--text) 14%, transparent);
  }
  .action-bar__btn--primary {
    background: var(--accent); color: var(--accent-contrast); border-color: transparent;
    flex: 1.4 1 0;
  }
  .action-bar__btn svg { width: 20px; height: 20px; flex: 0 0 auto; }
  /* Zweitzeile fuer eine Statusangabe (Oeffnungszeiten, naechster Termin). */
  .action-bar__hint {
    position: absolute; left: 0; right: 0; top: -1.65rem;
    text-align: center; font-size: .75rem; font-weight: 700;
    color: var(--accent);
  }

  /* Praxis: aeltere Besucher — groesser und kontrastreicher. */
  .action-bar--praxis .action-bar__btn { min-height: 58px; font-size: 1.08rem; }
  /* Gastro: wird abends im Dunkeln aufgerufen. */
  .action-bar--gastro {
    background: color-mix(in srgb, var(--text) 94%, transparent);
    border-top-color: color-mix(in srgb, var(--bg) 20%, transparent);
  }
  .action-bar--gastro .action-bar__btn {
    background: color-mix(in srgb, var(--bg) 16%, transparent); color: var(--bg);
    border-color: color-mix(in srgb, var(--bg) 28%, transparent);
  }
  .action-bar--gastro .action-bar__btn--primary {
    background: var(--accent); color: var(--accent-contrast);
  }
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 48px; padding: .8rem 2rem; border-radius: 999px;
  font-weight: 800; font-size: 1.05rem; text-decoration: none;
  transition: transform .15s, box-shadow .15s, background .2s;
}
.btn--primary { background: var(--accent); color: var(--accent-contrast); }
.btn--primary:hover { transform: translateY(-2px); }
.btn--ghost { border: 2px solid color-mix(in srgb, var(--text) 40%, transparent); color: var(--text); }
.btn--ghost:hover { border-color: var(--text); }

/* ---------- Bild-Hilfen ---------- */
.img-cover { width: 100%; height: 100%; object-fit: cover; }
.img-card { border-radius: var(--radius); overflow: hidden; }

/* ---------- Footer ---------- */
.site-footer {
  background: var(--bg-alt);
  border-top: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
  padding-block: 3rem 5.5rem;
}
.footer-grid { display: grid; gap: 2rem; grid-template-columns: 1fr; }
/* Drei Fussspalten erst, wenn sie auch Platz haben. Bei 768px ergaben sie je
   180px - gemessen 4 Woerter auf 10 Zeilen, der Text las sich senkrecht. */
@media (min-width: 700px) { .footer-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 900px) { .footer-grid { grid-template-columns: repeat(3, 1fr); } }
.site-footer h2 { font-size: 1.05rem; margin-bottom: .6rem; text-transform: uppercase; letter-spacing: .1em; }
.site-footer p, .site-footer li { color: var(--text-dim); font-size: .95rem; }
.site-footer ul { list-style: none; }
.site-footer a { color: var(--text); text-underline-offset: 3px; }
.footer-legal {
  margin-top: 2.5rem; padding-top: 1.5rem;
  border-top: 1px solid color-mix(in srgb, var(--text) 10%, transparent);
  font-size: .85rem; color: var(--text-dim);
  display: flex; gap: 1.5rem; flex-wrap: wrap;
}

/* Tippflaeche eigenstaendiger Links auf dem Handy.
   Buttons haben ihre 44px laengst, die Textlinks nicht: gemessen 16-22px Hoehe
   bei Telefonnummer, E-Mail-Adresse, Fussnavigation und Rechtslinks - also
   ausgerechnet bei dem, was ein Besucher unterwegs antippt. Die Hoehe waechst
   ueber die Zeilenbox, nicht ueber Abstand, damit sich am Satzbild nichts
   verschiebt. Fliesstext ist ausgenommen: dort greift die WCAG-Ausnahme fuer
   Links im Satz, und ein aufgeblaehter inline-Link zerreisst den Absatz. */
@media (max-width: 899px) {
  .site-footer li > a,
  .site-footer address a,
  .site-footer nav a,
  .footer-legal a,
  .footer-legal-bar a,
  .kontakt-grid address a,
  .kontakt-grid li > a,
  .info-band-row a[href^="tel:"],
  .info-band-row a[href^="mailto:"] {
    display: inline-flex; align-items: center;
    min-height: 44px;
    /* Ohne diese beiden Zeilen wird eine lange E-Mail-Adresse als eine
       unteilbare Inline-Flex-Box zur Mindestbreite der Fussspalte und
       quetscht die Nachbarspalte auf ein Wort pro Zeile (gemessen 768px:
       180px Spalte, 10 Zeilen fuer 4 Woerter). */
    flex-wrap: wrap; overflow-wrap: anywhere;
  }
}

/* ---------- Entwurfs-Badge (Pflicht) ---------- */
/* Entwurfs-Hinweis. Lag vorher fix unten rechts und überlappte bei 375x667
   die .dock-nav um gemessene 6682 px² — auf dem Handy des Kunden verdeckte
   der Hinweis also die halbe Navigation. Behoben an beiden Enden: die
   Dock-Navigation ist auf Mobil abgeschaltet (dort gilt das Burger-Menü),
   und der Hinweis hält zusätzlich Abstand zur Aktionsleiste. */
.draft-badge {
  position: fixed; right: .9rem; z-index: 60;
  bottom: calc(.9rem + var(--action-bar-h) + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--text) 92%, transparent);
  color: var(--bg); border-radius: 999px; padding: .45rem 1rem;
  font-size: .78rem; font-weight: 600; pointer-events: none;
  max-width: calc(100vw - 1.8rem);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
}
/* Solange das Burger-Panel offen ist, hat der Hinweis dort nichts zu suchen. */
.nav-locked .draft-badge { display: none; }

/* ---------- Scroll-Reveal ----------
   Sichtbar per Default (progressive enhancement): erst wenn base.js läuft,
   setzt es .js-reveal und blendet aus. Bleibt JS aus, verschwindet nichts. */
.reveal.js-reveal { opacity: 0; transform: translateY(24px); transition: opacity .6s ease, transform .6s ease; }
.reveal.js-reveal.is-visible { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .reveal.js-reveal { opacity: 1; transform: none; transition: none; } }

/* ---------- Scroll-gekoppeltes Text-Aufhellen ----------
   Für kurze Statement-Absätze (nicht für lange Fließtexte). Startet als
   normaler, voll sichtbarer Text (progressive enhancement) — erst base.js
   zerlegt ihn in Wörter und dimmt sie, danach hellen sie proportional zum
   Scrollfortschritt durch den Block auf. .sh-ready markiert "JS ist bereit",
   ohne das bleibt der Absatz einfach normaler Text. */
/* Gedimmt wird gegen die GEERBTE Textfarbe, nicht gegen --text: eine dunkle
   Sektion setzt ihre helle Schrift meist direkt (color: #fff) und laesst
   --text unveraendert. Mit var(--text) hellte der Baustein dort auf die
   dunkle Grundschrift auf - der Absatz war auf dunklem Grund unlesbar und
   blieb es auch nach dem Aufhellen (gemessen 1.09:1). */
.scroll-highlight.sh-ready .sh-w {
  color: color-mix(in srgb, currentColor 25%, transparent);
  transition: color .15s linear;
}
.scroll-highlight.sh-ready .sh-w.is-lit { color: inherit; }
@media (prefers-reduced-motion: reduce) {
  .scroll-highlight.sh-ready .sh-w { color: inherit; transition: none; }
}

/* ---------- Swipebare Abschnitte (mobil) ----------
   Handy: horizontales Scroll-Snap-Karussell, kein JS nötig für die Bedienung.
   Aktive Karte wird zentriert und ist dabei bündig mit dem normalen Seitenrand
   (--swipe-peek = derselbe Rand wie .container/--pad) statt an einem eigenen
   Prozent-Abstand zu kleben. Springt pro Wisch genau eine Karte weiter:
   scroll-snap-stop:always verhindert, dass ein schneller Wisch mehrere Karten
   überspringt und "weiterrotiert". ::before/::after sind so breit, dass die
   erste/letzte Karte trotzdem bündig zentriert einrasten kann.
   Ab 720px: normales Grid, keine Karten mehr abgeschnitten. Für Cards, Referenzen,
   Zitate, Logo-/Badge-Reihen – überall, wo auf dem Handy mehr "Bewegung" gut tut. */
/* Wisch-Karussell. GEMESSENER FEHLER (375x667): --swipe-peek entsprach exakt
   --pad und wurde vom negativen Aussenabstand vollstaendig aufgebraucht — die
   Karte war 343px breit bei 343px Inhaltsbreite, die naechste lag zu 100%
   ausserhalb des Bildes. Ergebnis: nichts deutete an, dass hier gewischt
   werden kann, und niemand wischte. --swipe-next schneidet die Folgekarte
   jetzt sichtbar an; das ist der Hinweis, der die Geste ueberhaupt ausloest. */
.swipe {
  --swipe-peek: var(--pad);
  --swipe-next: 2.75rem;
  display: flex; gap: 1rem; overflow-x: auto; scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch; padding-block: .25rem;
  scrollbar-width: none; margin-inline: calc(var(--swipe-peek) * -1);
  scroll-padding-inline-start: var(--swipe-peek);
}
.swipe::-webkit-scrollbar { display: none; }
.swipe > * {
  /* start statt center: mit sichtbarem Anschnitt muss die Karte am linken
     Rand einrasten, sonst sitzt der Anschnitt auf beiden Seiten halb. */
  scroll-snap-align: start;
  scroll-snap-stop: always;
  flex: 0 0 calc(100% - 2 * var(--swipe-peek) - var(--swipe-next));
  max-width: 22rem;
}
.swipe::before,
.swipe::after { content: ""; flex: 0 0 calc(var(--swipe-peek) - 1rem); }
.swipe::before { scroll-snap-align: start; }
@media (min-width: 720px) {
  .swipe {
    overflow-x: visible; display: grid;
    /* GEMESSENER FEHLER: hier stand repeat(var(--swipe-cols,3), 1fr) — eine
       FESTE Spaltenzahl unabhaengig von der Breite. Bei 4 Karten auf 900px
       ergab das 189px breite Spalten; deutsche Komposita
       ("Schaufensterbeschriftung") brechen dort auf fast ein Wort pro Zeile um
       und der Text liest sich, als stuende er senkrecht.
       auto-fit + minmax deckelt die Spaltenzahl nach oben, laesst eine Spalte
       aber nie schmaler als 15rem werden — lieber eine Reihe mehr als
       unlesbare Saeulen. */
    grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
    gap: 1.5rem; margin-inline: 0; padding-inline: 0;
  }
  /* Wer wirklich eine feste Spaltenzahl braucht, setzt --swipe-cols UND
     bekommt trotzdem die Mindestbreite garantiert. */
  .swipe[style*="--swipe-cols"] {
    grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
  }
  .swipe > * { flex: initial; max-width: none; scroll-snap-align: none; scroll-snap-stop: normal; }
  .swipe::before, .swipe::after { content: none; }
}
/* .swipe bricht per negativem Margin bis zum Seitenrand aus — das setzt
   voraus, dass es DIREKT im .container sitzt. In einer schmaleren Spalte
   (.split-row__body) ist der Seitenrand nicht der Viewport-Rand, der
   Ausbruch erzeugt dort echten horizontalen Überlauf. Deshalb dort neutral. */
.split-row__body .swipe { margin-inline: 0; }
.split-row__body .swipe::before, .split-row__body .swipe::after { content: none; }

.swipe-dots { display: flex; justify-content: center; gap: .4rem; margin-top: 1rem; }
.swipe-dots span {
  width: 6px; height: 6px; border-radius: 50%;
  background: color-mix(in srgb, var(--text) 25%, transparent);
  transition: background .2s, transform .2s;
}
.swipe-dots span.is-active { background: var(--accent); transform: scale(1.4); }
@media (min-width: 720px) { .swipe-dots { display: none; } }
@media (prefers-reduced-motion: reduce) { .swipe { scroll-behavior: auto; } }

/* ---------- Magnetische Buttons ----------
   Button folgt der Maus minimal (max. ~8px), federt beim Verlassen zurück.
   Ohne JS/bei reduced-motion: ganz normaler Button, keine Bewegung. */
.btn--magnetic { transition: transform .25s cubic-bezier(.2,.8,.2,1); }

/* ---------- Zahlen zählen hoch ----------
   <strong class="count-up" data-target="150" data-suffix="+">0</strong>
   Startwert im Markup ist "0" (oder Endwert für No-JS/Crawler) — base.js zählt
   erst hoch, wenn der Block ins Bild scrollt. font-variant-numeric hält die
   Ziffernbreite stabil, damit der Text beim Zählen nicht "zittert". */
.count-up { font-variant-numeric: tabular-nums; }

/* ---------- Sticky Schritt-Erklärung ----------
   Links ein sticky-fixiertes Bild/Icon, rechts mehrere Schritte, die beim
   Vorbeiscrollen einzeln hervorgehoben werden (nicht alle gleichzeitig grau).
   Nur für echte Abläufe mit Reihenfolge (z. B. "So arbeite ich"), keine
   künstliche Nummerierung für Aussagen ohne echte Sequenz. */
.steps-scroll { display: grid; gap: 3rem; }
@media (min-width: 900px) {
  .steps-scroll { grid-template-columns: 1fr 1fr; align-items: start; gap: 4rem; }
  .steps-scroll__visual { position: sticky; top: 6rem; }
}
.steps-scroll__step {
  padding-block: 2.5rem; border-top: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
  opacity: .4; transition: opacity .35s ease;
}
.steps-scroll__step:first-child { border-top: 0; }
.steps-scroll__step.is-active { opacity: 1; }
/* Frueher die Schrittnummer ("01"). Nummerierungen sind als Gestaltungsmittel
   abgeschafft (siehe SKILL.md Phase 2) — die Klasse traegt jetzt eine kurze
   BEZEICHNUNG statt einer Zahl ("Aufmass", "Angebot", "Montage"). Der
   Akzentstrich davor macht die Abfolge sichtbar, ohne sie zu durchnummerieren.
   Klassenname bleibt, damit bestehende Kundenprojekte nicht brechen. */
.steps-scroll__num {
  font-family: var(--display); font-weight: 700; font-size: .85rem;
  color: var(--accent); letter-spacing: .06em; margin-bottom: .5rem;
  display: flex; align-items: center; gap: .6rem;
  text-transform: uppercase;
}
.steps-scroll__num::before {
  content: ""; flex: 0 0 auto; width: 1.75rem; height: 2px;
  background: currentColor;
}
@media (prefers-reduced-motion: reduce) { .steps-scroll__step { opacity: 1; transition: none; } }

/* ---------- Marquee-Band (Endlos-Laufband) ----------
   Für Logo-/Partnerreihen oder eine wiederholte kurze Botschaft. Reiner CSS-Loop,
   kein JS nötig. Inhalt im Markup EINMAL schreiben, das zweite Mal identisch
   kopieren (siehe blocks.html) — die Animation verschiebt exakt um 50%, damit
   der Loop nahtlos wirkt. Pausiert bei Hover und bei reduzierter Bewegung. */
.marquee { overflow: hidden; -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
.marquee__track { display: flex; width: max-content; gap: 3.5rem; animation: marquee 26s linear infinite; }
.marquee:hover .marquee__track { animation-play-state: paused; }
@keyframes marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .marquee__track { animation: none; } .marquee { overflow-x: auto; } }

/* ---------- Cursor-Glow (dunkle Sektionen) ----------
   Ein weiches Licht folgt dem Mauszeiger — nur auf .section--glow, nur Desktop
   (kein Touch), nur wenn Bewegung erlaubt ist. Rein dekorativ, per JS über
   --mx/--my gesteuerte CSS-Variablen, kein Layout-Einfluss. */
.section--glow { position: relative; overflow: hidden; }
.section--glow::before {
  content: ""; position: absolute; z-index: 0; pointer-events: none;
  left: var(--mx, 50%); top: var(--my, 50%); width: 42rem; height: 42rem;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle, color-mix(in srgb, var(--accent) 16%, transparent) 0%, transparent 70%);
  opacity: 0; transition: opacity .4s ease;
}
.section--glow.is-glowing::before { opacity: 1; }
.section--glow > * { position: relative; z-index: 1; }

/* ---------- Vorher/Nachher-Bildregler ----------
   Zwei Fotos exakt gleicher Größe übereinander, ein <input type="range">
   zieht die Trennlinie. Ohne JS/CSS-only: fixer 50/50-Split (Regler ist
   dann nur optisch inert, nicht kaputt) — JS synchronisiert clip-path und
   Griff live mit dem Wert. Für echte Vorher/Nachher-Fotos, nie erfunden. */
.compare-slider {
  position: relative; aspect-ratio: 16 / 10; overflow: hidden;
  border-radius: var(--radius); user-select: none; touch-action: pan-y;
}
.compare-slider__before, .compare-slider__after {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block;
}
.compare-slider__before { clip-path: inset(0 50% 0 0); }
.compare-slider__label {
  position: absolute; top: .75rem; z-index: 1;
  font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .08em;
  color: #fff; background: rgba(0,0,0,.5); padding: .3rem .7rem; border-radius: 999px;
  pointer-events: none;
}
.compare-slider__label--before { left: .75rem; }
.compare-slider__label--after { right: .75rem; }
.compare-slider__handle {
  position: absolute; top: 0; bottom: 0; left: 50%; width: 3px; background: #fff;
  transform: translateX(-50%); box-shadow: 0 0 0 1px rgba(0,0,0,.15); pointer-events: none; z-index: 1;
}
.compare-slider__handle::after {
  content: "\21d4"; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 2.4rem; height: 2.4rem; border-radius: 50%; background: #fff; color: #18181b;
  display: flex; align-items: center; justify-content: center; font-size: 1.1rem;
  box-shadow: 0 4px 14px rgba(0,0,0,.25);
}
.compare-slider__range {
  position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: ew-resize;
  -webkit-appearance: none; appearance: none; z-index: 2;
}

/* ---------- 3D-Tilt-Karten ----------
   Karte kippt minimal Richtung Mauszeiger (Perspektive), federt beim
   Verlassen zurück. Nur Desktop/Maus. Sparsam einsetzen — für einzelne
   Bild-/Produktkarten, nicht für ganze Grids gleichzeitig. */
.tilt { transition: transform .2s ease-out; will-change: transform; }

/* ---------- Scroll-Fortschrittsleiste ----------
   Hauchdünner fixierter Balken oben, zeigt den Lesefortschritt der Seite.
   <div class="scroll-progress"></div> einmal direkt nach <body> einfügen. */
.scroll-progress {
  position: fixed; top: 0; left: 0; width: 100%; height: 3px;
  background: var(--accent); transform: scaleX(0); transform-origin: 0 0;
  z-index: 70; pointer-events: none;
}

/* ---------- Bottom-Dock-Navigation ----------
   Alternative zur klassischen Kopfzeile (.site-header): schwebende Pille
   unten, zentriert, statt oben fixiert. Passt zu One-Page-Portfolios/
   Agentur-Auftritten mit wenigen Ankern. Der Link zum aktuell sichtbaren
   Abschnitt wird per JS hervorgehoben (scroll-spy) — ohne JS bleiben alle
   Links normal klickbar, nur ohne aktive Markierung. Ersetzt .site-header,
   nicht zusätzlich zu ihr einsetzen. */
/* Bottom-Dock: Desktop-Alternative zur Kopfzeile. Auf Mobil ABGESCHALTET —
   dort läuft die Navigation ausnahmslos über das Burger-Menü. Das war auch
   die Ursache der Überlappung mit dem Entwurfs-Hinweis. */
.dock-nav { display: none; }
@media (min-width: 900px) {
.dock-nav {
  position: fixed; left: 50%; bottom: 1.25rem; transform: translateX(-50%);
  z-index: 60; display: flex; gap: .2rem; max-width: calc(100% - 2rem);
  overflow-x: auto; scrollbar-width: none;
  background: var(--bg); border-radius: 999px; padding: .3rem;
  box-shadow: 0 10px 30px rgba(0,0,0,.14), 0 0 0 1px color-mix(in srgb, var(--text) 10%, transparent);
}
.dock-nav::-webkit-scrollbar { display: none; }
.dock-nav a {
  display: inline-flex; align-items: center; white-space: nowrap; min-height: 40px;
  padding: .5rem 1.1rem; border-radius: 999px; font-weight: 700; font-size: .85rem;
  color: var(--text-dim); text-decoration: none; transition: background .2s, color .2s;
}
.dock-nav a:hover { color: var(--text); }
.dock-nav a.is-active { background: var(--accent); color: var(--accent-contrast); }
}

/* ---------- Scroll-Schärfe-Reveal ----------
   Statement/Bild kommt beim Scrollen unscharf-zu-scharf in den Fokus statt
   nur zu faden — wirkt filmischer als reines .reveal. Für einzelne
   Aussagen/Bilder, NICHT für Fließtext (Lesbarkeit während der Bewegung).
   Progressive enhancement: ohne JS/bei reduzierter Bewegung immer
   scharf & voll sichtbar. */
.blur-reveal { will-change: filter, opacity; }
@media (prefers-reduced-motion: reduce) { .blur-reveal { filter: none !important; opacity: 1 !important; } }

/* ---------- Punktraster-Hintergrund ----------
   Dezente Textur hinter Hero-Content/Cards, reines CSS, kein JS. Farbe
   folgt automatisch --text, funktioniert also auf hell und dunkel. */
.bg-dots {
  background-image: radial-gradient(color-mix(in srgb, var(--text) 18%, transparent) 1.5px, transparent 1.5px);
  background-size: 22px 22px;
}
/* Variante: Fadenkreuz-Raster statt Punkte — nüchterner/technischer,
   passt zu Kanzleien/Ingenieurbüros/Präzisionshandwerk. */
.bg-crosshair {
  background-image:
    linear-gradient(color-mix(in srgb, var(--text) 12%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--text) 12%, transparent) 1px, transparent 1px);
  background-size: 44px 44px;
  background-position: center;
}

/* ---------- Theme-Umschalter (Hell/Dunkel) ----------
   Setzt data-theme="light"/"dark" auf <html> und überschreibt damit
   gezielt die System-Einstellung — merkt sich die Wahl (localStorage).
   Setzt voraus, dass das Theme nach demselben Muster wie bei Artifacts
   aufgebaut ist: Basis-Palette auf :root, System-Dark über
   "@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) }",
   explizites Dark nochmal über ":root[data-theme=\"dark\"]" (siehe
   Theme-Vorgabe in Phase 2/3). Ohne JS bleibt der Button funktionslos,
   die Seite folgt einfach der System-Einstellung — kein Bruch. */
.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: 999px; flex: none;
  border: 1px solid color-mix(in srgb, var(--text) 18%, transparent);
  background: transparent; color: var(--text); cursor: pointer;
}
.theme-toggle svg { width: 18px; height: 18px; }
.theme-toggle__moon { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle__sun { display: none; }
  :root:not([data-theme="light"]) .theme-toggle__moon { display: block; }
}
:root[data-theme="dark"] .theme-toggle__sun { display: none; }
:root[data-theme="dark"] .theme-toggle__moon { display: block; }

/* ---------- Status-Punkt (verfügbar/online) ----------
   Kleiner pulsierender Punkt vor einer Verfügbarkeits-/Statusangabe
   ("2 Plätze frei im Juli", "Aktuell erreichbar"). Feste grüne Farbe
   (Semantik "verfügbar"), unabhängig von der Markenakzentfarbe — wie bei
   Ampelfarben üblich. **Nur bei echten, aktuellen Angaben**, nie als
   Deko-Attrappe (siehe Live-Mikro-Details-Regel in Phase 2). */
.status-dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  background: #22c55e; position: relative; flex: none;
}
.status-dot::after {
  content: ""; position: absolute; inset: -4px; border-radius: 50%;
  background: inherit; opacity: .5; animation: status-pulse 2s ease-out infinite;
}
@keyframes status-pulse { 0% { transform: scale(.6); opacity: .5; } 100% { transform: scale(1.8); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .status-dot::after { animation: none; display: none; } }

/* ---------- Anlass-Auswahl (Kontaktformular) ----------
   Kachel-Auswahl statt langweiligem <select> für "Worum geht es?" im
   Kontaktformular. Radio-Buttons visuell versteckt, Kachel reagiert auf
   :checked — reines CSS, keine Zeile JS. Funktioniert identisch ohne
   CSS (dann normale native Radios), also niemals kaputt. */
.anlass-waehler { display: flex; flex-wrap: wrap; gap: .6rem; }
.anlass-option { position: relative; }
.anlass-option input {
  position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer;
}
.anlass-option span {
  display: inline-flex; align-items: center; gap: .5rem; min-height: 44px;
  padding: .6rem 1.1rem; border-radius: 999px; border: 1.5px solid color-mix(in srgb, var(--text) 20%, transparent);
  font-weight: 600; font-size: .92rem; color: var(--text-dim); background: var(--bg);
  transition: border-color .15s, color .15s, background .15s;
}
.anlass-option input:checked + span {
  border-color: var(--accent); color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--bg));
}
.anlass-option input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- Wunschzeit-Chips (Terminanfrage) ----------
   Mehrfachauswahl "wann passt es Ihnen" als Chips statt Freitext/Dropdown
   — für Betriebe, die echte Terminvorschläge statt Live-Kalender wollen
   (kein Cal.com/Buchungssystem, nur eine strukturierte Angabe fürs
   Kontaktformular). Checkbox-basiert, reines CSS. */
.zeit-chips { display: flex; flex-wrap: wrap; gap: .5rem; }
.zeit-chip { position: relative; }
.zeit-chip input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
.zeit-chip span {
  display: inline-flex; align-items: center; min-height: 40px; padding: .45rem .9rem;
  border-radius: var(--radius); border: 1.5px dashed color-mix(in srgb, var(--text) 25%, transparent);
  font-size: .88rem; color: var(--text-dim); transition: border-color .15s, color .15s, background .15s, border-style .15s;
}
.zeit-chip input:checked + span {
  border-style: solid; border-color: var(--accent); color: var(--accent-contrast); background: var(--accent);
}
.zeit-chip input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- Live-Ortszeit ----------
   Zeigt die aktuelle Uhrzeit am Firmenstandort, aktualisiert sich selbst.
   Ohne JS steht einfach der statische Fallback-Text im Markup (z. B. der
   Ortsname) da — kein "--:--", kein Bruch. Wirkt lebendig/lokal verankert,
   nur bei echtem Standort einsetzen (siehe Live-Mikro-Details-Regel). */
.local-time { font-variant-numeric: tabular-nums; }

/* ---------- Eckklammern (Viewfinder-Rahmen) ----------
   Vier kleine Eckklammern um ein Element, wie ein Kamera-Fokusrahmen —
   gibt Fotos/Hero-Flächen einen präzisen, "ausgewählten" Akzent. Reines
   CSS, kein JS. Sparsam einsetzen (1 Element pro Seite), gut für
   Fotografie/Architektur/Präzisionshandwerk. Markup: 4 leere <span
   aria-hidden="true"> vor dem eigentlichen Inhalt (siehe blocks.html). */
.frame-corners { position: relative; }
.frame-corners > span {
  position: absolute; width: 1.4rem; height: 1.4rem; pointer-events: none; z-index: 1;
  border: 2px solid var(--accent);
}
.frame-corners > span:nth-of-type(1) { top: -.5rem; left: -.5rem; border-right: 0; border-bottom: 0; }
.frame-corners > span:nth-of-type(2) { top: -.5rem; right: -.5rem; border-left: 0; border-bottom: 0; }
.frame-corners > span:nth-of-type(3) { bottom: -.5rem; left: -.5rem; border-right: 0; border-top: 0; }
.frame-corners > span:nth-of-type(4) { bottom: -.5rem; right: -.5rem; border-left: 0; border-top: 0; }

/* ---------- Film-Grain-Textur ----------
   Feines Rauschen über dunklen (oder hellen) Flächen statt flacher
   Farbfläche — wirkt hochwertiger/fotografischer. Reines SVG-Rauschen
   als Daten-URI, kein externer Request, kein Bild-Download. Sehr dezent
   dosiert (Opacity), overlay-Blend sorgt dafür, dass es auf hell wie
   dunkel funktioniert. Rein dekorativ, nimmt keinen Platz im Layout ein. */
/* ACHTUNG — .grain belegt ::after. Bekommt dieselbe Sektion ein zweites
   Overlay (Foto-Verlauf o. Ä.), darf dieses NICHT ebenfalls ::after nutzen:
   zwei Regeln für dasselbe Pseudo-Element mischen ihre Eigenschaften, das
   zweite Overlay erbt dann stillschweigend die opacity:.05 von hier und
   dunkelt praktisch nicht ab — der Text wird unlesbar, ohne dass im Code
   ein Fehler sichtbar wäre. Zweites Overlay deshalb auf ::before legen.
   Betrifft auch die fertigen Bausteine .chapter--photo, .hero-atmo,
   .opening und .portal__panel — die nutzen alle ::after. */
.grain { position: relative; isolation: isolate; }
.grain::after {
  content: ""; position: absolute; inset: 0; z-index: 5; pointer-events: none;
  opacity: .05; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}

/* ---------- Schwebende Foto-Badges (Trust-Pills auf Portrait/Hero-Foto) ----------
   Kleine, freigestellte Kennzahl-Pillen, die auf einem Portrait- oder
   Team-Foto "schweben" statt in einer separaten Kennzahlen-Sektion zu
   stehen — bindet echte Zahlen (Jahre Erfahrung, Kundenanzahl) direkt an
   das Gesicht dahinter. Reines CSS, kein JS. Nur echte, belegbare Angaben,
   nie erfinden. Sparsam: max. 2 Badges pro Foto, sonst wirkt es unruhig. */
.foto-badges { position: relative; display: inline-block; line-height: 0; }
.foto-badges img { display: block; width: 100%; border-radius: var(--radius); }
.foto-badges__badge {
  position: absolute; z-index: 2;
  display: inline-flex; align-items: center;
  background: var(--bg); color: var(--text);
  padding: .6rem 1.1rem; border-radius: 999px;
  font-size: .85rem; font-weight: 600; line-height: 1.2; white-space: nowrap;
  box-shadow: 0 10px 28px rgba(0, 0, 0, .18);
}
.foto-badges__badge--tr { top: 1.25rem; right: -1rem; }
.foto-badges__badge--br { bottom: 1.25rem; right: -1rem; }
.foto-badges__badge--bl { bottom: 1.25rem; left: -1rem; }
.foto-badges__badge--tl { top: 1.25rem; left: -1rem; }
@media (max-width: 640px) {
  .foto-badges__badge--tr, .foto-badges__badge--tl { left: 1rem; right: auto; top: 1rem; }
  .foto-badges__badge--br, .foto-badges__badge--bl { left: 1rem; right: auto; bottom: 1rem; }
}

/* ---------- Video-Teaser mit Lightbox (ohne JS, reines :target) ----------
   Schwebende Abspielen-Pille über einem Vorschaubild statt eines
   eingebetteten <iframe> mit externem Anbieter (kein DSGVO-Cookie-Problem).
   Klick öffnet ein Vollbild-Modal per CSS-:target-Technik, komplett ohne
   JavaScript. Nur mit einem echten, selbst gehosteten Vorstellungsvideo
   einsetzen (z. B. kurzer Werkstatt-/Kanzlei-Rundgang), nie als Platzhalter. */
.video-teaser { position: relative; display: block; line-height: 0; }
.video-teaser__poster { display: block; width: 100%; border-radius: var(--radius); }
.video-teaser__play {
  position: absolute; right: 1.25rem; bottom: 1.25rem; z-index: 2;
  display: inline-flex; align-items: center; gap: .6rem;
  background: var(--bg); color: var(--text); text-decoration: none;
  padding: .5rem 1.1rem .5rem .5rem; border-radius: 999px;
  font-size: .85rem; font-weight: 600; line-height: 1.2;
  box-shadow: 0 10px 28px rgba(0, 0, 0, .22);
  transition: transform .2s ease;
}
.video-teaser__play:hover { transform: scale(1.04); }
.video-teaser__play-icon { position: relative; width: 2.25rem; height: 2.25rem; border-radius: 50%; background: var(--accent); flex-shrink: 0; }
.video-teaser__play-icon::before {
  content: ""; position: absolute; top: 50%; left: 57%; transform: translate(-50%, -50%);
  border-style: solid; border-width: .5rem 0 .5rem .8rem;
  border-color: transparent transparent transparent var(--accent-contrast);
}
.video-teaser__modal {
  position: fixed; inset: 0; z-index: 100; padding: 2rem;
  display: flex; align-items: center; justify-content: center;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .25s ease;
}
.video-teaser__modal:target { opacity: 1; visibility: visible; pointer-events: auto; }
.video-teaser__backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, .75); }
.video-teaser__modal-box { position: relative; width: 100%; max-width: 900px; }
.video-teaser__modal-box video { display: block; width: 100%; border-radius: var(--radius); }
.video-teaser__close {
  position: absolute; top: -2.75rem; right: 0; z-index: 1;
  width: 2.25rem; height: 2.25rem; display: flex; align-items: center; justify-content: center;
  color: #fff; font-size: 1.75rem; line-height: 1; text-decoration: none;
}
@media (prefers-reduced-motion: reduce) { .video-teaser__modal { transition: none; } }

/* ---------- Prozedurale 3D-Szene (scroll-gekoppelt) ----------
   Braucht zusätzlich <script type="module" src="assets/scene3d.js"></script>
   und assets/vendor/three.module.min.js — NICHT in base.js enthalten (siehe
   Kommentar dort). Ohne JS/WebGL/bei reduced-motion bleibt das Fallback-Bild
   stehen, die Sektion ist also nie leer. */
.scene-3d { position: relative; aspect-ratio: 1 / 1; }
.scene-3d canvas { display: block; width: 100%; height: 100%; }
.scene-3d__fallback { width: 100%; height: 100%; object-fit: contain; }
.scene-3d.is-live .scene-3d__fallback { display: none; }

/* ============================================================
   SEITENARCHITEKTUREN (Achse 1 des Design-Registers)
   ------------------------------------------------------------
   Jede der in SKILL.md Phase 2 gelisteten Architekturen hat hier ihr
   Layout-Fundament, damit sie genauso billig zu bauen ist wie der
   klassische Vertikal-Stapel — sonst gewinnt immer der Stapel, nur weil
   er der bequemste Weg ist. Markup-Skelette dazu: blocks.html, Abschnitt
   "SEITENARCHITEKTUREN". Pro Projekt genau EINE davon wählen.
   Alle Architekturen sind reines CSS (Ausnahme: Scroll-Spy der
   Seitenleisten-Navigation, siehe base.js) und mobil-tauglich.
============================================================ */

/* ---------- A) Persistente Seitenleiste ----------
   Feste schmale Spalte links (Logo/Navigation/Kontakt), rechts scrollt der
   Inhalt. Passt zu Kanzleien/Beratungen mit viel Text. Ersetzt .site-header,
   nicht zusätzlich einsetzen.

   MOBIL: Die Leiste klappt zwar zu einem Kopfbereich auf, aber die Navigation
   läuft auch hier über das Burger-Menü — die Regel gilt ausnahmslos für jede
   Architektur. Bei fünf Punkten brach die aufgeklappte Leiste sonst genauso
   um wie die normale Kopfzeile. Das Skelett in blocks.html trägt deshalb
   denselben .nav-toggle, und die Liste heißt dort zusätzlich .site-nav. */
.layout-sidebar__aside {
  padding: var(--pad);
  border-bottom: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
  background: var(--bg);
}
.layout-sidebar__brand {
  font-family: var(--display); font-weight: 800; font-size: 1.5rem;
  color: var(--text); text-decoration: none; display: inline-block;
}
.layout-sidebar__nav ul { list-style: none; display: flex; flex-wrap: wrap; gap: .25rem 1.25rem; margin-top: 1rem; }
.layout-sidebar__nav a {
  display: inline-flex; align-items: center; min-height: 44px;
  color: var(--text-dim); text-decoration: none; font-weight: 600; font-size: .95rem;
  transition: color .2s;
}
.layout-sidebar__nav a:hover, .layout-sidebar__nav a.is-active { color: var(--text); }
.layout-sidebar__foot { display: none; }

/* Aufgeklappte Leiste = Kopfzeilen-Anordnung, damit der Burger rechts sitzt. */
@media (max-width: 899.98px) {
  .layout-sidebar__aside {
    display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  }
  .js-nav .layout-sidebar__nav ul { margin-top: 0; }
}
@media (min-width: 1000px) {
  .layout-sidebar { display: grid; grid-template-columns: minmax(15rem, var(--sidebar-w, 26%)) minmax(0, 1fr); align-items: start; }
  .layout-sidebar__aside {
    position: sticky; top: 0; height: 100vh; height: 100svh;
    display: flex; flex-direction: column; gap: 2rem;
    border-bottom: 0; border-right: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
    padding: clamp(1.75rem, 3vw, 2.75rem);
  }
  .layout-sidebar__nav ul { flex-direction: column; gap: 0; margin-top: 0; }
  /* Aktiver Punkt bekommt einen Akzent-Marker statt nur Farbwechsel. */
  .layout-sidebar__nav a { position: relative; padding-left: 1.1rem; }
  .layout-sidebar__nav a::before {
    content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
    width: .4rem; height: .4rem; border-radius: 50%; background: var(--accent);
    opacity: 0; transition: opacity .2s;
  }
  .layout-sidebar__nav a.is-active::before { opacity: 1; }
  .layout-sidebar__foot { display: block; margin-top: auto; color: var(--text-dim); font-size: .9rem; }
  .layout-sidebar__foot a { color: inherit; text-underline-offset: 3px; }
  .layout-sidebar__main { min-width: 0; }
  /* Der Inhaltsbereich hat schon die Leiste neben sich — der innere
     Container darf deshalb nicht nochmal auf volle Seitenbreite zielen. */
  .layout-sidebar__main .container { max-width: 60rem; }
}

/* ---------- B) Kapitel-Sequenz ----------
   3–5 fast bildschirmfüllende "Momente" statt einer langen hellen Fläche mit
   einer Ausnahme-Sektion. Jedes Kapitel setzt seinen Ton per Inline-Variablen
   im Markup — weil es die Theme-Variablen selbst überschreibt, folgen Buttons,
   Kicker und Trennlinien automatisch mit:
     <section class="chapter" style="--chapter-bg:#12211C; --text:#fff; --text-dim:rgba(255,255,255,.72)">
   Gedeckte Markenvarianten verwenden, keine Neonfarben.
   ACHTUNG: Wer in einem dunklen Kapitel zusätzlich --accent aufhellt, MUSS
   --accent-contrast mitsetzen (z. B. auf den dunklen Kapitelgrund) — sonst
   steht weißer Buttontext auf hellem Akzent und der Kontrast fällt durch.
   Das ist der häufigste Fehler bei dieser Architektur. */
.chapter {
  background: var(--chapter-bg, var(--bg));
  color: var(--text);
  min-height: 86vh; min-height: 86svh;
  display: flex; align-items: center;
  padding-block: clamp(4rem, 9vw, 7.5rem);
}
/* PFLICHT bei allen Flex-Sektionen: ein .container als Flex-Item schrumpft
   sonst auf seine Inhaltsbreite (shrink-to-fit) und wird zentriert — die
   Sektion bekommt dadurch eine ANDERE linke Kante als der Rest der Seite.
   Das ist als uneinheitlicher Seitenabstand sichtbar und schwer zu finden. */
.chapter > .container { width: 100%; }
/* Ehemals Kapitelnummer. Jetzt reine Kapitel-BEZEICHNUNG, ohne Zahl. */
.chapter__num {
  font-family: var(--display); font-weight: 700; font-size: .8rem;
  letter-spacing: .22em; color: var(--accent); margin-bottom: 1rem;
  display: flex; align-items: center; gap: .7rem; text-transform: uppercase;
}
.chapter__num::before {
  content: ""; flex: 0 0 auto; width: 2rem; height: 2px; background: currentColor;
}
/* Kapitel mit Vollbild-Foto als Grund: Verlauf großzügig über die ganze
   Fläche ziehen, damit auch ein dreizeilig umbrechender Titel auf dem Handy
   nicht in eine helle Bildstelle wächst. */
.chapter--photo { position: relative; overflow: hidden; color: #fff; }
.chapter--photo > img, .chapter--photo > picture > img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 0;
}
/* Deckkraft nirgends unter ~0.68: der Text sitzt vertikal zentriert, also
   genau dort, wo ein Verlauf üblicherweise am schwächsten ist. Bei 0.3 wäre
   weißer Text auf einem sonnigen Foto rechnerisch bei 1.9:1 — unlesbar, und
   Lighthouse meldet es nicht, weil es Text auf Bildern nicht bewerten kann.
   Faustregel: 0.68 ergibt auch über reinweißem Foto noch ~6.9:1. */
.chapter--photo::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(0,0,0,.74) 0%, rgba(0,0,0,.68) 40%, rgba(0,0,0,.86) 100%);
}
.chapter--photo > .container { position: relative; z-index: 2; text-shadow: 0 2px 18px rgba(0,0,0,.5); }

/* ---------- C) Durchgehender Asymmetrie-Split ----------
   Nicht nur der Hero: JEDE Sektion behält denselben schmal/breit-Split
   (Kicker/Meta links schmal, Inhalt rechts breit) als visuelle Leitlinie über
   die ganze Seite. Die Meta-Spalte bleibt am Desktop stehen, während der
   Inhalt daran vorbeiscrollt. */
.split-row { display: grid; gap: .75rem; }
.split-row__meta { color: var(--text-dim); font-size: .85rem; letter-spacing: .12em; text-transform: uppercase; font-weight: 700; }
.split-row__body { min-width: 0; }
@media (min-width: 880px) {
  .split-row { grid-template-columns: minmax(0, var(--split-meta, 13rem)) minmax(0, 1fr); gap: clamp(2.5rem, 6vw, 5rem); }
  .split-row__meta { position: sticky; top: 6rem; align-self: start; }
}

/* ---------- D) Dossier-Sequenz ----------
   Fortlaufende Kartei statt luftiger Cards: jeder Eintrag bekommt eine
   schmale Kennzeichnungsleiste ("Aktendeckel": Nummer + 1–2 Meta-Tags,
   Trennlinie), direkt gefolgt von großer Überschrift, kurzem Text und Bild.
   Die Blöcke stapeln sich dicht — gut für viele vergleichbare Einträge
   (Leistungen, Referenzen, Ablaufschritte). */
.dossier__item {
  border-top: 1px solid color-mix(in srgb, var(--text) 16%, transparent);
  padding-block: clamp(2rem, 5vw, 3.25rem);
}
.dossier__item:last-child { border-bottom: 1px solid color-mix(in srgb, var(--text) 16%, transparent); }
.dossier__tab {
  display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap;
  font-size: .74rem; letter-spacing: .16em; text-transform: uppercase;
  font-weight: 700; color: var(--text-dim); margin-bottom: 1.1rem;
}
/* Ehemals Aktenzeichen ("MOD-01"). Jetzt Schlagwort statt Zaehler. */
.dossier__num {
  color: var(--accent); font-family: var(--display);
  text-transform: uppercase; letter-spacing: .1em; font-size: .82rem;
}
.dossier__tags { margin-left: auto; display: flex; gap: 1rem; flex-wrap: wrap; }
.dossier__body { display: grid; gap: 1.25rem; }
.dossier__body h3 { font-family: var(--display); font-size: clamp(1.6rem, 3.6vw, 2.5rem); line-height: 1.1; }
@media (min-width: 880px) {
  .dossier__body { grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr); gap: clamp(2rem, 4vw, 3.5rem); align-items: start; }
}

/* ---------- E) Portal-Auswahl-Start ----------
   Statt eines einzigen Heldenmoments zwei (selten drei) gleich große
   Bild-Panels nebeneinander, die in getrennte Angebotsbereiche führen.
   NUR bei wirklich getrennten Bereichen/Zielgruppen einsetzen (Privat/
   Gewerbe, zwei Standorte, Neubau/Sanierung) — sonst wirkt es unmotiviert.
   Spaltenzahl über --portal-cols (Default 2). */
.portal { display: grid; grid-template-columns: 1fr; gap: 2px; }
@media (min-width: 820px) {
  .portal { grid-template-columns: repeat(var(--portal-cols, 2), 1fr); min-height: 88vh; min-height: 88svh; }
}
.portal__panel {
  position: relative; overflow: hidden; text-decoration: none; color: #fff;
  display: flex; align-items: flex-end;
  min-height: 46vh; min-height: 46svh;
  padding: clamp(1.5rem, 3.5vw, 3rem);
}
.portal__panel > img, .portal__panel > picture > img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 0;
  transition: transform .7s cubic-bezier(.2,.8,.2,1);
}
/* Verlauf bewusst großzügig und unten sehr kräftig: die Panels bekommen
   im Zweifel ein helles Stockfoto, und der Text sitzt am unteren Rand —
   ein knapp bemessener Verlauf liefert dort grauen Text auf hellem Grund. */
.portal__panel::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(0,0,0,.42) 0%, rgba(0,0,0,.22) 28%, rgba(0,0,0,.72) 72%, rgba(0,0,0,.92) 100%);
}
.portal__panel > div { position: relative; z-index: 2; text-shadow: 0 2px 16px rgba(0,0,0,.55); }
.portal__panel h2 { font-family: var(--display); font-size: clamp(1.9rem, 4vw, 3.2rem); line-height: 1.05; }
.portal__panel p { color: rgba(255,255,255,.86); margin-top: .5rem; max-width: 34ch; }
.portal__panel:hover > img, .portal__panel:hover > picture > img,.portal__panel:focus-visible > img, .portal__panel:focus-visible > picture > img { transform: scale(1.045); }
@media (prefers-reduced-motion: reduce) {.portal__panel > img, .portal__panel > picture > img { transition: none; } }

/* ---------- F) Ultra-minimaler Auftakt ----------
   Startseite besteht fast nur aus einem großen Foto + Wortmarke, der Inhalt
   folgt erst nach explizitem Scroll-Anker. Nur bei wenig Textinhalt und
   starkem Bildmaterial. Gehört zusammen mit .wordmark-echo als Seitenabschluss
   statt eines mehrspaltigen Footers. */
.opening {
  position: relative; overflow: hidden;
  min-height: 100vh; min-height: 100svh;
  display: grid; place-items: center; text-align: center;
}
.opening > img, .opening > picture > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 0; }
/* Wortmarke und Unterzeile sitzen mittig (place-items: center) — der Verlauf
   darf genau dort nicht einbrechen. 0.25 in der Mitte ergäbe über einem hellen
   Foto 1.7:1; die Unterzeile ist normaler Text und braucht 4.5:1. */
.opening::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(0,0,0,.5) 0%, rgba(0,0,0,.58) 45%, rgba(0,0,0,.8) 100%);
}
.opening__inner { position: relative; z-index: 2; padding: var(--pad); color: #fff; text-shadow: 0 2px 24px rgba(0,0,0,.5); }
.opening__mark {
  font-family: var(--display); font-weight: 800;
  font-size: clamp(2.6rem, 11vw, 8rem); line-height: .95; letter-spacing: -.02em;
}
.opening__sub { margin-top: 1rem; font-size: clamp(1rem, 2vw, 1.25rem); color: rgba(255,255,255,.88); }
.opening__scroll {
  position: absolute; left: 50%; bottom: 1.75rem; transform: translateX(-50%); z-index: 3;
  display: inline-flex; flex-direction: column; align-items: center; gap: .4rem;
  color: #fff; text-decoration: none; font-size: .78rem; letter-spacing: .16em; text-transform: uppercase;
  min-height: 44px; text-shadow: 0 2px 12px rgba(0,0,0,.7);
}
.opening__scroll::after {
  content: ""; width: 1px; height: 2rem;
  background: linear-gradient(180deg, #fff, transparent);
  animation: opening-hint 2.2s ease-in-out infinite;
}
@keyframes opening-hint { 0%, 100% { opacity: .35; transform: scaleY(.7); transform-origin: top; } 50% { opacity: 1; transform: scaleY(1); transform-origin: top; } }
/* Großes Wortmarken-Echo als Seitenabschluss. Bewusst am unteren Rand
   angeschnitten (overflow: hidden + negativer Abstand), damit es wie ein
   grafischer Schlusspunkt wirkt und nicht wie eine weitere Überschrift. */
.wordmark-echo { overflow: hidden; padding-top: clamp(3rem, 8vw, 6rem); }
.wordmark-echo > span {
  display: block; font-family: var(--display); font-weight: 800;
  font-size: clamp(3rem, 17vw, 13rem); line-height: .78; letter-spacing: -.03em;
  text-align: center; color: var(--text); margin-bottom: -.14em;
}

/* ---------- G) Organischer Streu-Layout ----------
   Karten/Fotos bewusst leicht versetzt statt exakt im Raster — wirkt
   handgemacht statt Baukasten. Dezent halten (Versatz in rem, nicht in
   Rotation), sparsam für Referenzen/Team mit wenigen Fotos einsetzen.
   Die Vignette entsteht über eine weiche Maske statt eines harten Beschnitts. */
.scatter { display: grid; gap: clamp(1.5rem, 4vw, 2.75rem); grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); align-items: start; }
.scatter__item { text-align: center; }
.scatter__item img {
  width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 50%;
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 56%, transparent 76%);
  mask-image: radial-gradient(circle at 50% 50%, #000 56%, transparent 76%);
}
.scatter__item strong { display: block; margin-top: .75rem; font-family: var(--display); }
.scatter__item small { color: var(--text-dim); }
@media (min-width: 840px) {
  .scatter__item:nth-child(2n) { transform: translateY(2.75rem); }
  .scatter__item:nth-child(3n) { transform: translateY(-1.25rem) scale(1.07); }
}

/* ---------- Hintergrund-Wortmarke im Hero ----------
   Firmenname als sehr große Display-Typo mit niedrigem Kontrast hinter dem
   Hero-Inhalt, teils vom Rand angeschnitten. Nur bei kurzen, gut lesbaren
   Markennamen. Rein dekorativ (aria-hidden), nimmt keinen Platz ein und darf
   die eigentliche Headline nie stören — deshalb sehr niedriger Kontrast.
   Gehört in einen Container mit position: relative. */
.hero-wordmark {
  position: absolute; z-index: 0; pointer-events: none; user-select: none;
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  font-family: var(--display); font-weight: 800; white-space: nowrap;
  font-size: clamp(6rem, 26vw, 22rem); line-height: 1; letter-spacing: -.04em;
  color: color-mix(in srgb, var(--text) 8%, transparent);
}
.hero-wordmark--edge { left: auto; right: -.08em; top: auto; bottom: -.22em; transform: none; }
/* Auf schmalen Screens liegt die Marke fast deckungsgleich unter der
   Headline und macht sie unruhig — dort deutlich weiter zurücknehmen. */
@media (max-width: 700px) { .hero-wordmark { color: color-mix(in srgb, var(--text) 5%, transparent); } }

/* ============================================================
   HERO-MUSTER (Achse 2 des Design-Registers)
   ------------------------------------------------------------
   Die drei Grundformen (Split / Zentriert+Band / Vollbild-Overlay) stehen
   als Skelett in blocks.html (HERO A–C). Hier liegen die aufwendigeren
   Muster, die in SKILL.md Phase 2 beschrieben sind — sie brauchen echtes
   Layout-CSS und wurden deshalb bisher nie gewählt.
   EIN Hero-Muster pro Projekt. Markup: blocks.html, Abschnitt "HERO-MUSTER".
============================================================ */

/* ---------- Nahtstellen-Wortmarke (vgl. apf-international.nl) ----------
   Firmenname sehr groß auf der Kante zwischen leerer Fläche und Foto: obere
   Buchstabenhälfte steht auf dem Seitenhintergrund, untere Hälfte liegt im
   Bild. Der Zweifarb-Effekt entsteht über einen harten Verlauf in der
   Schriftfüllung — eine zweite Textkopie wäre für Screenreader doppelt.
   Nur bei kurzen Markennamen (bis ~10 Zeichen), sonst wird die Marke winzig. */
.hero-seam { position: relative; }
/* Kein overflow: hidden — die obere Markenhälfte ragt absichtlich über die
   Bildkante hinaus, genau das ist der Nahtstellen-Effekt. */
.hero-seam__photo { position: relative; aspect-ratio: 16 / 7; }
.hero-seam__photo img { width: 100%; height: 100%; object-fit: cover; }
/* Die untere Markenhälfte ist weiß und liegt im oberen Bildstreifen — auf
   einem hellen Foto verschwindet sie sonst spurlos. Der Verlauf dunkelt
   genau diesen Streifen ab und macht das Muster unabhängig vom Motiv. */
.hero-seam__photo::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 42%; z-index: 1;
  background: linear-gradient(180deg, rgba(0,0,0,.5) 0%, rgba(0,0,0,.28) 45%, transparent 100%);
}
.hero-seam__mark {
  position: absolute; left: 50%; top: 0; transform: translate(-50%, -50%);
  z-index: 2; width: max-content; max-width: 96vw; pointer-events: none;
  font-family: var(--display); font-weight: 800; line-height: .8;
  font-size: clamp(2.75rem, 15vw, 12rem); letter-spacing: -.03em;
  color: var(--text);
}
/* color: transparent erst setzen, wenn die Schriftfüllung wirklich greift —
   sonst wäre die Marke in alten Browsern schlicht unsichtbar. */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .hero-seam__mark {
    background-image: linear-gradient(180deg, var(--text) 0 50%, #fff 50% 100%);
    -webkit-background-clip: text; background-clip: text; color: transparent;
  }
}

/* ---------- Kreisausschnitt-Hero (vgl. j3facades.co.uk) ----------
   Ein großer heller Kreis liegt im Vollbild-Foto und trägt dort Eyebrow,
   Headline und CTA — punktuelle statt flächiger Lesbarkeit, das Foto bleibt
   unangetastet hell. Auf schmalen Screens fasst ein Kreis zu wenig Text,
   deshalb wird er dort zur abgerundeten Fläche: Lesbarkeit vor Form. */
.hero-circle { position: relative; overflow: hidden; display: grid; align-items: center; min-height: 82vh; min-height: 82svh; }
.hero-circle > img, .hero-circle > picture > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 0; }
.hero-circle__disc {
  position: relative; z-index: 2; margin: var(--pad);
  background: var(--bg); color: var(--text);
  border-radius: var(--radius); padding: clamp(1.75rem, 5vw, 3rem);
  display: flex; flex-direction: column; justify-content: center;
}
@media (min-width: 820px) {
  .hero-circle { justify-items: var(--circle-side, end); }
  .hero-circle__disc {
    border-radius: 50%; aspect-ratio: 1; width: min(42vw, 30rem);
    text-align: center; align-items: center;
    padding: clamp(2.5rem, 5vw, 4rem); margin: clamp(1.5rem, 4vw, 4rem);
  }
  .hero-circle__disc > * { max-width: 26ch; }
}

/* ---------- Hero als eingerahmte Karte (vgl. titanintake.com) ----------
   Das Hero-Foto sitzt in einer stark abgerundeten Karte mit sichtbarem
   Seitenhintergrund ringsherum statt randlos bis zum Viewport-Rand — wirkt
   aufgeräumter/produkthafter. Die Vorteils-Pillen sitzen auf der unteren
   Kartenkante; sie liegen bewusst im normalen Fluss mit negativem Abstand
   statt absolut positioniert, damit sie auf dem Handy nichts überdecken. */
.hero-framed { padding: clamp(.5rem, 1.6vw, 1.25rem); }
.hero-framed__card {
  position: relative; overflow: hidden;
  border-radius: clamp(1rem, 3vw, 2rem);
  min-height: 64vh; min-height: 64svh;
  display: flex; align-items: flex-end;
  padding: clamp(1.5rem, 4vw, 3.5rem);
  color: #fff;
}
.hero-framed__card > img, .hero-framed__card > picture > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 0; }
.hero-framed__card::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(0,0,0,.4) 0%, rgba(0,0,0,.2) 30%, rgba(0,0,0,.7) 75%, rgba(0,0,0,.88) 100%);
}
.hero-framed__card > .container, .hero-framed__card > div { position: relative; z-index: 2; width: 100%; text-shadow: 0 2px 16px rgba(0,0,0,.5); }
.hero-framed__pills {
  position: relative; z-index: 3; margin: -1.4rem auto 0;
  display: flex; flex-wrap: wrap; justify-content: center; gap: .5rem;
  padding-inline: var(--pad);
}
.hero-framed__pills > span {
  background: var(--bg); color: var(--text);
  border-radius: 999px; padding: .55rem 1.1rem;
  font-size: .85rem; font-weight: 600; line-height: 1.2;
  box-shadow: 0 8px 24px rgba(0,0,0,.14);
}

/* ---------- Portrait vor Farbkreis-Cluster (vgl. kraaccountancy.co.uk) ----------
   Freigestelltes Portrait vor zwei bis drei sanft überlappenden Kreisflächen
   in verwandten Markentönen statt in einem rechteckigen Fotorahmen — wirkt
   persönlicher/organischer. Für clean & corporate bewusst GEDECKT halten:
   die Töne entstehen aus --accent gemischt mit dem Seitenhintergrund, nicht
   aus Signalfarben. Gut für Einzelunternehmer:innen mit starkem Portrait. */
.hero-orbs { position: relative; display: block; max-width: 34rem; margin-inline: auto; }
.hero-orbs > span { position: absolute; border-radius: 50%; z-index: 0; }
/* Die Kreise ragen bewusst über die Bildkanten hinaus: bei einem sauber
   freigestellten Portrait entsteht so der Cluster-Eindruck, und selbst wenn
   das Bild doch rechteckig ist, bleibt das Muster sichtbar statt verdeckt. */
.hero-orbs > span:nth-of-type(1) { width: 84%; aspect-ratio: 1; left: 8%;   top: 2%;  background: color-mix(in srgb, var(--accent) 26%, var(--bg)); }
.hero-orbs > span:nth-of-type(2) { width: 48%; aspect-ratio: 1; left: -9%;  top: 40%; background: color-mix(in srgb, var(--accent) 44%, var(--bg)); }
.hero-orbs > span:nth-of-type(3) { width: 36%; aspect-ratio: 1; right: -7%; top: 10%; background: color-mix(in srgb, var(--accent) 16%, var(--bg)); }
.hero-orbs > img, .hero-orbs > picture > img { position: relative; z-index: 1; width: 100%; }

/* ---------- Atmosphären-Hero mit scharfem Insert (vgl. schyns.de) ----------
   Stark unscharfes Foto füllt die Fläche als reiner Stimmungsträger, ein
   kleines scharfes Detailfoto liegt als "Beweisbild" darin. Löst den Fall,
   dass ein einzelnes scharfes Foto zu nüchtern wirkt — und der Weichzeichner
   nimmt dem Hintergrund gleichzeitig die Kontrastprobleme unter dem Text.
   scale(1.08) verhindert die hellen Ränder, die Blur an den Bildkanten
   erzeugt. Das unscharfe Bild ist dekorativ (alt=""), das Insert nicht. */
.hero-atmo { position: relative; overflow: hidden; display: flex; align-items: center; min-height: 78vh; min-height: 78svh; }
.hero-atmo > img, .hero-atmo > picture > img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 0;
  filter: blur(22px) saturate(1.15); transform: scale(1.08);
}
/* Der Weichzeichner mittelt die Helligkeit, er dunkelt aber NICHT ab: ein
   sonniges Motiv bleibt auch unscharf hell. Deshalb hier ebenfalls mindestens
   ~0.58 (ergibt über weissem Foto noch ~5.2:1). */
.hero-atmo::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(0,0,0,.58) 0%, rgba(0,0,0,.66) 100%);
}
/* width: 100% aus demselben Grund wie bei .chapter — ohne das rutscht der
   Hero-Text nach innen und sitzt nicht auf der Kante des übrigen Layouts. */
.hero-atmo > .container { position: relative; z-index: 2; width: 100%; color: #fff; text-shadow: 0 2px 18px rgba(0,0,0,.5); }
/* Das Insert liegt bewusst IM Grid und nicht absolut über dem Text: absolut
   positioniert kollidiert es zwangsläufig mit Headline, Fließtext oder CTA,
   sobald der Text breit genug wird oder der Viewport schmaler. Als eigene
   Spalte kann es den Text konstruktiv nicht mehr überdecken. Der schwebende
   Charakter bleibt über Radius und Schatten erhalten.
   Markup: das <figure class="hero-atmo__insert"> gehört IN den .container. */
@media (min-width: 900px) {
  .hero-atmo > .container { display: grid; grid-template-columns: 1.4fr .6fr; align-items: center; gap: clamp(2rem, 5vw, 4rem); }
}
.hero-atmo__insert {
  overflow: hidden; margin: 2rem 0 0;
  width: 100%; max-width: 20rem; aspect-ratio: 4 / 5;
  border-radius: var(--radius); box-shadow: 0 14px 40px rgba(0,0,0,.35);
}
@media (min-width: 900px) { .hero-atmo__insert { margin: 0; justify-self: end; } }
.hero-atmo__insert img { width: 100%; height: 100%; object-fit: cover; }

/* ---------- News-Teaser in der Bildecke (vgl. praxis4.vercel.app) ----------
   Kleine Karte mit Vorschaubild und Anreißer in der unteren Ecke eines
   Foto-Heros — ersetzt eine eigene News-Sektion. NUR mit echten, aktuellen
   Inhalten (Jubiläum, Aktion, Meldung), nie erfunden. Modifier für jeden
   Hero mit position: relative. Auf dem Handy über die volle Breite, weil
   eine Eckkarte dort das halbe Bild verdecken würde. */
.hero-news {
  position: absolute; z-index: 3; bottom: 1rem; left: 1rem; right: 1rem;
  display: flex; align-items: center; gap: .8rem;
  background: var(--bg); color: var(--text); text-decoration: none;
  border-radius: var(--radius); padding: .6rem;
  box-shadow: 0 12px 34px rgba(0,0,0,.22);
  transition: transform .2s ease;
}
.hero-news:hover { transform: translateY(-2px); }
.hero-news img { width: 3.4rem; height: 3.4rem; object-fit: cover; border-radius: calc(var(--radius) * .5); flex: none; }
.hero-news strong { display: block; font-size: .92rem; line-height: 1.3; }
.hero-news small { color: var(--accent); font-weight: 700; font-size: .78rem; }
@media (min-width: 720px) {
  .hero-news { left: auto; right: clamp(1.25rem, 3vw, 2.5rem); bottom: clamp(1.25rem, 3vw, 2.5rem); max-width: 21rem; }
}

/* ---------- Beweis-Hero: zweifarbige Headline + Fotoreihe (vgl. philsuttonlandscapes.co.uk) ----------
   Die Headline hat zwei Kontraststufen (Hauptaussage volltonig, Kontextzeile
   blass), direkt darunter beginnt schon eine schmale Reihe quadratischer
   Projektfotos statt einer eigenen Referenzen-Sektion weiter unten — bindet
   den Beweis unmittelbar ans Versprechen. Gut für Handwerk/Garten-/Baubetriebe
   mit vielen kleinen Vorher-Fertig-Fotos. Die Reihe scrollt bewusst
   horizontal (wie .swipe), das ist kein Layout-Unfall. */
/* 60 % ist bewusst kalibriert, nicht geschätzt: darunter reißt der Kontrast
   der Kontextzeile unter 4.5:1 (von Lighthouse als Fehler gemeldet), darüber
   verliert sie den blassen Charakter. Nicht "schöner" (blasser) machen. */
.hero-proof__title em { font-style: normal; display: block; color: color-mix(in srgb, var(--text) 60%, transparent); }
.hero-proof__strip {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(8.5rem, 1fr);
  gap: clamp(.5rem, 1.5vw, 1rem); overflow-x: auto; scrollbar-width: none;
  scroll-snap-type: x proximity; margin-top: clamp(1.5rem, 4vw, 2.5rem);
}
.hero-proof__strip::-webkit-scrollbar { display: none; }
.hero-proof__strip img {
  width: 100%; aspect-ratio: 1; object-fit: cover;
  border-radius: calc(var(--radius) * .5); scroll-snap-align: start;
}

/* ============================================================
   BAUSTEINE, ergaenzt am 23.08.2026
   Quellen ausgewertet mit skillui --mode ultra: animejs.com (MIT),
   motion.dev (MIT), kokonutui.com (MIT), bklit-ui (MIT), ui.shadcn.com (MIT).
   Alles hier ist NACHGEBAUT: die Vorlagen sind durchweg React + Tailwind,
   uebernommen wurden Idee und Verhalten, kein Code. Von bklit.com selbst
   wurde nichts uebernommen — das Hauptrepo hat keine Lizenz.
   ============================================================ */

/* ---------- FAQ-Akkordeon ----------
   Der meistgesuchte Block ueberhaupt und bisher nicht vorhanden: "Kommen Sie
   auch nach X?", "Nehmen Sie Kassenpatienten?", "Gibt es vegane Gerichte?",
   "Was kostet der Beitrag?" — jede der vier Branchen lebt davon.
   Bewusst <details>/<summary>: aufklappbar, tastaturbedienbar und von
   Screenreadern verstanden OHNE eine Zeile JavaScript. Ein nachgebautes
   ARIA-Akkordeon waere mehr Code und schlechter. */
.faq { border-top: 1px solid color-mix(in srgb, var(--text) 14%, transparent); }
.faq__item {
  border-bottom: 1px solid color-mix(in srgb, var(--text) 14%, transparent);
}
.faq__q {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 1.25rem;
  min-height: 56px; padding: 1.1rem 0; cursor: pointer;
  font-family: var(--display); font-weight: 700;
  font-size: clamp(1.05rem, 2.6vw, 1.3rem); line-height: 1.3;
  list-style: none; /* Firefox */
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q:hover { color: var(--accent); }
.faq__item[open] .faq__q { color: var(--accent); }
/* Plus, das sich beim Oeffnen zum Minus dreht. Reines CSS. */
.faq__q::after {
  content: ""; flex: 0 0 auto; width: 18px; height: 18px; margin-top: .35rem;
  background:
    linear-gradient(currentColor, currentColor) center/100% 2px no-repeat,
    linear-gradient(currentColor, currentColor) center/2px 100% no-repeat;
  transition: transform .3s cubic-bezier(.2,.8,.2,1);
}
.faq__item[open] .faq__q::after { transform: rotate(135deg); }
.faq__a {
  padding-bottom: 1.25rem; max-width: 46rem;
  color: var(--text-dim); font-size: 1.02rem;
}
.faq__item[open] .faq__a { animation: faq-in .32s ease-out both; }
@keyframes faq-in { from { opacity: 0; transform: translateY(-6px); } }

/* ---------- Bento-Raster (ARCHITEKTUR H) ----------
   Asymmetrisches Kachelfeld: Leistung, Foto, Oeffnungszeiten und Telefon
   liegen unterschiedlich gross nebeneinander, statt als sieben gleich
   aussehende Sektionen untereinander zu zerfallen. Traegt vor allem bei
   Gastro und Verein, wo viele kleine gleichrangige Angaben zu zeigen sind.
   Mobil bewusst EINE Spalte — ein Bento auf 375px ist nur Unordnung. Die
   Betonung uebernimmt dort --bento-hero (hoehere Kachel) statt der Breite. */
.bento { display: grid; gap: clamp(.75rem, 2vw, 1.15rem); grid-template-columns: 1fr; }
.bento__tile {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; justify-content: flex-end;
  gap: .5rem; min-height: 9.5rem;
  padding: clamp(1.15rem, 3vw, 1.9rem);
  border-radius: var(--radius);
  background: var(--bg-alt); color: var(--text);
  border: 1px solid color-mix(in srgb, var(--text) 10%, transparent);
  text-decoration: none;
}
a.bento__tile { transition: transform .25s cubic-bezier(.2,.8,.2,1), border-color .25s; }
a.bento__tile:hover { transform: translateY(-3px); border-color: var(--accent); }
.bento__tile--hero { min-height: 15rem; }
.bento__tile--accent { background: var(--accent); color: var(--accent-contrast); border-color: transparent; }
.bento__tile--dark { background: var(--text); color: var(--bg); border-color: transparent; }
.bento__tile--photo { padding: 0; min-height: 12rem; border: 0; }
.bento__tile--photo img { width: 100%; height: 100%; object-fit: cover; }
.bento__label {
  font-size: .74rem; font-weight: 800; letter-spacing: .16em; text-transform: uppercase;
  opacity: .7;
}
.bento__tile > * { min-width: 0; }
.bento__value {
  font-family: var(--display); font-size: clamp(1.6rem, 5vw, 2.6rem); line-height: 1;
  overflow-wrap: break-word; hyphens: auto;
}
.bento__text { font-size: .98rem; opacity: .85; }
/* Bento: bei 720px direkt auf vier Spalten zu springen ergab 194px schmale
   Kacheln — dieselbe Saeulen-Optik wie beim Karussell. Deshalb zwei Stufen. */
@media (min-width: 720px) {
  .bento { grid-template-columns: repeat(2, 1fr); grid-auto-rows: minmax(9rem, auto); }
}
@media (min-width: 1000px) {
  .bento { grid-template-columns: repeat(4, 1fr); }
  /* Die Unregelmaessigkeit ist der ganze Punkt — gleich grosse Kacheln
     waeren wieder nur ein Grid. */
  .bento__tile--hero  { grid-column: span 2; grid-row: span 2; }
  .bento__tile--wide  { grid-column: span 2; }
  .bento__tile--tall  { grid-row: span 2; }
}

/* ---------- Fotofreie Route ----------
   Alle zwoelf Registerprojekte waren Fotolayouts. Genau daran scheitern
   Handwerk und Vereine: es gibt keine brauchbaren Fotos, und Stockware zu
   "Angelverein Gifhorn" sieht nach Stockware aus. Diese Bausteine tragen
   eine Seite ohne ein einziges Bild — Typografie, Flaeche und Linie.
   Als Foto-Behandlung im Register: "fotofrei". */
.typo-panel {
  display: grid; gap: clamp(1rem, 3vw, 2rem);
  padding: clamp(1.75rem, 5vw, 4rem);
  border-radius: var(--radius);
  background: var(--text); color: var(--bg);
}
/* min-width:0 ist hier Pflicht, nicht Kosmetik: Grid- und Flex-Elemente
   schrumpfen von sich aus nicht unter die Breite ihres laengsten Wortes.
   Bei einer Display-Schrift in 32px und deutschen Komposita
   ("Fassadensanierung", "Nachforderung") sprengt genau das den Bildschirm —
   gemessen 392px Spurbreite in einem 287px-Kasten, also 61px Ueberlauf auf
   einem 375px-Geraet. Zusammen mit overflow-wrap bricht das Wort statt die
   Seite zu verbreitern. */
.typo-panel > * { min-width: 0; }
.typo-panel__statement {
  font-family: var(--display); font-weight: 800;
  font-size: clamp(2rem, 8vw, 4.5rem); line-height: 1.02;
  letter-spacing: -.02em; text-wrap: balance;
  overflow-wrap: break-word; hyphens: auto;
}
.typo-panel__statement mark {
  background: none; color: var(--accent);
}
/* Grosse Zahl als Blickfang statt Bild — traegt Kennzahlen von Vereinen
   (Mitglieder, Gruendungsjahr) und Handwerk (Jahre, Projekte). */
.numeral-row { display: grid; gap: clamp(1.5rem, 4vw, 3rem); }
@media (min-width: 720px) { .numeral-row { grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); } }
.numeral {
  display: grid; gap: .35rem; min-width: 0;
  border-top: 3px solid var(--accent); padding-top: .9rem;
}
.numeral__label, .numeral__value { min-width: 0; overflow-wrap: break-word; }
.numeral__value {
  font-family: var(--display); font-weight: 800; line-height: .95;
  font-size: clamp(3rem, 11vw, 6rem); letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
}
.numeral__label { font-size: .95rem; color: var(--text-dim); }
/* Gezeichnete Linie statt Foto: Grundriss, Zahnbogen, Laufstrecke, Route.
   Der Pfad zeichnet sich beim Hereinscrollen selbst (base.js: .draw-line). */
.draw-line { display: block; width: 100%; height: auto; }
.draw-line path, .draw-line line, .draw-line polyline, .draw-line circle {
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
.js-draw .draw-line [data-draw] {
  stroke-dasharray: var(--len, 1000); stroke-dashoffset: var(--len, 1000);
}
.js-draw .draw-line.is-drawn [data-draw] {
  transition: stroke-dashoffset 1.6s cubic-bezier(.4,0,.2,1);
  stroke-dashoffset: 0;
}

/* ---------- Zeilenweiser Titel-Aufbau (SplitText-Prinzip) ----------
   Nachgebaut nach dem SplitText/Stagger-Muster von anime.js: die Zeilen einer
   Ueberschrift setzen versetzt nach, statt dass der ganze Block einfach
   einblendet. base.js zerlegt den Text und setzt --i je Zeile; der
   Vorlesetext bleibt ueber aria-label erhalten. Ohne JS passiert nichts —
   die Ueberschrift steht dann einfach da. */
.js-split .split-lines { min-width: 0; }
.js-split .split-line { display: block; overflow: hidden; overflow-wrap: break-word; }
.js-split .split-line > span {
  display: block; will-change: transform;
  transform: translateY(105%);
}
.js-split .split-lines.is-in .split-line > span {
  transition: transform .85s cubic-bezier(.16,1,.3,1);
  transition-delay: calc(var(--i, 0) * 85ms);
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  .js-split .split-line > span { transform: none; }
}

/* Kopfbereich der Dock-Variante: mobil traegt er den Burger, am Desktop
   uebernimmt das Bottom-Dock und der Kopf verschwindet. */
@media (min-width: 900px) {
  .site-header--dock-mobil { display: none; }
}

/* ---------- Farbmodi des Betriebssystems ----------
   Drei Zustaende, die der Besucher einschalten kann und die eine Seite
   ueberstehen muss. Der erste ist der wichtigste und wird in :root oben
   geregelt (color-scheme).

   1) Erzwungener Dunkelmodus / Farbfilter -> color-scheme, siehe :root.
   2) Windows-Kontrastdesign -> forced-colors, hier unten.
   3) "Staerkeren Kontrast anzeigen" -> prefers-contrast, hier unten. */

@media (forced-colors: active) {
  /* Im Kontrastdesign ersetzt Windows alle Farben durch Systemfarben. Was es
     NICHT ersetzt, sind Verlaeufe, halbtransparente Overlays und
     Hintergrundbilder. Wo der Kontrast bisher aus so einer Flaeche kam, faellt
     er ersatzlos weg — deshalb hier alle dekorativen Gruende entfernen und den
     Text auf eine echte Systemflaeche stellen. */
  .bg-dots, .bg-crosshair, .grain, .hero-orbs, .section--glow,
  .typo-panel, .bento__tile--accent, .bento__tile--dark {
    background-image: none !important;
  }
  .typo-panel, .bento__tile, .card, .img-card, .faq__item {
    background-color: Canvas; color: CanvasText; border: 1px solid CanvasText;
  }
  /* Foto-Heros mit Text-Overlay: das Foto traegt den Text hier nicht mehr. */
  .hero-atmo, .hero-framed__card, .video-teaser__poster {
    background-color: Canvas; color: CanvasText;
  }
  /* !important ist hier richtig: das Kunden-Theme steht als <style> im
     Dokument und schlaegt base.css sonst auf gleicher Spezifitaet. Gemessen
     bei HD Vision — der Primaerknopf kam im Kontrastdesign auf 1.28:1.
     Eine Barrierefreiheits-Ueberschreibung darf das Theme uebersteuern. */
  .btn, .action-bar__btn, .nav-toggle {
    border: 1px solid ButtonText !important;
    color: ButtonText !important;
    background-color: ButtonFace !important;
    background-image: none !important;
  }
  .btn--primary, .action-bar__btn--primary {
    background-color: Highlight !important;
    color: HighlightText !important;
    border-color: Highlight !important;
  }
  /* Zustaende muessen sichtbar bleiben, wenn Farbe als Signal wegfaellt. */
  .site-nav a[aria-current="page"], .dock-nav a.is-active { text-decoration: underline 3px; }
  .nav-scrim { background: Canvas; }
}

@media (prefers-contrast: more) {
  /* Gedaempfter Text ist die erste Stelle, an der es kippt. */
  :root { --text-dim: var(--text); }
  .section-kicker, .bento__label { opacity: 1; }
  .faq__item, .bento__tile { border-color: var(--text); }
}
