/* ===================== SCHRIFTEN, SELBST GEHOSTET =====================
   Die oeffentliche Website laedt KEINE Schriften mehr von Google-Servern.
   Ein Aufruf von fonts.googleapis.com/fonts.gstatic.com uebertraegt die
   IP-Adresse jedes Besuchers in die USA, bevor irgendeine Einwilligung
   vorliegt -- genau die Risikoklasse, wegen der der Bauauftrag schon
   Google Maps abgelehnt hat (Final-Review-Fund C2).

   Die WOFF2-Dateien unter app/static/fonts/ wurden einmalig per curl von
   fonts.gstatic.com geholt (Vorgehen wie bei den eingebetteten PDF-
   Schriften aus Etappe 3) und auf ihre WOFF2-Signatur "wOF2" geprueft.
   Lizenzen: Archivo und Inter stehen unter der SIL Open Font License 1.1,
   Roboto Mono unter Apache 2.0 -- Selbsthosten ist ausdruecklich erlaubt.

   Google liefert diese drei Familien als VARIABLE Fonts aus: fuer alle
   angeforderten Schnitte einer Familie kommt dieselbe Datei je Zeichensatz
   zurueck (identische URL fuer 600/700/800 usw.). Deshalb liegt hier eine
   Datei pro Familie und Zeichensatz statt einer pro Schnitt, und die
   @font-face-Regeln geben den Gewichtsbereich an, den die Datei abdeckt.
   Die unicode-range-Angaben sind 1:1 die von Google -- so laedt ein
   Besucher die latin-ext-Datei nur, wenn die Seite solche Zeichen zeigt. */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 600 800;
  font-display: swap;
  src: url("/static/fonts/archivo-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 600 800;
  font-display: swap;
  src: url("/static/fonts/archivo-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/static/fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/static/fonts/inter-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Roboto Mono";
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("/static/fonts/roboto-mono-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Roboto Mono";
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("/static/fonts/roboto-mono-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* Baukasten 0: neutrale Rueckfallwerte. Die Marke der Instanz setzt /ci.css
     (instanz.json), geladen nach dieser Datei. Gleiche Werte wie die
     Standardwerte in app/instanz.py; Kontrast siehe test_kontrast. */
  --grund: #FFFFFF;
  --signal: #8C959A;
  --akzent-dunkel: #4A5257;
  --akzent-hover: #3A4145;
  /* Hover-Ton fuer farbige Flaechen in --signal (etwas dunkler, Ink-Text
     darauf muss 4.5:1 halten, siehe test_kontrast). */
  --signal-hover: #848D92;
  --ink: #1A1D1F;
  --muted: #5A6166;
  --linie: #E3E6E8;
  --flaeche: #F3F4F5;
  --akzent-raster: rgba(74,82,87,.18);
  --sp-1: 8px; --sp-2: 16px; --sp-3: 24px; --sp-4: 32px; --sp-5: 40px; --sp-6: 48px; --sp-8: 64px; --sp-10: 80px;
  --maxw: 1180px;
  --radius: 3px;
  --font-head: "Archivo", sans-serif;
  --font-body: "Inter", sans-serif;
  --font-mono: "Roboto Mono", monospace;
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
}
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0; background: var(--grund); color: var(--ink); font-family: var(--font-body);
  font-size: 16px; line-height: 1.6; -webkit-font-smoothing: antialiased;
}
img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
ul, ol { margin: 0; padding: 0; list-style: none; }
button { font-family: inherit; cursor: pointer; }
h1, h2, h3, h4 { font-family: var(--font-head); font-weight: 700; margin: 0; }
p { margin: 0; }
.container { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--sp-3); }
.skip-link { position: absolute; left: -999px; top: 0; }
.skip-link:focus { left: 8px; top: 8px; background: var(--signal); color: var(--ink); padding: 8px 14px; border-radius: var(--radius); z-index: 999; display: inline-flex; align-items: center; min-height: 44px; }
.icon { width: 20px; height: 20px; stroke: currentColor; fill: none; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }
.icon-sm { width: 16px; height: 16px; }

.btn {
  display: inline-flex; align-items: center; gap: 8px; padding: 13px 22px; border-radius: var(--radius);
  font-family: var(--font-head); font-weight: 700; font-size: 13px; text-transform: uppercase; letter-spacing: .5px;
  border: 1px solid var(--signal); transition: background 150ms ease-out, color 150ms ease-out, transform 160ms var(--ease-out); min-height: 44px;
}
/* Fund 3 (STANDARDS.md "Button press feedback"): jeder Button brauchte
   bislang nur Hover-Farbwechsel, kein :active-Feedback beim Antippen. */
.btn:active { transform: scale(0.97); }
/* 06.09.2026: auf der Website gibt es nur noch EINE farbige
   Flaechenfarbe -- --signal (Markenfarbe der Instanz). Vorher trugen
   Buttons, Chat-Knopf, aktive Tabs und Sitz-Marke --akzent-dunkel mit
   weissem Text, die Topbar aber --signal: zwei Tuerkis nebeneinander, das
   sah nach Fehler aus. Weisser Text auf --signal schafft nur 3.14:1,
   deshalb tragen alle farbigen Flaechen jetzt INK-Text (5.34:1) --
   dieselbe Regel wie in der Topbar. --akzent-dunkel bleibt fuer TEXT und
   Linien auf weissem Grund (Links, Icons, Ueberschriften-Akzente). */
.btn-primary { background: var(--signal); color: var(--ink); border-color: var(--signal); }
.btn-primary:hover { background: var(--signal-hover); border-color: var(--signal-hover); }
.btn-outline { background: transparent; color: var(--akzent-hover); }
.btn-outline:hover { background: var(--flaeche); }
.btn-block { width: 100%; justify-content: center; }

/* 04.09.2026 (Kundenwunsch): oberer Balken in der Signalfarbe statt schwarz.
   Text bleibt Ink -- auf --signal mindestens 4.5:1 (test_kontrast), also auch
   fuer 12.5px-Text ueber der 4.5:1-Schwelle (weisser Text waere nur
   3.14:1, siehe tests/test_kontrast.py). */
.topbar { background: var(--signal); color: var(--ink); font-size: 12.5px; }
.topbar-inner { display: flex; gap: var(--sp-4); align-items: center; padding: 8px var(--sp-3); flex-wrap: wrap; }
.topbar-inner a { color: var(--ink); display: inline-flex; align-items: center; min-height: 44px; font-weight: 600; }
.topbar-inner a:hover { text-decoration: underline; text-underline-offset: 3px; }
.topbar-zeiten { font-family: var(--font-mono); color: var(--ink); margin-left: auto; }
/* 21.09.2026 Smartphone-Check: die vier Angaben brachen in zwei Zeilen mit
   einem grossen Loch dazwischen (gap 32px + 44px Mindesthoehe der Links).
   Auf dem Handy enger setzen, Zeilenabstand klein, Adresse bleibt lesbar. */
@media (max-width: 700px) {
  .topbar-inner { gap: 4px var(--sp-2); padding: 4px var(--sp-2); }
  /* 24.09.2026: Tippflaeche 44 px (WCAG/Test test_klickziele_mindestens_44px_mobil),
     optisch aber weiter kompakt -- die negativen Raender nehmen die 8 px
     wieder aus dem Layout, die Links haben keinen Hintergrund. */
  .topbar-inner a { min-height: 44px; margin: -4px 0; }
  .topbar-inner span { display: inline-flex; align-items: center; min-height: 28px; }
}

.site-header { position: sticky; top: 0; z-index: 60; background: var(--grund); border-bottom: 1px solid var(--linie); }
.header-inner { display: flex; align-items: center; justify-content: space-between; padding: 14px var(--sp-3); gap: var(--sp-3); }
.brand { display: flex; align-items: center; gap: 11px; min-height: 44px; }
.brand-mark { width: 34px; height: 34px; flex-shrink: 0; }
.brand-word { font-family: var(--font-head); font-weight: 800; font-size: 14.5px; line-height: 1.15; text-transform: uppercase; letter-spacing: .5px; }
.brand-word .l2 { display: block; font-weight: 600; color: var(--muted); font-size: 12.5px; letter-spacing: 1.5px; }
/* 24.09.2026: echtes Logo des Betriebs (PNG 603x259) statt SVG + Schriftzug.
   Hoehe fest, Breite ergibt sich aus dem Seitenverhaeltnis -- width/height
   im Markup reservieren den Platz, damit beim Laden nichts springt. */
.brand-logo { display: block; height: 40px; width: auto; }
.brand-fuss { align-self: flex-start; }
.brand-fuss .brand-logo { height: 44px; }
@media (max-width: 700px) { .site-header .brand-logo { height: 34px; } }
.main-nav { display: flex; gap: var(--sp-4); }
.main-nav a { font-size: 13.5px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .3px; display: flex; align-items: center; min-height: 44px; }
.main-nav a:hover { color: var(--akzent-hover); }
.header-actions { display: flex; align-items: center; gap: var(--sp-2); }
.burger { display: none; background: none; border: 1px solid var(--linie); border-radius: var(--radius); color: var(--ink); padding: 7px; min-height: 44px; min-width: 44px; align-items: center; justify-content: center; transition: transform 160ms var(--ease-out); }
.burger:active { transform: scale(0.9); }
/* Fund 2 (STANDARDS.md): das Menü klappte bisher instant per
   display:none/flex auf -- ohne jede Übergangsanimation. display kann man
   nicht animieren (kein Zwischenzustand), deshalb bleibt die Box jetzt
   immer im Layout, wird aber per position:absolute aus dem Dokumentfluss
   genommen (schiebt beim Öffnen keinen Seiteninhalt mehr nach unten) und
   per opacity/transform/visibility ein- bzw. ausgeblendet -- exakt das in
   STANDARDS.md verlangte Paar performanter Eigenschaften. visibility
   entfernt das Menü währenddessen weiterhin aus dem Zugänglichkeits- und
   Tab-Baum (Ersatz für das frühere display:none), pointer-events:none hält
   es im geschlossenen Zustand echt inert. */
.mobile-nav {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 1;
  display: flex; flex-direction: column; background: var(--grund); padding: 0 var(--sp-3) var(--sp-3); border-top: 1px solid var(--linie);
  opacity: 0; transform: translateY(-8px); visibility: hidden; pointer-events: none;
  transition: opacity 220ms var(--ease-out), transform 220ms var(--ease-out), visibility 0s linear 220ms;
}
.mobile-nav a { padding: 12px 4px; color: var(--muted); font-size: 15px; border-bottom: 1px solid var(--linie); text-transform: uppercase; font-weight: 600; min-height: 44px; display: flex; align-items: center; }
/* Ohne diese Regel gewinnt ".mobile-nav a" (Spezifität 0,1,1) gegen
   ".btn-primary" (Spezifität 0,1,0) und der "Anrufen"-Button im mobilen
   Menü würde grauen statt weißen Text auf dunklem Grund zeigen (real
   gemessen: nur 1.45:1 Kontrast statt der geforderten 4.5:1). */
.mobile-nav a.btn-primary { color: var(--ink); justify-content: center; border-bottom: none; }
.mobile-nav.open {
  opacity: 1; transform: none; visibility: visible; pointer-events: auto;
  transition: opacity 220ms var(--ease-out), transform 220ms var(--ease-out), visibility 0s linear 0s;
}
@media (max-width: 860px) {
  .main-nav { display: none; }
  .btn-phone span { display: none; }
  .burger { display: flex; }
}

.site-footer { background: var(--flaeche); padding: var(--sp-6) 0 var(--sp-4); border-top: 1px solid var(--linie); }
.footer-inner { display: flex; flex-wrap: wrap; gap: var(--sp-4); justify-content: space-between; align-items: flex-start; padding-bottom: var(--sp-3); border-bottom: 1px solid var(--linie); }
.footer-brand { display: flex; flex-direction: column; gap: 8px; max-width: 320px; }
.footer-tagline { font-family: var(--font-head); font-weight: 700; font-size: 12.5px; text-transform: uppercase; letter-spacing: .6px; color: var(--akzent-hover); }
.footer-brand p { font-size: 13px; color: var(--muted); line-height: 1.5; }
.footer-links { display: flex; flex-direction: column; gap: 10px; }
.footer-links a { color: var(--muted); font-size: 13.5px; display: flex; align-items: center; gap: 8px; min-height: 44px; }
.footer-links a:hover { color: var(--akzent-hover); }
.footer-copy { font-size: 12px; color: var(--muted); margin-top: var(--sp-3); }
.footer-cleardeck { display: flex; gap: 10px; align-items: center; font-family: var(--font-mono); font-size: 12px; color: var(--muted); margin-top: 8px; justify-content: flex-end; }

/* Platzhalter für die Startseite, Inhalt folgt in Task 3-5 */
.placeholder-notice { padding: var(--sp-6) 0; text-align: center; color: var(--muted); }

.section { padding: var(--sp-10) 0; }
.section-flaeche { background: var(--flaeche); }
.label-num { display: flex; align-items: center; gap: 12px; font-family: var(--font-head); font-weight: 700; font-size: 12px; letter-spacing: 2px; text-transform: uppercase; color: var(--akzent-hover); margin-bottom: var(--sp-2); }
.label-num::after { content: ""; flex: 1; height: 1px; background: var(--linie); }
.big-head { font-weight: 800; text-transform: uppercase; font-size: clamp(1.7rem, 3.6vw, 2.7rem); line-height: 1.08; letter-spacing: -0.01em; max-width: 720px; margin-bottom: var(--sp-5); }

/* Hero-Variante 1 ("Bildhero", von Leon am 30.08.2026 gewaehlt, mit
   die Rahmen-Abwandlung des Betriebs -- siehe Kommentar in
   app/templates/website/index.html): ein grosses, gerahmtes Foto mit
   Text+Verlauf darauf, statt der vorherigen Zwei-Spalten-Aufteilung.
   .hero-frame wiederverwendet dasselbe "gerahmte Flaeche"-Muster wie
   .karte-rahmen (#einsatzgebiet, weiter unten in dieser Datei):
   position:relative, Rahmenlinie, Eck-Radius, overflow:hidden. */
.hero { padding: var(--sp-10) 0; }
/* Feste Hoehe statt min-height: eine echte Mindest-Fotoflaeche muss
   GARANTIERT sichtbar bleiben, unabhaengig davon, wie viel Platz der
   Text braucht (real gemessen, siehe Task-Bericht 30.08.). */
.hero-frame { position: relative; height: 600px; display: flex; align-items: flex-end; justify-content: flex-start; overflow: hidden; border: 1px solid var(--linie); border-radius: var(--radius); }
.hero-bg { position: absolute; inset: 0; z-index: 0; }
.hero-bg picture { display: block; width: 100%; height: 100%; }
.hero-bg img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 50%; }
/* 06.09.2026 (Kundenwunsch): Titelbild ist jetzt die Visualisierung
   "aus dem Bauplan wird der Garten" (links Plan, rechts Foto) -- der Text
   soll SO TRANSPARENT WIE MOEGLICH darauf liegen, ohne weisse Box.
   Loesung: eine Glasflaeche (Milchglas) -- das Foto scheint durch, wird
   hinter dem Text aber weichgezeichnet (backdrop-filter), sodass keine
   harten Bildkanten durch die Buchstaben laufen. Die Deckkraft ist NICHT
   geraten, sondern gegen das echte Bild gerechnet (tests/test_kontrast.py
   simuliert object-fit:cover + Weichzeichner + Weissanteil je Viewport und
   misst den dunkelsten Pixel unter dem Textfeld):
   - 45 % weiss: das 600 px breite, kompakte Panel (Runde 3) reicht auf
     allen Breiten bis in die dunkle Gartenseite -- gemessen >= 4,9:1
     Ink-Text (bei 40 % waeren es auf Desktop nur 4,35:1, daher 45).
   - Mobil: Bildausschnitt 20 % von links (Plan + Uebergang sichtbar),
     40 % weiss (>= 5,2:1).
   Ohne backdrop-filter (alte Browser) faellt die Flaeche auf 82 % weiss
   zurueck -- lesbar auch ueber einem schwarzen Pixel (>= 10:1).
   Kein Farbakzent in der Headline: Tuerkis auf Glas ueber dem Foto
   schafft die 3:1 fuer grossen Text nicht sicher, deshalb reines Ink;
   die Signalfarbe sitzt in der Eyebrow-Linie und im CTA. */
