/* neoliet Backoffice — App-Styles (ergaenzt Tabler v1.4). */

/* Keine Markenfarben-Overrides: --tblr-primary bleibt der Tabler-Standard. */

.code-input { letter-spacing: .35em; text-align: center; font-variant-numeric: tabular-nums; }
.env-badge { text-transform: uppercase; letter-spacing: .05em; }

#userBadge { cursor: pointer; }

/* Gruppentitel im Hauptmenue: Tabler zeichnet .dropdown-header schon als graue
   Versalien - die Toenung kommt aus den Utilities am Element selbst
   (bg-secondary-lt text-secondary, wie die hellen Badges). Hier nur, was keine
   Utility abdeckt: Luft davor, falls ein Titel ohne Trenner direkt auf einen
   Eintrag folgt. */
.dropdown-menu .dropdown-item + .dropdown-header { margin-top: .5rem; }

/* Randlose Icon-Buttons in der Kopfzeile (Home/Menue/Logout) */
.btn-icon2 {
  background: none; border: 0; padding: .25rem .4rem; line-height: 1;
  color: var(--tblr-secondary); cursor: pointer; border-radius: var(--tblr-border-radius);
}
.btn-icon2:hover { background: var(--tblr-active-bg); color: var(--tblr-body-color); }
/* Deaktiviert (z.B. Zuruecksetzen, solange nichts ueberschrieben ist): sichtbar
   inaktiv und ohne Hover-Flaeche. */
.btn-icon2:disabled { opacity: .35; cursor: default; }
.btn-icon2:disabled:hover { background: none; color: var(--tblr-secondary); }
/* Gedrueckter Zustand (z.B. Zeitraum-Toggle aus): Akzentfarbe */
.btn-icon2.active { color: var(--tblr-primary); }

/* Die .4rem Polsterung der Icon-Buttons addiert sich zum gap-3 der Kopfzeile: neben
   einem Icon steht damit 1.4rem, zwischen Menue und Abmelden sogar 1.8rem statt der
   1rem zwischen den Textelementen. Die Polsterung (Hover-Flaeche) bleibt, wird aber
   per negativem Rand aus dem Abstand herausgerechnet - Ergebnis: ueberall 1rem von
   Glyphe zu Glyphe. Die aeusseren Raender bleiben unangetastet. */
.navbar .btn-icon2 { margin-inline: -.4rem; }
.navbar #homeBtn { margin-inline-start: 0; }
.navbar #logoutBtn { margin-inline-end: 0; }

/* Ticket-Detail: Faktenzeilen im Kopf (Ticket / Termin / Personen / Geld).
   Icon-Groesse in rem, nicht em - die Modal-Schrift ist klein, 1.15em war winzig. */
.tkt-facts { margin-bottom: .75rem; }
.tkt-facts > div { display: flex; align-items: center; gap: .6rem; margin-bottom: .3rem; }
.tkt-facts .ti { color: var(--tblr-secondary); font-size: 1.4rem; flex-shrink: 0; }

/* Ticket-Detail: Verlauf- und Zahlungstabelle mit identischen Spaltenbreiten
   (fixed layout; die Breiten haengen an der Titelzeile). Details nimmt den Rest. */
.tkt-table { table-layout: fixed; }
.tkt-table th:nth-child(1) { width: 8rem; }
.tkt-table th:nth-child(3) { width: 9rem; }
.tkt-table th:nth-child(4) { width: 6rem; }
.tkt-table td { overflow-wrap: break-word; }

/* Storno-Dialog: Zahlenfeld je Personenkategorie. Feste Breite, damit die Felder
   untereinander fluchten - ein statischer Wert, also eine Klasse und kein
   style-Attribut (die CSP des backoffice-vhosts erlaubt keine Inline-Styles). */
.tkt-qty { width: 5rem; }

/* Profil-Popover am User-Badge */
.user-profile-popover .popover-body { padding: .5rem .75rem; }
.user-profile-popover table { margin-bottom: 0; }
.user-profile-popover th { font-weight: 600; padding-right: .75rem; white-space: nowrap; }

/* Tageskurve: kleiner Abstand oben */
#chart-occupancy { padding-top: 5px; }

/* Zugeklappte Karte (core/collapse.js): der Kopf soll seinen unteren Rand
   verlieren, sonst schwebt eine Linie unter dem Titel ohne Inhalt darunter.
   Das AUSBLENDEN selbst macht das Modul mit d-none an den Kindern - eine Regel
   hier waere der Fuellkette (.app-fill-view > .card > .card-body, teils mit
   View-ID davor) in der Spezifitaet unterlegen und wirkte still nicht. */
.card-is-collapsed > .card-header { border-bottom: 0; }

/* Legenden-Punkt fuer die Belegung-nach-Typ-Karte */
.legend-dot { display: inline-block; width: .75rem; height: .75rem; border-radius: .25rem; vertical-align: -1px; }

