/* ---------- Schriften ----------

   KEINE Webfonts, und das ist eine Entscheidung, kein Versehen.

   Hier stand ein @import auf Google Fonts (Fraunces, Inter, JetBrains Mono).
   Er hat nie gewirkt: eine Regel stand darueber, und ein @import nach der
   ersten Regel wird vom Browser verworfen - schon im Ausgangs-Commit. Die
   Anwendung lief also von Anfang an auf den Systemschriften, und genau so
   ist jede Ansicht gebaut und abgenommen worden.

   Ihn "geradezuruecken" haette deshalb nichts wiederhergestellt, sondern das
   Aussehen der ganzen Anwendung zum ersten Mal veraendert - und dafuer drei
   Dinge eingehandelt: die einzige externe Laufzeitabhaengigkeit ueberhaupt
   (alles andere hier ist eigenstaendig, bis hin zum Auswahlpfeil als
   data-URI), eine Anwendung, die bei gestoerter Internetverbindung anders
   aussieht, und eine IP-Uebertragung an Google bei jedem Seitenaufruf - bei
   einem internen Werkzeug mit Anmeldung ein vermeidbares Datenschutzthema.

   Die font-family-Angaben unten nennen deshalb nur noch, was tatsaechlich
   gerendert wird. Wer die entworfene Typografie doch will, legt die drei
   Familien als woff2 neben diese Datei und bindet sie per @font-face ein -
   dann bleibt die Anwendung eigenstaendig. */


/* ---------- Farben ----------

   Zwei Ansichten, eine Regelmenge: unter dieser Stelle steht keine Farbe mehr
   fest, alles greift auf die Tokens hier zu. Eine helle Ansicht ist damit ein
   zweiter Wertesatz und keine zweite Datei.

   Dunkel steht in :root und ist damit der Wert, der ohne Attribut gilt. Hell
   kommt ueber data-theme="light" an <html>.

   Bewusst KEIN @media (prefers-color-scheme): welche Ansicht gilt, entscheidet
   public/js/theme.js - ohne eigene Wahl folgt es der Systemeinstellung, ein
   Klick auf den Umschalter legt sie fest. Stuende die Systemabfrage hier,
   braeuchte sie einen zweiten Block mit denselben 19 Farbwerten, und ein
   geaenderter Hexwert waere an einer der beiden Stellen vergessen. Das Skript
   blockiert ohnehin schon vor dem ersten Zeichnen. */

:root {
  --bg: #14181d;
  --surface: #1b2127;
  --surface-alt: #20272e;
  --border: #2b333b;
  --text: #ece8e1;
  --text-dim: #8b9199;
  --accent: #c1793d;
  --accent-dim: #8c5a2c;
  --success: #6fa37b;
  --danger: #d4766b;
  --warning: #d9a54b;
  --radius: 6px;

  /* Der Akzent traegt zwei Rollen: Schriftfarbe auf dem Untergrund UND
     Flaeche des Hauptknopfes. --on-accent ist die Schrift AUF dieser Flaeche
     und laesst sich nicht aus --text ableiten: im Dunklen ist sie dunkel, im
     Hellen hell. */
  --accent-hover: #d4884c;
  --on-accent: #14181d;

  /* Durchscheinende Flaechen und Schatten brauchen je Ansicht eine eigene
     Deckkraft: 12 % Gruen fallen auf dunklem Grund auf, auf hellem nicht, und
     ein Schatten aus 35 % Schwarz wirkt im Hellen wie ein Fleck. */
  --tint-success: rgba(111, 163, 123, 0.12);
  --tint-danger: rgba(212, 118, 107, 0.12);
  --pulse-ring: rgba(111, 163, 123, 0.5);
  --menu-shadow: 0 10px 24px rgba(0, 0, 0, 0.35);
  --login-glow: rgba(193, 121, 61, 0.08);

  /* Der Pfeil der Auswahlfelder ist eine Bilddatei, seine Farbe steht also in
     der URL und nicht in einer Eigenschaft - er muss als ganzes Token
     ausgetauscht werden. %23 ist das # der Farbe. */
  --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%238b9199' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

  /* Die aufgeklappte Liste eines <select> zeichnet das Betriebssystem, nicht
     die Seite - ohne diese Zeile ist sie weiss, egal wie das Feld gestylt
     ist. Gilt genauso fuer Scrollbalken und Zahlen-Spinner. */
  color-scheme: dark;
}

/* Nicht Weiss und nicht Schwarz: der dunkle Satz ist warm getoent (#ece8e1
   auf #14181d), ein reines #fff/#000 daneben liesse die helle Ansicht wie ein
   fremdes Programm aussehen. Akzent, Gruen, Rot und Gelb sind abgedunkelt -
   #c1793d auf hellem Grund kommt nur auf 3,2:1 und ist als Schrift unlesbar;
   die Werte hier liegen alle ueber 4,5:1. */
:root[data-theme="light"] {
  --bg: #f5f2ec;
  --surface: #fffefb;
  --surface-alt: #eae5dc;
  --border: #d8d1c5;
  --text: #1c2227;
  --text-dim: #676d74;
  --accent: #9a5d28;
  --accent-dim: #b5834f;
  --accent-hover: #834d1f;
  --on-accent: #fffefb;
  --success: #3d7550;
  --danger: #a4322a;
  --warning: #83600f;

  --tint-success: rgba(61, 117, 80, 0.13);
  --tint-danger: rgba(164, 50, 42, 0.11);
  --pulse-ring: rgba(61, 117, 80, 0.45);
  --menu-shadow: 0 10px 24px rgba(28, 34, 39, 0.14);
  --login-glow: rgba(154, 93, 40, 0.1);

  --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%23676d74' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

  color-scheme: light;
}

* {
  box-sizing: border-box;
}

/* Ohne diese Regel versteckt das Attribut hidden nichts, sobald das Element
   auch eine Klasse mit display traegt: die Browser-Vorgabe fuer [hidden] ist
   eine Regel des Browsers, und Autorenregeln schlagen die unabhaengig von der
   Spezifitaet. .tab-count{display:inline-block} und .pager{display:flex}
   hatten damit gewonnen - die leeren Zaehler in den Einstellungsreitern
   nahmen Platz weg, und ein Seitenwechsler blieb nach einem fehlgeschlagenen
   Aufruf samt Knoepfen stehen.

   !important, weil [hidden] mit 0-1-0 dieselbe Spezifitaet hat wie eine
   Klasse: ohne waere nur die Reihenfolge entscheidend, und jede spaeter
   angefuegte Klassenregel wuerde das Versteckte wieder hervorholen. */
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, sans-serif;
  font-size: 15px;
  line-height: 1.5;
}

h1, h2, h3 {
  font-family: serif;
  font-weight: 500;
  letter-spacing: -0.01em;
  margin: 0;
}

.mono {
  font-family: monospace;
  font-size: 0.85em;
}

a {
  color: var(--accent);
  text-decoration: none;
}

/* ---------- Login ---------- */

.login-screen {
  min-height: 100vh;
  display: flex;
  /* Spalte, seit unter der Karte die Sprachleiste steht - als Zeile saesse
     sie daneben. */
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: 24px;
  background:
    radial-gradient(circle at 20% 15%, var(--login-glow), transparent 40%),
    var(--bg);
}

.login-card {
  /* Nicht fest 360px: auf einem 360er-Display bliebe kein Rand, auf einem
     320er wuerde die Karte aus dem Bild laufen. */
  width: min(360px, 100%);
  padding: 40px 36px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* Die Marke als Bild ueber dem Schriftzug. Bewusst klein: sie soll die
   Anmeldung einordnen, nicht die Karte fuellen. */
.login-logo {
  display: block;
  margin-bottom: 14px;
}

/* Sprachwahl unter der Karte - gedaempft, weil sie selten gebraucht wird,
   aber auffindbar, weil sie ohne Anmeldung der einzige Weg dorthin ist. */
.lang-switch {
  display: flex;
  gap: 4px;
}

.lang-option {
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: 4px 10px;
  font-family: monospace;
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  color: var(--text-dim);
  cursor: pointer;
}

.lang-option:hover { color: var(--text); }

.lang-option.is-current {
  border-color: var(--border);
  color: var(--accent);
}

.login-eyebrow {
  font-family: monospace;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 8px;
  display: block;
}

.login-card h1 {
  font-size: 1.7rem;
  margin-bottom: 28px;
}

.field {
  margin-bottom: 16px;
}

.field label {
  display: block;
  font-size: 0.8rem;
  color: var(--text-dim);
  margin-bottom: 6px;
}

.field input, .field select {
  width: 100%;
  padding: 10px 12px;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font-size: 0.95rem;
  font-family: inherit;
}

.field input:focus, .field select:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Eigener Pfeil: der systemeigene bringt in jedem Browser eine andere
   Groesse und Farbe mit und sitzt neben den Textfeldern sichtbar daneben.
   Als data-URI, damit kein zusaetzlicher Request faellig wird - die Farbe
   steckt darin und kommt deshalb aus --select-arrow. */
.field select, .property-filters select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 34px;
  background-image: var(--select-arrow);
  background-repeat: no-repeat;
  background-position: right 12px center;
  cursor: pointer;
}

.field select:hover, .property-filters select:hover { border-color: var(--accent-dim); }

.btn-primary {
  width: 100%;
  padding: 11px;
  margin-top: 8px;
  background: var(--accent);
  border: none;
  border-radius: var(--radius);
  color: var(--on-accent);
  font-weight: 600;
  font-size: 0.95rem;
  cursor: pointer;
  transition: background 0.15s ease;
}

.btn-primary:hover {
  background: var(--accent-hover);
}