.hero-content { position: relative; z-index: 2; margin: var(--sp-2); padding: var(--sp-3) var(--sp-4) var(--sp-3) var(--sp-3); color: var(--ink); max-width: 600px; background: rgba(255,255,255,.45); -webkit-backdrop-filter: blur(16px) saturate(1.05); backdrop-filter: blur(16px) saturate(1.05); border-radius: var(--radius); border: 1px solid rgba(255,255,255,.55); }
/* Eyebrow in Ink (nicht Akzent): 11.5px-Text braucht 4.5:1 auch ueber
   dem dunkelsten Bildpixel hinter dem Glas. Die Signalfarbe sitzt in der
   Linie darunter. */
.hero-eyebrow { display: inline-flex; align-items: center; font-family: var(--font-head); font-size: 11px; letter-spacing: 1.6px; text-transform: uppercase; font-weight: 700; color: var(--ink); border-bottom: 2px solid var(--signal); padding: 0 0 8px; margin-bottom: var(--sp-2); }
/* Runde 3 (Leon, 06.09.): Block KOMPAKT, damit die Bauplan-Zeichnung im
   Bild sichtbar bleibt -- breiter (600 px) statt hoch, Headline in zwei
   Zeilen ("Schnell. Zuverlässig." passt bei 36 px in die 544 px breite
   Innenflaeche), kurzer Beschreibungstext, Buttons nebeneinander. */
