/* ==========================================================================
   Gama Reinigungsservice — "App"-Layout (index.html)
   Statische Navigation oben (Tabs) + Punkte unten, kein Seiten-Scroll.
   Jeder Tab ersetzt die komplette Ansicht.

   Baut auf den Tokens aus style.css auf:
     <link rel="stylesheet" href="assets/css/style.css">
     <link rel="stylesheet" href="assets/css/app.css">

   Style-Richtung laut ui-ux-pro-max: "Soft UI Evolution"
   (weiche, aber klar sichtbare Schatten, WCAG AA+, 200-300ms Transitions).
   Farben bleiben bewusst die Marken-Palette aus style.css, damit alle drei
   Varianten dieselbe Marke zeigen.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Höhen-Budget
   Der harte Teil dieses Layouts: Kopf, Tab-Leiste und Punkte sind fix, der Rest ist
   genau EIN Bildschirm. Alle Innenmaße leiten sich deshalb aus der
   Viewport-Höhe ab (vh-Anteil in den clamp()-Formeln), damit Inhalte auf
   flachen Geräten mitschrumpfen statt überzulaufen.
   -------------------------------------------------------------------------- */
:root {
  --app-head-h: 3.25rem;
  --app-dots-h: 0px;   /* wird ab 700px auf einen echten Wert gesetzt */
  --app-tab-h: 3.875rem;
  --app-gap: clamp(0.5rem, 0.25rem + 1vh, 1.25rem);
  --app-pad-x: clamp(1rem, 0.5rem + 2vw, 2.5rem);

  /* Höhen-elastische Schriftgrade — der vh-Anteil ist der Kern der Sache.

     WICHTIG zur Kalibrierung: Die Formeln sind so gesetzt, dass ein normaler
     Desktop (Viewport-Höhe ~900px) die ZIELGRÖSSEN trifft, nicht das untere
     Ende der Skala. Vorher lag der feste Anteil zu hoch und die Steigung zu
     flach — dadurch wurde das clamp()-Maximum erst bei ~1500px Höhe erreicht
     und Fließtext landete auf 1440×900 bei 14,9px statt bei 16px.

     Jetzt: flacher fester Anteil + steilere vh-Kopplung. Ergebnis bei
     Viewport-Höhe 900px / 768px / 640px (gerechnet, Basis 16px):
       --af-xs    13,0 / 12,3 / 11,3
       --af-sm    14,5 / 13,6 / 12,8
       --af-base  16,0 / 14,9 / 13,8   ← Fließtext, Ziel 16px auf Desktop
       --af-lg    18,0 / 16,9 / 15,8
       --af-h2    30,0 / 27,4 / 24,8
       --af-h1    42,0 / 37,6 / 33,3
     Die Minima bleiben auf den alten Werten, damit sehr flache Geräte nicht
     schlechter dastehen als vorher. */
  --af-xs: clamp(0.6875rem, 0.53rem + 0.5vh, 0.9375rem);
  --af-sm: clamp(0.75rem, 0.53rem + 0.67vh, 1.0625rem);
  --af-base: clamp(0.8125rem, 0.53rem + 0.83vh, 1.1875rem);
  --af-lg: clamp(0.875rem, 0.656rem + 0.83vh, 1.375rem);
  --af-h2: clamp(1.25rem, 0.75rem + 2vh, 2.25rem);
  --af-h1: clamp(1.5rem, 0.75rem + 3.33vh, 3.25rem);

  /* Soft-UI-Schatten: weicher als flach, klarer als Neumorphismus */
  --soft-1: 0 1px 2px rgba(12, 39, 64, 0.05), 0 2px 8px -2px rgba(12, 39, 64, 0.08);
  --soft-2: 0 2px 4px -1px rgba(12, 39, 64, 0.06), 0 8px 20px -6px rgba(12, 39, 64, 0.12);
}

/* --------------------------------------------------------------------------
   2. Shell — nichts scrollt
   Alle Regeln hängen an html.app: Ohne JavaScript greift keine davon und
   die Seite bleibt ein normales, scrollbares Dokument (Fallback-Ebene).
   -------------------------------------------------------------------------- */
html.app,
html.app body {
  height: 100%;
  overflow: hidden;          /* der eigentliche "kein Scroll"-Schalter */
  overscroll-behavior: none; /* kein Pull-to-Refresh, kein Gummiband */
  position: fixed;           /* iOS: verhindert das Wegschieben der Shell */
  inset: 0;
  width: 100%;
}