/* Dashboard-Ladezustand: der Karteninhalt wird ausgeblendet und durch EINEN grauen
   Block je Karte ersetzt, ueber den ein hellerer Streifen laeuft.
   visibility statt display: die Karte behaelt ihre Hoehe, es springt nichts, wenn
   die Daten eintreffen. Der Block sitzt auf der KARTE, nicht am card-body - die
   Tabellenkarte haelt ihre Zeilen ausserhalb des card-body und haette sonst
   waehrend des Ladens weiter die alten Daten gezeigt.

   Eigener Schimmer statt Bootstraps .placeholder-wave: deren Maske moduliert die
   Deckkraft nur von 100% auf 90%. Das reicht auf einem .placeholder mit 50%
   Deckkraft - auf unserem hellen Block ist ein 10%-Unterschied unsichtbar. */
/* Zwei Koerner:
   - .card             ganze Karte, ausser sie traegt .dash-noskel
     (die beiden Tabellen melden den Ladezustand selbst in der Zeile "Lade…";
      die Willkommenskarte soll Begruessung und Satz stehen lassen)
   - .dash-skel        EIN Block innerhalb einer solchen Karte - fuer die zwei
     Kennzahlen unter der Begruessung, die als Einzige auf Daten warten. */
.dash-loading .card:not(.dash-noskel),
.dash-loading .dash-skel { position: relative; }
.dash-loading .card:not(.dash-noskel) > *,
.dash-loading .dash-skel > * { visibility: hidden; }
.dash-loading .card:not(.dash-noskel)::after,
.dash-loading .dash-skel::after {
  content: ""; position: absolute; inset: .75rem;
  border-radius: var(--tblr-border-radius);
  background: linear-gradient(90deg,
    var(--app-skel-base) 25%, var(--app-skel-hi) 50%, var(--app-skel-base) 75%);
  background-size: 220% 100%;
  animation: app-shimmer 1.4s linear infinite;
}
/* Der kleine Block braucht weniger Rand als eine Karte, sonst bleibt kaum
   Flaeche uebrig, ueber die der Streifen laufen kann. */
.dash-loading .dash-skel::after { inset: .25rem 0; }
:root { --app-skel-base: rgba(0, 0, 0, .07); --app-skel-hi: rgba(0, 0, 0, .14); }
[data-bs-theme="dark"] { --app-skel-base: rgba(255, 255, 255, .07); --app-skel-hi: rgba(255, 255, 255, .16); }

@keyframes app-shimmer {
  from { background-position: 120% 0; }
  to   { background-position: -20% 0; }
}

/* Bewegung respektiert die Systemeinstellung (WCAG 2.3.3): ohne Animation bleibt
   der graue Block stehen - der Ladezustand ist weiterhin erkennbar. */
@media (prefers-reduced-motion: reduce) {
  .dash-loading .card:not(.dash-noskel)::after,
  .dash-loading .dash-skel::after { animation: none; background: var(--app-skel-base); }
}

/* Chart.js zeichnet auf Canvas und hat KEINE eigene Hoehe: sie kommt vom
   umgebenden .chart-box (mit maintainAspectRatio:false im JS). Ohne feste Hoehe
   am Container waechst das Canvas endlos. position:relative ist Pflicht - der
   Responsive-Modus misst das positionierte Elternelement. */
.chart-box { position: relative; }
.chart-occ { height: 110px; }
/* Statistik: die Rueckschau-Charts sind die Hauptsache der Seite und bekommen
   deutlich mehr Hoehe als die Dashboard-Kacheln. */
.chart-stat { height: 340px; }
/* Zweite Chart-Reihe der Statistik (zwei halbe Breiten, z. B. Kanal/Zahlart) */
.chart-stat-half { height: 260px; }
/* Ladezustaende der Statistik: ERSTES Laden = Skeleton/Shimmer (.is-loading
   .skel); der ZEITRAUMWECHSEL nutzt den globalen Busy-Overlay (body.is-busy,
   weiter unten). Skeleton-Klassennamen aus dem Referenzmodul (Skill
   loading-states); Token und Keyframe sind die schon vorhandenen (--app-skel-*,
   app-shimmer), damit es nicht zwei Skeleton-Systeme gibt. */
.is-loading .skel { position: relative; }
.is-loading .skel > * { visibility: hidden; }
.is-loading .skel::after {
  content: ""; position: absolute; inset: .5rem;
  border-radius: var(--tblr-border-radius);
  background: linear-gradient(90deg, var(--app-skel-base) 25%, var(--app-skel-hi) 50%, var(--app-skel-base) 75%);
  background-size: 220% 100%;
  animation: app-shimmer 1.4s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .is-loading .skel::after { animation: none; background: var(--app-skel-base); }
}

/* Globaler Busy-Indikator (Prompt BUSY-SPINNER): EIN Voll-Viewport-Overlay am
   body - Backdrop dunkelt ab und faengt Klicks, zentrierter Spinner. Loest den
   frueheren lokalen Chart-Spinner ab (Prinzip 1: eine Funktion, ein Zustand, ein
   Overlay - kein Spinner-Zoo pro View). Setzt/loescht die Klasse setBusy im
   Referenzmodul busy-indicator.module.js (Zaehler, await-Repaint, finally). */