.hero h1 { font-weight: 800; text-transform: uppercase; font-size: clamp(1.7rem, 3vw, 2.25rem); line-height: 1.05; letter-spacing: -0.01em; margin-bottom: 10px; color: var(--ink); }
.hero-sub { max-width: 56ch; font-size: 15px; line-height: 1.5; color: var(--ink); margin-bottom: var(--sp-2); }
.hero-cta { display: flex; gap: 12px; flex-wrap: wrap; }
/* Bildnachweis UNTER dem Bild (Leon, 06.09.: auf dem Bild nimmt er dem
   Motiv etwas weg), rechtsbuendig, gedaempft -- Text auf weissem Grund,
   kein Kontrastrisiko mehr. */
.hero-credit { display: block; text-align: right; font-size: 12px; color: var(--muted); margin-top: 8px; }
.photo-placeholder { aspect-ratio: 3/4; border: 1px solid var(--linie); background: var(--flaeche); display: flex; align-items: center; justify-content: center; color: var(--muted); font-size: 12px; height: 100%; }
@media (max-width: 700px) {
  /* Deutlich hoeher als der Desktop-Rahmen: derselbe Fliesstext braucht
     auf schmaler Breite viel mehr Zeilen -- ohne genug Rahmenhoehe
     schneidet .hero-frames overflow:hidden den Text oben ab (real so
     gefunden, siehe Task-Bericht 30.08.). */
  .hero-frame { height: 640px; align-items: flex-end; }
  /* Ausschnitt von links: der helle Plan und der Uebergang zum Garten
     liegen hinter dem Text (Kontrast gemessen, siehe oben). */
  .hero-bg img { object-position: 20% 50%; }
  .hero-content { margin: var(--sp-1); padding: var(--sp-3) var(--sp-2); max-width: none; background: rgba(255,255,255,.40); }
  .hero-credit { text-align: left; }
  .photo-placeholder { aspect-ratio: 16/9; }
}
/* Rueckfall ohne backdrop-filter -- steht bewusst NACH allen Viewport-
   Varianten, damit er ueberall gewinnt. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .hero-content { background: rgba(255,255,255,.82); }
}

/* ===================== LEISTUNGSKATALOG (04.09.2026) =====================
   Interaktiv: links acht Leistungs-Buttons (Tabs, editorial mit 1-px-
   Linien, kein Kachel-Grid), rechts das Panel mit Foto + Text zur
   gewaehlten Leistung. Uebergang: nur opacity/transform, 200 ms ease-out,
   reduced-motion respektiert. Klickziele >= 44 px. */
.leistung-icon { width: 36px; height: 36px; border: 1px solid var(--linie); border-radius: var(--radius); display: flex; align-items: center; justify-content: center; color: var(--signal); flex-shrink: 0; background: var(--grund); }
.leistung-icon .icon { width: 18px; height: 18px; }
/* 06.09.2026: Leistungs-Symbole des Betriebs (eigene Icons, app/static/img/
   tokens/, transparentes PNG in der Markenfarbe) sitzen in derselben
   36-px-Flaeche wie die Strich-Icons. Im aktiven Tab wird die Flaeche
   tuerkis, das Token dafuer schwarz (brightness(0) -- wirkt nur auf die
   Farbflaeche des PNG, die Transparenz bleibt). */
.leistung-icon.hat-token img { width: 26px; height: 26px; object-fit: contain; }
.katalog-tab.aktiv .leistung-icon.hat-token img { filter: brightness(0); }
.leistung-body { flex: 1; min-width: 0; display: flex; flex-direction: column; text-align: left; }
.leistung-name { font-family: var(--font-head); font-weight: 700; font-size: 15px; color: var(--ink); }
.leistung-detail { font-size: 12.5px; color: var(--muted); margin-top: 1px; }
.katalog { display: grid; grid-template-columns: minmax(280px, 380px) 1fr; gap: var(--sp-6); align-items: start; }
.katalog-nav { display: flex; flex-direction: column; border-top: 1px solid var(--linie); }
.katalog-tab { display: flex; align-items: center; gap: 14px; width: 100%; padding: 13px 8px 13px 0; border: 0; border-bottom: 1px solid var(--linie); background: transparent; color: inherit; text-align: left; min-height: 56px; cursor: pointer; position: relative; transition: background 150ms ease-out; }
.katalog-tab::before { content: ""; position: absolute; left: -12px; top: 0; bottom: 0; width: 2px; background: var(--signal); opacity: 0; transition: opacity 200ms var(--ease-out); }
.katalog-tab:hover { background: var(--flaeche); }
.katalog-tab:focus-visible { outline: 2px solid var(--akzent-dunkel); outline-offset: 2px; }
.katalog-tab .katalog-pfeil { width: 16px; height: 16px; color: var(--muted); opacity: 0; transform: translateX(-4px); transition: opacity 200ms var(--ease-out), transform 200ms var(--ease-out); }
.katalog-tab.aktiv { background: var(--flaeche); }
.katalog-tab.aktiv::before { opacity: 1; }
.katalog-tab.aktiv .leistung-icon { background: var(--signal); border-color: var(--signal); color: var(--ink); }
.katalog-tab.aktiv .katalog-pfeil { opacity: 1; transform: none; color: var(--akzent-dunkel); }
.katalog-panels { position: relative; min-height: 420px; }
.katalog-panel { display: grid; grid-template-columns: 1fr; gap: var(--sp-3); }
.katalog-panel[hidden] { display: none; }
.katalog-panel.einblenden { animation: katalog-auf 200ms var(--ease-out); }
@keyframes katalog-auf { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.katalog-bild { position: relative; aspect-ratio: 16 / 10; border: 1px solid var(--linie); border-radius: var(--radius); overflow: hidden; background: var(--flaeche); }
.katalog-bild img { width: 100%; height: 100%; object-fit: cover; }
/* Markenfarbige Ersatzflaeche (Design-Standard "Bildmaterial statt
   Leerstellen"): CI-Flaeche mit dezentem Punktraster, Icon und ehrlicher
   Bildunterschrift -- nie eine leere graue Box. */
.katalog-bild-ersatz { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; background-color: var(--flaeche); background-image: radial-gradient(circle at 1px 1px, var(--akzent-raster) 1px, transparent 0); background-size: 18px 18px; color: var(--akzent-dunkel); }
.katalog-bild-ersatz .icon { width: 44px; height: 44px; stroke-width: 1.2; }
.katalog-bild-ersatz .katalog-token-gross { width: 96px; height: 96px; object-fit: contain; }
.katalog-bild-text { font-family: var(--font-mono); font-size: 11.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--akzent-dunkel); text-align: center; max-width: 34ch; padding: 0 16px; }
.katalog-text h3 { font-size: clamp(1.25rem, 2.2vw, 1.6rem); text-transform: uppercase; letter-spacing: -0.01em; margin-bottom: 10px; }
.katalog-beschreibung { color: var(--ink); max-width: 60ch; margin-bottom: var(--sp-2); }
.katalog-arbeiten-label { font-family: var(--font-mono); font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); margin-bottom: 4px; }
.katalog-arbeiten { color: var(--muted); font-size: 14px; max-width: 60ch; margin-bottom: var(--sp-3); padding-bottom: var(--sp-2); border-bottom: 1px solid var(--linie); }
@media (min-width: 900px) { .katalog-panel { grid-template-columns: 1fr 1fr; align-items: start; } }
@media (max-width: 820px) {
  .katalog { grid-template-columns: 1fr; gap: var(--sp-3); }
  .katalog-nav { flex-direction: row; overflow-x: auto; border-top: 0; gap: 8px; padding-bottom: 6px; -webkit-overflow-scrolling: touch; }
  .katalog-tab { flex: 0 0 auto; width: auto; border: 1px solid var(--linie); border-radius: var(--radius); padding: 8px 12px; min-height: 44px; }
  .katalog-tab::before { display: none; }
  .katalog-tab .leistung-detail, .katalog-tab .katalog-pfeil { display: none; }
  .katalog-tab.aktiv { border-color: var(--akzent-dunkel); }
  .katalog-panels { min-height: 0; }
}
@media (prefers-reduced-motion: reduce) { .katalog-panel.einblenden { animation: none; } .katalog-tab .katalog-pfeil { transition: none; } }

