/* ============================================================================
   app.css — Design-Schicht fuer Portal, Admin und Anmeldung
   ----------------------------------------------------------------------------
   Gegenstueck zu modern.css: modern.css liegt ueber den oeffentlichen Seiten,
   app.css ueber dem eingeloggten Bereich. Beide teilen dieselben Marken-Tokens,
   damit Website und Portal als ein Haus wirken - aber sie loesen verschiedene
   Aufgaben. Die Website wirbt, das Portal arbeitet. Deshalb ist hier alles
   dichter, ruhiger und auf Wiedererkennung von Zustaenden ausgelegt.

   Ladereihenfolge: style.css -> auth.css -> portal.css -> app.css
   Additiv. Das Markup der Seiten bleibt unveraendert. Zum Abschalten reicht es,
   das <link> aus dem <head> zu nehmen.
   ========================================================================= */

:root {
  /* Marken-Tokens, identisch zu modern.css */
  --brand:        #1d6e80;
  --brand-600:    #175c6d;
  --brand-700:    #13505d;
  --brand-300:    #5cc8d8;
  --brand-100:    #e3f1f3;
  --ink:          #0d1b24;
  --ink-soft:     #4a5c68;
  --ink-muted:    #6b7c88;
  --deep:         #0f2433;
  --deep-2:       #1a3d4f;

  --surface:      #ffffff;
  --surface-alt:  #f5f9fb;
  --surface-sunk: #eef4f7;
  --line:         #e4ebf0;
  --line-strong:  #d3dde4;

  /* Statusfarben. Die Werte stammen aus den bereits verwendeten Pills in
     auth.css - hier nur zentral benannt, damit sie nicht weiter driften. */
  --ok:        #1e7a4d;
  --ok-bg:     #eaf3de;
  --ok-line:   #c3ddb0;
  --warn:      #854f0b;
  --warn-bg:   #faeeda;
  --warn-line: #eccfa4;
  --err:       #a32d2d;
  --err-bg:    #fcebeb;
  --err-line:  #f0a8a8;

  --radius-sm: 8px;
  --radius:    12px;
  --radius-lg: 16px;

  --shadow-sm: 0 1px 2px rgba(13,27,36,.05), 0 2px 6px rgba(13,27,36,.04);
  --shadow-md: 0 2px 4px rgba(13,27,36,.04), 0 12px 28px rgba(13,27,36,.08);
  --shadow-lg: 0 4px 8px rgba(13,27,36,.05), 0 24px 56px rgba(13,27,36,.13);

  --ease: cubic-bezier(.22,.68,.36,1);
}

/* ---------------------------------------------------------------- Fokus --
   Das Wichtigste in dieser Datei.

   style.css setzt bei Formularfeldern outline:none, und der Fokusring aus
   modern.css liegt nur auf den oeffentlichen Seiten. Im gesamten Portal und
   Admin war damit per Tastatur nicht erkennbar, wo man steht - eine Seite,
   die man nur mit der Maus bedienen kann. Der Ring gilt hier fuer alles,
   was fokussierbar ist, und liegt bewusst ausserhalb des Elements, damit er
   auch auf farbigen Flaechen sichtbar bleibt. */
.portal-page a:focus-visible,
.portal-page button:focus-visible,
.portal-page input:focus-visible,
.portal-page select:focus-visible,
.portal-page textarea:focus-visible,
.portal-page summary:focus-visible,
.portal-page [tabindex]:focus-visible,
.auth-page a:focus-visible,
.auth-page button:focus-visible,
.auth-page input:focus-visible,
.auth-page select:focus-visible,
.auth-page textarea:focus-visible,
.reset-modal a:focus-visible,
.reset-modal button:focus-visible,
.reset-modal input:focus-visible,
.reset-modal select:focus-visible,
.reset-modal textarea:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  border-radius: 6px;
}

