/* ==========================================================================
   Ciblu QR Tracker – Gestaltungssystem "Kartei"

   Entschieden in CQT-23, umgesetzt in CQT-24. Die Formensprache kommt aus dem
   Druck, für den diese Anwendung Codes erzeugt: harte Kanten, keine Rundungen,
   keine Schatten, ein Magenta als Schmuckfarbe. Zustandsfarben sind davon
   getrennt, damit "aktiv" und "die eine Hauptaktion" nicht dasselbe Signal
   senden.

   Ohne externe Schriften, Skripte oder Symbolbibliotheken – die
   Content-Security-Policy der Anwendung erlaubt sie nicht.
   ========================================================================== */

:root {
  /* Flächen und Linien */
  --ground: #fbfbfc;
  --surface: #ffffff;
  --surface-sunken: #f2f3f5;
  --rule: #e4e6ea;
  --rule-strong: #101216;

  /* Schrift
     --ink-faint trägt Auszeichnungen, Hinweise und Ziel-URLs, also echten Text
     in kleiner Größe. Der Wert ist deshalb so gewählt, dass er auf der Fläche
     mindestens 4,5:1 erreicht (WCAG AA) – ein hellerer Grauton sah ruhiger aus,
     war aber nicht lesbar genug.

     Gemessen gilt das für --ground (4,67:1) und --surface (4,83:1), nicht aber
     für --surface-sunken (4,35:1). Kleiner Text auf der gesenkten Fläche braucht
     deshalb --ink-soft; siehe die Regel für `th` (CQT-30). */
  --ink: #101216;
  --ink-soft: #545a63;
  --ink-faint: #6b7280;

  /* Schmuckfarbe */
  --accent: #a81550;
  --accent-ink: #ffffff;
  --accent-soft: #fdeef4;

  /* Zustände – bewusst nicht die Schmuckfarbe */
  --ok: #1b6337;
  --ok-soft: #e6f2ea;
  --warn: #8a5510;
  --warn-soft: #fbf0dd;
  --crit: #a3231c;
  --crit-soft: #fbe9e7;

  --focus: #a81550;

  --font-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  --font-mono: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace;

  /* Abstandsstufen – Layout setzt Abstände über gap, nicht über Ränder */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.5rem;
  --sp-6: 2rem;
  --sp-7: 3rem;

  --edge: 1.5px;
  --content: 66rem;

  /* Höhe aller Bedienelemente an einer Stelle. Wird sie je Seite nachjustiert,
     stehen Felder und Schaltflächen wieder unterschiedlich hoch. */
  --control-height: 2.25rem;
  --control-height-small: 1.9rem;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground: #0d0f12;
    --surface: #14171b;
    --surface-sunken: #1a1e23;
    --rule: #262b32;
    --rule-strong: #e6e8ec;

    --ink: #e8eaee;
    --ink-soft: #a5adb8;
    --ink-faint: #78818d;

    /* Auf dunklem Grund aufgehellt, damit der Kontrast tragfähig bleibt */
    --accent: #f0699b;
    --accent-ink: #1a0410;
    --accent-soft: #2a1220;

    --ok: #6cc48f;
    --ok-soft: #16281d;
    --warn: #e0b158;
    --warn-soft: #2b2211;
    --crit: #e58b80;
    --crit-soft: #2d1614;

    --focus: #f0699b;
  }
}