/* Einsatzgebiets-Karte (Leaflet + OpenStreetMap, siehe app/static/js/karte.js).
   Werte fuer .karte-rahmen/#karte/.karte-fallback/.orte-zeile/.karte-pin
   uebernommen aus der Referenzdatei (anfrage-tool-referenz, Zeilen
   112-125), auf die echten CSS-Variablen dieses Projekts uebertragen
   (--akzent existiert hier nicht, dafuer --akzent-dunkel; Fonts ueber
   --font-head/--font-body statt hart kodierter Familennamen). */
.karte-rahmen { position: relative; border: 1px solid var(--linie); border-radius: var(--radius); overflow: hidden; height: 440px; margin-bottom: var(--sp-3); }
/* 06.09.2026 (Kundenwunsch): Karte wieder MIT Farbe und "ein bisschen
   interaktiv" -- Ziehen und Zoom-Knoepfe an (karte.js), Mausrad-Zoom
   bleibt aus, damit die Seite beim Scrollen nicht in der Karte haengen
   bleibt. Die OSM-Kacheln werden leicht entsaettigt (kein Grau mehr, aber
   auch kein Kartenbunt, das mit der CI streitet). Orte deutlicher: grosse
   Punkte in Signalfarbe mit weissem Ring, Beschriftung als feste, gut
   lesbare Marke (13 px, Ink auf Weiss, Signal-Linie). */
#karte { position: absolute; inset: 0; z-index: 2; background: transparent; filter: saturate(.72) contrast(1.02); cursor: grab; }
#karte:active { cursor: grabbing; }
.karte-ortslabel { background: rgba(255,255,255,.96); border: 1px solid var(--signal); border-left-width: 3px; border-radius: var(--radius); color: var(--ink); font-family: var(--font-head); font-weight: 700; font-size: 13px; letter-spacing: .01em; padding: 4px 9px; box-shadow: none; white-space: nowrap; }
.karte-ortslabel::before { display: none; }
.karte-ortslabel.sitz { background: var(--signal); color: var(--ink); border-color: var(--signal); }
@media (max-width: 600px) { .karte-ortslabel { font-size: 11.5px; padding: 3px 7px; } }
.karte-fallback { position: absolute; inset: 0; z-index: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; background: var(--flaeche); color: var(--muted); text-align: center; padding: 24px; }
.karte-fallback.verborgen { display: none; }
.karte-fallback .gross { font-family: var(--font-head); font-weight: 700; font-size: 20px; color: var(--ink); }
.orte-zeile { display: flex; flex-wrap: wrap; gap: 0; margin: 0 0 var(--sp-3); border-top: 1px solid var(--linie); }
.orte-zeile div { flex: 1 1 150px; padding: 14px 16px 0; border-right: 1px solid var(--linie); }
.orte-zeile div:last-child { border-right: 0; }
.orte-zeile .ort { font-family: var(--font-head); font-weight: 700; font-size: 17px; }
.orte-zeile .sub { font-size: 13px; color: var(--muted); }
.karte-pin { background: var(--signal); border: 3px solid #fff; border-radius: 50%; box-shadow: 0 0 0 1px var(--akzent-dunkel); }
.karte-pin.klein { background: var(--signal); }
.leaflet-container { font: 12px/1.4 var(--font-body); }
/* Klickziel-Standard dieser Website (>=44px, siehe tests/e2e
   test_klickziele_mindestens_44px_*): Leafleft liefert seine Zoom-Buttons
   standardmaessig nur 26-30px gross an -- hier auf das Projekt-Minimum
   angehoben (echtes, primaeres Bedienelement, daher lohnt sich der Fix).
   Die winzige Copyright-Attribution ("Leaflet" + "OpenStreetMap", siehe
   Datenschutz §5 fuer Letzteres) bleibt bewusst bei ihrer ueblichen
   kleinen Groesse -- sie auf 44px aufzublasen wuerde die Kartenecke in
   eine unverhaeltnismaessig grosse graue Leiste verwandeln, was auf
   keiner Karte im Web ueblich ist; dichter Inline-Rechtstext ist dafuer
   auch die von WCAG 2.5.8 selbst vorgesehene Ausnahme von der
   Zielgroessen-Regel. Ist deshalb im E2E-Klickziel-Test explizit
   ausgenommen (siehe dortiger Kommentar zu LEAFLET_ATTRIBUTION_SELECTOR,
   der auch die Unterscheidung Leaflet-Credit vs. OSM-Pflichtangabe
   festhaelt) statt hier auf 44px aufgeblasen zu werden. */
.leaflet-control-zoom-in, .leaflet-control-zoom-out,
.leaflet-touch a.leaflet-control-zoom-in, .leaflet-touch a.leaflet-control-zoom-out {
  /* Spezifitaet muss `.leaflet-touch .leaflet-bar a` in leaflet.css
     (0,2,1) erreichen -- das `a` hier ist deshalb kein Stilmittel,
     sondern noetig, damit dieser Override wirklich zieht. */
  width: 44px; height: 44px; line-height: 44px;
}
/* "Alle Orte zeigen"-Knopf (karte.js) unter den Zoom-Knoepfen, gleiche
   Groesse, Signalfarbe als Zeichen. */
.karte-zurueck a, .leaflet-touch .karte-zurueck a { width: 44px; height: 44px; line-height: 44px; font-size: 22px; color: var(--akzent-dunkel); text-align: center; display: block; }
.karte-zurueck a:hover { color: var(--akzent-hover); }
.eg-note { text-align: center; font-size: 13.5px; color: var(--muted); max-width: 480px; margin: 0 auto; }
@media (max-width: 640px) {
  .karte-rahmen { height: 320px; }
  .orte-zeile div { flex: 1 1 45%; border-right: 0; border-bottom: 1px solid var(--linie); padding-bottom: 12px; }
}

.mosaic { display: grid; grid-template-columns: 1.6fr 1fr 1fr; grid-template-rows: 1fr 1fr; gap: 1px; background: var(--linie); margin-bottom: var(--sp-4); }
.mosaic-tile { position: relative; min-height: 170px; background: var(--grund); }
.mosaic-tile.big { grid-column: 1; grid-row: 1 / 3; min-height: 352px; }
.mosaic-tile.has-photo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.mosaic-tile .tile-caption { position: absolute; left: 0; right: 0; bottom: 0; background: rgba(255,255,255,.92); color: var(--ink); font-size: 11.5px; font-weight: 600; padding: 7px 10px; border-top: 1px solid var(--linie); }
@media (max-width: 760px) {
  .mosaic { grid-template-columns: 1fr 1fr; }
  .mosaic-tile.big { grid-column: 1 / 3; grid-row: auto; min-height: 220px; }
}
/* 24.09.2026: Das Mosaik ist jetzt im Editor pflegbar (0-8 Fotos). Bei 5
   Fotos bleibt alles wie bisher; fuer andere Anzahlen fuellen diese Regeln
   die Luecken, damit nie eine leere graue Zelle entsteht. */
@media (min-width: 761px) {
  .mosaic-n1 { grid-template-columns: 1fr; grid-template-rows: auto; }
  .mosaic-n2 { grid-template-columns: 1.6fr 1fr; grid-template-rows: auto; }
  .mosaic-n1 .mosaic-tile.big, .mosaic-n2 .mosaic-tile.big { grid-row: auto; }
  .mosaic-n2 .mosaic-tile { min-height: 352px; }
  .mosaic-n3 { grid-template-columns: 1.6fr 1fr; }
  .mosaic-n4 .mosaic-tile:nth-child(4) { grid-column: 2 / 4; }
  .mosaic-n6 .mosaic-tile:nth-child(6) { grid-column: 1 / 4; }
  .mosaic-n7 .mosaic-tile:nth-child(7) { grid-column: 2 / 4; }
}
@media (min-width: 521px) and (max-width: 760px) {
  .mosaic-tile.big ~ .mosaic-tile:last-child:nth-child(even) { grid-column: 1 / 3; }
}
@media (max-width: 520px) { .mosaic { grid-template-columns: 1fr; } .mosaic-tile.big { grid-column: 1; } }
/* ===================== SPONSORING + GOOGLE-REZENSIONEN (04.09.2026) ===
   Sponsoring-Foto gross (zweispaltig, Bild links, 1-px-Linien, kein
   Schatten). Rezensionen: alle acht kompakt in Spalten, Kopfzeile mit
   Bewertung + Link zum Google-Profil. */
.sponsor-block { display: grid; grid-template-columns: minmax(220px, 360px) 1fr; gap: var(--sp-5); align-items: center; padding: var(--sp-4) 0; border-top: 1px solid var(--linie); border-bottom: 1px solid var(--linie); margin-bottom: var(--sp-5); }
.sponsor-bild { border: 1px solid var(--linie); border-radius: var(--radius); overflow: hidden; aspect-ratio: 1 / 1; }
.sponsor-bild img { width: 100%; height: 100%; object-fit: cover; }
.sponsor-label { font-family: var(--font-mono); font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--akzent-dunkel); margin-bottom: 8px; }
.sponsor-text h3 { font-size: clamp(1.25rem, 2.2vw, 1.6rem); text-transform: uppercase; letter-spacing: -0.01em; margin-bottom: 10px; }
.sponsor-text p { color: var(--muted); max-width: 56ch; }
.sponsor-block.sponsor-ohne-bild { grid-template-columns: 1fr; }
@media (max-width: 700px) { .sponsor-block { grid-template-columns: 1fr; gap: var(--sp-3); } .sponsor-bild { max-width: 320px; } }
.rezensionen-kopf { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding-bottom: var(--sp-2); margin-bottom: var(--sp-2); border-bottom: 1px solid var(--linie); }
.rezensionen-wert { font-family: var(--font-head); font-weight: 800; font-size: 28px; line-height: 1; }
.rezensionen-sterne { display: inline-flex; gap: 2px; }
.rezensionen-sterne svg { width: 18px; height: 18px; fill: var(--signal); }
.rezensionen-meta { font-size: 13.5px; color: var(--muted); }
.rezensionen-link { margin-left: auto; font-size: 13px; font-weight: 600; color: var(--akzent-hover); display: inline-flex; align-items: center; min-height: 44px; }
.rezensionen-link:hover { text-decoration: underline; text-underline-offset: 3px; }
.rezensionen-liste { column-count: 3; column-gap: var(--sp-4); }
.rezension { break-inside: avoid; margin: 0 0 var(--sp-2); padding: 0 0 var(--sp-2); border-bottom: 1px solid var(--linie); }
.rezension p { font-size: 13.5px; line-height: 1.55; margin-bottom: 6px; }
.rezension cite { font-family: var(--font-mono); font-size: 11px; letter-spacing: .06em; color: var(--muted); font-style: normal; }
@media (max-width: 900px) { .rezensionen-liste { column-count: 2; } }
@media (max-width: 600px) { .rezensionen-liste { column-count: 1; } .rezensionen-link { margin-left: 0; } }

