/* GridSense Mesh - Admin/Monitoring. Technisch, minimal, kein Framework. */

:root {
    color-scheme: light dark;
    --bg: #f5f6f7;
    --surface: #ffffff;
    --border: #d9dde1;
    --text: #1b1f23;
    --muted: #6a7078;
    --accent: #2b6cb0;
    --fresh: #2f9e5e;
    --stale: #b7860b;
    --old: #9aa1a9;
    --danger: #c0392b;
    --restore: #7c3aed;
    --mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
}

@media (prefers-color-scheme: dark) {
    :root {
        --bg: #14171a;
        --surface: #1b1f23;
        --border: #2c3237;
        --text: #e6e9ec;
        --muted: #8b939c;
        --accent: #5b9bd5;
        --fresh: #4fbf7a;
        --stale: #d4a72c;
        --old: #6c737b;
        --danger: #e0685c;
        --restore: #a78bfa;
    }
}

* { box-sizing: border-box; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

.admin-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.75rem;
    padding: 0.9rem 1.5rem;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}

.admin-header-title {
    font-weight: 600;
    font-size: 1.05rem;
}

.admin-header-sub {
    font-weight: 400;
    color: var(--muted);
    margin-left: 0.4rem;
}

.admin-nav a {
    display: inline-block;
    margin-left: 1.25rem;
    padding: 0.3rem 0;
    color: var(--muted);
    text-decoration: none;
    border-bottom: 2px solid transparent;
}

.admin-nav a:hover {
    color: var(--text);
}

.admin-nav a.active {
    color: var(--text);
    border-bottom-color: var(--accent);
    font-weight: 600;
}

.admin-main {
    max-width: 1200px;
    margin: 0 auto;
    padding: 1.5rem;
}

h1 {
    font-size: 1.15rem;
    margin: 0 0 0.9rem;
}

table {
    width: 100%;
    border-collapse: collapse;
    background: var(--surface);
    border: 1px solid var(--border);
    font-size: 0.85rem;
}

th, td {
    padding: 0.5rem 0.65rem;
    text-align: left;
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
}

th {
    color: var(--muted);
    font-weight: 600;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    background: var(--bg);
}

tr:last-child td { border-bottom: none; }

/* overflow-wrap:anywhere zusätzlich zu white-space:normal, weil die Payload-
   JSON oft ein einziges langes Token ohne Leerzeichen ist (z.B. Hex-Werte);
   ohne das bricht der Browser dort gar nicht um, siehe device-layout oben. */
td.wrap, th.wrap { white-space: normal; overflow-wrap: anywhere; }

.mono { font-family: var(--mono); }

.muted { color: var(--muted); }

.badge {
    display: inline-block;
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 600;
    line-height: 1.5;
}

.badge-enabled { background: color-mix(in srgb, var(--fresh) 18%, transparent); color: var(--fresh); }
.badge-disabled { background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger); }
.badge-accepted { background: color-mix(in srgb, var(--fresh) 18%, transparent); color: var(--fresh); }
.badge-rejected { background: color-mix(in srgb, var(--old) 25%, transparent); color: var(--muted); }
.badge-muted { background: color-mix(in srgb, var(--stale) 18%, transparent); color: var(--stale); }

/* Schütz-Zustand (echte, hardwarebestätigte OPTO4-Rückmeldung, siehe
   docs/decisions/schuetz-rueckmeldung.md): dieselben drei Zustände wie in
   der iOS-App, AN (grün), AUS (grau) und unbekannt (nur Rahmen, noch keine
   Rückmeldung bekommen). AUS bewusst grau und nicht rot wie ein Alarm: ein
   offenes Schütz ist ein normaler Betriebszustand, kein Fehler. Gemeinsam
   genutzt von beiden Dashboards, den Geräte-Detailseiten und den
   Schalt-Kacheln weiter unten. */
.badge-schuetz-on { background: color-mix(in srgb, var(--fresh) 18%, transparent); color: var(--fresh); }
.badge-schuetz-off { background: color-mix(in srgb, var(--old) 25%, transparent); color: var(--muted); }
.badge-schuetz-unknown { border: 1px dashed var(--border); color: var(--muted); }