:root[data-theme="dark"] {
  --ground: #0d0f12;
  --surface: #14171b;
  --surface-sunken: #1a1e23;
  --rule: #262b32;
  --rule-strong: #e6e8ec;

  --ink: #e8eaee;
  --ink-soft: #a5adb8;
  --ink-faint: #78818d;

  --accent: #f0699b;
  --accent-ink: #1a0410;
  --accent-soft: #2a1220;

  --ok: #6cc48f;
  --ok-soft: #16281d;
  --warn: #e0b158;
  --warn-soft: #2b2211;
  --crit: #e58b80;
  --crit-soft: #2d1614;

  --focus: #f0699b;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

a {
  color: var(--accent);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

/* --------------------------------------------------------------------------
   Grundgerüst
   -------------------------------------------------------------------------- */

.app-shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.app-header {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  padding: var(--sp-3) var(--sp-6);
  background: var(--surface);
  border-bottom: var(--edge) solid var(--rule-strong);
  flex-wrap: wrap;
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-weight: 700;
  font-size: 1rem;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.brand:hover {
  text-decoration: none;
}

.brand-mark {
  width: 22px;
  height: 22px;
  flex: none;
  color: var(--ink);
}

.app-nav {
  display: flex;
  gap: var(--sp-4);
  flex: 1 1 auto;
}

.app-nav a {
  color: var(--ink-soft);
  font-size: 0.92rem;
  padding-bottom: 2px;
}

.app-nav a:hover {
  color: var(--ink);
  text-decoration: none;
}

.app-nav a.active {
  color: var(--ink);
  font-weight: 600;
  box-shadow: inset 0 -2px 0 var(--accent);
}

.header-right {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}

.user-name {
  font-size: 0.85rem;
  color: var(--ink-faint);
  font-family: var(--font-mono);
}

.app-main {
  flex: 1 1 auto;
  width: 100%;
  max-width: var(--content);
  margin: 0 auto;
  padding: var(--sp-6) var(--sp-6) var(--sp-7);
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

.app-footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-6);
  border-top: 1px solid var(--rule);
  color: var(--ink-faint);
  font-size: 0.8rem;
  font-family: var(--font-mono);
}

.plain-shell {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--sp-5);
}

/* --------------------------------------------------------------------------
   Typografie
   -------------------------------------------------------------------------- */

h1 {
  font-size: clamp(1.7rem, 4vw, 2.1rem);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.1;
  margin: 0;
  text-wrap: balance;
}

h2 {
  font-size: 1.1rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0;
}

h3 {
  font-size: 0.95rem;
  font-weight: 600;
  margin: 0;
}

p {
  margin: 0;
}

code {
  font-family: var(--font-mono);
  font-size: 0.9em;
}

.label {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.hint {
  font-size: 0.83rem;
  color: var(--ink-faint);
  margin: 0;
}

.muted {
  color: var(--ink-faint);
}

/* Für Hilfsmittel lesbar, visuell nicht vorhanden – etwa Spaltentitel,
   die auf dem Bildschirm überflüssig sind. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.small {
  font-size: 0.83rem;
}

.page-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-5);
  flex-wrap: wrap;
}

.page-head-actions {
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
}

/* --------------------------------------------------------------------------
   Kennzahlen
   -------------------------------------------------------------------------- */

.summary {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-6);
}

.summary-item {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.summary-value {
  font-size: 1.6rem;
  font-weight: 700;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

.summary-value.is-text {
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0;
}

/* --------------------------------------------------------------------------
   Kennzahlen-Karten

   In der Formensprache der Kartei: harte Kanten, kein Eckenradius, keine
   Schatten. Die ganze Karte ist ein Verweis – eine Kennzahl ohne Ziel wäre
   eine Sackgasse.
   -------------------------------------------------------------------------- */

.kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: var(--sp-3);
}

.kpi {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  background: var(--surface);
  border: 1px solid var(--rule);
  padding: var(--sp-4);
  color: var(--ink);
  min-height: 8.5rem;
}

.kpi:hover {
  border-color: var(--ink);
  text-decoration: none;
}

.kpi:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* Warnton nur, wenn tatsächlich etwas ansteht. Eine Kachel, die immer warnt,
   wird nach zwei Tagen übersehen. */
.kpi-warn {
  border-left: 3px solid var(--warn);
}

.kpi-label {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.kpi-value {
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.kpi-delta,
.kpi-hint {
  font-size: 0.8rem;
}

.kpi-hint {
  color: var(--ink-faint);
}

.kpi-delta-up {
  color: var(--ok);
}

.kpi-delta-down {
  color: var(--crit);
}

.kpi-delta-flat {
  color: var(--ink-faint);
}

.kpi .spark {
  margin-top: auto;
  width: 100%;
  color: var(--accent);
}

/* Zwei Flächen nebeneinander (Wochentage und Rangliste) */
.detail-columns {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: var(--sp-4);
}

/* Wochentagsverteilung als waagerechte Balken */
.weekdays {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.weekday {
  display: grid;
  grid-template-columns: 6.5rem 1fr auto;
  align-items: center;
  gap: var(--sp-3);
}

.weekday-name {
  font-size: 0.85rem;
  color: var(--ink-soft);
}

.weekday-bar {
  display: block;
  height: 0.85rem;
  background: var(--surface-sunken);
  border: 1px solid var(--rule);
}

.weekday-fill {
  display: block;
  height: 100%;
  background: var(--accent);
}

.weekday-value {
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
  font-weight: 600;
}

/* Rangliste der meistgenutzten Kurzlinks */
.top-links {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.top-links li {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--rule);
}

.top-links li:last-child {
  border-bottom: 0;
}

.top-qr {
  flex: none;
  background: #ffffff;
  border: 1px solid var(--rule);
  display: block;
}

.top-id {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.top-id span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.top-scans {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 1.05rem;
}

/* --------------------------------------------------------------------------
   Flächen
   -------------------------------------------------------------------------- */

.panel {
  background: var(--surface);
  border: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
}

.panel-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--rule);
}

.panel-body {
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

/* --------------------------------------------------------------------------
   Bedienelemente
   -------------------------------------------------------------------------- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  border: var(--edge) solid var(--rule-strong);
  border-radius: 0;
  background: var(--surface);
  color: var(--ink);
  padding: 0.42rem 0.9rem;
  font: inherit;
  font-size: 0.88rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}

.button:hover:not(:disabled) {
  background: var(--surface-sunken);
  text-decoration: none;
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.button-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.button-primary:hover:not(:disabled) {
  background: var(--accent);
  filter: brightness(1.12);
}

.button-quiet {
  border-color: var(--rule);
  font-weight: 500;
}

.button-danger {
  border-color: var(--crit);
  color: var(--crit);
  background: transparent;
}

.button-danger:hover:not(:disabled) {
  background: var(--crit-soft);
}

.button-small {
  padding: 0.28rem 0.6rem;
  font-size: 0.8rem;
}

.button-link {
  border: 0;
  background: none;
  padding: 0;
  color: var(--accent);
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
}

/* Umschalter Karten/Tabelle */
.viewswitch {
  display: inline-flex;
  border: var(--edge) solid var(--rule-strong);
}

.viewswitch button {
  border: 0;
  background: var(--surface);
  color: var(--ink-soft);
  font: inherit;
  font-size: 0.83rem;
  font-weight: 600;
  padding: 0.35rem 0.75rem;
  cursor: pointer;
}

.viewswitch button + button {
  border-left: 1px solid var(--rule);
}

.viewswitch button[aria-pressed="true"] {
  background: var(--ink);
  color: var(--ground);
}

/* --------------------------------------------------------------------------
   Formulare
   -------------------------------------------------------------------------- */

input,
select,
textarea {
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 0;
  padding: 0.45rem 0.6rem;
  max-width: 100%;
  width: 100%;
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  border-color: var(--accent);
}

input:disabled {
  color: var(--ink-faint);
  background: var(--surface-sunken);
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  min-width: 0;
}

.field-inline {
  flex-direction: row;
  align-items: center;
  gap: var(--sp-2);
}

.field-inline input[type="checkbox"] {
  width: auto;
}

.field label,
.field .label {
  display: block;
}

.form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  max-width: 40rem;
}

.form-actions {
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
  padding-top: var(--sp-1);
}

/* Unterstrichene Eingabe für die Detailansicht */
.field-underline input {
  border: 0;
  border-bottom: var(--edge) solid var(--rule-strong);
  padding: 0.3rem 0;
  background: transparent;
}

/* Filterleiste

   Wichtig: Die flex-Angaben gelten nur für **direkte** Kinder der Leiste.
   Als Nachfahrenregel geschrieben trafen sie auch Felder, die in einem
   .field-Container mit `flex-direction: column` liegen. Dort ist die Hauptachse
   die Höhe – aus "12 rem breit" wurde "12 rem hoch": 192 px hohe und 21 px
   breite Auswahlfelder auf der Protokollseite. Kein Stilfehler im Detail,
   sondern eine Regel am falschen Ort. */
.toolbar {
  display: flex;
  gap: var(--sp-3);
  align-items: center;
  flex-wrap: wrap;
}

.toolbar > .search {
  flex: 1 1 15rem;
  border: var(--edge) solid var(--rule-strong);
}

.toolbar > select {
  flex: 0 1 12rem;
  border: 1px solid var(--rule);
}

/* Beschriftetes Feld in der Leiste: Die Breite gehört an den Container,
   das Bedienelement füllt ihn aus. */
.toolbar > .field {
  flex: 0 1 12rem;
}

/* Einheitliche Höhe aller Bedienelemente.

   Bewusst eine feste Höhe und nicht nur eine Mindesthöhe: Rahmenstärke,
   Innenabstand und die vom Browser mitgebrachte Zeilenhöhe unterscheiden sich je
   Feldtyp. Mit `min-height` allein standen Suchfeld (40 px), Auswahlfeld (38 px)
   und Schaltfläche (37 px) unterschiedlich hoch nebeneinander. */
input:not([type="checkbox"]),
select {
  height: var(--control-height);
}

.button {
  min-height: var(--control-height);
}

.button-small {
  min-height: var(--control-height-small);
}

textarea {
  min-height: 4.5rem;
}

/* --------------------------------------------------------------------------
   Auszeichnungen
   -------------------------------------------------------------------------- */

.tag {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0.15rem 0.6rem;
  background: transparent;
  color: var(--ink-soft);
  font: inherit;
  font-size: 0.78rem;
  cursor: pointer;
}

.tag:hover {
  border-color: var(--ink-soft);
}

.tag-static {
  cursor: default;
}

.tag[aria-pressed="true"],
.tag-selected {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--ground);
}

.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
}

.state {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.8rem;
  color: var(--ink-soft);
  white-space: nowrap;
}

.state::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

.state-active {
  color: var(--ok);
}

.state-warn {
  color: var(--warn);
}

.state-off {
  color: var(--ink-faint);
}

.state-deleted {
  color: var(--crit);
}

/* --------------------------------------------------------------------------
   Karten
   -------------------------------------------------------------------------- */

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
  gap: var(--sp-4);
}

.card {
  background: var(--surface);
  border: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
}

.card-top {
  display: flex;
  gap: var(--sp-3);
  padding: var(--sp-3);
  border-bottom: 1px solid var(--rule);
  min-width: 0;
}

.card-qr {
  width: 66px;
  height: 66px;
  flex: none;
  background: #ffffff;
  border: 1px solid var(--rule);
  display: block;
}

.card-id {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
}

.card-code {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.98rem;
  letter-spacing: -0.01em;
  color: var(--ink);
  overflow-wrap: anywhere;
}

a.card-code:hover {
  color: var(--accent);
  text-decoration: none;
}

.card-title,
.card-target {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card-title {
  font-size: 0.85rem;
  color: var(--ink-soft);
}

.card-target {
  font-size: 0.78rem;
  color: var(--ink-faint);
}

.card-body {
  padding: var(--sp-3);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-3);
}

.card-scans {
  display: flex;
  flex-direction: column;
}

.card-scans-value {
  font-size: 1.3rem;
  font-weight: 700;
  letter-spacing: -0.025em;
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}

.card-foot {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-top: 1px solid var(--rule);
  flex-wrap: wrap;
}

.card-foot-actions {
  margin-left: auto;
  display: flex;
  gap: var(--sp-1);
  align-items: center;
}

/* --------------------------------------------------------------------------
   Tabelle
   -------------------------------------------------------------------------- */

.table-scroll {
  overflow-x: auto;
  border: 1px solid var(--rule);
  background: var(--surface);

  /* Haelt die Scrollflaeche bei sich. Ohne diese Zeile schlaegt die Breite der
     Tabelle auf das Dokument durch, sobald der Block in einem Container mit
     Innenabstand liegt (.panel > .panel-body): Die Seite liess sich dann auf
     375 px um 239 px nach rechts ziehen, in leere Flaeche hinein. */
  contain: paint;
}

table {
  width: 100%;
  border-collapse: collapse;
  min-width: 44rem;
}

/* Der Tabellenkopf ist der einzige Ort, an dem kleiner Text auf der gesenkten
   Fläche steht. Dort reicht --ink-faint nicht: 4,35:1 hell und 4,25:1 dunkel,
   die Grenze für kleinen Text liegt bei 4,5:1 (CQT-30). Auf --ground und
   --surface bleibt --ink-faint tragfähig, deshalb wird hier die Farbe getauscht
   und nicht der Token verändert. */
th {
  text-align: left;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
  font-weight: 500;
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--rule);
  background: var(--surface-sunken);
  white-space: nowrap;
}