.big-head-flush { margin-bottom: 0; }

.mini-ablauf { display: flex; border: 1px solid var(--linie); margin-bottom: var(--sp-5); background: var(--grund); }
.mini-ablauf-item { flex: 1; padding: 16px 18px; border-right: 1px solid var(--linie); }
.mini-ablauf-item:last-child { border-right: none; }
.mini-ablauf-num { font-family: var(--font-head); font-weight: 800; color: var(--akzent-hover); font-size: 12.5px; }
.mini-ablauf-item h4 { font-size: 13px; margin: 4px 0; text-transform: uppercase; letter-spacing: .3px; }
.mini-ablauf-item p { font-size: 12.5px; color: var(--muted); line-height: 1.5; }
@media (max-width: 760px) { .mini-ablauf { flex-direction: column; } .mini-ablauf-item { border-right: none; border-bottom: 1px solid var(--linie); } }

.kontakt-grid { display: grid; grid-template-columns: 0.9fr 1.1fr; gap: var(--sp-8); align-items: start; }
.kontakt-intro p { color: var(--muted); max-width: 400px; margin-top: var(--sp-2); }
.kontakt-direct { display: flex; flex-direction: column; gap: 10px; margin-top: var(--sp-4); }
.kontakt-direct a { display: flex; align-items: center; gap: 10px; font-weight: 600; font-size: 14.5px; min-height: 44px; }
.kontakt-direct .icon { color: var(--signal); }
.kontakt-form-wrap { background: var(--grund); border: 1px solid var(--linie); border-radius: var(--radius); padding: var(--sp-4); }
.form-row { margin-bottom: var(--sp-3); }
.form-row label { display: block; font-size: 12px; font-weight: 700; letter-spacing: .3px; margin-bottom: 6px; color: var(--muted); text-transform: uppercase; }
.form-row input[type=text], .form-row textarea { width: 100%; padding: 11px 13px; border-radius: var(--radius); border: 1px solid var(--linie); background: var(--grund); color: var(--ink); font-family: var(--font-body); font-size: 14.5px; min-height: 44px; }
/* Die Rahmenfarbe als einziges Fokus-Signal wirkt nur bei Textfeldern.
   Bei Checkboxen war der Browser-Fokusring dadurch ersatzlos abgeschaltet
   -- alle acht Bereichs-Checkboxen und die PFLICHT-Datenschutz-Checkbox
   hatten beim Tabben gar keine sichtbare Markierung (WCAG 2.4.7,
   Final-Review-Fund I2). Deshalb: Rahmenregel nur noch fuer Textfelder,
   plus ein echter Fokusring fuer jedes Eingabeelement. */
.form-row input[type=text]:focus, .form-row textarea:focus { border-color: var(--signal); outline: none; }
.form-row input:focus-visible { outline: 2px solid var(--akzent-dunkel); outline-offset: 2px; }
.form-row.has-error input, .form-row.has-error textarea { border-color: #B5442B; }
.field-error { color: #B5442B; font-size: 12.5px; margin-top: 6px; }
.checkbox-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.checkbox-item { display: flex; align-items: center; gap: 8px; font-size: 13.5px; font-weight: 400; text-transform: none; min-height: 44px; letter-spacing: normal; color: var(--ink); }
.form-row-checkbox .checkbox-item { align-items: flex-start; }
.hp-feld { position: absolute; left: -9999px; top: -9999px; }
.form-success { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); text-align: center; padding: var(--sp-4) 0; }
.form-success .icon { width: 40px; height: 40px; color: var(--signal); }
/* Hinweis auf teilweise verworfene Fotos (Fund I4): sachlich, gedaempft --
   kein Fehlerrot. Die Anfrage selbst ist ja angekommen. */
.form-success-hinweis { color: var(--muted); font-size: 13px; max-width: 46ch; margin-top: 4px; }
@media (max-width: 900px) { .kontakt-grid { grid-template-columns: 1fr; gap: var(--sp-4); } .checkbox-grid { grid-template-columns: 1fr; } }

.chat-widget { position: fixed; right: var(--sp-3); bottom: var(--sp-3); z-index: 80; }
.chat-toggle { width: 52px; height: 52px; border-radius: var(--radius); background: var(--signal); color: var(--ink); border: 1px solid var(--signal); display: flex; align-items: center; justify-content: center; transition: background 150ms ease-out, border-color 150ms ease-out, transform 160ms var(--ease-out); }
.chat-toggle:hover { background: var(--signal-hover); border-color: var(--signal-hover); }
.chat-toggle:active { transform: scale(0.97); }
/* Fund 1 (STANDARDS.md, Occasional-UI-Zeile "Standard animation"): das
   Chat-Panel blendete bisher instant per [hidden]->display:none ein/aus.
   display lässt sich nicht animieren (kein Zwischenzustand zwischen
   "kein Element" und "Element"), deshalb bleibt display hier konstant
   flex und opacity/transform/visibility übernehmen die Optik -- Ein- und
   Ausblenden bleiben so echte Übergänge. Das `hidden`-Attribut wird von
   website.js unverändert weiter gesetzt/entfernt (initChat()), nur die
   CSS-Wirkung davon ändert sich: `.chat-panel[hidden]` hat höhere
   Spezifität (0,2,0) als die Basisregel `.chat-panel` (0,1,0) und
   überschreibt damit gezielt display/opacity/transform/visibility, statt
   sich (wie vor Task 11 schon einmal passiert) von einer gleich
   spezifischen Regel überstimmen zu lassen. visibility:hidden entfernt
   das Panel weiterhin aus dem Zugänglichkeits-/Tab-Baum (Ersatz für das
   frühere display:none), pointer-events:none hält es währenddessen echt
   unklickbar. */
.chat-panel {
  position: absolute; right: 0; bottom: 64px; width: 340px; max-height: 70vh; background: var(--grund); border-radius: var(--radius); border: 1px solid var(--linie); display: flex; flex-direction: column; overflow: hidden;
  transform-origin: bottom right;
  opacity: 1; transform: none; visibility: visible; pointer-events: auto;
  transition: opacity 220ms var(--ease-out), transform 220ms var(--ease-out), visibility 0s linear 0s;
}
.chat-panel[hidden] {
  display: flex; /* überschreibt das UA-display:none von [hidden], damit die Box für den Übergang erhalten bleibt */
  /* Bewusst NUR translateY, kein scale(): scale() verzerrt laut
     STANDARDS.md auch alle Kindelemente mit -- während des Übergangs
     wären Buttons/Eingabefeld im Panel dann kurzzeitig unter 44px
     gemessen worden (real mit Playwright aufgefallen: 43.96px/43.98px
     statt 44px, weil die Klickziel-Messung sofort nach dem Öffnen ohne
     Warten auf das Transitionsende lief). */
  opacity: 0; transform: translateY(8px); visibility: hidden; pointer-events: none;
  transition: opacity 220ms var(--ease-out), transform 220ms var(--ease-out), visibility 0s linear 220ms;
}
.chat-head { display: flex; align-items: center; justify-content: space-between; padding: 13px 16px; font-weight: 700; font-size: 13.5px; border-bottom: 2px solid var(--signal); text-transform: uppercase; }
.chat-head button { background: none; border: none; color: var(--muted); min-height: 44px; min-width: 44px; display: flex; align-items: center; justify-content: center; transition: transform 160ms var(--ease-out); }
.chat-head button:active { transform: scale(0.9); }
.chat-log { flex: 1; overflow-y: auto; padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; min-height: 180px; max-height: 320px; }
.chat-msg { max-width: 85%; padding: 9px 12px; border-radius: var(--radius); font-size: 13.5px; line-height: 1.5; }
.chat-msg-bot { background: var(--flaeche); align-self: flex-start; border-left: 2px solid var(--signal); }
.chat-msg-user { background: var(--signal); color: var(--ink); align-self: flex-end; }
.chat-quick { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 16px 10px; }
.chat-quick button { font-size: 11.5px; padding: 6px 10px; border-radius: var(--radius); border: 1px solid var(--linie); background: transparent; color: var(--muted); min-height: 44px; display: inline-flex; align-items: center; transition: border-color 150ms ease-out, color 150ms ease-out, transform 160ms var(--ease-out); }
.chat-quick button:hover { border-color: var(--signal); color: var(--akzent-hover); }
.chat-quick button:active { transform: scale(0.97); }
.chat-input-row { display: flex; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--linie); }
.chat-input-row input { flex: 1; border: 1px solid var(--linie); border-radius: var(--radius); padding: 9px 12px; font-size: 13.5px; min-height: 44px; }
.chat-input-row button { width: 44px; height: 44px; border-radius: var(--radius); border: 1px solid var(--signal); background: var(--signal); color: var(--ink); display: flex; align-items: center; justify-content: center; flex-shrink: 0; transition: transform 160ms var(--ease-out); }
.chat-input-row button:active { transform: scale(0.97); }
@media (max-width: 520px) { .chat-panel { width: calc(100vw - 24px); right: -4px; } }