/* Schlafbereitschaft einer Node (siehe gridsense-mesh
   docs/decisions/deep-sleep-verhalten.md). Zwei bewusst verschieden
   gewichtete Zustände: "scharf" ist eine WARNUNG, die Node sieht normal aus,
   verschweigt aber den nächsten echten Stromausfall, und bekommt deshalb den
   Warnton. "schläft" ist nur die Erklärung für ein Offline und bleibt grau
   wie ein ausgeschaltetes Schütz. Gemeinsam genutzt von /admin und /m. */
.badge-sleep-armed { background: color-mix(in srgb, var(--stale) 18%, transparent); color: var(--stale); }
.badge-sleep-asleep { background: color-mix(in srgb, var(--old) 25%, transparent); color: var(--muted); }

.recency-fresh .recency-dot { background: var(--fresh); }
.recency-stale .recency-dot { background: var(--stale); }
.recency-old .recency-dot { background: var(--old); }
.recency-never .recency-dot { background: var(--border); }
/* Schlafend: offline, aber gewollt. Eigener Punkt statt des Offline-Graus,
   sonst sieht die Node in der Liste aus wie eine ausgefallene. */
.recency-sleeping .recency-dot { background: var(--accent); }

.recency-dot {
    display: inline-block;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    margin-right: 0.4rem;
}

.recency-old, .recency-never, .recency-sleeping { color: var(--muted); }

.filter-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: end;
    gap: 0.9rem;
    margin-bottom: 1rem;
    padding: 0.85rem 1rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
}

.filter-bar label {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    font-size: 0.78rem;
    color: var(--muted);
}

.filter-bar select {
    padding: 0.3rem 0.4rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--bg);
    color: var(--text);
    font-size: 0.85rem;
}

.filter-bar button {
    padding: 0.35rem 0.9rem;
    border: 1px solid var(--accent);
    border-radius: 4px;
    background: var(--accent);
    color: #fff;
    font-size: 0.85rem;
    cursor: pointer;
}

.filter-bar label.filter-bar-checkbox-label {
    flex-direction: row;
    align-items: center;
    gap: 0.4rem;
}

.filter-bar a.reset {
    font-size: 0.8rem;
    color: var(--muted);
    align-self: center;
}

.pagination {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: 1rem;
    font-size: 0.85rem;
    color: var(--muted);
}

.pagination a {
    color: var(--accent);
    text-decoration: none;
    padding: 0.3rem 0.7rem;
    border: 1px solid var(--border);
    border-radius: 4px;
}

.pagination a.disabled {
    color: var(--muted);
    pointer-events: none;
    opacity: 0.5;
}

.count-hint {
    font-size: 0.8rem;
    color: var(--muted);
    margin-bottom: 0.75rem;
}

.count-hint.error-text {
    color: var(--danger);
}

.actions-cell {
    white-space: nowrap;
}

.btn {
    display: inline-block;
    padding: 0.25rem 0.65rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--surface);
    color: var(--text);
    font-size: 0.78rem;
    cursor: pointer;
}

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

.btn-sm {
    font-size: 0.75rem;
}

.btn-primary {
    border-color: var(--accent);
    background: var(--accent);
    color: #fff;
}

.btn-danger {
    border-color: var(--danger);
    background: var(--danger);
    color: #fff;
}

.row-edit-input,
.row-edit-select {
    padding: 0.25rem 0.4rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--bg);
    color: var(--text);
    font-size: 0.85rem;
    max-width: 12rem;
}

/* Oeffentliches Dashboard + Geraete-Detailseite (public/dashboard.php,
   public/device.php): gleiche Bausteine wie der Admin-Bereich oben, plus
   ein paar zusaetzliche fuer Panels/Kennzahlen/Chart, die der Admin-Bereich
   bisher nicht braucht. */

.dashboard-title-link {
    color: inherit;
    text-decoration: none;
}

.stat-row {
    display: flex;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1.25rem;
}

.stat-tile {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0.65rem 1rem;
    min-width: 110px;
}

.stat-tile .stat-value {
    font-size: 1.35rem;
    font-weight: 700;
    line-height: 1.3;
}

.stat-tile .stat-value.danger { color: var(--danger); }

