/*!
 * Basis - Eigene Stylesheets
 * Wird NACH Bootstrap geladen, um gezielt zu erweitern (nicht zu ersetzen).
 *
 * Konventionen:
 *  - Eigene Klassen mit Praefix .app-...
 *  - Bootstrap Utility-Klassen bevorzugen, hier nur das ergaenzen,
 *    was Utilities nicht abdecken (z. B. Layout-Shell, Branding-Slots).
 *
 * @author Graup-IT <info@graup-it.de>
 * @since  0.0.1
 */

/* --- Layout-Shell: zentriert ab grossen Monitoren -------------------------- */
/* vorgaben.md: Desktop-Referenz 1920x1080, auf groesseren Monitoren zentriert. */
/* Der Shell-Container hat einen eigenen Hintergrund (var(--bs-body-bg)),
   damit ein optionaler "Aussen-Hintergrund" (Branding-Farbe oder Bild auf
   <body>) nur LINKS und RECHTS der App sichtbar ist — und nicht durch den
   App-Inhalt durchscheint. */
.app-shell {
    width: 100%;
    max-width: 1920px;
    margin-inline: auto;
    padding-inline: 1rem;
    background-color: var(--bs-body-bg);
}

@media (min-width: 992px) {
    .app-shell {
        padding-inline: 1.5rem;
    }
}

/* --- Header / Footer Slots ------------------------------------------------- */
/* Aeusserer <header> / <footer> bleibt transparent — damit Background-Farbe
   und Border NICHT über die ganze Bildschirmbreite gehen, sondern nur
   in der App-Shell-Breite (max. 1920 px). Auf grossen Monitoren ist neben
   der App damit der Outer-Hintergrund (Body-bg/Image) sichtbar. */
.app-header,
.app-footer {
    background-color: transparent;
}
/* Innerer .app-shell-Container traegt den eigentlichen Background sowie
   Border-Bottom/Top — die ueberschreibbaren Branding-Settings (in
   Branding::cssVariables()) zielen ebenfalls auf diesen Container. */
.app-header > .app-shell {
    background-color: var(--bs-body-bg);
    border-bottom: 1px solid var(--bs-border-color);
}
.app-footer > .app-shell {
    background-color: var(--bs-body-bg);
    border-top: 1px solid var(--bs-border-color);
}

.app-brand-logo {
    object-fit: contain;
    flex-shrink: 0;
}

/* Multi-Upload-Dropzone */
.app-dropzone {
    border: 2px dashed var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    padding: 1.25rem;
    text-align: center;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}
.app-dropzone:hover { background: var(--bs-tertiary-bg); }
.app-dropzone.is-dragover {
    border-color: var(--bs-primary);
    background: rgba(var(--bs-primary-rgb), 0.08);
}
.app-dropzone input[type="file"] {
    display: none;
}
.app-dropzone-preview {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.75rem;
    justify-content: center;
}
.app-dropzone-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.25rem;
    width: 96px;
}
.app-dropzone-item img {
    width: 96px;
    height: 96px;
    object-fit: cover;
    border-radius: var(--bs-border-radius);
    border: 1px solid var(--bs-border-color);
    background: #fff;
}
.app-dropzone-label {
    max-width: 96px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--bs-secondary-color);
}

/* --- Upload-Zone (einheitlicher Webkomponent, ab 0.15.0) ------------- */
.upload-zone {
    position: relative;
    border: 2px dashed var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    padding: 1.25rem;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
    background: transparent;
}
.upload-zone:hover { background: var(--bs-tertiary-bg); }
.upload-zone.is-dragover {
    border-color: var(--bs-primary);
    background: rgba(var(--bs-primary-rgb), 0.08);
}
.upload-zone-input { display: none; }
.upload-zone-body {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0.35rem;
    color: var(--bs-secondary-color);
}
.upload-zone-icon { font-size: 1.75rem; opacity: .75; }
.upload-zone-hint { font-size: .95rem; }
.upload-zone-files {
    margin-top: .75rem;
    display: flex;
    flex-direction: column;
    gap: .35rem;
}
.upload-zone-files:empty { display: none; }
.upload-zone-file {
    background: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    padding: .35rem .5rem;
}
.upload-zone-file-icon {
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--bs-secondary-color);
}
.upload-zone-file-icon img {
    width: 32px; height: 32px;
    object-fit: cover;
    border-radius: var(--bs-border-radius-sm);
}
.upload-zone-file-meta {
    word-break: break-all;
    color: var(--bs-body-color);
}
.upload-zone-file-remove {
    line-height: 1;
    background: transparent;
    border: 0;
}

/* --- Mobile-Hochformat-Overflow-Fixes ------------------------------------ */

/* Notification-Bell-Dropdown: ab 576 px die urspruengliche feste Breite,
   darunter responsive (Viewport minus kleiner Rand), damit der rechts-
   ausgerichtete Dropdown nicht ueber die linke Body-Kante hinaus laeuft. */
.app-notif-menu {
    min-width: 340px;
    max-width: 420px;
}
@media (max-width: 575.98px) {
    .app-notif-menu {
        min-width: calc(100vw - 1rem);
        max-width: calc(100vw - 1rem);
    }
}

/* Mail-Editor-Toolbar: Bootstrap's .btn-toolbar ist `display: flex`, aber
   ohne `flex-wrap`. Auf schmalen Viewports reihen sich die ~12 Buttons in
   einer einzigen, horizontal scrollbaren Zeile. Wrap erlauben -> Stack. */
.mtpl-toolbar {
    flex-wrap: wrap;
}

/* Lange Tokens/IDs/Pfade in <code> brechen, statt das Layout zu sprengen.
   `overflow-wrap: anywhere` ist weniger aggressiv als `word-break: break-all`
   -- bricht nur, wenn das Wort sonst wirklich nicht passt. Auf normalen
   Viewports aendert sich gar nichts. */
code {
    overflow-wrap: anywhere;
}