.reveal { opacity: 0; transform: translateY(14px); transition: opacity 500ms var(--ease-out), transform 500ms var(--ease-out); }
.reveal.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  html { scroll-behavior: auto; }
  /* Fund 1/2/3: Bewegungsreduktion behält den (nützlichen) Opacity-Fade,
     entfernt aber jede Verschiebung/Skalierung -- gemäß STANDARDS.md
     ("Reduced motion means fewer and gentler animations, not zero"). */
  .btn:active, .chat-toggle:active, .chat-head button:active, .chat-quick button:active, .chat-input-row button:active, .burger:active {
    transform: none;
  }
  /* Fund M1 (Abschluss-Review Etappe 6): "visibility 0s linear" OHNE
     Verzoegerung liess visibility beim SCHLIESSEN sofort auf hidden
     springen -- das Element ist damit ab Millisekunde 0 nicht mehr
     gerendert und vom versprochenen 150ms-Fade sieht niemand etwas. Der
     Fade war also faktisch doch wieder ein hartes Ausblenden, genau das,
     was der Kommentar oben ausschliessen wollte. Deshalb wie in den
     Regeln ohne Bewegungsreduktion (Zeile ~151/~305/~316) getrennt nach
     Richtung: der ZIELzustand eines Uebergangs bestimmt dessen
     transition, also
       - Zustand "offen"       -> visibility sofort (0s Verzoegerung),
                                  damit der Einblend-Fade von Anfang an
                                  sichtbar ist,
       - Zustand "geschlossen" -> visibility erst nach 150ms, also nach
                                  Ende des Ausblend-Fades.
     Ein gemeinsamer Selektor fuer beide Zustaende (wie urspruenglich
     vorgeschlagen) wuerde die 150ms auch dem Oeffnen verpassen und das
     Panel dort erst NACH dem Fade sichtbar machen. */
  .chat-panel {
    transition: opacity 150ms ease, visibility 0s linear 0s;
    transform: none;
  }
  .chat-panel[hidden] {
    transition: opacity 150ms ease, visibility 0s linear 150ms;
    transform: none;
  }
  .mobile-nav {
    transition: opacity 150ms ease, visibility 0s linear 150ms;
    transform: none;
  }
  .mobile-nav.open {
    transition: opacity 150ms ease, visibility 0s linear 0s;
    transform: none;
  }
}

/* ===================== ANFRAGE-TOOL (Task 3) =====================
   Adaptiert aus anfrage-tool-referenz-2026-08-30-v1.html (Zeilen
   32-110), auf die echten Projekt-Variablen uebertragen (--akzent-dunkel
   statt --akzent, --font-head/--font-body/--font-mono statt harter
   Familiennamen, --ease-out statt "ease-out" wortwoertlich). Buttons
   nutzen bewusst die bestehenden .btn/.btn-primary/.btn-outline-Klassen
   der Website statt einer zweiten, parallelen Button-Optik wie in der
   Referenzdatei (".btn.sekundaer") -- ein Design-System reicht. */
.tool { max-width: 100%; }
.tool-noscript { color: var(--muted); font-size: 14px; }
.tool-noscript a { color: var(--akzent-hover); font-weight: 600; }
.fortschritt { display: flex; align-items: center; gap: 14px; margin: 0 0 var(--sp-2); }
.fortschritt .schritt-label { font-family: var(--font-mono); font-size: 12px; letter-spacing: .12em; color: var(--muted); white-space: nowrap; }
.balken { flex: 1; height: 2px; background: var(--linie); }
.balken i { display: block; height: 2px; background: var(--signal); width: 0; transition: width .2s var(--ease-out); }
.schritt { display: none; padding: var(--sp-3) 0 4px; }
.schritt.aktiv { display: block; animation: tool-auf .2s var(--ease-out); }
@keyframes tool-auf { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  /* Bewusst auf ".tool"/".tool *" statt eines seitenweiten "*"-Selektors
     beschraenkt -- ein blanket-"*{transition-duration:0s!important}" wie
     in der Referenzdatei wuerde sonst auch die bereits korrekten,
     bereits getesteten reduced-motion-Uebergaenge von Chat-Panel und
     mobilem Menue (Etappe 6) lahmlegen. */
  .tool .schritt.aktiv { animation: none; }
  .tool, .tool * { transition-duration: 0s !important; }
}
.frage { font-family: var(--font-head); font-weight: 700; font-size: clamp(19px, 3vw, 24px); line-height: 1.2; margin-bottom: 6px; }
.tool .hinweis { color: var(--muted); font-size: 14px; margin-bottom: 18px; }

.kacheln { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; }
.kachel { position: relative; border: 1px solid var(--linie); border-radius: var(--radius); background: var(--grund); cursor: pointer; text-align: left; padding: 0; overflow: hidden; min-height: 44px; transition: border-color .2s var(--ease-out); }
.kachel:hover { border-color: var(--akzent-dunkel); }
.kachel:focus-visible { outline: 2px solid var(--akzent-dunkel); outline-offset: 2px; }
/* 04.09.2026 (Kundenwunsch): Token statt Foto -- CI-Flaeche mit dem
   Leistungs-Icon in Akzent dunkel (4.37:1 auf --flaeche). */
.kachel .bild { display: flex; align-items: center; justify-content: center; position: relative; height: 78px; background: var(--flaeche); overflow: hidden; color: var(--akzent-dunkel); }
.kachel .bild.token .icon { width: 30px; height: 30px; stroke-width: 1.3; }
.kachel[aria-pressed="true"] .bild.token { background: var(--signal); color: var(--ink); }
.kachel .bild img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* 06.09.2026: Leistungs-Symbole als Bild in der Kachel -- NICHT flaechenfuellend
   (object-fit: contain, 44 px), im gewaehlten Zustand schwarz auf tuerkis. */
.kachel .bild.token img { width: 44px; height: 44px; object-fit: contain; }
.kachel[aria-pressed="true"] .bild.token img { filter: brightness(0); }
/* Farbe hier bewusst --akzent-dunkel statt --signal: die Kachel-Flaeche
   dahinter ist --flaeche (kein --grund), darauf erreicht --signal nur
   2.89:1 -- unter der 3:1-Schwelle, die selbst grosser/fetter Text
   braucht (siehe tests/test_kontrast.py). --akzent-dunkel schafft dort
   4.37:1. */