td {
  padding: var(--sp-3);
  border-bottom: 1px solid var(--rule);
  vertical-align: middle;
}

tbody tr:last-child td {
  border-bottom: 0;
}

.numeric {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.nowrap {
  white-space: nowrap;
}

.cell-actions {
  display: flex;
  gap: var(--sp-1);
  flex-wrap: wrap;
  justify-content: flex-end;
}

.cell-target {
  color: var(--ink-soft);
  font-size: 0.85rem;
}

.cell-target span {
  display: block;
  max-width: 20rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Sortierbare Spaltentitel: Die Schaltfläche füllt den Titel aus, damit die
   gesamte Fläche klickbar ist und nicht nur der Text. */
.th-sort {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  border: 0;
  background: none;
  padding: 0;
  margin: 0;
  min-height: 0;
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  white-space: nowrap;
}

.th-sort:hover {
  color: var(--ink);
}

.th-sort-mark {
  color: var(--accent);
  font-size: 0.9em;
}

th[aria-sort="ascending"] .th-sort,
th[aria-sort="descending"] .th-sort {
  color: var(--ink);
}

/* Filterzeile in der Tabelle */
.filter-row td {
  padding: var(--sp-2) var(--sp-3);
  background: var(--surface-sunken);
  border-bottom: var(--edge) solid var(--rule-strong);
  vertical-align: top;
}

/* In der Tabellenkopfzeile bewusst kompakter als in einer Filterleiste –
   aber untereinander exakt gleich hoch. */
.filter-row input:not([type="checkbox"]),
.filter-row select {
  font-size: 0.85rem;
  padding: 0.2rem 0.45rem;
  height: var(--control-height-small);
}

.filter-dates {
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
}

.filter-dates input {
  flex: 1 1 8.5rem;
  min-width: 0;
}

.table-empty {
  text-align: center;
  color: var(--ink-faint);
  padding: var(--sp-6) var(--sp-3);
}

/* Zeile mit Trefferzahl und Hinweis über der Tabelle */
.result-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--ink-soft);
}

