/* App-Shell: Seitenleiste (Rail), Kopfleiste (Topbar) und der Inhaltsbereich, der
   beide umschliesst — plus die Login-Ansicht, die stattdessen zentriert auf demselben
   grauen Grund liegt. Ersetzt die frühere Anordnung aus drei gestapelten Kopfbalken
   (Logo-Zeile, Werkzeugleiste, Tab-Leiste) auf 960px festem Inhalt. Reiner Rahmen —
   die Tab-Inhalte selbst (die sechs Partials) sind unverändert.
   Reference: /tmp/…/scratchpad/mock/{shell.css,dashboard.html}, hier auf die
   bestehenden Tokens aus tokens.css umgemünzt statt neuer Hex-Werte. */

body {
  margin: 0;
  font-family: var(--font);
  background: var(--bg);
  color: var(--fg);
  line-height: 1.55;
}

.app { display: grid; grid-template-columns: var(--rail) 1fr; min-height: 100vh; }
/* Explizite Platzierung statt Auto-Flow: mit x-cloak/x-show sind Rail und .main
   solange unsichtbar, bis Alpine `authed` kennt — ohne feste Spalten würde die
   Login-Karte (der einzige übrige Grid-Kind) in die schmale Rail-Spalte rutschen. */
.rail { grid-column: 1; }
.main { grid-column: 2; min-width: 0; }

/* ── Rail ───────────────────────────────────────────────────────────────── */
.rail {
  background: var(--surface);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
}
.rail-brand { flex: none; padding: 1.15rem 1.25rem 1rem; border-bottom: 1px solid var(--border); }
.rail-brand img { height: 26px; display: block; }
.rail-brand .product {
  display: block; margin-top: .5rem; font-size: .95rem; font-weight: 600;
  color: var(--heading); letter-spacing: -.01em;
}
.rail nav {
  flex: 1 1 auto; overflow-y: auto;
  padding: .9rem .75rem; display: flex; flex-direction: column; gap: .15rem;
}
.rail-group {
  padding: .9rem .5rem .35rem; font-size: .68rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .08em; color: var(--muted);
}
.rail-group:first-child { padding-top: .2rem; }

.nav-item {
  display: flex; align-items: center; gap: .6rem; width: 100%;
  padding: .5rem .6rem; border-radius: var(--radius-sm);
  background: none; border: none; border-left: 3px solid transparent;
  color: var(--fg); font-family: inherit; font-size: .875rem; font-weight: 500;
  cursor: pointer;
}
.nav-item .icon { color: var(--muted); }
.nav-item:hover:not(.is-active) { background: var(--surface-2); }
.nav-item:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
/* Kleiner roter Text auf Weiss reisst AA (design.md §7) — --brand-active statt
   --brand, wie bei den Textlinks anderswo in der Konsole. */
.nav-item.is-active { background: var(--brand-soft); color: var(--brand-active); font-weight: 600; border-left-color: var(--brand); }
.nav-item.is-active .icon { color: var(--brand); }
.nav-count {
  margin-left: auto; min-width: 1.35rem; text-align: center;
  font-size: .7rem; font-weight: 600; line-height: 1;
  padding: .25rem .5rem; border-radius: 999px; background: var(--brand); color: var(--on-brand);
}

.rail-foot {
  flex: none; margin-top: auto; padding: .9rem 1.25rem; border-top: 1px solid var(--border);
  font-size: .75rem; color: var(--muted); display: flex; align-items: center; justify-content: space-between;
}

