/* ==================================================================
   app.css — SOGERER. Uebertragen aus docs/_dev/design/design.html v1.
   ⚠ Token-Aenderungen auch in assets/css/critical.css spiegeln.
   ================================================================== */

/* ==================================================================
   LAYER-REIHENFOLGE — steuert die Kaskade, nicht die Gliederung.
   Bleibt auch in der Produktion erhalten.
   ================================================================== */
@layer reset, tokens, base, layout, components, pages, utilities, motion, responsive;

/* ==================================================================
   SCHRIFTEN — self-hosted, SIL OFL 1.1 (assets/fonts/LICENSE-OFL.txt).
   Nur diese drei Schnitte; 500 faellt auf 400/600 zurueck.
   ================================================================== */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/fonts/archivo-700.woff2") format("woff2");
}

@font-face {
  font-family: "Fira Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/fira-sans-400.woff2") format("woff2");
}

@font-face {
  font-family: "Fira Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/fira-sans-600.woff2") format("woff2");
}

/* ===== CRITICAL START — reset, tokens, base, layout ===============
   Zusätzlich kritisch, aber im components-Layer (dort einzeln markiert):
   .site-header, .hero, .btn, .brand
   ================================================================== */

/* ==================================================================
   LAYER 1/9 — RESET
   ================================================================== */
@layer reset {

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

  * {
    margin: 0;
  }

  html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
  }

  body {
    min-block-size: 100svh;
    -webkit-font-smoothing: antialiased;
  }

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

  img,
  video {
    block-size: auto;
  }

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

  button {
    background: none;
    border: 0;
  }

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

  ul[class],
  ol[class] {
    padding-inline-start: 0;
    list-style: none;
  }

  /* Der Router setzt hidden. Kein display auf .page, damit hier
     nichts überschrieben werden muss und !important entfällt. */
  [hidden] {
    display: none;
  }

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

/* ==================================================================
   LAYER 2/9 — TOKENS
   Ausschließlich Custom Properties. Rohpalette → semantische Ebene.
   ================================================================== */