/* --------------------------------------------------------------------------
   Meldungen
   -------------------------------------------------------------------------- */

.alert {
  padding: var(--sp-3) var(--sp-4);
  border-left: 3px solid currentColor;
  font-size: 0.9rem;
}

.alert ul {
  margin: 0;
  padding-left: 1.1rem;
}

.alert-error {
  background: var(--crit-soft);
  color: var(--crit);
}

.alert-success {
  background: var(--ok-soft);
  color: var(--ok);
}

.alert-warn {
  background: var(--warn-soft);
  color: var(--warn);
}

.empty {
  border: 1px dashed var(--rule);
  padding: var(--sp-6);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  color: var(--ink-faint);
}

/* --------------------------------------------------------------------------
   Anmeldung
   -------------------------------------------------------------------------- */

.login-card {
  background: var(--surface);
  border: var(--edge) solid var(--rule-strong);
  padding: var(--sp-6);
  width: min(24rem, 100%);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

.login-card h1 {
  font-size: 1.4rem;
}

.login-card form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

/* Abgesetzter Bereich für Entwicklungsfunktionen: Er soll sichtbar nicht zum
   regulären Anmeldeweg gehören. */
.dev-shortcut {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding-top: var(--sp-4);
  border-top: 1px dashed var(--rule);
}

.login-brand {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

/* --------------------------------------------------------------------------
   Detailansicht
   -------------------------------------------------------------------------- */

.detail-layout {
  display: grid;
  grid-template-columns: 15rem 1fr;
  gap: var(--sp-6);
  align-items: start;
}

.detail-qr {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.detail-qr img {
  width: 100%;
  height: auto;
  display: block;
  background: #ffffff;
  border: var(--edge) solid var(--rule-strong);
  padding: 10px;
}

.detail-fields {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  min-width: 0;
}

.detail-code {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 1.5rem;
  letter-spacing: -0.02em;
  overflow-wrap: anywhere;
}

.detail-value {
  overflow-wrap: anywhere;
  font-size: 0.95rem;
}

.copy-row {
  display: flex;
  gap: var(--sp-2);
  align-items: center;
  flex-wrap: wrap;
}

.copy-row .short-url {
  font-family: var(--font-mono);
  font-size: 0.9rem;
  overflow-wrap: anywhere;
}

/* --------------------------------------------------------------------------
   Verlaufskurven und Diagramme
   -------------------------------------------------------------------------- */

.spark {
  display: block;
  color: var(--accent);
  flex: none;
}

.chart {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

.chart svg {
  width: 100%;
  height: 130px;
  display: block;
  color: var(--accent);
}

.chart-bar {
  fill: currentColor;
}

.chart-axis {
  stroke: var(--rule);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* Bezugslinie auf Höhe des Höchstwerts */
.chart-reference {
  stroke: var(--rule);
  stroke-width: 1;
  stroke-dasharray: 3 3;
  vector-effect: non-scaling-stroke;
}

.chart-caption {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-2);
  color: var(--ink-faint);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  flex-wrap: wrap;
}

/* --------------------------------------------------------------------------
   Seitenwechsel
   -------------------------------------------------------------------------- */

.pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
  color: var(--ink-faint);
  font-size: 0.85rem;
  font-family: var(--font-mono);
}

/* --------------------------------------------------------------------------
   Dialog
   -------------------------------------------------------------------------- */

.dialog-backdrop {
  position: fixed;
  inset: 0;
  background: rgb(0 0 0 / 0.55);
  display: grid;
  place-items: center;
  padding: var(--sp-4);
  z-index: 50;
}

.dialog {
  background: var(--surface);
  border: var(--edge) solid var(--rule-strong);
  padding: var(--sp-5);
  width: min(30rem, 100%);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-2);
  flex-wrap: wrap;
  padding-top: var(--sp-2);
}

/* --------------------------------------------------------------------------
   Protokoll
   -------------------------------------------------------------------------- */

.change-cell {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  max-width: 22rem;
}

.change-old {
  color: var(--ink-faint);
  text-decoration: line-through;
  overflow-wrap: anywhere;
}

.change-new {
  overflow-wrap: anywhere;
}

.log-time {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--ink-faint);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Verbindungsverlust – sperrende Überlagerung, damit Eingaben nicht
   ins Leere laufen
   -------------------------------------------------------------------------- */

#blazor-error-ui {
  display: none;
  position: fixed;
  inset: 0;
  background: rgb(0 0 0 / 0.6);
  z-index: 100;
  place-items: center;
  padding: var(--sp-4);
  text-align: center;
}

#blazor-error-ui > * {
  background: var(--warn-soft);
  color: var(--warn);
  border: var(--edge) solid currentColor;
  padding: var(--sp-5);
  max-width: 26rem;
}