.btn-primary:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 2px;
}

.login-error {
  color: var(--danger);
  font-size: 0.85rem;
  margin-top: 14px;
  min-height: 1.2em;
}

/* ---------- Dashboard ---------- */

/* Die Kopfzeile bleibt beim Scrollen oben stehen: Mandantenname und
   Navigation sind auf langen Listen (Immobilien, Protokoll) genau dann
   gefragt, wenn man weit unten ist.

   Der eigene Hintergrund ist dafuer Pflicht - ohne ihn scheint der Inhalt
   durch, weil die Leiste bisher einfach die Seitenfarbe zeigte. var(--bg) ist
   dieselbe Farbe wie vorher, das Aussehen im unscrollten Zustand aendert sich
   also nicht.

   z-index unter dem Lichtkasten (100), damit ein grosses Bild weiter ueber
   ALLES liegt - auch ueber der Kopfzeile. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 32px;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}

.topbar .brand {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.topbar .brand h1 {
  font-size: 1.3rem;
}

.brand-link {
  display: flex;
  align-items: baseline;
  gap: 10px;
  color: inherit;
  text-decoration: none;
}

.brand-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Die Bildmarke kommt als <img> auf /favicon.svg herein - so steht ihre
   Geometrie genau einmal im Projekt, in scripts/make-icons.js. Deshalb kein
   currentColor: die Farbe ist in der Datei. */
.brand-mark {
  width: 24px;
  height: 24px;
  align-self: center;
  flex: 0 0 auto;
}

.topbar .tenant-name {
  color: var(--text-dim);
  font-size: 0.85rem;
}

/* Das Trennzeichen gehoert zur E-Mail, nicht zum Mandanten: so verschwindet
   es mit ihr, wenn der Platz auf dem Telefon nicht fuer beides reicht. */
.topbar .user-email {
  color: var(--text-dim);
  font-size: 0.85rem;
}

.topbar .user-email:hover { color: var(--accent); }

.topbar .user-email:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* margin-right statt eines Leerzeichens im content: ein Leerzeichen ist nur
   so breit wie im Fliesstext ueblich, der Abstand zum Mandantennamen davor
   kommt aber vom 10px-gap in .topbar .brand - der Punkt sass dadurch naeher
   an der E-Mail als am Mandanten, statt mittig zwischen beiden. */
.topbar .user-email::before { content: "·"; margin-right: 10px; }

/* Zeigt, dass GERADE ein Dropbox-Sync oder ein Export laeuft - egal ob auf
   dieser Seite oder anderswo gestartet (beide haengen nicht an der Seite, die
   sie ausgeloest hat).
 *
 * Eine eigene Zeile am unteren Rand statt eines Rings in der Kopfzeile: dort
 * reichte der Platz ab ca. 1050px nicht mehr fuer Marke, Mandant, E-Mail,
 * Text UND Linkleiste zusammen. Erzeugt wird sie in js/nav.js; die
 * Begruendung steht dort ausfuehrlich.
 *
 * position: fixed und NICHT sticky, obwohl der Kommentar in nav.js sie
 * "sticky Fusszeile" nennt: dieselbe Datei rechnet mit
 * document.body.style.paddingBottom Platz fuer sie frei. Das ergibt nur
 * Sinn, wenn sie aus dem Fluss heraus ist - klebte sie bloss, zaehlte der
 * Abstand doppelt. */
.sync-footer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  /* Ueber der Blaetter-Leiste und der Listen-Kopfzeile (beide z-index 5),
     unter der Kopfzeile (30) - die bleibt die oberste Ebene. */
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 32px;
  background: var(--bg);
  border-top: 1px solid var(--border);
  font-size: 0.82rem;
}

/* MUSS hier stehen: nav.js blendet die Zeile ueber das hidden-Attribut aus,
   und eine eigene display-Regel schlaegt das [hidden]{display:none} des
   Browsers. Ohne diese Zeile klebte die Fusszeile dauerhaft am Rand, auch
   wenn gar nichts laeuft. */
.sync-footer[hidden] { display: none; }

.sync-footer-spinner {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  border-radius: 50%;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  animation: sync-spin 0.8s linear infinite;
}

@keyframes sync-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .sync-footer-spinner { animation: none; }
}

.logout-btn {
  background: none;
  border: 1px solid var(--border);
  color: var(--text-dim);
  padding: 7px 14px;
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 0.85rem;
  font-family: inherit;
}

.logout-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.topbar-actions {
  display: flex;
  align-items: center;
  gap: 16px;
}

/* Umschalter fuer helle und dunkle Ansicht. Quadratisch und randlos: er steht
   zwischen den Navigationslinks und dem Abmelde-Knopf und soll keinen von
   beiden nachahmen. 34px sind am Schreibtisch genug - auf dem Telefon wird er
   im Mobil-Abschnitt auf 44px gebracht. */
.theme-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  padding: 0;
  background: none;
  border: none;
  border-radius: var(--radius);
  color: var(--text-dim);
  cursor: pointer;
}

.theme-btn:hover { color: var(--accent); }
.theme-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- Hamburger-Knopf ----------
   Am Schreibtisch nicht vorhanden: display:none nimmt ihn auch aus der
   Tabreihenfolge, hidden allein tut das nicht. Sichtbar wird er erst im
   Mobil-Abschnitt am Ende dieser Datei. */
.nav-toggle {
  display: none;
  position: relative;
  width: 42px;
  height: 42px;
  flex: 0 0 auto;
  padding: 0;
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
}

.nav-toggle:hover { border-color: var(--accent-dim); }
.nav-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Drei Balken aus einem Element: der mittlere ist das Element selbst, die
   beiden aeusseren sind seine Pseudo-Elemente. Ein Knopf mit drei leeren
   <span> waere derselbe Strich in mehr Markup. */
.nav-toggle-icon,
.nav-toggle-icon::before,
.nav-toggle-icon::after {
  position: absolute;
  left: 50%;
  width: 20px;
  height: 2px;
  margin-left: -10px;
  background: var(--text);
  transition: transform 0.18s ease, opacity 0.18s ease;
}

.nav-toggle-icon { top: 50%; margin-top: -1px; }
.nav-toggle-icon::before { content: ""; top: -6px; left: 0; margin-left: 0; }
.nav-toggle-icon::after { content: ""; top: 6px; left: 0; margin-left: 0; }

/* Aufgeklappt wird aus den Balken ein Kreuz - der Zustand ist damit auch ohne
   Blick auf das Menue erkennbar. Gesteuert ueber aria-expanded am Knopf, statt
   ueber eine zweite Klasse: der Zustand steht dann genau einmal im DOM. */
.nav-toggle[aria-expanded="true"] .nav-toggle-icon { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle-icon::before { transform: translateY(6px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-icon::after { transform: translateY(-6px) rotate(-45deg); }

@media (prefers-reduced-motion: reduce) {
  .nav-toggle-icon,
  .nav-toggle-icon::before,
  .nav-toggle-icon::after { transition: none; }
}

.nav-link {
  font-size: 0.85rem;
}

main {
  padding: 32px;
  max-width: 1100px;
  margin: 0 auto;
}

.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 16px;
  margin-bottom: 36px;
}

.stat-card {
  display: block;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px 20px;
}

/* Die Kacheln sind Links. Sie sollen aber weiter wie Kacheln aussehen -
   also keine Linkfarbe, kein Unterstrich, und der Hinweis auf das Ziel
   erst beim Darueberfahren. */
a.stat-card {
  color: inherit;
  text-decoration: none;
  transition: border-color 0.15s ease;
}
a.stat-card:hover, a.stat-card:focus-visible { border-color: var(--accent-dim); }
.stat-hint {
  display: block;
  margin-top: 8px;
  color: var(--accent);
  font-size: 0.75rem;
  opacity: 0;
  transition: opacity 0.15s ease;
}
a.stat-card:hover .stat-hint, a.stat-card:focus-visible .stat-hint { opacity: 1; }

/* Der Hinweis liegt dauerhaft im Layout und wird nur ein- und ausgeblendet -
   sonst wuerde die Kachel beim Darueberfahren wachsen und die Abschnitte
   darunter verschieben. Ohne Bewegungswunsch entfaellt nur das Ueberblenden,
   der Hinweis erscheint dann hart beim Darueberfahren - ein sofortiges
   Umschalten ist keine Bewegung. Vorher stand hier opacity: 1, damit war der
   Hinweis auf Systemen mit abgeschalteten Animationen (Windows 11 mit
   deaktivierten Animationseffekten z. B.) dauerhaft sichtbar. */
@media (prefers-reduced-motion: reduce) {
  .stat-hint { transition: none; }
}

.stat-card .label {
  font-size: 0.75rem;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 6px;
}

.stat-card .value {
  font-family: serif;
  font-size: 2rem;
}

section {
  margin-bottom: 40px;
}

/* Auch das h2 in .section-heading-row erfassen: als Enkel von <section>
   griff der Kindselektor dort nicht, die Überschrift fiel auf die
   Serifen-Grundregel zurück und sah anders aus als alle anderen. */
section > h2,
.section-heading-row > h2 {
  font-size: 1rem;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-family: system-ui, sans-serif;
  font-weight: 600;
  margin-bottom: 14px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}

.section-heading-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  border-bottom: 1px solid var(--border);
}

.section-heading-row > h2 { border-bottom: none; margin-bottom: 0; }
.section-note { color: var(--text-dim); font-size: 0.75rem; }
.property-table { overflow-x: auto; }
.property-table-head, .property-table-row { min-width: 780px; display: grid; grid-template-columns: 1.3fr 1.2fr 0.7fr 0.9fr 1fr; gap: 16px; align-items: center; }
.property-table-head { padding: 10px 4px 8px; color: var(--text-dim); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; }
.property-table-row { padding: 14px 4px; border-bottom: 1px solid var(--border); font-size: 0.88rem; }
.property-table-row strong, .property-meta { display: block; }
.property-meta { color: var(--text-dim); font-size: 0.76rem; margin-top: 2px; }

.ledger {
  display: flex;
  flex-direction: column;
}

.ledger-row {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  align-items: center;
  gap: 16px;
  padding: 13px 4px;
  border-bottom: 1px solid var(--border);
}

.ledger-row:last-child {
  border-bottom: none;
}

.pulse-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 8px;
}