html.app .app {
  display: grid;
  /* Reihen: Kopf | Tab-Leiste | Bühne (dehnt sich) | Punkte */
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  height: 100dvh;
  overflow: hidden;
}

/* Ohne .app (kein JS): Views einfach untereinander als normale Seite */
.app-view + .app-view { border-top: 1px solid var(--line); }

/* --------------------------------------------------------------------------
   3. Kopfzeile (statisch)
   -------------------------------------------------------------------------- */
.app-head {
  display: flex;
  align-items: center;
  gap: var(--app-gap);
  min-height: var(--app-head-h);
  padding: 0.5rem var(--app-pad-x);
  background: #edf5fb96;
  backdrop-filter: blur(12px) saturate(140%);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  border-bottom: 1px solid var(--line);
  z-index: var(--z-header);
}
.app-head .brand { margin-right: auto; }
.app-head .brand img { width: clamp(96px, 60px + 8vw, 140px); }

.app-head__phone {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 40px;
  padding-inline: var(--sp-2);
  font-family: 'Poppins', sans-serif;
  font-weight: 600;
  font-size: var(--af-xs);
  color: var(--navy-800);
  text-decoration: none;
  border-radius: var(--r-full);
  transition: background-color 200ms var(--ease), color 200ms var(--ease);
}
.app-head__phone svg { width: 1.125rem; height: 1.125rem; color: var(--sky-600); flex: none; }
.app-head__phone:hover { background: var(--sky-50); color: var(--sky-700); }
.app-head__phone span { display: none; }
@media (min-width: 480px) { .app-head__phone span { display: inline; } }

.app-head .btn {
  min-height: 40px;
  padding: 0.5rem 1rem;
  font-size: var(--af-xs);
}

/* --------------------------------------------------------------------------
   3b. Punkt-Navigation (statisch, UNTEN mittig — nur ab Tablet)
   Rein additiv: die Tab-Leiste oben bleibt auf jedem Gerät die primäre,
   vollständige Navigation. Auf dem Handy ist ohnehin kein Platz für eine
   zweite Leiste, ohne das mühsam erreichte Höhen-Budget zu sprengen.
   -------------------------------------------------------------------------- */
.app-dots {
  /* WICHTIG: niemals display:none verwenden — das würde das Element aus
     dem Grid herausnehmen und die übrigen drei Kinder (Kopf/Tabs/Bühne)
     in die falschen Reihen rutschen lassen. Stattdessen bleibt es immer
     ein Grid-Teilnehmer und wird nur auf Höhe 0 zusammengefaltet. */
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 0.625rem;
  height: var(--app-dots-h);
  min-height: 0;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  border-top: 0 solid var(--line);
}

/* Unterhalb 700px ist der Container 0px hoch (siehe --app-dots-h oben) —
   die Buttons selbst müssen dann aber auch aus der Tab-Reihenfolge und
   der Sichtbarkeit verschwinden, sonst blieben unsichtbare, aber
   fokussierbare Buttons übrig. `visibility:hidden` allein reicht: solche
   Elemente sind in allen gängigen Browsern nicht mehr per Tab erreichbar. */
.app-dot {
  visibility: hidden;
  pointer-events: none;
}

@media (min-width: 700px) {
  /* Nur im aktiven App-Modus einen echten Wert setzen — ohne JavaScript
     bleibt --app-dots-h bei 0px, weil die Punkte dort ohnehin wirkungslos
     wären (keine Klick-Handler vorhanden). Andernfalls stünde ohne JS ein
     funktionsloses Leerelement im Weg. */
  html.app { --app-dots-h: 2.5rem; }

  html.app .app-dots {
    border-top-width: 1px;
    /* untere Leiste: Platz für die iOS-Home-Indikator-Zone */
    height: calc(var(--app-dots-h) + env(safe-area-inset-bottom, 0px));
    padding-bottom: env(safe-area-inset-bottom, 0px);
    box-shadow: 0 -2px 12px -6px rgba(12, 39, 64, 0.18);
  }

  html.app .app-dot {
    visibility: visible;
    pointer-events: auto;
    position: relative;
    width: 2rem;
    height: 100%;
    padding: 0;
    border: none;
    background: none;
    cursor: pointer;
  }
  /* größere Klickfläche (2rem) bei kleinem sichtbaren Punkt — WCAG-Zielgröße
     ohne dass die Leiste optisch schwer wirkt */
  html.app .app-dot::before {
    content: '';
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 7px; height: 7px;
    border-radius: var(--r-full);
    background: #b9c6d3;
    transition: background-color 200ms var(--ease), transform 200ms var(--ease);
  }
  html.app .app-dot:hover::before { background: var(--sky-500); transform: translate(-50%, -50%) scale(1.3); }
  html.app .app-dot[aria-current='true']::before {
    background: var(--sky-600);
    transform: translate(-50%, -50%) scale(1.5);
  }
  html.app .app-dot:focus-visible { outline-offset: 2px; }
}