#blazor-error-ui[style*="display: block"],
#blazor-error-ui[style*="display:block"] {
  display: grid !important;
}

#blazor-error-ui .reload {
  display: inline-block;
  margin-top: var(--sp-3);
  color: inherit;
  font-weight: 700;
  text-decoration: underline;
}

/* --------------------------------------------------------------------------
   Schmale Fenster
   -------------------------------------------------------------------------- */

@media (max-width: 760px) {
  .app-header {
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
  }

  .app-nav {
    order: 3;
    flex-basis: 100%;

    /* Umbrechen statt schieben: Mit dem fuenften Eintrag lag "Konto" ausserhalb
       des Sichtfelds, und ein seitlich scrollbares Menue zeigt nicht an, dass da
       noch etwas ist. */
    flex-wrap: wrap;
    row-gap: var(--sp-2);
    gap: var(--sp-3);
    padding-bottom: 2px;
  }

  .header-right {
    margin-left: auto;
  }

  .app-main {
    padding: var(--sp-4) var(--sp-4) var(--sp-6);
  }

  .app-footer {
    padding: var(--sp-3) var(--sp-4);
  }

  .summary {
    gap: var(--sp-4) var(--sp-5);
  }

  .detail-layout {
    grid-template-columns: 1fr;
    gap: var(--sp-5);
  }

  .detail-qr {
    max-width: 15rem;
  }

  .toolbar .search {
    flex-basis: 100%;
  }

  .cell-target span {
    max-width: 12rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ==========================================================================
   Betreiberportal

   Bewusst anders als der Kundenbereich. Wer hier arbeitet, sieht die Daten
   aller Kunden; sähe das Portal aus wie die Kundenoberfläche, wäre die
   naheliegendste Verwechslung auch die folgenreichste. Der Balken oben sagt
   deshalb bei jedem Blick, wo man ist.
   ========================================================================== */

.ops-shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--ground);
}