body.is-busy::before {
  content: ""; position: fixed; inset: 0;
  background: rgba(255, 255, 255, .6); z-index: 2000;
}
[data-bs-theme="dark"] body.is-busy::before { background: rgba(24, 36, 51, .6); }
body.is-busy::after {
  content: ""; position: fixed; top: 50%; left: 50%;
  width: 2.5rem; height: 2.5rem; margin: -1.25rem 0 0 -1.25rem;
  /* Token, nicht currentColor: sonst erbt der Spinner am body eine zufaellige
     Textfarbe (Prompt "Groesse und Abdeckung"). */
  border: .25em solid var(--tblr-primary); border-right-color: transparent;
  border-radius: 50%;
  animation: busy-spin .75s linear infinite; z-index: 2001;
}
@keyframes busy-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  body.is-busy::after { animation-duration: 2s; }
}

/* Gauge: der Doughnut liegt als Hintergrundebene UNTER dem Text - Ueberschrift und
   Zahlen bleiben oben links wie in den Nachbarkarten, die Grafik fuellt die Karte
   dahinter. Dadurch entfaellt die Stapelhoehe aus Text + Grafik.
   Der Text braucht einen eigenen Stapelkontext (position:relative), sonst
   verschwindet er hinter dem Canvas. */
.gauge-body { position: relative; min-height: 150px; }
/* Oben unterhalb der Ueberschrift ansetzen (die Unterkante der .subheader-Zeile
   liegt ~32px unter dem Body-Rand) - sonst laeuft der Bogen durch den Titel.
   Unten/seitlich buendig lassen. */
.chart-gauge-bg { position: absolute; top: 2.25rem; right: 0; bottom: 0; left: 0; z-index: 0; }
.gauge-body > :not(.chart-gauge-bg) { position: relative; z-index: 1; }
/* Zahl, Trend und Beschriftung sitzen auf einer halbtransparenten Platte in
   Kartenfarbe: sie deckt den Ring genau dort ab, wo der Text steht - der Rest des
   Bogens bleibt volldeckend. Farbe aus der Tabler-Variablen, damit Hell und Dunkel
   von selbst stimmen; leicht weichgezeichnet, damit die Kante nicht schneidet. */
.gauge-text {
  background: color-mix(in srgb, var(--tblr-bg-surface) 30%, transparent);
  backdrop-filter: blur(2px);
  border-radius: var(--tblr-border-radius);
  padding: .25rem .5rem; margin: -.25rem -.5rem;
  width: fit-content;
}

/* Hallenauslastung ueber den Tag: das Diagramm sitzt buendig am unteren Rand der Karte. */
.occ-body { padding-bottom: 0; }

/* Segmentierte Balken des Dashboards (Belegung nach Typ, Personenmix): ein
   gestapelter horizontaler Chart.js-Balken. Die Trennung der Segmente macht jetzt
   der weisse Rahmen im Datensatz (app.js), nicht mehr CSS. */
.chart-seg { height: 1.25rem; }

/* Externer Tooltip der Segmentbalken (siehe externalTooltip in app.js): Chart.js
   wuerde ihn ins Canvas zeichnen, wo er bei 20px Hoehe abgeschnitten wird. */
.chart-tip {
  position: absolute; z-index: 1080; pointer-events: none;
  transform: translate(-50%, -125%);
  padding: .25rem .5rem; border-radius: var(--tblr-border-radius);
  background: rgba(24, 36, 51, .92); color: #fff; font-size: .75rem; white-space: nowrap;
  opacity: 0; transition: opacity .1s;
}
.chart-tip.is-on { opacity: 1; }

/* Fluid-Layout: weniger Luft oben, gleiches Padding unten zur Navbar.
   page-body als Flex-Spalte, damit die Tickets-View bis unten fuellen kann. */
.page-body {
  margin-top: 0;
  margin-bottom: 0;   /* Tabler-Default hat margin oben UND unten -> beide weg */
  padding-top: 1rem;
  padding-bottom: 1rem;
  display: flex;
  flex-direction: column;
}

/* app-lock (body, gesetzt von showView fuer Tickets/Hilfe): deckelt die Seite auf
   Fensterhoehe. Ohne den Deckel ist die app-fill-Kette darunter wirkungslos, denn
   .page hat nur min-height:100% und waechst mit dem Inhalt - und .page-wrapper/
   .page-body haben als Flex-Items min-height:auto, das automatische Minimum haelt
   sie auf Inhaltshoehe. Beides gilt nur solange app-lock aktiv ist; das Dashboard
   behaelt sein normales Seiten-Scrolling. */
body.app-lock { height: 100vh; overflow: hidden; }
body.app-lock .page { height: 100%; }
body.app-lock .page-wrapper, body.app-lock .page-body { min-height: 0; }

/* Fuellkette: Container -> View -> Karte -> card-body -> Tabellen-Host strecken
   sich auf die Resthoehe (jede Stufe flex:1 + min-height:0, sonst reisst die
   Kette). Als KLASSEN, nicht je View-ID - vorher stand dieselbe 4-Zeilen-Kette
   dreimal (Tickets/Gutscheine/Ferien) fast zeichengleich da. app-fill-view an der
   View, app-table-fill am Tabellen-Host; beide markieren "will die Karte fuellen".
   Nur aktiv, solange der Container .app-fill traegt. Generisches Muster im Skill:
   docs/prompts/TABULATOR.md Punkt 4. */