/* --------------------------------------------------------------------------
   4. Bühne + Views
   -------------------------------------------------------------------------- */
.app-stage {
  position: relative;
  min-height: 0;   /* wichtig: erlaubt dem Grid-Kind zu schrumpfen */
  overflow: hidden;
}

html.app .app-view {
  position: absolute;
  inset: 0;
  display: grid;
  align-content: center;
  gap: var(--app-gap);
  padding: var(--app-gap) var(--app-pad-x);
  overflow: hidden;
  border-top: none;
}

/* Inaktive Views komplett aus dem Fluss UND aus der Tab-Reihenfolge */
html.app .app-view[hidden] { display: none; }

/* --------------------------------------------------------------------------
   Wisch-Übergang zwischen den Views
   Echter Slide statt Fade: die alte View schiebt seitlich hinaus, die neue
   schiebt von der Gegenseite hinein — Richtung folgt der Tab-Reihenfolge
   (rechts = vorwärts, links = zurück). Beide Views liegen dank
   `position:absolute; inset:0` exakt übereinander; `.app-stage` schneidet
   den Überstand via `overflow:hidden` ab, sodass dabei nichts scrollt.
   Steuerung ausschließlich über data-slide, gesetzt von app.js.
   -------------------------------------------------------------------------- */
html.app .app-view {
  transform: translateX(0);
  transition: transform 340ms cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: transform;
}
html.app .app-view[data-slide='enter-from-right'] { transform: translateX(100%); }
html.app .app-view[data-slide='enter-from-left']  { transform: translateX(-100%); }
html.app .app-view[data-slide='leave-to-left']    { transform: translateX(-100%); }
html.app .app-view[data-slide='leave-to-right']   { transform: translateX(100%); }
/* 'active' ist der Zielzustand, in den enter-from-* animiert */
html.app .app-view[data-slide='active']           { transform: translateX(0); }

/* Während des Übergangs ist die auslaufende View für Screenreader
   unsichtbar, aber optisch noch da — sie wird erst danach per hidden
   ausgeblendet (siehe app.js transitionend-Handler). */
html.app .app-view[aria-hidden='true'] { pointer-events: none; }

.app-view__inner {
  width: 100%;
  max-width: 1080px;
  margin-inline: auto;
  min-height: 0;
}

/* Typo im App-Kontext: alles höhen-elastisch */
.app-view h1 { font-size: var(--af-h1); line-height: 1.1; }
.app-view h2 { font-size: var(--af-h2); line-height: 1.15; }
.app-view h3 { font-size: var(--af-base); }
.app-view p { font-size: var(--af-sm); line-height: 1.5; }
.app-view .eyebrow {
  font-size: var(--af-xs);
  margin-bottom: clamp(0.25rem, 0.1rem + 0.4vh, 0.75rem);
}
.app-view .lead {
  font-size: var(--af-base);
  color: var(--muted);
  max-width: 46rem;
}