.ops-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-5);
  background: var(--rule-strong);
  color: var(--ground);
  border-bottom: var(--edge) solid var(--accent);
}

/* Gefüllt statt umrandet: Die Schmuckfarbe als Textfarbe auf dem dunklen Balken
   erreicht nur 2,58:1 und ist damit nicht lesbar. Als Fläche mit --accent-ink
   darauf sind es rund 7:1 – und das Abzeichen ist zugleich deutlicher. */
.ops-mark {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  background: var(--accent);
  color: var(--accent-ink);
  padding: 0.15rem var(--sp-3);
}

.ops-nav { display: flex; gap: var(--sp-4); }

.ops-nav a { color: var(--ground); text-decoration: none; }

/* Wie im Kundenbereich: Der aktive Eintrag wird unterstrichen, nicht eingefärbt.
   Die Schmuckfarbe als Textfarbe auf dem dunklen Balken erreicht nur 2,58:1. */
.ops-nav a.active {
  box-shadow: inset 0 -2px 0 var(--accent);
  font-weight: 600;
}

.ops-user {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 0.8rem;
}

.ops-main {
  flex: 1;
  width: min(var(--content), 100%);
  margin: 0 auto;
  padding: var(--sp-6) var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

.ops-foot {
  padding: var(--sp-4) var(--sp-5);
  border-top: var(--edge) solid var(--rule);
  color: var(--ink-soft);
  font-size: 0.87rem;
}

.ops-login {
  width: min(26rem, 100%);
  margin: var(--sp-7) auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  background: var(--surface);
  border: var(--edge) solid var(--rule-strong);
  padding: var(--sp-6);
}

.ops-login form { display: flex; flex-direction: column; gap: var(--sp-4); }

/* --------------------------------------------------------------------------
   Kontingentanzeige (CQT-33)
   -------------------------------------------------------------------------- */

.quota-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--sp-4);
  margin: 0;
}

/* Die Beschriftung nutzt die gemeinsame Regel .label - zwei Panels
   nebeneinander sollen nicht zwei Schriftbilder zeigen. */

.quota dd {
  margin: var(--sp-1) 0 0;
  font-variant-numeric: tabular-nums;
}

.quota-bar {
  display: block;
  height: var(--sp-2);
  margin-top: var(--sp-2);
  background: var(--ground);
  border: 1px solid var(--rule);
}

.quota-bar-fill {
  display: block;
  height: 100%;

  /* Sichtbar bleiben: Ein Aufruf von tausend ergaebe sonst ein Drittel Pixel,
     und "etwas belegt" saehe aus wie "nichts belegt". */
  min-width: 2px;
  background: var(--ink-soft);
}

/* Erreicht ist ein Zustand, nicht die Hauptaktion - deshalb die Warnfarbe
   und nicht die Schmuckfarbe. */
.quota-bar-full {
  background: var(--warn);
}

/* Schiebt ein Element im Kartenkopf nach rechts. Als Klasse statt inline,
   damit Abstaende aus den Token kommen. */
.push-right {
  margin-left: auto;
}

/* --------------------------------------------------------------------------
   Vertragstexte (CQT-45)
   -------------------------------------------------------------------------- */