.pulse-dot.ok {
  background: var(--success);
  box-shadow: 0 0 0 0 var(--pulse-ring);
  animation: pulse 2s infinite;
}

.pulse-dot.fail {
  background: var(--danger);
}

.pulse-dot.pending {
  background: var(--text-dim);
}

/* Der auslaufende Ring endet auf "transparent" statt auf demselben Gruen mit
   Alpha 0: Browser rechnen die Zwischenschritte mit vorgewichtetem Alpha, das
   Ergebnis ist identisch - und es braucht kein zweites Token je Ansicht. */
@keyframes pulse {
  0% { box-shadow: 0 0 0 0 var(--pulse-ring); }
  70% { box-shadow: 0 0 0 6px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

.format-tag {
  font-family: monospace;
  font-size: 0.72rem;
  padding: 3px 8px;
  border-radius: 3px;
  background: var(--surface-alt);
  color: var(--text-dim);
  white-space: nowrap;
}

.empty-state {
  color: var(--text-dim);
  font-size: 0.9rem;
  padding: 20px 4px;
}

/* Umbrechen, sobald die Zeile nicht mehr reicht.

   Vorher stand hier eine starre Zeile, und die ging beim iPhone im Querformat
   ueber den Rand hinaus. Der Grund ist, dass 720px - die Schwelle, ab der
   unten der Klappknopf uebernimmt - die VIEWPORT-Breite meint: ein iPhone
   liegend misst 844px und faellt damit in die Schreibtisch-Darstellung.
   Nutzbar sind davon aber nur rund 686px, weil iOS im Querformat links und
   rechts die Kerbe freihaelt und main noch 32px Innenabstand je Seite
   abzieht. Die vier Felder brauchen zusammen rund 900px.

   Eine hoehere Schwelle waere der schlechtere Weg: der Klappknopf oeffnet
   eine SPALTE, und die frisst auf einem liegenden Telefon mit 390px Hoehe
   den ganzen Schirm. Zwei umgebrochene Zeilen sind dort das bessere Bild.

   Unterhalb von 720px bleibt die Zeile davon unberuehrt - ein Spaltenfluss
   ohne Hoehenbegrenzung bricht nie um. */
.property-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-bottom: 20px;
}

/* Knopf zum Auf- und Zuklappen der Filterzeile. Am Schreibtisch nicht
   vorhanden: dort ist die Zeile immer sichtbar, und display: none nimmt den
   Knopf auch aus der Tabreihenfolge. */
.filter-toggle {
  display: none;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
  padding: 9px 14px;
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-dim);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}

.filter-toggle:hover { border-color: var(--accent-dim); color: var(--accent); }
.filter-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Ein Dreieck, das sich beim Aufklappen dreht - der Zustand ist damit auch
   ohne Blick auf die Zeile darunter erkennbar. */
.filter-toggle::after {
  content: "";
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  transition: transform 0.18s ease;
}

.filter-toggle[aria-expanded="true"]::after { transform: rotate(180deg); }

@media (prefers-reduced-motion: reduce) {
  .filter-toggle::after { transition: none; }
}

.property-filters label {
  display: flex;
  align-items: center;
  gap: 9px;
  color: var(--text-dim);
  font-size: 0.8rem;
}

.property-filters select {
  padding: 8px 30px 8px 10px;
  /* background-color, nicht die Kurzform: die wuerde das oben gesetzte
     Pfeil-Symbol wieder loeschen. */
  background-color: var(--surface-alt);
  background-position: right 10px center;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font: inherit;
}

.property-directory {
  border-top: 1px solid var(--border);
}

.property-directory-scroll { min-width: 850px; width: 100%; }
.property-directory-scroll.no-targets { min-width: 0; width: 100%; }

/* Erzwingt auf dem Telefon einen Zeilenumbruch vor den Exporthaken.
   display: none nimmt das Element am Schreibtisch komplett aus dem Grid -
   sonst hätte die Kopfzeile eine Spalte weniger als die Zeilen darunter. */
.directory-break { display: none; }
.directory-head, .directory-row { display: grid; grid-template-columns: 1.35fr 1.35fr 0.75fr 0.95fr 0.55fr repeat(4, minmax(105px, 0.8fr)); gap: 14px; align-items: center; }
.directory-head { padding: 10px 4px 8px; color: var(--text-dim); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em; }
.directory-row { padding: 13px 4px; border-bottom: 1px solid var(--border); font-size: 0.84rem; }
.directory-row.is-disabled { opacity: 0.5; }

/* Sortierbare Spaltenkoepfe sollen wie die uebrigen Kopfzeilen-Labels
   aussehen, nicht wie Buttons - deshalb erben sie Schrift und Farbe. */
.sort-header {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-transform: inherit;
  letter-spacing: inherit;
  text-align: left;
  cursor: pointer;
}
.sort-header:hover, .sort-header:focus-visible { color: var(--accent); }
.sort-header.is-sorted { color: var(--text); }
/* Feste Breite, damit die Kopfzeile beim Wechsel der Spalte nicht springt. */
.sort-arrow { width: 0.8em; color: var(--accent); }
/* Beschriftung und Haken einer Exportspalte müssen dieselbe Ausrichtung
   haben, sonst stehen sie bei einer 105px breiten Spalte gut fünfzig Pixel
   auseinander. Mittig statt links, weil ein 17px-Kästchen am linken Rand
   einer breiten Spalte aussieht, als hätte es sich verlaufen. */
.directory-target-head, .target-toggle-cell { text-align: center; }
/* Die ganze Spalte schaltet den Haken. Das <label> um den Haken herum erledigt
   das von selbst - aber nur so weit, wie es reicht, und genau das war der
   Haken: die Zeile ist ein Grid mit align-items:center, das Label also nur so
   hoch wie das 17px-Kaestchen. Ueber und unter ihm blieb ein Streifen, der zur
   ZEILE gehoert - und ein Klick dorthin fuehrte ins Detail statt umzuschalten.

   align-self:stretch nimmt die volle Zeilenhoehe, flex haelt das Kaestchen
   trotzdem mittig. Damit ist die ganze Zellflaeche das Label. */
.target-toggle-cell {
  cursor: pointer;
  align-self: stretch;
  display: flex;
  align-items: center;
  justify-content: center;
}
.directory-area, .directory-price { text-align: right; }
.sort-header.directory-area, .sort-header.directory-price { justify-content: flex-end; }
.target-toggle-cell input { width: 17px; height: 17px; accent-color: var(--accent); cursor: pointer; }
/* Die ganze Zeile fuehrt in die Detailansicht - frueher tat das ein Auge am
   rechten Rand, das erst beim Ueberfahren erschien. Ein Bedienelement, das man
   nur sieht, wenn man schon dort ist, findet niemand; und auf dem Telefon, wo
   es kein Ueberfahren gibt, musste es ohnehin dauerhaft eingeblendet werden.

   Zeiger UND Hintergrund: der Hintergrund allein ist bei einer Tabelle
   mehrdeutig (er koennte auch blosse Zeilenhervorhebung sein), der Zeiger
   allein faellt erst auf, wenn die Maus schon steht. */
.directory-row, .property-table-row { cursor: pointer; }
.directory-row:hover, .property-table-row:hover { background: var(--surface-alt); }

/* Deaktivierte Zeilen stehen auf halber Deckkraft - ein Hintergrund darunter
   waere kaum zu sehen. Sie bekommen deshalb beim Ueberfahren die volle
   Deckkraft statt einer Flaeche: anklickbar sind sie genauso. */
.directory-row.is-disabled:hover { opacity: 0.75; background: none; }

/* Die Objektnummer bleibt ein echter Verweis - fuer die Tastatur, fuer
   "in neuem Tab oeffnen" und fuer Vorleseprogramme, denen ein anklickbares
   div nichts sagt. Sie soll nur nicht wie einer aussehen, damit die Zeile
   ruhig bleibt. */
.row-link { color: inherit; text-decoration: none; }
.row-link:hover, .row-link:focus-visible { text-decoration: underline; }
.back-link { display: inline-block; margin-bottom: 24px; font-size: 0.85rem; }

/* Kopfleiste der Detailansicht: Rueckweg links, Blaettern rechts. Umbricht auf
   schmalen Geraeten, damit die beiden Knoepfe nicht auf Briefmarkengroesse
   zusammengedrueckt werden. */