/* Flächen */
.app-view--hero {
  background:
    radial-gradient(60rem 32rem at 82% -10%, var(--sky-100) 0%, transparent 62%),
    linear-gradient(180deg, var(--sky-50) 0%, #fff 76%);
}
.app-view--tint { background: var(--surface-tint); }
.app-view--alt { background: var(--surface-alt); }
.app-view--dark {
  background: linear-gradient(150deg, var(--navy-900) 0%, var(--navy-700) 52%, var(--sky-800) 100%);
  color: var(--on-dark-body);
}
.app-view--dark h1,
.app-view--dark h2,
.app-view--dark h3 { color: var(--on-dark); }
.app-view--dark .lead { color: var(--on-dark-body); }
.app-view--dark .eyebrow { color: var(--on-dark-accent); }
.app-view--dark .eyebrow::before { background: var(--on-dark-accent); }

/* --------------------------------------------------------------------------
   5. Tab-Leiste (statisch, OBEN — auf allen Geräten)
   -------------------------------------------------------------------------- */
.app-tabs {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  align-items: stretch;
  min-height: var(--app-tab-h);
  background: rgba(255, 255, 255, 0.94);
  backdrop-filter: blur(12px) saturate(140%);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 2px 12px -6px rgba(12, 39, 64, 0.18);
  z-index: var(--z-header);
}

.app-tab {
  position: relative;
  /* Grid + place-items zentriert Icon UND Label zuverlässig in beiden
     Achsen. Wichtig: bei der Tablet-Variante unten bleibt es Grid
     (nur die Flussrichtung wechselt), damit die Zentrierung erhalten bleibt. */
  display: grid;
  place-items: center;
  align-content: center;
  justify-content: center;
  gap: 0.1875rem;
  padding: 0.375rem 0.25rem;
  border: none;
  background: none;
  color: var(--muted);
  font-family: 'Poppins', sans-serif;
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1.1;
  text-align: center;
  cursor: pointer;
  /* Farbwechsel statt Skalierung: kein Layout-Sprung beim Hover */
  transition: color 200ms var(--ease), background-color 200ms var(--ease);
}
.app-tab svg {
  width: 1.375rem; height: 1.375rem;
  stroke-width: 1.8;
}
.app-tab:hover { color: var(--sky-700); background: var(--sky-50); }

/* Aktiver Tab: Farbe UND Balken — Farbe ist nie das einzige Merkmal.
   Der Balken läuft über die volle Buttonbreite und sitzt an der Unterkante,
   da die Leiste jetzt oben steht. */
.app-tab[aria-selected='true'] { color: var(--sky-700); }
.app-tab[aria-selected='true']::before {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 3px;
  border-radius: 3px 3px 0 0;
  background: var(--sky-600);
}
.app-tab[aria-selected='true'] svg { stroke-width: 2.2; }

/* Der Kontakt-Tab ist das Konversionsziel — dezent hervorgehoben */
.app-tab--cta { color: var(--cta-600); }
.app-tab--cta:hover { color: var(--cta-700); background: var(--cta-100); }
.app-tab[aria-selected='true'].app-tab--cta { color: var(--cta-700); }
.app-tab[aria-selected='true'].app-tab--cta::before { background: var(--cta-600); }

.app-tab__label { display: block; }
@media (max-width: 359px) {
  /* Sehr schmale Geräte: Labels weg, Icons bleiben (aria-label trägt den Namen) */
  .app-tab__label {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }
  .app-tab svg { width: 1.5rem; height: 1.5rem; }
}

/* Ab Tablet: Icon und Text nebeneinander, wirkt weniger gedrängt.
   Bewusst weiterhin Grid (nicht flex) — sonst greift place-items nicht
   und der Inhalt klebt links. */
@media (min-width: 700px) {
  .app-tabs { justify-content: center; grid-auto-columns: minmax(7rem, 11rem); }
  .app-tab {
    grid-auto-flow: column;
    gap: var(--sp-2);
    font-size: var(--af-xs);
  }
}

/* --------------------------------------------------------------------------
   6. Kompakt-Komponenten für die Views
   -------------------------------------------------------------------------- */

/* Hero */
.app-hero {
  display: grid;
  gap: var(--app-gap);
  align-items: center;
}
@media (min-width: 860px) { .app-hero { grid-template-columns: 1.05fr 0.95fr; } }
.app-hero__media { display: grid; place-items: center; min-height: 0; }
html.app .app-hero__media img {
  max-height: clamp(10.5rem, 52vh, 39rem);
  width: auto;
  filter: drop-shadow(0 18px 32px rgba(12, 39, 64, 0.16));
}
@media (max-width: 859px) {
  .app-hero__media { order: -1; }
  html.app .app-hero__media img { max-height: 44vh; }
}

.app-hero h1 em { font-style: normal; color: var(--sky-600); }
.app-hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--app-gap);
}
.app-hero__actions .btn { min-height: 44px; font-size: var(--af-sm); }
.app-hero__trust {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem var(--sp-4);
  margin-top: var(--app-gap);
  padding-top: var(--app-gap);
  border-top: 1px solid var(--line);
}
.app-hero__trust li {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--af-xs);
  font-weight: 600;
  color: var(--navy-800);
}
.app-hero__trust svg { width: 1rem; height: 1rem; flex: none; color: var(--green-600); }

