@font-face {
    font-family: 'Figtree';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('/assets/fonts/figtree-400.woff2') format('woff2');
}

@font-face {
    font-family: 'Figtree';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('/assets/fonts/figtree-500.woff2') format('woff2');
}

@font-face {
    font-family: 'Figtree';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('/assets/fonts/figtree-600.woff2') format('woff2');
}

@font-face {
    font-family: 'Figtree';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('/assets/fonts/figtree-700.woff2') format('woff2');
}

:root {
    --color-bg: #f6f7f4;
    --color-surface: #ffffff;
    --color-border: #e3e7de;
    --color-border-input: #d9dfd2;
    --color-text: #22301f;
    --color-text-secondary: #4a5544;
    --color-muted: #6f7a68;
    --color-primary: #2e6b3e;
    --color-primary-dark: #224f2e;
    --color-danger: #a3453d;

    --color-free-bg: #eef5eb;
    --color-free-border: #bcd6b6;
    --color-free-text: #2e6b3e;

    --color-booked-bg: #faf3e3;
    --color-booked-border: #ecd9ab;
    --color-booked-text: #8a6a2a;

    --color-blocked-bg: #f9ecea;
    --color-blocked-border: #e5bcb7;
    --color-blocked-text: #a3453d;

    --color-past-bg: #eceeea;
    --color-past-border: #cfd3ca;
    --color-past-text: #6f7a68;

    --radius-card: 14px;
    --radius-control: 8px;
    --shadow-card: 0 1px 2px rgba(34, 48, 31, 0.05);

    font-size: 16px;
}

* { box-sizing: border-box; }

body {
    margin: 0;
    background: var(--color-bg);
    color: var(--color-text);
    font-family: 'Figtree', 'Segoe UI', system-ui, sans-serif;
    line-height: 1.5;
}

a { color: var(--color-primary); }
a:hover { color: var(--color-primary-dark); }

h1, h2, h3, h4 { font-weight: 700; }

/* Standard-Clip-Pattern: aus dem Textfluss genommen und quasi auf einen Punkt
   zusammengeschrumpft, statt display: none — Screenreader lesen es trotzdem
   vor. Für Zusatzinformation, die visuell schon durch Icon/Farbe da ist
   (z. B. "Buchungen" neben dem Personen-Icon im Monatskalender). */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* --- Header --- */

.site-header {
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border);
}

.site-header__inner {
    max-width: 1265px;
    margin: 0 auto;
    min-height: 64px;
    padding: 0.5rem 1rem;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
}

.site-header__brand {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    color: var(--color-text);
    text-decoration: none;
}

.site-header__brand:hover { color: var(--color-text); }

.site-header__logo {
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--color-primary);
    color: #fff;
    font-weight: 700;
    font-size: 0.85rem;
    display: flex;
    align-items: center;
    justify-content: center;
    letter-spacing: 0.02em;
}

.site-header__brandtext {
    display: flex;
    flex-direction: column;
    line-height: 1.25;
}

.site-header__brandname {
    font-weight: 700;
    font-size: 0.95rem;
    color: var(--color-text);
}

.site-header__brandsub {
    font-weight: 400;
    font-size: 0.8rem;
    color: var(--color-muted);
}

.site-header__nav {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1.25rem;
}

.site-header__nav a {
    color: var(--color-text-secondary);
    text-decoration: none;
    font-size: 0.875rem;
    font-weight: 500;
}

.site-header__nav a:hover { color: var(--color-primary); }

.site-header__user {
    color: var(--color-muted);
    font-size: 0.85rem;
}

.site-header__logout-form { margin: 0; }

.link-button {
    background: none;
    border: none;
    color: var(--color-text-secondary);
    text-decoration: underline;
    cursor: pointer;
    font: inherit;
    font-size: 0.875rem;
    padding: 0;
}

.link-button:hover { color: var(--color-primary); }

.page {
    max-width: 1265px;
    margin: 0 auto;
    padding: 1.5rem 1rem 3rem;
}

.site-footer {
    text-align: center;
    color: var(--color-muted);
    font-size: 0.85rem;
    padding: 1.5rem 1rem;
}

h1 { margin-top: 0; }

.flash-list { margin-bottom: 1.25rem; }

.flash {
    padding: 0.65rem 0.9rem;
    border-radius: var(--radius-control);
    margin: 0 0 0.5rem;
    border: 1px solid transparent;
    font-size: 0.9rem;
}

.flash--success {
    background: var(--color-free-bg);
    border-color: var(--color-free-border);
    color: var(--color-free-text);
}

.flash--error {
    background: var(--color-blocked-bg);
    border-color: var(--color-blocked-border);
    color: var(--color-blocked-text);
}

/* --- Cards --- */

.card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-card);
    padding: 1.25rem;
    margin-bottom: 1.5rem;
}

.card--narrow { max-width: 360px; }

/* --- Tables --- */

.table-wrap {
    overflow-x: auto;
    margin-bottom: 1.5rem;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-card);
}

table {
    border-collapse: collapse;
    width: 100%;
    background: var(--color-surface);
}

th, td {
    border: none;
    border-bottom: 1px solid var(--color-border);
    padding: 0.65rem 0.75rem;
    text-align: left;
    vertical-align: middle;
}

thead th {
    background: #f6f7f4;
    color: var(--color-text-secondary);
    font-size: 0.85rem;
    font-weight: 600;
}

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

/* --- Buttons --- */