.detail-nav { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 24px; }
.detail-nav .back-link { margin-bottom: 0; }
.detail-steps { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.detail-position { color: var(--text-dim); font-size: 0.78rem; white-space: nowrap; }
/* Kein Verweis-Unterstrich: die Knoepfe sollen wie die der Blaetter-Leiste
   aussehen, deren Stil sie sich teilen (s. .pager-step). */
.detail-step { text-decoration: none; }
.detail-step.is-disabled { opacity: 0.35; cursor: default; }
.property-detail-main { max-width: 960px; }
.detail-hero { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; margin-bottom: 24px; }
.detail-hero h2 { font-size: 2.3rem; margin-bottom: 6px; }
.detail-hero p { color: var(--text-dim); margin: 0; }
.detail-status { border-radius: 3px; padding: 5px 9px; font-size: 0.75rem; }
.detail-status.active, .included { color: var(--success); background: var(--tint-success); }
.detail-status.disabled, .excluded { color: var(--danger); background: var(--tint-danger); }
.detail-summary { display: grid; grid-template-columns: repeat(6, 1fr); gap: 1px; background: var(--border); border: 1px solid var(--border); margin-bottom: 36px; }
.detail-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; padding: 14px; background: var(--surface); }
.detail-field span { color: var(--text-dim); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; }
.detail-field strong { overflow-wrap: anywhere; font-weight: 500; }
.detail-rich-field { grid-column: 1 / -1; }
.rich-text { overflow-wrap: anywhere; }
/* Gekuerzte Ansicht: drei Zeilen, den Rest holt der Umschalter. */
.rich-text.is-clamped { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; }
.rich-toggle { align-self: flex-start; margin-top: 2px; }
.rich-toggle:hover { text-decoration: underline; }
.rich-text p { margin: 0 0 8px; }
.rich-text p:last-child { margin-bottom: 0; }
.rich-text ul, .rich-text ol { margin: 0; padding-left: 20px; }
.detail-section { margin-bottom: 32px; }
.detail-section h3 { margin-bottom: 14px; font-size: 1.1rem; }
.detail-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; background: var(--border); border: 1px solid var(--border); }
/* Block und NICHT flex: die Zeile "x von y Bildern gefunden" gehoert ueber die
   Galerie, nicht daneben. Als flex-Container mit Umbruch stimmte das nur
   zufaellig - bei vielen Bildern war die Galerie zu breit und rutschte von
   selbst nach unten, bei EINEM Bild passte sie daneben und der Text stand
   mitten in der Reihe. Den Abstand macht die margin unten drunter. */
.photo-list { display: block; }
.photo-status { color: var(--text-dim); font-size: 0.8rem; margin-bottom: 12px; }
.photo-gallery { display: flex; flex-wrap: wrap; gap: 12px; }
.photo-gallery--grouped { display: block; }
.photo-group + .photo-group { margin-top: 20px; }
.photo-group h4 { margin: 0 0 10px; }
.photo-preview, .photo-missing { display: flex; flex-direction: column; gap: 6px; width: 150px; color: var(--text-dim); font-size: 0.72rem; overflow-wrap: anywhere; }
.photo-preview img { width: 150px; height: 105px; object-fit: cover; background: var(--surface-alt); border: 1px solid var(--border); }
.photo-preview--floorplan img { width: 220px; height: 170px; object-fit: contain; }
.photo-preview:hover img { border-color: var(--accent); }
.photo-missing { padding: 12px; background: var(--surface-alt); border: 1px dashed var(--border); }
.photo-missing small { color: var(--danger); }
.photo-lightbox[hidden] { display: none; }
.photo-lightbox { position: fixed; inset: 0; z-index: 100; display: grid; place-items: center; padding: 28px; background: rgb(0 0 0 / 0.82); cursor: zoom-out; }
.photo-lightbox-image { max-width: min(1100px, 94vw); max-height: 88vh; object-fit: contain; box-shadow: 0 18px 60px rgb(0 0 0 / 0.45); cursor: default; }
.photo-lightbox-close { position: absolute; top: 18px; right: 22px; width: 42px; height: 42px; border: 1px solid rgb(255 255 255 / 0.55); border-radius: 50%; background: rgb(0 0 0 / 0.35); color: white; font-size: 1.7rem; line-height: 1; cursor: pointer; }
.photo-lightbox-close:hover, .photo-lightbox-close:focus-visible { border-color: white; background: rgb(0 0 0 / 0.7); }
.lightbox-open { overflow: hidden; }
.export-list { border-top: 1px solid var(--border); }
.export-state { display: flex; justify-content: space-between; gap: 16px; padding: 12px 4px; border-bottom: 1px solid var(--border); }
.export-state small { color: var(--text-dim); margin-left: 6px; }

/* ---------- Settings ---------- */

/* 920px statt vorher 760px: bei acht Reitern (mit "Sicherung" dazugekommen)
   war die Leiste auf 760px schon bei einem Admin-Konto knapp - sie soll
   absichtlich NICHT umbrechen (s. Kommentar bei .settings-tabs unten), also
   braucht sie mehr Platz statt einer zweiten Zeile. Die Formulare bleiben
   bei zwei Spalten im .form-grid gut lesbar, auch mit den paar zusaetzlichen
   Pixeln je Spalte. */
.settings-main {
  max-width: 920px;
}

.settings-header {
  margin-bottom: 28px;
}

.settings-header h2 {
  font-size: 2.2rem;
  margin-bottom: 8px;
}

.settings-header p {
  color: var(--text-dim);
  margin: 0;
}

.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

.settings-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 10px; }

.settings-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px 20px;
  border-top: 1px solid var(--border);
  margin-top: 8px;
  padding-top: 24px;
}

/* Ein Kontrollkaestchen mit erklaerender Beschriftung und daneben drei Knoepfe
   passen in keine Zeile. Das Kaestchen bekommt deshalb immer eine eigene Zeile
   darueber - besser als Knoepfe, die auf zwei Zeilen umbrechen und dabei ihre
   Reihenfolge verlieren. Ein Kaestchen kommt in dieser Leiste ohnehin nur
   zusammen mit .settings-actions vor. */
.settings-footer .toggle-field { flex: 1 1 100%; }

/* Damit die Knopfgruppe in ihrer eigenen Zeile rechts bleibt: sie ist dann
   das einzige Element, und space-between allein wuerde sie nach links legen. */
.settings-footer .settings-actions { margin-left: auto; }

.toggle-field {
  display: flex;
  align-items: center;
  gap: 9px;
  color: var(--text-dim);
  font-size: 0.9rem;
  cursor: pointer;
}

.toggle-field input {
  accent-color: var(--accent);
  width: 16px;
  height: 16px;
}

.settings-submit {
  width: auto;
  min-width: 140px;
  margin-top: 0;
}

.settings-submit:disabled {
  cursor: wait;
  opacity: 0.6;
}

.form-status {
  min-height: 1.4em;
  margin-top: 18px;
  color: var(--text-dim);
  font-size: 0.85rem;
  /* Mehrzeilige Meldungen bleiben mehrzeilig: setStatus() schreibt ueber
     textContent, und ohne diese Regel faellt jeder Zeilenumbruch zu einem
     Leerzeichen zusammen. Die Antwort des Mailservers waere dann eine einzige
     lange Zeile. pre-line und nicht pre: umbrochen wird weiterhin normal,
     nur die eigenen Umbrueche bleiben stehen. */
  white-space: pre-line;
}

.form-status.success { color: var(--success); }
.form-status.error { color: var(--danger); }
/* overflow-wrap: anywhere, weil in einem Hinweis eine URL stehen kann.
   "Aktuelle Quelle: Freigabelink https://www.dropbox.com/scl/fo/..." unter
   dem Freigabelink-Feld (s. showDropboxSource() in js/settings.js) ist ein
   einziges Wort ohne Trennstelle - im Hochformat schob es die Karte ueber
   den Rand hinaus. "anywhere" statt "break-word": nur das erste senkt auch
   die Mindestbreite des Kastens, und genau die ist hier der Hebel. */
.field-hint { display: block; margin-top: 6px; color: var(--text-dim); font-size: 0.75rem; overflow-wrap: anywhere; }

/* Nie umbrechen, notfalls seitlich schieben. Eine Reiterleiste, die beim
   Erscheinen eines Zaehlers auf zwei Zeilen geht, verschiebt alles darunter -
   und der Zaehler kommt erst, wenn der Reiter einmal geoeffnet wurde. Der
   Scrollbalken bleibt weg: der angeschnittene Reiter am Rand ist der Hinweis
   und kostet keine Zeile Hoehe. */
.settings-tabs {
  display: flex;
  flex-wrap: nowrap;
  gap: 22px;
  overflow-x: auto;
  scrollbar-width: none;
  border-bottom: 1px solid var(--border);
  margin-bottom: 24px;
}

.settings-tabs::-webkit-scrollbar { display: none; }

.settings-tabs a {
  flex: 0 0 auto;
  white-space: nowrap;
}

/* Zaehler im Reiter: nur die Zahl. Den Sinn traegt das title-Attribut -
   "(195 offen)" waere in drei Sprachen drei verschiedene Laengen, und die
   Leiste ist bei acht Reitern (Admin-Konto) ohnehin knapp. */
.tab-count {
  display: inline-block;
  min-width: 1.5em;
  padding: 1px 5px;
  border-radius: 9px;
  background: var(--surface-alt);
  color: var(--text-dim);
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

/* Offene Uebersetzungen sind eine Arbeitsliste - die soll auffallen. */
.tab-count.is-open { background: var(--accent-dim); color: var(--text); }

.settings-tabs a {
  padding: 0 2px 11px;
  color: var(--text-dim);
  border-bottom: 2px solid transparent;
}

.settings-tabs a.active {
  color: var(--text);
  border-color: var(--accent);
}

.settings-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 28px;
}

.panel-heading, .form-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 24px;
}

.panel-heading h3, .form-heading h3 {
  font-size: 1.1rem;
  margin-bottom: 4px;
}

.config-state {
  color: var(--success);
  font-family: monospace;
  font-size: 0.75rem;
  white-space: nowrap;
}
.config-state.is-not-configured { color: var(--danger); }