/* Button-Rendering: helle Hairline am Border-Radius vermeiden.
   Chromium rendert bei abgerundeten Ecken mit gleichzeitig gesetztem
   Background-Color UND 1px-Border (Default: `background-clip: border-box`)
   am inneren Stroke-Rand der Ecke einen 1px-Sub-Pixel-Mismatch zwischen
   Fill-AA und Stroke-AA. Bei pixelgenauem Zoom (100% Browser + 100%
   Windows-Skalierung) wird dieser als heller Pixel sichtbar; bei
   abweichendem Zoom (>100%) verwischt das Sub-Pixel-AA den Effekt. Mit
   `background-clip: padding-box` faellt die Fuellung exakt INNERHALB
   des Borders -- Fill- und Stroke-Geometrie liegen nicht mehr auf
   demselben Pfad, der Mismatch verschwindet. Optisch identisch, weil
   im Branding-CSS `--bs-btn-bg` und `--bs-btn-border-color` denselben
   Wert tragen. */
.btn {
    background-clip: padding-box;
}

/* Farb-Swatch (Branding-Auswahl): kleiner Indikator neben den Mode-Radios.
   Statt Bootstraps `.border` + `.rounded` (sichtbarer hellgrauer Ring +
   Ecken-Antialiasing gegen weisse Card-Flaeche -> wirkt als "weisse
   Artefakte"): abgerundetes Quadrat mit Inset-Shadow als Rahmen, der
   innerhalb der Farbflaeche liegt und diese nicht reduziert. */
.app-color-swatch {
    display: inline-block;
    width: 1.125rem;
    height: 1.125rem;
    border-radius: 0.375rem;
    flex-shrink: 0;
    background-clip: padding-box;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
}
[data-bs-theme="dark"] .app-color-swatch {
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);
}

/* Log-Anzeige im Konfig-Bereich */
.app-log {
    background: var(--bs-tertiary-bg);
    color: var(--bs-body-color);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    padding: 0.75rem 1rem;
    max-height: 60vh;
    overflow: auto;
    font-family: var(--bs-font-monospace);
    line-height: 1.4;
    white-space: pre;
    margin: 0;
}

/* Changelog im Konfig-Tab */
.app-changelog .card-header { border-bottom: 1px solid var(--bs-border-color); }
.app-cl-current { border-color: var(--bs-success); border-width: 2px; }
.app-cl-current .card-header { background: rgba(var(--bs-success-rgb), 0.08) !important; }

.app-cl-section {
    padding-left: 0.6rem;
    border-left: 3px solid var(--bs-border-color);
}
.app-cl-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    color: #fff;
    font-size: 0.78rem;
    flex-shrink: 0;
}
.app-cl-list {
    padding-left: 1.2rem;
    margin-bottom: 0.5rem;
    font-size: 0.92rem;
    line-height: 1.55;
}
.app-cl-list li { margin-bottom: 0.2rem; }
.app-cl-list li code,
.app-cl-section p code {
    background: var(--bs-tertiary-bg);
    padding: 0.05em 0.35em;
    border-radius: 3px;
    font-size: 0.85em;
}