.btn {
    display: inline-block;
    background: var(--color-primary);
    color: #fff;
    border: 1px solid var(--color-primary);
    padding: 0.55rem 1.1rem;
    border-radius: var(--radius-control);
    cursor: pointer;
    text-decoration: none;
    font: inherit;
    font-weight: 600;
    font-size: 0.9rem;
    line-height: 1.3;
}

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

.btn--danger { background: var(--color-danger); border-color: var(--color-danger); }
.btn--danger:hover { background: #832f28; border-color: #832f28; }

.btn--secondary {
    background: var(--color-surface);
    color: var(--color-text-secondary);
    border-color: var(--color-border-input);
}
.btn--secondary:hover { background: #f6f7f4; color: var(--color-text); border-color: var(--color-border-input); }

.btn--ghost {
    background: var(--color-surface);
    color: var(--color-muted);
    border-color: var(--color-border-input);
}

/* Deckt sowohl serverseitig gerendertes disabled (z. B. Test-Mail-Button ohne
   Empfänger) als auch das per app.js im Doppel-Submit-Schutz gesetzte
   disabled während des Absendens ab (data-submit-once, siehe app.js). */
.btn:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

.btn--small { padding: 0.3rem 0.65rem; font-size: 0.8rem; }

.btn--full { display: block; width: 100%; text-align: center; }

form.inline { display: inline; margin: 0; }

/* --- Forms --- */

.form-group {
    margin-bottom: 1rem;
}

.form-group label {
    display: block;
    font-weight: 600;
    font-size: 0.875rem;
    margin-bottom: 0.3rem;
}

.form-group input[type="text"],
.form-group input[type="password"],
.form-group input[type="number"],
.form-group input[type="date"],
.form-group input[type="time"],
.form-group input[type="file"],
.form-group select,
.form-group textarea {
    width: 100%;
    max-width: 420px;
    padding: 0.5rem 0.65rem;
    border: 1px solid var(--color-border-input);
    border-radius: var(--radius-control);
    font: inherit;
    background: #fff;
    color: var(--color-text);
}

.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
    outline: 2px solid var(--color-primary);
    outline-offset: 1px;
    border-color: var(--color-primary);
}

.form-group textarea { max-width: 100%; }

.checkbox-group label {
    font-weight: normal;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin-right: 1rem;
}

.checkbox-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1.5rem;
}

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

.auth-page {
    max-width: 360px;
    margin: 3rem auto 0;
    text-align: center;
}

.auth-page__logo {
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: var(--color-primary);
    color: #fff;
    font-weight: 700;
    font-size: 1.2rem;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 auto 0.75rem;
}

.auth-page__name {
    font-weight: 700;
    font-size: 1.05rem;
    color: var(--color-text);
    margin: 0;
}

.auth-page__sub {
    font-weight: 400;
    font-size: 0.9rem;
    color: var(--color-muted);
    margin: 0.15rem 0 1.5rem;
}

.auth-page .card {
    text-align: left;
}

.auth-page__hint {
    color: var(--color-muted);
    font-size: 0.85rem;
    margin: 0.5rem 0 0;
}

/* --- Kalender (Mitglieder-Wochenansicht) --- */

.week-head {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: flex-end;
    gap: 1rem 1.5rem;
    margin-bottom: 1rem;
}

.week-head__eyebrow {
    margin: 0;
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-muted);
}

.week-head__kw {
    margin: 0.05rem 0 0;
    font-size: 2.4rem;
    line-height: 1;
    font-weight: 800;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
}

.week-head__range {
    margin: 0.4rem 0 0;
    font-size: 1.1rem;
    font-weight: 600;
}

.week-head__nav {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.45rem;
}

.week-head__today { color: var(--color-primary); }
span.week-head__today { cursor: default; }

/* KW-Leiste über den gesamten Buchungshorizont, horizontal scrollbar statt
   umzubrechen — bei 8-9 Wochen wäre ein Umbruch auf schmalen Bildschirmen
   unruhiger als ein seitliches Wischen. */
.week-bar { margin: 0 0 1rem; }

.week-bar__scroll {
    display: flex;
    gap: 6px;
    overflow-x: auto;
    padding: 2px 2px 6px;
    scrollbar-width: thin;
}

.week-chip {
    flex: 0 0 auto;
    display: grid;
    grid-template-columns: auto auto;
    column-gap: 0.4rem;
    align-items: baseline;
    text-align: left;
    min-width: 5.2rem;
    background: var(--color-surface);
    border: 1px solid var(--color-border-input);
    border-radius: var(--radius-control);
    padding: 0.38rem 0.6rem 0.42rem;
    color: var(--color-text);
    text-decoration: none;
    font-variant-numeric: tabular-nums;
}

.week-chip:hover { border-color: var(--color-primary); }

.week-chip__kw { font-size: 0.86rem; font-weight: 700; }
.week-chip__marks { justify-self: end; font-size: 0.72rem; letter-spacing: 0.08em; line-height: 1; }
.week-chip__mine { color: var(--color-primary); }
.week-chip__title { color: var(--color-booked-text); }
.week-chip__date { grid-column: 1 / -1; font-size: 0.7rem; color: var(--color-muted); }

.week-chip--current .week-chip__kw { color: var(--color-primary); }

.week-chip[aria-current="true"] {
    background: var(--color-primary);
    border-color: var(--color-primary);
    color: #fff;
}

.week-chip[aria-current="true"] .week-chip__kw,
.week-chip[aria-current="true"] .week-chip__date,
.week-chip[aria-current="true"] .week-chip__mine,
.week-chip[aria-current="true"] .week-chip__title {
    color: #fff;
}