.stat-tile .stat-label {
    font-size: 0.72rem;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

.section-heading {
    font-size: 0.95rem;
    font-weight: 600;
    margin: 1.6rem 0 0.6rem;
}

.section-heading:first-of-type { margin-top: 0; }

table.clickable tbody tr { cursor: pointer; }
table.clickable tbody tr:hover { background: var(--bg); }

.panel {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 1rem 1.1rem;
    margin-bottom: 1rem;
}

.panel h2 {
    font-size: 0.95rem;
    margin: 0 0 0.2rem;
}

.panel .subline {
    font-size: 0.78rem;
    color: var(--muted);
    margin-bottom: 0.75rem;
}

/* Willkommens-Banner auf dem Admin-Dashboard (public/admin/index.php,
   dashboard.js), siehe docs/decisions/hello-onboarding-und-aktivierung.md
   in gridsense-docs. Grüner Akzentrand statt --danger: das ist ein
   Hinweis, keine Störung. */

.welcome-banner {
    border-left: 4px solid var(--fresh);
}

.welcome-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.welcome-list li a {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.25rem 0.75rem;
    padding: 0.5rem 0.6rem;
    border-radius: 4px;
    background: var(--bg);
    text-decoration: none;
    color: inherit;
}

.welcome-list li a:hover {
    background: color-mix(in srgb, var(--fresh) 12%, var(--bg));
}

.welcome-list .welcome-device {
    font-weight: 600;
}

.welcome-list .welcome-meta {
    font-size: 0.8rem;
    color: var(--muted);
}

/* Firmware-Verwaltung (public/admin/firmware.php, firmware.js), siehe
   docs/decisions/wlan-ota-firmware-update.md in gridsense-docs. Rot statt
   grün: das ist keine Info wie das Willkommens-Banner, sondern eine
   ausdrückliche Warnung vor einer Aktion, die ein Feldgerät ohne Kabel
   unerreichbar machen kann. */

.warning-banner {
    border-left: 4px solid var(--danger);
}

.firmware-upload-form {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 0.75rem;
}

.firmware-upload-form label {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    font-size: 0.85rem;
}

.firmware-upload-form input,
.firmware-upload-form select {
    padding: 0.4rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--bg);
    color: inherit;
    font: inherit;
}

/* Login-Seite (public/admin/login.php, siehe docs/decisions/benutzer-auth.md). */

.login-page {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
}

.login-panel {
    width: 100%;
    max-width: 320px;
}

.login-panel h1 {
    font-size: 1.05rem;
    margin: 0 0 0.9rem;
}

.login-field {
    display: block;
    font-size: 0.8rem;
    color: var(--muted);
    margin-bottom: 0.8rem;
}

.login-input {
    display: block;
    width: 100%;
    max-width: none;
    margin-top: 0.3rem;
}

.login-panel .btn {
    width: 100%;
}

.device-layout {
    display: grid;
    /* minmax(0, 1fr) statt 1fr: sonst erzwingt der Inhalt (z.B. die lange,
       leerzeichenlose Payload-JSON in der Events-Tabelle) eine größere
       Spurbreite als der Viewport und die ganze Seite wird breiter als
       der Bildschirm, siehe docs/decisions/mobile-web-uebergang.md,
       Nebenbefund "Payload-Zeile sprengte die Node-Detailseite". */
    grid-template-columns: minmax(260px, 340px) minmax(0, 1fr);
    gap: 1rem;
    align-items: start;
}

@media (max-width: 860px) {
    .device-layout { grid-template-columns: 1fr; }
}

.kv-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0.65rem 1rem;
}

.kv-grid .k {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    color: var(--muted);
}

.kv-grid .v {
    font-size: 0.88rem;
    margin-top: 0.1rem;
}

.raw-payload {
    font-family: var(--mono);
    font-size: 0.78rem;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.6rem;
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 220px;
    overflow: auto;
}

.chart-wrap {
    position: relative;
    height: 220px;
}

.chart-controls {
    display: flex;
    gap: 0.75rem;
    flex-wrap: wrap;
    align-items: center;
    font-size: 0.78rem;
    color: var(--muted);
}

.rx-strip {
    display: flex;
    flex-wrap: wrap;
    gap: 3px;
    align-items: flex-end;
    min-height: 32px;
    margin-top: 0.5rem;
}