/* Auf der dunklen Seitenleiste geht Petrol auf Petrol unter - dort hell. */
.portal-sidebar a:focus-visible,
.portal-sidebar button:focus-visible {
  outline-color: var(--brand-300);
}

/* Das Feld selbst zeigt den Fokus zusaetzlich ueber den Rand an. Der Ring
   allein reicht rechtlich, der farbige Rand macht es im Alltag lesbarer. */
.portal-page input:focus,
.portal-page select:focus,
.portal-page textarea:focus,
.auth-page input:focus,
.auth-page select:focus,
.auth-page textarea:focus {
  border-color: var(--brand);
}

/* ------------------------------------------------------- Sprungmarke --
   Im Admin steht vor dem Inhalt eine lange Navigation. Ohne Sprungmarke
   muss sich jede Tastaturbedienung auf jeder Seite erneut durchtabben. */
.skip-link {
  position: absolute;
  left: 8px;
  top: -60px;
  z-index: 999;
  background: var(--brand);
  color: #fff;
  padding: 10px 16px;
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  text-decoration: none;
  transition: top .18s var(--ease);
}
/* Die Sprungmarke steht vor .portal-page und wird von den Fokusregeln oben
   nicht erfasst - sie braucht ihren eigenen Ring, sonst zeigt der Browser
   sein gelbes Standardmuster mitten in der Marke. */
.skip-link:focus { top: 8px; }
.skip-link:focus-visible {
  outline: 2px solid var(--brand-300);
  outline-offset: 2px;
}

/* ------------------------------------------------------------- Flaeche --
   Ruhigerer Hintergrund als das bisherige #f4f8f9: minimal entsaettigt,
   damit die weissen Karten klarer davor stehen. */
.portal-page,
.auth-page { background: var(--surface-alt); }

/* style.css setzt html auf 120 Prozent. Auf der Website ist das richtig -
   dort steht wenig Text gross im Raum. In einer Arbeitsoberflaeche mit
   Seitenleiste, Tabellen und Masken sprengt es die Spaltenbreiten: die
   Navigationseintraege brachen um ("Meine Bestellungen" auf zwei Zeilen),
   ebenso die Kennzahl-Beschriftungen. 106,25 Prozent entsprechen 17 Pixel -
   gross genug zum bequemen Lesen, schmal genug fuer einzeilige Labels.
   Der Wert bleibt relativ, damit eine groessere Browser-Schrift weiter wirkt.

   .portal-page und .auth-page sind ein div innerhalb von body, nicht body
   selbst - der Selektor sucht sie deshalb im Nachfahrenbereich. */
html:has(.portal-page),
html:has(.auth-page) { font-size: 106.25%; }

.portal-main { color: var(--ink); }
.portal-main > h1 span { color: var(--brand); }
.portal-section-sub { color: var(--ink-soft); }

/* portal.css setzt die ruhige Ueberschriftgroesse nur fuer direkte Kinder
   von .portal-main. Steht die h1 - wie im Admin ueblich - in einem
   .portal-section-header, greift stattdessen die Hero-Groesse aus style.css
   und die Ueberschrift erscheint mit 34 Pixel wie auf einer Werbeseite.
   clamp haelt sie auf schmalen Geraeten klein, ohne sie am Desktop zu
   verkleinern. */
.portal-main h1 {
  font-size: clamp(1.35rem, 1rem + 1.6vw, 1.7rem);
  line-height: 1.25;
  color: var(--ink);
  overflow-wrap: break-word;
}
.portal-main h2 { font-size: 1.15rem; }

/* Der Kopfbereich ist als Zeile fuer "Ueberschrift + Button" gedacht. Traegt
   er stattdessen Ueberschrift und Beschreibung, quetschen sich beide auf
   schmalen Bildschirmen nebeneinander - der Titel lief aus dem Bild, der
   Untertitel stand in einer vier Zeichen breiten Spalte. */