@layer tokens {

  :root {

    /* -- Rohpalette --------------------------------------------------
       Asphalt: warmes Tiefdunkel, Hue 68. Der Gelbstich ist Absicht —
       er unterscheidet die Seite von Systemgrau, ohne sichtbar zu sein. */
    --asphalt-950: oklch(0.19 0.014 68);
    --asphalt-800: oklch(from var(--asphalt-950) calc(l + 0.13) c h);
    --asphalt-600: oklch(from var(--asphalt-950) calc(l + 0.31) calc(c - 0.004) h);
    --asphalt-400: oklch(from var(--asphalt-950) calc(l + 0.47) calc(c - 0.006) h);

    /* Kalk: gebrochenes Weiß, Chroma 0.004 — bewusst kein Creme. */
    --kalk-50:  oklch(0.975 0.004 85);
    --kalk-100: oklch(from var(--kalk-50) calc(l - 0.03) c h);
    --kalk-200: oklch(from var(--kalk-50) calc(l - 0.075) c h);

    /* Grün: Hauptleistung ist Grau- und Grünpflege (Kundenentscheidung
       16.09.2026, davor Blaumann-Indigo). Ein Moos-/Waldgrün, dunkler und
       weniger bunt als jedes Signalgrün — gleiche Helligkeiten wie das
       frühere Blau, damit alle Kontraste erhalten bleiben. */
    --gruen-700: oklch(0.34 0.090 148);
    --gruen-600: oklch(0.43 0.110 148);
    --gruen-400: oklch(0.68 0.110 148);

    /* Gold/Messing: Erbe der alten Website (schwarz/gold). Ausschließlich
       als sparsamer Akzent über --c-accent, nie als Fläche und nie für
       Text unter --fs-1 (Kontrast). Noch ohne Verwendung — bewusst. */
    --gold-600: oklch(0.62 0.120 80);
    --gold-500: oklch(0.72 0.130 85);

    /* Bildplatzhalter: sehr helles Rechteck mit minimal dunkleren
       Diagonalen. Steht überall, wo später ein Bild oder Video kommt. */
    --c-ph-base: light-dark(oklch(0.955 0.002 85), oklch(0.245 0.006 68));
    --c-ph-line: light-dark(oklch(0.855 0.004 85), oklch(0.345 0.010 68));

    /* Ziegel: ausschließlich Signal. Einziger zulässiger Einsatzort:
       Formularfehler (Sendefehler-Callout). Nie dekorativ. */
    /* Helligkeiten so gewählt, dass Fehlermeldungen in beiden Themes
       AA erreichen: 5,25:1 auf getönter Fläche hell, 4,66:1 dunkel.
       Bei 0.55 / 0.67 lagen sie mit 4,42 bzw. 3,99 darunter. */
    --ziegel-500: oklch(0.51 0.145 33);
    --ziegel-400: oklch(from var(--ziegel-500) calc(l + 0.20) c h);

    /* -- Semantische Ebene: Light und Dark in einem Schritt ----------
       color-scheme steht bewusst auf "light", nicht auf "light dark":
       Die Systempräferenz wird ignoriert, Standard ist immer hell.
       Dark kommt ausschließlich über den Toggle. Das vermeidet auch
       ein Umspringen beim Laden. */
    color-scheme: light;

    --c-bg:          light-dark(var(--kalk-50), var(--asphalt-950));
    --c-surface:     light-dark(var(--kalk-100), var(--asphalt-800));
    --c-surface-alt: light-dark(var(--kalk-200), var(--asphalt-800));
    --c-text:        light-dark(var(--asphalt-950), var(--kalk-100));
    --c-text-muted:  light-dark(var(--asphalt-600), var(--asphalt-400));
    --c-brand:       light-dark(var(--gruen-600), var(--gruen-400));
    --c-brand-hover: light-dark(var(--gruen-700), var(--gruen-600));
    --c-brand-ink:   light-dark(var(--kalk-50), var(--asphalt-950));
    --c-accent:      light-dark(var(--gold-600), var(--gold-500));
    --c-signal:      light-dark(var(--ziegel-500), var(--ziegel-400));
    --c-focus:       light-dark(var(--gruen-600), var(--gruen-400));

    /* Karte Einsatzgebiet: Zonenflaeche ruhig, aktiv deutlich. */
    --c-karte-zone:       color-mix(in oklch, var(--c-brand) 7%, transparent);
    --c-karte-zone-aktiv: color-mix(in oklch, var(--c-brand) 24%, transparent);
    --c-karte-hover:      color-mix(in oklch, var(--c-brand) 14%, var(--c-bg));

    /* Bewusst der vom Auftraggeber vorgegebene Name, obwohl er vom
       --c-*-Schema abweicht. Wiedererkennbarkeit schlägt Systematik. */
    --image-placeholder-gradient: repeating-linear-gradient(
      45deg,
      var(--c-ph-base) 0 6px,
      var(--c-ph-line) 6px 7px
    );

    /* Der Platzhalter wird bewusst OHNE Schleier eingesetzt — sonst
       wirkt er auf jeder Fläche unterschiedlich stark. Der Schleier
       hier ist für später: sobald echte Fotos einziehen, kommt er
       unter den Text, damit der Kontrast nicht kippt. */
    --c-scrim: light-dark(
      color-mix(in oklch, var(--kalk-50) 72%, transparent),
      color-mix(in oklch, var(--asphalt-950) 72%, transparent)
    );

    --c-border: light-dark(
      color-mix(in oklch, var(--asphalt-950) 14%, transparent),
      color-mix(in oklch, var(--kalk-100) 20%, transparent)
    );
    --c-border-strong: light-dark(
      color-mix(in oklch, var(--asphalt-950) 30%, transparent),
      color-mix(in oklch, var(--kalk-100) 38%, transparent)
    );

    /* -- Typografie -------------------------------------------------- */
    --ff-display: "Archivo", ui-sans-serif, system-ui, sans-serif;
    --ff-body: "Fira Sans", ui-sans-serif, system-ui, sans-serif;

    --fw-400: 400;
    --fw-500: 500;
    --fw-600: 600;
    --fw-700: 700;

    /* Verhältnis 1.2 mobil → 1.25 Desktop. Fließtext 17px am Desktop,
       weil das hier B2B-Lesetext ist und keine App-Oberfläche. */
    --fs--1: clamp(0.8125rem, 0.79rem + 0.11vw, 0.875rem);
    --fs-0:  clamp(1rem, 0.97rem + 0.15vw, 1.0625rem);
    --fs-1:  clamp(1.125rem, 1.08rem + 0.23vw, 1.25rem);
    --fs-2:  clamp(1.3125rem, 1.24rem + 0.37vw, 1.5rem);
    --fs-3:  clamp(1.5rem, 1.38rem + 0.60vw, 1.875rem);
    --fs-4:  clamp(1.75rem, 1.55rem + 1.00vw, 2.375rem);
    --fs-5:  clamp(2.125rem, 1.79rem + 1.68vw, 3.125rem);
    --fs-6:  clamp(2.5rem, 1.95rem + 2.75vw, 4.25rem);

    --lh-tight: 1.1;
    --lh-snug: 1.3;
    --lh-base: 1.6;

    --ls-wide: 0.08em;
    --ls-tight: -0.01em;

    /* -- Raum -------------------------------------------------------- */
    --s-0: 0;
    --s-1: 0.25rem;
    --s-2: 0.5rem;
    --s-3: 0.75rem;
    --s-4: 1rem;
    --s-5: 1.5rem;
    --s-6: 2rem;
    --s-7: 3rem;
    --s-8: 4rem;
    --s-9: 6rem;
    --s-10: 8rem;
    --s-11: 12rem;
    --s-12: 16rem;

    /* Sektionsabstand fluid statt per Breakpoint. */
    --s-section: clamp(3.5rem, 8vw, 7rem);

    /* -- Maße -------------------------------------------------------- */
    /* Höhe der Kopfzeile. Der Hero rechnet damit, deshalb ein Token
       und keine Zahl an zwei Stellen. Wird ab lg überschrieben. */
    --h-header: 4rem;

    --w-prose: 68ch;
    --w-content: 72rem;
    --w-gutter: clamp(1rem, 4vw, 2.5rem);

    /* Alles eckig. Die Tokens bleiben als einzige Stellschraube
       bestehen, falls das je zurückgedreht werden soll. */
    --r-1: 0;
    --r-2: 0;
    --r-3: 0;

    --bw-1: 1px;
    --bw-2: 2px;
    --bw-3: 3px;

    --sh-1: 0 1px 2px oklch(from var(--asphalt-950) l c h / 0.08);
    --sh-2: 0 6px 20px -6px oklch(from var(--asphalt-950) l c h / 0.16);

    --z-base: 1;
    --z-sticky: 100;
    --z-overlay: 200;
    --z-skip: 300;

    /* -- Bewegung ---------------------------------------------------- */
    --dur-1: 120ms;
    --dur-2: 200ms;
    --dur-3: 320ms;
    --dur-4: 700ms;

    --ease-out: cubic-bezier(0.2, 0, 0, 1);
    --ease-in-out: cubic-bezier(0.6, 0, 0.2, 1);
  }

  /* Der Toggle erzwingt ein Schema. Die Tokens bleiben unverändert —
     es gibt bewusst keine zweite Farbwelt. */
  :root[data-theme="light"] {
    color-scheme: light;
  }

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

/* ==================================================================
   LAYER 3/9 — BASE
   Nur Element-Selektoren.
   ================================================================== */
@layer base {

  html {
    scroll-behavior: smooth;
  }

  body {
    background-color: var(--c-bg);
    color: var(--c-text);
    font-family: var(--ff-body);
    font-size: var(--fs-0);
    font-weight: var(--fw-400);
    line-height: var(--lh-base);
  }

  h1,
  h2,
  h3,
  h4 {
    font-family: var(--ff-display);
    font-weight: var(--fw-700);
    line-height: var(--lh-snug);
    text-wrap: balance;
  }

  h1 {
    font-size: var(--fs-6);
    line-height: var(--lh-tight);
    letter-spacing: var(--ls-tight);
  }

  h2 {
    font-size: var(--fs-4);
  }

  h3 {
    font-size: var(--fs-2);
  }

  h4 {
    font-size: var(--fs-1);
  }

  p {
    text-wrap: pretty;
  }

  a {
    color: var(--c-brand);
    text-decoration-thickness: max(0.06em, 1px);
    text-underline-offset: 0.18em;
    transition: color var(--dur-1) var(--ease-out);

    &:hover {
      color: var(--c-brand-hover);
    }
  }

  :focus-visible {
    outline: var(--bw-3) solid var(--c-focus);
    outline-offset: 2px;
  }

  strong {
    font-weight: var(--fw-600);
  }

  hr {
    border: 0;
    border-block-start: var(--bw-1) solid var(--c-border);
  }

  address {
    font-style: normal;
  }

  table {
    inline-size: 100%;
    border-collapse: collapse;
    font-size: var(--fs--1);
  }

  th,
  td {
    padding: var(--s-3);
    border-block-end: var(--bw-1) solid var(--c-border);
    text-align: start;
    vertical-align: top;
  }

  th {
    font-weight: var(--fw-600);
  }

  /* Fließtextlisten behalten ihre Marker — nur Listen mit Klasse
     werden zurückgesetzt (siehe reset). */
  ul:not([class]),
  ol:not([class]) {
    padding-inline-start: 1.25em;

    & li + li {
      margin-block-start: var(--s-2);
    }
  }

  figure {
    margin: 0;
  }

  figcaption {
    margin-block-start: var(--s-3);
    color: var(--c-text-muted);
    font-size: var(--fs--1);
  }

  details {
    border-block-end: var(--bw-1) solid var(--c-border);
  }

  summary {
    cursor: pointer;
  }

  fieldset {
    margin: 0;
    padding: 0;
    border: 0;
  }

  legend {
    padding: 0;
    font-family: var(--ff-display);
    font-size: var(--fs-1);
    font-weight: var(--fw-600);
  }
}

/* ==================================================================
   LAYER 4/9 — LAYOUT
   Layout-Primitives. Kennen keine Farben und keine Inhalte.
   ================================================================== */
@layer layout {

  /* ---------------------------------------------------------------
     l-container — horizontale Begrenzung + Seitenabstand.
     Breite über --w, damit derselbe Baustein drei Maße bedienen kann.
     Verwendet auf: allen Seiten.
     --------------------------------------------------------------- */
  .l-container {
    --w: var(--w-content);

    inline-size: min(100%, var(--w));
    margin-inline: auto;
    padding-inline: var(--w-gutter);
  }

  .l-container--prose {
    --w: calc(var(--w-prose) + 2 * var(--w-gutter));
  }

  /* ---------------------------------------------------------------
     l-stack — vertikaler Rhythmus über das Nachbar-Prinzip.
     Abstand über --gap überschreibbar.
     --------------------------------------------------------------- */
  .l-stack {
    --gap: var(--s-4);

    display: flex;
    flex-direction: column;

    & > * + * {
      margin-block-start: var(--gap);
    }
  }

  .l-stack--tight {
    --gap: var(--s-2);
  }

  .l-stack--loose {
    --gap: var(--s-6);
  }

  /* ---------------------------------------------------------------
     l-cluster — horizontale Gruppe, die umbricht. Für Buttongruppen,
     Navigation, Meta-Zeilen.
     --------------------------------------------------------------- */
  .l-cluster {
    --gap: var(--s-4);

    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--gap);
  }

  .l-cluster--tight {
    --gap: var(--s-2);
  }

  /* ---------------------------------------------------------------
     l-grid — auto-fit ohne Breakpoint. --min steuert, ab wann
     umgebrochen wird. Ein Breakpoint ist für Layoutwechsel da,
     nicht für Größenverläufe.
     --------------------------------------------------------------- */
  .l-grid {
    --min: 17rem;
    --gap: var(--s-5);

    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(var(--min), 100%), 1fr));
    gap: var(--gap);
  }

  .l-grid--wide-cells {
    --min: 22rem;
  }

  .l-grid--narrow-cells {
    --min: 12rem;
  }

  /* ---------------------------------------------------------------
     l-split — zwei Spalten ab md. Basis ist einspaltig.
     Verhältnis über --ratio.
     --------------------------------------------------------------- */
  .l-split {
    --gap: var(--s-7);
    --ratio: 1fr 1fr;

    display: grid;
    gap: var(--gap);
    align-items: start;
  }

  .l-split--text-media {
    --ratio: 1fr 1.15fr;
  }

  /* ---------------------------------------------------------------
     l-section — vertikaler Sektionsabstand. Eine einzige Stelle,
     an der Sektionsabstand definiert wird — verhindert die üblichen
     Margin-Kollisionen zwischen Sektionstypen.
     --------------------------------------------------------------- */
  .l-section {
    padding-block: var(--s-section);
  }

  .l-section--tinted {
    background-color: var(--c-surface);
  }

  /* SIGNATURE: gewölbte Oberkante am Sektionsübergang — der Kehrbogen
     als Bauteil. Elliptischer Radius: waagrecht die volle Breite,
     senkrecht wenige rem. Kein Rand, kein Bild, kein Extra-Markup.
     Bauteile bleiben eckig, nur die Schichten der Seite sind gewölbt. */
  .l-section--arc {
    border-start-start-radius: 100% clamp(2rem, 6vw, 5rem);
    border-start-end-radius: 100% clamp(2rem, 6vw, 5rem);
  }
}