/* Section-Typen: Akzentfarbe an Bordersleft + Icon-Hintergrund */
.app-cl-section.app-cl-added       { border-left-color: var(--bs-success); }
.app-cl-section.app-cl-added       .app-cl-icon { background: var(--bs-success); }
.app-cl-section.app-cl-changed     { border-left-color: var(--bs-primary); }
.app-cl-section.app-cl-changed     .app-cl-icon { background: var(--bs-primary); }
.app-cl-section.app-cl-fixed       { border-left-color: #fd7e14; }
.app-cl-section.app-cl-fixed       .app-cl-icon { background: #fd7e14; }
.app-cl-section.app-cl-security    { border-left-color: var(--bs-danger); }
.app-cl-section.app-cl-security    .app-cl-icon { background: var(--bs-danger); }
.app-cl-section.app-cl-deprecated,
.app-cl-section.app-cl-removed     { border-left-color: var(--bs-secondary); }
.app-cl-section.app-cl-deprecated  .app-cl-icon,
.app-cl-section.app-cl-removed     .app-cl-icon { background: var(--bs-secondary); }
.app-cl-section.app-cl-note        { border-left-color: var(--bs-info, #0dcaf0); }
.app-cl-section.app-cl-note        .app-cl-icon { background: var(--bs-info, #0dcaf0); }

/* App-Tabellen mit clientseitiger Sortierung */
table[data-app-table] thead th[data-sortable] {
    user-select: none;
    position: relative;
    padding-right: 1.4rem;
}
table[data-app-table] thead th[data-sortable]::after {
    content: "\F127";  /* bi-chevron-expand */
    font-family: "bootstrap-icons";
    position: absolute;
    right: 0.4rem;
    opacity: 0.4;
    font-size: 0.85em;
}
table[data-app-table] thead th[data-sort-dir="asc"]::after  { content: "\F128"; opacity: 1; } /* bi-chevron-up */
table[data-app-table] thead th[data-sort-dir="desc"]::after { content: "\F126"; opacity: 1; } /* bi-chevron-down */

/* --- Layout-Skelett mit feststehender Sidebar ------------------------------ */
/* Body ist immer eine Flex-Column (Header / Mittelteil / Footer). Auf
   Mobile darf der Body natuerlich scrollen (min-height:100vh); auf Desktop
   wird der Body auf Viewport-Hoehe fixiert, und stattdessen scrollt
   `.app-content`. Dadurch bleibt die Sidebar (Mittelteil-hoch via
   align-items:stretch) inklusive Profil-/Logout-Block immer vollstaendig
   sichtbar — der Profil-Block "sticky am unteren Rand" funktioniert
   automatisch, weil die Sidebar selbst genauso hoch ist wie der sichtbare
   Mittelteil. */
body {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}
.app-header,
.app-footer {
    flex: 0 0 auto;
}
main.app-shell {
    flex: 1 1 auto;
}

/* Layout-Modus "mit Sidebar" (eingeloggter Benutzer). Auf Desktop wird der
   Body zu einer Flex-Row: Sidebar links ueber die volle Hoehe, rechts ein
   .app-shell-main-Container mit Header, Banner, Content und Footer in
   Spaltenanordnung. Damit reicht die Sidebar vom oberen Browser-Rand bis
   nach unten und trennt Logo + Branding-Block oben klar vom Header rechts. */
.app-has-sidebar > .app-shell-main {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    min-width: 0;
}

@media (min-width: 768px) {
    html, body {
        /* 100dvh fuer mobile Browser-Chrome (Adressleiste); fuer aeltere
           Browser faellt 100vh weiter unten als zweite Deklaration zurueck. */
        height: 100vh;
        height: 100dvh;
    }
    body {
        overflow: hidden;
        min-height: 0;
    }
    main.app-shell {
        /* min-height:0 erlaubt dem Content-Child, korrekt zu scrollen
           statt das Flex-Layout zu sprengen. */
        min-height: 0;
    }
    .app-content {
        overflow-y: auto;
        overflow-x: hidden;
    }
    /* Im Sidebar-Modus wird der Body zu einer Flex-Row */
    .app-has-sidebar {
        flex-direction: row;
    }
    .app-has-sidebar > .app-shell-main {
        min-height: 0;
    }
    /* Header + Footer im Sidebar-Modus: feste Hoehe, Content scrollt */
    .app-has-sidebar > .app-shell-main > .app-header,
    .app-has-sidebar > .app-shell-main > .app-footer {
        flex: 0 0 auto;
    }
    .app-has-sidebar > .app-shell-main > .app-content {
        flex: 1 1 auto;
        overflow-y: auto;
        overflow-x: hidden;
    }
    /* Banner-Container (Maintenance, 2FA) im Sidebar-Modus: auch fix.
       `:not(.app-content)` schliesst das Main-Element aus, das BEIDE Klassen
       (`app-shell` + `app-content`) traegt — sonst wuerde der Selector dieses
       gewinnen und das Main waere `flex: 0 0 auto` -> kein Scrollen mehr. */
    .app-has-sidebar > .app-shell-main > .app-shell:not(.app-content) {
        flex: 0 0 auto;
    }
}

.app-content {
    flex: 1 1 auto;
    min-width: 0; /* damit Tabellen / Pre-Tags nicht das Layout sprengen */
    padding-block: 1.25rem;
    padding-inline: 1rem;
}

@media (min-width: 992px) {
    .app-content {
        padding-inline: 1.5rem;
    }
}

.app-sidebar {
    /* Sidebar-Farbpalette als CSS-Custom-Properties, scope-isoliert auf den
       Sidebar-Container. Damit die Sidebar dauerhaft dunkel bleibt (auch im
       Light-Mode der App), und im Dark-Mode der App noch dunkler wird.
       Branding.sidebar_bg ueberschreibt --sidebar-bg ueber Inline-Style. */
    --sidebar-bg: #212529;
    --sidebar-fg: rgba(255, 255, 255, 0.85);
    --sidebar-fg-muted: rgba(255, 255, 255, 0.45);
    --sidebar-hover-bg: rgba(255, 255, 255, 0.08);
    --sidebar-border: rgba(255, 255, 255, 0.10);

    flex: 0 0 240px;
    width: 240px;
    /* Eigener Flex-Container, damit .offcanvas-body mit flex:1 die ganze
       Sidebar-Hoehe einnimmt und der Profil-Block am unteren Sidebar-Ende
       sitzt — und nicht am Menue-Ende. */
    display: flex;
    flex-direction: column;
    /* !important haengt Bootstrap aus: `.offcanvas-md` setzt ab dem md-
       Breakpoint `background-color: transparent !important`, was unseren
       Dunkel-Hintergrund ohne diesen Override platt machen wuerde. */
    background-color: var(--sidebar-bg) !important;
    color: var(--sidebar-fg);
}

[data-bs-theme="dark"] .app-sidebar {
    --sidebar-bg: #111418;
}

/* Auf Desktop reicht die Sidebar ueber die volle Viewport-Hoehe (Logo oben,
   Profil-Block unten). Sie sitzt VOR dem Header — der Header rechts daneben
   ist also nur so breit wie der Main-Content-Bereich, nicht ueber die Sidebar
   hinweg. */
@media (min-width: 768px) {
    .app-has-sidebar > .app-sidebar {
        height: 100vh;
        height: 100dvh;
    }
}

/* Bootstrap-Override fuer .offcanvas-body innerhalb der Sidebar:
   Standard-Bootstrap setzt am md+-Breakpoint `flex-grow:0; overflow-y:visible`
   auf `.offcanvas-md .offcanvas-body` — das verhindert, dass der Body die
   Sidebar-Hoehe einnimmt und macht den Profil-Block "unsichtbar nach unten".
   Wir zwingen den Body explizit zu Flex-Wachstum, damit die innere
   `.app-sidebar-nav` das einzige scrollbare Element wird. Die doppelte
   Klasse hebt die Spezifitaet ueber Bootstraps `.offcanvas-md .offcanvas-body`
   (0,2,0) hinweg auf 0,3,0. */
.app-sidebar > .offcanvas-body.offcanvas-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
    padding: 0;
    background-color: transparent;
}

/* Brand-Block oben in der Sidebar (Logo + App-Name) — analog zur
   Family-Organizer-Optik. Sitzt VOR der scrollbaren Nav, ist also stets
   sichtbar. */
.app-sidebar-brand {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.9rem 1rem;
    color: var(--sidebar-fg);
    text-decoration: none;
    border-bottom: 1px solid var(--sidebar-border);
    flex-shrink: 0;
    transition: color 0.15s ease;
}
.app-sidebar-brand:hover {
    color: #fff;
}
.app-sidebar-brand-logo {
    max-height: 32px;
    width: auto;
    flex-shrink: 0;
}
.app-sidebar-brand-fallback {
    font-size: 1.25rem;
    line-height: 1;
}
.app-sidebar-brand-name {
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

@media (min-width: 768px) {
    .app-sidebar {
        /* Sidebar fuellt den gesamten Mittelteil (Viewport minus Header/
           Footer) ueber align-items:stretch vom Eltern-Wrapper. Sie scrollt
           NICHT — nur die innere `.app-sidebar-nav` scrollt. Damit bleibt
           der Profil-/Logout-Block am Sidebar-Ende immer sichtbar. */
        overflow: hidden;
    }
}

.app-sidebar-nav {
    padding: 0.75rem;
    /* In der Desktop-Sidebar uebernimmt die Nav das Scrollen, damit der
       Profil-Block am Sidebar-Boden sichtbar bleibt. min-height:0 ist
       Pflicht, sonst koennen Flex-Children nie unter ihre Content-Hoehe
       schrumpfen. */
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

/* Gruppen-Header in der Sidebar. Defaults via CSS-Custom-Properties,
   damit der Admin im Menue-Editor diese pro Gruppe per Inline-Style
   ueberschreiben kann (hoechste Spezifitaet, kein Klassen-Kollisions-Risiko). */
.app-sidebar-heading {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size:      var(--gs-fs,    0.7rem);
    font-weight:    var(--gs-fw,    600);
    text-transform: var(--gs-tt,    uppercase);
    letter-spacing: var(--gs-ls,    0.06em);
    color:          var(--gs-color, var(--sidebar-fg-muted));
    margin: 1rem 0.75rem 0.4rem;
    background: none;
    border: 0;
    padding: 0;
    width: auto;
    text-align: left;
}
.app-sidebar-heading:first-child { margin-top: 0.25rem; }
.app-sidebar-heading > i { font-size: var(--gs-icon-fs, 1em); color: inherit; }

/* Variante: Card (Bordered) — Bootstrap-Standard-Optik (alle vier Borders,
   abgerundete Ecken). Die negativen Side-Margins kompensieren einen Grossteil
   des 0.75rem-padding der Sidebar-Nav, so dass die Card ~0.35rem Luft zur
   Sidebar-Kante hat — minimaler Abstand, aber Card-Look bleibt klar.
   Im dunklen Sidebar-Kontext: transparenter Hintergrund + heller Border,
   damit die Card sich vom dunklen Untergrund abhebt, aber nicht aufhellt. */
.app-sidebar-group-card {
    margin: 0.6rem -0.4rem 0.75rem;
    background-color: transparent;
    border-color: var(--gs-color, var(--sidebar-border));
}
.app-sidebar-group-card .card-header {
    background: color-mix(in srgb, var(--gs-color, var(--bs-primary)) 22%, transparent);
    color:      var(--gs-color, var(--sidebar-fg));
    border-bottom-color: var(--gs-color, var(--sidebar-border));
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.app-sidebar-group-card .card-header > i { font-size: var(--gs-icon-fs, 1em); }
.app-sidebar-group-card .app-sidebar-list { padding: 0.3rem 0.2rem; }

/* Variante: Boxed (dezenter Hintergrund-Tint) — analog full-width. */
.app-sidebar-group-boxed {
    background: color-mix(in srgb, var(--gs-color, #ffffff) 6%, transparent);
    border-radius: var(--bs-border-radius);
    padding: 0.2rem 0.5rem 0.4rem;
    margin: 0.5rem -0.4rem 0.6rem;
}
.app-sidebar-group-boxed .app-sidebar-heading { margin: 0.3rem 0.4rem 0.2rem; }

/* Variante: Collapse (klickbarer Header) */
.app-sidebar-collapse-toggle {
    cursor: pointer;
    width: calc(100% - 1.5rem);
}
.app-sidebar-collapse-toggle:hover { color: var(--gs-color, #fff); }
.app-sidebar-collapse-caret {
    margin-left: auto;
    transition: transform 0.2s ease;
    font-size: 0.85em;
}
.app-sidebar-collapse-toggle[aria-expanded="false"] .app-sidebar-collapse-caret {
    transform: rotate(-90deg);
}

.app-sidebar-list {
    list-style: none;
    padding: 0;
    margin: 0 0 0.5rem;
}

.app-sidebar-link {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5rem 0.75rem;
    border-radius: var(--bs-border-radius);
    color: var(--sidebar-fg);
    text-decoration: none;
    font-size: 0.92rem;
    line-height: 1.2;
    transition: background 0.15s ease, color 0.15s ease;
}
.app-sidebar-link i {
    font-size: 1.05rem;
    width: 1.25rem;
    text-align: center;
    flex-shrink: 0;
}
.app-sidebar-link:hover {
    background: var(--sidebar-hover-bg);
    color: #fff;
}
.app-sidebar-link.is-active {
    background: var(--bs-primary);
    color: #fff;
}
/* Trenner-Element (Type "separator") — wird vom Menue-Editor zwischen
   Items oder zwischen Gruppen eingefuegt. Vier Varianten:
   - line   : durchgehende Linie
   - dashed : gestrichelt
   - dotted : gepunktet
   - space  : nur vertikaler Abstand, keine Linie sichtbar */
.app-sidebar-separator {
    list-style: none;
    margin: 0.4rem 0.4rem;
    border-top: 1px solid var(--sidebar-border);
    line-height: 0;
}
.app-sidebar-separator--dashed { border-top-style: dashed; }
.app-sidebar-separator--dotted { border-top-style: dotted; }
.app-sidebar-separator--space  {
    border-top: 0;
    margin: 0.6rem 0;
    height: 0.4rem;
}

.app-sidebar-link.is-active:hover {
    background: var(--bs-primary);
    color: #fff;
    opacity: 0.92;
}

/* Profil-Block am unteren Sidebar-Ende. `flex-shrink:0` haelt ihn auch dann
   sichtbar, wenn die Nav darueber lang ist (Nav scrollt, Profil-Block bleibt).
   Hintergrund ist der Sidebar-Default (transparent ueberlagert vom Sidebar-bg);
   Border-Top trennt visuell von der Nav darueber. */
.app-sidebar-profile {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.65rem 0.75rem;
    flex-shrink: 0;
    background: transparent;
    border-top: 1px solid var(--sidebar-border);
}
.app-sidebar-profile-link {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.35rem 0.5rem;
    border-radius: var(--bs-border-radius);
    color: var(--sidebar-fg);
    text-decoration: none;
    transition: background 0.15s ease, color 0.15s ease;
}
.app-sidebar-profile-link:hover {
    background: var(--sidebar-hover-bg);
    color: #fff;
}
.app-sidebar-profile-link.is-active {
    background: var(--bs-primary);
    color: #fff;
}
.app-sidebar-profile-link.is-active:hover {
    background: var(--bs-primary);
    color: #fff;
    opacity: 0.92;
}

.app-sidebar-profile-avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    background: var(--sidebar-hover-bg);
}
.app-sidebar-profile-avatar--initials {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--bs-primary);
    color: #fff;
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.02em;
}

.app-sidebar-profile-text {
    display: flex;
    flex-direction: column;
    line-height: 1.2;
    min-width: 0;
}
.app-sidebar-profile-name {
    font-size: 0.88rem;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.app-sidebar-profile-email {
    font-size: 0.72rem;
    color: var(--sidebar-fg-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.app-sidebar-profile-link.is-active .app-sidebar-profile-email {
    color: rgba(255, 255, 255, 0.85);
}

.app-sidebar-profile-logout {
    flex-shrink: 0;
}

/* Auf Mobile: Sidebar ist Bootstrap-Offcanvas (position:fixed), Default-Breite
   240px reicht — kein eigener Width-Override hier noetig. */

/* Hauptnavigation oben */
.app-mainnav {
    gap: 0.25rem;
    align-items: center;
}
.app-mainnav-item {
    color: var(--gs-color, var(--bs-body-color));
    text-decoration: none;
    padding: 0.4rem 0.85rem;
    border-radius: var(--bs-border-radius);
    font-size:   var(--gs-fs, 0.92rem);
    font-weight: var(--gs-fw, 400);
    transition: background 0.15s ease, color 0.15s ease;
}
.app-mainnav-item > i { font-size: var(--gs-icon-fs, 1em); }
.app-mainnav-item:hover {
    background: var(--bs-tertiary-bg);
    color: var(--bs-primary);
}
.app-mainnav-item.is-active {
    background: var(--bs-primary);
    color: #fff;
}
.app-mainnav-item.is-active:hover {
    background: var(--bs-primary);
    color: #fff;
    opacity: 0.92;
}

/* User-Menu-Avatar (Initialen-Kreis) */
.app-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--bs-primary);
    color: #fff;
    font-weight: 600;
    font-size: 0.8rem;
    flex-shrink: 0;
}

.app-footer a {
    text-decoration: none;
}
.app-footer a:hover {
    text-decoration: underline;
}

/* --- Spezielle Karten (Installer, Login, modale Workflows) ----------------- */
.app-card-narrow {
    max-width: 560px;
}

/* --- Branding-Slot fuer Hintergrund (Installer/Login) ---------------------- */
/* Wird in Etappe 2 ueber CSS-Variablen konfigurierbar gemacht. */
.app-installer {
    min-height: 100vh;
    background: var(--bs-body-bg);
}

/* --- Installer-Stepper ----------------------------------------------------- */
.app-stepper-list {
    list-style: none;
    padding: 0;
    margin: 0;
    gap: 0.5rem;
    counter-reset: app-step;
}

.app-stepper-item {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.25rem;
    padding: 0.5rem 0.25rem;
    border-radius: var(--bs-border-radius);
    color: var(--bs-secondary-color);
    font-size: 0.85rem;
    text-align: center;
    border-bottom: 3px solid var(--bs-border-color);
    transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}

.app-stepper-number {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--bs-tertiary-bg);
    color: var(--bs-secondary-color);
    font-weight: 600;
    font-size: 0.85rem;
}

.app-stepper-title {
    font-weight: 500;
    line-height: 1.2;
}

.app-stepper-current {
    color: var(--bs-primary);
    border-bottom-color: var(--bs-primary);
}
.app-stepper-current .app-stepper-number {
    background: var(--bs-primary);
    color: #fff;
}

.app-stepper-done {
    color: var(--bs-success);
    border-bottom-color: var(--bs-success);
}
.app-stepper-done .app-stepper-number {
    background: var(--bs-success);
    color: #fff;
}

.app-stepper-pending {
    opacity: 0.55;
}

/* === 0.0.8: Notification-Bell + Maintenance + 2FA ====================== */

.app-notif-bell {
    position: relative;
}
.app-notif-bell .app-notif-count {
    font-size: .65rem;
    line-height: 1;
}
.app-notif-list .app-notif-item {
    border-radius: 0;
    text-decoration: none;
    color: inherit;
}
.app-notif-list .app-notif-item:last-child {
    border-bottom: 0 !important;
}
.app-notif-list .app-notif-item.app-notif-unread {
    background: rgba(var(--bs-primary-rgb), .06);
}
.app-notif-list .app-notif-item:hover {
    background: rgba(var(--bs-primary-rgb), .10);
}

/* Maintenance-Vollbild-Karte (Wiederverwendung der error-card-Optik) */
.app-maintenance .app-error-card {
    max-width: 560px;
}

/* 2FA-Setup-Karten */
.app-2fa-codes {
    columns: 2;
    column-gap: 1rem;
    list-style: none;
    padding-left: 0;
    font-family: var(--bs-font-monospace);
    font-size: .95rem;
}

/* Konfigurations-Seitennavigation (Sidebar links, Inhalt rechts).
   Ersetzt die alte horizontale `nav-tabs`-Leiste. */
.config-nav-card {
    /* Sticky beim Scrollen — sticky funktioniert nur, wenn der Parent
       (Bootstrap .col) Overflow nicht clippt, daher hier statt am .col. */
    position: sticky;
    top: 1rem;
    z-index: 1;
}
@media (max-width: 991.98px) {
    .config-nav-card { position: static; top: auto; }
}
/* Ab lg: schmale Sidebar mit fester Breite, Content nimmt den Rest.
   220 px reicht fuer das laengste Label („Aktivitätsprotokoll") inkl. Icon
   und gewinnt im Vergleich zu Bootstraps col-lg-3 (25%) deutlich Content-Breite. */
@media (min-width: 992px) {
    .config-shell > .config-sidebar-col {
        flex: 0 0 220px;
        max-width: 220px;
    }
    .config-shell > .config-content-col {
        flex: 1 1 0%;
        max-width: none;
        min-width: 0; /* Schutz gegen Overflow durch <pre>/Tabellen im Tab */
    }
}
.config-nav-heading {
    font-size: .72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--bs-secondary-color);
    margin: .8rem .25rem .25rem;
}
.config-nav-group:first-child .config-nav-heading { margin-top: .25rem; }
.config-nav-link {
    display: flex;
    align-items: center;
    padding: .35rem .6rem;
    border-radius: .375rem;
    color: var(--bs-body-color);
    text-decoration: none;
    line-height: 1.2;
}
.config-nav-link:hover {
    background: var(--bs-tertiary-bg);
    color: var(--bs-body-color);
}
.config-nav-link.active {
    background: rgba(var(--bs-primary-rgb), .12);
    color: var(--bs-primary);
    font-weight: 600;
}
.config-nav-link > i.bi {
    flex: 0 0 1.25rem;
    font-size: .95em;
}
.config-nav-label { flex: 1; }
.config-nav-group[hidden] { display: none; }
.config-nav-link[hidden]  { display: none; }

/* Dashboard-Widgets (Card-Varianten, Akzent-Farben, Edit-Mode-Overlay).
   Reihen sind Bootstrap `.row`, jedes Widget ein `.dash-cell` mit col-md-*.
   Im Edit-Modus erscheinen Drag-Handles + Pencil/X-Buttons als Overlay
   am Rand der Card. */
.dash-shell { position: relative; }
.dash-shell--editing .dash-row {
    position: relative;
    padding-left: 28px;
    min-height: 80px;
}
/* Leere Reihe (kein Widget drin) im Edit-Mode als Drop-Target visualisieren.
   `:has()` ist von allen modernen Browsern (Chrome 105+, Safari 15.4+,
   Firefox 121+) unterstuetzt. */
.dash-shell--editing .dash-row:not(:has([data-dash-widget])) {
    background: rgba(var(--bs-primary-rgb), .04);
    outline: 2px dashed rgba(var(--bs-primary-rgb), .35);
    outline-offset: -4px;
    border-radius: .5rem;
}
.dash-shell--editing .dash-row:not(:has([data-dash-widget]))::after {
    content: attr(data-empty-text);
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1 1 100%;
    min-height: 72px;
    color: var(--bs-secondary-color);
    font-size: .9em;
    font-style: italic;
}
.dash-row-handle {
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: grab;
    color: var(--bs-secondary-color);
    background: rgba(var(--bs-secondary-rgb), .08);
    border-radius: .375rem;
}
.dash-widget { position: relative; }
/* Der Cell-Wrapper muss ebenfalls positioniert sein, damit das Edit-Overlay
   (position:absolute) sich am Wrapper ausrichtet und nicht an einem
   entfernten Ahnen — sonst sass nur ein Overlay pro Reihe sichtbar an
   der rechten Kante. */
.dash-cell { position: relative; }
.dash-widget--flat { box-shadow: none !important; border: 0; background: transparent; }
.dash-widget--bordered { box-shadow: none !important; border-width: 2px; }
.dash-widget--accent {
    border-left: 4px solid var(--bs-primary);
}
.dash-widget-color-primary.dash-widget--accent   { border-left-color: var(--bs-primary); }
.dash-widget-color-secondary.dash-widget--accent { border-left-color: var(--bs-secondary); }
.dash-widget-color-success.dash-widget--accent   { border-left-color: var(--bs-success); }
.dash-widget-color-warning.dash-widget--accent   { border-left-color: var(--bs-warning); }
.dash-widget-color-danger.dash-widget--accent    { border-left-color: var(--bs-danger); }
.dash-widget-color-info.dash-widget--accent      { border-left-color: var(--bs-info); }
/* Im Header die Farbe als Akzent fuer Titel-Text */
.dash-widget-color-primary.dash-widget--accent .card-header strong,
.dash-widget-color-primary.dash-widget--bordered .card-header strong { color: var(--bs-primary); }
.dash-widget-color-secondary.dash-widget--accent .card-header strong,
.dash-widget-color-secondary.dash-widget--bordered .card-header strong { color: var(--bs-secondary); }
.dash-widget-color-success.dash-widget--accent .card-header strong,
.dash-widget-color-success.dash-widget--bordered .card-header strong { color: var(--bs-success); }
.dash-widget-color-warning.dash-widget--accent .card-header strong,
.dash-widget-color-warning.dash-widget--bordered .card-header strong { color: var(--bs-warning); }
.dash-widget-color-danger.dash-widget--accent .card-header strong,
.dash-widget-color-danger.dash-widget--bordered .card-header strong { color: var(--bs-danger); }
.dash-widget-color-info.dash-widget--accent .card-header strong,
.dash-widget-color-info.dash-widget--bordered .card-header strong { color: var(--bs-info); }

/* Modul-Widgets bringen ihre eigene `.card` mit — die Style-Klassen sitzen
   am `.dash-cell--module`-Wrapper. Variant/Farbe greifen daher per
   Descendant-Selektor auf die innere Card des Modul-Templates. So bekommen
   Geo-/Immobilien-/etc.-Widgets dieselbe Akzent-Optik wie Core-Widgets. */
.dash-cell--module.dash-widget--flat > .card {
    box-shadow: none !important; border: 0; background: transparent;
}
.dash-cell--module.dash-widget--bordered > .card {
    box-shadow: none !important; border-width: 2px;
}
.dash-cell--module.dash-widget--accent > .card {
    border-left: 4px solid var(--bs-primary);
}
.dash-cell--module.dash-widget-color-primary.dash-widget--accent > .card   { border-left-color: var(--bs-primary); }
.dash-cell--module.dash-widget-color-secondary.dash-widget--accent > .card { border-left-color: var(--bs-secondary); }
.dash-cell--module.dash-widget-color-success.dash-widget--accent > .card   { border-left-color: var(--bs-success); }
.dash-cell--module.dash-widget-color-warning.dash-widget--accent > .card   { border-left-color: var(--bs-warning); }
.dash-cell--module.dash-widget-color-danger.dash-widget--accent > .card    { border-left-color: var(--bs-danger); }
.dash-cell--module.dash-widget-color-info.dash-widget--accent > .card      { border-left-color: var(--bs-info); }
.dash-cell--module.dash-widget-color-primary.dash-widget--accent .card-header strong,
.dash-cell--module.dash-widget-color-primary.dash-widget--bordered .card-header strong { color: var(--bs-primary); }
.dash-cell--module.dash-widget-color-secondary.dash-widget--accent .card-header strong,
.dash-cell--module.dash-widget-color-secondary.dash-widget--bordered .card-header strong { color: var(--bs-secondary); }
.dash-cell--module.dash-widget-color-success.dash-widget--accent .card-header strong,
.dash-cell--module.dash-widget-color-success.dash-widget--bordered .card-header strong { color: var(--bs-success); }
.dash-cell--module.dash-widget-color-warning.dash-widget--accent .card-header strong,
.dash-cell--module.dash-widget-color-warning.dash-widget--bordered .card-header strong { color: var(--bs-warning); }
.dash-cell--module.dash-widget-color-danger.dash-widget--accent .card-header strong,
.dash-cell--module.dash-widget-color-danger.dash-widget--bordered .card-header strong { color: var(--bs-danger); }
.dash-cell--module.dash-widget-color-info.dash-widget--accent .card-header strong,
.dash-cell--module.dash-widget-color-info.dash-widget--bordered .card-header strong { color: var(--bs-info); }

/* Edit-Mode-Overlay */
.dash-widget-overlay {
    position: absolute;
    top: 6px;
    right: 6px;
    display: flex;
    gap: 4px;
    z-index: 3;
}
.dash-shell:not(.dash-shell--editing) .dash-widget-overlay { display: none; }
.dash-widget-overlay .btn { padding: 2px 6px; line-height: 1; }
.dash-shell--editing .dash-cell { outline: 1px dashed rgba(var(--bs-primary-rgb), .35); border-radius: .375rem; }

.dash-edit-toolbar { position: sticky; bottom: 1rem; z-index: 5; }

/* Markdown-Widget-Body: leichte vertikale Rhythmus-Korrekturen */
.dash-md > :last-child { margin-bottom: 0; }
.dash-md h1, .dash-md h2, .dash-md h3 { margin-top: .25rem; }

/* Richtext-Widget-Body (Welcome u.a.) */
.dash-rt > :last-child { margin-bottom: 0; }
.dash-rt h1, .dash-rt h2, .dash-rt h3 { margin-top: .25rem; }

/* Squire-Editor im Widget-Settings-Modal */
.dash-rt-host {
    min-height: 180px;
    max-height: 50vh;
    overflow-y: auto;
    border: 1px solid var(--bs-border-color);
    border-radius: .375rem;
    padding: .75rem;
    background: var(--bs-body-bg);
}
.dash-rt-host:focus,
.dash-rt-host:focus-within {
    border-color: var(--bs-primary);
    outline: 0;
    box-shadow: 0 0 0 .25rem rgba(var(--bs-primary-rgb), .15);
}

/* Custom-Field open_select Combobox (eigenes Dropdown, ersetzt native datalist).
   Verwendet Bootstrap-.dropdown-menu-Styling. Das Menue wird absolut unter
   dem Input positioniert und ueber die Buttons gestackt. */
.cf-combobox {
    position: relative;
}
.cf-combobox .cf-combobox-menu {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    max-height: 260px;
    overflow-y: auto;
    z-index: 1055; /* ueber Modal-Backdrop, falls Combobox in Modal */
    margin-top: 2px;
    padding: .25rem 0;
}
.cf-combobox .cf-combobox-menu.show {
    display: block;
}
.cf-combobox .cf-combobox-option {
    text-align: left;
    width: 100%;
}
.cf-combobox-menu.cf-combobox-menu--empty::after {
    content: '—';
    display: block;
    text-align: center;
    padding: .35rem 0;
    color: var(--bs-secondary-color);
    font-size: .85em;
}

/* ====================================================================
 * Multi-Bild-Galerie (Basis\Core\Images + tmpl/_partials/gallery.php)
 * Seit 0.20.0 — modulübergreifend verwendet.
 * ==================================================================== */
.image-gallery [data-gallery-item] {
    transition: opacity .15s ease, transform .15s ease;
}
.image-gallery [data-gallery-item][draggable="true"] {
    cursor: grab;
}
.image-gallery [data-gallery-item].dragging {
    opacity: .35;
}
.image-gallery [data-gallery-item].drop-target .card {
    outline: 2px dashed var(--bs-primary);
    outline-offset: -2px;
}
/* Gruppierte Galerie: Sektionen + Drop-Zonen */
.image-gallery-section + .image-gallery-section {
    border-top: 1px solid var(--bs-border-color);
    padding-top: .75rem;
}
.image-gallery [data-group-grid].drop-target-zone {
    outline: 2px dashed var(--bs-primary);
    outline-offset: 4px;
    border-radius: var(--bs-border-radius);
}
.image-gallery [data-group-grid]:empty {
    min-height: 4.5rem;
    border: 1px dashed var(--bs-border-color);
    border-radius: var(--bs-border-radius);
}
.image-gallery-group-handle { cursor: grab; user-select: none; }
.image-gallery-group-handle:active { cursor: grabbing; }
.image-gallery-section.dragging { opacity: .5; }
.image-gallery-section.section-drop-target {
    outline: 2px dashed var(--bs-primary);
    outline-offset: 4px;
    border-radius: var(--bs-border-radius);
}
/* Mehrfachauswahl */
.image-gallery-select {
    z-index: 2;
    background: rgba(var(--bs-body-bg-rgb), .78);
    border-radius: .375rem;
    padding: .1rem .3rem;
    line-height: 1;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .35);
}
.image-gallery-select .form-check-input {
    width: 1.15rem;
    height: 1.15rem;
    margin: 0;
    cursor: pointer;
}
.image-gallery [data-gallery-item].is-selected .image-gallery-card {
    outline: 3px solid var(--bs-primary);
    outline-offset: -1px;
}
.image-gallery-bulkbar {
    position: sticky;
    top: .5rem;
    z-index: 4;
}
.image-gallery-thumb {
    aspect-ratio: 4 / 3;
    object-fit: cover;
    width: 100%;
    height: auto;
    display: block;
}
.image-gallery-thumb-btn {
    display: block;
    line-height: 0;
}
.image-gallery-thumb-btn:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: 2px;
    border-radius: var(--bs-border-radius);
}
/* Detail-Modal: bildzentriert, volle Breite, Meta-Caption darunter. */
.image-gallery-modal .modal-content {
    min-height: 60vh;
}
.image-gallery-modal-stage {
    position: relative;
    background-color: var(--bs-tertiary-bg);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    min-height: 65vh;
}
[data-bs-theme="dark"] .image-gallery-modal-stage {
    background-color: #0a0a0a;
}
.image-gallery-modal-img {
    max-height: calc(100vh - 10rem);
    width: auto;
    max-width: 100%;
    height: auto;
    object-fit: contain;
    display: block;
}
/* Navigations-Buttons innerhalb der Bühne. Halbtransparent, sichtbar bei Hover. */
.image-gallery-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 3rem;
    height: 3rem;
    border-radius: 50%;
    border: 0;
    background-color: rgba(0, 0, 0, .35);
    color: #fff;
    font-size: 1.5rem;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: background-color .15s ease, opacity .15s ease;
    opacity: .55;
    z-index: 2;
}
.image-gallery-nav:hover,
.image-gallery-nav:focus-visible {
    background-color: rgba(0, 0, 0, .65);
    opacity: 1;
    outline: none;
}
.image-gallery-nav-prev { left: 1rem; }
.image-gallery-nav-next { right: 1rem; }
@media (max-width: 575.98px) {
    .image-gallery-nav { width: 2.25rem; height: 2.25rem; font-size: 1.15rem; }
    .image-gallery-nav-prev { left: .5rem; }
    .image-gallery-nav-next { right: .5rem; }
}
/* Meta-Caption unter dem Bild: schmal, einzeilig, dezent. */
.image-gallery-modal-caption {
    background-color: var(--bs-body-bg);
    border-top: 1px solid var(--bs-border-color);
}
.image-gallery-modal-meta {
    font-size: .8rem;
    color: var(--bs-tertiary-color);
}
.image-gallery-modal-meta .list-inline-item {
    margin-right: .35rem;
}
.image-gallery-modal-meta .list-inline-item + .list-inline-item {
    margin-left: 0;
}
/* Card-Aktionsleiste und Inline-Edit-Panel */
.image-gallery-card-actions .btn {
    --bs-btn-padding-y: .15rem;
    --bs-btn-padding-x: .4rem;
    --bs-btn-font-size: .75rem;
}
.image-gallery-action-primary.is-active {
    color: var(--bs-warning);
    border-color: var(--bs-warning);
    opacity: .85;
}
.image-gallery-card-edit {
    background-color: var(--bs-tertiary-bg);
}

/* ====================================================================
 * Generischer Listen-View-Toggle (Cards/Tabelle) — seit 0.20.0
 * Markup: <div data-list-view data-view-mode="cards"> mit zwei Sub-Containern
 *         data-view-target="cards" und data-view-target="table".
 * Switcher: <button data-view-switch="cards|table" data-list-view-target="...">
 * ==================================================================== */
[data-list-view][data-view-mode="cards"] [data-view-target="table"] { display: none; }
[data-list-view][data-view-mode="table"] [data-view-target="cards"] { display: none; }

/* Card-Style für Listen-Karten (modulübergreifend wiederverwendbar). */
.app-list-card {
    transition: transform .12s ease-out, box-shadow .12s ease-out;
    overflow: hidden;
}
.app-list-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .08) !important;
}
.app-list-card-thumb {
    position: relative;
    background-color: var(--bs-tertiary-bg);
    aspect-ratio: 16 / 9;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
}
/* Lesbarkeits-Scrim hinter den Overlay-Badges (oben), damit Badges auf
   hellen wie dunklen Bildern immer kontrastieren. Nur fuer Cards mit
   absoluten Overlay-Elementen (z. B. Immobilien Typ/Besitzart-Badges). */
.app-list-card:has(> .position-absolute) .app-list-card-thumb::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 4.5rem;
    background: linear-gradient(to bottom, rgba(0, 0, 0, .45), rgba(0, 0, 0, 0));
    pointer-events: none;
    z-index: 1;
}
.app-list-card > .position-absolute,
.app-list-card > .badge.position-absolute {
    z-index: 2;
}
.app-list-card .position-absolute .badge,
.app-list-card .badge.position-absolute {
    box-shadow: 0 1px 3px rgba(0, 0, 0, .55);
}
.app-list-card-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.app-list-card-thumb-placeholder {
    color: var(--bs-secondary-color);
    font-size: 2.5rem;
    opacity: .35;
}

/* =====================================================================
 * Eck-Banderole ("app-ribbon") — wiederverwendbarer Ecken-Marker, der
 * app-weit ueber Bildern/Containern eingeblendet wird (Partial:
 * tmpl/_partials/corner_ribbon.php). Der Host-Container braucht die
 * Klasse .app-ribbon-host (position:relative); ideal mit overflow:hidden.
 * Stil: leicht gedrehte "Stempel-Pill", klick-durchlaessig.
 * ===================================================================== */
.app-ribbon-host { position: relative; }
.app-ribbon {
    position: absolute;
    z-index: 3;
    display: inline-flex;
    align-items: center;
    max-width: calc(100% - 1.2rem);
    font-size: .9rem;
    font-weight: 800;
    letter-spacing: .05em;
    text-transform: uppercase;
    line-height: 1.1;
    padding: .33rem .72rem;
    border-radius: .5rem;
    border: 2px solid rgba(255, 255, 255, .75);
    box-shadow: 0 2px 6px rgba(0, 0, 0, .3);
    transform: rotate(-8deg);
    transform-origin: center;
    pointer-events: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    /* Default-Farbe (secondary): dunkel-transluzent, hell-/dunkelmodus-tauglich */
    background: rgba(33, 37, 41, .85);
    color: #fff;
}
/* Positionen */
.app-ribbon--top-right    { top: .5rem;    right: .6rem; }
.app-ribbon--top-left     { top: .5rem;    left: .6rem;  transform: rotate(8deg); }
.app-ribbon--bottom-right { bottom: .5rem; right: .6rem; transform: rotate(8deg); }
.app-ribbon--bottom-left  { bottom: .5rem; left: .6rem;  transform: rotate(-8deg); }
/* Farb-Varianten ueber Bootstrap-CSS-Variablen */
.app-ribbon--secondary { background: rgba(33, 37, 41, .85); color: #fff; }
.app-ribbon--danger    { background: rgba(var(--bs-danger-rgb), .9);  color: #fff; }
.app-ribbon--primary   { background: rgba(var(--bs-primary-rgb), .9); color: #fff; }
.app-ribbon--success   { background: rgba(var(--bs-success-rgb), .9); color: #fff; }
.app-ribbon--warning   { background: rgba(var(--bs-warning-rgb), .92); color: #1a1a1a; border-color: rgba(0, 0, 0, .2); }