#appContainer.app-fill { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
#appContainer.app-fill > .app-fill-view { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
.app-fill-view > .card { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; margin-bottom: 0; }
.app-fill-view > .card > .card-body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.app-table-fill { flex: 1 1 auto; min-height: 0; }

/* Jahrgangsfeld in der Card-Header-Toolbar: schmal genug, dass die Zeile nicht umbricht */
.fer-year { width: 7rem; }

/* Auswahlliste der list-Kopffilter. BEWUSST NICHT auf eine Tabelle gescopt:
   Tabulator haengt das Popup an document.body (popupContainer ist per Default
   false), es ist also KEIN Kind des Tabellen-Hosts - eine Regel #xTable ...
   erreicht es nie (im Browser gemessen: parentElement === BODY). Das Theme
   setzt dort font-size:16px fest, womit die Liste die groesste Schrift der
   ganzen Tabelle waere - groesser als die Zellen. Alle Tabellen wollen
   dieselbe Optik, deshalb eine gemeinsame Regel. */
.tabulator-edit-list { font-size: .8rem; }
.tabulator-edit-list .tabulator-edit-list-item { padding: 2px 6px; }

/* Tickets-View: Suchfeld in der Card-Header-Toolbar */
.tickets-search { min-width: 16rem; }

/* Kompakt-Optik ALLER Datentabellen ueber EINE Klasse am Tabellen-Host
   (app-table-compact), nicht je Tabellen-ID - vorher stand derselbe Block dreimal
   (couponsTable/ferienTable/ticketsTable) fast zeichengleich da. Referenz +
   Begruendung generisch im Skill: tabulator-js/references/compact-table.css.
   Werte aus dem Referenzmuster: 24px-Zeilen, kleinere Schrift/Paddings, schlanke
   Header-Filter. Tabulator setzt am Host nur classList.add("tabulator") (6.5.2),
   die eigene Klasse ueberlebt. */
.app-table-compact .tabulator-row { min-height: 24px; }
.app-table-compact .tabulator-cell { padding: 2px 6px; font-size: .85rem; }
/* Dieselbe Spezifitaets-Falle wie beim Filter-Feld (s.u.), nicht !important: das
   bs5-Theme polstert col-content ueber
   .tabulator .tabulator-header .tabulator-col .tabulator-col-content{padding:12px}
   (Spezifitaet 0,4,0, im Bundle gemessen). Eine kurze .app-table-compact
   .tabulator-col-content-Regel ist nur (0,2,0) und VERLIERT - der Kopf bliebe 12px
   oben/unten gepolstert (der "verschenkte" Platz ueber Titel und unter Filter).
   Fix: die Struktur-Klassen .tabulator-header .tabulator-col mittragen -> (0,5,0)
   schlaegt (0,4,0), ohne !important. NB: die Theme-Regel mit 5px!important gilt NUR
   fuer .tabulator.table-sm - unsere Tabulator-Tabellen tragen das nicht. */
.app-table-compact .tabulator-header .tabulator-col .tabulator-col-content { padding: 2px 6px; }
.app-table-compact .tabulator-col-title { font-size: .8rem; line-height: 1.2; }
/* checkbox ausnehmen: eine feste Hoehe wuerde die tickCross-Checkbox strecken.
   Die volle Struktur-Kette .tabulator-header .tabulator-col ist KEIN Zierrat: das
   bs5-Theme setzt die Filter-Schrift ueber genau diesen 4-Klassen-Selektor
   (.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input =
   Spezifitaet 0,4,1). Eine kuerzere Klassen-Regel verliert dagegen und die
   Schrift bliebe bei 1rem - deshalb hier mindestens gleich tief (die eigene
   Host-Klasse macht sie hoeher). Die frueheren #id-Regeln gewannen nur wegen der
   ID; beim Umstieg auf die Klasse muss die Tiefe nachgezogen werden. */
.app-table-compact .tabulator-header .tabulator-col .tabulator-header-filter input:not([type="checkbox"]),
.app-table-compact .tabulator-header .tabulator-col .tabulator-header-filter select { padding: 1px 4px; font-size: .7rem; height: 22px; line-height: 1.2; }
.app-table-compact .tabulator-cell .badge { font-size: .72rem; }
/* Aktions-Icons der Inline-Edit-Zeile (Speichern/Verwerfen): Groesse in rem, NICHT
   em - dieselbe Falle wie bei .tkt-facts weiter oben. Die Zelle steht auf .85rem,
   die Glyphe erbt das und misst dann 13,6px bei 13,6px Text daneben (gemessen
   2026-07-26). Optisch ist sie damit KLEINER als der Text, weil eine Icon-Glyphe
   das em nicht ausfuellt, Buchstaben mit Ober- und Unterlaengen aber schon. Die
   Polsterung des Knopfes wird zurueckgenommen, sonst sprengt die groessere Glyphe
   die 24px-Zeile. */
.app-table-compact .tabulator-cell .btn-icon2 { padding: 0 .25rem; }
.app-table-compact .tabulator-cell .btn-icon2 .ti { font-size: 1.1rem; }
/* Der Icon-Block sitzt als inline-flex in der Zelle und richtet sich sonst an der
   GRUNDLINIE aus. Die kommt aus dem Zeilenabstand des Knopfes (line-height:1 mal
   die geerbten 13,6px der Zelle) - die Glyphe ist mit 17,6px aber groesser als
   diese Box und ragt oben heraus. Ergebnis: die Icons standen 1,5px hoeher als der
   Text daneben (gemessen 2026-07-26). middle richtet an der Mitte aus und ist
   unabhaengig davon, wie gross die Glyphe im Verhaeltnis zur Zellschrift ist. */