.rx-block {
    width: 14px;
    height: 28px;
    border-radius: 2px;
    flex-shrink: 0;
}

.rx-legend {
    display: flex;
    gap: 1rem;
    flex-wrap: wrap;
    font-size: 0.72rem;
    color: var(--muted);
    margin-top: 0.5rem;
}

.rx-legend .dot {
    display: inline-block;
    width: 0.6rem;
    height: 0.6rem;
    border-radius: 2px;
    margin-right: 0.3rem;
    vertical-align: middle;
}

/* Karten-Grid fuers oeffentliche Dashboard (public/dashboard.php): vom
   Gen-1-Dashboard uebernommenes Layout (Karten statt Tabellenzeilen, siehe
   docs/decisions/oeffentliches-dashboard.md), aber mit den Gen-2-Farben/
   -Bausteinen von oben statt Gen-1s eigenem dunklem Farbschema. */

.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 0.75rem;
    margin-bottom: 1rem;
}

.grid-card {
    background: var(--surface);
    border: 2px solid var(--border);
    border-radius: 8px;
    padding: 0.75rem 0.85rem;
    cursor: pointer;
    transition: transform 0.1s ease, border-color 0.1s ease;
}

.grid-card:hover { transform: translateY(-1px); }

.grid-card.status-ok { border-color: var(--fresh); }
.grid-card.status-alarm { border-color: var(--danger); }
.grid-card.status-restore { border-color: var(--restore); }
.grid-card.status-offline { border-color: var(--border); filter: saturate(0.6); opacity: 0.75; }

.grid-card .row1 {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 0.5rem;
}

.grid-card .name {
    font-weight: 700;
    font-size: 0.9rem;
}

.grid-card .id {
    margin-top: 0.1rem;
    font-size: 0.72rem;
    color: var(--muted);
}

.grid-card .pill {
    font-size: 0.68rem;
    font-weight: 600;
    padding: 0.15rem 0.5rem;
    border-radius: 999px;
    background: var(--bg);
    border: 1px solid var(--border);
    color: var(--text);
    white-space: nowrap;
}

.grid-card .card-meta {
    margin-top: 0.6rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.75rem;
    color: var(--muted);
}

.grid-card .card-site {
    margin-top: 0.15rem;
}

.grid-card .card-schuetz {
    margin-top: 0.55rem;
}

.bars8 {
    display: grid;
    grid-auto-flow: column;
    gap: 2px;
    align-items: end;
}

.bars8 span {
    width: 3px;
    border-radius: 1px;
    background: var(--border);
}

.bars8 span:nth-child(1) { height: 4px; }
.bars8 span:nth-child(2) { height: 5px; }
.bars8 span:nth-child(3) { height: 6px; }
.bars8 span:nth-child(4) { height: 7px; }
.bars8 span:nth-child(5) { height: 8px; }
.bars8 span:nth-child(6) { height: 9px; }
.bars8 span:nth-child(7) { height: 10px; }
.bars8 span:nth-child(8) { height: 11px; }

.bars8.q1 span:nth-child(-n+1),
.bars8.q2 span:nth-child(-n+2),
.bars8.q3 span:nth-child(-n+3),
.bars8.q4 span:nth-child(-n+4),
.bars8.q5 span:nth-child(-n+5),
.bars8.q6 span:nth-child(-n+6),
.bars8.q7 span:nth-child(-n+7),
.bars8.q8 span:nth-child(-n+8) {
    background: var(--accent);
}

/* Site-Auswahl (public/index.php): neutrale Kacheln im selben Grid wie
   .card-grid oben, aber ohne Status-Farbmodifikatoren - eine Site hat
   selbst keinen Alarm-/Offline-Zustand. */

.site-card {
    background: var(--surface);
    border: 2px solid var(--border);
    border-radius: 8px;
    padding: 1rem 1.1rem;
    cursor: pointer;
    transition: transform 0.1s ease, border-color 0.1s ease;
}

.site-card:hover { transform: translateY(-1px); border-color: var(--accent); }

.site-card .site-name { font-weight: 700; font-size: 1rem; }

.site-card .site-count {
    margin-top: 0.3rem;
    font-size: 0.8rem;
    color: var(--muted);
}