.btn-secondary, .text-button, .icon-button {
  font: inherit;
  cursor: pointer;
}

.btn-secondary {
  background: transparent;
  border: 1px solid var(--accent);
  color: var(--accent);
  border-radius: var(--radius);
  padding: 8px 12px;
}

.target-list { display: flex; flex-direction: column; }

.target-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding: 16px 0;
  border-bottom: 1px solid var(--border);
}

.target-row:last-child { border-bottom: none; }
.target-main { display: flex; align-items: center; gap: 9px; min-width: 0; }
.target-main strong, .target-meta { display: block; }
.target-meta { color: var(--text-dim); font-size: 0.8rem; margin-top: 2px; }
.target-actions { display: flex; gap: 12px; white-space: nowrap; }
.text-button { background: none; border: 0; color: var(--accent); padding: 2px 0; font-size: 0.8rem; }
.danger-button { color: var(--danger); }
.target-form { border-top: 1px solid var(--border); margin-top: 18px; padding-top: 24px; }
/* Anbieterangaben im FTP-Formular. Als eigener, abgesetzter Abschnitt: sie
   erscheinen nur bei OpenImmo, und ohne Trennlinie sieht das Umschalten des
   Exportformats aus, als tauchten wahllos Felder auf. */
#target-provider { margin: 24px 0 18px; padding-top: 20px; border-top: 1px solid var(--border); }
.provider-intro { margin: 0 0 16px; }
/* Knoepfe in Abschnittskoepfen nicht umbrechen - "+ Neuer Benutzer" und
   "Bestand pruefen" wurden sonst zweizeilig. */
.panel-heading .btn-secondary { white-space: nowrap; }

/* Ergebnis des Export-Probelaufs. Die Zeile ist ein Flex-Container mit
   Name und Aktionen - das Ergebnis soll darunter ueber die ganze Breite
   stehen, deshalb umbrechen und volle Basis. */
.target-row { flex-wrap: wrap; }
/* width statt flex-basis: unter 720px wird .target-row zur Spalte, dort
   waere eine Basis von 100% eine Hoehen- und keine Breitenangabe. */
.target-result { width: 100%; margin-top: 14px; font-size: 0.82rem; }
.result-note { color: var(--text-dim); margin: 0 0 10px; }
.result-warn { color: var(--danger); margin: 0 0 10px; overflow-wrap: anywhere; }
/* Feste 3 Spalten statt auto-fit: es sind immer genau sechs Werte, damit
   gehen 3x2 auf. Bei auto-fit bleibt je nach Breite eine Zelle leer, und
   die faellt als Loch in der Rasterlinie auf. */
.result-facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1px; margin: 0 0 12px; background: var(--border); border: 1px solid var(--border); }
.result-facts > div { display: flex; flex-direction: column; gap: 3px; padding: 10px 12px; background: var(--surface); }
.result-facts dt { color: var(--text-dim); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; }
.result-facts dd { margin: 0; overflow-wrap: anywhere; }
.result-actions { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 12px; }
.result-actions a { text-decoration: none; }
.result-actions a:hover { text-decoration: underline; }

/* Kopfzeile des Probelauf-Ergebnisses: Meldung links, Schliessknopf rechts.
   Das Feld war vorher gar nicht zu schliessen - s. resultPanel() in
   public/js/settings.js. align-items: flex-start, damit das "x" bei einer
   mehrzeiligen Meldung oben stehen bleibt und nicht in die Mitte rutscht. */
.result-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.result-head > :first-child { margin: 0; }
.result-xml summary { color: var(--text-dim); cursor: pointer; }
/* Die XML ist breiter als die Spalte - lieber scrollen als die Seite sprengen. */
.result-xml pre { max-height: 320px; margin: 10px 0 0; padding: 12px; overflow: auto; background: var(--surface-alt); border: 1px solid var(--border); font-size: 0.75rem; line-height: 1.5; }
.icon-button { background: none; border: 0; color: var(--text-dim); font-size: 1.4rem; line-height: 1; }
.icon-button:hover { color: var(--text); }

@media (prefers-reduced-motion: reduce) {
  .pulse-dot.ok {
    animation: none;
  }
}

@media (max-width: 640px) {
  main { padding: 20px; }
  .topbar { padding: 16px 20px; }
  /* Gleicher schmalerer Seitenrand wie Kopfzeile und Inhalt. */
  .sync-footer { padding: 10px 20px; }
  /* Bisher grid-template-columns:1fr auto fuer 4 Eintraege - das ergab je
     nach Zeile (FTP-Ziele/Dateien hatten Titel, Chip(s), Datum und Status in
     unterschiedlicher Reihenfolge) zwei ungleich aussehende, umgebrochene
     Zeilen. Einheitlich jetzt: Titel links (schrumpft/schneidet ab, wenn der
     Platz fehlt), Chips und ganz rechts das Datum - immer in einer Zeile,
     nie umgebrochen. */
  .ledger-row {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    gap: 8px;
  }
  .ledger-row > :first-child {
    flex: 1 1 auto;
    min-width: 0;
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .ledger-row > :not(:first-child) { flex: 0 0 auto; }
  .topbar-actions { gap: 10px; }
  .form-grid { grid-template-columns: 1fr; gap: 0; }
  .settings-footer { align-items: stretch; flex-direction: column; }
  .settings-submit { width: 100%; }
  .result-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .panel-heading, .target-row { align-items: flex-start; flex-direction: column; }
  .target-actions { flex-wrap: wrap; }
  .detail-grid { grid-template-columns: 1fr; }
  .settings-actions { flex-direction: column; }
  .settings-actions .btn-secondary { width: 100%; }

  /* FTP-Ziele/Synchronisierte Dateien/Aktivität: Titel + "Verwalten ›"-Link
     sitzen im h2 nur durch ein Leerzeichen getrennt inline hintereinander.
     Bricht die Zeile dort um, klebt der Link ohne jeden Abstand am linken
     Rand der naechsten Zeile - das sieht wie eine kaputte Zeile aus statt
     wie ein zweizeiliger Titel. flex-wrap mit gap loest das: passt beides
     in eine Zeile, aendert sich nichts; bricht es um, bekommt der Link
     einen ordentlichen Abstand zur Zeile darueber.
     .section-heading-row (Immobilien) ist NICHT betroffen: dort steht das
     h2 unter einem <div>, nicht direkt unter <section>. */
  section > h2 {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 12px;
  }
  section > h2 .section-link { margin-left: 0; }
}

/* Suchfeld in der Filterzeile - optisch gleich wie die Selects daneben */
.property-filters input[type="search"] {
  padding: 8px 10px;
  min-width: 240px;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font: inherit;
}
.property-filters input[type="search"]::placeholder { color: var(--text-dim); }
.property-filters input[type="search"]:focus-visible,
.property-filters select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Blätter-Leiste unter der Immobilienliste */
.pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}
.pager-pages { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }

/* Nur zwei Eintraege (Status-Chip, Datum) statt der ueblichen 3-4 -
   .ledger-row's erste Spalte ist 1fr (fuer einen dehnbaren Titel gedacht),
   das haette hier den Status-Chip zu einem breiten Balken gestreckt statt
   ihn klein zu lassen. Flex mit space-between passt fuer genau zwei feste
   Elemente besser. */
.upload-history-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* Kompakte Blaetterleiste fuer Ergebnisfelder INNERHALB der Seite (z.B. der
   FTP-Upload-Verlauf in den Einstellungen) - bewusst nicht .pager: die ist
   seit der sticky Fusszeile am unteren Bildschirmrand fixiert, was fuer
   einen Verlauf mitten in der Zielliste falsch waere. Nutzt trotzdem
   .pager-step/.pager-gap fuer denselben Knopf-/Text-Stil. */
.mini-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-top: 12px;
}
.pager-step, .pager-page, .detail-step {
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font: inherit;
  font-size: 0.82rem;
  padding: 7px 12px;
  cursor: pointer;
}
.pager-page { min-width: 38px; text-align: center; }
.pager-step:hover:not(:disabled), .pager-page:hover:not(.is-current), a.detail-step:hover { border-color: var(--accent-dim); color: var(--accent); }
.pager-step:disabled { opacity: 0.35; cursor: default; }
.pager-page.is-current { background: var(--accent); border-color: var(--accent); color: var(--bg); font-weight: 600; }
.pager-gap { color: var(--text-dim); padding: 0 4px; }

/* Die Blätter-Leiste bleibt am Schreibtisch sichtbar, waehrend die Zeilen
   darueber scrollen - sonst ist der "Weiter"-Knopf erst nach dem
   Runterscrollen zur letzten Zeile erreichbar. Sie liegt AUSSERHALB von
   .property-directory und ist von dessen overflow-x (s.u.) nicht betroffen,
   deshalb ab derselben Breite wie die Karten-Ansicht der Liste endet.
   Nur am Schreibtisch: ob sie auf dem Telefon ebenfalls kleben soll, ist eine
   eigene Frage. */
@media (min-width: 721px) {
  .pager {
    position: sticky;
    /* Nicht 0: laeuft gerade ein Sync oder Export, klebt dort schon die
       Statuszeile. Ihre Hoehe misst js/nav.js und legt sie in
       --sync-footer-height ab; ohne Lauf ist der Wert 0 und es bleibt beim
       unteren Rand. */
    bottom: var(--sync-footer-height, 0px);
    z-index: 5;
    background: var(--bg);
    /* main hat unten 32px Rand - der gilt aber nur im normalen Fluss. Sticky
       reisst die Leiste aus diesem Fluss heraus, sie klebt sonst ohne jeden
       Abstand an der Unterkante des Fensters. Eigenes Innenpolster gleicht
       das aus, waehrend sie klebt. */
    padding-bottom: 16px;
  }
}