.portal-section-header { flex-wrap: wrap; gap: .5rem 1rem; }
.portal-section-header > .portal-section-sub {
  flex-basis: 100%;
  margin-bottom: 0;
}

/* --------------------------------------------------------- Seitenleiste --
   Das Logo stand mit 140px Hoehe hoeher als der gesamte Navigationsblock
   darunter und hat die Seitenleiste zu einer Logoflaeche mit Menue gemacht.
   88px ist gross genug zur Wiedererkennung und laesst die Navigation die
   Hauptsache sein. */
.portal-sidebar {
  background: linear-gradient(180deg, var(--deep-2) 0%, #14303f 100%);
  padding: 1rem .875rem 1.25rem;
}

/* img/logo.png hat 141 Pixel durchsichtigen Rand oben und unten - die
   Bildmarke fuellt nur 21 Prozent der Datei. Deshalb standen ueberall
   Hoehen von 140 bis 190 Pixel, nur damit das Zeichen sichtbar wird.
   img/logo-mark.png ist dieselbe Marke ohne den Leerraum, dadurch reichen
   52 Pixel fuer ein groesseres sichtbares Zeichen als vorher. */
.portal-logo img { height: 52px; }
.portal-logo { margin: 0 -0.25rem .5rem; padding: .5rem .25rem; }

.portal-nav-item {
  transition: background .16s var(--ease), color .16s var(--ease);
}
/* Der aktive Eintrag wurde nur durch die Fuellfarbe markiert. Auf kleinen
   Bildschirmen und bei schwachem Kontrast war das kaum zu sehen; die Kante
   links ist die zweite, formbasierte Markierung. */
.portal-nav-item.active {
  background: var(--brand);
  box-shadow: inset 3px 0 0 var(--brand-300);
}

/* ------------------------------------------------------------- Kontrast --
   Sammelstelle fuer die Grautoene aus style.css, auth.css und portal.css,
   die auf weissem Grund unter 4,5:1 liegen und damit fuer Menschen mit
   eingeschraenktem Sehen nicht zuverlaessig lesbar sind. Betroffen war
   durchgehend Sekundaertext - Betraege, Datumsangaben, Feldhinweise. */
.portal-copyright { color: #8fb6c2; }          /* auf dunkler Leiste */
.invoice-card .invoice-meta { color: var(--ink-soft); }
.portal-page .stat-label,
.auth-page .stat-label { color: var(--ink-soft); }
.pw-toggle { color: var(--ink-soft); }
.dashboard-info { color: var(--ink-soft); border-color: var(--line); background: var(--surface-alt); }

/* ---------------------------------------------------------------- Karten */
.portal-stat-card,
.portal-panel,
.portal-details {
  border-color: var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.portal-stat-label { color: var(--ink-muted); }
.portal-stat-value { color: var(--ink); font-weight: 600; }
.portal-stat-hint { color: var(--ink-muted); }

a.portal-stat-card { transition: border-color .16s var(--ease), box-shadow .16s var(--ease), transform .16s var(--ease); }
a.portal-stat-card:hover { border-color: var(--brand-300); box-shadow: var(--shadow-md); }

.portal-stat-card.is-alert { border-color: var(--err-line); background: var(--err-bg); }
.portal-stat-card.is-alert .portal-stat-value { color: var(--err); }

/* ------------------------------------------------------------- Tabellen --
   Datentabellen sind der eigentliche Arbeitsbereich im Admin. Bisher waren
   sie reine Linienraster ohne Zeilenfuehrung: bei zwanzig Lizenzen verliert
   man beim Lesen die Zeile. Hover und ein ruhiger Kopf loesen das, ohne
   Zebrastreifen - die wuerden mit den farbigen Statuszeilen kollidieren. */
/* Im Admin sind ueber die Zeit sechs Tabellenklassen fuer dieselbe Sache
   entstanden: admin-table, tbl, lz-table, ord-table, hst-domtable. Jede mit
   eigenen Abstaenden und Linien - beim Wechsel zwischen Lizenzen, Hosting
   und Bestellungen sah jede Liste anders aus. Sie bekommen hier ein
   gemeinsames Aussehen; die Klassennamen bleiben, damit kein JavaScript
   angefasst werden muss. */
.portal-page .tbl,
.portal-page .lz-table,
.portal-page .ord-table,
.portal-page .hst-domtable {
  width: 100%;
  border-collapse: collapse;
  text-align: left;
}
.portal-page .tbl th,
.portal-page .lz-table th,
.portal-page .ord-table th,
.portal-page .hst-domtable th {
  color: var(--ink-muted);
  background: var(--surface-sunk);
  font-weight: 600;
  font-size: .78rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line-strong);
}
.portal-page .tbl td,
.portal-page .lz-table td,
.portal-page .ord-table td,
.portal-page .hst-domtable td {
  padding: 11px 12px;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
}
.portal-page .tbl tbody tr:hover td,
.portal-page .lz-table tbody tr:hover td,
.portal-page .ord-table tbody tr:hover td,
.portal-page .hst-domtable tbody tr:hover td { background: var(--surface-alt); }

.admin-table { font-size: .875rem; }
.admin-table th {
  color: var(--ink-muted);
  background: var(--surface-sunk);
  letter-spacing: .04em;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line-strong);
  position: sticky;
  top: 0;
  z-index: 1;
}
.admin-table td {
  padding: 11px 12px;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
}
.admin-table tbody tr { transition: background .12s var(--ease); }
.admin-table tbody tr:hover td { background: var(--surface-alt); }
.admin-table tbody tr:last-child td { border-bottom: none; }
.admin-table tr.row-alert td { background: var(--err-bg); }
.admin-table tr.row-alert:hover td { background: #fbe4e4; }

/* Zahlen gehoeren rechts und in gleiche Ziffernbreiten, sonst kann man
   Betraege nicht untereinander vergleichen. */
.admin-table td.num,
.admin-table th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.admin-table th.sortable:hover { color: var(--brand); }
.admin-table th.sort-asc::after,
.admin-table th.sort-desc::after { color: var(--brand); }

/* Tabelle in einer Karte: Rahmen aussen, damit sie nicht im Nichts endet. */
.table-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.table-card .admin-table { margin-top: 0; }

/* ------------------------------------------------- Waagrechtes Scrollen --
   portal.css faengt breite Tabellen erst unter 768px in einem eigenen
   Scrollbereich ab. Dazwischen - also auf jedem kleineren Laptop - schob
   eine breite Lizenz- oder Rechnungstabelle die *gesamte* Seite zur Seite:
   die Seitenleiste wanderte aus dem Bild und man scrollte zurueck, um
   ueberhaupt navigieren zu koennen.

   Zwei Dinge loesen das: der Hauptbereich darf nicht breiter werden als der
   Platz neben der Leiste (min-width:0 ist noetig, weil ein Flex-Element sonst
   auf seinen Inhalt aufgeht), und die Tabelle scrollt in sich selbst. */
.portal-main { min-width: 0; }

.table-card { overflow-x: auto; }
.portal-page .admin-table { min-width: 100%; }

/* Tabellen ausserhalb einer .table-card - so liegen sie heute im Admin -
   bekommen denselben Schutz. display:block macht aus der Tabelle einen
   scrollbaren Kasten; Zeilen und Spalten richten sich intern weiter
   korrekt aneinander aus. Dieselbe Technik nutzt portal.css bereits
   fuer Mobilgeraete - hier gilt sie fuer jede Breite, weil breite Listen
   sonst zwischen 769 und rund 1100 Pixel die ganze Seite verschieben. */
.portal-main .admin-table,
.portal-main .tbl,
.portal-main .lz-table,
.portal-main .ord-table,
.portal-main .hst-domtable {
  display: block;
  overflow-x: auto;
  max-width: 100%;
}
/* Der Kopf kann in einem eigenen Scrollkasten nicht kleben. */
.portal-main .admin-table th { position: static; }

/* Liegt die Tabelle bereits in einer .table-card, scrollt die Karte - dann
   darf die Tabelle nicht zusaetzlich scrollen, sonst entstehen zwei
   ineinander liegende Scrollbalken. Dort bleibt sie eine echte Tabelle
   und der Kopf kann kleben. */
.table-card .admin-table,
.table-card .tbl,
.table-card .lz-table,
.table-card .ord-table,
.table-card .hst-domtable {
  display: table;
  overflow: visible;
}
.table-card .admin-table th { position: sticky; top: 0; }

/* --------------------------------------------------------- Formularfelder
   auth.css, portal.css und die Inline-Styles der Seiten hatten je eigene
   Feldmasse. Hier eine Definition fuer alle, damit ein Eingabefeld im Admin
   genauso aussieht wie im Portal. */
.portal-page input[type="text"],
.portal-page input[type="email"],
.portal-page input[type="password"],
.portal-page input[type="number"],
.portal-page input[type="search"],
.portal-page input[type="date"],
.portal-page input[type="tel"],
.portal-page input[type="url"],
.portal-page select,
.portal-page textarea,
.auth-page input[type="text"],
.auth-page input[type="email"],
.auth-page input[type="password"],
.auth-page input[type="tel"],
.auth-page select,
.auth-page textarea {
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  transition: border-color .16s var(--ease);
}

.portal-page label,
.auth-page label { color: var(--ink); }

.portal-page input::placeholder,
.auth-page input::placeholder { color: #9aa9b4; }

.portal-page input:disabled,
.portal-page select:disabled,
.portal-page textarea:disabled,
.auth-page input:disabled {
  background: var(--surface-sunk);
  color: var(--ink-muted);
  cursor: not-allowed;
}

/* Ungueltige Felder erst markieren, wenn der Benutzer das Feld verlassen hat
   oder abgeschickt wurde - sonst wird jedes Feld waehrend des Tippens rot.
   :user-invalid macht genau das; Browser ohne Unterstuetzung zeigen wie
   bisher nur die Fehlermeldung. */
.portal-page input:user-invalid,
.auth-page input:user-invalid {
  border-color: var(--err-line);
  background: var(--err-bg);
}

/* ------------------------------------------------- Feldbausteine --------
   Diese vier Klassen ersetzen die am haeufigsten wiederholten Inline-Styles
   im Portal. Sie sind bewusst schlicht benannt und beschreiben die Rolle,
   nicht das Aussehen - damit eine spaetere Aenderung an einer Stelle wirkt
   und nicht in achtzig Attributen gesucht werden muss. */
.field-label {
  display: block;
  font-size: .82rem;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: .3rem;
}
.form-control {
  width: 100%;
  padding: 9px 12px;
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius-sm);
  font-size: .9rem;
  font-family: inherit;
  box-sizing: border-box;
  background: var(--surface);
  color: var(--ink);
}
textarea.form-control { resize: vertical; min-height: 5.5em; }

.text-muted { font-size: .85rem; color: var(--ink-soft); }
.text-error { color: var(--err); }
.text-ok    { color: var(--ok); }
.text-mono  { font-family: ui-monospace, 'Cascadia Mono', Consolas, monospace; font-size: .82rem; }

/* Kurzer Platzhaltertext in einer Karte oder Tabelle ("noch keine Eintraege").
   Ersetzt die bisherigen #999-Grautoene, die auf Weiss nur 2,85:1 erreichen
   und damit unter der lesbaren Mindestgrenze liegen. */
.app-note {
  color: var(--ink-muted);
  text-align: center;
  padding: 1.5rem;
  font-size: .9rem;
}
.portal-footnote {
  text-align: center;
  padding: 1.5rem;
  font-size: .82rem;
  color: var(--ink-muted);
  border-top: 1px solid var(--line);
  margin-top: 2rem;
}

/* --------------------------------------------------------------- Buttons --
   style.css bringt btn-primary und btn-secondary in Website-Groesse mit.
   In dichten Masken sind die zu gross; ausserdem fehlten bisher Zustaende
   fuer "laeuft gerade" und "loeschen". */
.portal-page .btn-primary,
.portal-page .btn-secondary,
.auth-page .btn-primary,
.auth-page .btn-secondary {
  font-size: .9rem;
  padding: 10px 18px;
  border-radius: var(--radius-sm);
  transition: background .16s var(--ease), border-color .16s var(--ease), opacity .16s var(--ease);
}
/* Der Versatz beim Hover ist auf einer Werbeseite Zierde, in einer Tabelle
   mit dreissig Aktionsbuttons ein Zappeln. */
.portal-page .btn-primary:hover,
.portal-page .btn-secondary:hover { transform: none; }

.btn-sm { font-size: .8rem !important; padding: 6px 12px !important; }

.btn-danger {
  background: var(--err);
  color: #fff;
  border: none;
  font-size: .875rem;
  padding: 10px 18px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-family: inherit;
  transition: background .16s var(--ease);
}
.btn-danger:hover { background: #8b2525; }

.btn-ghost {
  background: none;
  border: 1px solid var(--line-strong);
  color: var(--ink-soft);
  font-size: .875rem;
  padding: 9px 16px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-family: inherit;
  transition: border-color .16s var(--ease), color .16s var(--ease);
}
.btn-ghost:hover { border-color: var(--brand); color: var(--brand); }

/* Waehrend eine Aktion laeuft, muss der Button gesperrt und erkennbar sein,
   sonst wird doppelt abgeschickt. Das Attribut setzt das jeweilige Skript. */
.portal-page button[disabled],
.auth-page button[disabled] {
  opacity: .55;
  cursor: not-allowed;
}
button.is-busy { position: relative; color: transparent !important; }
button.is-busy::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 16px; height: 16px;
  border: 2px solid rgba(255,255,255,.4);
  border-top-color: #fff;
  border-radius: 50%;
  animation: app-spin .7s linear infinite;
}
.btn-secondary.is-busy::after,
.btn-ghost.is-busy::after {
  border-color: rgba(29,110,128,.3);
  border-top-color: var(--brand);
}
@keyframes app-spin { to { transform: rotate(360deg); } }

/* ---------------------------------------------------------- Rueckmeldung --
   Bisher gab es nur .auth-error. Erfolg, Hinweis und Warnung wurden auf den
   Seiten jeweils per Inline-Style nachgebaut - in leicht anderen Farben.
   Ein Satz Meldungsflaechen fuer alles. */
.app-msg {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  font-size: .875rem;
  line-height: 1.5;
  border: 1px solid;
  margin-bottom: 1rem;
  text-align: left;
}
.app-msg .ti { font-size: 18px; flex-shrink: 0; margin-top: 1px; }
.app-msg-ok    { background: var(--ok-bg);   border-color: var(--ok-line);   color: #2f5d16; }
.app-msg-warn  { background: var(--warn-bg); border-color: var(--warn-line); color: var(--warn); }
.app-msg-error { background: var(--err-bg);  border-color: var(--err-line);  color: var(--err); }
.app-msg-info  { background: var(--brand-100); border-color: #b8dde4; color: #14505e; }

.auth-error {
  border-radius: var(--radius-sm);
  line-height: 1.5;
}

/* ------------------------------------------------------------ Zustaende --
   Leer, laedt, Fehler. Ohne diese drei sieht eine Seite ohne Daten aus wie
   eine kaputte Seite - der haeufigste Grund fuer Rueckfragen von Kunden. */
.portal-empty {
  color: var(--ink-muted);
  padding: 1.25rem 0;
}
.app-empty {
  text-align: center;
  padding: 2.5rem 1.5rem;
  color: var(--ink-muted);
}
.app-empty .ti {
  font-size: 34px;
  color: var(--line-strong);
  display: block;
  margin-bottom: .6rem;
}
.app-empty strong {
  display: block;
  color: var(--ink);
  font-size: .95rem;
  font-weight: 600;
  margin-bottom: .25rem;
}
.app-empty p { font-size: .875rem; max-width: 34ch; margin: 0 auto; }
.app-empty .btn-primary,
.app-empty .btn-secondary { margin-top: 1rem; }

/* Platzhalterzeilen waehrend des Ladens. Ruhiger als ein Spinner, weil die
   Seite dabei ihre spaetere Form schon zeigt und nicht springt. */
.app-skeleton {
  background: linear-gradient(90deg, var(--surface-sunk) 25%, #f7fafb 37%, var(--surface-sunk) 63%);
  background-size: 400% 100%;
  animation: app-shimmer 1.3s ease-in-out infinite;
  border-radius: 6px;
  height: 1em;
  margin-bottom: .55rem;
}
.app-skeleton:last-child { margin-bottom: 0; width: 60%; }
@keyframes app-shimmer { from { background-position: 100% 0; } to { background-position: 0 0; } }

/* ---------------------------------------------------------------- Pills --
   Vorhandene Statusfarben auf die Tokens gezogen und lesbarer gemacht:
   die Pills standen bisher ohne Rand auf weissem Grund und sind bei
   hellen Fuellungen fast verschwunden. */
.status-pill,
.category-pill {
  display: inline-block;
  border: 1px solid transparent;
  font-weight: 600;
  white-space: nowrap;
}
.status-open { background: var(--warn-bg); color: var(--warn); border-color: var(--warn-line); }
.status-done { background: var(--ok-bg);   color: #2f5d16; border-color: var(--ok-line); }
.priority-hoch    { background: var(--err-bg);  color: var(--err);  border-color: var(--err-line); }
.priority-mittel  { background: var(--warn-bg); color: var(--warn); border-color: var(--warn-line); }
.priority-niedrig { background: #f1efe8; color: #5f5e5a; border-color: #ddd9cd; }

/* ---------------------------------------------------------------- Dialog --
   Der Dialog erschien bisher hart. Ein kurzer Auftritt zeigt, dass er ueber
   der Seite liegt und nicht die Seite ersetzt hat. */
.reset-modal { backdrop-filter: blur(2px); animation: app-fade .16s var(--ease); }
.reset-modal-box {
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  animation: app-rise .2s var(--ease);
}
@keyframes app-fade { from { opacity: 0; } }
@keyframes app-rise { from { opacity: 0; transform: translateY(10px) scale(.985); } }

.modal-section-title { color: var(--ink-muted); border-top-color: var(--line); }

/* ------------------------------------------------------------- Toolbar --- */
.portal-toolbar input[type="search"],
.portal-toolbar input[type="text"],
.portal-toolbar select {
  border-color: var(--line-strong);
  border-radius: var(--radius-sm);
}
.portal-toolbar .portal-toolbar-count { color: var(--ink-muted); }

/* ------------------------------------------------------------ Tabs ------- */
.admin-tabs { border-bottom-color: var(--line); flex-wrap: wrap; }
.admin-tab { color: var(--ink-soft); transition: color .16s var(--ease), border-color .16s var(--ease); }
.admin-tab:hover { color: var(--brand); }
.admin-tab.active { color: var(--brand); border-bottom-color: var(--brand); font-weight: 600; }

/* --------------------------------------------------------- Ticketverlauf - */
.msg-customer { background: var(--surface-alt); border-color: var(--line); }
.msg-admin { background: var(--brand-100); border-color: #b8dde4; }
.msg-time { color: var(--ink-muted); }

/* ------------------------------------------------------------ Anmeldung -- */
.auth-card {
  border-color: var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}
.auth-card h1 { color: var(--ink); }
.auth-sub { color: var(--ink-soft); }
.auth-logo img { height: 56px; }
.auth-switch { color: var(--ink-soft); }
.auth-switch a { color: var(--brand); }
.auth-back { color: var(--ink-muted); }
.auth-back:hover { color: var(--brand); }
.form-fieldset { border-color: var(--line); }
.form-fieldset legend { color: var(--brand); }
.form-hint { color: var(--ink-muted); }
.form-hint.hint-ok { color: var(--ok); }
.form-hint.hint-bad { color: var(--err); }

/* --------------------------------------------------------------- Mobile --
   portal.css legt die Seitenleiste unter 768px waagrecht. Das bleibt, wird
   aber bedienbar gemacht: Touchziele auf 44px und ein Hinweis, dass die
   Leiste seitlich scrollt. */
@media (max-width: 768px) {
  .portal-sidebar {
    padding: .5rem .75rem;
    gap: 4px;
    /* Ohne diese beiden Zeilen rastet die waagrechte Leiste beim Wischen
       irgendwo zwischen zwei Eintraegen ein. */
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
  }
  .portal-nav-item {
    min-height: 44px;
    padding: 10px 14px;
    scroll-snap-align: start;
  }
  .portal-logo img { height: 34px; }
  .portal-main { padding: 1.25rem 1rem 2.5rem; }
  .portal-main > h1 { font-size: 1.4rem; }

  /* white-space:nowrap aus portal.css laesst jede Tabelle beliebig breit
     werden - bei einer Adressspalte scrollt man dann minutenlang. Der
     Umbruch bleibt erlaubt, nur die Mindestbreite haelt die Spalten lesbar. */
  .admin-table { white-space: normal; }
  .admin-table th,
  .admin-table td { min-width: 8ch; }
  /* Der Kopf kann in einem eigenen Scrollbereich nicht kleben. */
  .admin-table th { position: static; }

  .portal-toolbar input[type="search"] { min-width: 0; }
  .reset-modal-box { padding: 1.25rem; }
  .modal-field-grid { grid-template-columns: 1fr; }
}

/* Auf Telefonen sind Eingabefelder unter 16px der Grund, warum iOS beim
   Antippen in das Feld hineinzoomt. */
@media (max-width: 480px) {
  .portal-page input,
  .portal-page select,
  .portal-page textarea,
  .auth-page input,
  .auth-page select,
  .auth-page textarea { font-size: 16px; }
  .auth-card { padding: 1.75rem 1.25rem; }
}

/* -------------------------------------------------- Reduzierte Bewegung --
   Wer Bewegung abbestellt hat, bekommt hier keine. Der Ladezustand bleibt
   trotzdem erkennbar - er haelt dann einfach still. */
@media (prefers-reduced-motion: reduce) {
  .app-skeleton,
  button.is-busy::after,
  .reset-modal,
  .reset-modal-box { animation: none; }
  .app-skeleton { background: var(--surface-sunk); }
  .portal-page *,
  .auth-page * { transition-duration: .01ms !important; }
}

/* ---------------------------------------------------------------- Druck --
   Rechnungen und Lizenzlisten werden ausgedruckt. Ohne diese Regeln landet
   die Navigation auf dem Papier und die Tabelle wird abgeschnitten. */
@media print {
  .portal-sidebar,
  .portal-toolbar,
  .skip-link,
  .cookie-banner { display: none !important; }
  .portal-main { padding: 0; max-width: none; }
  .portal-stat-card,
  .portal-panel,
  .table-card { box-shadow: none; border-color: #ccc; }
  .admin-table { font-size: 10pt; }
  .admin-table th { position: static; background: none; }
  a[href]::after { content: ""; }
}