.app-table-compact .tabulator-cell > .d-inline-flex { vertical-align: middle; }
/* Beide Scrollbalken-Achsen: x nur bei Ueberlauf (auto), y dauerhaft reserviert
   (scroll), damit beim Nachladen nichts springt. Das Balken-Aussehen kommt von
   Tabler (::-webkit-scrollbar global -> klassische statt ueberlagernde Balken). */
.app-table-compact .tabulator-tableholder { overflow-x: auto; overflow-y: scroll; }
/* Das native x zum Leeren eines list-Kopffilters: clearable:true macht das Feld
   type="search"; Tabler blendet das Browser-x global aus
   (::-webkit-search-cancel-button{-webkit-appearance:none}). Hier zurueckgeholt.
   KEIN Umweg um Tabulator - clearable rendert kein eigenes x, und ein
   Pseudo-Element kann Tabulator nicht per Inline-Style zuruecksetzen (es setzt
   inline nur padding/width/cursor/caret-color am Input selbst). Nur bei
   type="search", also nur an den list-Filtern. */
.app-table-compact .tabulator-header-filter input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: auto; appearance: auto; cursor: pointer;
}
/* Nur die Ticketliste: Zeilenklick oeffnet das Detail (Zeigefinger); feste
   Zellenhoehe geht nur ohne Zell-Editor (sonst schneidet sie ein Editor-Feld ab)
   - deshalb je Tabelle, nicht in der geteilten Regel. */
#ticketsTable .tabulator-row { cursor: pointer; }
#ticketsTable .tabulator-cell { height: 24px; }

/* --- Kalender-View (FullCalendar 6) --- */
/* Fill-Kette wie Tickets/Hilfe: Karte fuellt die Resthoehe, FullCalendar
   (height:100%) scrollt innen. */
#appContainer.app-fill > #viewKalender { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
#viewKalender > .card { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; margin-bottom: 0; }
#viewKalender > .card > .card-body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
#viewKalender #kalenderBox { flex: 1 1 auto; min-height: 0; }

/* Feiertags-/Ferienfarben laufen ueber die Event-Eigenschaft color (app.js,
   CALGEN_COLORS) - FullCalendar 7 wendet classNames nicht mehr an. Nur die
   Check-in-Abblendung ist CSS; die Klasse setzt eventDidMount. */
.fc-eingecheckt { opacity: .55; text-decoration: line-through; }

/* --- Belegung-View (FullCalendar Scheduler resourceTimeline) --- */
/* Fill-Kette wie Kalender */
#appContainer.app-fill > #viewBelegung { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
#viewBelegung > .card { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; margin-bottom: 0; }
#viewBelegung > .card > .card-body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
#viewBelegung #belegungBox { flex: 1 1 auto; min-height: 0; }

/* --- Einlass-View --- */
/* Fill-Kette wie Kalender/Belegung: ohne sie hat .ein-cards keine begrenzte
   Hoehe und scrollt nicht, sondern schiebt die Karte auf. */
#appContainer.app-fill > #viewEinlass { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
#viewEinlass > .card { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; margin-bottom: 0; }
#viewEinlass > .card > .card-body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }

/* Auslastungsampel der Belegungsbloecke (Schwellen <=50/75/95% wie occColor;
   Farben und schwarze Schrift wie der Legacy-Belegungsplan). Klassen setzt
   eventDidMount aus extendedProps.level. */
.fcTlEvt { text-align: center; }
.fcTlEvt0  { background-color: greenyellow; border-color: greenyellow; color: black; }
.fcTlEvt50 { background-color: yellow;      border-color: yellow;      color: black; }
.fcTlEvt75 { background-color: orange;      border-color: orange;      color: black; }
.fcTlEvt95 { background-color: red;         border-color: red;         color: black; }
/* Reservierter Anteil: diagonale Schraffur ueber die Ampelfarbe, damit
   reserviert (kein Ticket) von gebucht unterscheidbar bleibt. Halbtransparentes
   Weiss, damit es auf jeder Ampelstufe lesbar ist. */
.fcTlEvt-res {
  background-image: repeating-linear-gradient(
    45deg,
    rgba(255, 255, 255, 0.55) 0, rgba(255, 255, 255, 0.55) 3px,
    transparent 3px, transparent 7px);
}

/* --- Hilfe-View (Markdown aus hilfe/*.md, marked + Pikchr + KaTeX) --- */
/* Fill-Kette wie Tickets: Karte fuellt die Resthoehe, Inhalt scrollt innen. */
#appContainer.app-fill > #viewHilfe { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
#viewHilfe > .card { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; margin-bottom: 0; }
/* helpHost liegt IN der Karte (nicht direkt unter viewHilfe) - der fruehere
   Selektor mit > traf nie, langer Inhalt wurde seit dem app-lock-Deckel
   abgeschnitten. Balken dauerhaft (scroll), damit zwischen kurzen und langen
   Seiten nichts springt. */