/* Gruppenchat pro Site (public/admin/chat.php/js), siehe
   docs/decisions/site-chat.md. Einfache Bubble-Liste, kein Rich-Text. */
.chat-messages {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    max-height: 60vh;
    overflow-y: auto;
    padding: 0.85rem 1rem;
    margin-bottom: 0.75rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
}

/* Sprechblase, ausgerichtet per align-self innerhalb der flex-column
   .chat-messages: fremde Nachrichten links (Default), eigene rechts
   (.chat-message-own), gleiche Konvention wie .m-msg/.m-msg-own im
   Mobil-Bereich (public/m/m.css) - is_own kommt seit Chat-API v2 direkt
   von der API statt ueber einen Namensvergleich. */
.chat-message {
    max-width: 78%;
    align-self: flex-start;
    padding: 0.5rem 0.7rem;
    border-radius: 8px;
    background: var(--bg);
    border: 1px solid var(--border);
}

.chat-message-own {
    align-self: flex-end;
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    border-color: color-mix(in srgb, var(--accent) 35%, transparent);
}

.chat-message-meta {
    display: block;
    font-size: 0.72rem;
    color: var(--muted);
    margin-bottom: 0.15rem;
}

.chat-author-user { font-weight: 700; color: var(--text); }
.chat-author-device { font-weight: 700; color: var(--accent); }

.chat-message-body {
    white-space: pre-wrap;
    word-break: break-word;
    font-size: 0.9rem;
}

/* Tagestrenner zwischen zwei Kalendertagen, gleiche Konvention wie in der
   App (ChatDaySeparator in gridsense-app-ios ChatView.swift). Zentriert
   ueber die volle Breite der flex-column .chat-messages. */
.chat-day {
    align-self: center;
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--muted);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 0.15rem 0.7rem;
    margin: 0.2rem 0;
}

/* Byte-Zaehler unterm Eingabefeld gegen ChatLimits::BODY_MAX_BYTES (siehe
   chat.js/chat.php), gleiche Schwellen wie ChatLimits.remainingBytes() in
   der iOS-App: ab 20 Byte Rest orange, darueber (negativer Rest) rot. */
.chat-counter {
    text-align: right;
}

.chat-counter-warn { color: var(--stale); }
.chat-counter-over { color: var(--danger); font-weight: 600; }

.chat-compose {
    display: flex;
    gap: 0.6rem;
    align-items: flex-end;
}

.chat-compose textarea {
    flex: 1;
    min-height: 2.4rem;
    max-height: 8rem;
    padding: 0.5rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--bg);
    color: var(--text);
    font-family: inherit;
    font-size: 0.9rem;
    resize: vertical;
}

.chat-compose button {
    padding: 0.5rem 1.1rem;
    border: 1px solid var(--accent);
    border-radius: 4px;
    background: var(--accent);
    color: #fff;
    font-size: 0.85rem;
    cursor: pointer;
}

/* Schalt-Kacheln (public/admin/schuetz.php): Layout vom Gen-1-WebApp-Bildschirm
   "Switch-Steuerung" übernommen (Kachelraster mit AN/AUS je Node, darüber
   Gruppenkarten in voller Breite), aber mit den Gen-2-Farben/-Bausteinen von
   oben statt Gen-1s eigenem dunklem Farbschema, genau wie beim Karten-Grid des
   öffentlichen Dashboards. Kachelrahmen grün/rot je Schütz-Zustand: hier ist
   Rot für AUS eindeutig, weil auf dieser Seite kein Alarm-Zustand mitgefärbt
   wird (deshalb tragen die Badges selbst weiter das neutrale Grau, siehe
   .badge-schuetz-off oben). */

.switch-tile-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
    gap: 0.75rem;
    margin-bottom: 1rem;
}

.switch-tile {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
    min-height: 9rem;
    padding: 0.75rem 0.85rem;
    background: var(--surface);
    border: 2px solid var(--border);
    border-radius: 8px;
}

.switch-tile.state-on { border-color: var(--fresh); }
.switch-tile.state-off { border-color: var(--danger); }
.switch-tile.state-unknown { border-style: dashed; }

/* Deep-Link von der Geräte-Detailseite (?source_id=, siehe schuetz.js):
   hebt genau die gemeinte Kachel hervor, ohne die Zustandsfarbe des Rahmens
   zu überschreiben. */