/* Karten-Raster (Leistungen, Vorteile) */
.app-cards {
  display: grid;
  gap: clamp(0.5rem, 0.3rem + 0.8vh, 1.25rem);
}
@media (min-width: 560px) { .app-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.app-card {
  display: grid;
  gap: clamp(0.25rem, 0.15rem + 0.4vh, 0.75rem);
  align-content: start;
  padding: clamp(0.75rem, 0.5rem + 1vh, 1.5rem);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--soft-1);
  transition: border-color 200ms var(--ease), box-shadow 200ms var(--ease);
}
.app-card:hover { border-color: var(--sky-400); box-shadow: var(--soft-2); }
.app-card__icon {
  display: grid;
  place-items: center;
  width: clamp(2rem, 1.5rem + 1.6vh, 2.75rem);
  height: clamp(2rem, 1.5rem + 1.6vh, 2.75rem);
  background: var(--sky-100);
  color: var(--sky-700);
  border-radius: var(--r-md);
}
.app-card__icon svg { width: 60%; height: 60%; }
.app-card p { font-size: var(--af-xs); }

/* Nummerierte Schritte */
.app-steps {
  display: grid;
  gap: clamp(0.5rem, 0.3rem + 0.9vh, 1.25rem);
  counter-reset: appstep;
}
@media (min-width: 560px) { .app-steps { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.app-step {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-3);
  align-items: start;
  padding: clamp(0.75rem, 0.5rem + 1vh, 1.5rem);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--soft-1);
}
@media (min-width: 560px) { .app-step { grid-template-columns: 1fr; } }
.app-step::before {
  counter-increment: appstep;
  content: counter(appstep);
  display: grid;
  place-items: center;
  width: clamp(1.75rem, 1.4rem + 1.2vh, 2.25rem);
  height: clamp(1.75rem, 1.4rem + 1.2vh, 2.25rem);
  font-family: 'Poppins', sans-serif;
  font-size: var(--af-sm);
  font-weight: 700;
  color: #fff;
  background: linear-gradient(135deg, var(--sky-500), var(--sky-700));
  border-radius: var(--r-full);
  box-shadow: 0 4px 10px -3px rgba(2, 132, 199, 0.5);
}
.app-step h3 { margin-bottom: 0.125rem; }
.app-step p { font-size: var(--af-xs); }