/* ===== CRITICAL END ================================================
   Ende des zusammenhängenden Critical-CSS-Bereichs.
   Zusätzlich ins Critical CSS gehören die unten mit "→ CRITICAL CSS"
   markierten Komponenten (.skip, .brand, .site-header, .btn, .hero) —
   sie liegen im components-Layer und lassen sich deshalb nicht in
   denselben zusammenhängenden Block legen. Beim Extrahieren des
   Critical CSS also: dieser Block + die fünf markierten Komponenten.
   Siehe Handoff §5.
   ================================================================== */

/* ==================================================================
   LAYER 5/9 — COMPONENTS
   ================================================================== */
@layer components {

  /* ---------------------------------------------------------------
     u-skip — Sprunglink. Sichtbar nur bei Tastaturfokus.
     → CRITICAL CSS
     --------------------------------------------------------------- */
  .skip {
    position: absolute;
    inset-block-start: var(--s-2);
    inset-inline-start: var(--s-2);
    z-index: var(--z-skip);
    padding: var(--s-3) var(--s-4);
    background-color: var(--c-brand);
    color: var(--c-brand-ink);
    font-weight: var(--fw-600);
    text-decoration: none;
    transform: translateY(-200%);

    &:focus-visible {
      transform: translateY(0);
    }
  }

  /* ---------------------------------------------------------------
     brand — Wortmarke + Kehrbogen-Signet.
     Archivo 700 in Versalien mit weitem Tracking: liest sich wie ein
     Typenschild an einer Maschine, nicht wie ein erdachtes Logo.
     Verwendet in: Header, Footer.
     → CRITICAL CSS
     --------------------------------------------------------------- */
  .brand {
    display: inline-flex;
    align-items: center;
    gap: var(--s-3);
    color: var(--c-text);
    text-decoration: none;
    transition: color var(--dur-1) var(--ease-out);

    &:hover {
      color: var(--c-brand);
    }
  }

  .brand__mark {
    inline-size: 2rem;
    block-size: 2rem;
    flex: none;
    color: var(--c-brand);
    fill: none;
    stroke: currentColor;
    stroke-width: 4;
    stroke-linecap: round;
  }

  .brand__name {
    font-family: var(--ff-display);
    font-size: var(--fs-1);
    font-weight: var(--fw-700);
    letter-spacing: var(--ls-wide);
    line-height: 1;
  }

  .brand__services {
    display: none;
    color: var(--c-text-muted);
    font-size: var(--fs--1);
    letter-spacing: 0.02em;
  }

  /* ---------------------------------------------------------------
     site-header — sticky, mit Unterkante statt Schatten.
     Abhängigkeit: [data-js="nav-toggle"] und #site-nav im JS.
     → CRITICAL CSS
     --------------------------------------------------------------- */
  .site-header {
    position: sticky;
    inset-block-start: 0;
    z-index: var(--z-sticky);
    background-color: var(--c-bg);
    border-block-end: var(--bw-1) solid var(--c-border);
  }

  .site-header__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-4);
    min-block-size: var(--h-header);
  }

  .site-header__actions {
    display: flex;
    align-items: center;
    gap: var(--s-2);
  }

  /* Telefonlink erst ab lg: mobil reicht der Platz nicht neben Marke,
     Theme-Toggle und Menü; die Nummer steht im Footer-Rückruf jeder
     Seite (Fabian, 09/2026). Einblenden im responsive-Layer. */
  .site-header__tel {
    display: none;
  }

  /* ---------------------------------------------------------------
     nav — mobil aufklappbar, ab lg horizontal.
     Zustand ausschließlich über aria-expanded am Button, kein .is-open.
     --------------------------------------------------------------- */
  .nav {
    display: none;
    padding-block: var(--s-4) var(--s-6);
    border-block-start: var(--bw-1) solid var(--c-border);
  }

  .nav[data-open="true"] {
    display: block;
  }

  .nav__list {
    display: flex;
    flex-direction: column;
    gap: var(--s-1);
  }

  .nav__link {
    display: block;
    padding: var(--s-3) var(--s-2);
    color: var(--c-text);
    font-weight: var(--fw-500);
    text-decoration: none;
    transition: background-color var(--dur-1) var(--ease-out),
                color var(--dur-1) var(--ease-out);

    &:hover {
      background-color: var(--c-surface);
      color: var(--c-brand);
    }

    /* Aktivzustand über ARIA, nicht über eine Extraklasse.
       Der Bogen als Marker statt gerader Unterstreichung. */
    &[aria-current="page"] {
      color: var(--c-brand);
      font-weight: var(--fw-600);
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 8'%3E%3Cpath d='M2 6 Q20 0 38 6' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
      background-repeat: no-repeat;
      background-position: left 0.5rem bottom 0.35rem;
      background-size: 2.5rem 0.5rem;
    }
  }

  /* ---------------------------------------------------------------
     btn — Basis für alle Aktionen.
     Varianten: --primary (eine pro Ansicht), --ghost, --plain.
     → CRITICAL CSS
     --------------------------------------------------------------- */
  .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--s-2);
    /* min-block-size statt height, damit umbrechende Labels
       nicht abgeschnitten werden. */
    min-block-size: 2.75rem;
    padding: var(--s-3) var(--s-5);
    border: var(--bw-2) solid transparent;
    font-family: var(--ff-body);
    font-size: var(--fs-0);
    font-weight: var(--fw-600);
    line-height: 1.2;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    transition: background-color var(--dur-1) var(--ease-out),
                border-color var(--dur-1) var(--ease-out),
                color var(--dur-1) var(--ease-out);

    & .ico {
      font-size: 1.15em;
      flex: none;
    }

    &[aria-disabled="true"] {
      opacity: 0.55;
      pointer-events: none;
    }
  }

  .btn--primary {
    background-color: var(--c-brand);
    border-color: var(--c-brand);
    color: var(--c-brand-ink);

    &:hover {
      background-color: var(--c-brand-hover);
      border-color: var(--c-brand-hover);
      color: var(--c-brand-ink);
    }
  }

  .btn--ghost {
    border-color: var(--c-border-strong);
    color: var(--c-text);

    &:hover {
      background-color: var(--c-surface-alt);
      border-color: var(--c-brand);
      color: var(--c-brand);
    }
  }

  /* Kopfzeile: ohne Umrandung. */
  .btn--bare {
    padding-inline: var(--s-3);
    border-color: transparent;
    color: var(--c-text);

    &:hover {
      background-color: var(--c-surface);
      color: var(--c-brand);
    }
  }

  .btn--block {
    inline-size: 100%;
  }

  /* ---------------------------------------------------------------
     icon-btn — quadratische Schaltfläche für Theme-Toggle und Menü.
     Mindestens 44px, damit die Klickfläche auf Touch trägt.
     --------------------------------------------------------------- */
  .icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 2.75rem;
    block-size: 2.75rem;
    border: 0;
    color: var(--c-text);
    cursor: pointer;
    transition: background-color var(--dur-1) var(--ease-out),
                color var(--dur-1) var(--ease-out);

    & .ico {
      font-size: 1.25rem;
    }

    &:hover {
      background-color: var(--c-surface);
      color: var(--c-brand);
    }
  }

  /* ---------------------------------------------------------------
     ico — Icon aus der Bootstrap-Icons-Schrift (Entscheidung 25.07.,
     siehe docs/PROJECT.md): <i class="ico bi bi-name" aria-hidden="true">.
     Immer aria-hidden, Bedeutung trägt das umgebende Element.
     Größe läuft über font-size, Glyphen liefert bootstrap-icons.css.
     --------------------------------------------------------------- */
  .ico {
    display: inline-block;
    font-size: 1.25rem;
    line-height: 1;
  }

  /* ---------------------------------------------------------------
     bogen — SIGNATURE-ELEMENT.
     Ein Kreissegment, konstante Strichstärke, ein Pfad, eine Farbe.
     Damit stickbar, lasertauglich und bei 60px noch lesbar.
     Vier Verwendungen: Signet, Bildtrenner, Sektionstrenner,
     Navigations-Aktivmarker (dort als background-image).
     --------------------------------------------------------------- */
  .bogen {
    display: block;
    inline-size: 100%;
    block-size: auto;
    color: var(--c-brand);
    fill: none;
    stroke: currentColor;
    stroke-linecap: round;
  }

  .bogen--divider {
    max-inline-size: 14rem;
    margin-inline: auto;
    stroke-width: 2;
    opacity: 0.85;
  }

  /* ---------------------------------------------------------------
     section-head — Vorspann jeder Sektion.
     Eyebrow kodiert die Nutzerfrage, die die Sektion beantwortet —
     das ist keine Dekoration, sondern die Storytelling-Logik sichtbar.
     --------------------------------------------------------------- */
  .section-head {
    max-inline-size: var(--w-prose);
    margin-block-end: var(--s-7);
  }

  .section-head__eyebrow {
    display: block;
    margin-block-end: var(--s-3);
    color: var(--c-text-muted);
    font-family: var(--ff-display);
    font-size: var(--fs--1);
    font-weight: var(--fw-600);
    letter-spacing: var(--ls-wide);
    text-transform: uppercase;
  }

  .section-head__lead {
    margin-block-start: var(--s-4);
    font-size: var(--fs-1);
    color: var(--c-text-muted);
  }

  /* ---------------------------------------------------------------
     hero — erste Bildschirmseite jeder Seite.
     Beantwortet: Was ist das, für wen, warum ich.
     → CRITICAL CSS
     --------------------------------------------------------------- */
  .hero {
    position: relative;
    display: grid;
    align-items: center;
    /* Volle Bildschirmhöhe minus Kopfzeile — sonst schiebt die
       sticky Kopfzeile den Hero über den Falz hinaus. */
    min-block-size: calc(100svh - var(--h-header));
    padding-block: var(--s-8);
    overflow: hidden;
    /* Eigener Stapelkontext: Foto (-2) und Schleier (-1) bleiben im Hero. */
    isolation: isolate;
  }

  /* Foto aus config/bilder.js (partials/bild.ejs), füllt den Hero. */
  .hero__img {
    position: absolute;
    inset: 0;
    z-index: -2;
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    object-position: center;
  }

  /* Fläche hinter dem Text. Ohne Foto: Platzhalter-Schraffur.
     --scrim: liegt über dem Foto und hält den Text lesbar — ohne ihn
     kippt der Kontrast. --ruhig: Seiten ohne Foto (Kontakt, Einsätze,
     Rechtstexte, Fehlerseiten). */
  .hero__bg {
    position: absolute;
    inset: 0;
    z-index: -1;
    background-image: var(--image-placeholder-gradient);
  }

  .hero__bg--scrim {
    background-image: none;
    background-color: var(--c-scrim);
  }

  .hero__bg--ruhig {
    background-image: none;
    background-color: var(--c-surface);
  }

  /* Noch ungenutzt: greift, sobald das Einsatzvideo für die
     Kehrdienst-Seite vorliegt. Bewusst behalten, damit der Entwickler
     die Regel nicht neu erfinden muss. Siehe Handoff "Bekannte
     Ausnahmen". */
  .hero__video {
    position: absolute;
    inset: 0;
    z-index: -1;
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
  }

  /* Nur im Debugmode: nennt die fehlende Bilddatei des Heros. */
  .hero__note {
    position: absolute;
    inset-block-end: var(--s-4);
    inset-inline-start: 50%;
    translate: -50% 0;
    padding: var(--s-1) var(--s-3);
    background-color: var(--c-surface);
    border: var(--bw-1) solid var(--c-border);
    color: var(--c-text-muted);
    font-size: var(--fs--1);
  }

  .hero__eyebrow {
    display: block;
    margin-block-end: var(--s-5);
    color: var(--c-text-muted);
    font-family: var(--ff-display);
    font-size: var(--fs--1);
    font-weight: var(--fw-600);
    letter-spacing: var(--ls-wide);
    text-transform: uppercase;
  }

  .hero__lead {
    max-inline-size: 46ch;
    margin-block-start: var(--s-5);
    font-size: var(--fs-2);
    line-height: 1.45;
  }

  .hero__actions {
    margin-block-start: var(--s-7);
  }

  .hero__trust {
    margin-block-start: var(--s-5);
    color: var(--c-text-muted);
    font-size: var(--fs--1);
  }

  /* ---------------------------------------------------------------
     frame — Bildrahmen mit festem Seitenverhältnis für echte Bilder.
     Im Entwurf noch ungenutzt, weil bisher ausschließlich Platzhalter
     stehen (.ph). Bewusst behalten, damit der Entwickler die Bildregel
     nicht neu erfinden muss. Zuschnitt über object-position, nicht
     über Bildbearbeitung. Siehe Handoff "Bekannte Ausnahmen".
     --------------------------------------------------------------- */
  .frame {
    --ratio: 16 / 9;

    overflow: hidden;
    aspect-ratio: var(--ratio);
    background-color: var(--c-surface);
  }

  .frame--4x3 {
    --ratio: 4 / 3;
  }

  .frame--3x4 {
    --ratio: 3 / 4;
  }

  .frame__img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    object-position: center;
  }

  /* ---------------------------------------------------------------
     card — Leistung, Anwendungsfall, Segment.
     Trennung über Kante und Tonwert, nicht über Schweben.
     --------------------------------------------------------------- */
  .card {
    display: flex;
    flex-direction: column;
    justify-content: end;
    gap: var(--s-2);
    /* Mindesthöhe, damit die Bildfläche über dem Text zur Geltung
       kommt. Oben mehr Luft als unten — der Inhalt sitzt am Fuß. */
    min-block-size: 12rem;
    padding: var(--s-7) var(--s-4) var(--s-4);
    background-image: var(--image-placeholder-gradient);
    border: var(--bw-1) solid var(--c-border);
    transition: border-color var(--dur-1) var(--ease-out);
  }

  .card__icon {
    font-size: 1.5rem;
    color: var(--c-brand);
  }

  .card__title {
    font-size: var(--fs-2);
  }

  .card__text {
    /* Nicht die volle Kartenbreite: in weiten Rastern liest sich das
       sonst wie ein Fließtextblock. */
    max-inline-size: 46ch;
    color: var(--c-text-muted);
  }

  .card__link {
    align-self: start;
    margin-block-start: var(--s-2);
    font-weight: var(--fw-600);
    text-decoration: none;

    &:hover {
      text-decoration: underline;
    }
  }

  /* Anklickbare Karte: der Link spannt die ganze Fläche auf,
     damit kein Klick-Handler auf einem div nötig ist. */
  .card--linked {
    position: relative;

    &:hover {
      border-color: var(--c-brand);
    }

    & .card__link::after {
      content: "";
      position: absolute;
      inset: 0;
    }
  }

  /* Ohne Bild, nur Fläche — für dichte Aufzählungen. */
  .card--plain {
    justify-content: start;
    min-block-size: 0;
    padding: var(--s-4);
    background-image: none;
    background-color: var(--c-surface);
  }

  /* ---------------------------------------------------------------
     tile — Zielgruppen-Kachel. Bildfläche mit der Akzentfarbe
     multipliziert, Titel immer sichtbar, Beschreibung erst bei Hover.
     Auf Geräten ohne Hover ist die Beschreibung dauerhaft sichtbar
     (siehe @media (hover: hover) im responsive-Layer).
     Verwendet auf: Startseite, Hausmeisterservice.
     --------------------------------------------------------------- */
  .tile {
    position: relative;
    display: grid;
    align-content: end;
    min-block-size: 15rem;
    overflow: hidden;
    /* Hält den Multiply-Modus der Schleierebene innerhalb der Kachel. */
    isolation: isolate;
    border: var(--bw-1) solid var(--c-border);
  }

  /* Eigene Ebene, damit nur das Bild zoomt und nicht der Text.
     Echtes Foto als <img> (partials/bild.ejs), sonst <span> mit
     Schraffur. Der Akzentschleier darüber multipliziert beides. */
  .tile__img {
    position: absolute;
    inset: 0;
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    background-image: var(--image-placeholder-gradient);
    transition: scale var(--dur-3) var(--ease-out);
  }

  /* Akzentschleier. Liegt als eigene Ebene über dem Bild und blendet
     beim Hover ein — deshalb bewusst die Rohfarbe und nicht --c-brand:
     eine Bildfläche dreht sich nicht mit dem Theme. */
  .tile__tint {
    position: absolute;
    inset: 0;
    background-color: var(--gruen-600);
    mix-blend-mode: multiply;
    opacity: 0;
    transition: opacity var(--dur-3) var(--ease-out);
  }

  /* Zwei Zeilen: Titel und Beschreibung. Die zweite Zeile wächst von
     0fr auf 1fr — dadurch schiebt der erscheinende Text den Titel
     nach oben statt ihn zu überspringen. */
  .tile__body {
    position: relative;
    display: grid;
    grid-template-rows: auto auto;
    padding: var(--s-5) var(--s-4) var(--s-4);
    color: var(--c-text);
    transition: color var(--dur-2) var(--ease-out);
  }

  .tile__title {
    font-family: var(--ff-display);
    font-size: var(--fs-2);
    font-weight: var(--fw-700);
    line-height: var(--lh-snug);
  }

  .tile__text {
    max-inline-size: 34ch;
    padding-block-start: var(--s-2);
    font-size: var(--fs--1);
  }

  .tile:hover .tile__img,
  .tile:focus-within .tile__img {
    scale: 1.06;
  }

  .tile:hover .tile__tint,
  .tile:focus-within .tile__tint {
    opacity: 1;
  }

  /* ---------------------------------------------------------------
     leistung — Hauptleistungen als Liste mit Haarlinien statt als
     Kartenraster. Große Typo, viel Luft, der Bogen als Marker.
     Verwendet auf: Startseite, 404.
     --------------------------------------------------------------- */
  .leistungen {
    display: grid;
  }

  .leistung {
    position: relative;
    display: grid;
    gap: var(--s-2);
    padding-block: var(--s-5);
    border-block-start: var(--bw-1) solid var(--c-border);

    &:last-child {
      border-block-end: var(--bw-1) solid var(--c-border);
    }

    &:hover .leistung__title {
      color: var(--c-brand);
    }
  }

  .leistung__mark {
    inline-size: 3rem;
    block-size: 1.25rem;
    color: var(--c-brand);
    fill: none;
    stroke: currentColor;
    stroke-width: 4;
    stroke-linecap: round;
  }

  .leistung__title {
    font-family: var(--ff-display);
    font-size: var(--fs-4);
    font-weight: var(--fw-700);
    line-height: var(--lh-tight);
    letter-spacing: var(--ls-tight);
    transition: color var(--dur-1) var(--ease-out);
  }

  .leistung__text {
    max-inline-size: 52ch;
    color: var(--c-text-muted);
  }

  .leistung__link {
    font-weight: var(--fw-600);
    text-decoration: none;

    &::after {
      content: "";
      position: absolute;
      inset: 0;
    }
  }




  /* ---------------------------------------------------------------
     figures — Zahlenblock. Vier Werte, keine Prosa.
     Verwendet auf: Startseite (Ausstattung), Kehrdienst (Maschine).
     --------------------------------------------------------------- */
  .figure {
    padding-block-start: var(--s-4);
    border-block-start: var(--bw-3) solid var(--c-brand);
  }

  .figure__value {
    display: block;
    font-family: var(--ff-display);
    font-size: var(--fs-4);
    font-weight: var(--fw-700);
    line-height: var(--lh-tight);
    letter-spacing: var(--ls-tight);
  }

  .figure__label {
    display: block;
    margin-block-start: var(--s-1);
    color: var(--c-text-muted);
    font-size: var(--fs--1);
  }

  /* ---------------------------------------------------------------
     steps — echter Ablauf, deshalb nummeriert. Die Nummer trägt hier
     Information (Reihenfolge), sie dekoriert nicht.
     Verwendet auf: Startseite, Hausmeisterservice.
     --------------------------------------------------------------- */
  .step {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--s-4);
    align-items: start;
  }

  .step__num {
    display: grid;
    place-items: center;
    inline-size: 2.5rem;
    block-size: 2.5rem;
    border: var(--bw-2) solid var(--c-brand);
    color: var(--c-brand);
    font-family: var(--ff-display);
    font-size: var(--fs-0);
    font-weight: var(--fw-700);
  }

  .step__title {
    font-size: var(--fs-1);
  }

  .step__text {
    margin-block-start: var(--s-2);
    color: var(--c-text-muted);
  }

  /* ---------------------------------------------------------------
     ph — Foto-Platzhalter. Kein leerer Kasten, sondern eine Fläche
     mit Schraffur, Eckwinkeln und Klartext-Anforderung, damit man
     sich beim Durchsehen vorstellen kann, was dort hinkommt.
     Verwendet auf: allen Seiten.
     --------------------------------------------------------------- */
  .ph {
    --ratio: 16 / 9;

    display: grid;
    place-content: center;
    gap: var(--s-1);
    aspect-ratio: var(--ratio);
    padding: var(--s-4);
    overflow: hidden;
    background-image: var(--image-placeholder-gradient);
    color: var(--c-text-muted);
    text-align: center;
  }

  .ph--4x3 {
    --ratio: 4 / 3;
  }

  .ph--3x4 {
    --ratio: 3 / 4;
  }

  /* Schraffur liegt als eigene Ebene darunter, damit der Text lesbar
     bleibt und die Deckkraft unabhängig steuerbar ist. */




  .ph__label {
    font-family: var(--ff-display);
    font-size: var(--fs--1);
    font-weight: var(--fw-700);
    letter-spacing: var(--ls-wide);
    text-transform: uppercase;
    color: var(--c-text);
  }

  .ph__req {
    max-inline-size: 32ch;
    font-size: var(--fs--1);
    line-height: 1.4;
  }

  /* ---------------------------------------------------------------
     compare — Vorher/Nachher mit Schieber.
     Basis ohne JS: beide Bilder nebeneinander, vollständig lesbar.
     Mit JS: übereinander gelegt, der Range-Regler blendet über.
     Der Regler ist ein echtes <input type="range"> — dadurch ist die
     Tastaturbedienung ohne Zusatzcode korrekt.
     Verwendet auf: Startseite, Einsätze, Kehrdienst.
     --------------------------------------------------------------- */
  .compare {
    display: grid;
    gap: var(--s-3);
  }

  .compare__stack {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--bw-2);
  }

  /* --before liegt mit JS über --after und wird beschnitten.
     --after braucht keine eigenen Regeln, ist aber im Markup benannt,
     damit die Schichtung ohne Blick ins CSS lesbar bleibt. */
  .compare__layer {
    min-inline-size: 0;
  }

  .compare__range {
    display: none;
  }

  .compare__labels {
    display: flex;
    justify-content: space-between;
    color: var(--c-text-muted);
    font-family: var(--ff-display);
    font-size: var(--fs--1);
    font-weight: var(--fw-700);
    letter-spacing: var(--ls-wide);
    text-transform: uppercase;
  }

  .compare__meta {
    color: var(--c-text-muted);
    font-size: var(--fs--1);
  }

  /* Ab hier der Zustand mit JavaScript. */
  .compare[data-ready="true"] {
    & .compare__stack {
      position: relative;
      display: block;
      overflow: hidden;
      touch-action: pan-y;
      cursor: ew-resize;
      /* Beim Ziehen darf nichts markiert werden. */
      user-select: none;
      -webkit-user-select: none;
    }

    & .compare__layer--before {
      position: absolute;
      inset: 0;
      /* --pos wird vom Regler gesetzt. Einzige zulässige Inline-
         Custom-Property, siehe Handoff "Bekannte Ausnahmen". */
      clip-path: inset(0 calc(100% - var(--pos, 50%)) 0 0);
    }

    & .compare__range {
      display: block;
      position: absolute;
      inset-block-end: var(--s-4);
      inset-inline: var(--s-4);
      inline-size: calc(100% - 2 * var(--s-4));
      margin: 0;
      accent-color: var(--c-brand);
      cursor: ew-resize;
    }

    & .compare__handle {
      position: absolute;
      inset-block: 0;
      inset-inline-start: var(--pos, 50%);
      inline-size: var(--bw-3);
      margin-inline-start: calc(var(--bw-3) / -2);
      background-color: var(--c-brand);
      /* Griff ist anfassbar: pointerdown landet hier statt auf dem Foto
         darunter, compare.js zieht ueber das Eltern-Element .compare__stack. */
      cursor: ew-resize;
    }

    /* Fotos nicht anfassbar machen, sonst startet der Browser sein
       natives Bild-Ziehen (Verboten-Cursor) statt den Schieber zu bewegen. */
    & .frame__img {
      pointer-events: none;
    }

    & .compare__grip {
      position: absolute;
      inset-block-start: 50%;
      inset-inline-start: 50%;
      inline-size: 2.75rem;
      block-size: 2.75rem;
      translate: -50% -50%;
      display: grid;
      place-items: center;
      background-color: var(--c-brand);
      color: var(--c-brand-ink);
    }

    & .compare__labels {
      padding-inline: var(--s-1);
    }
  }

  /* ---------------------------------------------------------------
     callout — Hinweis mit Gewicht. Zwei Varianten:
     neutral (Abgrenzung, Erklärung) und --signal (Sendefehler, Achtung).
     Signal ist die einzige dekorationsfreie Verwendung von Ziegel.
     --------------------------------------------------------------- */
  .callout {
    padding: var(--s-4) var(--s-5);
    border-inline-start: var(--bw-3) solid var(--c-border-strong);
    background-color: var(--c-surface);
  }

  .callout--signal {
    border-inline-start-color: var(--c-signal);
  }

  .callout__title {
    margin-block-end: var(--s-2);
    font-size: var(--fs-1);
  }

  /* ---------------------------------------------------------------
     zone — Einsatzgebiet. Ersetzt die Radiusangabe: der Interessent
     sortiert sich selbst ein, bevor er anruft.
     Verwendet auf: Startseite, Kontakt.
     --------------------------------------------------------------- */
  .zone {
    display: grid;
    gap: var(--s-1);
    padding: var(--s-4);
    border: var(--bw-1) solid var(--c-border);
    background-image: var(--image-placeholder-gradient);
    transition: border-color var(--dur-1) var(--ease-out);

    &:hover {
      border-color: var(--c-brand);
    }
  }

  .zone__range {
    font-family: var(--ff-display);
    font-size: var(--fs-3);
    font-weight: var(--fw-700);
    color: var(--c-brand);
  }

  .zone__name {
    font-size: var(--fs-1);
  }

  .zone__rule {
    color: var(--c-text-muted);
    font-size: var(--fs--1);
  }

  /* ---------------------------------------------------------------
     price — Kostentreiber und Beispieleinsätze.
     Bewusst keine Preisliste: Beispiele binden weniger und wirken
     konkreter. Verwendet auf allen Leistungsseiten.
     --------------------------------------------------------------- */
  .price-case {
    display: grid;
    gap: var(--s-2);
    padding: var(--s-4);
    border: var(--bw-1) solid var(--c-border);
    background-image: var(--image-placeholder-gradient);
  }

  .price-case__what {
    font-family: var(--ff-display);
    font-size: var(--fs-1);
    font-weight: var(--fw-600);
  }

  .price-case__specs {
    display: grid;
    gap: var(--s-1);
    color: var(--c-text-muted);
    font-size: var(--fs--1);
  }

  .price-case__value {
    padding-block-start: var(--s-3);
    border-block-start: var(--bw-1) solid var(--c-border);
    font-family: var(--ff-display);
    font-size: var(--fs-3);
    font-weight: var(--fw-700);
    color: var(--c-brand);
  }

  /* ---------------------------------------------------------------
     faq — natives details/summary. Kein JS.
     Verwendet auf: Kehrdienst.
     --------------------------------------------------------------- */
  .faq {
    border-block-start: var(--bw-1) solid var(--c-border);
  }

  .faq__item {
    border-block-end: var(--bw-1) solid var(--c-border);
  }

  .faq__q {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-4);
    padding-block: var(--s-4);
    font-family: var(--ff-display);
    font-size: var(--fs-1);
    font-weight: var(--fw-600);
    list-style: none;
    transition: color var(--dur-1) var(--ease-out);

    &::-webkit-details-marker {
      display: none;
    }

    & .ico {
      flex: none;
      transition: transform var(--dur-2) var(--ease-out);
    }
  }

  .faq__item[open] .faq__q .ico {
    transform: rotate(45deg);
  }

  .faq__a {
    padding-block-end: var(--s-5);
    max-inline-size: var(--w-prose);
    color: var(--c-text-muted);
  }

  /* ---------------------------------------------------------------
     field — Formularfeld. Fehler ausschließlich über aria-invalid.
     Verwendet in: Rückruf-Formular, Anfrage-Formular.
     --------------------------------------------------------------- */
  .field {
    display: grid;
    gap: var(--s-2);
  }

  .field__label {
    font-weight: var(--fw-600);
  }

  .field__optional {
    color: var(--c-text-muted);
    font-weight: var(--fw-400);
  }

  .field__input {
    transition: border-color var(--dur-1) var(--ease-out);
    inline-size: 100%;
    min-block-size: 2.75rem;
    padding: var(--s-3) var(--s-4);
    background-color: var(--c-bg);
    border: var(--bw-2) solid var(--c-border-strong);
    color: var(--c-text);

    &::placeholder {
      color: var(--c-text-muted);
    }

    &:hover {
      border-color: var(--c-brand);
    }

    &[aria-invalid="true"] {
      border-color: var(--c-signal);
    }
  }

  .field__input--area {
    min-block-size: 7rem;
    resize: vertical;
  }

  .field__hint {
    color: var(--c-text-muted);
    font-size: var(--fs--1);
  }

  .field__error {
    display: none;
    color: var(--c-signal);
    font-size: var(--fs--1);
    font-weight: var(--fw-600);
  }

  .field:has([aria-invalid="true"]) .field__error {
    display: block;
  }

  /* Honeypot. Nicht über hidden, weil Bots hidden auswerten.
     Aus dem Tab-Fluss über tabindex und aria-hidden im Markup. */
  .field--trap {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }

  /* ---------------------------------------------------------------
     callform — Rückruf. Ein sichtbares Feld. Name und Zeitfenster
     erscheinen erst nach dem ersten Zeichen.
     Progressive Enhancement: ohne JS sind alle Felder sichtbar,
     das Ausblenden übernimmt ausschließlich JS.
     --------------------------------------------------------------- */
  .callform {
    display: grid;
    gap: var(--s-3);
  }

  /* Das große Anfrageformular. Eigene Hülle, weil es im Inhalt steht
     und nicht im Footer. */
  .inquiry {
    display: grid;
    gap: var(--s-4);
    padding: var(--s-5);
    background-color: var(--c-surface);
    border: var(--bw-1) solid var(--c-border);
  }

  .callform__more[data-collapsed="true"] {
    display: none;
  }

  .callform__more {
    display: grid;
    gap: var(--s-3);
  }

  .callform__legal {
    color: var(--c-text-muted);
    font-size: var(--fs--1);
  }

  /* ---------------------------------------------------------------
     formstatus — Erfolgs- und Fehlerzustand der Attrappe.
     DYNAMISCH: Text kommt in Produktion aus der Server-Response.
     --------------------------------------------------------------- */
  .formstatus {
    display: none;
    padding: var(--s-5);
    border-inline-start: var(--bw-3) solid var(--c-brand);
    background-color: var(--c-surface);
  }

  .formstatus[data-state="success"] {
    display: block;
  }

  /* Nach erfolgreichem Versand ersetzt die Erfolgsmeldung das Formular —
     sichtbar bleibt nur noch .formstatus. Zustand setzt der Server
     (No-JS, data-sent im Markup) oder assets/js/forms.js (AJAX). */
  .callform[data-sent="true"],
  .inquiry[data-sent="true"] {
    & > *:not(.formstatus) {
      display: none;
    }
  }

  /* ---------------------------------------------------------------
     entry — Eintrag auf der Einsätze-Seite.
     --------------------------------------------------------------- */
  .entry {
    padding-block: var(--s-7);
    border-block-start: var(--bw-1) solid var(--c-border);
  }

  .entry__meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-2) var(--s-5);
    margin-block-start: var(--s-3);
    color: var(--c-text-muted);
    font-size: var(--fs--1);
  }

  .entry__term {
    font-weight: var(--fw-600);
    color: var(--c-text);
  }

  /* ---------------------------------------------------------------
     einsatzgebiet + karte — Zonenliste neben der Niederbayern-Karte.
     Verwendet auf: Startseite, Kontakt (partials/einsatzgebiet.ejs).
     Die Karte rechnet in Kilometern (viewBox), Linien bleiben dank
     non-scaling-stroke in px. Schrift- und Konturgrößen der Labels stehen
     als SVG-Attribute in km, damit sie mit der Karte skalieren.
     Interaktion: Zone unter der Maus → passender Ring hervorgehoben (:has);
     Landkreis unter der Maus → Fläche getönt, Name als Tooltip.
     --------------------------------------------------------------- */
  /* Karte in voller Containerbreite, Zonen-Kacheln darunter. */
  .einsatzgebiet {
    display: grid;
    gap: var(--s-6);

    /* Grid-Kinder dürfen schmaler werden als das SVG „will“ — sonst
       läuft die Karte mobil über den Rand. */
    & > * {
      min-inline-size: 0;
    }

    &:has(.zone[data-zone="1"]:hover) .karte__ring--1,
    &:has(.zone[data-zone="2"]:hover) .karte__ring--2,
    &:has(.zone[data-zone="3"]:hover) .karte__ring--3 {
      fill: var(--c-karte-zone-aktiv);
      stroke-width: var(--bw-3);
    }

    /* Zone 4 = alles außerhalb des äußersten Rings. */
    &:has(.zone[data-zone="4"]:hover) .karte__ring--3 {
      stroke-width: var(--bw-3);
      stroke-dasharray: 6 4;
    }
  }

  .einsatzgebiet__karte {
    display: grid;
    gap: var(--s-2);
    margin: 0;
  }

  /* Lizenzpflichtiger Quellenvermerk — bewusst klein und zurückhaltend,
     aber sichtbar (dl-de/by-2-0 verlangt „deutlich sichtbar“). */
  .einsatzgebiet__quelle {
    justify-self: end;
    color: var(--c-text-muted);
    font-size: var(--fs--1);

    & a {
      color: inherit;
    }
  }

  .karte {
    display: block;
    inline-size: 100%;
    block-size: auto;
    border: var(--bw-1) solid var(--c-border);
    background-color: var(--c-surface-alt);
    font-family: var(--ff-body);

    & path,
    & circle {
      vector-effect: non-scaling-stroke;
    }
  }

  .karte__ausland path {
    fill: var(--c-surface-alt);
    stroke: var(--c-border-strong);
    stroke-width: var(--bw-1);
  }

  .karte__nachbarn path {
    fill: var(--c-surface);
    stroke: var(--c-border);
    stroke-width: var(--bw-1);
  }

  .karte__kreis {
    fill: var(--c-bg);
    stroke: var(--c-border-strong);
    stroke-width: var(--bw-1);
    transition: fill var(--dur-1) var(--ease-out);

    &:hover {
      fill: var(--c-karte-hover);
    }
  }

  .karte__ring {
    fill: var(--c-karte-zone);
    stroke: var(--c-brand);
    stroke-width: var(--bw-2);
    pointer-events: none;
    transition: fill var(--dur-2) var(--ease-out),
                stroke-width var(--dur-2) var(--ease-out);
  }

  .karte__labels,
  .karte__ringlabels,
  .karte__zentrum text {
    paint-order: stroke;
    stroke: var(--c-bg);
    stroke-linejoin: round;
    pointer-events: none;
  }

  .karte__labels {
    fill: var(--c-text-muted);
  }

  .karte__ringlabels {
    fill: var(--c-brand);
    font-weight: var(--fw-600);
  }

  .karte__zentrum {
    pointer-events: none;

    & circle {
      fill: var(--c-brand);
      stroke: var(--c-bg);
      stroke-width: var(--bw-2);
    }

    & text {
      fill: var(--c-text);
      font-weight: var(--fw-600);
    }
  }

  /* ---------------------------------------------------------------
     einsatz — Vorher/Nachher als datierter Eintrag, wie ein Blogbeitrag.
     Datum oben macht sichtbar, dass laufend gearbeitet wird.
     Verwendet auf: Startseite, Kehrdienst (partials/einsatz-karte.ejs);
     .einsatz__date zusätzlich auf /einsaetze und in der Verwaltung.
     --------------------------------------------------------------- */
  .einsatz {
    display: grid;
    gap: var(--s-4);
    align-content: start;
  }

  .einsatz__head {
    display: grid;
    gap: var(--s-1);
  }

  .einsatz__date {
    display: block;
    color: var(--c-text-muted);
    font-family: var(--ff-display);
    font-size: var(--fs--1);
    font-weight: var(--fw-700);
    letter-spacing: var(--ls-wide);
    text-transform: uppercase;
  }

  .einsatz__title {
    font-size: var(--fs-2);
  }

  .einsatz__text {
    color: var(--c-text-muted);
  }

  /* ---------------------------------------------------------------
     admin-item — ein Einsatz in der Liste der Verwaltung (/verwaltung).
     Vorschaubild, Datum, Titel, Status, Aktionen. Mobil gestapelt, ab md
     Bild links. __thumb auch im Formular und auf der Löschseite.
     --------------------------------------------------------------- */
  .admin-item {
    display: grid;
    gap: var(--s-4);
    padding-block: var(--s-5);
    border-block-start: var(--bw-1) solid var(--c-border);
  }

  .admin-item__thumb {
    inline-size: 100%;
    max-inline-size: var(--s-11);
    block-size: auto;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    background-color: var(--c-surface);
  }

  .admin-item__title {
    font-size: var(--fs-1);
  }

  /* btn--danger — ausschließlich endgültiges Löschen in der Verwaltung.
     Ziegel ist hier Warnsignal, nicht Dekoration. */
  .btn--danger {
    background-color: var(--c-signal);
    border-color: var(--c-signal);
    color: var(--c-brand-ink);

    &:hover {
      background-color: var(--c-text);
      border-color: var(--c-text);
      color: var(--c-bg);
    }
  }

  /* ---------------------------------------------------------------
     site-footer
     --------------------------------------------------------------- */
  /* Trägt auf jeder Seite das Rückruf-Formular — deshalb steht es
     nicht mehr als Inhaltsblock in den Seiten. */
  .site-footer {
    padding-block: var(--s-8) var(--s-5);
    background-color: var(--c-surface);
    border-block-start: var(--bw-1) solid var(--c-border);
  }

  .site-footer__cta {
    display: grid;
    gap: var(--s-6);
    padding-block-end: var(--s-7);
    margin-block-end: var(--s-7);
    border-block-end: var(--bw-1) solid var(--c-border);
  }

  .site-footer__cols {
    display: grid;
    gap: var(--s-6);
  }

  .site-footer__title {
    margin-block-end: var(--s-3);
    font-family: var(--ff-display);
    font-size: var(--fs--1);
    font-weight: var(--fw-700);
    letter-spacing: var(--ls-wide);
    text-transform: uppercase;
    color: var(--c-text-muted);
  }

  .site-footer__link {
    display: inline-block;
    padding-block: var(--s-1);
    color: var(--c-text);
    text-decoration: none;

    &:hover {
      color: var(--c-brand);
      text-decoration: underline;
    }
  }

  .site-footer__legal {
    margin-block-start: var(--s-8);
    padding-block-start: var(--s-5);
    border-block-start: var(--bw-1) solid var(--c-border);
    color: var(--c-text-muted);
    font-size: var(--fs--1);
  }
}