/* Termine der Woche + Legende nebeneinander, brechen auf schmalen Bildschirmen um. */
.week-info {
    margin: 0 0 1.25rem;
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem 2rem;
    align-items: flex-start;
    justify-content: space-between;
}

.week-appointments { display: grid; gap: 0.2rem; }

.week-appointments__label {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-muted);
}

.week-appointment { color: var(--color-booked-text); font-weight: 600; font-size: 0.95rem; }
.week-appointment__date { font-variant-numeric: tabular-nums; }

.legend {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    margin-bottom: 1.25rem;
    font-size: 0.85rem;
    color: var(--color-text-secondary);
}

.legend__item {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.legend__swatch {
    width: 10px;
    height: 10px;
    border-radius: 3px;
    border: 1px solid transparent;
    display: inline-block;
}

.legend__swatch--frei { background: var(--color-free-bg); border-color: var(--color-free-border); }
.legend__swatch--belegt { background: var(--color-booked-bg); border-color: var(--color-booked-border); }
.legend__swatch--eigen { background: var(--color-primary); border-color: var(--color-primary-dark); }
.legend__swatch--blockiert { background: var(--color-blocked-bg); border-color: var(--color-blocked-border); }
.legend__swatch--vergangen { background: var(--color-past-bg); border-color: var(--color-past-border); }

.legend__why {
    flex-basis: 100%;
    margin: 0.3rem 0 0;
    font-size: 0.76rem;
    color: var(--color-muted);
    max-width: 62ch;
}

/* Die fünf obigen Modifier (--frei/--belegt/--eigen/--blockiert/--vergangen)
   gehören der Mitglieder-Wochenansicht (templates/kalender/index.html.twig).
   Die folgenden sind für die Legende des Admin-Monatskalenders
   (dashboard.html.twig) — der Standard-Swatch (10×10px) ist dort zu klein,
   um Füllung von Rahmen zu unterscheiden, deshalb die breitere Box. */
.legend__swatch--open,
.legend__swatch--closed,
.legend__swatch--past {
    width: 1.1rem;
    height: 0.8rem;
}

.legend__swatch--open { background: var(--color-free-bg); border-color: var(--color-free-border); }
.legend__swatch--closed { background: var(--color-surface); border-color: var(--color-border-input); }
/* --color-free-bg auf Weiß gemischt statt per opacity: eine reine
   Deckkraftreduktion hätte auf dem Legendenhintergrund einen anderen
   Farbton ergeben als das tatsächliche, auf --color-bg gemischte Grün eines
   vergangenen Öffnungstags in der Zeile (siehe --day-row--past.--open). */
.legend__swatch--past { background: #f6faf5; border-color: #ddeada; }

.legend__swatch--today {
    width: 0.9rem;
    height: 0.9rem;
    border-radius: 50%;
    background: var(--color-primary);
}

/* Kein Swatch — das Wort selbst steht in der Titelfarbe, passend dazu, dass
   ein Tagestitel im Kalender selbst auch nur farbiger Text ist. */
.legend__title-sample {
    color: var(--color-booked-text);
    font-weight: 600;
}

.day-list { display: grid; gap: 1.1rem; }

.day-card__head {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: flex-start;
    gap: 0.5rem 1.5rem;
}

.day-card__who { display: grid; gap: 0.15rem; min-width: 0; }

.day-card__name {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

.day-card__name h2 { margin: 0; font-size: 1.1rem; font-weight: 700; }

.day-card__today {
    background: var(--color-primary);
    color: #fff;
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    padding: 0.12rem 0.5rem;
    border-radius: 999px;
}

/* Der Tagestitel steht in derselben Farbe wie im Admin-Monatskalender
   (--color-booked-text, siehe .day-row--titled dort) — derselbe Titel, also
   dieselbe visuelle Sprache. */
.day-card__title {
    margin: 0;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--color-booked-text);
    font-weight: 600;
    font-size: 0.98rem;
}

.day-card__summary {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.35rem 0.75rem;
    font-size: 0.8rem;
    color: var(--color-muted);
    font-variant-numeric: tabular-nums;
}

.day-card__summary b { color: var(--color-text); font-weight: 700; }

/* <progress> statt div+Inline-Style (CSP erlaubt kein style=), Fill-Farbe
   über die browserspezifischen Pseudo-Elemente. */
.day-card__meter {
    width: 64px;
    height: 6px;
    vertical-align: middle;
}

.day-card__meter,
.day-card__meter::-webkit-progress-bar {
    background: var(--color-border);
    border-radius: 3px;
    border: 0;
}

.day-card__meter::-webkit-progress-value { background: var(--color-primary); border-radius: 3px; }
.day-card__meter::-moz-progress-bar { background: var(--color-primary); border-radius: 3px; }

.day-card__scroll {
    /* Fallback für sehr viele Slots: bei den üblichen 4-6 Slots/Tag bleibt
       die Zeile dank flex-basis (siehe .slot, .slot-grid__time) innerhalb der
       Kartenbreite, auch auf einem 390px breiten Handy. */
    overflow-x: auto;
    margin-top: 0.9rem;
}

.slot-grid {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.slot-grid__row {
    display: flex;
    gap: 4px;
    align-items: stretch;
}

/* Namensspalte fix und breit genug für die meisten Kurzlabel — Bahnen ohne
   Bahntyp (voller Name statt Kurzlabel) dürfen hier auf zwei Zeilen umbrechen
   (kein white-space: nowrap), sonst würde ein langer Name die Spalte sprengen. */
.slot-grid__corner,
.slot-grid__label {
    flex: 0 0 6.5rem;
    min-width: 0;
}

.slot-grid__label {
    display: flex;
    align-items: center;
    font-size: 0.86rem;
    font-weight: 600;
    line-height: 1.2;
    padding-right: 0.4rem;
    color: inherit;
    text-decoration: none;
    /* Lange Namen ohne Bahntyp (voller Name statt Kurzlabel, z. B.
       "Fallscheibenanlage 1") dürfen mitten im Wort umbrechen, statt unter
       die erste Slot-Zelle zu rutschen — bei einem kurzen Label wie "Bahn 1"
       greift das nie. */
    overflow-wrap: anywhere;
    hyphens: auto;
}

.slot-grid__label:hover,
.slot-grid__label:focus-visible {
    color: var(--color-primary);
    text-decoration: underline;
}

.slot-grid__time {
    flex: 1 0 2.75rem;
    /* Wie bei .slot (siehe dort): ohne overflow: hidden/visible-Angabe wäre
       die automatische Mindestbreite sonst der Inhalt ("18:00") statt 0 —
       auf sehr schmalen Bildschirmen könnte das die Zeitspalten sonst leicht
       gegenüber den Slot-Spalten darunter verschieben. */
    min-width: 0;
    text-align: center;
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--color-text-secondary);
    font-variant-numeric: tabular-nums;
    padding-bottom: 0.1rem;
}

.slot-grid__time.is-past { color: #a2aa9c; font-weight: 600; }

.slot-grid__group {
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--color-muted);
    padding: 0.6rem 0 0.05rem;
}

.slot-grid__group:first-child { padding-top: 0.2rem; }

/* flex: 1 0 <min> statt grid-template-columns (die CSP erlaubt kein
   style=, eine dynamische Spaltenzahl könnte sonst nicht je Tag gesetzt
   werden): die Zellen wachsen gleichmäßig, schrumpfen aber nicht unter ihre
   Basisbreite — erst dort greift .day-card__scroll als Scroll-Fallback. */
.slot {
    appearance: none;
    flex: 1 0 2.75rem;
    /* Ohne das fiele eine Zelle mit overflow: visible (.slot--run:not(.slot--join),
       für den über die Zelle hinauslaufenden Namen eines Laufs) auf ihre
       automatische Inhalts-Mindestbreite zurück (min-width: auto ist bei
       overflow: visible der min-content, sonst 0) — ein langer Name ließe die
       Zelle dann über ihre flex-basis hinaus wachsen und verschöbe die
       übrigen Zellen derselben Zeile gegenüber anderen Zeilen. */
    min-width: 0;
    height: 2.3rem;
    margin: 0;
    border-radius: 7px;
    border: 1px solid transparent;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 0.45rem;
    font-size: 0.76rem;
    font-weight: 600;
    line-height: 1.1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    background: transparent;
    color: inherit;
    text-decoration: none;
}

.slot--frei { background: var(--color-free-bg); border-color: var(--color-free-border); color: var(--color-free-text); }

/* "frei" wird bei Hover/Fokus durch "Buchen" ersetzt — beide Texte stehen
   im Markup, damit kein JavaScript für diesen Hinweis nötig ist. */
.slot__hover { display: none; }

a.slot--frei:hover,
a.slot--frei:focus-visible {
    background: #dcebd5;
    border-color: #8fbf86;
}

a.slot--frei:hover .slot__rest,
a.slot--frei:focus-visible .slot__rest { display: none; }

a.slot--frei:hover .slot__hover,
a.slot--frei:focus-visible .slot__hover { display: inline; }

.slot--belegt {
    background: var(--color-booked-bg);
    border-color: var(--color-booked-border);
    color: var(--color-booked-text);
    justify-content: flex-start;
}

/* Eigene Buchung: grün gefüllt (--color-primary) statt nur umrandet, damit
   sie in der Zeile sofort auffällt — Link auf "Meine Buchungen", Mitglieder
   stornieren nicht selbst (siehe Plan, Storno bleibt Vorstandssache). */
.slot--eigen {
    background: var(--color-primary);
    border-color: var(--color-primary-dark);
    color: #fff;
    justify-content: flex-start;
}

a.slot--eigen:hover,
a.slot--eigen:focus-visible { background: var(--color-primary-dark); }

.slot--blockiert {
    background: repeating-linear-gradient(135deg, var(--color-blocked-bg) 0 4px, #fbf4f2 4px 9px);
    border-color: var(--color-blocked-border);
}

.slot.is-past { opacity: 0.5; }
.slot--frei.is-past { background: var(--color-past-bg); border-color: var(--color-past-border); }
.slot--blockiert.is-past { background: repeating-linear-gradient(135deg, var(--color-past-bg) 0 4px, #f1f2ee 4px 9px); border-color: var(--color-past-border); }

/* Der sichtbare Name in belegten/eigenen Zellen steht in einem eigenen Span
   statt als direktem Textknoten von .slot: .slot ist ein Flex-Container, und
   text-overflow greift auf dem anonymen Flex-Item eines nackten Textknotens
   nicht zuverlässig — erst ein echtes Element mit eigenem min-width: 0 lässt
   sich unter seine Inhaltsbreite schrumpfen und damit sauber kürzen. Bei
   sehr langen Namen auf schmalen Bildschirmen unvermeidlich gekürzt, aber
   sichtbar als "…" statt unvermittelt hart abgeschnitten. */
.slot__name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Aufeinanderfolgende Slots derselben Person (Verschmelzung siehe
   MemberWeekViewBuilder::cellsForRange()) verschmelzen NUR optisch. Jede
   Zelle behält exakt dieselbe Flex-Größe wie eine unverschmolzene — anders
   als mit der früheren negativen margin-left, die den freien Flex-Raum der
   ganzen Zeile neu verteilt und die Spalten gegenüber unverschmolzenen
   Zeilen und der Zeitkopfzeile verschoben hatte. Die Lücke zwischen den
   Zellen (.slot-grid__row: gap: 4px) überbrückt stattdessen ein ::before auf
   der zweiten Zelle, ohne selbst Platz im Flex-Layout zu beanspruchen. */
.slot--join {
    position: relative;
    border-left-color: transparent;
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
    /* .slot selbst setzt overflow: hidden (fürs Ellipsis-Kürzen) — das würde
       das ::before hier ohne Gegenregel abschneiden, weil es außerhalb der
       eigenen Box sitzt (siehe left: -5px unten). Die Zelle selbst zeigt
       ohnehin keinen eigenen sichtbaren Text mehr (.visually-hidden im
       Template), ein überstehendes ::before ist also unbedenklich. */
    overflow: visible;
}

.slot--join::before {
    content: '';
    position: absolute;
    top: -1px;
    bottom: -1px;
    /* Etwas großzügiger als die 4px Lücke, damit die Bridge auch die
       (transparente) Randbreite beider Nachbarzellen mit abdeckt, statt auf
       den Pixel genau an sie anzustoßen. */
    left: -5px;
    width: 6px;
    background: inherit;
    border-top: 1px solid transparent;
    border-bottom: 1px solid transparent;
}

.slot--belegt.slot--join::before { border-color: var(--color-booked-border); }
.slot--eigen.slot--join::before { border-color: var(--color-primary-dark); }

/* border-radius/-color gelten für JEDE Zelle mit weiterem Nachbarn (auch die
   mittlere Zelle eines 3+-Blocks, die sowohl .slot--run als auch .slot--join
   trägt) — sonst behielte genau diese mittlere Zelle ihren rechten Rand samt
   Rundung. Nur die ERSTE Zelle eines Laufs (ohne .slot--join) bekommt
   zusätzlich position/z-index/overflow, damit ihr Name über die folgenden
   verschmolzenen Zellen hinweglaufen kann; deren .slot__name folgt separat. */
.slot--run {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
    border-right-color: transparent;
}

.slot--run:not(.slot--join) {
    position: relative;
    z-index: 1;
    overflow: visible;
    text-overflow: clip;
}

.slot--run:not(.slot--join) .slot__name {
    overflow: visible;
    text-overflow: clip;
}

@media (max-width: 600px) {
    .week-head { flex-direction: column; align-items: stretch; }
    .week-head__kw { font-size: 2.1rem; }
    .week-head__nav .btn { flex: 1 1 0; justify-content: center; padding-inline: 0.5rem; }

    .day-card { padding: 0.85rem 0.7rem 1rem; border-radius: 12px; }
    .day-card__name h2 { font-size: 1rem; }

    /* Schmalere Namensspalte, dafür etwas höhere Zellen (größere Touch-Ziele) —
       vier Slots bleiben damit auch bei 390px ohne horizontales Scrollen sichtbar. */
    .slot-grid__corner,
    .slot-grid__label {
        flex-basis: 4.3rem;
    }

    .slot-grid__time,
    .slot {
        flex-basis: 2.4rem;
    }

    .slot { height: 2.6rem; font-size: 0.72rem; padding: 0 0.3rem; }
    /* .legend gehört auch dem Admin-Monatskalender (dashboard.html.twig) —
       ohne die Beschränkung auf .week-info würde diese Regel dessen Legende
       ungewollt mitverkleinern. */
    .week-info .legend { gap: 0.4rem 0.8rem; font-size: 0.76rem; }
}

/* --- Monatskalender (Admin) --- */

.month-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 1rem;
    flex-wrap: wrap;
    gap: 0.75rem;
}

.month-nav__title { font-weight: 600; }

.month-nav__actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.month-nav__today {
    color: var(--color-primary);
}

span.month-nav__today {
    cursor: default;
}

/* Drei gleich breite Spalten ohne grid-template-columns und ohne Inline-Style
   (die CSP erlaubt kein style=) — wie beim bestehenden Wochenraster
   (siehe Kommentar bei .day-grid). */
.month-grid {
    display: flex;
    gap: 1rem;
    align-items: flex-start;
}

.month-grid__month {
    flex: 1 1 0;
    min-width: 0;
}

/* flex-wrap statt eine Zeile erzwingen: bei mittleren Breiten (drei Spalten,
   aber noch kein Umbruch auf @media 720px) reicht der Platz neben einem
   langen Monatsnamen plus Summe sonst nicht, und die Summe liefe aus der
   Karte statt in eine zweite Zeile zu rutschen. */
.month-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    row-gap: 0.1rem;
    margin: 0 0 0.75rem;
}

.month-head h2 {
    font-size: 1rem;
    margin: 0;
    white-space: nowrap;
}

.month-head__sum {
    flex: 0 0 auto;
    font-size: 0.75rem;
    color: var(--color-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.month-days {
    display: flex;
    flex-direction: column;
}

/* Ein Wochenblock ist ein zweispaltiges Grid aus KW-Label und Tagesliste, durch
   eine Trennlinie vom vorigen Block abgesetzt — dadurch werden Wochengrenzen
   sichtbar, ohne dass (wie vorher) jeder einzelne Tag einen Rahmen bräuchte. */
.month-week {
    display: grid;
    grid-template-columns: 3.3rem 1fr;
    padding: 0.3rem 0;
    border-top: 1px solid var(--color-border);
}

.month-week:first-child {
    border-top: 0;
    padding-top: 0;
}

.month-week:last-child {
    padding-bottom: 0;
}

.month-week__kw {
    padding-top: 0.3rem;
    font-size: 0.72rem;
    color: var(--color-muted);
    font-variant-numeric: tabular-nums;
}

.month-week--current .month-week__kw {
    color: var(--color-primary);
    font-weight: 700;
}

.month-week__days {
    display: flex;
    flex-direction: column;
    gap: 1px;
    /* Ohne das würde die 1fr-Spur des umgebenden Grids sich am Min-Content
       der langen, nicht umbrechenden Titelzeile orientieren ("Grid-Blowout")
       und über die Kartenbreite hinauswachsen, statt dass der Titel per
       ellipsis abgeschnitten wird. */
    min-width: 0;
}

/* Die Zeile ist ein Flex-Container aus drei Kindern (Link, Stift, Formular) statt
   selbst ein <a> — ein Button/<a> darf nicht in einem <a> verschachtelt sein, und
   der Stift-Button braucht einen eigenen Klickbereich neben dem Tageslink. Anders
   als vorher trägt die Zeile selbst keinen Rahmen und keine Füllung mehr — nur
   --open bekommt eine Füllung, alle anderen Zustände färben höchstens einzelne
   Kinder (siehe Kaskaden-Kommentar weiter unten). min-height statt fester Höhe,
   damit das eingeblendete Titelformular genug Platz hat. */
.day-row {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.15rem;
    min-height: 1.7rem;
    padding: 0 0.3rem 0 0.2rem;
    border-radius: 6px;
    font-size: 0.85rem;
}

.day-row:hover {
    background: var(--color-bg);
}

.day-row--open:hover {
    background: #e4efe0;
}

/* [hidden] muss unabhängig davon greifen, was die Elemente sonst für ein
   display bekommen (flex, inline-flex, …) — sonst gewinnt die speziellere
   Author-Regel gegen die UA-Regel "[hidden] { display: none }". */
.day-row__link[hidden],
.day-row__edit[hidden],
.day-row__form[hidden] {
    display: none !important;
}

.day-row__link {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex: 1 1 0;
    min-width: 0;
    padding: 0;
    text-decoration: none;
    color: inherit;
}

.day-row__edit {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: var(--radius-control);
    border: 1px solid transparent;
    color: var(--color-muted);
    text-decoration: none;
    font-size: 0.85rem;
    line-height: 1;
}

.day-row__edit:hover,
.day-row__edit:focus-visible {
    background: var(--color-surface);
    border-color: var(--color-border-input);
    color: var(--color-text);
}

/* Der Stift soll die Zeilenliste nicht mit 30+ Stiften vollstellen, muss aber
   per Tastatur erreichbar bleiben und auf Touch-Geräten (kein echtes :hover)
   dauerhaft sichtbar sein — deshalb das Ausblenden nur unter
   @media (hover: hover), nicht generell per Klasse. */
@media (hover: hover) {
    .day-row__edit {
        opacity: 0;
    }

    .day-row:hover .day-row__edit,
    .day-row:focus-within .day-row__edit,
    .day-row__edit:focus-visible {
        opacity: 1;
    }
}

.day-row__form {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    flex: 1 1 0;
    min-width: 0;
}

.day-row__form input[type="text"] {
    flex: 1 1 0;
    min-width: 0;
    padding: 0.25rem 0.5rem;
    border: 1px solid var(--color-border-input);
    border-radius: var(--radius-control);
    font: inherit;
    font-size: 0.85rem;
    background: #fff;
    color: var(--color-text);
}

.day-row__form input[type="text"]:focus {
    outline: 2px solid var(--color-primary);
    outline-offset: 1px;
    border-color: var(--color-primary);
}

.day-row__form .btn--small,
.day-row__cancel {
    flex: 0 0 auto;
    white-space: nowrap;
}

.day-row__num {
    flex: 0 0 1.6rem;
    text-align: right;
    font-weight: 700;
}

.day-row__wd {
    flex: 0 0 2.5ch;
    color: var(--color-muted);
}

.day-row__title {
    flex: 1 1 0;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.day-row__today {
    flex: 0 0 auto;
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-primary);
    white-space: nowrap;
}

/* Die Zahl steht in --color-text (kräftiger Kontrast, sie ist die eigentliche
   Information), nur das Icon bleibt gedämpft (--color-muted auf dem Icon
   selbst, siehe unten — es überschreibt das geerbte currentColor sonst mit
   der kräftigen Textfarbe). */
.day-row__count {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--color-text);
    white-space: nowrap;
}

.day-row__count-icon,
.legend__icon {
    width: 0.75rem;
    height: 0.75rem;
    flex: none;
    color: var(--color-muted);
    fill: currentColor;
}

/* Reihenfolge ist absichtlich, auch wenn die Zustände inzwischen keine
   Füllung/Rahmen mehr teilen, sondern unterschiedliche Eigenschaften setzen:
   --closed/--open schließen sich im Template gegenseitig aus, --past dämpft
   danach nur noch den Link (das offene Titelformular soll dabei nicht mit
   verblassen) — außer in Kombination mit --open, wo die Füllung selbst
   gedämpft wird, weil opacity auf dem Link allein eine kräftige grüne Zeile
   mit nur noch verblasstem Text ergäbe. --titled färbt zuletzt nur noch den
   Titeltext (ein gepflegter Termin bleibt so auch an einem vergangenen oder
   geschlossenen Tag lesbar). Die Zahlenfarbe (`__num`) wird an drei Stellen
   gesetzt — --open (grün), --sunday (rot), --today (weiß auf grünem Kreis) —
   und --today muss dabei gewinnen, deshalb schließt die Sonntagsregel
   --today per :not() ausdrücklich aus, statt sich auf die Kaskade zu
   verlassen (beide Selektoren sind sonst annähernd gleich spezifisch).
   --selected setzt mit outline eine unabhängige Eigenschaft. */
.day-row--closed {
    color: var(--color-text-secondary);
}

.day-row--open {
    background: var(--color-free-bg);
}

.day-row--open .day-row__num,
.day-row--open .day-row__wd {
    color: var(--color-primary);
    font-weight: 700;
}

.day-row--past .day-row__link {
    opacity: 0.5;
}

/* --color-free-bg auf --color-bg gemischt statt zusätzlich zur
   Link-Opacity auch die Füllung zu dämpfen (das würde den Grünton doppelt
   abschwächen) — ein vergangener Öffnungstag bleibt dadurch als solcher
   erkennbar, aber deutlich blasser als ein aktueller. */
.day-row--past.day-row--open {
    background: #f6faf5;
}

.day-row--past.day-row--open:hover {
    background: var(--color-free-bg);
}

.day-row--titled .day-row__title {
    color: var(--color-booked-text);
    font-weight: 600;
}

/* Am offenen Sonntag bleibt die Zahl grün wie an jedem anderen Öffnungstag —
   erst ein geschlossener Sonntag macht mit der roten Zahl auf die Ausnahme
   vom sonstigen Wochenrhythmus aufmerksam. :not(.day-row--today) zusätzlich,
   weil sonst ein geschlossener, heutiger Sonntag die rote Zahl auf dem
   grünen Heute-Kreis zeigen würde (schlechter Kontrast) — die Spezifität
   beider Selektoren liegt sonst zu nah beieinander, um sich allein auf die
   Reihenfolge zu verlassen. */
.day-row--sunday:not(.day-row--open):not(.day-row--today) .day-row__num {
    color: var(--color-danger);
}

.day-row--today .day-row__num {
    display: inline-grid;
    place-items: center;
    height: 1.35rem;
    border-radius: 999px;
    background: var(--color-primary);
    color: #fff;
}

/* z-index: ohne das würde die nachfolgende Zeile (gleiche Stapelreihenfolge,
   aber später im DOM) den Outline-Rahmen der ausgewählten Zeile an der
   gemeinsamen Kante überdecken — .day-row ist bereits position: relative,
   z-index greift also ohne weitere Positionierung. */
.day-row--selected {
    z-index: 1;
    outline: 2px solid var(--color-primary);
    outline-offset: 1px;
}

/* Schwebende Karte mit den Buchungen eines Tages (app.js, dayFragment()).
   position: fixed statt absolute+document.body: die Positionierung rechnet
   dadurch nur mit dem Viewport (getBoundingClientRect()), ohne scrollX/
   scrollY der Seite einzurechnen — einfacher und robust, siehe app.js. */
.day-overlay {
    position: fixed;
    z-index: 30;
    width: 480px;
    max-width: calc(100vw - 1.5rem);
    max-height: min(560px, calc(100vh - 1.5rem));
    /* Die Karte selbst scrollt NICHT — sonst wanderte der absolut
       positionierte Schliessen-Knopf bei langen Buchungslisten mit dem
       Inhalt aus dem Bild. Gescrollt wird nur der Rumpf darunter. */
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-card);
    padding: 1rem 1.1rem;
}

.day-overlay__close {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    width: 1.75rem;
    height: 1.75rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--color-surface);
    border: 1px solid var(--color-border-input);
    border-radius: var(--radius-control);
    color: var(--color-muted);
    cursor: pointer;
    font-size: 0.9rem;
    line-height: 1;
}

.day-overlay__close:hover,
.day-overlay__close:focus-visible {
    color: var(--color-text);
    background: var(--color-bg);
}

.day-overlay__body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

.day-overlay__body h2 {
    margin: 0 1.75rem 0.75rem 0;
    font-size: 1rem;
}

.day-overlay__body .table-wrap {
    margin: 0 0 0.75rem;
    box-shadow: none;
}

/* Die Buchungstabelle steht hier in einer schmalen Karte statt auf einer
   ganzen Seite: engere Zellen, und die Uhrzeit darf nicht umbrechen — sonst
   wird aus "18:00–18:30" eine vierzeilige Spalte und der Mitgliedsname
   rutscht aus dem sichtbaren Bereich. */
.day-overlay__body th,
.day-overlay__body td {
    padding: 0.45rem 0.5rem;
}

.day-overlay__body td:first-child,
.day-overlay__body th:first-child {
    white-space: nowrap;
}

.day-overlay__body td:last-child,
.day-overlay__body th:last-child {
    text-align: right;
}

/* --- Bahn / Bilder --- */

.range-images {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin: 1rem 0;
}

.range-images img {
    width: 160px;
    height: 120px;
    object-fit: cover;
    border-radius: var(--radius-control);
    border: 1px solid var(--color-border);
}

/* --- Badges --- */

.badge {
    display: inline-block;
    padding: 0.2rem 0.6rem;
    border-radius: var(--radius-control);
    font-size: 0.78rem;
    font-weight: 600;
    border: 1px solid transparent;
}

.badge--active { background: var(--color-free-bg); border-color: var(--color-free-border); color: var(--color-free-text); }
.badge--inactive { background: var(--color-blocked-bg); border-color: var(--color-blocked-border); color: var(--color-blocked-text); }

.filter-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    align-items: flex-end;
    margin-bottom: 1.25rem;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-card);
    padding: 1rem 1.25rem;
}