/* .property-directory braucht ihr eigenes horizontales Scrollen NUR in
   diesem schmalen Bereich: darunter greift die Karten-Ansicht (kein
   Tabellenlayout mehr, s. max-width:720px unten), darueber ist main
   (max-width 1100px, 32px Rand) selbst breit genug fuer die 850px der Liste
   mit Exportzielen (.property-directory-scroll). */
@media (min-width: 721px) and (max-width: 919px) {
  .property-directory { overflow-x: auto; }
}

/* Kopfzeile der Immobilienliste bleibt am Schreibtisch sichtbar, waehrend
   die Zeilen darunter scrollen - bei hunderten Objekten sonst kein Bezug
   mehr zur Spaltenbeschriftung.
   Erst ab hier und nicht schon ab 721px: position:sticky wirkt relativ zum
   naechsten Vorfahren mit overflow ungleich visible - im Bereich direkt
   darueber (721-919px) ist das wegen des horizontalen Scrollens
   .property-directory selbst, und DESSEN Position ist keine, an der ein
   Kleben "oben am Bildschirmrand" ergibt (getestet: die Kopfzeile "klebt"
   dann irgendwo im Nichts, weit ausserhalb des sichtbaren Bereichs). */
@media (min-width: 920px) {
  .directory-head {
    position: sticky;
    /* 2px WENIGER als die gemessene Kopfzeilenhoehe: absichtlich ein
       hauchduenner Ueberlapp. Ohne ihn blieb beim Scrollen ein 1-2px Spalt
       zwischen Kopfzeile und dieser Leiste, durch den Tabellenzeilen
       durchschimmerten (Rundungsfehler zwischen zwei unabhaengig sticky
       positionierten Elementen). Der Ueberlapp verschwindet einfach hinter
       der Kopfzeile - die hat den hoeheren z-index und denselben
       Hintergrund. */
    top: calc(var(--topbar-height, 64px) - 2px);
    z-index: 5;
    background: var(--bg);
  }
}

/* ---------- Kopfzeile auf schmalen Geraeten ----------

   Eigene Grenze bei 900px und nicht bei den 720px der Listen weiter unten:
   die beiden messen Verschiedenes. Unten geht es darum, ab wann eine Tabelle
   zur Karte wird - hier darum, ab wann Marke, Mandant, E-Mail, Linkleiste,
   Ansichtsknopf und Abmelden nicht mehr nebeneinander passen.

   900px, weil ein iPhone im QUERFORMAT 844 bis 932 Punkte breit ist. Mit der
   alten 720er-Grenze fiel genau dieser Fall durch: kein Hamburger, aber auch
   kein Platz - die Kopfzeile quoll ueber. Dass es dort eng wird, war schon
   bekannt (s. den Kommentar zur Fusszeile in js/nav.js: ab ca. 1050px reichte
   der Platz nicht mehr fuer alles zusammen). */
