/* ============================================================================
   EKH WebTerm — Global Theme & Base Styles
   
   Architecture:
   - This file:  CSS Variables (theme), reset, layout, global components
   - *.razor.css: Component-scoped styles using var(--...) references
   - Blazor bundles all .razor.css into WebTerm.Web.styles.css automatically
   ============================================================================ */

/* === THEME VARIABLES === */
/* KEIN @import der Tokens mehr — sie kommen als eigenes <link> aus App.razor.
   Grund: der @import trug keinen Cache-Schluessel, app.css schon. Nach einem Deploy
   konnte der Browser neues app.css mit ALTEN Tokens mischen; jedes `var(--neues-token)`
   war dann unbekannt und die ganze Deklaration ungueltig (2026-08-26: der
   Verbindungsdialog verlor so seinen z-index und verschwand). Nicht wieder eintragen. */

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

/* 87.5% von 16px = 14px: optisch identisch zu vorher, aber die Schriftgroessen-Einstellung
   des Browsers wirkt wieder proportional (bei 20px Grundeinstellung werden daraus 17,5px).
   Ein festes `font-size: 14px` hat sie ueberstimmt. Die Wurzel traegt den Wert allein --
   stuende er in der gemeinsamen html,body-Regel, naehme body 87,5% VON den 14px. */
html { font-size: 87.5%; }

html, body {
    margin: 0; padding: 0;
    font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
    line-height: 1.5;
    color: var(--text-primary);
    background-color: var(--surface-page);
    min-height: 100vh;
}

#app { min-height: 100vh; }

/* === MAIN LAYOUT === */
.page { display: flex; min-height: 100vh; }

.sidebar {
    width: var(--sidebar-width); min-height: 100vh;
    background: var(--sidebar-bg); color: var(--text-body);
    position: fixed; left: 0; top: 0; bottom: 0; z-index: 100;
    overflow-y: auto;
    border-right: 1px solid #d0e8ef;
    box-shadow: var(--shadow-sm);
    border-radius: 0 var(--radius-xl) var(--radius-xl) 0;
}

/* Bottom padding inside the sidebar so the last nav items aren't
   flush against the rounded-corner edge AND so the scroll-indicator
   pseudo-element below has room to sit without covering any nav item.
   24 px = ~16 px arrow + 8 px breathing room. */
.sidebar nav { padding-bottom: 24px; }

/* Scroll-down indicator — sits in the bottom padding area, NEVER
   overlapping any nav item. Visible only when the sidebar overflows
   AND the user hasn't scrolled to the bottom. The earlier overlay
   (commit dc787b2, reverted in 9bfc805) covered the last 36 px of
   nav with a sidebar-bg gradient and accidentally hid Bug-Reports +
   Beta-Visu + Dokumentation; this replacement is a single 16 px arrow
   in the padding region, opaque enough to read but transparent enough
   not to look like chrome.
   `position: fixed` so it stays anchored to the viewport edge while
   the user scrolls within the sidebar. */
.sidebar.has-overflow:not(.scrolled-to-bottom)::after {
    content: '▾  scrollen';
    position: fixed;
    left: 0;
    bottom: 0;
    width: var(--sidebar-width);
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.7rem;
    color: var(--text-secondary);
    background: var(--sidebar-bg);
    border-top: 1px solid var(--border-subtle);
    pointer-events: none;
    z-index: 101;
    border-radius: 0 0 var(--radius-xl) 0;
    opacity: 0.85;
}

.sidebar-header {
    height: var(--topbar-height); padding: 0 0.5rem;
    border-bottom: 1px solid #d0e8ef;
    display: flex; align-items: center; justify-content: flex-start;
    background: var(--sidebar-bg);
}

.sidebar-logo-link { display: flex; align-items: center; height: 100%; text-decoration: none; padding: 0.5rem 0; }
.sidebar-logo { height: 100%; width: auto; object-fit: contain; }
.sidebar nav { padding: 1rem 0; border-radius: var(--radius-lg); }
.nav-item { margin: 0.25rem 0.75rem; }

.nav-link {
    display: flex; align-items: center; gap: 0.75rem;
    padding: 0.75rem 1rem; color: var(--text-body);
    text-decoration: none; border-radius: var(--radius);
    transition: all 0.2s ease; cursor: pointer;
}
.nav-link:hover { background: var(--surface-hover); color: var(--text-primary); }
.nav-link.active { background: var(--color-primary); color: var(--text-on-primary); }
.nav-link .icon { width: 20px; height: 20px; opacity: 0.8; }

/* Mobile-app promo banner at the bottom of the sidebar (admin-toggleable). */
.app-banner {
    margin: 1.25rem 0.75rem 0.5rem;
    padding: 0.85rem;
    background: var(--surface-sunken);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    text-align: center;
}
.app-banner-title {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-secondary);
    margin-bottom: 0.6rem;
}
.app-banner-qr {
    display: inline-block;
    padding: 6px;
    background: var(--surface-card);
    border-radius: var(--radius);
    line-height: 0;
}
.app-banner-qr img,
.app-banner-qr canvas { display: block; }
.app-banner-link {
    display: block;
    margin-top: 0.6rem;
    font-size: 0.8rem;
    color: var(--color-primary);
    text-decoration: none;
}
.app-banner-link:hover { text-decoration: underline; }

.nav-section {
    padding: 0.5rem 1.5rem; margin-top: 1rem;
    font-size: 0.75rem; font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.05em;
    color: var(--color-primary);
}

.nav-section-toggle {
    cursor: pointer !important; display: flex; justify-content: space-between;
    align-items: center; border-radius: var(--radius-sm);
    transition: opacity 0.2s ease, background 0.2s ease;
    user-select: none; list-style: none; list-style-type: none;
}
.nav-section-toggle::marker { display: none; content: ""; font-size: 0; }
.nav-section-toggle::-webkit-details-marker { display: none; }
summary.nav-section-toggle { list-style: none; list-style-type: none; cursor: pointer; }
details.nav-details > summary { list-style: none; cursor: pointer; }
details.nav-details > summary::marker { display: none; content: none; }
details.nav-details > summary::-webkit-details-marker { display: none; }
.nav-section-toggle:hover { opacity: 0.8; background: var(--surface-hover); }
.toggle-chevron { font-size: 0.7rem; transition: transform 0.3s ease; }

.nav-details { margin: 0; }
.nav-details[open] .toggle-chevron { transform: rotate(90deg); }
/* Bidirectional fade for Energie-Monitoring / Administration / etc.
   nav <details> sections. Previous version used a one-way keyframe
   animation (navFadeIn) that ran on open but closed instantly —
   plus held a final max-height of 600 px that was clipping long
   sections (the bug from a8b33df).
   New approach: CSS transition on opacity + transform.
     * transition-behavior: allow-discrete on the `display` property
       keeps `display: block` until the opacity fade-out finishes,
       then discretely flips to `display: none`. Otherwise the
       browser would hide the element instantly and we'd see no
       fade-out at all.
     * @starting-style sets the initial state (opacity 0, slight
       upward offset) when the element first becomes visible —
       so the open-transition animates IN smoothly, matching the
       fade-out feel.
   Browser support: Chrome 117+, Firefox 129+, Safari 17.4+. All
   modern browsers as of 2026; older fall back to instant toggle. */
.nav-details .nav-collapse-content {
    opacity: 1;
    transform: translateY(0);
    transition:
        opacity 0.25s ease,
        transform 0.25s ease,
        display 0.25s ease allow-discrete;
}

.nav-details:not([open]) .nav-collapse-content {
    display: none;
    opacity: 0;
    transform: translateY(-4px);
}

@starting-style {
    .nav-details[open] .nav-collapse-content {
        opacity: 0;
        transform: translateY(-4px);
    }
}

/* Nested sub-collapses inside #adminDetails and #energieDetails.
   Visually distinct from the top-level section but lighter than a
   nav-item: smaller font, slight left indent, subtle hover. The
   parent .nav-section-toggle inherits the uppercase/letter-spacing
   from .nav-section, so we explicitly reset those here for the
   sub-section header. */
.nav-subsection > summary.nav-section-toggle {
    font-size: 0.85rem;
    font-weight: 600;
    text-transform: none;
    letter-spacing: 0;
    padding: 0.4rem 0.5rem 0.4rem 0.75rem;
    color: var(--text-secondary);
    margin-top: 0.25rem;
}
.nav-subsection > summary.nav-section-toggle:hover {
    background: var(--surface-hover);
    color: var(--text-primary);
    opacity: 1;
}
.nav-subsection > .nav-collapse-content {
    padding-left: 0.75rem;
}
.nav-subsection > .nav-collapse-content > .nav-item .nav-link {
    font-size: 0.9rem;
}