/* ==================================================================
   LAYER 6/9 — PAGES
   Ausschließlich seitenspezifische Ausnahmen. Bleibt klein.
   Wenn hier viel landet, war eine Komponente falsch geschnitten.
   ================================================================== */
@layer pages {

  /* Startseite: Rubrikzeile im Hero eine Stufe größer, weil dort die
     Marke selbst steht. */
  .p-home__hero .hero__eyebrow {
    font-size: var(--fs-0);
  }

  /* Die Impressums- und Datenschutzseiten laufen einspaltig im
     Lesemaß — keine eigene Komponente nötig. */
  .p-legal h2 {
    margin-block-start: var(--s-7);
    font-size: var(--fs-2);
  }

  .p-legal h3 {
    margin-block-start: var(--s-5);
    font-size: var(--fs-1);
  }

  .p-legal p + p {
    margin-block-start: var(--s-4);
  }
}

/* ==================================================================
   LAYER 7/9 — UTILITIES
   Einzweckklassen.
   ================================================================== */
@layer utilities {

  .u-visually-hidden {
    position: absolute !important; /* Utility: muss die Komponente schlagen */
    inline-size: 1px !important;
    block-size: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip-path: inset(50%) !important;
    white-space: nowrap !important;
    border: 0 !important;
  }

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

  .u-muted {
    color: var(--c-text-muted);
  }

  .u-lead {
    font-size: var(--fs-1);
  }

  .u-center {
    text-align: center;
  }

  .u-nowrap {
    white-space: nowrap;
  }

  /* Abstands-Utilities. Bewusst nur die vier tatsächlich benutzten
     Stufen — keine vollständige Skala, die niemand braucht. */
  .u-mt-4 {
    margin-block-start: var(--s-4);
  }

  .u-mt-5 {
    margin-block-start: var(--s-5);
  }

  .u-mt-6 {
    margin-block-start: var(--s-6);
  }

  .u-mt-7 {
    margin-block-start: var(--s-7);
  }

  .u-mt-8 {
    margin-block-start: var(--s-8);
  }

  .u-mb-4 {
    margin-block-end: var(--s-4);
  }

  .u-mb-5 {
    margin-block-end: var(--s-5);
  }

  .u-mb-7 {
    margin-block-end: var(--s-7);
  }

  /* Zentrierte Buttongruppe — .l-cluster allein zentriert nicht. */
  .u-center-cluster {
    justify-content: center;
  }

}