@media (max-width: 900px) {

  /* Die E-Mail faellt als Erstes weg. Sie ist die laengste Angabe in der
     Zeile, beliebig lang (eine Adresse kann 40 Zeichen haben), und sie sagt
     dem Angemeldeten nichts, was er nicht weiss. Erreichbar bleibt das Konto
     ueber "Einstellungen" im Menue. */
  .topbar .user-email { display: none; }

  /* Der Mandantenname bleibt - er ist bei mehreren Mandanten die einzige
     Angabe, an der man sieht, WESSEN Daten man gerade vor sich hat. Statt ihn
     zu verstecken, wird er gekuerzt: so kann er die Zeile nicht mehr
     sprengen, bleibt aber lesbar und steht vollstaendig im title. */
  .topbar .tenant-name {
    max-width: 12ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* ---- Kopfzeile: Marke und Hamburger in einer Zeile ----
     Kein position: relative mehr - das wuerde das sticky von oben
     ueberschreiben. Als positioniertes Element ist sticky ohnehin schon der
     Bezug fuer das absolut haengende Menue darunter. */
  .topbar { gap: 12px; padding: 12px 16px; }
  /* Die Marke nimmt den Rest der Zeile und darf innen umbrechen, wenn der
     Mandantenname lang ist - min-width: 0 erlaubt ihr, kleiner als ihr Inhalt
     zu werden, sonst schiebt sie den Knopf aus dem Bild. */
  .topbar .brand { flex: 1 1 auto; min-width: 0; flex-wrap: wrap; }
  .nav-toggle { display: block; }
  /* Die Bildmarke weglassen: neben Mandantenname und Hamburger ist sie das
     Element, das am wenigsten sagt - der Schriftzug steht ja daneben. */
  .brand-mark { display: none; }

  /* Das Menü legt sich ÜBER den Seiteninhalt statt ihn nach unten zu
     schieben: beim Auf- und Zuklappen soll die Seite nicht springen.
     Geschlossen ist es display:none und damit auch aus der Tabreihenfolge -
     ein Menü, in das man hineintabbt, ohne es zu sehen, ist eine Falle. */
  .topbar-actions {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 20;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 4px 16px 14px;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    box-shadow: var(--menu-shadow);
  }

  .topbar-actions.is-open { display: flex; }

  /* Eine Zeile pro Ziel, über die ganze Breite antippbar - 44px hoch ist das
     Maß, unter dem ein Daumen daneben trifft. */
  .nav-link {
    display: block;
    padding: 13px 0;
    border-bottom: 1px solid var(--border);
  }

  .nav-link:last-of-type { border-bottom: none; }

  /* Im aufgeklappten Menue steht alles untereinander in voller Breite - ein
     34px-Quadrat mittendrin waere weder zu treffen noch als Zeile zu lesen.
     Deshalb hier mit Beschriftung aus dem title-Attribut und 44px hoch. */
  .topbar-actions .theme-btn {
    width: 100%;
    height: auto;
    justify-content: flex-start;
    gap: 10px;
    padding: 13px 0;
    border-bottom: 1px solid var(--border);
    font-family: inherit;
    font-size: 1rem;
    color: var(--text);
  }

  .topbar-actions .theme-btn::after { content: attr(title); }

  .topbar-actions .logout-btn { width: 100%; margin-top: 12px; padding: 11px; }
}

@media (max-width: 720px) {
  .property-filters input[type="search"] { min-width: 0; width: 100%; }
  .pager { justify-content: center; }
}

/* Fehlgeschlagene Importe in der Dateiliste sichtbar machen */
.ledger-row.is-failed { border-left: 2px solid var(--danger); padding-left: 8px; }
.ledger-error { display: block; margin-top: 3px; color: var(--danger); font-size: 0.75rem; white-space: normal; }
.format-tag.tag-failed { background: var(--danger); color: var(--bg); }

/* Zwischenüberschrift innerhalb eines Formulars */
.subform-heading { margin: 0 0 14px; font-family: serif; font-size: 1.05rem; font-weight: 500; }
#notify-form { margin-bottom: 34px; padding-bottom: 26px; border-bottom: 1px solid var(--border); }
#notify-form .toggle-field { align-items: flex-start; margin-bottom: 10px; }
#notify-form .toggle-field span { line-height: 1.45; }

/* Passwort vergessen / neu setzen */
.reset-link { display: block; margin-top: 18px; color: var(--accent); font-size: 0.82rem; }
.reset-intro { margin: 0 0 20px; color: var(--text-dim); font-size: 0.85rem; line-height: 1.5; }
.reset-back { display: inline-block; margin-top: 18px; font-size: 0.82rem; }
#forgot-form { margin-top: 18px; padding-top: 18px; border-top: 1px solid var(--border); }

/* Benutzer eines Mandanten */
.user-row { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 18px; align-items: center; padding: 14px 0; border-bottom: 1px solid var(--border); }
.user-row:last-child { border-bottom: none; }
.user-main { min-width: 0; }
.user-main strong { display: block; overflow-wrap: anywhere; }
.user-meta { display: block; margin-top: 2px; color: var(--text-dim); font-size: 0.8rem; }
.user-actions { display: flex; gap: 12px; white-space: nowrap; }
/* Das Formular steht in der Zeile des Benutzers, ueber die ganze Breite -
   sonst waere beim Tippen nicht sichtbar, um wessen Passwort es geht.
   .user-row ist ein Grid, deshalb reicht grid-column. */
.user-reset { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 12px; padding: 12px; background: var(--surface-alt); border: 1px solid var(--border); border-radius: var(--radius); }
/* Beschriftung auf eigener Zeile: Adresse, Eingabefeld und zwei Knoepfe
   passen sonst nicht nebeneinander und brechen unschoen um. */
.user-reset label { flex: 1 1 100%; color: var(--text-dim); font-size: 0.78rem; }
.user-reset input { flex: 1 1 220px; min-width: 0; padding: 8px 10px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font: inherit; font-size: 0.86rem; }
.user-reset input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.role-tag { border: 1px solid var(--border); border-radius: 3px; padding: 3px 8px; color: var(--text-dim); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; }
.role-tag.is-admin { border-color: var(--accent-dim); color: var(--accent); }

@media (max-width: 720px) {
  .user-row { grid-template-columns: 1fr; gap: 8px; }
  .user-actions { flex-wrap: wrap; }
}

/* Wertuebersetzungen je Exportformat */
.inline-field { display: flex; align-items: center; gap: 9px; color: var(--text-dim); font-size: 0.8rem; white-space: nowrap; }
.inline-field select { padding: 8px 30px 8px 10px; background-color: var(--surface-alt); background-position: right 10px center; border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font: inherit; }

/* Keine Trennlinie zwischen den Feldern mehr: sie standen frueher
   untereinander, seit den Reitern (.field-tabs) ist immer nur eines
   sichtbar. Der +-Kombinator fragt aber nach dem vorherigen GESCHWISTER,
   nicht nach dem vorherigen sichtbaren - die Linie erschien deshalb bei
   jedem Feld ausser dem ersten im Markup, direkt unter der Reiterleiste. */
.translation-summary { margin: 0 0 16px; color: var(--text-dim); font-size: 0.82rem; }

/* Eine Zeile pro Wertepaar: Quellwert, Pfeil, Zielfeld, Loeschen. Der
   Quellwert darf umbrechen statt abgeschnitten zu werden - Gemeindenamen
   wie "Appiano sulla strada del vino" sind lang und muessen lesbar sein. */
.translation-row { display: grid; grid-template-columns: minmax(0, 1fr) 18px minmax(0, 1fr) auto; gap: 12px; align-items: center; padding: 9px 0; border-bottom: 1px solid var(--border); }
.translation-from { overflow-wrap: anywhere; font-size: 0.86rem; }
.translation-arrow { color: var(--text-dim); text-align: center; }
.translation-to { width: 100%; padding: 8px 10px; background: var(--surface-alt); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font: inherit; font-size: 0.86rem; }
.translation-to:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.translation-to::placeholder { color: var(--warning); }

/* Offene Eintraege sind die Arbeitsliste - sie sollen ins Auge fallen,
   ohne wie ein Fehler auszusehen. */
.translation-row.is-open .translation-to { border-color: var(--warning); }

.translation-add { display: grid; grid-template-columns: minmax(0, 1fr) 18px minmax(0, 1fr) auto; gap: 12px; align-items: center; margin-top: 18px; }
.translation-add input { padding: 8px 10px; background: var(--surface-alt); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font: inherit; font-size: 0.86rem; }
.translation-add input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

@media (max-width: 720px) {
  /* Pfeilspalte weglassen und untereinander stapeln - vier Spalten sind
     auf einem Telefon nicht mehr lesbar. */
  .translation-row, .translation-add { grid-template-columns: 1fr; gap: 6px; }
  .translation-arrow { display: none; }
}

/* Aktive Seite in der gemeinsamen Kopfzeile markieren */
.nav-link.is-current { color: var(--accent); font-weight: 600; }

/* "Alle anzeigen" neben einer Abschnittsüberschrift.
   Bewusst vollständig gesetzt statt geerbt: der Link sitzt in einem h2,
   das je nach Abschnitt Versalien und Sperrsatz vorgibt - ohne die
   Rücksetzer sähe derselbe Link an zwei Stellen unterschiedlich aus. */
.section-link {
  margin-left: 12px;
  font-family: system-ui, sans-serif;
  font-size: 0.78rem;
  font-weight: 500;
  text-transform: none;
  letter-spacing: normal;
  color: var(--accent);
  text-decoration: none;
}
.section-link:hover { text-decoration: underline; }

/* Aktivitätsprotokoll auf eigener Seite */
.activity-row {
  display: grid;
  grid-template-columns: 165px 1fr;
  gap: 16px;
  align-items: baseline;
  padding: 12px 4px;
  border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
}
.activity-row.is-failed { border-left: 2px solid var(--danger); padding-left: 8px; }
.activity-row.is-failed strong { color: var(--danger); }
.activity-when { color: var(--text-dim); font-size: 0.78rem; white-space: nowrap; }
.activity-what { display: flex; flex-direction: column; gap: 3px; }
.activity-detail { color: var(--text-dim); font-size: 0.8rem; word-break: break-word; }
.activity-links { color: var(--text-dim); font-size: 0.8rem; }
.activity-links a { color: var(--accent); font-weight: 600; text-decoration: none; }
.activity-links a:hover { text-decoration: underline; }

@media (max-width: 720px) {
  .activity-row { grid-template-columns: 1fr; gap: 4px; }
}

/* ---------------------------------------------------------------------------
 * Mobil
 *
 * Die Seite war für den Schreibtisch gebaut: Kopfzeile, Reiterreihe und die
 * beiden Immobilienlisten setzen alle auf eine Zeilenbreite, die es auf einem
 * Telefon nicht gibt. Alles, was sich unterwegs ändert, steht bewusst hier
 * am Ende und an EINER Stelle - so sieht man beim Ändern einer Grundregel
 * sofort, ob es mobil eine Ausnahme dazu gibt.
 *
 * 720px trennt Telefon und hochkant gehaltenes Tablet von allem Größeren,
 * 480px fängt die schmalen Telefone ab. Die älteren Blöcke bei 640px und
 * 720px weiter oben gelten weiter; wo sich etwas widerspricht, gewinnt
 * dieser Abschnitt, weil er später kommt.
 * ------------------------------------------------------------------------- */

@media (max-width: 720px) {


  /* ---- Reiterreihe der Einstellungen ----
     Seitlich schiebbar ist sie schon in der Grundregel; hier wird nur der
     Abstand enger, damit mehr Reiter gleichzeitig zu sehen sind. */
  .settings-tabs { gap: 18px; }

  /* ---- Flächen und Überschriften ---- */
  main { padding: 20px 16px; }
  .settings-panel { padding: 18px; }
  .settings-header h2 { font-size: 1.7rem; }
  .detail-hero { flex-wrap: wrap; gap: 12px; }
  .detail-hero h2 { font-size: 1.6rem; }
  .stat-card .value { font-size: 1.6rem; }
  /* Bei mehrzeiligen Beschriftungen gehört das Kästchen an den Anfang der
     ersten Zeile, nicht in die Mitte des Absatzes. */
  .toggle-field { align-items: flex-start; }
  .toggle-field input { margin-top: 2px; }

  /* Filterzeile und Kurzuebersicht stapeln - beide standen bisher erst ab
     640px untereinander, was zwischen 641 und 720px zu sechs sehr engen
     Spalten bzw. einer gequetschten Filterzeile fuehrte. */
  /* Zugeklappt starten: gestapelt nimmt die Filterzeile fast eine Bildhöhe
     ein, und man scrollt an ihr vorbei, bevor die erste Zeile zu sehen ist.
     Welche Filter greifen, steht ohnehin in der Zeile über der Liste. */
  .filter-toggle { display: inline-flex; }
  .property-filters { display: none; align-items: stretch; flex-direction: column; }
  .property-filters.is-open { display: flex; }
  .property-filters label { justify-content: space-between; }
  .detail-summary { grid-template-columns: repeat(2, 1fr); }

  /* ---- Immobilienlisten als Karten statt als Tabelle ----
     Beide Listen (Dashboard und Immobilienseite) tragen dieselben
     Zellenklassen, deshalb genügt ein Regelsatz.

     Die Spaltenbreiten stehen in der Immobilienliste als Inline-Style in
     jeder Zeile - properties.js setzt sie je nach Anzahl der Exportziele.
     Inline-Styles ließen sich hier nur mit !important überschreiben;
     "display: flex" macht sie stattdessen einfach wirkungslos. */
  .property-table, .property-directory { overflow-x: visible; }
  .property-directory-scroll { min-width: 0; }
  .property-table-head, .directory-head { display: none; }
  .property-table-row, .directory-row {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 10px;
    min-width: 0;
    padding: 14px 2px;
  }
  .directory-object, .directory-location { flex: 1 1 100%; }
  /* Die Exporthaken bekommen eine eigene Zeile - zwischen den Zahlen stehend
     sieht man nicht, wo die Angaben aufhören und die Schalter anfangen. */
  .directory-break { display: block; flex: 1 1 100%; height: 2px; }
  .directory-fact { color: var(--text-dim); font-size: 0.8rem; }
  /* Punkt zwischen den Zahlen, die am Schreibtisch eigene Spalten sind. */
  .directory-fact + .directory-fact::before { content: "· "; }
  /* Ohne Kopfzeile wären die Haken namenlos - der Name kommt aus data-label,
     das properties.js mitgibt. */
  .target-toggle-cell {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--text-dim);
    font-size: 0.78rem;
    text-align: left;
    /* Hier ist die Zeile ein flex-Container und kein Grid - das Strecken vom
       Schreibtisch wuerde die Zelle ueber die ganze Zeilenhoehe ziehen. Der
       Zielname steht hier ohnehin daneben und gehoert zum Label, die Flaeche
       ist also schon gross genug. */
    align-self: auto;
  }
  .target-toggle-cell::after { content: attr(data-label); }

}

/* ---------------------------------------------------------------------------
 * Nur mit dem Finger
 *
 * Diese Regeln machen Dinge GRÖSSER. Sie hängen deshalb nicht an der Breite,
 * sondern an pointer: coarse - also daran, dass wirklich ein Finger tippt.
 * Ein halbiertes Laptop-Fenster ist schmal, hat aber eine Maus: dort wären
 * 16px-Felder und fingerbreite Knöpfe einfach zu grosse Schrift.
 *
 * Der Grund für die 16px steht nur hier: unter 16px zoomt iOS Safari beim
 * Antippen eines Feldes in die Seite hinein und kommt nicht mehr heraus. Das
 * ist ein Touch-Problem und hat mit der Fensterbreite nichts zu tun.
 * ------------------------------------------------------------------------- */

@media (max-width: 720px) and (pointer: coarse) {
  .field input, .field select,
  .property-filters select, .property-filters input[type="search"],
  .translation-to, .translation-add input, .user-reset input { font-size: 16px; }

  .pager-step, .pager-page, .detail-step { padding: 10px 14px; }
  .target-actions .text-button, .user-actions .text-button { padding: 8px 0; }
  .target-toggle-cell input { width: 20px; height: 20px; }
}