#viewHilfe #helpHost.card-body { flex: 1 1 auto; min-height: 0; overflow-y: scroll; }

.help-content { padding: .25rem .5rem 2rem; line-height: 1.5; }
.help-home-top, .help-home-bot { text-align: left; margin: .25rem 0; }
.help-home-bot { margin-top: 2rem; }
.help-home { display: inline-flex; text-decoration: none; }
.help-content h1, .help-content h2, .help-content h3, .help-content h4 { margin-top: 1.25em; margin-bottom: .5em; }
.help-content h1 { font-size: 1.5rem; }
.help-content h2 { font-size: 1.25rem; }
.help-content h3 { font-size: 1.1rem; }
.help-content h4 { font-size: 1rem; font-weight: 600; }
.help-content p, .help-content li { font-size: .9rem; }
.help-content code { background: rgba(0,0,0,.05); padding: 0 .3em; border-radius: 3px; font-size: .85em; }
.help-content pre { background: rgba(0,0,0,.04); padding: .6em .8em; border-radius: 4px; overflow-x: auto; }
.help-content pre code { background: transparent; padding: 0; }
.help-content table { border-collapse: collapse; margin: .75em 0; font-size: .85rem; }
.help-content th, .help-content td { border: 1px solid rgba(0,0,0,.12); padding: .3em .6em; text-align: left; }
.help-content blockquote { border-left: 3px solid #6c757d; padding-left: .8em; margin-left: 0; color: #6c757d; }
/* Inline-Icons erben sonst die kleine Fliesstext-/Tabellen-Groesse und wirken winzig; em skaliert im jeweiligen Kontext mit. */
.help-content .ti { font-size: 1.3em; vertical-align: -.18em; line-height: 1; }
.help-home .ti { font-size: 1.6rem; vertical-align: middle; }

/* Pikchr: Placeholder waehrend des Renderns, Fehler rot, SVG auf 70% Breite. */
.pikchr-block { color: #6c757d; font-style: italic; padding: .5rem 0; }
.pikchr-block::before { content: "Lade Pikchr-Renderer …"; }
.pikchr-block.pikchr-error { color: #dc3545; font-style: normal; font-family: ui-monospace, SFMono-Regular, monospace; font-size: .85em; }
.pikchr-block.pikchr-error::before { content: ""; }
.pikchr-rendered { margin: .5rem 0; }
.pikchr-rendered svg { max-width: 70%; height: auto; }

/* Lizenzseite: Frontend- und Backend-Tabelle spaltengleich (feste Breiten). */
.lic-table { table-layout: fixed; }
.lic-table col.lic-c1 { width: 40%; }
.lic-table col.lic-c2 { width: 10%; }
.lic-table col.lic-c3 { width: 25%; }
.lic-table col.lic-c4 { width: 25%; }
.lic-table th, .lic-table td { overflow-wrap: anywhere; }

/* Einlass: Scan-Feld bleibt stehen, die Kartenliste scrollt darunter (die View
   fuellt per app-fill die Resthoehe). Karten in einem Raster - auf einem
   Tresen-Monitor nebeneinander, auf schmalen Geraeten untereinander. */
/* Ticket erstellen: Personenfelder als Raster, Termine als Knopfleiste. */
/* Personenkategorien: EINE Zeile, die Felder schrumpfen mit. Vorher ein
   Grid mit minmax(7rem, 1fr) - fuenf Spalten brauchten damit ~37rem, und ein
   paar Pixel weniger Fensterbreite kippten "Begleitung" in die zweite Zeile.
   flex-basis 0 laesst die Felder gleich breit werden statt umzubrechen. */
.bk-persons { display: flex; gap: .5rem; }
.bk-person { flex: 1 1 0; min-width: 0; }
.bk-person .form-label { font-size: .75rem; margin-bottom: .15rem; }
/* Termin-Kacheln. Farbe/Rahmen kommen aus den Tabler-Button-Klassen (btn-primary,
   btn-outline-danger, btn-outline-secondary) - hier nur die Breite, damit das
   Raster gleichmaessig bleibt. */
.bk-slots { display: flex; flex-wrap: wrap; gap: .5rem; }
/* Kalender-Eigenschaften des Tages (Feiertag, Ferien, Wochentag) als Badges
   ueber den Terminen - leer, wenn der Tag nichts Besonderes hat.

   Die ZUORDNUNG Bedeutung -> Farbe steht hier, nicht im JavaScript: das JS
   kennt nur den Tag-Namen aus der Engine (holiday, vacation, ...) und haengt
   ihn als Klasse an. Wer eine Farbe aendern oder einen neuen calgen-Tag
   einfaerben will, fasst genau diese Datei an.

   BEWUSST OHNE View-Praefix: dieselben Klassen nutzen die Buchen-Ansicht und
   der Kalender. Ein bk-Praefix haette den Kalender gezwungen, entweder
   buchungs-benannte Klassen zu verwenden oder die Farben zu duplizieren.

   Aufbau wie Tablers eigene -lt-Badges (color-mix aus einer Basisfarbe): heller
   Grund, kraeftige Schrift. Fuer die Jahreszeiten/Zeitumstellung gibt es in
   Tabler KEIN Braun - hier als eigenes Token, statt auf eine fremde Farbe
   auszuweichen. */
.daytags:empty { display: none; }

/* Kalender + Tages-Badges nebeneinander. Der Kalender behaelt seine natuerliche
   Breite, die Badges nehmen den Rest - bei schmaler Karte rutschen sie unter
   den Kalender statt ihn zu quetschen. stretch (Default), damit die Badge-Spalte
   die volle Hoehe des Kalenders bekommt und darin ausrichten kann. */
.bk-date-row { display: flex; flex-wrap: wrap; gap: 1rem; }

/* Vertikale Variante: je Badge eine Zeile. Nur die ANORDNUNG unterscheidet sich
   von der waagerechten Liste - Farben und Markup sind identisch, damit beide
   Stellen (und spaeter der Kalender) denselben Helfer nutzen koennen. */
.daytags-v { display: flex; flex-direction: column; align-items: flex-start; }

/* Neben dem Kalender: unten buendig. Bewusst HIER und nicht in .daytags-v -
   die Ausrichtung gehoert zum Einbauort, nicht zur wiederverwendbaren Variante
   (der Kalender kann sie spaeter oben buendig brauchen). */
.bk-date-row .daytags-v { justify-content: flex-end; }
/* Das letzte Badge traegt mb-1; unten buendig soll es direkt abschliessen. */
.bk-date-row .daytags-v > :last-child { margin-bottom: 0 !important; }

.daytag {
  color: color-mix(in srgb, var(--daytag-color) 100%, transparent);
  background-color: color-mix(in srgb, var(--daytag-color) 10%, transparent);
  border-color: color-mix(in srgb, var(--daytag-color) 10%, transparent);
}
.daytag-holiday        { --daytag-color: var(--tblr-red); }
.daytag-vacation       { --daytag-color: var(--tblr-green); }
.daytag-festival       { --daytag-color: var(--tblr-orange); }
.daytag-meteorological { --daytag-color: #8a5a2b; }
.daytag-astronomical   { --daytag-color: #8a5a2b; }
.daytag-clockchange    { --daytag-color: #8a5a2b; }
/* Wochentagslage: erklaert einen Zuschlag, ist aber kein Kalender-Ereignis. */
.daytag-weekday        { --daytag-color: var(--tblr-secondary); }
.daytag-weekend        { --daytag-color: var(--tblr-azure); }
/* Unbekannter Tag (neuer calgen-Eintrag): sichtbar neutral statt unsichtbar. */
.daytag:not([class*="daytag-"]) { --daytag-color: var(--tblr-secondary); }
.bk-slot { min-width: 5rem; }

/* Kartennummern: Ziffernkolonne, deshalb dicktengleich. */
.bk-rtc { font-family: var(--tblr-font-monospace); font-size: .875rem; }

/* Summe: readonly, aber sie ist die wichtigste Zahl der Karte. */
.bk-sum { font-weight: 600; }

/* Gewaehlter Termin: bleibt sichtbar, waehrend der Bediener rechts die
   Buchungsdaten ausfuellt. */
.bk-slot-sel { box-shadow: 0 0 0 2px var(--tblr-primary); font-weight: 600; }

/* Bestaetigungen: strikt untereinander, eine Zeile je Verstoss. Nebeneinander
   liest sie niemand einzeln - und genau darum geht es hier. */
.bk-ack { display: flex; flex-direction: column; gap: .5rem; }
.bk-ack .form-check-label { line-height: 1.3; }

/* Resultset-Baum (nur Testumgebung): eigene Hoehe, damit die Seite nicht endlos wird.
   Das Farbschema steht NICHT in jsonTree.css (dort sind die Farbregeln
   auskommentiert) - es ist dasselbe wie in css/ticket.css, damit der Baum im
   Backoffice so aussieht wie gewohnt. */
.bk-debug { max-height: 40rem; overflow: auto; }
.bk-debug .jsontree_tree { font-size: 12px; }
.bk-debug .jsontree_label,
.bk-debug .jsontree_value_object,
.bk-debug .jsontree_value_array { color: #0451a5; font-weight: normal; }
.bk-debug .jsontree_value_null { color: #0000ff; font-weight: normal; }
.bk-debug .jsontree_value_string { color: #c94e4e; font-weight: normal; }
.bk-debug .jsontree_value_number { color: #2ba675; font-weight: normal; }
.bk-debug .jsontree_value_boolean { color: #e07b00; font-weight: normal; }

/* Legende: die Farbmuster sind dieselben Buttons wie im Raster, nur leer und schmal. */
.bk-legend { display: flex; align-items: center; flex-wrap: wrap; gap: .35rem; margin-top: .75rem; font-size: .75rem; color: var(--tblr-secondary); }
.bk-key { width: 1.75rem; min-width: 0; padding: 0; pointer-events: none; }
.bk-key ~ .bk-key { margin-left: .75rem; }
.bk-key-note { margin-left: .75rem; }

/* Zeitfenster-Felder in der Kopfzeile: schmal, damit die Kopfzeile nicht umbricht. */
.ein-win { width: 4.5rem; text-align: right; }

/* Scan-Feld: eine Ticket-ID ist 36 Zeichen - ueber die volle Kartenbreite waere das
   Feld ein leeres Band. Ein Viertel reicht und laesst die Karten daneben atmen. */
.ein-scan { width: 25%; min-width: 18rem; }

.ein-body { display: flex; flex-direction: column; min-height: 0; }
.ein-cards { flex: 1 1 auto; min-height: 0; overflow-y: auto;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr)); gap: .75rem;
  align-content: start; }
.ein-card { margin: 0; }
.ein-card .card-body { padding: .75rem; }
/* Eingecheckte bleiben sichtbar, treten aber zurueck (Checkbox blendet sie ganz aus). */
.ein-done { opacity: .45; }
.ein-head { display: flex; align-items: baseline; justify-content: space-between; gap: .5rem; margin-bottom: .4rem; }
.ein-title { font-weight: 600; }
.ein-badges { flex-shrink: 0; }
.ein-facts > div { display: flex; align-items: center; gap: .6rem; margin-bottom: .2rem; font-size: .875rem; }
.ein-facts .ti { color: var(--tblr-secondary); font-size: 1.2rem; flex-shrink: 0; }

/* Tooltip am Spaltenkopf (headerTooltip) sichtbar machen.
   BEWUSST UNGESCOPT: Tabulator haengt den Tooltip an document.body, er ist KEIN
   Kind des Tabellen-Hosts - eine Regel .app-table-compact ... erreicht ihn nie
   (dieselbe Falle wie beim list-Filter-Popup .tabulator-edit-list).

   Warum ueberhaupt: das Tabulator-Theme gibt ihm weissen Grund, box-shadow:none
   und einen Rahmen mit nur 1,30:1 Kontrast - auf fast weisser Seite kaum zu
   sehen und unter WCAG 1.4.11 (Non-text Contrast, 3:1) zu wenig.

   OPTIK WIE EIN LIGHT-BADGE (Entscheidung des Autors): getoente Flaeche,
   Akzentrahmen wie beim Outline-Badge - dieselbe Formensprache wie die Badges
   in den Tabellen. Ein invertierter schwarzer Kasten wirkte in der hellen
   Oberflaeche wie ein Fremdkoerper.

   ABER die Badge-Klassen sind hier NICHT direkt verwendbar: bg-*-lt ist mit 10 %
   Alpha getoent und badge-outline hat gar keine Flaeche (gemessen). Auf einer
   Karte ist das richtig - ein Tooltip SCHWEBT jedoch ueber Inhalt, und der
   Text darunter schiene durch. Deshalb dieselbe Farbe DECKEND gemischt:
   color-mix gegen die Kartenflaeche statt gegen transparent. Sieht identisch
   aus, deckt aber.

   Textfarbe bewusst body-color statt primary: das Light-Badge faerbt seinen Text
   in der Akzentfarbe, was bei einem kurzen Label passt - auf der getoenten
   Flaeche kommt das aber nur auf rund 4,3:1 und damit unter die 4,5:1, die WCAG
   1.4.3 fuer Fliesstext verlangt. Diese Tooltips sind ganze Saetze.

   Zweiter Selektor ist PFLICHT: das Tabulator-bs5-Theme hat
   html[data-bs-theme=dark] .tabulator-tooltip{background:var(--bs-body-bg)},
   Spezifitaet (0,2,1) gegen unsere (0,1,0) - im Dunkelmodus gewinnt sonst das
   Theme. Schlimmer: es greift nach --bs-body-bg, einer BOOTSTRAP-Variablen, die
   es unter dieser Oberflaeche (--tblr-*) nicht gibt - der Wert ist leer und der
   Tooltip wird durchsichtig. Der Fehler steckt im Theme und traefe auch ohne
   unsere Regel zu.

   NICHT var(--tblr-tooltip-bg) o. ae. benutzen: die --tblr-tooltip-* Variablen
   sind komponenten-lokal (nur im Selektor .tooltip{...} definiert) und loesen an
   einem Fremdelement nicht auf - gemessen: durchsichtig, Radius 0. */
.tabulator-tooltip,
html[data-bs-theme="dark"] .tabulator-tooltip {
  background: color-mix(in oklab, var(--tblr-primary) 10%, var(--tblr-bg-surface));
  color: var(--tblr-body-color);
  border: 1px solid var(--tblr-primary);
  border-radius: var(--tblr-border-radius);
  max-width: 320px;
  padding: .25rem .5rem;
  box-shadow: var(--tblr-box-shadow);
}

/* --- Kundenbuchungsstrecke (View "Buchen (ticket)", Praefix kb) ------------
   Die Strecke benutzt bewusst dieselben Bausteine wie die Backoffice-Buchung
   (.bk-persons, .bk-slots, .bk-legend, .bk-date-row) - gleiche Sache, gleiches
   Aussehen. Eigenes CSS gibt es nur fuer das, was es dort nicht gibt: die
   Betragszeile und die Reihe der Zahlungsknoepfe. */
.kb-sum { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.kb-amount { font-size: 1.5rem; font-weight: 600; font-variant-numeric: tabular-nums; }
/* Umbrechend, damit die drei Knoepfe auf einem Telefon untereinander stehen
   statt aus der Karte zu laufen. */
.kb-pay { display: flex; flex-wrap: wrap; gap: .5rem; }