/* Zitate */
.app-quotes {
  display: grid;
  gap: clamp(0.5rem, 0.3rem + 0.8vh, 1.25rem);
}
@media (min-width: 700px) { .app-quotes { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.app-quote {
  display: grid;
  gap: clamp(0.375rem, 0.25rem + 0.5vh, 0.875rem);
  align-content: start;
  padding: clamp(0.75rem, 0.5rem + 1vh, 1.5rem);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--soft-1);
}
.app-quote .rating svg { width: 0.9375rem; height: 0.9375rem; }
.app-quote blockquote { font-size: var(--af-xs); line-height: 1.5; color: var(--body); }
.app-quote figcaption {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding-top: var(--sp-2);
  border-top: 1px solid var(--line);
}
.app-quote .avatar {
  width: clamp(1.75rem, 1.4rem + 1.2vh, 2.25rem);
  height: clamp(1.75rem, 1.4rem + 1.2vh, 2.25rem);
  font-size: var(--af-xs);
}
.app-quote cite {
  font-style: normal;
  font-family: 'Poppins', sans-serif;
  font-weight: 600;
  font-size: var(--af-xs);
  color: var(--ink);
}
.app-quote figcaption span:last-child { font-size: 0.6875rem; color: var(--muted); }

/* Zahlen */
.app-figures {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(0.5rem, 0.3rem + 0.8vh, 1.25rem);
}
@media (min-width: 560px) { .app-figures { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.app-figure { text-align: center; }
.app-figure__num {
  font-family: 'Poppins', sans-serif;
  font-size: clamp(1.25rem, 0.9rem + 1.6vh, 2.25rem);
  font-weight: 700;
  line-height: 1;
  color: var(--sky-600);
  font-variant-numeric: tabular-nums;
}
.app-view--dark .app-figure__num { color: var(--sky-200); }
.app-figure__label {
  margin-top: 0.25rem;
  font-size: var(--af-xs);
  font-weight: 600;
  color: var(--navy-800);
}
.app-view--dark .app-figure__label { color: var(--on-dark-body); }

/* --------------------------------------------------------------------------
   7. Kontakt-View — die engste Ansicht
   Eingabefelder bleiben bei 16px: darunter zoomt iOS beim Fokus automatisch
   hinein und würde die fixe Shell zerreißen. Platz wird deshalb über
   Abstände, Label-Größe und Spaltenlayout gespart, nicht über die Feldgröße.
   -------------------------------------------------------------------------- */
.app-contact {
  display: grid;
  gap: var(--app-gap);
  align-items: start;
  min-height: 0;
}
@media (min-width: 860px) { .app-contact { grid-template-columns: 0.8fr 1.2fr; } }

.app-contact .contact-methods { display: grid; gap: 0.5rem; margin-top: 0; }
.app-contact .contact-method { padding: 0.625rem 0.75rem; box-shadow: var(--soft-1); }
.app-contact .contact-method__icon { width: 2rem; height: 2rem; }
.app-contact .contact-method__icon svg { width: 1rem; height: 1rem; }
.app-contact .contact-method strong { font-size: var(--af-sm); }
.app-contact .contact-method span span { font-size: var(--af-xs); }

.app-form {
  padding: clamp(0.75rem, 0.5rem + 1vh, 1.5rem);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--soft-2);
}
.app-form .form-grid { gap: clamp(0.375rem, 0.25rem + 0.5vh, 0.875rem); }
.app-form .field { gap: 0.1875rem; }
.app-form .field label { font-size: var(--af-xs); }
.app-form .field input,
.app-form .field select,
.app-form .field textarea {
  font-size: 1rem;                                  /* iOS-Auto-Zoom vermeiden */
  padding: clamp(0.375rem, 0.25rem + 0.4vh, 0.75rem) 0.75rem;
  border-radius: var(--r-sm);
}
.app-form .field textarea {
  min-height: clamp(2.5rem, 1.5rem + 4vh, 6rem);
  resize: none;
}
.app-form .checkbox { font-size: var(--af-xs); gap: var(--sp-2); }
.app-form .checkbox input { width: 1.125rem; height: 1.125rem; margin-top: 0.1rem; }
.app-form .btn { min-height: 44px; font-size: var(--af-sm); }
.app-form .form-status { padding: 0.625rem 0.75rem; font-size: var(--af-xs); }

/* Abschluss-Block im Kontakt-Tab */
.app-legal {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem var(--sp-4);
  align-items: center;
  font-size: 0.6875rem;
  color: var(--muted);
}
.app-legal a { color: var(--muted); text-decoration: none; }
.app-legal a:hover { color: var(--sky-700); text-decoration: underline; }

/* Rechtslinks im Kopf — nur auf Handys sichtbar, wo der Kontakt-View
   die eigene Fußzeile einsparen muss. Impressum und Datenschutz sind in
   Deutschland Pflicht und dürfen nie unerreichbar werden. */
.app-minilegal { display: none; }
@media (max-width: 559px) {
  .app-minilegal {
    display: inline-flex;
    gap: 0.625rem;
    font-size: 0.625rem;
    line-height: 1;
    color: var(--muted);
  }
  .app-minilegal a {
    color: var(--muted);
    text-decoration: none;
    padding: 0.5rem 0.125rem; /* Touch-Fläche ohne optische Vergrößerung */
  }
  .app-minilegal a:hover { color: var(--sky-700); text-decoration: underline; }
}

/* --------------------------------------------------------------------------
   7b. Handy-Verdichtung
   Vorgabe: strikt kein Scrollen. Auf schmalen Geräten stapeln die Raster
   einspaltig und die Views würden überlaufen. Deshalb hier gezielt kürzen:
   Karten kompakter, Beschreibungstexte weg, wo die Überschrift die Aussage
   schon trägt, und pro View nur so viele Elemente wie hineinpassen.
   Das kostet bewusst Information — siehe README.
   -------------------------------------------------------------------------- */
@media (max-width: 559px) {
  /* Karten: Beschreibung nur bei der ersten Karte, sonst Titel + Icon */
  .app-cards { gap: 0.5rem; }
  .app-card {
    grid-template-columns: auto 1fr;
    align-items: center;
    gap: var(--sp-3);
    padding: 0.625rem 0.75rem;
  }
  .app-card__icon { width: 2rem; height: 2rem; }
  .app-card h3 { font-size: var(--af-sm); }
  .app-card p { display: none; }

  /* Zahlenblock im Vorteile-Tab: zwei Reihen, kompakt */
  .app-figures { gap: 0.5rem; }

  /* Schritte kompakt, Text bleibt (er trägt hier die Aussage) */
  .app-steps { gap: 0.5rem; }
  .app-step { padding: 0.625rem 0.75rem; gap: var(--sp-3); align-items: center; }

  /* Referenzen: nur das erste Zitat — drei übereinander passen nie */
  .app-quotes { gap: 0.5rem; }
  .app-quote:nth-of-type(n + 2) { display: none; }

  /* Sonderreinigung: Überschrift einzeilig halten ("Herausforderungen") */
  #v-spezialreinigung h2 { font-size: var(--af-lg); }
  #v-spezialreinigung h2 .app-h2-long { display: none; }

  /* Kontakt: Telefon + WhatsApp reichen, E-Mail steht im Kopf und im Fuß.
     Auf Handys stehen die beiden Kacheln NEBENeinander statt untereinander —
     das spart die Höhe, die das Formular braucht. */
  .app-contact .contact-method:nth-of-type(n + 3) { display: none; }
  .app-contact .contact-method span span { display: none; }
  .app-contact .contact-methods {
    grid-template-columns: 1fr 1fr;
    gap: 0.375rem;
  }
  .app-contact .contact-method {
    grid-template-columns: 1fr;
    justify-items: center;
    text-align: center;
    gap: 0.25rem;
    padding: 0.5rem 0.375rem;
  }
  .app-contact .contact-method strong { font-size: 0.6875rem; }

  /* Kontakt-Formular auf Handys: Platz kommt aus Abständen und Struktur,
     NICHT aus der Feldgröße — die bleibt bei 16px (iOS-Auto-Zoom).
     Die Überschrift trägt die Aussage, die Labels stehen an den Feldern. */
  .app-view--tint#v-kontakt .lead,
  #v-kontakt h2 + .lead { display: none; }
  /* Überschrift einzeilig halten: "Angebot anfordern" statt Langfassung */
  #v-kontakt h2 { font-size: var(--af-lg); }
  #v-kontakt h2 .app-h2-long { display: none; }
  .app-form { padding: 0.625rem 0.75rem; }
  .app-form .form-grid { gap: 0.375rem; }
  .app-form .field textarea { min-height: 2.25rem; }
  /* Labels der zweispaltigen Kurzfelder einsparen: Platzhalter benennen sie,
     das zugehörige <label> bleibt für Screenreader visuell versteckt. */
  .app-form .form-grid--2 .field label {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }
  /* Rechtslinks rutschen aus dem Kontakt-View in die feste Fußzeile der
     Tab-Bar-Umgebung (siehe .app-minilegal) — sie müssen erreichbar bleiben. */
  .app-contact .app-legal { display: none; }

  /* Hero: Bild kleiner, damit Text und Buttons Platz haben */
  html.app .app-hero__media img { max-height: 22vh; }
  .app-hero__trust li:nth-child(n + 3) { display: none; }
}

/* Sehr flache Handys (z. B. 375x600): weiter verdichten */
@media (max-height: 680px) and (max-width: 559px) {
  .app-view .lead { display: none; }
  .app-card:nth-of-type(n + 4) { display: none; }
  .app-figures .app-figure:nth-child(n + 3) { display: none; }
}

/* --------------------------------------------------------------------------
   8. Notventil
   Wenn ein View trotz aller Verdichtung nicht passt (sehr flaches Fenster,
   Text-Zoom >= 150 %, sehr große Systemschrift), setzt app.js dieses Attribut.
   Ohne das Ventil wäre der überstehende Inhalt dauerhaft unerreichbar —
   ein WCAG-Verstoß (1.4.4 Resize Text). Es greift nur im Notfall.
   -------------------------------------------------------------------------- */
html.app .app-view[data-overflow='true'] {
  align-content: start;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
html.app .app-view[data-overflow='true'] .app-view__inner { padding-bottom: var(--app-gap); }

/* --------------------------------------------------------------------------
   9. Bewegung
   -------------------------------------------------------------------------- */

/* Gestaffelter Inhaltseinzug beim Ansichtswechsel.
   Der Slide oben bewegt den View als starren Block. Damit der Wechsel gebaut
   statt nur geschoben wirkt, fächern die Inhalte beim Ankommen leicht nach.

   Gesetzt wird data-enter von app.js — bewusst NUR beim Wechsel, nicht beim
   Erststart: h1 und Hero-Bild sind LCP-Kandidaten (das Bild trägt
   fetchpriority="high"), eine Opacity-Animation darauf würde die Core Web
   Vitals messbar verschlechtern.

   Sicherheitsnetze:
     - Der Startzustand hängt an `html.app`, das erst app.js setzt. Fällt JS
       aus, kann `opacity: 0` nie greifen — kein Risiko unsichtbarer Inhalte.
     - `animation … both` statt einer Transition: Der Endzustand bleibt auch
       dann stehen, wenn das Attribut früher entfernt wird als die Animation
       läuft (z. B. schnelles Mehrfach-Klicken).
     - Nur transform/opacity, also keine Layouthöhe — das Höhen-Notventil
       (checkOverflow) kann dadurch nicht anspringen. */
@keyframes app-enter {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

/* Zwei Ebenen, weil die Views unterschiedlich gebaut sind:
   - #v-leistungen / #v-kontakt: die Textblöcke sind DIREKTE Kinder von
     .app-view__inner — sie werden selbst gestaffelt.
   - #v-start / #v-spezialreinigung: .app-view__inner ist das Hero-GRID mit nur
     zwei Kindern (Textspalte + Bild). Würde man die Spalten staffeln, käme das
     Bild als Ganzes nachgereicht — unruhig. Dort fächern stattdessen die
     Elemente INNERHALB der Textspalte, das Bild kommt ruhig mit dem Slide. */
html.app .app-view[data-enter='true'] > .app-view__inner > *,
html.app .app-view[data-enter='true'] > .app-view__inner.app-hero > :not(.app-hero__media) > * {
  animation: app-enter 420ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
}
/* Beim Hero-Grid selbst nichts animieren — nur dessen Textspalten-Inhalte
   (siehe Regel oben). Sonst liefen zwei Animationen übereinander. */
html.app .app-view[data-enter='true'] > .app-view__inner.app-hero > * {
  animation: none;
}

/* Staffelung. Der erste Schritt liegt bei 120ms, damit der Einzug nach dem
   Slide (340ms) einsetzt und nicht dagegen arbeitet. */
html.app .app-view[data-enter='true'] > .app-view__inner > *,
html.app .app-view[data-enter='true'] > .app-view__inner.app-hero > :not(.app-hero__media) > * {
  animation-delay: 120ms;
}
html.app .app-view[data-enter='true'] > .app-view__inner > :nth-child(2),
html.app .app-view[data-enter='true'] > .app-view__inner.app-hero > :not(.app-hero__media) > :nth-child(2) {
  animation-delay: 180ms;
}
html.app .app-view[data-enter='true'] > .app-view__inner > :nth-child(3),
html.app .app-view[data-enter='true'] > .app-view__inner.app-hero > :not(.app-hero__media) > :nth-child(3) {
  animation-delay: 240ms;
}
html.app .app-view[data-enter='true'] > .app-view__inner > :nth-child(4),
html.app .app-view[data-enter='true'] > .app-view__inner.app-hero > :not(.app-hero__media) > :nth-child(4) {
  animation-delay: 300ms;
}
html.app .app-view[data-enter='true'] > .app-view__inner > :nth-child(n + 5),
html.app .app-view[data-enter='true'] > .app-view__inner.app-hero > :not(.app-hero__media) > :nth-child(n + 5) {
  animation-delay: 360ms;
}

@media (prefers-reduced-motion: reduce) {
  html.app .app-view {
    transition: none;
    opacity: 1;
    transform: none;
  }
  /* Der globale Kill-Switch in style.css kürzt nur die DAUER. Hier wird die
     Animation ganz abgeschaltet, damit kein Zwischenzustand aufblitzt. */
  html.app .app-view[data-enter='true'] > .app-view__inner > *,
  html.app .app-view[data-enter='true'] > .app-view__inner.app-hero > :not(.app-hero__media) > * {
    animation: none;
    opacity: 1;
    transform: none;
  }
  /* Glanz-Sweep und Foto-Zoom der Variante 4 (app4.css) */
  .app-cards--photo .app-card__icon::after { display: none; }
  .app-cards--photo .app-card:hover .app-card__icon img { transform: none; }
}

/* --------------------------------------------------------------------------
   10. Druck: Shell auflösen, alle Views ausgeben
   -------------------------------------------------------------------------- */
@media print {
  html.app, html.app body {
    height: auto;
    overflow: visible;
    position: static;
  }
  html.app .app { height: auto; display: block; }
  html.app .app-view {
    position: static;
    display: block !important;
    overflow: visible;
    page-break-after: always;
  }
  html.app .app-view[hidden] { display: block !important; }
  .app-head, .app-tabs, .app-dots { display: none !important; }
}