.filter-bar .form-group { margin-bottom: 0; }

.page-error {
    max-width: 640px;
    margin: 4rem auto;
    text-align: center;
}

.page-error--debug {
    max-width: 900px;
    text-align: left;
}

.error-trace {
    background: #232823;
    color: #d9e6d3;
    padding: 1rem;
    overflow-x: auto;
    border-radius: var(--radius-control);
    font-size: 0.85rem;
}

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

/* --- Einstellungen (Admin) --- */

.settings-layout {
    display: grid;
    grid-template-columns: 240px minmax(0, 1fr);
    gap: 1.5rem;
    align-items: start;
}

.settings-nav {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-card);
    padding: 0.6rem;
    position: sticky;
    top: 1rem;
}

.settings-nav__item {
    display: block;
    padding: 0.55rem 0.7rem;
    border-radius: var(--radius-control);
    border-left: 3px solid transparent;
    color: var(--color-text-secondary);
    text-decoration: none;
}

.settings-nav__item:hover { background: var(--color-bg); color: var(--color-text); }

.settings-nav__item--active {
    background: var(--color-free-bg);
    border-left-color: var(--color-primary);
    color: var(--color-text);
}

.settings-nav__label { display: block; font-weight: 600; }

.settings-nav__hint {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.78rem;
    color: var(--color-muted);
    line-height: 1.35;
}