/* Schmaler als der Rest: Ein Vertragstext wird gelesen, nicht überflogen, und
   über die volle Breite eines Bildschirms verliert man die Zeile. */
.legal-text {
  max-width: 42rem;
  font-size: 0.92rem;
  line-height: 1.6;
}

.legal-text h3,
.legal-text h4 {
  margin: var(--sp-5) 0 var(--sp-2);
}

.legal-text h3:first-child,
.legal-text h4:first-child {
  margin-top: 0;
}

.legal-text p {
  margin: 0 0 var(--sp-3);
}

/* Aufeinanderfolgende Aufzählungspunkte stehen je in einer eigenen Liste –
   der Textzerleger kennt keine Gruppen. Der Abstand gleicht das aus, damit sie
   nicht wie einzelne Absätze aussehen. */
.legal-text ul {
  margin: 0 0 var(--sp-1);
  padding-left: 1.2rem;
}

.legal-text ul + ul {
  margin-top: calc(var(--sp-1) * -1);
}

/* --------------------------------------------------------------------------
   Rechnungen (CQT-40)
   -------------------------------------------------------------------------- */

/* Auf breiten Fenstern zeigt die Tabelle alles; die Karten bleiben aus. */
.invoice-cards {
  display: none;
}

/* Gleichmäßige Zeilenhöhen: Ohne diese Vorgabe war eine Zeile mit
   "Jetzt zahlen" 56 px hoch und eine bezahlte ohne Schaltfläche 48 px – die
   Tabelle sprang je nach Zustand der Rechnung.

   Die Angabe steht an der Zelle und nicht am Container in ihr: `min-height`
   auf einem Element mit `display: flex` innerhalb einer Tabellenzelle wirkte
   nicht auf die Zeilenhöhe, weil die Zeile ihre Höhe aus den Zellen bezieht.
   In der Tabellendarstellung ist `height` an der Zelle eine Mindesthöhe –
   genau das, was hier gebraucht wird. */
.invoice-table tbody td {
  height: 2rem;
}

/* Zustand und Aktion nebeneinander.

   Die beiden letzten Spalten schrumpfen auf ihren Inhalt, die überschüssige
   Breite verteilen die vorderen. Vorher nahm die Zustandsspalte den Rest der
   Tabelle auf, und "Jetzt zahlen" stand über hundert Pixel von "offen"
   entfernt am rechten Rand – der Zusammenhang war optisch zerrissen.

   `justify-content` allein half nicht: Die Schaltfläche saß bereits am linken
   Rand ihrer Spalte, nur begann die Spalte zu weit rechts. */
.invoice-table th:nth-last-child(2),
.invoice-table td:nth-last-child(2),
.invoice-table th:last-child,
.invoice-table td:last-child {
  width: 1%;
  white-space: nowrap;
}

.invoice-table .cell-actions {
  align-items: center;
  justify-content: flex-start;
}

@media (max-width: 760px) {
  .invoice-table {
    display: none;
  }

  .invoice-cards {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    margin: 0;
    padding: var(--sp-4);
    list-style: none;
  }

  .invoice-card {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    padding: var(--sp-3);
    border: 1px solid var(--rule);
    background: var(--surface);
  }

  /* Nummer und Zustand in einer Zeile: Beides zusammen beantwortet die Frage,
     mit der jemand diese Seite öffnet. */
  .invoice-card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    flex-wrap: wrap;
  }

  .invoice-card-amount {
    margin: 0;
    font-size: 1.05rem;
    font-variant-numeric: tabular-nums;
  }

  .invoice-card-dates {
    display: flex;
    gap: var(--sp-5);
    margin: 0;
  }

  .invoice-card-dates dd {
    margin: var(--sp-1) 0 0;
    font-variant-numeric: tabular-nums;
  }

  /* Über die volle Breite: Die Hauptaktion soll auf einem Telefon nicht als
     schmale Schaltfläche am Rand stehen. */
  .invoice-card .button {
    width: 100%;
    justify-content: center;
  }
}

/* Aufzählung ohne Punkte, für kurze Listen in einem Panel (CQT-46). */
.plain-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  font-size: 0.9rem;
}

/* --------------------------------------------------------------------------
   Missbrauchsbetrieb (CQT-47)

   Die Fälle stehen als Karten und nicht als Tabelle: Ein Fall ist eine
   Entscheidung mit Begründung, keine Zeile zum Vergleichen. Als Karte trägt er
   sein Formular bei sich und braucht auf 375 px keine eigene zweite Darstellung
   (die Lehre aus CQT-40).
   -------------------------------------------------------------------------- */

/* Der Zustand der eigenen Domain. Die drei Fälle bekommen drei Farben, und
   "nicht geprüft" bekommt ausdrücklich nicht die von "in Ordnung": Der
   Unterschied zwischen beiden ist der Kern dieser Anzeige. */