.nav-link.nav-danger { color: var(--color-danger); }
.nav-link.nav-danger:hover { background: var(--color-danger-10); color: #b91c1c; }
.danger-badge { font-size: 0.7rem; margin-left: 0.25rem; }

.main-content {
    flex: 1; margin-left: var(--sidebar-width); min-height: 100vh;
    display: flex; flex-direction: column;
    /* min-width: 0 is load-bearing: as a flex item this column otherwise GROWS to fit
       the widest descendant (default min-width:auto), so a wide inner scroll area —
       e.g. the Gantt timeline at Tag zoom — inflates the whole page sideways instead
       of scrolling inside its own container, pushing everything right of it (edit
       panel, Anlagen picker) off-screen (field report gantt6). */
    min-width: 0;
}

.top-bar {
    background: var(--surface-raised);
    border-bottom: 1px solid #d0e8ef;
    height: var(--topbar-height); padding: 0 2rem;
    display: flex; justify-content: space-between; align-items: center;
    border-radius: var(--radius-lg);
    /* Stay visible while the content area scrolls — keeps the user's name,
       logout, and brand always reachable. z-index keeps it above scrolled
       page elements and any sticky chrome inside the content. */
    position: sticky;
    top: 0;
    z-index: 50;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
}
.top-bar .btn-outline-secondary { color: var(--text-secondary); border-color: var(--border-default); background: transparent; }
.top-bar .btn-outline-secondary:hover { background: var(--surface-hover); color: var(--text-primary); }

.content {
    flex: 1; padding: 2rem;
    background: var(--surface-page);
    border-radius: var(--radius-lg);
}

/* noVNC takes the full content area as a flex container so the iframe
   stretches to fill all remaining vertical space without overflowing. */
article.content:has(.novnc-wrapper) {
    display: flex;
    flex-direction: column;
    padding: 0.75rem;
    min-height: 0;
}

/* SSH terminal: same full-height flex treatment as noVNC so the xterm.js
   container fills the content area and the page never scrolls (the
   terminal manages its own internal scrollback instead). */
article.content:has(.ssh-terminal-page) {
    display: flex;
    flex-direction: column;
    padding: 0.75rem;
    min-height: 0;
}

/* === CARDS === */
.card {
    background: var(--surface-card); border-radius: var(--radius-lg);
    box-shadow: var(--shadow); border: 1px solid var(--border-default);
}
.card-header { padding: 1rem 1.5rem; border-bottom: 1px solid var(--border-default); font-weight: 600; }
.card-body { padding: 1.5rem; }
.card-footer { padding: 1rem 1.5rem; border-top: 1px solid var(--border-default); background: var(--gray-50); border-radius: 0 0 var(--radius-lg) var(--radius-lg); }

.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.5rem; margin-bottom: 2rem; }
.stat-card { background: var(--surface-card); border-radius: var(--radius-lg); padding: 1.5rem; box-shadow: var(--shadow); border: 1px solid var(--border-default); }
.stat-card .stat-icon { width: 48px; height: 48px; border-radius: var(--radius); display: flex; align-items: center; justify-content: center; margin-bottom: 1rem; }
.stat-card .stat-icon.primary { background: var(--color-primary-10); color: var(--color-primary); }
.stat-card .stat-icon.success { background: var(--color-success-10); color: var(--color-success); }
.stat-card .stat-icon.warning { background: var(--color-warning-10); color: var(--color-warning); }
.stat-card .stat-icon.danger { background: var(--color-danger-10); color: var(--color-danger); }
.stat-card .stat-value { font-size: 2rem; font-weight: 700; line-height: 1; margin-bottom: 0.5rem; }
.stat-card .stat-label { color: var(--text-secondary); font-size: 0.875rem; }

/* === BUTTONS === */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
    padding: 0.5rem 1rem; font-size: 0.875rem; font-weight: 500; line-height: 1.5;
    border-radius: var(--radius); border: 1px solid transparent;
    cursor: pointer; transition: all 0.2s ease; text-decoration: none;
}
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