.settings-content > h2 { margin-top: 0; }

.settings-group h3 { margin-top: 0; }

.settings-group + .settings-group { margin-top: 1rem; }

.settings-actions {
    position: sticky;
    bottom: 0;
    background: var(--color-bg);
    padding: 0.75rem 0;
    border-top: 1px solid var(--color-border);
}

.form-help {
    margin: 0.3rem 0 0;
    font-size: 0.8rem;
    color: var(--color-muted);
}

.lead { color: var(--color-text-secondary); max-width: 62ch; }

/* --- Notices (Status-/Warnhinweise, z. B. Einstellungen → E-Mail) --- */

.notice {
    padding: 0.65rem 0.9rem;
    border-radius: var(--radius-control);
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    margin: 0 0 0.75rem;
    font-size: 0.9rem;
}

.notice p { margin: 0.3rem 0 0; }
.notice p:first-child { margin-top: 0; }

.notice--ok {
    background: var(--color-free-bg);
    border-color: var(--color-free-border);
    color: var(--color-free-text);
}

.notice--warning {
    background: var(--color-booked-bg);
    border-color: var(--color-booked-border);
    color: var(--color-booked-text);
}

.notice--error {
    background: var(--color-blocked-bg);
    border-color: var(--color-blocked-border);
    color: var(--color-blocked-text);
}