.domain-state {
  display: grid;
  gap: var(--sp-1);
  padding: var(--sp-4);
  border-left: 3px solid currentColor;
}

.domain-listed {
  background: var(--crit-soft);
  color: var(--crit);
}

.domain-clear {
  background: var(--ok-soft);
  color: var(--ok);
}

.domain-unchecked {
  background: var(--warn-soft);
  color: var(--warn);
}

.domain-verdict {
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.domain-host {
  font-size: 0.9rem;
  overflow-wrap: anywhere;
}

.domain-note,
.domain-when {
  margin: 0;
  font-size: 0.85rem;
}

.domain-when {
  font-variant-numeric: tabular-nums;
}

.abuse-cases {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--sp-4);
}

.abuse-case {
  display: grid;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--rule);
  background: var(--surface);
  min-width: 0;
}

.abuse-case-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-4);
}

/* Eine offene Entscheidung ist der Anlass, überhaupt hier zu sein - sie darf
   nicht wie eine Fussnote aussehen. */
.label-open {
  color: var(--ink);
  font-weight: 700;
}

.abuse-case-facts {
  margin: 0;
  display: grid;
  grid-template-columns: 11rem minmax(0, 1fr);
  gap: var(--sp-2) var(--sp-4);
  font-size: 0.9rem;
}

.abuse-case-facts dd {
  margin: 0;
  min-width: 0;
}

/* Ziele sind lang und haben keine Leerzeichen. Ohne diese Zeile schiebt ein
   einziges Ziel die Seite auf schmalen Fenstern nach rechts. */
.abuse-case-target {
  overflow-wrap: anywhere;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--ink-soft);
}

.abuse-case-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.abuse-case-form {
  display: grid;
  gap: var(--sp-3);
  padding: var(--sp-3);
  background: var(--surface-sunken);
}

.abuse-found {
  margin-top: var(--sp-4);
}

@media (max-width: 640px) {
  /* Untereinander: Zwei Spalten mit fester Beschriftungsbreite lassen auf einem
     Telefon fuer den Wert kaum Platz. */
  .abuse-case-facts {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-1);
  }

  .abuse-case-facts dt {
    margin-top: var(--sp-2);
  }

  .abuse-case-actions > .button {
    width: 100%;
    justify-content: center;
  }
}

/* --------------------------------------------------------------------------
   Zweiter Faktor (CQT-21)
   -------------------------------------------------------------------------- */

.totp-setup {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--sp-4);
  margin-bottom: var(--sp-4);
}

/* Weisser Grund unter dem QR-Code: Im dunklen Erscheinungsbild waere er sonst
   heller Code auf hellem Grund im Bild und dunkel drumherum - manche Kameras
   lesen das nicht. */
.totp-setup img {
  background: #ffffff;
  border: 1px solid var(--rule);
  padding: var(--sp-2);
  flex: none;
}

.totp-setup-manual {
  display: grid;
  gap: var(--sp-1);
  min-width: 0;
}

.totp-setup-manual p {
  margin: 0;
}

/* Umbrechen erlaubt: Das Geheimnis ist 32 Zeichen ohne Leerraum und schoebe
   die Seite auf einem Telefon sonst nach rechts. */
.totp-secret {
  font-size: 0.95rem;
  letter-spacing: 0.08em;
  overflow-wrap: anywhere;
  background: var(--surface-sunken);
  padding: var(--sp-2) var(--sp-3);
}

.recovery-codes {
  margin: 0 0 var(--sp-4);
  padding: var(--sp-3);
  list-style: none;
  background: var(--surface-sunken);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: var(--sp-2);
  font-size: 0.95rem;
  letter-spacing: 0.04em;
}

/* --------------------------------------------------------------------------
   Massenanlage (CQT-22)
   -------------------------------------------------------------------------- */

/* Ziele in der Vorschau, einheitlich mit den uebrigen Zieldarstellungen.

   Eine Hoechstbreite stand hier zuerst mit - sie tat nichts. In einer Tabelle
   mit automatischem Layout richtet sich die Spaltenbreite nach dem Inhalt,
   und ein max-width an der Zelle wird ueberschritten (gemessen: 480 px statt
   der angegebenen 352 px). Ein langes Ziel laesst die Tabelle deshalb breiter
   werden; sie scrollt dann in ihrem eigenen Container - genau wie das
   Protokoll und die Statistik. */
.import-target {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  color: var(--ink-soft);
}

/* Die Dateiauswahl traegt die gemeinsame Hoehe der Bedienelemente, damit sie
   neben den uebrigen Feldern nicht aus der Reihe faellt. */
.field input[type="file"] {
  font: inherit;
  font-size: 0.9rem;
  padding: var(--sp-2);
  border: 1px solid var(--rule);
  background: var(--surface);
  color: var(--ink);
  width: 100%;
}