/* ── Topbar ─────────────────────────────────────────────────────────────── */
.topbar {
  position: sticky; top: 0; z-index: 5;
  display: flex; align-items: center; gap: 1.25rem;
  /* `min-height` und nicht bloss Polsterung: die Sprungleiste der Konfiguration
     klebt auf `top: var(--topbar-h)`, und dieses Token darf keine Schätzung
     sein — hier legt es die Höhe fest, statt sie hinterherzurechnen. */
  min-height: var(--topbar-h);
  padding: .85rem 1.75rem; background: var(--surface); border-bottom: 1px solid var(--border);
}
.topbar h1 { font-size: 1.2rem; letter-spacing: -.01em; }
.topbar .sub { font-size: .8rem; color: var(--muted); margin: .15rem 0 0; }
/* Kein zweites `margin-left:auto` neben dem der Verbindungspunkte: zwei
   Auto-Ränder teilen den freien Platz und reissen Status und Knopf auseinander —
   sie gehören als eine Gruppe an den rechten Rand.

   „Synchronisieren" ist die Primäraktion der ganzen Konsole — die eine Handlung, die
   von jedem Tab aus erreichbar sein soll. Sie ist damit der Ausnahmefall aus §5 und
   trägt die gefüllte Fläche. Sie steht hier und nicht als `class="primary"` im Markup,
   weil sie nicht *irgendein* Knopf ist, der zufällig primär gesetzt wurde, sondern der
   einzige Knopf der Kopfleiste: die Regel gehört zum Rahmen, so wie sein Fokusring und
   die Farbe seines Icons schon immer hier standen. */
.topbar button {
  display: inline-flex; align-items: center; gap: .5rem;
  flex: none;
  background: var(--brand); border-color: var(--brand); color: var(--on-brand);
}
.topbar button:hover:not(:disabled) { background: var(--brand-hover); border-color: var(--brand-hover); }
.topbar button:active:not(:disabled) { background: var(--brand-active); border-color: var(--brand-active); }
.topbar button:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.topbar button .icon { color: var(--on-brand); }

/* Verbindungspunkte — dieselbe Bedeutung wie die früheren .connector-chips im
   Kopf, hier kompakter neben dem Sync-Knopf statt in eigener Zeile. */
.conn { display: flex; align-items: center; gap: .5rem; margin-left: auto; }
.chip {
  font-size: .72rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
  border-radius: 999px; padding: .25rem .65rem; border: 1px solid var(--border-2);
  color: var(--muted); background: var(--surface-2);
}
/* Der Zustand steht als Zeichen im Text (siehe index.html), nicht nur in der Farbe. */
.chip.ok { color: var(--success); border-color: rgba(30, 142, 62, .35); background: rgba(30, 142, 62, .08); }
.chip.off { color: var(--muted); }

/* ── Sprungleiste innerhalb einer Seite ─────────────────────────────────── */
/* Gehört zum Rahmen und nicht zum Karteninhalt: sie ist Navigation wie die Rail,
   sie klebt wie die Kopfleiste, und sie rechnet mit deren Höhe. Gebaut für die
   Konfiguration (rund 4200px am Stück, fünf Karten in vier Themen), aber an
   nichts davon gebunden — jede Seite, die zu lang zum Scrollen wird, kann sie
   bekommen.

   Randlos über die Polsterung des Inhalts hinweg (negative Ränder): eine
   klebende Leiste, die nur so breit ist wie der Text, lässt den durchlaufenden
   Inhalt links und rechts an ihr vorbeischeinen. `z-index` unter dem der
   Kopfleiste, damit sie beim Scrollen darunter und nicht davor liegt. */