/* ==================================================================
   LAYER 8/9 — MOTION
   Motion-Level: dezent. Ein einziger auffälliger Effekt (der Bogen).
   Begründung im Handoff §3.4: Bauleiter mit schlechtem Netz.
   ================================================================== */
@layer motion {

  @keyframes formstatus-in {
    from {
      opacity: 0;
      transform: translateY(0.5rem);
    }

    to {
      opacity: 1;
      transform: translateY(0);
    }
  }

  .formstatus[data-state="success"] {
    animation: formstatus-in var(--dur-3) var(--ease-out) both;
  }

  /* Mobilmenü und Statusmeldungen: Ein-/Ausblenden mit
     @starting-style statt JS-Timing. */
  .nav {
    transition: opacity var(--dur-3) var(--ease-out),
                display var(--dur-3) allow-discrete;
  }

  .nav[data-open="true"] {
    opacity: 1;

    @starting-style {
      opacity: 0;
    }
  }
}

/* ==================================================================
   LAYER 9/9 — RESPONSIVE
   Alle Media-Queries. Jeder Breakpoint genau einmal im Dokument.
   Container-Queries stehen bewusst NICHT hier, sondern beim Bauteil
   (aktuell keine im Einsatz — siehe Handoff §3.5).
   ================================================================== */