.switch-tile.is-highlighted {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 35%, transparent);
}

.switch-tile .tile-name {
    font-weight: 700;
    font-size: 0.9rem;
}

.switch-tile .tile-id {
    margin-top: 0.1rem;
    font-size: 0.72rem;
    color: var(--muted);
}

.switch-badges {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
}

.switch-tile .tile-meta {
    font-size: 0.75rem;
    color: var(--muted);
}

.switch-tile .tile-buttons {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.4rem;
    margin-top: auto;
}

.btn-switch-on,
.btn-switch-off {
    padding: 0.55rem 0.4rem;
    border-radius: 4px;
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--text);
    cursor: pointer;
}

.btn-switch-on {
    border: 1px solid color-mix(in srgb, var(--fresh) 55%, transparent);
    background: color-mix(in srgb, var(--fresh) 14%, transparent);
}

.btn-switch-off {
    border: 1px solid color-mix(in srgb, var(--danger) 55%, transparent);
    background: color-mix(in srgb, var(--danger) 12%, transparent);
}

.btn-switch-on:hover { background: color-mix(in srgb, var(--fresh) 26%, transparent); }
.btn-switch-off:hover { background: color-mix(in srgb, var(--danger) 24%, transparent); }

/* Gruppenkarte: volle Breite, weil sie eine aufklappbare Mitgliederliste
   trägt (Gen-1-Aufteilung, dort ebenfalls Kacheln für Nodes und breite
   Karten für Gruppen). */
.switch-group-card {
    background: var(--surface);
    border: 2px solid var(--border);
    border-radius: 8px;
    margin-bottom: 0.75rem;
}

.switch-group-card.state-on { border-color: var(--fresh); }
.switch-group-card.state-off { border-color: var(--danger); }

.switch-group-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.75rem 0.9rem;
}

.switch-group-name {
    font-weight: 700;
    font-size: 0.95rem;
}

.switch-group-meta {
    margin-top: 0.15rem;
    font-size: 0.75rem;
    color: var(--muted);
}

.switch-group-buttons {
    display: flex;
    gap: 0.4rem;
}

.switch-group-buttons button {
    min-width: 6.5rem;
}

.star-btn {
    padding: 0 0.2rem;
    border: none;
    background: none;
    color: var(--muted);
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
}

.star-btn.is-favorite { color: var(--stale); }

.switch-group-card details > summary {
    padding: 0.5rem 0.9rem;
    border-top: 1px solid var(--border);
    font-size: 0.78rem;
    color: var(--muted);
    cursor: pointer;
}

.switch-member-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    padding: 0.55rem 0.9rem;
    border-top: 1px solid var(--border);
}

.switch-member-name {
    font-weight: 600;
    font-size: 0.85rem;
}

.switch-group-edit {
    padding: 0.75rem 0.9rem;
    border-top: 1px solid var(--border);
    background: var(--bg);
}

.switch-checkbox-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
    gap: 0.35rem;
    margin: 0.4rem 0 0.6rem;
}

.switch-checkbox-item {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.35rem 0.5rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 4px;
    font-size: 0.82rem;
}

.switch-inline-form {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: 0.6rem;
}

details.switch-new-group {
    margin-bottom: 1rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
}

details.switch-new-group > summary {
    padding: 0.6rem 0.9rem;
    font-size: 0.82rem;
    color: var(--muted);
    cursor: pointer;
}

.switch-new-group-body {
    padding: 0.75rem 0.9rem;
    border-top: 1px solid var(--border);
}

/* ── Geländeplan (site-map.js, docs/decisions/site-karte.md) ── */
.site-map-wrap {
    position: relative;
    width: 100%;
    background: repeating-conic-gradient(var(--bg) 0% 25%, var(--surface) 0% 50%) 50% / 16px 16px;
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;
}
/* Positionierung/Zoom kommt per Inline-Style von site-map.js (layout()):
   left/top/width/height bilden immer die kontenierte Bildfläche exakt ab,
   transform:scale() zoomt im Crop-Modus zentriert hinein. <img> UND <svg>
   liegen beide HIER drin und teilen sich dadurch zwangsläufig dieselbe
   Transformation, siehe Nachtrag-Kommentar in site-map.js. */