.section-nav {
  position: sticky; top: var(--topbar-h); z-index: 4;
  display: flex; flex-wrap: wrap; align-items: center; gap: .25rem;
  margin: -1.5rem -1.75rem 1.25rem;
  padding: .5rem 1.75rem;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
/* Dieselbe Sprache wie `.nav-item` der Rail — es ist dieselbe Sorte Handlung
   (irgendwohin gehen), nur eine Ebene tiefer. Also Satzschreibung und keine
   Fläche: die VERSALIEN sind seit Etappe 4 den Beschriftungen vorbehalten. */
.section-nav button {
  padding: .35rem .7rem; border: none; background: none;
  border-radius: var(--radius-sm);
  color: var(--fg); font-size: .85rem; font-weight: 500;
}
.section-nav button:hover:not(.is-active) { background: var(--surface-2); }
.section-nav button.is-active { background: var(--brand-soft); color: var(--brand-active); font-weight: 600; }

/* Der Sprung darf nicht unter den beiden klebenden Leisten landen. Dieselbe Zahl
   entscheidet in `initConfigSections` (parts/config.js), welcher Abschnitt als
   „oben" gilt — sie wird dort aus `scroll-margin-top` gelesen, nicht kopiert. */
main.content section[id] { scroll-margin-top: calc(var(--topbar-h) + 4rem); }

/* ── Fehlerstreifen ─────────────────────────────────────────────────────── */
/* Rahmenbestandteil wie Topbar und Rail, deshalb hier und nicht in components.css:
   er hat einen festen Platz im Gerüst (unter der Kopfleiste, über dem Inhalt) und
   gehört zu keinem Karteninhalt.
   Fläche/Text/Kantenstrich kommen aus dem Fehlerpaar der Tokens — dieselbe Sprache
   wie `.reason.error` und `button.funnel-leak` (design.md §2/§5). Keine Hexwerte.
   Im Fluss, nicht `position: fixed`: er SCHIEBT den Inhalt nach unten, statt die
   erste Zeile zu verdecken — nach einem Fehler will man genau diese Zeile ansehen. */
.failure {
  display: flex; align-items: center; flex-wrap: wrap; gap: .5rem .75rem;
  padding: .7rem 1.75rem;
  background: var(--error-soft);
  color: var(--error);
  border-bottom: 1px solid var(--border);
  border-left: 3px solid var(--error);
}
/* Der Text nimmt die Zeile, die Knöpfe bleiben rechts — bricht er um, wandern sie
   unter ihn, statt aus dem Streifen zu laufen. */
.failure p { flex: 1 1 20rem; min-width: 0; margin: 0; font-size: .9rem; font-weight: 500; }
.failure button { flex: none; }

/* ── Inhalt ─────────────────────────────────────────────────────────────── */
main.content { padding: 1.5rem 1.75rem 3rem; max-width: 1760px; }

/* ── Login ──────────────────────────────────────────────────────────────── */
/* `position: fixed` statt eines zweiten Grid-Kinds von .app: die Rail-Spalten
   sind fest (var(--rail) 1fr), und ein sichtbares Login-Element würde ohne
   eigene Platzierung in genau diese schmale erste Spalte rutschen. */
.login {
  position: fixed; inset: 0; display: flex; align-items: center; justify-content: center;
  background: var(--bg); z-index: 10; padding: 1.5rem;
}
.login-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 2rem 2.25rem; width: 100%; max-width: 22rem; text-align: center;
}
.login-card img { height: 40px; margin: 0 auto 1rem; display: block; }
.login-card h1 { margin-bottom: 1.5rem; }
.login-card form { display: flex; flex-direction: column; gap: .75rem; }
/* „Anmelden" ist die einzige Handlung der Seite — also die Primäraktion, gefüllt.
   Wie beim Sync-Knopf der Kopfleiste steht das hier statt als Klasse im Markup:
   index.html ist der Rahmen, und dieser Knopf hat keine Geschwister, zwischen denen
   eine Klasse etwas unterscheiden könnte. */