/* --- Mailvorschau (Einstellungen → E-Mail) --- */

.mail-preview {
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-control);
    padding: 0.85rem 1rem;
    margin: 0;
    font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
    font-size: 0.85rem;
    white-space: pre-wrap;
    word-break: break-word;
    overflow-x: auto;
}

.form-group textarea.textarea-mono {
    font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
}

/* Knopfzeile innerhalb einer Karte — anders als .settings-actions (die
   klebende Leiste der langen Texte-Seite) ohne eigenen Hintergrund. */
.form-actions { margin-top: 1rem; }
.form-actions .form-help { margin-top: 0.5rem; }

@media (max-width: 720px) {
    .settings-layout { grid-template-columns: 1fr; }
    .settings-nav { position: static; }
    /* Untereinander statt nebeneinander. `align-items: stretch` muss hier
       ausdruecklich zurueckgesetzt werden: in Spaltenrichtung ist die
       Querachse waagerecht, und das `align-items: flex-start` der
       Desktop-Regel (dort verhindert es, dass kurze Monate auf die Hoehe des
       laengsten gestreckt werden) laesst die Karten sonst auf Inhaltsbreite
       schrumpfen statt die volle Breite zu nutzen. */
    .month-grid {
        flex-direction: column;
        align-items: stretch;
    }

    /* Auf dem Handy ist die Monatsspalte schon 100% breit — "340px daneben"
       (die Desktop-Positionierung aus app.js) ergibt dort keinen Sinn, es
       gäbe keinen Platz und keine dritte Spalte zum Ausweichen mehr. Das
       Overlay wird stattdessen zum bildschirmfüllenden Panel am unteren
       Rand. !important überstimmt bewusst das von app.js per style.top/
       style.left gesetzte Inline-Style (Inline-Style schlägt sonst jede
       Klassenregel). */
    .day-overlay {
        top: auto !important;
        left: 0.75rem !important;
        right: 0.75rem;
        bottom: 0.75rem;
        width: auto;
        max-width: none;
        max-height: 70vh;
    }
}