@layer responsive {

  /* ===== sm — ab 30rem / 480px ====================================== */
  @media (min-width: 30rem) {

    /* -- components ------------------------------------------------- */
    .hero__actions {
      flex-wrap: nowrap;
    }

    .inquiry {
      padding: var(--s-6);
    }
  }

  /* ===== md — ab 48rem / 768px ====================================== */
  @media (min-width: 48rem) {

    /* -- layout ----------------------------------------------------- */
    .l-split {
      grid-template-columns: var(--ratio);
    }

    /* -- components ------------------------------------------------- */
    .brand__services {
      display: block;
    }

    .brand__mark {
      inline-size: 2.5rem;
      block-size: 2.5rem;
    }

    .site-footer__cols {
      grid-template-columns: 1.4fr 1fr 1fr;
    }

    .entry {
      display: grid;
      grid-template-columns: 1fr 1.6fr;
      gap: var(--s-7);
    }

    .admin-item {
      grid-template-columns: var(--s-11) 1fr;
      align-items: start;
      gap: var(--s-5);
    }
  }

  /* ===== lg — ab 64rem / 1024px ===================================== */
  @media (min-width: 64rem) {

    /* -- tokens ----------------------------------------------------- */
    :root {
      --h-header: 5rem;
    }

    /* -- components ------------------------------------------------- */
    .nav {
      display: block;
      padding-block: 0;
      border-block-start: 0;
    }

    .nav__list {
      flex-direction: row;
      gap: var(--s-1);
    }

    .nav__link {
      padding: var(--s-2) var(--s-3);
    }

    /* Der Menü-Button entfällt ab lg — die Navigation ist sichtbar. */
    .site-header__toggle {
      display: none;
    }

    .site-header__tel {
      display: inline-flex;
    }
  }

  /* ===== xl — ab 80rem / 1280px ===================================== */
  @media (min-width: 80rem) {

    /* -- components ------------------------------------------------- */
    .hero__lead {
      font-size: var(--fs-3);
    }
  }

  /* ===== xxl — ab 100rem / 1600px =================================== */
  @media (min-width: 100rem) {

    /* -- layout ----------------------------------------------------- */
    .l-container {
      --w: var(--w-content);
    }
  }

  /* ===== Eingabeart ================================================= */

  /* Nur wo es einen echten Zeigergerät-Hover gibt, wird die
     Beschreibung eingeklappt. Auf Touch bleibt sie stehen — dort
     gibt es kein Hover, und ein Antippen soll navigieren, nicht
     erst einen Text aufdecken. */
  @media (hover: hover) {

    /* -- components --------------------------------------------------- */
    .tile__body {
      grid-template-rows: auto 0fr;
    }

    .tile__text {
      overflow: hidden;
      opacity: 0;
      /* Verzögerung gegenüber dem Schleier: erst dunkelt es ab,
         dann kommt der Text. */
      transition: opacity var(--dur-2) var(--ease-out) 90ms;
    }

    .tile:hover .tile__body,
    .tile:focus-within .tile__body {
      grid-template-rows: auto 1fr;
      color: var(--kalk-50);
    }

    .tile:hover .tile__text,
    .tile:focus-within .tile__text {
      opacity: 1;
    }
  }

  /* ===== Präferenzen & Ausgabe ====================================== */

  /* Bewegung raus, Endzustand sofort. Nichts wird versteckt:
     der Bogen steht still auf voller Deckkraft, das Nachher-Bild
     ist vollständig sichtbar. */
  @media (prefers-reduced-motion: reduce) {

    html {
      scroll-behavior: auto;
    }

    *,
    *::before,
    *::after {
      animation-duration: 1ms !important; /* Utility-Charakter: muss alles schlagen */
      animation-iteration-count: 1 !important;
      transition-duration: 1ms !important;
    }

    /* Der Vergleichsschieber ist bewusst nicht animiert — er wird
       direkt bedient und braucht deshalb keine Reduced-Motion-Regel. */
  }

  @media (prefers-contrast: more) {

    :root {
      --c-border: light-dark(var(--asphalt-950), var(--kalk-100));
      --c-text-muted: light-dark(var(--asphalt-950), var(--kalk-100));
    }

    .btn--ghost {
      border-color: currentColor;
    }
  }

  @media print {

    .site-header,
    .site-footer,
    .hero__actions,
    .callform,
    .skip {
      display: none;
    }

    .page[hidden] {
      display: none;
    }

    body {
      background: #fff;
      color: #000;
      font-size: 11pt;
    }

    a::after {
      content: " (" attr(href) ")";
      font-size: 9pt;
    }
  }
}