.login-card button {
  background: var(--brand); border-color: var(--brand); color: var(--on-brand);
}
.login-card button:hover:not(:disabled) { background: var(--brand-hover); border-color: var(--brand-hover); }
.login-card button:focus-visible, .login-card input:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* ── Übersicht (Etappe 2) ───────────────────────────────────────────────── */
/* Zweispaltiges Raster ab 1400px: links breit „Wartet auf dich" + „Letzte
   Sync-Läufe", rechts schmal „Verbindungen" + „Geplante Läufe" — benannte
   Grid-Areas statt Dokumentreihenfolge, weil die DOM-Reihenfolge im Partial der
   einspaltigen (schmalen) Ansicht folgt und die Zuordnung zur Spalte unabhängig
   davon sein soll. Die KPI-Zeile (.kpis) liegt bewusst ausserhalb dieses Grids —
   sie ist immer volle Breite, gehört zu keiner der beiden Spalten. Gehört zum
   Rahmen (Seitenraster), nicht zum Karteninhalt, deshalb hier und nicht in charts.css.
   Unter der Grenze keine Areas definiert → ein einspaltiger Fluss in Dokumentreihenfolge.

   Die Grenze ist 1400px und nicht 1100px wie beim Rail-Kollaps: bei 1280px bleiben der
   breiten Spalte rund 620px, und darin schneidet die achtspaltige Tabelle der Sync-Läufe
   ihre Fehlerspalte ab (sie scrollt zwar, aber ein abgeschnittener Fehlertext liest sich
   wie ein Defekt). Erst ab 1400px ist die Spalte breit genug für die Tabelle. */
.overview-grid { display: grid; grid-template-columns: 1fr; gap: 1rem; align-items: start; }
/* Grid-Kinder haben ohne das eine `min-width: auto` — die Spur wächst dann auf den
   Mindestinhalt der breitesten Tabelle (die achtspaltigen Sync-Läufe mit Fehlertext)
   statt auf die `fr`-Breite zu schrumpfen, und die Seite bekäme waagrechten Scroll,
   obwohl `.table-scroll` genau das innerhalb der Karte verhindern soll. */
.overview-grid > .card { min-width: 0; }
@media (min-width: 1400px) {
  .overview-grid {
    grid-template-columns: 2fr 1fr;
    grid-template-areas:
      "waiting connections"
      "runs    scheduled";
  }
  .ov-waiting { grid-area: waiting; }
  .ov-connections { grid-area: connections; }
  .ov-runs { grid-area: runs; }
  .ov-scheduled { grid-area: scheduled; }
}
/* Verbindungen-Karte: drei Zeilen Name+Zustand, darunter die Knöpfe — bewusst kein
   <table> (siehe overview.html): eine <td> richtet sich nach ihrem Inhalt, nicht nach
   der Spaltenbreite, und in der schmalen (1fr-)Spalte sprengten drei Tabellenspalten
   plus zwei Knöpfe die Zelle und erzwangen den waagrechten Scroll aus tables.css. Als
   Liste darf `.row-actions` (components.css) ganz normal umbrechen. */
.conn-list { list-style: none; margin: 0; padding: 0; }
.conn-list li { padding: .6rem 0; border-bottom: 1px solid var(--border); }
.conn-list li:last-child { border-bottom: none; padding-bottom: 0; }
.conn-list li:first-child { padding-top: 0; }
.conn-row { display: flex; align-items: center; justify-content: space-between; gap: .5rem; margin-bottom: .4rem; }

/* ── Reaktiv ────────────────────────────────────────────────────────────── */
/* Nur Icons in der Rail: die Spaltenbreite ist ein Token, also reicht eine
   Media Query, die ihn umdefiniert — keine JS-Logik dafür nötig. */
@media (max-width: 1100px) {
  :root { --rail: 64px; }
  .rail-brand .product, .rail-group, .nav-label, .rail-foot span { display: none; }
  /* Das hochformatige Logo (siehe `<picture>` in index.html) füllt die schmale Spalte,
     statt bei fester Höhe seitlich herauszulaufen: Breite führt, Höhe folgt. Die
     Polsterung muss dafür mit — 2×1.25rem liessen von 64px nur 24px Inhalt übrig. */
  .rail-brand { padding: .9rem .5rem; }
  .rail-brand img { width: 100%; height: auto; }
  .nav-item { position: relative; justify-content: center; }
  /* Klein und in die Ecke gehängt statt zentriert überlagert — bei nur 16px
     Icon in einem 39px-Knopf würde die reguläre Badge-Grösse das Icon zudecken
     statt es zu ergänzen. */
  .nav-count {
    position: absolute; top: -.3rem; right: -.3rem; margin-left: 0;
    min-width: 0; width: .85rem; height: .85rem; padding: 0; font-size: .6rem;
  }
  .rail-foot { justify-content: center; }
}