@media (max-width: 480px) {
  /* Ganz schmal faellt auch der Mandantenname weg - dort zaehlt jeder
     Punkt, und die Kuerzung von oben brauchte selbst noch Platz. */
  .topbar .tenant-name { display: none; }
  main { padding: 18px 14px; }
  .settings-panel { padding: 16px 14px; }
  .settings-header h2 { font-size: 1.5rem; }
  .detail-hero h2 { font-size: 1.4rem; }
  .result-facts { grid-template-columns: 1fr; }
}

/* ---------- Startseite ----------

   Die einzige Seite ohne Anmeldung, die mehr ist als ein Formular. Sie teilt
   sich die Farbtokens und die Sprachleiste mit dem Rest; alles Weitere steht
   hier und nirgends sonst, damit eine Änderung an der Anwendung die
   Startseite nicht versehentlich mitnimmt und umgekehrt.

   Bewusst ohne eigene Schriften, ohne Bilder, ohne Animation: die Seite soll
   auch dann vollständig sein, wenn nichts nachgeladen wird. */

body.landing {
  /* Die Anmeldeseite zentriert eine Karte auf voller Höhe - hier wird
     gescrollt wie auf jeder normalen Seite. */
  display: block;
}

.landing-top {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 18px 32px;
  border-bottom: 1px solid var(--border);
}

.landing-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  color: inherit;
  text-decoration: none;
  /* Schiebt Sprachwahl und Anmelden ans andere Ende. */
  margin-right: auto;
}

.landing-brand strong {
  font-size: 1.1rem;
  letter-spacing: 0.02em;
}

.landing-login {
  /* Der Knopf trägt sonst die volle Breite eines Formulars. */
  width: auto;
  padding: 9px 18px;
  text-decoration: none;
  white-space: nowrap;
}

.landing-main {
  max-width: 880px;
  margin: 0 auto;
  padding: 0 32px 72px;
}

/* Text links, Ablaufbild rechts. minmax(0, ...) laesst lange Woerter in
   der Ueberschrift umbrechen, statt das Raster zu sprengen. */
.landing-hero {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr);
  gap: 48px;
  align-items: center;
  padding: 72px 0 8px;
}

.landing-eyebrow {
  display: inline-block;
  margin-bottom: 14px;
  font-family: monospace;
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
}

.landing-cta {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 28px;
}

.landing-cta .btn-primary { margin-top: 0; }

.landing-more {
  display: inline-flex;
  align-items: center;
  padding: 9px 18px;
  font-size: 0.95rem;
  text-decoration: none;
  white-space: nowrap;
}

.landing-more:hover { color: var(--accent-hover); border-color: var(--accent-hover); }

/* Ablaufbild: Quelle -> Immoport -> Portale, senkrecht gestapelt. Reine
   Kaesten und Linien, kein Bild - es kommt in beiden Ansichten aus denselben
   Farbwerten wie der Rest. */
.landing-flow {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  margin: 0;
}

.flow-node,
.flow-targets li {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 10px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-align: center;
}

.flow-hub {
  flex-direction: row;
  justify-content: center;
  gap: 10px;
  border-color: var(--accent);
}

.flow-hub strong { font-size: 1.05rem; letter-spacing: 0.02em; }

.flow-link {
  width: 1px;
  height: 22px;
  margin: 0 auto;
  background: var(--accent-dim);
}

.flow-caption {
  margin: 2px 0 8px;
  font-family: monospace;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: center;
  color: var(--text-dim);
}

.flow-targets {
  display: grid;
  gap: 8px;
  list-style: none;
  margin: 0;
  padding: 0;
}

.flow-targets li {
  flex-direction: row;
  justify-content: space-between;
}

.flow-title { font-weight: 600; font-size: 0.9rem; }

.flow-meta {
  font-family: monospace;
  font-size: 0.75rem;
  color: var(--text-dim);
}

.landing-hero h1 {
  font-size: 2.4rem;
  line-height: 1.15;
  margin-bottom: 20px;
}

.landing-lead {
  font-size: 1.15rem;
  line-height: 1.6;
  color: var(--text);
  max-width: 44ch;
}

.landing-problem {
  margin-top: 18px;
  color: var(--text-dim);
  line-height: 1.6;
  max-width: 52ch;
}

.landing-section {
  padding-top: 56px;
}

.landing-section h2 {
  font-size: 1.35rem;
  margin-bottom: 22px;
}

.landing-section h3 {
  font-size: 0.95rem;
  margin-bottom: 6px;
}

.landing-section p {
  color: var(--text-dim);
  line-height: 1.6;
}

/* Drei Schritte nebeneinander. Die Nummern kommen aus der Liste selbst -
   ein <ol> sagt Vorlesegeräten, dass es eine Reihenfolge gibt, was hier der
   ganze Punkt ist. */
.landing-steps {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
  list-style: none;
  counter-reset: schritt;
}

.landing-steps li {
  counter-increment: schritt;
  padding-top: 34px;
  position: relative;
}

.landing-steps li::before {
  content: counter(schritt);
  position: absolute;
  top: 0;
  left: 0;
  font-family: monospace;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  color: var(--accent);
  border: 1px solid var(--border);
  border-radius: 50%;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.landing-features {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 22px 32px;
}

.landing-features article {
  padding: 18px 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.feature-icon {
  display: block;
  margin-bottom: 10px;
  color: var(--accent);
}

.landing-selfhost p,
.landing-contact p {
  max-width: 52ch;
}

.landing-mail {
  display: inline-block;
  margin-top: 12px;
  font-family: monospace;
  font-size: 0.95rem;
  color: var(--accent);
}

.landing-foot {
  display: flex;
  justify-content: center;
  gap: 24px;
  padding: 22px 32px 30px;
  border-top: 1px solid var(--border);
  font-size: 0.82rem;
}

.landing-foot a {
  color: var(--text-dim);
  text-decoration: none;
}

.landing-foot a:hover { color: var(--accent); }

.landing-foot-brand { color: var(--text-dim); }

/* Hell/dunkel-Knopf ohne Laufzeittexte: beide Sinnbilder stehen im Markup,
   gezeigt wird das ZIEL - wie beim Knopf in der Anwendung (nav.js). Dunkel
   ist der Zustand ohne Attribut, also zeigt er dort die Sonne. */
.landing-theme .icon-moon { display: none; }
:root[data-theme="light"] .landing-theme .icon-sun { display: none; }
:root[data-theme="light"] .landing-theme .icon-moon { display: block; }

/* Rechtstexte: schmaler als die Startseite, weil es Fließtext ist. */
.landing-legal {
  max-width: 640px;
  padding-top: 56px;
}

.landing-legal h1 {
  font-size: 1.8rem;
  margin-bottom: 20px;
}

/* Sichtbar als Lücke gekennzeichnet - eine leere Seite sähe aus wie ein
   Fehler, und niemand darf sie für den fertigen Text halten. */
.landing-todo {
  padding: 14px 16px;
  background: var(--surface-alt);
  border: 1px dashed var(--warning);
  border-radius: var(--radius);
  color: var(--text-dim);
}

@media (max-width: 720px) {
  .landing-top { padding: 14px 18px; gap: 12px; flex-wrap: wrap; }
  /* Die Sprachleiste rutscht in die zweite Zeile, statt Marke und Knopf
     zusammenzuquetschen. */
  .landing-top .lang-switch { order: 3; width: 100%; }
  .landing-main { padding: 0 18px 56px; }
  .landing-hero { padding-top: 44px; grid-template-columns: 1fr; gap: 36px; }
  .landing-hero h1 { font-size: 1.9rem; }
  .landing-steps,
  .landing-features { grid-template-columns: 1fr; }
}

/* ---------- Karte auf der Detailseite ----------

   Feste Hoehe statt Seitenverhaeltnis: die Einbettung von OpenStreetMap
   bringt keine eigene Groesse mit, ein iframe ohne Hoehe waere 150px hoch
   (Vorgabe des Browsers) und damit unbrauchbar. */
.map-frame {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  line-height: 0;
}

.map-frame iframe {
  width: 100%;
  height: 320px;
  border: 0;
}

.map-note {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  align-items: baseline;
  margin-top: 8px;
  font-size: 0.82rem;
}

.map-note a { color: var(--accent); }

.map-coords {
  font-family: monospace;
  font-size: 0.75rem;
  color: var(--text-dim);
}

@media (max-width: 720px) {
  .map-frame iframe { height: 240px; }
}

/* ---------- Reiter innerhalb der Uebersetzungen ----------

   Eine Ebene unter .settings-tabs und bewusst leiser: zwei gleich aussehende
   Leisten uebereinander liessen offen, welche zu welcher Ebene gehoert. Statt
   der Unterstreichung wie oben bekommt der aktive Reiter hier eine Flaeche. */
.field-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 20px;
}

.field-tab {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 13px;
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-dim);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}

.field-tab:hover { color: var(--text); }

.field-tab.is-current {
  background: var(--surface-alt);
  border-color: var(--accent-dim);
  color: var(--accent);
}

/* Offene Eintraege hervorheben - sonst sieht man der Leiste nicht an, wo
   etwas zu tun ist, und genau dafuer ist sie da. */
.field-tab .tab-count.is-open {
  background: var(--warning);
  color: var(--bg);
}

/* Marke fuer ein abgeschaltetes Ziel. Gedaempft und ohne Signalfarbe: es ist
   kein Fehler, sondern eine Einstellung - aber es erklaert, warum dort nichts
   mehr passiert. */
.format-tag.tag-inactive {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-dim);
}