.btn-primary { background: var(--color-primary); color: var(--text-on-primary); border-color: var(--color-primary); }
.btn-primary:hover:not(:disabled) { background: var(--color-primary-hover); border-color: var(--color-primary-hover); }
.btn-secondary { background: var(--gray-500); color: var(--text-on-primary); border-color: var(--gray-500); }
.btn-secondary:hover:not(:disabled) { background: var(--gray-600); border-color: var(--gray-600); }
.btn-success { background: var(--color-success); color: var(--text-on-primary); border-color: var(--color-success); }
.btn-success:hover:not(:disabled) { background: #15803d; }
.btn-warning { background: var(--color-warning); color: var(--text-on-primary); border-color: var(--color-warning); }
.btn-warning:hover:not(:disabled) { background: #b45309; }
.btn-danger { background: var(--color-danger); color: var(--text-on-primary); border-color: var(--color-danger); }
.btn-danger:hover:not(:disabled) { background: #b91c1c; }
.btn-outline-danger { background: transparent; color: var(--color-danger); border-color: var(--color-danger); }
.btn-outline-danger:hover:not(:disabled) { background: var(--color-danger); color: var(--text-on-primary); }
.btn-outline-warning { background: transparent; color: var(--color-warning); border-color: var(--color-warning); }
.btn-outline-warning:hover:not(:disabled) { background: var(--color-warning); color: var(--text-on-primary); }
.btn-outline-primary { background: transparent; color: var(--color-primary); border: 1.5px solid var(--color-primary); }
.btn-outline-primary:hover:not(:disabled) { background: var(--color-primary); color: var(--text-on-primary); }
.btn-outline-secondary { background: transparent; color: var(--text-secondary); border-color: var(--border-default); }
.btn-outline-secondary:hover:not(:disabled) { background: var(--surface-hover); color: var(--text-primary); }

.btn-stoerung-aktiv { animation: stoerung-pulse 2.5s ease-in-out infinite; font-weight: 600; }
/* /heizungen: the Tickets button when the controller has open tickets. Filled tint
   instead of the outline default, so a row with open work is recognisable while
   scanning the list (the count badge alone is easy to miss). Deliberately the calm
   primary tint, NOT an alarm colour — the pulsing amber next to it belongs to active
   Störungen, and open tickets must not compete with that. Hover keeps the standard
   .btn-outline-primary fill (higher specificity). */
/* Open tickets: FILL the button. The first attempt used --color-primary-10, a 10 %
   tint, which is invisible when the row is scanned from a distance — the whole point
   of the marker. Same treatment as .btn-primary: this is an outline button in its
   "active" state, so it should look like the filled variant, not a tinted one. */
.btn-tickets-offen {
    background: var(--color-primary);
    color: var(--text-on-primary);
    border-color: var(--color-primary);
    font-weight: 600;
}

.btn-tickets-offen:hover:not(:disabled) {
    background: var(--color-primary-hover);
    border-color: var(--color-primary-hover);
}

/* The count must not change the button's BOX. Its neighbours in the row are plain
   buttons, and .badge brings its own vertical padding (0.25rem) plus a taller line
   box than .btn's text — so this one button came out bigger than the rest of the row.
   Sized to sit INSIDE the button's 0.875rem/1.5 line box, so the row stays even no
   matter how many tickets are open.
   The colours are not cosmetic either: .badge-info uses --color-primary-10, i.e. the
   very tint the button now fills with, so the badge would vanish into it. White chip
   with dark-cyan text instead — 4.96:1, clears AA for normal text. */
.btn-tickets-offen .badge {
    padding: 0 0.4rem;
    font-size: 0.6875rem;
    line-height: 1.25;
    background: var(--surface-card);
    color: var(--color-primary-hover);
}
@keyframes stoerung-pulse { 0%,100% { box-shadow: 0 0 0 0 rgba(217,119,6,0.4); } 50% { box-shadow: 0 0 10px 2px rgba(217,119,6,0.25); } }
.btn-sm { padding: 0.25rem 0.75rem; font-size: 0.8125rem; }
.btn-lg { padding: 0.75rem 1.5rem; font-size: 1rem; }

/* === FORMS === */
.form-group { margin-bottom: 1rem; }
.form-label { display: block; margin-bottom: 0.5rem; font-weight: 500; color: var(--text-body); }

.form-control {
    display: block; width: 100%; padding: 0.5rem 0.75rem;
    font-size: 0.875rem; line-height: 1.5;
    color: var(--text-primary); background-color: var(--surface-card);
    border: 1px solid var(--border-strong); border-radius: var(--radius);
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.form-control:focus { outline: none; border-color: var(--border-focus); box-shadow: 0 0 0 3px var(--ekh-cyan-15); }
.form-control:disabled { background-color: var(--surface-hover); color: var(--text-muted); cursor: not-allowed; }
.form-control.is-invalid { border-color: var(--color-danger); }
.form-check { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; }
.form-check-input { width: 1rem; height: 1rem; accent-color: var(--color-primary); }

.form-select, select {
    display: block; width: auto; padding: 0.5rem 2.5rem 0.5rem 0.75rem;
    font-size: 0.875rem; line-height: 1.5;
    color: var(--text-primary); background-color: var(--surface-card);
    border: 1px solid var(--border-strong); border-radius: var(--radius);
    appearance: none; -webkit-appearance: none; -moz-appearance: none;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
    background-position: right 0.5rem center; background-repeat: no-repeat; background-size: 1.5em 1.5em;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.form-select:focus, select:focus { outline: none; border-color: var(--border-focus); box-shadow: 0 0 0 3px var(--ekh-cyan-15); }
select option { background: var(--surface-card); color: var(--text-primary); }
.validation-message { color: var(--color-danger); font-size: 0.8125rem; margin-top: 0.25rem; }

/* === TABLES === */
/* Waagerechtes Scrollen fuer breite Tabellen -- OHNE Media-Query.
   `.table-responsive` war bis 2026-08-25 ausschliesslich im 768px-Block definiert: 14
   Komponenten trugen die Klasse und bekamen oberhalb davon nichts. Auf dem Wandpanel
   (1024x768) lief deshalb die Seite seitlich aus dem Bild statt der Tabelle. */
.table-container,
.table-responsive { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.table { width: 100%; border-collapse: collapse; }
.table th, .table td { padding: 0.75rem 1rem; text-align: left; border-bottom: 1px solid var(--border-default); }
.table th { background: var(--surface-sunken); font-weight: 600; color: var(--text-body); font-size: 0.8125rem; text-transform: uppercase; letter-spacing: 0.05em; }
.table tbody tr:hover { background: var(--surface-sunken); }
.table-striped tbody tr:nth-child(even) { background: #fafbfc; }
.table-success, .table tbody tr.table-success { background-color: var(--color-success-light) !important; }
.table-danger, .table tbody tr.table-danger { background-color: var(--color-danger-light) !important; }
.table-warning, .table tbody tr.table-warning { background-color: var(--color-warning-light) !important; }

/* === BADGES === */

/* === TERMINAL DISPLAY (global — JS generates DOM without Blazor scope) === */
.term-terminal-display,
.terminal-display {
    background: var(--terminal-bg);
}

.term-terminal-wrapper,
.terminal-wrapper {
    font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
    color: var(--terminal-text);
}

.term-terminal-text { background: var(--terminal-bg); }
.term-terminal-text pre { color: var(--terminal-text); }

.terminal-cursor { background: var(--terminal-text); }

/* Character attributes (created by terminal.js) */
.char-normal { color: var(--terminal-text); background: transparent; }
.char-inverse { color: var(--terminal-bg); background: var(--terminal-text); }
.char-red { color: #ff4444; background: transparent; }
.char-red-inverse { color: var(--terminal-bg); background: #ff4444; }
.char-byte { color: var(--terminal-text); }

/* Canvas area (P2xxx) */
.term-canvas-area { background: var(--terminal-bg); }
.term-canvas-area canvas { background: var(--terminal-bg); }

/* Sidebar bottom wrapper — pushes info + theme to bottom */
.term-sidebar-bottom {
    margin-top: auto;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}
.term-sidebar-info {
    padding: 0.5rem 0.6rem;
    background: rgba(0, 0, 0, 0.03);
    border: 1px solid var(--border-light, #e2e8f0);
    border-radius: var(--radius);
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    font-size: 0.85rem;
}
.term-sidebar-info .term-info-status { font-size: 0.85rem; }
.term-sidebar-info .term-info-item { font-size: 0.85rem; }

/* Terminal theme toggle (collapsible at sidebar bottom) */
.term-theme-toggle {
    display: flex; align-items: center; justify-content: space-between;
    padding: 0.5rem 0.6rem;
    background: #e5f2f6; border: 1px solid #d0e8ef;
    border-radius: var(--radius); cursor: pointer; user-select: none;
    font-size: 0.8rem; font-weight: 600; color: var(--color-primary);
    transition: all 0.2s;
}
.term-theme-toggle:hover { background: #d6ecf2; color: var(--color-primary-hover); }
.term-theme-chevron { font-size: 0.7rem; transition: transform 0.25s ease; }
.term-theme-chevron.open { transform: rotate(90deg); }
.term-theme-panel {
    max-height: 0; overflow: hidden; opacity: 0;
    transition: max-height 0.3s ease, opacity 0.25s ease, margin 0.3s ease;
    margin-top: 0;
}
.term-theme-panel.open {
    max-height: 500px; opacity: 1; margin-top: 0.5rem;
}

/* Saved themes (rendered by JS, no Blazor scope) */
.term-theme-grid {
    display: grid; grid-template-columns: 1fr 1fr; gap: 0.4rem;
}
.term-theme-btn {
    display: flex; align-items: center; gap: 0.4rem;
    padding: 0.35rem 0.5rem; background: var(--surface-card);
    border: 1.5px solid var(--border-default); border-radius: var(--radius);
    cursor: pointer; font-size: 0.75rem; color: var(--text-secondary); transition: all 0.15s;
}
.term-theme-btn:hover { border-color: var(--color-primary); background: var(--surface-hover); }
.term-theme-btn.active { border-color: var(--color-primary); background: var(--color-primary-10); color: var(--text-primary); font-weight: 600; }
.term-theme-preview {
    display: inline-flex; align-items: center; justify-content: center;
    width: 24px; height: 20px; border-radius: var(--radius-xs);
    font-family: 'Consolas', monospace; font-size: 11px; font-weight: 700;
    flex-shrink: 0; border: 1px solid rgba(0,0,0,0.15);
}
.term-saved-theme {
    display: flex; align-items: center; gap: 0.3rem;
    padding: 0.25rem 0.4rem; background: var(--surface-card);
    border: 1.5px solid var(--border-default); border-radius: var(--radius);
    margin-bottom: 0.3rem; cursor: pointer; transition: all 0.15s;
}
.term-saved-theme:hover { border-color: var(--color-primary); background: var(--surface-hover); }
.term-saved-theme.active { border-color: var(--color-primary); background: var(--color-primary-10); }
.term-saved-name { flex: 1; font-size: 0.72rem; color: var(--text-body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.term-saved-delete {
    width: 18px; height: 18px; display: flex; align-items: center; justify-content: center;
    background: none; border: none; border-radius: var(--radius-xs); color: var(--text-muted);
    cursor: pointer; font-size: 11px; flex-shrink: 0; padding: 0;
}
.term-saved-delete:hover { background: rgba(220,38,38,0.1); color: var(--color-danger); }
.term-saved-rename {
    width: 18px; height: 18px; display: flex; align-items: center; justify-content: center;
    background: none; border: none; border-radius: var(--radius-xs); color: var(--text-muted);
    cursor: pointer; font-size: 11px; flex-shrink: 0; padding: 0;
}
.term-saved-rename:hover { background: var(--color-primary-10); color: var(--color-primary); }

/* Komponenten-Editor sidebar splitter: keep the col-resize cursor while dragging even when the
   pointer is over the canvas (global — scoped component CSS can't target <body>). */
body.vke-col-resizing,
body.vke-col-resizing * {
    cursor: col-resize !important;
    user-select: none !important;
}
.term-custom-save {
    padding: 0.3rem 0.6rem; font-size: 0.75rem; background: var(--surface-hover);
    border: 1px solid var(--border-default); border-radius: var(--radius);
    cursor: pointer; color: var(--text-body); transition: all 0.15s;
}
.term-custom-save:hover:not(:disabled) { background: var(--color-primary-10); border-color: var(--color-primary); }
.badge { display: inline-flex; align-items: center; padding: 0.25rem 0.625rem; font-size: 0.75rem; font-weight: 600; border-radius: var(--radius-full); }
.badge-primary { background: var(--color-primary-10); color: var(--color-primary); }
.badge-success { background: var(--color-success-10); color: var(--color-success); }
.badge-warning { background: var(--color-warning-10); color: var(--color-warning-dark); }
.badge-danger { background: var(--color-danger-10); color: var(--color-danger); }
.badge-secondary { background: rgba(100,116,139,0.1); color: var(--text-secondary); }
.badge-info { background: var(--color-primary-10); color: var(--color-info); }

.status-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 0.5rem; }
.status-dot.online { background: var(--color-success); }
.status-dot.offline { background: var(--text-secondary); }
.status-dot.warning { background: var(--color-warning); }
.status-dot.error { background: var(--color-danger); }

/* EkIcon — inline SVG icon atom. Sizes to the surrounding font-size (1em) and
   inherits currentColor, so it drops in where an emoji glyph used to sit but
   renders on any browser/panel without an emoji font. */
.ek-icon { width: 1em; height: 1em; display: inline-block; vertical-align: -0.15em; flex-shrink: 0; }

/* === ALERTS === */
/* Meldungsband. Traegt seit 2026-08-26 Tokens statt Literalen und ist die Grundlage von
   <EkAlert>. Die dunklen -dark-Toene sind die SCHRIFT: die Signalfarben selbst
   (--color-success/-danger/-warning) erreichen auf hellem Grund keine 4,5:1. */
.alert { padding: 1rem; border-radius: var(--radius); margin-bottom: 1rem; }
.alert-success { background: var(--color-success-light); border: 1px solid var(--color-success); color: var(--color-success-dark); }
.alert-danger { background: var(--color-danger-light); border: 1px solid var(--color-danger); color: var(--color-danger-dark); }
.alert-warning { background: var(--color-warning-light); border: 1px solid var(--color-warning); color: var(--color-warning-dark); }
.alert-info { background: var(--color-primary-light); border: 1px solid var(--color-info); color: var(--color-info-dark); }

/* Die Fassung von <EkAlert>: Text links, Schliesskreuz rechts. */
.alert-ek { display: flex; align-items: flex-start; gap: 0.75rem; }
.alert-ek-text { flex: 1; min-width: 0; }
.alert-ek-close {
    flex-shrink: 0; background: none; border: none; cursor: pointer;
    color: inherit; font-size: 1rem; line-height: 1; padding: 0.125rem 0.25rem;
    border-radius: var(--radius-sm); opacity: 0.7;
    min-width: var(--size-touch-sm); min-height: var(--size-touch-sm);
}
.alert-ek-close:hover, .alert-ek-close:focus-visible { opacity: 1; background: rgba(0,0,0,0.06); }

/* === TABS === */
.tabs { display: flex; border-bottom: 1px solid var(--border-default); margin-bottom: 1.5rem; }
.tab { padding: 0.75rem 1.5rem; font-weight: 500; color: var(--text-secondary); border: none; border-bottom: 2px solid transparent; background: transparent; cursor: pointer; transition: all 0.2s ease; font-size: 1rem; }
.tab:hover { color: var(--text-primary); }
.tab.active { color: var(--color-primary); border-bottom-color: var(--color-primary); }

/* === MODAL === */
.modal-backdrop { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: var(--surface-overlay); z-index: 1000; }
.modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%,-50%); background: var(--surface-card); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); width: 100%; max-width: 500px; max-height: 90vh; overflow-y: auto; z-index: 1001; }
.modal-header { padding: 1rem 1.5rem; border-bottom: 1px solid var(--border-default); display: flex; justify-content: space-between; align-items: center; }
.modal-header h3 { margin: 0; font-size: 1.125rem; }
.modal-close { background: none; border: none; font-size: 1.5rem; cursor: pointer; color: var(--text-secondary); padding: 0; line-height: 1; }
.modal-body { padding: 1.5rem; }
.modal-footer { padding: 1rem 1.5rem; border-top: 1px solid var(--border-default); display: flex; justify-content: flex-end; gap: 0.75rem; }

/* === SHARED .ct-modal* chrome (used by NotizEditModal, NotizTemplate*Modal,
   NotizStatusModal, NotizSendEmailModal — kept global because Blazor scoped
   CSS would otherwise only match elements rendered by ONE component, leaving
   sibling modals unstyled and falling into page flow). */
.ct-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    z-index: var(--z-shell-modal-backdrop);
    backdrop-filter: blur(1px);
}

.ct-modal {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: var(--z-shell-modal);
    width: min(92vw, 680px);
    max-height: 90vh;
    background: var(--surface-card, #fff);
    border-radius: 0.6rem;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.28);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    color: var(--text-body, #212529);
}

.ct-modal-header {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 1rem 3.2rem 1rem 1.25rem;
    border-bottom: 1px solid var(--border-default, #d0e8ef);
    background: var(--surface-hover, #f4f8fb);
    flex-shrink: 0;
}

.ct-modal-header h3 {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--text-primary, #212529);
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ct-modal-close {
    position: absolute;
    top: 50%;
    right: 0.9rem;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0;
    border: 1px solid var(--border-default, #ced4da);
    border-radius: 50%;
    background: #ffffff;
    color: #495057;
    font-family: "Arial", sans-serif;
    font-size: 1.35rem;
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
    transition: background-color 0.15s, color 0.15s, border-color 0.15s, transform 0.1s, box-shadow 0.15s;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}
.ct-modal-close:hover {
    background: #dc3545;
    color: #ffffff;
    border-color: #dc3545;
    transform: translateY(-50%) scale(1.08);
    box-shadow: 0 2px 6px rgba(220, 53, 69, 0.35);
}
.ct-modal-close:active {
    background: #bb2d3b;
    transform: translateY(-50%) scale(0.98);
}
.ct-modal-close:focus-visible {
    outline: 2px solid var(--color-primary, #1E94BC);
    outline-offset: 2px;
}

.ct-modal-body {
    padding: 1rem 1.25rem;
    overflow-y: auto;
    flex: 1;
    min-height: 0;
}

.ct-modal-footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.5rem;
    padding: 0.85rem 1.25rem;
    border-top: 1px solid var(--border-default, #d0e8ef);
    background: var(--surface-hover, #f4f8fb);
    flex-shrink: 0;
    flex-wrap: wrap;
}

/* Dialogs built by wwwroot/js/ek-modal.js. These MUST stay in this non-scoped sheet:
   JS-created elements carry no Blazor scope attribute, so a .razor.css rule would
   never match them. The Blazor twin (EkConfirmModal) styles its own markup. */
.ek-modal {
    width: min(92vw, 30rem);
}

.ek-modal-message {
    margin: 0;
    white-space: pre-line; /* the replaced popups passed multi-line German text with \n */
    color: var(--text-body);
}

.ek-modal-label {
    display: block;
    margin: 0.75rem 0 0.25rem;
    font-weight: 600;
    color: var(--text-primary);
}

.ek-modal-label .form-control {
    margin-top: 0.25rem;
    font-weight: 400;
}

/* === UTILITIES === */
.grid { display: grid; gap: 1.5rem; }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 1024px) { .grid-4, .grid-3 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .grid-4, .grid-3, .grid-2 { grid-template-columns: 1fr; } }

.flex { display: flex; } .flex-col { flex-direction: column; } .items-center { align-items: center; }
.justify-between { justify-content: space-between; } .justify-end { justify-content: flex-end; }
.gap-1 { gap: 0.25rem; } .gap-2 { gap: 0.5rem; } .gap-3 { gap: 0.75rem; } .gap-4 { gap: 1rem; }
.mt-1 { margin-top: 0.25rem; } .mt-2 { margin-top: 0.5rem; } .mt-3 { margin-top: 0.75rem; } .mt-4 { margin-top: 1rem; }
.mb-1 { margin-bottom: 0.25rem; } .mb-2 { margin-bottom: 0.5rem; } .mb-3 { margin-bottom: 0.75rem; } .mb-4 { margin-bottom: 1rem; }
.p-4 { padding: 1rem; } .p-6 { padding: 1.5rem; }
.text-center { text-align: center; } .text-right { text-align: right; }
.text-sm { font-size: 0.875rem; } .text-xs { font-size: 0.75rem; } .text-lg { font-size: 1.125rem; } .text-xl { font-size: 1.25rem; } .text-2xl { font-size: 1.5rem; }
.font-bold { font-weight: 700; } .font-semibold { font-weight: 600; }
.text-muted { color: var(--text-secondary); }
.text-danger { color: var(--color-danger); }
.text-warning { color: var(--color-warning); }
/* === HILFSKLASSEN, DIE ES NIE GAB (2026-08-27) ============================================
   Nach demselben Muster wie der Raster-Nachbau darueber: **947 Vorkommen in 83 Dateien**
   benutzen Bootstrap-Hilfsklassen, die diese Anwendung nie ausgeliefert hat. Sie standen im
   Markup und taten NICHTS. Die groessten:

       text-end   424x in 46 Dateien   -> rechtsbuendige Spalten, die linksbuendig blieben
       small      158x in 28 Dateien   -> "kleiner Text" in normaler Groesse
       mb-0        92x in 24 Dateien   -> Absatz mit vollem Abstand darunter
       me-2/-1     42x in 20 Dateien   -> Symbol klebte an seiner Beschriftung

   ⚠ WARUM DEFINIEREN UND NICHT 947 STELLEN UMSCHREIBEN. Das Markup sagt bereits, was gemeint
   ist; kaputt war nur, dass niemand es umgesetzt hat. Eine Sammeländerung an 947 Stellen
   waere 947 Gelegenheiten, die Absicht zu verfehlen — das Umsetzen ist EINE Stelle und laesst
   sich in EINEM Schritt zuruecknehmen.

   ⚠ DIE SKALA IST DIE DES HAUSES, NICHT DIE VON BOOTSTRAP. Oben steht bereits
   `.mt-3 { 0.75rem }` — Bootstrap meint damit 1rem. Wer hier Bootstrap-Werte einsetzt, macht
   `mt-3` und `py-3` zu zwei verschiedenen Abstaenden. Die Zahl ist der Schritt der
   --space-Skala.

   ⚠ DAS AENDERT DAS AUSSEHEN von 83 Dateien — und zwar in Richtung dessen, was dort immer
   stand. Wo eine Seite bisher gut aussah, WEIL die Klasse wirkungslos war, sieht sie jetzt
   anders aus. Die Bildgrundlinien decken einen Teil davon ab; der Rest ist bewusst in Kauf
   genommen.

   UtilityClassConventionTests haelt fest, dass keine weitere solche Klasse dazukommt. */

/* Abstaende — Aussen */
.m-0 { margin: 0; }
.m-2 { margin: var(--space-2); }
.m-3 { margin: var(--space-3); }
.mt-0 { margin-top: 0; }
.mb-0 { margin-bottom: 0; }
.my-2 { margin-top: var(--space-2); margin-bottom: var(--space-2); }
.my-4 { margin-top: var(--space-4); margin-bottom: var(--space-4); }
.ms-1 { margin-left: var(--space-1); }
.ms-2 { margin-left: var(--space-2); }
.ms-3 { margin-left: var(--space-3); }
.me-1 { margin-right: var(--space-1); }
.me-2 { margin-right: var(--space-2); }
.me-3 { margin-right: var(--space-3); }

/* Abstaende — Innen */
.p-0 { padding: 0; }
.p-1 { padding: var(--space-1); }
.p-2 { padding: var(--space-2); }
.p-3 { padding: var(--space-3); }
.px-2 { padding-left: var(--space-2); padding-right: var(--space-2); }
.px-3 { padding-left: var(--space-3); padding-right: var(--space-3); }
.py-1 { padding-top: var(--space-1); padding-bottom: var(--space-1); }
.py-2 { padding-top: var(--space-2); padding-bottom: var(--space-2); }
.py-3 { padding-top: var(--space-3); padding-bottom: var(--space-3); }
.py-4 { padding-top: var(--space-4); padding-bottom: var(--space-4); }
.py-5 { padding-top: var(--space-5); padding-bottom: var(--space-5); }

/* Ausrichtung und Schrift.
   `text-end` ist mit Abstand die haeufigste: 424 Zahlenspalten, die rechtsbuendig sein
   sollten und es nie waren. */
.text-end { text-align: right; }
.text-start { text-align: left; }
.small { font-size: 0.875em; }
.fw-bold { font-weight: var(--font-weight-bold); }
.fw-semibold { font-weight: var(--font-weight-semibold); }
.text-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.text-decoration-none { text-decoration: none; }

/* Textfarben — ausschliesslich ueber Tokens. */
.text-primary { color: var(--color-primary); }
.text-success { color: var(--color-success); }
.text-info { color: var(--color-info); }
.text-dark { color: var(--text-primary); }
.text-light { color: var(--gray-100); }
.text-white { color: var(--text-on-primary); }

/* Rahmen */
.border { border: 1px solid var(--border-default); }
.border-primary { border-color: var(--color-primary); }
.border-secondary { border-color: var(--border-strong); }
.border-success { border-color: var(--color-success); }
.border-info { border-color: var(--color-info); }
.border-danger { border-color: var(--color-danger); }
.rounded { border-radius: var(--radius); }

/* Anzeige und Fluss */
.d-inline-block { display: inline-block; }
.flex-column { flex-direction: column; }
.align-self-center { align-self: center; }
.h-100 { height: 100%; }
.overflow-auto { overflow: auto; }

/* === RASTER-NACHBAU (2026-08-25) ==========================================================
   40 Dateien (348 Vorkommen) benutzen Bootstrap-Klassennamen -- `row`, `col-md-6`, `d-flex`,
   `g-2`, `btn-group`. Diese Anwendung liefert KEIN Bootstrap aus: bis heute waren alle diese
   Klassen undefiniert, jedes "zweispaltige Formular" stand also seit jeher untereinander,
   bei JEDER Bildbreite. Das war kein Responsive-Problem, sondern ein Layout, das nie existiert
   hat.

   Bewusst KEIN originalgetreuer 12-Spalten-Nachbau: `flex: 0 0 25%` haette ein `col-md-3` auch
   auf dem Telefon auf ein Viertel gequetscht. Stattdessen wird die Spaltenzahl als
   MINDESTBREITE gelesen -- nebeneinander, solange Platz ist, sonst umgebrochen. Damit sind die
   40 Dateien in einem Schritt brauchbar, ohne jede einzeln anzufassen.

   Der Ratchet BootstrapGridConventionTests haelt die Liste bei diesen 40 Dateien fest: neue
   Vorkommen fallen durch, die vorhandenen werden im Vorbeigehen abgeloest. */

.row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
}
/* Bootstraps Rinnenbreiten, hier schlicht als Abstand. */
.row.g-0 { gap: 0; }
.row.g-1 { gap: var(--space-1); }
.row.g-2 { gap: var(--space-2); }
.row.g-3 { gap: var(--space-3); }

/* Ohne Zahl: alle Geschwister teilen sich den Platz gleichmaessig.
   min-width: 0 ist tragend -- ohne das waechst eine Spalte auf ihren laengsten Inhalt
   (Standard min-width:auto) und sprengt die Zeile, statt umzubrechen. */
.row > .col,
.row > [class*="col-"] { min-width: 0; }
.row > .col { flex: 1 1 0; }

/* Die Spaltenzahl als Mindestbreite. min(100%, X) sorgt dafuer, dass eine Spalte in einem
   schmalen Behaelter trotzdem die volle Breite bekommt statt ueberzulaufen. */
.col-1,  .col-sm-1,  .col-md-1,  .col-lg-1,  .col-xl-1  { flex: 1 1 min(100%, 5rem); }
.col-2,  .col-sm-2,  .col-md-2,  .col-lg-2,  .col-xl-2  { flex: 1 1 min(100%, 8rem); }
.col-3,  .col-sm-3,  .col-md-3,  .col-lg-3,  .col-xl-3  { flex: 1 1 min(100%, 11rem); }
.col-4,  .col-sm-4,  .col-md-4,  .col-lg-4,  .col-xl-4  { flex: 1 1 min(100%, 14rem); }
.col-5,  .col-sm-5,  .col-md-5,  .col-lg-5,  .col-xl-5  { flex: 1 1 min(100%, 17rem); }
.col-6,  .col-sm-6,  .col-md-6,  .col-lg-6,  .col-xl-6  { flex: 1 1 min(100%, 20rem); }
.col-7,  .col-sm-7,  .col-md-7,  .col-lg-7,  .col-xl-7  { flex: 1 1 min(100%, 24rem); }
.col-8,  .col-sm-8,  .col-md-8,  .col-lg-8,  .col-xl-8  { flex: 1 1 min(100%, 28rem); }
.col-9,  .col-sm-9,  .col-md-9,  .col-lg-9,  .col-xl-9  { flex: 1 1 min(100%, 32rem); }
.col-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xl-10 { flex: 1 1 min(100%, 36rem); }
.col-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xl-11 { flex: 1 1 min(100%, 40rem); }
/* Zwoelf von zwoelf heisst: eigene Zeile. */
.col-12, .col-sm-12, .col-md-12, .col-lg-12, .col-xl-12 { flex: 1 1 100%; }

/* `container`/`container-fluid` absichtlich OHNE max-width: heute tun sie nichts, und eine
   Lesebreite einzufuehren wuerde bestehende Seiten schmaler machen, ohne dass es jemand
   verlangt hat. Eine Lesebreite gehoert -- falls gewollt -- in einer eigenen Aenderung an
   `.content`. */
.container, .container-fluid { width: 100%; }

/* Anzeige- und Ausrichtungs-Helfer, die im Markup vorkommen. */
.d-flex { display: flex; }
.d-block { display: block; }
.d-none { display: none; }
.flex-wrap { flex-wrap: wrap; }
.align-items-start { align-items: flex-start; }
.align-items-center { align-items: center; }
.align-items-end { align-items: flex-end; }
.align-items-baseline { align-items: baseline; }
.justify-content-center { justify-content: center; }
.justify-content-between { justify-content: space-between; }
.justify-content-end { justify-content: flex-end; }
.ms-auto { margin-left: auto; }
.me-auto { margin-right: auto; }

/* Zusammenhaengende Schaltergruppe -- umbruchfaehig, damit sie auf dem Telefon nicht
   herauslaeuft. */
.btn-group { display: inline-flex; flex-wrap: wrap; gap: var(--space-1); }

/* === LOGIN === */
.login-content { padding: 0; flex: 1; display: flex; flex-direction: column; }
.content:has(> .login-page) { padding: 0; display: flex; flex-direction: column; }
.login-page { flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; background: var(--surface-card); }
.login-card { background: var(--surface-card); border-radius: var(--radius-lg); box-shadow: 0 8px 40px rgba(0,0,0,0.08); width: 100%; max-width: 420px; padding: 2.5rem; border: 1px solid var(--border-default); flex-shrink: 0; }
.login-header { text-align: center; margin-bottom: 2rem; }
.login-header h1 { font-size: 1.75rem; margin: 0 0 0.5rem; font-weight: 700; }
.login-header p { color: var(--text-secondary); margin: 0; }
.login-logo-img { max-width: 280px; width: 100%; height: auto; margin-bottom: 1rem; }

/* === HEIZUNG CARDS === */
.heizung-card { background: var(--surface-card); border-radius: var(--radius-lg); box-shadow: var(--shadow); border: 1px solid var(--border-default); padding: 1.5rem; transition: box-shadow 0.2s ease; }
.heizung-card:hover { box-shadow: var(--shadow-md); }
.heizung-card .heizung-name { font-weight: 600; font-size: 1.125rem; margin: 0 0 0.25rem; }
.heizung-card .heizung-location { color: var(--text-secondary); font-size: 0.875rem; }

.heizung-card-new { background: var(--surface-card); border: 1px solid var(--border-default); border-radius: var(--radius-lg); padding: 1.25rem; margin-bottom: 1rem; box-shadow: var(--shadow); transition: box-shadow 0.2s, border-color 0.2s; }
.heizung-card-new:hover { box-shadow: var(--shadow-md); border-color: var(--color-primary); }
.heizung-card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 1rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--border-default); gap: 0.75rem; }
.heizung-header-left { display: flex; flex-direction: column; gap: 0.25rem; min-width: 0; }
.heizung-header-right { display: flex; flex-direction: column; align-items: flex-end; gap: 0.25rem; white-space: nowrap; flex-shrink: 0; }
.heizung-title { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.heizung-title h3 { margin: 0; font-size: 1.25rem; font-weight: 600; }
.button-group { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.status-indicator { display: inline-flex; align-items: center; gap: 0.4rem; font-weight: 600; font-size: 0.8rem; }
.status-online { color: var(--color-success); }
.status-offline { color: var(--color-danger); }
/* Halo fuer die dichte Anlagenliste — ein ZUSATZ zum kanonischen .status-dot, keine
   zweite Sprache.

   Vorher gab es hier .dot-online/.dot-offline: eigene Klassen mit eigenen Farben, und
   .dot-offline war ROT, waehrend .status-dot.offline im Rest des Systems GRAU ist. Auf der
   meistgenutzten Seite bedeutete derselbe Punkt damit etwas anderes als ueberall sonst. Der
   Unterschied ist echt — „nicht eingerichtet" ist nicht „sollte laufen, laeuft aber nicht" —
   nur trug er den falschen Namen: das ist .status-dot.error. */
.status-dot.glow.online { box-shadow: 0 0 6px var(--glow-success); }
.status-dot.glow.error { box-shadow: 0 0 6px var(--glow-danger); }

/* Rollen-Abzeichen der Organisationsverwaltung. Kategorial, nicht wertend — deshalb eine
   eigene Token-Gruppe und nicht --color-success. Stand bis 2026-08-26 wortgleich in zwei
   inline-<style>-Bloecken (Members.razor und Dashboard.razor). */
.role-badge { padding: 0.15rem 0.5rem; border-radius: var(--radius-sm); font-size: 0.75rem; font-weight: 600; color: var(--text-on-primary); }
.role-owner { background: var(--role-owner); }
.role-admin { background: var(--role-admin); }
.role-member { background: var(--role-member); }

.temp-display { display: flex; align-items: baseline; gap: 0.25rem; }
.temp-value { font-size: 2rem; font-weight: 700; }
.temp-unit { font-size: 1rem; color: var(--text-secondary); }
.measurement-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; }
.measurement-item { background: var(--surface-sunken); border-radius: var(--radius); padding: 1rem; text-align: center; border: 1px solid var(--border-default); }
.measurement-item .label { font-size: 0.75rem; color: var(--text-secondary); margin-bottom: 0.5rem; }
.measurement-item .value { font-size: 1.5rem; font-weight: 600; }

.fault-list { display: flex; flex-direction: column; gap: 0.75rem; }
.fault-item { display: flex; align-items: flex-start; gap: 1rem; padding: 1rem; background: var(--surface-sunken); border-radius: var(--radius); border-left: 4px solid var(--border-default); }
.fault-item.kritisch { border-left-color: var(--color-danger); }
.fault-item.hoch { border-left-color: var(--color-warning); }

.spinner { width: 40px; height: 40px; border: 3px solid var(--border-default); border-top-color: var(--color-primary); border-radius: 50%; animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.loading-container { display: flex; align-items: center; justify-content: center; padding: 3rem; }
.empty-state { text-align: center; padding: 3rem; color: var(--text-secondary); }

.password-wrapper { position: relative; display: flex; align-items: center; }
.password-wrapper input { padding-right: 45px !important; width: 100%; }
.password-toggle { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); background: transparent; border: none; cursor: pointer; padding: 4px 8px; font-size: 1.1rem; color: var(--text-secondary); opacity: 0.7; }
.password-toggle:hover { opacity: 1; }

.user-list-item { transition: background-color 0.15s ease; }
.user-list-item:hover { background-color: var(--surface-hover); }
.user-list-item.selected { background-color: var(--color-success-light) !important; border-left: 4px solid var(--color-success); }

.dropdown-container { position: relative; display: inline-block; }
.dropdown-menu { display: none; position: absolute; top: 100%; left: 0; z-index: 1000; min-width: 200px; padding: 0.5rem 0; background-color: var(--surface-card); border: 1px solid var(--border-default); border-radius: var(--radius-md); box-shadow: var(--shadow-lg); }
.dropdown-menu.show { display: block; }
.dropdown-item { display: block; width: 100%; padding: 0.5rem 1rem; text-align: left; background: none; border: none; cursor: pointer; font-size: 0.9rem; color: var(--text-primary); transition: background-color 0.15s; }
.dropdown-item:hover { background-color: var(--surface-hover); color: var(--text-primary); }

.search-box { position: relative; max-width: 500px; margin-bottom: 1rem; }
.search-box input { width: 100%; padding: 0.75rem 2.5rem 0.75rem 2.5rem; font-size: 1rem; border: 2px solid var(--border-default); border-radius: var(--radius-md); background-color: var(--surface-card); transition: border-color 0.2s, box-shadow 0.2s; }
.search-box input:focus { outline: none; border-color: var(--color-primary); box-shadow: 0 0 0 3px var(--ekh-cyan-15); }
.search-box input::placeholder { color: var(--text-secondary); }
.search-box .search-icon { position: absolute; left: 0.75rem; top: 50%; transform: translateY(-50%); font-size: 1.1rem; pointer-events: none; }
.search-box .clear-btn { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); background: transparent; border: none; color: var(--text-secondary); cursor: pointer; padding: 4px 8px; font-size: 1.1rem; opacity: 0.6; transition: opacity 0.2s; }
.search-box .clear-btn:hover { opacity: 1; color: var(--color-primary); }
.search-box .search-shortcut-hint { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); font-size: 0.72rem; color: var(--text-secondary); background: var(--surface-hover, rgba(0,0,0,0.04)); border: 1px solid var(--border-default); border-radius: var(--radius-sm); padding: 2px 6px; pointer-events: none; user-select: none; opacity: 0.7; }

/* Connection modal — shared between HeizungenListe (navigation) and HeizungConsole (TCP connect) */
.connection-modal-overlay { position: fixed; inset: 0; z-index: var(--z-shell-overlay); background: rgba(0,0,0,0.85); display: flex; align-items: center; justify-content: center; animation: modalFadeIn 0.3s ease-out; }
@keyframes modalFadeIn { from { opacity: 0; } to { opacity: 1; } }
.connection-modal-card { text-align: center; padding: 2.5rem 3.5rem; background: var(--surface-card); border: 1px solid var(--border-strong); border-radius: var(--radius-xl); box-shadow: 0 12px 48px rgba(0,0,0,0.5); min-width: 360px; max-width: 480px; }
.connection-modal-spinner { width: 56px; height: 56px; border: 4px solid var(--border-strong); border-top: 4px solid var(--color-primary); border-radius: 50%; margin: 0 auto 1.5rem; animation: spin 1s linear infinite; }
.connection-modal-icon { width: 56px; height: 56px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 1.5rem; font-size: 1.5rem; font-weight: 700; }
.connection-modal-icon.error { background: rgba(239,68,68,0.15); color: #ef4444; border: 2px solid rgba(239,68,68,0.3); }
.connection-modal-title { color: var(--color-primary); font-size: 1.2rem; font-weight: 600; margin-bottom: 0.5rem; }
.connection-modal-title.error { color: #ef4444; }
.connection-modal-message { color: var(--text-primary); font-size: 0.95rem; margin-bottom: 0.3rem; }
.connection-modal-sub { color: var(--text-secondary); font-size: 0.85rem; opacity: 0.7; }
.connection-modal-error { margin-top: 1rem; padding: 0.75rem 1rem; background: rgba(239,68,68,0.08); border: 1px solid rgba(239,68,68,0.2); border-radius: var(--radius-md); color: var(--text-secondary); font-size: 0.85rem; font-family: 'Consolas', monospace; word-break: break-all; }
.connection-modal-fallback { margin-top: 1rem; padding: 0.6rem 1rem; background: rgba(234,179,8,0.1); border: 1px solid rgba(234,179,8,0.25); border-radius: var(--radius-md); color: #eab308; font-size: 0.85rem; font-weight: 500; }
.connection-modal-actions { margin-top: 1.5rem; display: flex; gap: 0.75rem; justify-content: center; }
.connection-modal-btn { padding: 0.6rem 1.25rem; border-radius: var(--radius-md); font-size: 0.9rem; font-weight: 500; cursor: pointer; border: 1px solid var(--border-strong); transition: all 0.15s ease; }
.connection-modal-btn.primary { background: var(--color-primary); color: white; border-color: var(--color-primary); }
.connection-modal-btn.primary:hover { opacity: 0.9; }
.connection-modal-btn.secondary { background: var(--surface-raised); color: var(--text-primary); }
.connection-modal-btn.secondary:hover { background: var(--surface-hover); }

/* Toast notifications (global — JS creates these on document.body) */
.toast-container { position: fixed; top: 16px; right: 16px; z-index: var(--z-shell-toast); display: flex; flex-direction: column; gap: 8px; }
.toast { padding: 10px 18px; border-radius: var(--radius-md); font-size: 0.85rem; color: var(--text-primary); box-shadow: 0 4px 12px rgba(0,0,0,0.4); animation: toastIn 0.3s ease; max-width: 350px; }
.toast-info { background: var(--color-primary); }
.toast-success { background: var(--color-success); }
.toast-warning { background: #b45309; }
.toast-error { background: var(--color-danger); }
.toast-fade { opacity: 0; transition: opacity 0.3s ease; }
@keyframes toastIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }

/* Theme Settings */
.theme-section { margin-bottom: 1rem; }
.theme-section h5 { font-size: 0.9rem; color: var(--text-secondary); margin: 0 0 0.5rem 0; font-weight: 500; }
.theme-btn { background: var(--surface-card); border: 1px solid var(--border-default); padding: 0.5rem; border-radius: var(--radius-md); cursor: pointer; font-size: 0.85rem; transition: all 0.2s; display: flex; align-items: center; justify-content: center; gap: 0.3rem; }
.theme-btn:hover { background: var(--surface-hover); border-color: var(--color-primary); }
.theme-btn.active { background: var(--color-primary-10); border-color: var(--color-primary); }

/* === RESPONSIVE === */

/* Hamburger — hidden on desktop, visible on mobile */
.hamburger-btn {
    display: none;
    background: none; border: none; font-size: 1.4rem; cursor: pointer;
    color: var(--text-primary); padding: 0.25rem 0.5rem; line-height: 1;
}
.top-bar-left { display: flex; align-items: center; gap: 0.75rem; }
/* Mobile sidebar scrim. Choreographed with the .sidebar slide
   (transform: translateX 0.3s ease at line 887) — both elements
   animate over the same 300ms ease window so the drawer arrives and
   the scrim darkens together. The visibility transition is delayed
   on close so the backdrop stays click-blocking until the fade
   finishes, and flipped to 0s on open so it picks up clicks
   immediately. Matches the in-codebase opacity-fade idiom already
   used by .toast-fade (line 839) and .term-theme-section (line 521).
   Do NOT revert to display: none/block — that pops in one frame and
   breaks the synchronized motion. */
.sidebar-backdrop {
    position: fixed; inset: 0;
    background: rgba(0,0,0,0.4); z-index: 99;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s ease, visibility 0s linear 0.3s;
}
.sidebar-backdrop.show {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.3s ease, visibility 0s linear 0s;
}

/* ── 768px: Sidebar collapses, hamburger appears ── */
/* Umbruch der Seitenleiste bei 1024px, nicht bei 768px.
   Grund: das Wandpanel misst 1024x768. Bis 2026-08-25 blieb dort die 290px-Leiste stehen
   (28% der Breite) UND die Tabellen-Rettung griff nicht, weil beides in derselben Abfrage
   stand -- das Zielgeraet bekam von beidem das Schlechtere.
   ACHTUNG: MainLayout.razor traegt dieselbe Schwelle im Skript (Schublade nach Klick
   schliessen). Beide Zahlen gehoeren im Gleichschritt geaendert. */
@media (max-width: 1024px) {
    .hamburger-btn { display: block; }
    .sidebar {
        transform: translateX(-100%);
        transition: transform 0.3s ease;
        z-index: 200;
        width: 280px;
    }
    .sidebar.open { transform: translateX(0); box-shadow: 4px 0 20px rgba(0,0,0,0.15); }
    .main-content { margin-left: 0; }
    .content { padding: 1rem; }
    .top-bar { padding: 0 0.75rem; }
    .top-bar .brand-name { font-size: 0.95rem; }

    /* Cards stack */
    .heizung-card-header { flex-direction: column; gap: 0.5rem; }
    .heizung-header-right { align-items: flex-start; }
    .heizung-title h3 { font-size: 1.1rem; }

    /* Unter 768px darf auch eine Tabelle OHNE Wrapper scrollen. `display:block` opfert
       dafuer die Spaltenausrichtung -- vertretbar auf dem Telefon, aber bewusst auf diese
       Breite beschraenkt. `.table-responsive` selbst wirkt inzwischen ueberall (s. o.). */
    table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }

    /* Admin content */
    .search-box { max-width: 100%; }

    /* Grid utilities */
    .grid-4, .grid-3, .grid-2 { grid-template-columns: 1fr; }

    /* Touch targets — guarantee a usable tap area on phones (WCAG aims
       for ~44 px; we land at ~40 px which is the practical minimum
       without making the UI feel chunky on tablets). */
    .btn, .btn-action {
        min-height: 40px;
    }
    .btn-sm {
        min-height: 36px;
        padding: 0.35rem 0.75rem;
    }
    .form-control, .form-select, select, input[type="text"],
    input[type="email"], input[type="password"], input[type="number"],
    input[type="date"], input[type="search"], textarea {
        min-height: 40px;
    }
    /* Checkboxes and radios stay native size — auto-enlarging them
       on mobile breaks alignment with their labels. */
}

/* === ANNOUNCEMENT BANNER === */
.announcement-banner { padding: 10px 16px; display: flex; align-items: center; gap: 12px; font-size: 1rem; width: 80%; max-width: 1200px; margin: 0 auto 0.75rem; border-radius: var(--radius-md); }
.announcement-banner-info { background: rgba(59,130,246,0.12); border: 1px solid rgba(59,130,246,0.25); color: var(--text-primary); }
.announcement-banner-warning { background: rgba(234,179,8,0.12); border: 1px solid rgba(234,179,8,0.25); color: var(--text-primary); }
.announcement-banner-error { background: rgba(239,68,68,0.12); border: 1px solid rgba(239,68,68,0.25); color: var(--text-primary); }
.announcement-banner-icon { flex-shrink: 0; font-size: 1rem; }
.announcement-banner-content { flex: 1; min-width: 0; }
.announcement-banner-dismiss { margin-left: auto; background: none; border: none; cursor: pointer; opacity: 0.5; font-size: 1rem; padding: 2px 6px; border-radius: var(--radius-sm); transition: opacity 0.2s, background 0.2s; flex-shrink: 0; color: var(--text-primary); }
.announcement-banner-dismiss:hover { opacity: 1; background: rgba(0,0,0,0.08); }

/* ── 480px: Phone ── */
@media (max-width: 480px) {
    .content { padding: 0.75rem; }
    .content h2 { font-size: 1.2rem; }
    .heizung-card-new { padding: 0.75rem; }
    .heizung-title { flex-wrap: wrap; }
    .heizung-title h3 { font-size: 1rem; }
    .btn, .btn-action { padding: 0.45rem 0.7rem; font-size: 0.82rem; min-height: 40px; }
    .top-bar { height: auto; min-height: var(--topbar-height); padding: 0.4rem 0.75rem; flex-wrap: wrap; gap: 0.25rem; }
    .alert-card { padding: 0.75rem; }
    .alert-value { font-size: 1.3rem; }
    .alert-icon { font-size: 1.5rem; }
    .dropdown-menu { min-width: 160px; }

    /* Stack modal action buttons full-width on phones — primary action
       lands at the bottom (column-reverse) where the thumb naturally
       sits. Targets every modal footer pattern used in the app, both
       global classes and the scoped ones in Heizungen / Visualisierung
       (scoped CSS rules add an attribute filter to selectors but global
       rules in app.css match by class regardless of scope). */
    .modal-footer,
    .ct-modal-footer,
    .modal-actions,
    .connection-modal-actions,
    .preset-modal-footer,
    .delete-modal-actions {
        flex-direction: column-reverse;
        align-items: stretch;
    }
    .modal-footer .btn,
    .ct-modal-footer .btn,
    .modal-actions .btn,
    .connection-modal-actions .btn,
    .preset-modal-footer .btn,
    .delete-modal-actions .btn {
        width: 100%;
    }
}

/* === A4 Wartungsbericht print view (ADR-0062 Briefpapier Phase 3) =============
   When the on-screen A4 preview (NotizA4Ansicht) is open and the user hits
   "Drucken", print ONLY the sheet — not the surrounding app chrome. A scoped
   component stylesheet cannot reach `body *`, so this lives here as a global rule.
   The :has() gate keeps it inert for every other page's printing (the rule only
   fires while the A4 print area is in the DOM). The classic visibility trick hides
   everything outside the print area while leaving layout intact. */
@media print {
    body:has(.a4-print-area) * {
        visibility: hidden;
    }

    body:has(.a4-print-area) .a4-print-area,
    body:has(.a4-print-area) .a4-print-area * {
        visibility: visible;
    }

    body:has(.a4-print-area) .a4-print-area {
        position: absolute;
        inset: 0;
        width: 100%;
        margin: 0;
        padding: 0;
    }

    /* Drop the modal chrome / scroll box so the sheet flows across printed pages. */
    body:has(.a4-print-area) .ct-modal-backdrop {
        display: none !important;
    }

    body:has(.a4-print-area) .a4-modal {
        position: static;
        transform: none;
        width: auto;
        max-height: none;
        border: 0;
        box-shadow: none;
        overflow: visible;
    }

    body:has(.a4-print-area) .ct-modal-header {
        display: none !important;
    }

    body:has(.a4-print-area) .a4-scroll {
        overflow: visible;
        background: #fff;
    }

    body:has(.a4-print-area) .a4-sheet {
        border: 0;
        box-shadow: none;
        max-width: none;
    }
}

/* ===== "Follow me" co-browsing (ADR-0093) ===== */

/* Presenter share modal — opened from the console "🤝 Teilen" tab button. Backdrop centres the card. */
.followme-modal-backdrop {
    position: fixed;
    inset: 0;
    background: var(--surface-overlay);
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-4);
}
.followme-share-panel {
    width: min(420px, calc(100vw - 40px));
    max-height: 85vh;
    overflow-y: auto;
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    padding: var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}
.followme-search { width: 100%; }
.followme-share-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-weight: 600;
    color: var(--text-primary);
}
.followme-share-close {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--text-secondary);
    font-size: 1rem;
    padding: 2px 6px;
    border-radius: var(--radius-sm);
}
.followme-share-close:hover { background: var(--surface-sunken); }
.followme-share-hint { margin: 0; color: var(--text-secondary); font-size: 0.9rem; }
.followme-share-end { align-self: flex-start; }
.followme-colleague-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    max-height: 240px;
    overflow-y: auto;
}
.followme-colleague-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    padding: var(--space-2);
    background: var(--surface-sunken);
    border-radius: var(--radius-sm);
}
.followme-colleague-name { color: var(--text-primary); font-size: 0.9rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.followme-empty { color: var(--text-secondary); font-size: 0.9rem; padding: var(--space-2); }

/* Quick-invite favorites (★, max 5) */
.followme-favorites {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding-bottom: var(--space-2);
    border-bottom: 1px solid var(--border-default);
}
.followme-favorites-label { font-size: 0.8rem; font-weight: 600; color: var(--text-secondary); }
.followme-row-actions { display: inline-flex; align-items: center; gap: var(--space-2); flex-shrink: 0; }
.followme-star {
    background: none;
    border: none;
    cursor: pointer;
    font-size: 1.1rem;
    line-height: 1;
    color: var(--text-secondary);
    padding: 2px 4px;
}
.followme-star.is-fav { color: var(--color-warning); }
.followme-star:disabled { opacity: 0.35; cursor: default; }

/* Presence: name + online dot on the left of a row */
.followme-colleague-label { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.followme-status-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.followme-status-dot.online { background: var(--color-success); }
.followme-status-dot.offline { background: var(--border-default); }

/* Incoming-invite prompt (modal) */
.followme-invite-backdrop {
    position: fixed;
    inset: 0;
    background: var(--surface-overlay);
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
}
.followme-invite-card {
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    padding: var(--space-5);
    width: min(420px, calc(100vw - 40px));
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}
.followme-invite-title { font-weight: 600; font-size: 1.1rem; color: var(--text-primary); }
.followme-invite-body { margin: 0; color: var(--text-secondary); }
.followme-invite-actions { display: flex; gap: var(--space-2); justify-content: flex-end; }

/* Follower banner (top of viewport) */
.followme-banner {
    position: fixed;
    top: 8px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1600;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 8px 16px;
    background: var(--color-primary);
    color: var(--text-on-primary);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    max-width: calc(100vw - 32px);
}
.followme-banner-dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--text-on-primary);
    animation: followme-pulse 1.4s ease-in-out infinite;
    flex-shrink: 0;
}
@keyframes followme-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
.followme-banner-text { font-size: 0.9rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.followme-banner-leave {
    margin-left: var(--space-2);
    background: rgba(255,255,255,0.18);
    border: 1px solid rgba(255,255,255,0.35);
    color: var(--text-on-primary);
    cursor: pointer;
    font-size: 0.85rem;
    padding: 3px 10px;
    border-radius: var(--radius-sm);
    flex-shrink: 0;
}
.followme-banner-leave:hover { background: rgba(255,255,255,0.3); }

/* Presenter "you are sharing" banner — red/live accent to distinguish it from the follower banner. */
.followme-presenter-banner { background: var(--color-danger); }

/* Nummern-Marker der Wartungsfahrt auf /dashboard/karte.
   Steht HIER und nicht im scoped Stylesheet der Seite: das Element wird von
   fleet-map.js erzeugt (Leaflet DivIcon) und traegt deshalb kein b-hash-Attribut —
   eine scoped Regel wuerde es nie treffen. */
.fleet-route-marker {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: var(--radius-full);
    background: var(--color-primary);
    color: var(--text-on-primary);
    font-size: 0.75rem;
    font-weight: 600;
    border: 2px solid var(--surface-card);
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
}