/* Unter 820px wird die Rail zur waagrecht scrollbaren Zeile über dem Inhalt —
   .app verliert seine zweite Spalte, die Rail ihre feste Höhe. */
@media (max-width: 820px) {
  .app { grid-template-columns: 1fr; }
  .rail, .main { grid-column: 1; }
  .rail {
    position: static; height: auto; flex-direction: row;
    border-right: none; border-bottom: 1px solid var(--border);
    /* Dasselbe `min-width: 0` und dieselbe Begründung wie bei `.overview-grid > .card`
       oben: ein Grid-Kind bringt `min-width: auto` mit, die `1fr`-Spur wächst deshalb
       auf dessen Mindestinhalt statt zu schrumpfen, und die Seite bekommt waagrechten
       Scroll. Senkrecht fiel das nie auf — da ist die Spur `var(--rail)`, also fest.
       Waagrecht ist der Mindestinhalt der Rail Marke + Navigation + Fuss: gemessen 487px,
       bei 400px Fensterbreite also 100px zu viel, und zwar für die ganze Seite, nicht nur
       für die Rail. `.main` trägt sein `min-width: 0` aus demselben Grund seit je (oben). */
    min-width: 0;
  }
  /* Zurück zur festen Höhe: die Rail ist hier eine Zeile, kein Streifen. Die 100%-Breite
     aus der 1100px-Query würde das (hochformatige) Logo so hoch machen, wie die Zeile
     breit ist. Höhe führt, Breite folgt — das ergibt eine kleine quadratische Marke. */
  .rail-brand { padding: .75rem 1rem; }
  .rail-brand img { width: auto; height: 26px; }
  .rail-brand { border-bottom: none; border-right: 1px solid var(--border); }
  /* Das `min-width: 0` steht hier ausgeschrieben, obwohl es im Moment nichts ändert
     (gemessen): ein Flex-Item hat zwar `min-width: auto` und damit die Mindestbreite
     seiner sechs `.nav-item`, aber genau diese automatische Mindestgrösse ist per
     Definition 0, sobald das Item ein Scroll-Container ist — und das ist das `nav`
     durch sein eigenes `overflow-x: auto`. Die beiden Zeilen hängen also zusammen, und
     wer eine davon entfernt, holt sich die Mindestbreite zurück. Die Zeile schreibt
     das aus, statt es aus dem `overflow-x` folgen zu lassen.
     Den waagrechten Scroll verursachte nicht dieses `nav`, sondern die `.rail` selbst
     als Grid-Kind — siehe ihr `min-width: 0` oben. */
  .rail nav { flex-direction: row; overflow-x: auto; overflow-y: visible; min-width: 0; }
  /* `.nav-item` erbt `width: 100%` aus der senkrechten Rail — in der waagrechten
     Zeile macht das aus dem ersten Eintrag die ganze Breite (`flex-basis` greift
     bei `flex-basis: auto` auf `width` zurück), die übrigen fallen aus dem Bild. */
  .nav-item { flex: none; width: auto; }
  .rail-foot { border-top: none; border-left: 1px solid var(--border); margin-top: 0; }
  .topbar { flex-wrap: wrap; padding: .75rem 1rem; }
  .failure { padding: .7rem 1rem; }
  main.content { padding: 1rem 1rem 2rem; }
  /* Hier bricht die Kopfleiste um und ist höher als `--topbar-h` (gemessen 155px
     statt 67px bei 430px Fensterbreite). Eine Leiste, die auf einen falschen Wert
     klebt, verdeckt entweder Inhalt oder lässt einen Spalt — auf schmalem Schirm
     steht sie darum still am Anfang der Seite. Die Ränder folgen der schmaleren
     Polsterung des Inhalts. */
  .section-nav { position: static; margin: -1rem -1rem 1rem; padding: .5rem 1rem; }
  main.content section[id] { scroll-margin-top: 1rem; }
}