.kachel .bild .mono-gramm { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-family: var(--font-head); font-weight: 700; font-size: 24px; color: var(--akzent-dunkel); }
.kachel .bild img + .mono-gramm { display: none; }
.kachel .zeile { display: flex; align-items: baseline; gap: 8px; padding: 9px 11px; }
.kachel .kuerzel { font-family: var(--font-mono); font-size: 11px; color: var(--muted); border: 1px solid var(--linie); border-radius: var(--radius); padding: 1px 5px; }
.kachel .name { font-weight: 500; font-size: 13.5px; line-height: 1.25; }
.kachel[aria-pressed="true"] { border-color: var(--signal); box-shadow: inset 0 0 0 1px var(--signal); }
/* Ausgewaehlter Zustand: der Rahmen/Schatten darf --signal bleiben (reine
   UI-Kontur, WCAG-1.4.11-Schwelle 3:1, mit --grund/--flaeche erfuellt),
   aber die weisse Beschriftung im Badge braucht 4.5:1 (kleiner Text,
   11px) -- weiss auf --signal liefert nur 3.14:1, deshalb hier ebenfalls
   --akzent-dunkel statt --signal als Badge-Hintergrund. */
.kachel[aria-pressed="true"] .kuerzel { background: var(--signal); border-color: var(--signal); color: var(--ink); }

.optionen { display: grid; gap: 8px; max-width: 420px; }
.option { display: flex; align-items: center; gap: 12px; border: 1px solid var(--linie); border-radius: var(--radius); background: var(--grund); padding: 11px 14px; cursor: pointer; font-size: 14.5px; font-weight: 500; font-family: var(--font-body); text-align: left; min-height: 46px; color: var(--ink); transition: border-color .2s var(--ease-out); }
.option:hover { border-color: var(--akzent-dunkel); }
.option:focus-visible { outline: 2px solid var(--akzent-dunkel); outline-offset: 2px; }
.option .kuerzel { font-family: var(--font-mono); font-size: 11px; color: var(--muted); border: 1px solid var(--linie); border-radius: var(--radius); padding: 1px 6px; }
/* aria-checked statt aria-pressed: .option ist eine echte Einfachauswahl
   (role="radio" in einer role="radiogroup", siehe index.html) -- die
   Auswahl-Optik haengt deshalb am Radio-Attribut, nicht an aria-pressed
   (Review-Fund, siehe Task-3-Report). */
.option[aria-checked="true"] { border-color: var(--signal); box-shadow: inset 0 0 0 1px var(--signal); }
/* Gleicher Kontrast-Grund wie bei .kachel[aria-pressed="true"] .kuerzel
   oben: weiss auf --signal ist nur 3.14:1, zu wenig fuer kleinen Text. */
.option[aria-checked="true"] .kuerzel { background: var(--signal); border-color: var(--signal); color: var(--ink); }

.tool label.feld-label { display: block; font-family: var(--font-mono); font-size: 11.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin: 16px 0 6px; }
.tool input[type=text], .tool input[type=tel], .tool input[type=email], .tool textarea { width: 100%; max-width: 480px; border: 1px solid var(--linie); border-radius: var(--radius); padding: 11px 13px; font: inherit; color: var(--ink); background: var(--grund); min-height: 44px; }
.tool textarea { min-height: 100px; resize: vertical; }
.tool input:focus-visible, .tool textarea:focus-visible { outline: 2px solid var(--akzent-dunkel); outline-offset: 2px; }

.upload { border: 1px dashed var(--linie); border-radius: var(--radius); padding: 20px; max-width: 480px; text-align: center; color: var(--muted); cursor: pointer; font-size: 14px; display: block; min-height: 44px; transition: border-color .2s var(--ease-out); position: relative; }
.upload:hover { border-color: var(--akzent-dunkel); }
.upload:focus-within { outline: 2px solid var(--akzent-dunkel); outline-offset: 2px; }
/* Visuell versteckt statt display:none -- damit das Feld (anders als in
   der Referenzdatei) per Tab erreichbar und mit der Tastatur (Enter/
   Leertaste oeffnet den Dateidialog) bedienbar bleibt. */
.upload input { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.tool-vorschau { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.tool-vorschau .thumb { position: relative; width: 76px; height: 76px; border: 1px solid var(--linie); border-radius: var(--radius); overflow: hidden; }
.tool-vorschau .thumb img { width: 100%; height: 100%; object-fit: cover; }
.tool-vorschau .thumb button { position: absolute; top: 2px; right: 2px; border: 0; background: var(--ink); color: #fff; font-family: var(--font-mono); font-size: 11px; width: 20px; height: 20px; border-radius: var(--radius); cursor: pointer; line-height: 1; }

.tool .nav-zeile { display: flex; align-items: center; gap: 14px; margin-top: 22px; flex-wrap: wrap; }
.enter-tipp { font-family: var(--font-mono); font-size: 11.5px; color: var(--muted); }
.optionen-kontaktweg { margin-bottom: 6px; }

.tool-summe { border-top: 1px solid var(--linie); max-width: 540px; margin-top: 26px; }
.tool-summe .zeile { display: flex; gap: 16px; padding: 10px 0; border-bottom: 1px solid var(--linie); font-size: 14px; }
.tool-summe .zeile b { font-family: var(--font-mono); font-weight: 400; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); flex: 0 0 140px; padding-top: 2px; }
.ds-zeile { margin: 18px 0 4px; max-width: 540px; font-size: 13.5px; color: var(--muted); }
/* min-height:44px auf dem Label selbst (nicht nur der 18x18px-Checkbox) --
   dasselbe Muster wie .checkbox-item im frueheren statischen Formular,
   damit die ganze Zeile (nicht nur die winzige Box) das echte Klickziel
   ist (WCAG 2.5.8, Projekt-Minimum 44px, siehe tests/e2e
   test_klickziele_mindestens_44px_*). */
.ds-zeile-label { display: flex; gap: 10px; align-items: flex-start; min-height: 44px; cursor: pointer; }
.ds-zeile input { width: 18px; height: 18px; margin-top: 2px; accent-color: var(--akzent-dunkel); flex-shrink: 0; }
.ds-zeile input:focus-visible { outline: 2px solid var(--akzent-dunkel); outline-offset: 2px; }
.ds-zeile a { color: var(--akzent-dunkel); }

.tool .erfolg h3 { font-size: clamp(20px, 3vw, 26px); margin-bottom: 8px; }
.tool .erfolg p { color: var(--muted); }
.tool .erfolg-btn { margin-top: 18px; }
@media (max-width: 520px) { .kacheln { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); } }
/* 21.09.2026 Smartphone-Check: lange Leistungsnamen ("Hausmeisterservice",
   "Objektbetreuung") wurden in den Kacheln abgeschnitten -- ein Flex-Kind
   schrumpft nicht unter seine Wortbreite (min-width:auto) und .kachel hat
   overflow:hidden. Umbruch im Wort erlauben, Kuerzel nicht schrumpfen lassen,
   auf dem Handy etwas kleinere Schrift. "oder Enter druecken" gibt es auf
   dem Handy nicht. */
.kachel .name { min-width: 0; overflow-wrap: break-word; hyphens: manual; }
.kachel .kuerzel { flex-shrink: 0; }
@media (max-width: 520px) {
  .kachel .name { font-size: 12.5px; }
  .kachel .zeile { padding: 8px 9px; gap: 6px; }
}
@media (max-width: 700px), (hover: none) and (pointer: coarse) { .enter-tipp { display: none; } }

.rechtstext-inner { max-width: 760px; }
.rechtstext h2 { font-size: 17px; margin: var(--sp-4) 0 var(--sp-1); }
.rechtstext p { color: var(--muted); font-size: 14.5px; line-height: 1.7; margin-bottom: var(--sp-2); }
.rechtstext-hinweis { margin-top: var(--sp-5); padding: var(--sp-2); background: var(--flaeche); border-left: 2px solid var(--signal); font-size: 13px; }
.rechtstext-stand { margin-top: var(--sp-4); font-size: 13px; color: var(--muted); }

/* ===================== STELLENANGEBOTE (15.09.2026, Kundenwunsch) =====
   Eigene Unterseite, editorial wie der Rest: 1-px-Linien, keine Karten-
   Schatten, eine Signalfarbe. Stellen als Liste mit Kopfzeile
   (Titel + Umfang) und zwei Textbloecken. */
.stellen-inner { max-width: 820px; }
.stellen-einleitung { color: var(--ink); font-size: 17px; line-height: 1.6; max-width: 62ch; margin-bottom: var(--sp-6); }
.stellen-liste { border-top: 1px solid var(--linie); }
.stelle { display: grid; grid-template-columns: minmax(200px, 1fr) 2fr; gap: var(--sp-3) var(--sp-5); padding: var(--sp-4) 0; border-bottom: 1px solid var(--linie); }
.stelle h2 { font-size: clamp(1.15rem, 2vw, 1.4rem); text-transform: uppercase; letter-spacing: -0.01em; line-height: 1.15; }
.stelle-umfang { font-family: var(--font-mono); font-size: 12px; letter-spacing: .06em; text-transform: uppercase; color: var(--akzent-dunkel); margin-top: 8px; }
.stelle-label { font-family: var(--font-mono); font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); margin: var(--sp-2) 0 4px; }
.stelle-label:first-child { margin-top: 0; }
.stelle-body p:not(.stelle-label) { color: var(--ink); font-size: 15px; line-height: 1.6; max-width: 60ch; }
.stelle-cta { display: flex; gap: 12px; flex-wrap: wrap; margin-top: var(--sp-3); }
.stellen-leer { color: var(--muted); padding: var(--sp-4) 0; border-top: 1px solid var(--linie); border-bottom: 1px solid var(--linie); }
.stellen-kontakt { margin-top: var(--sp-6); padding-top: var(--sp-4); }
.stellen-kontakt h2 { font-size: 17px; text-transform: uppercase; margin-bottom: var(--sp-1); }
.stellen-kontakt p { color: var(--ink); max-width: 62ch; }
.stellen-kontaktzeile { margin-top: var(--sp-2); font-weight: 600; }
.stellen-kontaktzeile a { color: var(--akzent-dunkel); text-decoration: underline; text-underline-offset: 3px; }
@media (max-width: 700px) { .stelle { grid-template-columns: 1fr; gap: var(--sp-2); } }