.site-map-content {
    position: absolute;
    transform-origin: 0 0;
}
.site-map-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    user-select: none;
    pointer-events: none;
}
.site-map-svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}
.site-map-empty {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--muted);
    font-size: 0.85rem;
    text-align: center;
    padding: 1rem;
}

.site-map-marker-shape { stroke: var(--surface); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.site-map-marker-shape.status-ok      { fill: var(--fresh); }
.site-map-marker-shape.status-alarm   { fill: var(--danger); }
.site-map-marker-shape.status-restore { fill: var(--restore); }
.site-map-marker-shape.status-offline { fill: var(--old); }
.site-map-marker-shape.status-unknown,
.site-map-marker-shape:not([class*="status-"]) { fill: var(--accent); }
.site-map-alarm-ring {
    fill: none;
    stroke: var(--danger);
    stroke-width: 1.5;
    vector-effect: non-scaling-stroke;
    opacity: 0.75;
}
/* font-size kommt gezielt PRO Marker als Inline-Attribut von site-map.js
   (render()): die viewBox liegt jetzt in echten, unskalierten CSS-Pixeln
   der Content-Box (siehe dortiger Kommentar), ein fester CSS-Wert hier
   würde sich im gezoomten Crop-Modus mit hochskalieren, `vector-effect:
   non-scaling-stroke` gibt es für font-size nicht (nur für stroke). */
.site-map-marker-label {
    fill: var(--text);
    paint-order: stroke;
    stroke: var(--surface);
    stroke-width: 3px;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}
.site-map-marker { cursor: pointer; }
.site-map-marker-editable { cursor: grab; }
.site-map-marker-editable:active { cursor: grabbing; }

/* 40%-Ausschnitt auf der Node-Detailseite/Mobil-Gerätekarte: fest niedrigere
   Höhe als die volle Editor-/Live-Karte, damit er wirklich als kompakter
   Ausschnitt wirkt und nicht wie eine zweite Vollkarte. */
.site-map-wrap.site-map-crop { height: 220px; }
/* Nur Rückfall, solange noch kein Plan geladen ist ("lädt..."/"kein Plan
   konfiguriert"): sobald ein Bild da ist, setzt sizeContainer() in
   site-map.js eine Inline-Höhe nach dem Bild-Seitenverhältnis bei voller
   Container-Breite (immer maximale Breite statt einer festen Höhe, die
   einen breiten Plan winzig machte, siehe dortiger Kommentar). */
.site-map-wrap.site-map-full { height: 420px; }
@media (max-width: 900px) {
    .site-map-wrap.site-map-full { height: 320px; }
}

.site-map-toolbar {
    display: flex;
    gap: 0.4rem;
    flex-wrap: wrap;
    align-items: center;
    margin-bottom: 0.6rem;
}
.site-map-toolbar .active-tool { border-color: var(--accent) !important; color: var(--accent) !important; }
.site-map-flex-spacer { flex: 1; }

/* Geräte-Chip-Reihe (Nachtrag 2026-08-17): ersetzt die vorherige schmale
   Seitenspalten-Liste. Umbricht wie normaler Text, deshalb immer ohne
   Scrollbalken sichtbar, egal wie viele Geräte eine Site hat. */
.site-map-devices-panel { margin-bottom: 1rem; }
.site-map-chip-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.45rem;
}
.site-map-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.3rem 0.55rem 0.3rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface);
    color: var(--text);
    font-size: 0.82rem;
    font-family: inherit;
    cursor: pointer;
    line-height: 1.3;
}
.site-map-chip:hover { border-color: var(--accent); }
.site-map-chip.is-armed { border-color: var(--accent); color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.site-map-chip.is-placed { border-color: color-mix(in srgb, var(--fresh) 45%, var(--border)); }
.site-map-placed-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; background: var(--old); }
.site-map-placed-dot.yes { background: var(--fresh); }
.site-map-chip-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    color: var(--muted);
    font-size: 0.75rem;
    line-height: 1;
}
.site-map-chip-remove:hover { background: color-mix(in srgb, var(--danger) 18%, transparent); color: var(--danger); }

.site-map-upload-label input[type="file"] {
    display: block;
    font-size: 0.78rem;
    max-width: 260px;
}