/* 24.09.2026: Hinweis "Umsetzung: OpusVia" -- falls verlinkt, 44 px Tippflaeche. */
.footer-cleardeck a { display: inline-flex; align-items: center; min-height: 44px; color: inherit; }
.footer-cleardeck a:hover { color: var(--akzent-hover); }
/* 24.09.2026: Widerspruchsrecht (Art. 21 DSGVO) muss hervorgehoben sein. */
.rechtstext .rechtstext-hervorgehoben { border-left: 2px solid var(--signal); padding-left: 14px; color: var(--ink); }
/* 28.09.2026 Etappe 9: <picture> fuer WebP-Fassungen ohne Einfluss aufs Layout */
picture.bild-webp { display: contents; }

/* ============================================================================
   01.10.2026 Patch-Runde 2, Etappe 8: Handy-Feinschliff und Haertung.
   Der Betrieb aendert jetzt jeden Text selbst -- nichts davon darf das Layout
   sprengen, auch nicht ein ueberlanges Wort bei 320 px Breite.
   ============================================================================ */

/* (1) Ueberlange Woerter brechen um, statt aus dem Bild zu ragen.
   Am Handy (eine Spalte) darf ein Wort ueberall brechen, wenn es sonst nicht
   in die Zeile passt. Am Desktop bleibt der Umbruch, wie er war (ein langer
   Stellentitel darf wie bisher etwas in den Spaltenabstand ragen, statt mitten
   im Wort getrennt zu werden); gehaertet werden dort gezielt die Stellen, an
   denen ein langes Wort die Seite verbreitern wuerde -- siehe (4). */
@media (max-width: 700px) { body { overflow-wrap: break-word; } }

/* (4) Gezielte Haertung gegen ueberlange Woerter (z. B. eine lange E-Mail-Adresse,
   ein Menue-Eintrag ohne Leerzeichen). Zwei Bausteine:
   a) Kein Flex-/Rasterkind wird breiter als sein Rahmen -- darin bricht das Wort um.
   b) Elemente, die selbst "flex" sind und ihren Text direkt enthalten (Knoepfe,
      Links mit Icon), brauchen "anywhere", sonst kann der Text nicht umbrechen. */
.topbar-inner > *, .hero-cta > *, .nav-zeile > *, .orte-zeile > div, .footer-links, .footer-links a,
.mini-ablauf-item, .kontakt-direct a, .stelle-cta > *, .header-actions > .btn, .rezensionen-kopf > *,
.katalog-tab, .optionen > *, .chat-quick button, .mobile-nav a, .main-nav a, .footer-cleardeck > * { max-width: 100%; }
.btn, .topbar-inner a, .topbar-inner span, .footer-links a, .kontakt-direct a, .main-nav a, .mobile-nav a,
.option, .chat-quick button, .katalog-tab .leistung-body, .footer-cleardeck > *, .rezensionen-link { overflow-wrap: anywhere; }
.btn { white-space: normal; }
.fortschritt .schritt-label { min-width: 0; overflow-wrap: anywhere; }
.hero-eyebrow { max-width: 100%; overflow-wrap: anywhere; }
.chat-head > span { min-width: 0; overflow-wrap: anywhere; }
.label-num { overflow-wrap: anywhere; }
/* Das Logo wird nie gequetscht, auch wenn Menue oder Telefonnummer viel Platz brauchen. */
.site-header .brand { flex-shrink: 0; }
.ds-zeile-label > span { min-width: 0; overflow-wrap: anywhere; }
/* Zusammenfassung im Anfrage-Tool: der Wert darf umbrechen; auf sehr schmalen
   Handys ist die Beschriftungsspalte schmaler (vorher ragte z. B.
   "Hausmeisterservice" bei 320 px 8 px aus dem Bild). */
.tool-summe .zeile span { min-width: 0; overflow-wrap: anywhere; }
.tool-summe .zeile b { overflow-wrap: anywhere; }
@media (max-width: 360px) { .tool-summe .zeile { gap: 10px; } .tool-summe .zeile b { flex-basis: 96px; } }
.chat-msg { overflow-wrap: anywhere; }
@media (max-width: 700px) { .stelle { grid-template-columns: minmax(0, 1fr); } }
/* Textbloecke: ein Wort, das laenger ist als die Zeile, bricht als letzter Ausweg um. */
.hero-content, .big-head, .katalog-text, .katalog-bild-text, .sponsor-text, .mini-ablauf-item, .kontakt-intro,
.tool, .rezension, .eg-note, .site-footer, .stellen-einleitung, .stellen-leer, .stellen-kontakt, .stelle-body, .rechtstext-inner,
.chat-panel, .karte-fallback, .hero-credit, .tile-caption { overflow-wrap: break-word; }
.hero-content, .katalog-text, .sponsor-text, .kontakt-intro, .kontakt-form-wrap, .mini-ablauf-item, .stelle-body, .footer-brand { min-width: 0; }
.btn { max-width: 100%; }
/* Lange Menue-Eintraege: das Menue darf in eine zweite Zeile gehen. */
.main-nav { flex-wrap: wrap; row-gap: 0; }
.fortschritt .schritt-label { white-space: normal; }

/* (2) Sehr schmale Handys (bis 360 px): das Anfrage-Tool war 6 px breiter als
   der Bildschirm. Kacheln und Innenabstand etwas enger. */
@media (max-width: 360px) {
  .kontakt-form-wrap { padding: var(--sp-3) var(--sp-2); }
  .kacheln { grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 8px; }
}

/* (3) Chat-Knopf (Fund 7): am Handy kleiner, Abstand zum Rand mit Safe-Area
   (Geraete mit Gestenleiste/Notch), unten Platz im Fussbereich, damit er am
   Seitenende nichts verdeckt. */
.chat-widget {
  right: calc(var(--sp-3) + env(safe-area-inset-right, 0px));
  bottom: calc(var(--sp-3) + env(safe-area-inset-bottom, 0px));
}
/* Solange das Anfrage-Tool im Bild ist, tritt der Chat-Knopf zurueck (website.js
   setzt .chat-zurueck): zwei Kontaktwege gleichzeitig verwirren, und der Knopf
   lag ueber den Kacheln. Das geoeffnete Chat-Fenster bleibt davon unberuehrt.
   Nur opacity/transform (STANDARDS.md), visibility nimmt den Knopf aus der
   Tab-Reihenfolge. */
.chat-toggle { transition: background 150ms ease-out, border-color 150ms ease-out, transform 160ms var(--ease-out), opacity 180ms var(--ease-out), visibility 0s linear 0s; }
.chat-widget.chat-zurueck .chat-toggle { opacity: 0; transform: translateY(8px); visibility: hidden; pointer-events: none; transition: opacity 180ms var(--ease-out), transform 180ms var(--ease-out), visibility 0s linear 180ms; }
@media (max-width: 700px) {
  .chat-widget { right: calc(12px + env(safe-area-inset-right, 0px)); bottom: calc(12px + env(safe-area-inset-bottom, 0px)); }
  .chat-toggle { width: 44px; height: 44px; }
  .chat-toggle .icon { width: 18px; height: 18px; }
  .chat-panel { bottom: 54px; }
  body.mit-chat .site-footer { padding-bottom: calc(68px + env(safe-area-inset-bottom, 0px)); }
}
@media (prefers-reduced-motion: reduce) {
  .chat-widget.chat-zurueck .chat-toggle { transform: none; }
}
