/**
 * TP Design System - Design Tokens
 * Colors extracted 1:1 from IssueHub (crates/issuehub-web/src/app.css).
 * Scale (spacing, typography, density) added by D-00T.
 */

/* ===========================
   Scale — theme independent
   (spacing, typography, density; see plan 0010 / D-00T)
   =========================== */
:root {
  /* Spacing — 4px grid; -0-5 / -1-5 are half steps (Tailwind reading) */
  --tp-space-0:    0;
  --tp-space-0-5:  2px;
  --tp-space-1:    4px;
  --tp-space-1-5:  6px;
  --tp-space-2:    8px;
  --tp-space-3:   12px;
  --tp-space-4:   16px;
  --tp-space-5:   20px;
  --tp-space-6:   24px;
  --tp-space-8:   32px;
  --tp-space-12:  48px;

  /* Font size — 5 steps. Named `font-size` because --tp-text* are colors. */
  --tp-font-size-1: 10px;  /* micro: uppercase label, table head, chip, icon */
  --tp-font-size-2: 12px;  /* secondary: table cell, meta, sub line, help text */
  --tp-font-size-3: 14px;  /* base: body copy, form, card/section title */
  --tp-font-size-4: 18px;  /* large title / metric */
  --tp-font-size-5: 28px;  /* display: the hero metric, and nothing else */

  /* Role alias: tune the KPI size here, not in the scale */
  --tp-font-size-metric:    var(--tp-font-size-4);
  --tp-font-size-metric-lg: var(--tp-font-size-5);

  --tp-line-none:  1;      /* icons, chips */
  --tp-line-tight: 1.2;    /* metrics, headings */
  --tp-line-snug:  1.4;    /* table cells, dense lists */
  --tp-line-base:  1.5;    /* body copy (= current base.css) */

  --tp-font-weight-normal:   400;
  --tp-font-weight-medium:   500;
  --tp-font-weight-semibold: 600;
  --tp-font-weight-bold:     700;

  --tp-font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Radius — one ramp. V2 carried 3/4/5/6/7/8/9/10/12px next to each other;
     D-00T §8 A5 parked this as its own candidate, D-003 is that candidate.
     Named by role, so a component picks a rung instead of a number. */
  --tp-radius-xs:    3px;  /* chip, code, trend pill, share track edge */
  --tp-radius-sm:    5px;  /* control: button, input, select, segment */
  --tp-radius-md:    8px;  /* surface: card, tile, banner, table frame */
  --tp-radius-lg:   12px;  /* overlay: drawer, picker panel */
  --tp-radius-pill: 999px;

  /* Density — padding, gaps and control heights only.
     Never font size, never line height (A3). Default = compact. */
  --tp-density-pad-y:     var(--tp-space-3);  /* 12px — card/tile/panel */
  --tp-density-pad-x:     var(--tp-space-3);  /* 12px */
  --tp-density-cell-y:    var(--tp-space-2);  /*  8px — table row */
  --tp-density-cell-x:    var(--tp-space-3);  /* 12px */
  --tp-density-gap:       var(--tp-space-3);  /* 12px — grid/stack */
  --tp-density-control-h: 28px;               /* button/input/select (7×4) */
  --tp-density-section-gap: var(--tp-space-5); /* 20px — between page sections */
}

/* Opt-in roomier density, e.g. <body data-density="comfortable"> on form
   and detail pages. Variants (-lg/-sm) use the scale directly, not this set. */
[data-density="comfortable"] {
  --tp-density-pad-y:     var(--tp-space-4);  /* 16px */
  --tp-density-pad-x:     var(--tp-space-4);
  --tp-density-cell-y:    var(--tp-space-3);  /* 12px */
  --tp-density-cell-x:    var(--tp-space-4);  /* 16px */
  --tp-density-gap:       var(--tp-space-4);
  --tp-density-control-h: 32px;               /* 8×4 */
  --tp-density-section-gap: var(--tp-space-8); /* 32px */
}

/* ===========================
   Light Theme (Default)
   =========================== */
:root,
[data-theme="light"] {
  /* Brand Colors.
     --tp-primary is the *fill* role: a surface that carries white glyphs
     (filled button, share bar, avatar). It must stay dark in both themes.
     --tp-accent is the *on-surface* role: text, icon, border, underline.
     Splitting them is the whole point — see the dark block. */
  --tp-primary: #1d4b8e;
  --tp-primary-hover: #174080;
  --tp-accent: #1d4b8e;        /* 8.6:1 on #fff — unchanged in light */
  --tp-accent-hover: #174080;
  --tp-secondary: #6d7175;
  --tp-dark: #1a1a1a;

  /* Layout Colors. The page/rail greys sit on one cool neutral ramp with
     --tp-table-border (#e5e7eb) and --tp-hover-bg (#f3f4f6); the old
     #efefef/#e8e8e8/#e1e1e1 were a second, warmer ramp, and secondary text
     on #efefef measured 4.28:1. */
  --tp-nav-bg: #1a1a1a;
  --tp-sidebar-bg: #eceef2;
  --tp-content-bg: #f4f5f7;
  --tp-surface: #ffffff;
  --tp-border: #e3e6ea;
  --tp-hover-bg: #f3f4f6;

  /* Text Colors */
  --tp-text: #202223;
  --tp-text-secondary: #61666b;
  --tp-highlight: #e3f2fd;

  /* Form Colors */
  --tp-input-bg: #ffffff;
  --tp-input-border: #d1d5db;
  --tp-focus-ring: rgba(29, 75, 142, 0.2);

  /* Status Colors */
  --tp-success: #18cf72;
  --tp-warning: #f59e0b;
  --tp-danger: #ef4444;
  --tp-info: #3b82f6;

  /* Badge Colors */
  --tp-badge-success-bg: #dcfce7;
  --tp-badge-success-text: #166534;
  --tp-badge-warning-bg: #fef3c7;
  --tp-badge-warning-text: #92400e;
  --tp-badge-danger-bg: #fee2e2;
  --tp-badge-danger-text: #991b1b;
  --tp-badge-info-bg: #dbeafe;
  --tp-badge-info-text: #1e40af;
  --tp-badge-gray-bg: #f3f4f6;
  --tp-badge-gray-text: #374151;
  /* Purple role (D-002c): `Closed`/`Merged` and the api-proxy detail pages
     referenced --tp-badge-purple-* long before it existed — those elements
     rendered transparent. Text measures 7.39:1 on its own tint. */
  --tp-badge-purple-bg: #f3e8ff;
  --tp-badge-purple-text: #6b21a8;

  /* Status Pill Fills (D-002c)
     Solid markers for primary entity state, louder than a table badge on
     purpose. Light mode: dark fill, --tp-status-fg glyph (4.59–6.16:1). */
  --tp-status-fg: #ffffff;
  --tp-status-open: #238636;
  --tp-status-closed: #8957e5;
  --tp-status-merged: #8957e5;
  --tp-status-draft: #6e7681;
  --tp-status-progress: #1f6feb;
  --tp-status-resolved: #9a4c07;

  /* Prose / Markdown Colors */
  --tp-prose-text: #1f2328;
  --tp-prose-heading: #1f2328;
  --tp-prose-link: #0969da;
  --tp-prose-code-bg: rgba(175, 184, 193, 0.2);
  --tp-prose-pre-bg: #f6f8fa;
  --tp-prose-border: #d1d9e0;
  --tp-prose-muted: #59636e;
  --tp-prose-table-bg: #ffffff;
  --tp-prose-table-alt-bg: #f6f8fa;
  --tp-prose-mention-bg: #ddf4ff;

  /* Table Colors. Zebra is its own tone: reusing --tp-table-header-bg made
     the head row indistinguishable from every second body row. */
  --tp-table-header-bg: #f6f8fa;
  --tp-table-row-alt: #fafbfc;
  --tp-table-row-hover: #f1f4f8;
  --tp-table-border: #e5e7eb;
  --tp-table-border-light: #f3f4f6;

  /* Card/Surface */
  --tp-card-shadow: rgba(0, 0, 0, 0.1);
  --tp-card-border: rgba(209, 213, 219, 0.3);

  /* Elevation — exactly two rungs (plan 0010 lever 5).
     -1 = resting surface (card, tile, sticky bar). -2 = overlay that floats
     above the page (drawer, picker panel, dialog). Nothing else casts a
     shadow. */
  --tp-elev-1: 0 1px 2px rgba(16, 24, 40, 0.05), 0 1px 3px rgba(16, 24, 40, 0.04);
  --tp-elev-2: 0 12px 32px rgba(16, 24, 40, 0.14), 0 2px 6px rgba(16, 24, 40, 0.06);

  /* HTMX Loading */
  --tp-loading-overlay: rgba(255, 255, 255, 0.6);

  /* Numerics & Delta — aliases, no new colors.
     Named by judgement, not by sign: a falling return rate is `good`.
     Color carries the delta only; the value itself stays --tp-text. */
  --tp-numeric: tabular-nums;
  --tp-num-unit:         var(--tp-text-secondary);
  --tp-delta-good:       var(--tp-badge-success-text);
  --tp-delta-bad:        var(--tp-badge-danger-text);
  /* the neutral delta sits on --tp-delta-neutral-bg, so it takes the text of
     that same badge family — plain secondary text measured 4.50:1 on it */
  --tp-delta-neutral:    var(--tp-badge-gray-text);
  --tp-delta-good-bg:    var(--tp-badge-success-bg);
  --tp-delta-bad-bg:     var(--tp-badge-danger-bg);
  --tp-delta-neutral-bg: var(--tp-badge-gray-bg);
}

/* ===========================
   Dark Theme — CockpitHub-Grau (Tailwind gray-800/700, Plan 0116)
   =========================== */
[data-theme="dark"] {
  /* Brand Colors — the fill role keeps the TP blue (white glyphs on it read
     at 8.7:1). The on-surface role does not: #1d4b8e on #161b22 measures
     2.02:1, which is why links, active tabs, sort arrows and the spinner
     were barely visible in dark mode. --tp-accent is the same brand hue
     (212°) lifted to the value --tp-prose-link has used all along. */
  --tp-primary: #1d4b8e;
  --tp-primary-hover: #2563eb;
  --tp-accent: #60a5fa;        /* blue-400 wie CockpitHub-Links: 5.2:1 auf #273142, 5.8:1 auf #1f2937 */
  --tp-accent-hover: #93c5fd;
  --tp-secondary: #9ca3af;
  --tp-dark: #111827;

  /* Layout Colors — Seite gray-800, Karte eine Stufe heller */
  --tp-nav-bg: #111827;
  --tp-sidebar-bg: #1b2330;
  --tp-content-bg: #1f2937;
  --tp-surface: #273142;
  --tp-border: #374151;
  --tp-hover-bg: #323d4f;

  /* Text Colors */
  --tp-text: #f3f4f6;
  --tp-text-secondary: #a3acb9;
  --tp-highlight: #1e3a5f;

  /* Form Colors */
  --tp-input-bg: #1f2937;
  --tp-input-border: #4b5563;
  --tp-focus-ring: rgba(88, 166, 255, 0.4);

  /* Status Colors - keep our green */
  --tp-success: #18cf72;
  --tp-warning: #d29922;
  --tp-danger: #f85149;
  --tp-info: #58a6ff;

  /* Badge Colors */
  --tp-badge-success-bg: rgba(24, 207, 114, 0.15);
  --tp-badge-success-text: #3fb950;
  --tp-badge-warning-bg: rgba(210, 153, 34, 0.15);
  --tp-badge-warning-text: #d29922;
  --tp-badge-danger-bg: rgba(248, 81, 73, 0.15);
  --tp-badge-danger-text: #ff7b72;  /* #f85149 measured 4.35:1 on its own tint */
  --tp-badge-info-bg: rgba(88, 166, 255, 0.15);
  --tp-badge-info-text: #58a6ff;
  --tp-badge-gray-bg: rgba(139, 148, 158, 0.15);
  --tp-badge-gray-text: #b4bcc8;  /* auf der Tönung über #273142 ≥ 4.5:1 */
  --tp-badge-purple-bg: rgba(163, 113, 247, 0.15);
  --tp-badge-purple-text: #d2a8ff;  /* 7.26:1 on its own tint */

  /* Status Pill Fills — pair inverted for dark (D-002a banner medallion):
     the bright tone carries the fill, the page surface carries the glyph.
     The light-mode fills sat at 2.81–3.77:1 against #0d1117, so the pill
     stopped reading as a shape. Measured 5.29–7.03:1 (Glyph gray-900). */
  --tp-status-fg: #111827;
  --tp-status-open: #3fb950;
  --tp-status-closed: #a371f7;
  --tp-status-merged: #a371f7;
  --tp-status-draft: #8b949e;
  --tp-status-progress: #58a6ff;
  --tp-status-resolved: #d29922;

  /* Prose / Markdown Colors */
  --tp-prose-text: #f3f4f6;
  --tp-prose-heading: #f3f4f6;
  --tp-prose-link: #60a5fa;
  --tp-prose-code-bg: rgba(110, 118, 129, 0.4);
  --tp-prose-pre-bg: #273142;
  --tp-prose-border: #374151;
  --tp-prose-muted: #a3acb9;
  --tp-prose-table-bg: #1f2937;
  --tp-prose-table-alt-bg: #273142;
  --tp-prose-mention-bg: rgba(56, 139, 253, 0.15);

  /* Table Colors */
  --tp-table-header-bg: #2c3748;
  --tp-table-row-alt: #2a3445;
  --tp-table-row-hover: #323d4f;
  --tp-table-border: #374151;
  --tp-table-border-light: #2f3a4b;

  /* Card/Surface */
  --tp-card-shadow: rgba(0, 0, 0, 0.35);
  --tp-card-border: #374151;

  /* Elevation. On a dark page a drop shadow adds nothing — resting height is
     already carried by the surface being lighter than the page (#273142 on
     #1f2937). So rung 1 is honestly `none`; only the overlay lifts. */
  --tp-elev-1: none;
  --tp-elev-2: 0 12px 32px rgba(3, 7, 18, 0.55), 0 0 0 1px rgba(255, 255, 255, 0.06);

  /* HTMX Loading */
  --tp-loading-overlay: rgba(17, 24, 39, 0.6);

  /* Numerics & Delta — re-aliased so dark mode does not freeze on the
     light values when [data-theme] sits below :root. */
  --tp-numeric: tabular-nums;
  --tp-num-unit:         var(--tp-text-secondary);
  --tp-delta-good:       var(--tp-badge-success-text);
  --tp-delta-bad:        var(--tp-badge-danger-text);
  /* the neutral delta sits on --tp-delta-neutral-bg, so it takes the text of
     that same badge family — plain secondary text measured 4.50:1 on it */
  --tp-delta-neutral:    var(--tp-badge-gray-text);
  --tp-delta-good-bg:    var(--tp-badge-success-bg);
  --tp-delta-bad-bg:     var(--tp-badge-danger-bg);
  --tp-delta-neutral-bg: var(--tp-badge-gray-bg);
}

/* ===========================================================================
   V2-Aliase (Plan 0065, Befund R-3)
   ===========================================================================
   Die V2-Bausteine schreiben `var(--tpv2-surface, #fff)`. Diese Namen gab es
   nie — jede dieser Stellen fiel auf ihren hellen Rückfallwert zurück, und
   zwar in JEDEM Thema. Sichtbar war es dort, wo der Rückfallwert eine Fläche
   ist: das Fenster einer Tat stand weiß auf der dunklen Seite.

   Sie stehen als Verweise und nicht als Farben, und in allen drei Selektoren:
   ein Verweis wird auf dem Element aufgelöst, das ihn deklariert — stünde er
   nur auf `:root`, fröre er auf den hellen Werten ein, sobald ein
   `[data-theme]` unterhalb der Wurzel sitzt. Aus demselben Grund stehen die
   Numerics weiter oben in beiden Themenblöcken. */
:root,
[data-theme='light'],
[data-theme='dark'] {
  --tpv2-surface: var(--tp-surface);
  --tpv2-surface-muted: var(--tp-hover-bg);
  --tpv2-border: var(--tp-border);
  --tpv2-text: var(--tp-text);
  --tpv2-text-muted: var(--tp-text-secondary);
  /* Derselbe Befund, anderer Name: sechs Stellen schreiben
     `--tp-text-primary`, definiert war es nirgends. Ohne Rückfallwert ist
     eine solche Farbe ungültig und der Text erbt — meistens richtig, und
     genau deshalb fiel es nie auf. */
  --tp-text-primary: var(--tp-text);
}
/**
 * TP Design System - Base Styles
 * Reset and typography
 */

*, *::before, *::after {
  box-sizing: border-box;
}

html, body {
  height: 100%;
  margin: 0;
  padding: 0;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--tp-text);
  background-color: var(--tp-content-bg);
}

a {
  color: var(--tp-accent);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

/* Alpine.js cloak - hide elements until Alpine loads */
[x-cloak] { display: none !important; }

/* Utility classes */
.tp-text-success-v1 { color: var(--tp-success); }
.tp-text-warning-v1 { color: var(--tp-warning); }
.tp-text-danger-v1 { color: var(--tp-danger); }
.tp-text-secondary-v1 { color: var(--tp-text-secondary); }

.tp-mb-1-v1 { margin-bottom: var(--tp-space-1); }
.tp-mb-2-v1 { margin-bottom: var(--tp-space-2); }
.tp-mb-3-v1 { margin-bottom: var(--tp-space-3); }
.tp-mb-4-v1 { margin-bottom: var(--tp-space-4); }
.tp-mb-5-v1 { margin-bottom: var(--tp-space-5); }
.tp-mb-6-v1 { margin-bottom: var(--tp-space-6); }
/**
 * TP V2 — Accordion
 *
 * Eine Reihe gleichartiger Sachverhalte, von denen jeder auf Verlangen seine
 * Tiefe zeigt: die Sendungen eines Vorgangs, die Belege einer Akte
 * (Plan 0057, E10 §2 und E11).
 *
 * **Nativ `<details>`, kein Alpine.** Der Inhalt steht schon in der Antwort,
 * das Aufklappen kostet also keinen zweiten Abruf; ohne JavaScript bleibt er
 * erreichbar, die Tastatur- und Screenreader-Semantik kommt vom Browser, und
 * die Suche im Text findet auch, was gerade zu ist. Genau das meint die
 * Doktrin oben in `v2/main.css` mit „native <details> accordions".
 *
 * **Der Anker sitzt am `<details>`, nicht im Körper.** Eine Sendung ist ein
 * Abschnitt ihres Vorgangs, keine eigene Seite (Plan 0057, E3):
 * `…/orders/{gid}#f-904418` landet auf der **Zeile**, und die trägt im
 * geschlossenen Zustand bereits Nummer, Dienstleister, Datum und Zustand.
 * `:target` hebt sie hervor, damit sichtbar ist, welche gemeint war. Ein Ziel
 * im Körper wäre in einer geschlossenen Zeile ohne CSS-Mittel unerreichbar.
 *
 * Zwei Gestalten, und die Seite wählt eine:
 *
 *   .tpv2-acc            — eine Karte, die sich öffnet: die Belegakte, in der
 *                          jeder Beleg für sich steht (E11).
 *   .tpv2-acc-list       — Zeilen in EINER Karte: die Sendungsliste, die man
 *                          herunterliest (E10 §2). Umschließt die `<details>`.
 *
 * Verwendung:
 *   <div class="tpv2-acc-list">
 *     <details class="tpv2-acc" id="f-904418" open>
 *       <summary class="tpv2-acc-h">
 *         <i class="fas fa-chevron-right tpv2-acc-chev" aria-hidden="true"></i>
 *         <span class="tpv2-glyph tpv2-glyph-success" aria-hidden="true">
 *           <i class="fas fa-truck-fast"></i>
 *         </span>
 *         <span class="tpv2-acc-main">
 *           <span class="tpv2-acc-title mono">00340434161094876543</span>
 *           <span class="tpv2-acc-meta">DHL Paket · 04.09.2026 · 2 Stück</span>
 *         </span>
 *         <span class="tpv2-acc-aside">
 *           <span class="tpv2-pill tpv2-pill-success tpv2-pill-sm">Versendet</span>
 *         </span>
 *       </summary>
 *       <div class="tpv2-acc-b">…</div>
 *     </details>
 *   </div>
 */

.tpv2-acc {
  background-color: var(--tp-surface, #fff);
  border: 1px solid var(--tp-border, #e1e1e1);
  border-radius: var(--tp-radius-md);
  overflow: hidden;
  font-family: inherit;
  min-width: 0;
}

/* ===========================
   Der Kopf — die Aussage der Zeile
   =========================== */
.tpv2-acc-h {
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: var(--tp-space-2) var(--tp-space-3);
  cursor: pointer;
  list-style: none;
  user-select: none;
  min-width: 0;
}

.tpv2-acc-h::-webkit-details-marker { display: none; }
.tpv2-acc-h::marker { content: ''; }

.tpv2-acc-h:hover {
  background-color: var(--tp-hover-bg, #f3f4f6);
}

/* Der Fokusring gehört auf die Zeile, nicht auf einen unsichtbaren Marker:
   `<summary>` ist der Schalter, und ohne sichtbaren Fokus ist eine Liste aus
   zwanzig Belegen mit der Tastatur nicht benutzbar. */
.tpv2-acc-h:focus-visible {
  outline: 2px solid var(--tp-accent, #1d4b8e);
  outline-offset: -2px;
}

.tpv2-acc-chev {
  margin-top: 5px;
  color: var(--tp-text-secondary, #6d7175);
  font-size: var(--tp-font-size-1);
  width: var(--tp-space-3);
  flex: none;
  transition: transform 0.15s;
}

.tpv2-acc[open] > .tpv2-acc-h .tpv2-acc-chev {
  transform: rotate(90deg);
  color: var(--tp-accent, #1d4b8e);
}

@media (prefers-reduced-motion: reduce) {
  .tpv2-acc-chev { transition: none; }
}

/* Titel und Qualifizierung stehen untereinander: eine Sendungsnummer ist
   zwanzig Zeichen lang, und daneben passt in einem 390-px-Rahmen nichts mehr.
   `flex-basis` statt `width`, damit der Block ab etwa 12 rem die freie Breite
   nimmt und darunter die Pille unter sich wandern lässt. */
.tpv2-acc-main {
  flex: 1 1 12rem;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--tp-space-0-5);
}

.tpv2-acc-title {
  font-size: var(--tp-font-size-3);
  font-weight: var(--tp-font-weight-semibold);
  line-height: var(--tp-line-tight);
  color: var(--tp-text, #202223);
  overflow-wrap: anywhere;
}

.tpv2-acc-title.mono {
  font-family: var(--tp-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  letter-spacing: -0.01em;
}

/* Was den Sachverhalt qualifiziert: Dienstleister, Datum, Stückzahl, Quelle.
   Kurz — sonst steht hier ein Satz und die Pille daneben ist nicht mehr die
   Aussage (Plan 0057, E7 §2). */
.tpv2-acc-meta {
  font-size: var(--tp-font-size-1);
  line-height: var(--tp-line-snug);
  color: var(--tp-text-secondary, #6d7175);
  overflow-wrap: anywhere;
}

.tpv2-acc-meta .mono {
  font-family: var(--tp-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
}

.tpv2-acc-aside {
  margin-left: auto;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--tp-space-2);
  min-width: 0;
}

/* ===========================
   Der Körper — worin die Tiefe steckt
   =========================== */
/* Heller Grund, damit die Kästen darin weiß sein können und eine eigene Kante
   haben (Plan 0057, E12 §3). „Gehört dazu" und „ist ein Ding darin" lassen
   sich mit einem zweiten Grauwert allein nicht unterscheiden. */
.tpv2-acc-b {
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
  background-color: var(--tp-content-bg, #f4f5f7);
  border-top: 1px solid var(--tp-border, #e1e1e1);
  display: grid;
  gap: var(--tp-density-gap);
  min-width: 0;
}

/* ===========================
   Gestalt 2 — Zeilen in EINER Karte
   =========================== */
/* Die Sendungsliste ist eine Liste, keine Kartenwand (Plan 0057, E7 §5): die
   Zeilen verlieren Rahmen und Rundung und trennen sich durch eine Haarlinie,
   so wie die Zeilen einer Tabelle. Die Karte darum ist `tpv2-card-flush`. */
.tpv2-acc-list {
  min-width: 0;
}

.tpv2-acc-list > .tpv2-acc {
  border: none;
  border-radius: 0;
  box-shadow: none;
  background-color: transparent;
}

.tpv2-acc-list > .tpv2-acc + .tpv2-acc {
  border-top: 1px solid var(--tp-border, #e1e1e1);
}

.tpv2-acc-list > .tpv2-acc > .tpv2-acc-h {
  padding: var(--tp-space-3) var(--tp-density-pad-x);
}

/* Eine Zeile ist keine Überschrift: in der Liste steht der Titel im
   Fließtextgrad und nur das Gewicht trägt ihn. */
.tpv2-acc-list .tpv2-acc-title {
  font-size: var(--tp-font-size-2);
}

/* ===========================
   Die angesprungene Zeile
   =========================== */
/* Aus der Nachtrag-Warteliste kommt `#f-904418` (E3). Ohne Marke landet der
   Leser irgendwo in einer Liste aus fünf gleich aussehenden Zeilen. */
.tpv2-acc:target > .tpv2-acc-h {
  background-color: var(--tp-badge-info-bg, #dbeafe);
  box-shadow: inset 3px 0 0 var(--tp-accent, #1d4b8e);
}

/* ===========================
   Gestalt 1 — jede Zeile eine Karte
   =========================== */
/* Die Belegakte: ein Beleg steht für sich, mit eigener Kante und eigenem
   Abstand. Die Hülle trägt nur den Größen-Rahmen — die Zeilen darin sind
   schon Karten (siehe oben) und brauchen keine zweite Fläche. */
.tpv2-acc-cards {
  min-width: 0;
}
/**
 * TP V2 — App Shell (ConsoleHub `/apps/*`)
 *
 * The frame around a composed page: fixed topbar, service bar, the global
 * navigation rail, and the canvas the page layout (`layout.css`) drops into.
 * Spec 177 §3.5; plan 0010 D-005 moved it here out of a 300-line inline style
 * block in `consolehub-layout`.
 *
 * Structure:
 *
 *   <body class="tpv2-app">
 *     <div class="tpv2-app-topbar">…</div>          fixed, filled by HTMX
 *     <div class="tp-service-bar">…</div>           app identity + tabs
 *     <div class="tpv2-app-scrim"></div>            mobile drawer backdrop
 *     <div class="tpv2-app-shell">
 *       <aside class="tpv2-app-rail">…</aside>      global navigation
 *       <div class="tpv2-app-canvas">
 *         <div class="tpv2-layout-body tpv2-layout-body-…">   ← layout.css
 *           <main class="tpv2-layout-main">
 *             <div class="tpv2-page-h">…</div>
 *             <div class="tpv2-app-content">…cells…</div>
 *           </main>
 *         </div>
 *       </div>
 *     </div>
 *   </body>
 *
 * The two rhythms this file owns:
 *   • chrome heights (`--tpv2-app-topbar-h`, `--tpv2-app-bar-h`) — one place,
 *     because body padding, rail offset and `min-height` all depend on them;
 *   • the cell grid: a slot is 12 columns and a cell declares how much of the
 *     row it takes (`.tpv2-cell-half|-third|-quarter`, default full). The
 *     breakpoints are `@container`, not `@media` — a cell in a 280px rail must
 *     stack even when the window is 1440px wide.
 */

:root {
  --tpv2-app-topbar-h: 3.5rem;
  --tpv2-app-bar-h: 3rem;
  --tpv2-app-chrome-h: calc(var(--tpv2-app-topbar-h) + var(--tpv2-app-bar-h));
  --tpv2-app-rail-w: 220px;
  --tpv2-app-max-w: 1820px;
}

/* ===========================
   Body + fixed chrome
   =========================== */
.tpv2-app {
  margin: 0;
  padding-top: var(--tpv2-app-chrome-h);
  background-color: var(--tp-content-bg);
}

.tpv2-app-topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--tpv2-app-topbar-h);
  background-color: var(--tp-nav-bg);
  z-index: 50;
}

/* ===========================
   Shell: global rail + canvas
   =========================== */
.tpv2-app-shell {
  display: grid;
  grid-template-columns: var(--tpv2-app-rail-w) minmax(0, 1fr);
  min-height: calc(100vh - var(--tpv2-app-chrome-h));
  max-width: var(--tpv2-app-max-w);
  margin-inline: auto;
}

/* No global rail configured — the canvas takes the full width. */
.tpv2-app-shell-flat {
  grid-template-columns: minmax(0, 1fr);
}

.tpv2-app-canvas {
  min-width: 0;
}

/* ===========================
   Global navigation rail
   =========================== */
.tpv2-app-rail {
  background-color: var(--tp-sidebar-bg);
  border-right: 1px solid var(--tp-border);
  padding: var(--tp-space-4) var(--tp-space-3);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--tp-space-4);
}

.tpv2-app-nav-group {
  display: flex;
  flex-direction: column;
  gap: var(--tp-space-0-5);
}

/* A group without a title continues the one above it (the operations links
   under the operations apps) — it must not read as a new block. */
.tpv2-app-nav-group:not(:has(.tpv2-app-nav-title)) {
  margin-top: calc(-1 * var(--tp-space-3));
}

.tpv2-app-nav-title {
  font-size: var(--tp-font-size-1);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: var(--tp-font-weight-semibold);
  color: var(--tp-text-secondary);
  padding: var(--tp-space-1) var(--tp-space-2) var(--tp-space-1-5);
}

.tpv2-app-nav-items {
  display: flex;
  flex-direction: column;
  gap: var(--tp-space-0-5);
}

.tpv2-app-nav-item,
.tpv2-app-nav-sub-item {
  display: flex;
  align-items: center;
  gap: var(--tp-space-2);
  padding: var(--tp-space-1) var(--tp-space-2);
  border-radius: var(--tp-radius-sm);
  text-decoration: none;
  line-height: var(--tp-line-snug);
}

.tpv2-app-nav-item {
  color: var(--tp-text);
  font-size: var(--tp-font-size-3);
}

.tpv2-app-nav-sub-item {
  color: var(--tp-text-secondary);
  font-size: var(--tp-font-size-2);
}

.tpv2-app-nav-item:hover,
.tpv2-app-nav-sub-item:hover {
  background-color: var(--tp-hover-bg);
  color: var(--tp-text);
}

.tpv2-app-nav-item.is-active,
.tpv2-app-nav-sub-item.is-active {
  background-color: color-mix(in srgb, var(--tp-accent) 12%, transparent);
  color: var(--tp-accent);
  font-weight: var(--tp-font-weight-medium);
}

.tpv2-app-nav-icon {
  width: 1rem;
  flex: 0 0 1rem;
  text-align: center;
  font-size: var(--tp-font-size-2);
  color: var(--tp-text-secondary);
}

.tpv2-app-nav-item.is-active .tpv2-app-nav-icon,
.tpv2-app-nav-sub-item.is-active .tpv2-app-nav-icon {
  color: var(--tp-accent);
}

.tpv2-app-nav-label {
  min-width: 0;
}

.tpv2-app-nav-sub {
  display: flex;
  flex-direction: column;
  gap: var(--tp-space-0-5);
  padding-left: var(--tp-space-4);
  margin: var(--tp-space-0-5) 0;
}

.tpv2-app-nav-divider {
  border: 0;
  border-top: 1px solid var(--tp-border);
  margin: var(--tp-space-1-5) 0;
}

.tpv2-app-nav-subheader {
  font-size: var(--tp-font-size-1);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: var(--tp-font-weight-semibold);
  color: var(--tp-text-secondary);
  padding: var(--tp-space-2) var(--tp-space-2) var(--tp-space-1);
}

/* Items the viewer may see but not open (spec 177 §6.4): dimmed, with a lock
   marker, click leads to the access request. Hiding them would hide that a
   permission exists at all. */
.tpv2-app-nav-item.is-locked,
.tpv2-app-nav-sub-item.is-locked {
  opacity: 0.55;
}

.tpv2-app-nav-item.is-locked:hover,
.tpv2-app-nav-sub-item.is-locked:hover {
  opacity: 0.8;
}

.tpv2-app-nav-marker {
  margin-left: auto;
  font-size: var(--tp-font-size-1);
  color: var(--tp-text-secondary);
}

/* ===========================
   Cell area
   =========================== */
/* The slot that holds cells. It is the query container for the spans below —
   a cell asks its slot how wide it is, not the window. Gutter and page frame
   come from `.tpv2-layout-main` / `.tpv2-layout-rail-*` (layout.css); this
   class only distributes.
   Twin: `.tpv2-dash-grid` is the same rhythm for a hand-written page; this one
   is the YAML-composed page, where the span comes from a cell declaration. */
.tpv2-app-content {
  container-type: inline-size;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--tp-density-section-gap);
  align-content: start;
}

/* Grid items default to min-width:auto — one wide table would stretch the
   track and push the page sideways. */
.tpv2-app-content > * {
  min-width: 0;
  grid-column: span 12;
}

.tpv2-cell-full { grid-column: span 12; }
.tpv2-cell-half { grid-column: span 6; }
.tpv2-cell-third { grid-column: span 4; }
.tpv2-cell-quarter { grid-column: span 3; }

/* Narrow slot (rail, split view, tablet): thirds and quarters pair up. */
@container (max-width: 64rem) {
  .tpv2-cell-third,
  .tpv2-cell-quarter {
    grid-column: span 6;
  }
}

/* Phone-width slot: one cell per row. */
@container (max-width: 40rem) {
  .tpv2-cell-half,
  .tpv2-cell-third,
  .tpv2-cell-quarter {
    grid-column: span 12;
  }
}

/* A rail holds cells too, at rail width — same grid, tighter rhythm. */
.tpv2-layout-rail-left > .tpv2-app-content,
.tpv2-layout-rail-right > .tpv2-app-content {
  gap: var(--tp-density-gap);
}

/* ===========================
   Full bleed — one embedded surface fills the canvas
   (Metabase/Grafana dashboards: no page header, no gutter)
   =========================== */
.tpv2-app-content-flush {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  padding: 0;
  gap: 0;
}

.tpv2-app-content-flush > [data-fragment] {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

.tpv2-app-content-flush > [data-fragment] > * {
  flex: 1;
  min-height: 0;
}

/* ===========================
   Mobile: the global rail becomes a drawer
   This one is a window decision, not a container one — the drawer covers the
   viewport, so it has to know the viewport.
   =========================== */
.tpv2-app-drawer-trigger {
  display: none;
  align-items: center;
  justify-content: center;
  width: var(--tp-density-control-h);
  height: var(--tp-density-control-h);
  margin-right: var(--tp-space-1);
  border: 0;
  background: transparent;
  color: var(--tp-text);
  cursor: pointer;
}

.tpv2-app-scrim {
  display: none;
  position: fixed;
  inset: var(--tpv2-app-chrome-h) 0 0 0;
  background: rgba(0, 0, 0, 0.4);
  z-index: 55;
}

.tpv2-app-scrim.is-open {
  display: block;
}

@media (max-width: 1023px) {
  .tpv2-app-shell {
    grid-template-columns: minmax(0, 1fr);
  }

  .tpv2-app-rail {
    position: fixed;
    top: var(--tpv2-app-topbar-h);
    bottom: 0;
    left: 0;
    width: min(280px, 86vw);
    z-index: 60;
    transform: translateX(-100%);
    transition: transform 0.25s ease;
    box-shadow: var(--tp-elev-2);
  }

  .tpv2-app-rail.is-open {
    transform: translateX(0);
  }

  .tpv2-app-drawer-trigger {
    display: inline-flex;
  }
}

@media (prefers-reduced-motion: reduce) {
  .tpv2-app-rail {
    transition: none;
  }
}

@media (max-width: 767px) {
  :root {
    --tpv2-app-pad: var(--tp-space-3);
  }

  .tpv2-app-head {
    padding-top: var(--tp-space-4);
  }

  /* The app tab row scrolls sideways instead of wrapping into two lines. */
  .tpv2-app-tabs {
    overflow-x: auto;
    scrollbar-width: none;
  }

  .tpv2-app-tabs::-webkit-scrollbar {
    display: none;
  }
}
/* ===========================
   ConsoleHub layout engine (Plan 0010, D-005 Teil B)

   Diese Regeln standen als ~280-Zeilen-String in
   crates/consolehub-layout/src/render/mod.rs und bildeten damit ein drittes
   Design-Vokabular neben @tp/ui und shopcloud-ui — eines, das gewann, weil es
   zuletzt im Dokument stand. Sie liegen jetzt im v2-Bundle, das die Seite
   ohnehin lädt, und die Engine ordnet nur noch an.
   =========================== */

/* Theme-aware Tokens — Sidebar/Rail teilen Body-Farbe (Notion-Look) */
    :root, [data-theme="light"] {
      --apps-stripe-bg: rgba(0,0,0,.025);
      --apps-progress-fill: #374151;
      --apps-rail-bg: var(--tp-content-bg, #efefef);
    }
    [data-theme="dark"] {
      --apps-stripe-bg: rgba(255,255,255,.04);
      --apps-progress-fill: rgba(255,255,255,.55);
      --apps-rail-bg: var(--tp-content-bg, #1a1a1a);
    }

    /* Globale Apps-Shell-Sidebar (Spec 177 Phase 1). Wenn sidebar.yaml
       Sections enthält, wickelt sich das Page-Grid in dieses 2-Spalten-Grid. */
    .apps-shell-grid {
      display: grid;
      grid-template-columns: 220px minmax(0, 1fr);
      min-height: calc(100vh - 6.5rem);
      max-width: 1820px;
      margin: 0 auto;
    }
    .apps-shell-sidebar {
      background: var(--apps-rail-bg);
      border-right: 1px solid var(--tp-border, #e1e1e1);
      padding: 1rem .75rem;
      overflow-y: auto;
      font-size: .8125rem;
      display: flex;
      flex-direction: column;
      gap: 1rem;
    }
    .apps-sidebar-section { display: flex; flex-direction: column; gap: .125rem; }
    /* Titellose Section continuiert die vorherige (z.B. Betrieb-Links unter der
       Betrieb-App-Liste) — neutralisiert den 1rem-Sektions-Gap, kein doppelter
       Leerraum. Spec 183 §A3. */
    .apps-sidebar-section:not(:has(.apps-sidebar-section-title)) { margin-top: -.875rem; }
    .apps-sidebar-section-title {
      font-size: .6875rem; text-transform: uppercase; letter-spacing: .04em;
      font-weight: 600; color: var(--tp-text-secondary, #6d7175);
      padding: .25rem .5rem .375rem;
    }
    .apps-sidebar-items { display: flex; flex-direction: column; gap: .125rem; }
    .apps-sidebar-item {
      display: flex; align-items: center; gap: .5rem;
      padding: .375rem .5rem; border-radius: .375rem;
      color: var(--tp-text, #202223); text-decoration: none;
      font-size: .8125rem;
    }
    .apps-sidebar-item:hover { background: rgba(0,0,0,.04); }
    .apps-sidebar-item.is-active {
      background: color-mix(in srgb, var(--tp-primary, #1d4b8e) 12%, transparent);
      color: var(--tp-primary, #1d4b8e); font-weight: 500;
    }
    .apps-sidebar-icon { font-size: .75rem; width: 1rem; text-align: center; color: var(--tp-text-secondary, #6d7175); }
    .apps-sidebar-item.is-active .apps-sidebar-icon { color: var(--tp-primary, #1d4b8e); }
    .apps-sidebar-icon-empty { display: inline-block; width: 1rem; }
    /* Seitenbaum der aktiven App (Plan 0085). Jede Ebene rückt um 1rem ein —
       bei Containern im Container summiert sich das von selbst. */
    .apps-sidebar-subtree { padding-left: 1rem; display: flex; flex-direction: column; gap: .0625rem; margin: .125rem 0; }
    /* Ein Baum-Eintrag ist ein `.apps-sidebar-item`, nur enger und leiser —
       Hover, Aktiv-Look und Sperre erbt er von dort. */
    .apps-sidebar-nav-item {
      padding: .25rem .5rem; border-radius: .25rem;
      color: var(--tp-text-secondary, #6d7175); font-size: .75rem;
    }
    /* Ein Container mit Kindern ist eine Überschrift, kein Ziel: gleiche
       Metrik, aber kein Klickangebot — also auch keine Hover-Fläche. */
    .apps-sidebar-nav-group { font-weight: 500; cursor: default; }
    .apps-sidebar-nav-group:hover { background: none; }
    .apps-sidebar-divider { border: 0; border-top: 1px solid var(--tp-border, #e1e1e1); margin: .375rem 0; }
    .apps-sidebar-subheader {
      font-size: .625rem; text-transform: uppercase; letter-spacing: .04em;
      font-weight: 600; color: var(--tp-text-secondary, #6d7175);
      padding: .5rem .5rem .25rem;
    }
    .apps-sidebar-skeleton { opacity: .4; }

    .apps-page-grid {
      display: grid;
      grid-template-columns: var(--apps-grid-cols);
      gap: 0;
      min-height: calc(100vh - 6.5rem);
      /* max-width + margin sitzen bewusst auf .apps-shell-grid; hier nicht
         setzen, sonst kollidiert margin:0 auto mit dem Grid-Item-Stretch
         der Shell und das page-grid schrumpft auf intrinsic-Breite. */
    }
    .apps-kpi-grid {
      display: grid;
      grid-template-columns: var(--apps-kpi-cols, 1fr);
      gap: var(--apps-kpi-gap, 1rem);
    }
    .apps-page-main { min-width:0; padding-bottom:2rem; }
    /* Full-Bleed: Page-Header + Padding entfallen, Cell-Fragment füllt den
       gesamten Bereich rechts neben der Sidebar (Topbar + Service-Bar bleiben). */
    .apps-page-main--fullbleed { padding-bottom:0; display:flex; flex-direction:column; min-height:calc(100vh - 6.5rem); }
    .apps-main-content--fullbleed { flex:1; min-height:0; }
    .apps-main-content--fullbleed > [data-fragment] {
      flex:1; min-height:0; display:flex; flex-direction:column;
    }
    .apps-main-content--fullbleed > [data-fragment] > * { flex:1; min-height:0; }

    /* Lock-Look für Apps/Seiten/Items ohne ausreichende User-Scopes (Spec 177
       §10 — Sidebar zeigt Items weiterhin an, gegraut + Lock-Badge + Klick
       führt zur 403-Page mit Access-Request-Form). */
    .apps-sidebar-item--locked {
      opacity: .55;
    }
    .apps-sidebar-item--locked:hover {
      opacity: .75;
      background: rgba(0,0,0,.04);
    }
    .apps-sidebar-lock-badge {
      font-size: .625rem;
      color: var(--tp-text-secondary, #6d7175);
      margin-left: auto;
      opacity: .8;
    }

    /* Analytics-Skeleton für Full-Bleed-Embeds (Metabase/Grafana). */
    @keyframes apps-skel-shimmer {
      0%   { background-position: -800px 0; }
      100% { background-position:  800px 0; }
    }
    .apps-skeleton-shimmer {
      background: linear-gradient(90deg, #e8e8ec 0%, #f3f4f6 50%, #e8e8ec 100%);
      background-size: 800px 100%;
      animation: apps-skel-shimmer 1.6s ease-in-out infinite;
      border-radius: .25rem;
    }
    .apps-skel-bar { display:block; }
    .apps-fullbleed-skel {
      display:flex; flex-direction:column; height:100%;
      background:var(--tp-content-bg,#fafafa);
      padding:1.25rem 1.5rem 1.5rem;
      gap:1rem; min-height:32rem;
    }
    .apps-skel-titlebar {
      display:flex; justify-content:space-between; align-items:center;
      padding-bottom:.5rem;
    }
    .apps-skel-kpis {
      display:grid; grid-template-columns:repeat(4,minmax(0,1fr));
      gap:1rem;
    }
    .apps-skel-tile {
      background:var(--tp-surface,#fff);
      border:1px solid var(--tp-border,#e1e1e1);
      border-radius:.5rem;
      padding:.875rem 1rem;
      min-height:5rem;
    }
    .apps-skel-chart {
      background:var(--tp-surface,#fff);
      border:1px solid var(--tp-border,#e1e1e1);
      border-radius:.5rem;
      padding:1rem 1.25rem;
      flex:1; min-height:14rem;
    }
    .apps-skel-table {
      background:var(--tp-surface,#fff);
      border:1px solid var(--tp-border,#e1e1e1);
      border-radius:.5rem;
      padding:.75rem 1.25rem 1rem;
    }
    @media (max-width: 767px) {
      .apps-skel-kpis { grid-template-columns:repeat(2,minmax(0,1fr)); }
    }
    /* Grid- und Flex-Items müssen `min-width:0` haben, sonst dehnen sie
       ihre min-content-Größe in den Container und verursachen Overflow. */
    .apps-main-content > * { min-width:0; }
    .apps-kpi-grid > * { min-width:0; }
    /* Der Seitenkopf der YAML-Seiten sitzt schon immer auf dem Seitengrund —
       er trug nur seine eigene Schriftskala (1.5rem/700) neben der von
       tpv2-page-h (18px/600). Eine Regel, eine Groesse (Plan 0010, D-009). */
    .apps-page-header {
      display:flex; align-items:flex-start; gap:var(--tp-density-gap);
      padding:var(--tp-density-section-gap) 1.5rem var(--tp-density-pad-y);
      background:var(--tp-content-bg, #efefef);
    }
    .apps-page-title {
      font-size:var(--tp-font-size-4); font-weight:var(--tp-font-weight-semibold);
      line-height:var(--tp-line-tight); margin:0; color:var(--tp-text,#202223);
    }
    /* `margin-left:auto` schiebt die Chips nach rechts — auch dann, wenn links
       kein Titelblock steht (Kopf ohne Titel). Vorher tat das ein leeres
       Platzhalter-Div, das nur für diesen Zweck existierte. */
    .apps-action-chips { display:flex; gap:.375rem; align-items:center; flex-wrap:wrap; margin-left:auto; }
    .apps-action-chips .chip-btn {
      display:inline-flex; align-items:center; gap:.375rem;
      background:var(--tp-surface,#fff); color:var(--tp-text,#202223);
      border:1px solid var(--tp-border,#e1e1e1);
      padding:.375rem .625rem; border-radius:.375rem;
      font:inherit; font-size:.75rem; font-weight:500; cursor:pointer; height:2rem;
      text-decoration:none;
    }
    .apps-action-chips .chip-primary {
      background:var(--tp-primary,#1d4b8e); color:#fff; border-color:var(--tp-primary,#1d4b8e);
    }
    /* D-018b (Revision 2): EIN Zeitraum-Element im Seitenkopf — der
       shopcloud-ui-DateRange-Picker (tpv2-date-range) in einem GET-Formular;
       die benannten Presets wohnen in der Panel-Spalte des Pickers. Das
       Formular trägt `tpv2-date-range--align-end`, die Panel-Ankerung kommt
       damit aus date-range.css. Hier steht nur, was die Chip-Optik dem
       Trigger über `chip-btn` hinaus noch abverlangt. */
    .apps-action-chips .chip-pop { display:inline-flex; margin:0; }
    .apps-action-chips .chip-pop .tpv2-date-range { width:auto; }
    .apps-action-chips .tpv2-date-range__trigger.chip-pop-summary {
      width:auto; min-height:0; font-variant-numeric:tabular-nums;
    }

    .apps-hamburger {
      display:none; background:transparent; border:0; color:var(--tp-text,#202223);
      width:2rem; height:2rem; align-items:center; justify-content:center; cursor:pointer;
      margin-right:.25rem; font-size:.9375rem;
    }
    .apps-drawer-scrim {
      display:none; position:fixed; inset:6.5rem 0 0 0;
      background:rgba(0,0,0,.4); z-index:55;
    }

    @media (max-width: 1279px) {
      .apps-page-grid--with-rail { grid-template-columns: 200px minmax(0,1fr) !important; }
      .apps-page-grid--with-rail .apps-page-rail {
        grid-column: 1 / -1; border-top: 1px solid var(--tp-border,#e1e1e1);
      }
    }
    @media (max-width: 1023px) {
      /* Überschreibt das inline `--apps-kpi-cols` damit das Page-Header-Grid
         auch dann shrinked, wenn die YAML explizit 3 Spalten verlangt. */
      .apps-kpi-grid { --apps-kpi-cols: repeat(2, minmax(0,1fr)) !important; }
      .apps-shell-grid { grid-template-columns: 1fr !important; }
      /* Sidebar wird Off-Canvas-Drawer: standardmäßig links versteckt,
         slidet via `.is-open` ein. Hamburger im AppBar toggelt. */
      .apps-shell-sidebar {
        position: fixed;
        top: 3.5rem;
        bottom: 0;
        left: 0;
        width: min(280px, 86vw);
        z-index: 60;
        transform: translateX(-100%);
        transition: transform .25s ease;
        box-shadow: 4px 0 20px rgba(0, 0, 0, .15);
      }
      .apps-shell-sidebar.is-open { transform: translateX(0); }
      /* Öffner und Scrim gehören in denselben Block wie der Drawer-Modus:
         standen sie erst ab 767px, war die Sidebar zwischen 768 und 1023px
         off-canvas geschoben und nichts holte sie zurück. */
      .apps-hamburger { display:inline-flex; }
      .apps-drawer-scrim.is-open { display:block; }
    }
    @media (max-width: 767px) {
      .apps-page-grid { grid-template-columns: 1fr !important; }
      .apps-page-sidebar { display:none; }
      .apps-page-sidebar.is-open {
        display:block; position:fixed; top:6.5rem; left:0; bottom:0;
        width:min(280px, 86vw); z-index:60;
        box-shadow:4px 0 20px rgba(0,0,0,.15); overflow-y:auto;
        background:var(--apps-rail-bg);
      }
      .apps-page-rail { grid-column: 1 / -1; border-top:1px solid var(--tp-border,#e1e1e1); }
      .apps-kpi-grid { --apps-kpi-cols: 1fr !important; }
      .apps-page-header { flex-direction:column !important; align-items:stretch !important; padding:1rem .75rem .5rem !important; }

      .apps-main-content { padding-left:.75rem !important; padding-right:.75rem !important; gap:.75rem !important; }
      /* Edge-to-edge für Daten-Tabellen auf Mobile — brechen über die
         seitliche Padding hinaus aus, damit die Tabelle die volle
         Viewport-Breite nutzen kann. Negative Margin gleicht die .75rem
         Container-Padding aus. */
      .apps-main-content > .tp-data-table-card,
      .apps-main-content > .datahub-fragment,
      .apps-main-content > [data-fragment] > .tp-data-table-card {
        margin-left: -.75rem !important;
        margin-right: -.75rem !important;
        border-radius: 0 !important;
        border-left: 0 !important;
        border-right: 0 !important;
        box-shadow: none !important;
      }
      .apps-channel-table-wrap { overflow-x:auto; -webkit-overflow-scrolling:touch; }
      .apps-channel-table-wrap table { min-width:600px; }
    }
/**
 * TP V2 — Banner
 * Wide horizontal alert/notification bar with optional icon, content and
 * actions. Used for acute call-to-actions at the top of a page (e.g.
 * open Nachforschungen, escalated tickets, maintenance windows).
 *
 * Usage:
 *   <div class="tpv2-banner tpv2-banner-danger">
 *     <div class="tpv2-banner-icon"><i class="fas fa-magnifying-glass"></i></div>
 *     <div class="tpv2-banner-body">
 *       <div class="tpv2-banner-title">Offene Nachforschung · NF-2026-0813</div>
 *       <div class="tpv2-banner-sub">Kunde meldet: Paket noch nicht angekommen</div>
 *     </div>
 *     <div class="tpv2-banner-actions">
 *       <button class="tpv2-btn">Bearbeiten</button>
 *     </div>
 *   </div>
 *
 * Variants: tpv2-banner-danger | tpv2-banner-warning | tpv2-banner-info | tpv2-banner-success
 *
 * Tones are built from the badge tokens, not from hex literals: a banner used
 * to be a bright island on a dark page. Surface = `--tp-badge-*-bg`,
 * text = `--tp-badge-*-text`, border mixed from the same tone. The icon
 * medallion inverts that pair — fill = `--tp-badge-*-text`, glyph =
 * `--tp-surface`. A white glyph on `--tp-success` measured 2.06:1 (D-003).
 */

.tpv2-banner {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--tp-space-3);
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
  border-radius: var(--tp-radius-md);
  border: 1px solid transparent;
  font-family: inherit;
  /* default = neutral */
  background-color: var(--tp-badge-gray-bg, #f3f4f6);
  border-color: var(--tp-border, #e1e1e1);
  color: var(--tp-text, #202223);
}

.tpv2-banner-icon {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--tp-badge-gray-text, #374151);
  color: var(--tp-surface, #fff);
  flex-shrink: 0;
  font-size: var(--tp-font-size-3);
}

.tpv2-banner-body {
  flex: 1;
  min-width: 0;
}

.tpv2-banner-title {
  font-size: var(--tp-font-size-3);
  font-weight: var(--tp-font-weight-bold);
  line-height: var(--tp-line-tight);
}

.tpv2-banner-sub {
  font-size: var(--tp-font-size-2);
  margin-top: var(--tp-space-0-5);
  line-height: var(--tp-line-snug);
  opacity: 0.9;
}

.tpv2-banner-actions {
  display: flex;
  gap: var(--tp-space-1-5);
  flex-shrink: 0;
  margin-left: auto;
}

/* ===========================
   Tone variants
   =========================== */
.tpv2-banner-danger {
  background-color: var(--tp-badge-danger-bg, #fee2e2);
  border-color: color-mix(in srgb, var(--tp-badge-danger-text, #991b1b) 30%, transparent);
}
.tpv2-banner-danger .tpv2-banner-icon {
  background-color: var(--tp-badge-danger-text, #991b1b);
  animation: tpv2-banner-pulse 2s ease-in-out infinite;
}
.tpv2-banner-danger .tpv2-banner-title,
.tpv2-banner-danger .tpv2-banner-sub { color: var(--tp-badge-danger-text, #991b1b); }

.tpv2-banner-warning {
  background-color: var(--tp-badge-warning-bg, #fef3c7);
  border-color: color-mix(in srgb, var(--tp-badge-warning-text, #92400e) 30%, transparent);
}
.tpv2-banner-warning .tpv2-banner-icon { background-color: var(--tp-badge-warning-text, #92400e); }
.tpv2-banner-warning .tpv2-banner-title,
.tpv2-banner-warning .tpv2-banner-sub { color: var(--tp-badge-warning-text, #92400e); }

.tpv2-banner-info {
  background-color: var(--tp-badge-info-bg, #dbeafe);
  border-color: color-mix(in srgb, var(--tp-badge-info-text, #1e40af) 30%, transparent);
}
.tpv2-banner-info .tpv2-banner-icon { background-color: var(--tp-badge-info-text, #1e40af); }
.tpv2-banner-info .tpv2-banner-title,
.tpv2-banner-info .tpv2-banner-sub { color: var(--tp-badge-info-text, #1e40af); }

.tpv2-banner-success {
  background-color: var(--tp-badge-success-bg, #dcfce7);
  border-color: color-mix(in srgb, var(--tp-badge-success-text, #166534) 30%, transparent);
}
.tpv2-banner-success .tpv2-banner-icon { background-color: var(--tp-badge-success-text, #166534); }
.tpv2-banner-success .tpv2-banner-title,
.tpv2-banner-success .tpv2-banner-sub { color: var(--tp-badge-success-text, #166534); }

/* ===========================
   Pulse animation (for danger banners)
   =========================== */
@keyframes tpv2-banner-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--tp-badge-danger-text, #991b1b) 40%, transparent); }
  50%      { box-shadow: 0 0 0 10px transparent; }
}

@media (prefers-reduced-motion: reduce) {
  .tpv2-banner-danger .tpv2-banner-icon { animation: none; }
}

/* Narrow viewports: actions move under the text instead of squeezing it. */
@media (max-width: 767px) {
  .tpv2-banner-actions {
    margin-left: 0;
    width: 100%;
  }
}
/**
 * TP V2 — Breadcrumb trail
 * Path above a detail or form page ("zurück nach …"). One line, wraps on
 * narrow viewports, current page is text and never a link.
 *
 * Usage:
 *   <nav class="tpv2-crumbs" aria-label="Breadcrumb">
 *     <a href="/x"><i class="fas fa-arrow-left"></i> Übersicht</a>
 *     <i class="fas fa-chevron-right tpv2-crumbs-sep"></i>
 *     <span class="tpv2-crumbs-current">Neuer Eintrag</span>
 *   </nav>
 */

.tpv2-crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--tp-space-2);
  margin-bottom: var(--tp-density-gap);
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  color: var(--tp-text-secondary, #6d7175);
  font-family: inherit;
}

.tpv2-crumbs a {
  display: inline-flex;
  align-items: center;
  gap: var(--tp-space-1-5);
  color: var(--tp-text-secondary, #6d7175);
  text-decoration: none;
  transition: color 0.12s;
}

.tpv2-crumbs a:hover {
  color: var(--tp-text, #202223);
  text-decoration: underline;
}

.tpv2-crumbs a i {
  font-size: var(--tp-font-size-1);
}

.tpv2-crumbs-sep {
  font-size: var(--tp-font-size-1);
  color: var(--tp-border, #e1e1e1);
}

.tpv2-crumbs-current {
  color: var(--tp-text, #202223);
  font-weight: var(--tp-font-weight-medium);
}
/**
 * TP V2 — Button
 * Soft, subtle button used throughout V2 layouts. White by default, blue
 * for primary, danger for destructive. Icon-only and pill-icon variants.
 *
 * Usage:
 *   <button class="tpv2-btn">Cancel</button>
 *   <button class="tpv2-btn tpv2-btn-primary"><i class="fas fa-check"></i> Speichern</button>
 *   <button class="tpv2-btn tpv2-btn-danger">Löschen</button>
 *   <button class="tpv2-btn tpv2-btn-icon" title="More"><i class="fas fa-ellipsis"></i></button>
 *   <button class="tpv2-btn tpv2-btn-sm">Small</button>
 */

/* Height comes from the density set so button, input, select and segmented
   control line up in the same toolbar; padding and type come from the scale. */
.tpv2-btn {
  padding: 0 var(--tp-space-3);
  min-height: var(--tp-density-control-h);
  border: 1px solid var(--tp-border, #e1e1e1);
  background-color: var(--tp-surface, #fff);
  border-radius: var(--tp-radius-md);
  font-size: var(--tp-font-size-2);
  color: var(--tp-text, #202223);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--tp-space-1-5);
  font-family: inherit;
  font-weight: var(--tp-font-weight-medium);
  line-height: var(--tp-line-tight);
  transition: background-color 0.1s, border-color 0.1s, color 0.1s;
  white-space: nowrap;
  text-decoration: none;
}

.tpv2-btn:hover {
  background-color: var(--tp-hover-bg, #f3f4f6);
}

.tpv2-btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--tp-focus-ring, rgba(29, 75, 142, 0.2));
}

.tpv2-btn:disabled,
.tpv2-btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

.tpv2-btn i {
  font-size: var(--tp-font-size-1);
}

/* ===========================
   Variants
   =========================== */
.tpv2-btn-primary {
  background-color: var(--tp-primary, #1d4b8e);
  color: #fff;
  border-color: var(--tp-primary, #1d4b8e);
}
.tpv2-btn-primary:hover {
  background-color: var(--tp-primary-hover, #174080);
}

/* Filled status buttons darken the status color: the status tokens are tuned
   for text and dots on a page, not as a background under white label text. */
.tpv2-btn-danger {
  background-color: color-mix(in srgb, var(--tp-danger, #ef4444) 80%, #000);
  color: #fff;
  border-color: color-mix(in srgb, var(--tp-danger, #ef4444) 80%, #000);
}
.tpv2-btn-danger:hover {
  background-color: color-mix(in srgb, var(--tp-danger, #ef4444) 66%, #000);
}

.tpv2-btn-success {
  background-color: color-mix(in srgb, var(--tp-success, #18cf72) 76%, #000);
  color: #fff;
  border-color: color-mix(in srgb, var(--tp-success, #18cf72) 76%, #000);
}
.tpv2-btn-success:hover {
  background-color: color-mix(in srgb, var(--tp-success, #18cf72) 62%, #000);
}

.tpv2-btn-warning {
  background-color: color-mix(in srgb, var(--tp-warning, #f59e0b) 78%, #000);
  color: #fff;
  border-color: color-mix(in srgb, var(--tp-warning, #f59e0b) 78%, #000);
}
.tpv2-btn-warning:hover {
  background-color: color-mix(in srgb, var(--tp-warning, #f59e0b) 64%, #000);
}

/* Ghost — no border, no background until hover */
.tpv2-btn-ghost {
  background: none;
  border-color: transparent;
}
.tpv2-btn-ghost:hover {
  background-color: var(--tp-hover-bg, #f3f4f6);
  border-color: var(--tp-border, #e1e1e1);
}

/* ===========================
   Sizes
   =========================== */
/* Sizes reach into the scale directly, not into the density set — otherwise
   variants and density multiply each other (D-00T §4). */
.tpv2-btn-sm {
  padding: 0 var(--tp-space-2);
  min-height: calc(var(--tp-density-control-h) - var(--tp-space-1));
  font-size: var(--tp-font-size-2);
}

.tpv2-btn-lg {
  padding: 0 var(--tp-space-4);
  min-height: calc(var(--tp-density-control-h) + var(--tp-space-1));
  font-size: var(--tp-font-size-3);
}
.tpv2-btn-lg i { font-size: var(--tp-font-size-2); }

/* ===========================
   Icon-only
   =========================== */
.tpv2-btn-icon {
  padding: 0 var(--tp-space-2);
}

.tpv2-btn-icon-only {
  padding: 0;
  width: var(--tp-density-control-h);
  min-width: var(--tp-density-control-h);
  height: var(--tp-density-control-h);
  justify-content: center;
}

/* ===========================
   Block — full width
   =========================== */
.tpv2-btn-block {
  width: 100%;
  justify-content: center;
}

.tpv2-btn-block.tpv2-btn-align-start {
  justify-content: flex-start;
}

/* ===========================
   Button group
   =========================== */
.tpv2-btn-group {
  display: inline-flex;
  gap: var(--tp-space-1-5);
  flex-wrap: wrap;
}
/**
 * TP V2 — Card
 * White content container with optional header, body and footer.
 * Used everywhere in V2 layouts as the primary content unit.
 *
 * Usage:
 *   <div class="tpv2-card">
 *     <div class="tpv2-card-h">
 *       <h3>Section Title</h3>
 *       <button>Action</button>
 *     </div>
 *     <div class="tpv2-card-b">Content</div>
 *     <div class="tpv2-card-f">Footer</div>
 *   </div>
 *
 * Sub-variants:
 *   .tpv2-card-soft   — softer surface for nested boxes
 *   .tpv2-card-flat   — no border, no shadow
 *   .tpv2-card-dashed — dashed border (scheduled / draft)
 *   .tpv2-card-flush  — body padding removed (for tables that go edge-to-edge)
 *   .tpv2-card-bar    — control strip between header and body (filters)
 */

.tpv2-card {
  background-color: var(--tp-surface, #fff);
  border: 1px solid var(--tp-border, #e1e1e1);
  border-radius: var(--tp-radius-md);
  overflow: hidden;
  color: var(--tp-text, #202223);
  font-family: inherit;
  /* Rung 1 of two. Not a per-card judgement call: every resting surface takes
     this one, overlays take rung 2, nothing else casts a shadow. */
  box-shadow: var(--tp-elev-1);
}

.tpv2-card-h {
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
  border-bottom: 1px solid var(--tp-border, #e1e1e1);
  display: flex;
  align-items: center;
  justify-content: space-between;
  background-color: var(--tp-surface, #fff);
  gap: var(--tp-space-3);
}

.tpv2-card-h h3,
.tpv2-card-h h4 {
  font-size: var(--tp-font-size-3);
  font-weight: var(--tp-font-weight-semibold);
  line-height: var(--tp-line-tight);
  margin: 0;
  color: var(--tp-text, #202223);
}

.tpv2-card-h .sub {
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  color: var(--tp-text-secondary, #6d7175);
  font-weight: var(--tp-font-weight-normal);
}

.tpv2-card-b {
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
}

/* Control strip between header and body: the filters that narrow *this* card's
   content. Tied to the head surface, not to the body, because it belongs to
   the frame around the data and not to the data (plan 0010, D-001b). */
.tpv2-card-bar {
  padding: var(--tp-space-2) var(--tp-density-pad-x);
  border-bottom: 1px solid var(--tp-border, #e1e1e1);
  background-color: var(--tp-table-header-bg, #f6f8fa);
}

.tpv2-card-f {
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
  border-top: 1px solid var(--tp-border, #e1e1e1);
  background-color: var(--tp-table-header-bg, #f6f8fa);
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  color: var(--tp-text-secondary, #6d7175);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tp-space-3);
}

.tpv2-card-f a {
  color: var(--tp-prose-link, #0969da);
  text-decoration: none;
}

.tpv2-card-f a:hover {
  text-decoration: underline;
}

/* Footer variant: fact pairs (label + figure). The footer states facts about
   the content — it is not a second action row. */
.tpv2-card-facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--tp-space-2) var(--tp-space-6);
  min-width: 0;
}

.tpv2-card-facts > span {
  display: inline-flex;
  align-items: baseline;
  gap: var(--tp-space-1-5);
}

.tpv2-card-facts strong {
  color: var(--tp-text, #202223);
  font-weight: var(--tp-font-weight-semibold);
  font-variant-numeric: var(--tp-numeric);
}

/* Card header with a subtitle: the title block owns the free width, the
   control keeps its own. `flex: 1` and not `justify-content` alone — with a
   state glyph in front (plan 0057, E12 §2) the header has three children, and
   `space-between` would push the title into the middle of the card. */
.tpv2-card-h-text {
  flex: 1 1 auto;
  min-width: 0;
}

/* Rechts im Kopf: Zustand und Knöpfe, nebeneinander auf einer Linie (Plan
   0065, Befund R-6). Sie behalten ihre Eigenbreite — der Titel nimmt den
   Rest — und brechen erst um, wenn sie wirklich nicht mehr nebeneinander
   passen. */
.tpv2-card-h-actions {
  display: flex;
  flex: 0 0 auto;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--tp-space-2);
}

@media (max-width: 767px) {
  .tpv2-card-h {
    flex-wrap: wrap;
  }
  .tpv2-card-f {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--tp-space-1-5);
  }
}

/* Soft nested box — for putting boxes inside a card. The surface comes from a
   theme token; the literal #fafafa was a light island in dark mode. */
.tpv2-card-soft {
  box-shadow: none;
  background-color: var(--tp-table-header-bg, #f6f8fa);
  border: 1px solid var(--tp-border, #e1e1e1);
  border-radius: var(--tp-radius-md);
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-base);
}

/* Dashed — scheduled / draft / placeholder content */
.tpv2-card-dashed {
  border-style: dashed;
  background-color: transparent;
}

/* Flat — for content that doesn't need an outer border */
.tpv2-card-flat {
  border: none;
  background: transparent;
  box-shadow: none;
}

/* Flush body — removes padding (use when a table is direct child) */
.tpv2-card-flush .tpv2-card-b {
  padding: 0;
}

/* Hover-able card (whole card behaves as a link target) */
.tpv2-card-hover {
  cursor: pointer;
  transition: border-color 0.12s, box-shadow 0.12s, transform 0.12s;
}
.tpv2-card-hover:hover {
  border-color: color-mix(in srgb, var(--tp-accent, #1d4b8e) 35%, var(--tp-border, #e1e1e1));
  box-shadow: var(--tp-elev-2);
}

/* ===========================
   Ein zweiter Teil desselben Karteninhalts (Plan 0057, E10)
   =========================== */
/* Die Positionen und die Summen sind EIN Beleg: was gekauft wurde und was es
   kostet, wird zusammen gelesen. Zwei Karten untereinander zwingen den Blick
   zu springen, obwohl es eine Rechnung ist — also steht der zweite Teil in
   derselben Karte, durch eine Linie abgesetzt.

   Nicht `tpv2-card-f`: der Fuß ist grau, klein und sekundär und stellt Fakten
   ÜBER den Inhalt fest. Hier steht Inhalt — „Gesamt" ist die wichtigste Zahl
   der Karte und nicht ihre Fußnote.

   Gehört in eine `tpv2-card-flush`: dort bringt jeder Teil seine eigene
   Polsterung mit, und die Linie läuft von Kante zu Kante. */
.tpv2-card-part {
  border-top: 1px solid var(--tp-border, #e1e1e1);
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
  min-width: 0;
}

/* ===========================
   Ein Kasten IN einer Fläche (Plan 0057, E12 §3)
   =========================== */
/* „Gehört dazu" und „ist ein Ding darin" sind zwei verschiedene Aussagen, und
   ein zweiter Grauwert kann nur die erste. Der verschachtelte Kasten ist
   deshalb dieselbe Karte wie draußen — weiße Fläche, runde Ecken, sichtbare
   Kante — nur ohne Erhebung: es schwebt nur, was auf dem Grund der Seite
   liegt, nicht was in etwas anderem steckt.

   Er steht auf hellem Grund: im Körper eines Akkordeons (`tpv2-acc-b`) oder
   in einem `tpv2-card-part`, dem die Seite ihren Grund gibt. Auf weißem Grund
   wäre er ein Rahmen ohne Aussage. */
.tpv2-card-nest {
  box-shadow: none;
}

.tpv2-card-nest > .tpv2-card-h {
  padding: var(--tp-space-2) var(--tp-space-3);
  background-color: transparent;
}

.tpv2-card-nest > .tpv2-card-h h3,
.tpv2-card-nest > .tpv2-card-h h4 {
  font-size: var(--tp-font-size-2);
}

.tpv2-card-nest > .tpv2-card-b {
  padding: var(--tp-space-3);
}

.tpv2-card-nest.tpv2-card-flush > .tpv2-card-b {
  padding: 0;
}

/* Zwei Kästen nebeneinander — Liefer- und Rechnungsadresse (Plan 0057, E11).
   `auto-fit` statt einer Medienabfrage: was hier zählt, ist die Breite des
   Kastens, in dem die Reihe steht, und die kennt eine Medienabfrage nicht.
   Unter etwa 500 px fällt die zweite Spalte von selbst darunter. */
.tpv2-card-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--tp-density-gap);
  min-width: 0;
}
/* Conversation — der Faden an einem Gegenstand (Plan 0065, C-1).
 *
 * Vorbild ist die SupportHub-Chronik (Plan 0022) und der V1-Baustein
 * `message-bubble.css`: die Darstellung ist übernommen, der Code nicht.
 * V1 kennt Avatar, Sprechblase und Kanalfuß; hier zählen Richtung, Zeit,
 * Gelesen und Dichte. Keine neue Farbe, keine neue Radiusstufe — alles aus
 * tokens.css.
 *
 * Den ZUSTAND des Fadens trägt nicht diese Komponente, sondern die Karte
 * darum herum (Pille + Glyph): sonst gäbe es zwei Orte für „geschlossen".
 */
.tpv2-convo {
  display: flex;
  flex-direction: column;
  gap: var(--tp-space-4);
  container-type: inline-size;
}

.tpv2-convo-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--tp-space-2);
}

/* Tages-Trennzeile — dasselbe Muster wie .tpv2-tl-day in der Chronik. */
.tpv2-convo-day {
  margin-top: var(--tp-space-2);
  padding-bottom: var(--tp-space-1);
  border-bottom: 1px solid var(--tp-border);
  font-size: var(--tp-font-size-1);
  font-weight: var(--tp-font-weight-bold);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--tp-text-secondary);
}

.tpv2-convo-day:first-child {
  margin-top: 0;
}

/* Eine Nachricht ist eine Zeile mit Kante — keine Sprechblase, keine
 * Seitenwahl: bei zwei bis fünf Nachrichten kostet ein Messenger-Layout
 * die halbe Spaltenbreite und gibt dafür nichts als Nostalgie. */
.tpv2-convo-msg {
  border: 1px solid var(--tp-border);
  border-left: 3px solid var(--tp-border);
  border-radius: var(--tp-radius-md);
  background: var(--tp-surface);
  padding: var(--tp-space-3) var(--tp-density-pad-x);
}

/* Richtung ist Kante + Fläche + Einzug, dreifach codiert: die Kante trägt
 * sie für den schnellen Blick, die Fläche für den unruhigen, die Rolle im
 * Kopf für den Vorleser. Farbe allein trüge sie nicht. */
.tpv2-convo-in {
  border-left-color: var(--tp-accent);
}

.tpv2-convo-out {
  margin-left: var(--tp-space-6);
  background: var(--tp-hover-bg);
  border-left-color: var(--tp-border);
}

@container (max-width: 520px) {
  .tpv2-convo-out {
    margin-left: 0;
  }
}

.tpv2-convo-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--tp-space-2);
  margin-bottom: var(--tp-space-1);
}

.tpv2-convo-who {
  font-size: var(--tp-font-size-2);
  font-weight: var(--tp-font-weight-semibold);
  color: var(--tp-text);
}

.tpv2-convo-role {
  font-size: var(--tp-font-size-1);
  line-height: var(--tp-line-none);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--tp-text-secondary);
  border: 1px solid var(--tp-border);
  border-radius: var(--tp-radius-xs);
  padding: 2px var(--tp-space-1);
}

.tpv2-convo-in .tpv2-convo-role {
  color: var(--tp-accent);
  border-color: var(--tp-accent);
}

.tpv2-convo-time {
  margin-left: auto;
  font-size: var(--tp-font-size-2);
  color: var(--tp-text-secondary);
  font-variant-numeric: var(--tp-numeric);
}

/* „gelesen" / „ungelesen" — hinter der Uhrzeit, klein und grau. Es steht
 * im Kopf und nicht unter dem Rumpf: eine Aussage ueber die Nachricht,
 * keine Fussnote zu ihrem Inhalt. */
.tpv2-convo-read {
  font-size: var(--tp-font-size-1);
  color: var(--tp-text-secondary);
}

/* Der Rumpf: escapeter Text, Absätze an Leerzeilen. Markdown wird NICHT
 * gerendert (C-1 E9) — der Renderer kommt mit N-1 und gehört zu Mails. Die
 * Maßeinheit ist die Zeile und nicht die Karte: 78 Zeichen Lesebreite. */
.tpv2-convo-body {
  font-size: var(--tp-font-size-3);
  line-height: var(--tp-line-base);
  color: var(--tp-text);
  max-width: 78ch;
}

.tpv2-convo-body p {
  margin: 0;
}

.tpv2-convo-body p + p {
  margin-top: var(--tp-space-2);
}

/* Belege an einer Nachricht (Plan 0103, F) — unter dem Text, vor der
 * Fußzeile. */
.tpv2-convo-thumbs {
  margin-top: var(--tp-space-2);
  display: flex;
  gap: var(--tp-space-2);
  flex-wrap: wrap;
}

.tpv2-convo-meta {
  margin: var(--tp-space-1-5) 0 0;
  font-size: var(--tp-font-size-1);
  color: var(--tp-text-secondary);
  display: flex;
  gap: var(--tp-space-2);
  flex-wrap: wrap;
}

/* Das Antwortfeld ist Teil des Fadens, kein Tat-Fenster: eine Antwort ist
 * Fließarbeit und kein Dialog mit Bestätigung. */
.tpv2-convo-reply {
  border-top: 1px solid var(--tp-border);
  padding-top: var(--tp-space-3);
  display: flex;
  flex-direction: column;
  gap: var(--tp-space-2);
}

.tpv2-convo-reply-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tp-space-3);
  flex-wrap: wrap;
}

.tpv2-convo-count {
  font-size: var(--tp-font-size-1);
  color: var(--tp-text-secondary);
  font-variant-numeric: var(--tp-numeric);
}

/* Geschlossen heißt gesperrt, nie versteckt (Hausregel, IT#16588): das
 * Feld bleibt stehen, wird grau und nennt den Grund darüber. */
.tpv2-convo-reply-locked .tpv2-textarea,
.tpv2-convo-reply-locked .tpv2-btn {
  cursor: not-allowed;
}

.tpv2-convo-sk {
  display: flex;
  flex-direction: column;
  gap: var(--tp-space-2);
}

.tpv2-convo-sk-msg {
  border: 1px solid var(--tp-border);
  border-left: 3px solid var(--tp-border);
  border-radius: var(--tp-radius-md);
  padding: var(--tp-space-3) var(--tp-density-pad-x);
  display: flex;
  flex-direction: column;
  gap: var(--tp-space-2);
}

.tpv2-convo-sk-msg:nth-child(2) {
  margin-left: var(--tp-space-6);
}
/* CopyValue — ein Wert mit einem Anfasser zum Kopieren (Plan 0065, T-16).
 *
 * Er lag bis hierher als `scb-copy` in der Basis-Vorlage von ShopcloudHub
 * und hing an einer Funktion, die nur dort steht. Ein Fenster in einer
 * anderen Hülle zeigte damit einen Knopf, der nichts tut — die stillste
 * Sorte kaputt.
 *
 * Keine neue Farbe und keine neue Radiusstufe: alles aus tokens.css.
 */
.tpv2-copy {
  display: inline-flex;
  align-items: center;
  gap: var(--tp-space-2);
  min-width: 0;
  max-width: 100%;
}

/* Der Wert selbst. Er bricht um statt zu scrollen: eine Adresse, die man
 * kopieren soll, muss man auch lesen können. */
.tpv2-copy-value {
  font-family: var(--tp-font-mono);
  font-size: var(--tp-text-xs);
  line-height: 1.6;
  color: var(--tp-text-primary);
  word-break: break-all;
  white-space: normal;
  min-width: 0;
}

/* Ist der Wert eine Adresse, ist er auch ein Anker (Plan 0065, Befund R-5):
 * er sieht aus wie ein Link, weil er einer ist — unterstrichen erst beim
 * Zeigen, damit eine 90 Zeichen lange Adresse die Zeile nicht zerschneidet. */
.tpv2-copy-link {
  color: var(--tp-accent);
  text-decoration: none;
}

.tpv2-copy-link:hover {
  text-decoration: underline;
}

.tpv2-copy-link:focus-visible {
  outline: 2px solid var(--tp-accent);
  outline-offset: 1px;
  border-radius: var(--tp-radius-sm);
}

/* Für einen Wert, den ein Mensch liest und nicht abtippt. */
.tpv2-copy-value.tpv2-copy-prose {
  font-family: inherit;
  font-size: inherit;
  word-break: normal;
}

/* Der Anfasser: ein Symbolknopf, so zurückgenommen wie möglich — er ist
 * eine Bequemlichkeit und keine Tat. */
.tpv2-copy-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  font-size: var(--tp-text-xs);
  color: var(--tp-text-secondary);
  background: var(--tp-surface);
  border: 1px solid var(--tp-border);
  border-radius: var(--tp-radius-sm);
  cursor: pointer;
}

.tpv2-copy-btn:hover {
  color: var(--tp-text-primary);
  background: var(--tp-hover-bg);
}

.tpv2-copy-btn:focus-visible {
  outline: 2px solid var(--tp-accent);
  outline-offset: 1px;
}

/* Die eine Rückmeldung: kopiert. Ohne sie drückt ein Mensch zweimal und
 * weiß danach nicht, ob es beim ersten Mal ging. */
.tpv2-copy-btn.is-done {
  color: var(--tp-success, #116329);
  border-color: var(--tp-success, #116329);
}
/**
 * TP V2 — Data Table
 * Clean, light data table used for positions, fulfillments, list pages,
 * CRUD overviews, etc. Tabular-nums for numeric columns, subtle borders,
 * sticky header optional.
 *
 * Usage:
 *   <table class="tpv2-table">
 *     <thead>
 *       <tr>
 *         <th>SKU</th>
 *         <th>Bezeichnung</th>
 *         <th class="num">Menge</th>
 *         <th class="num">Preis</th>
 *       </tr>
 *     </thead>
 *     <tbody>
 *       <tr>
 *         <td class="mono">10119463-0</td>
 *         <td>Apple iPhone 17 256GB</td>
 *         <td class="num">1</td>
 *         <td class="num">789,00 €</td>
 *       </tr>
 *     </tbody>
 *     <tfoot>
 *       <tr>
 *         <td colspan="3" class="total-label">Gesamt</td>
 *         <td class="num total">789,00 €</td>
 *       </tr>
 *     </tfoot>
 *   </table>
 *
 * Modifiers:
 *   .tpv2-table-bordered    add an outer rounded border (use when not
 *                           inside a card with overflow:hidden)
 *   .tpv2-table-compact     denser row padding for long lists
 *   .tpv2-table-zebra       alternating row backgrounds
 *   .tpv2-table-hover       row hover highlight
 *   .tpv2-table-sticky      sticky thead at top of scroll container
 *   .tpv2-table-headless    two-column rank list (no thead)
 *
 * Scroll frame (wrap the table, see the block at the bottom of this file):
 *   .tpv2-tscroll           horizontal scroll container
 *   .tpv2-tscroll-stick     + sticky first column, column-wise snapping
 *   .tpv2-tscroll-tall      + capped height and sticky thead
 */

.tpv2-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  color: var(--tp-text, #202223);
  font-family: inherit;
}

/* Head/foot surface comes from the theme token: the literal is only a
   fallback. Hard-coding it left a white header in dark mode. */
.tpv2-table thead tr {
  background-color: var(--tp-table-header-bg, #f6f8fa);
  border-bottom: 1px solid var(--tp-border, #e1e1e1);
}

.tpv2-table th {
  text-align: left;
  padding: var(--tp-density-cell-y) var(--tp-density-cell-x);
  font-weight: var(--tp-font-weight-semibold);
  color: var(--tp-text-secondary, #6d7175);
  font-size: var(--tp-font-size-1);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
}

.tpv2-table td {
  padding: var(--tp-density-cell-y) var(--tp-density-cell-x);
  border-bottom: 1px solid var(--tp-border, #e1e1e1);
  vertical-align: top;
}

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

.tpv2-table .num {
  text-align: right;
  font-variant-numeric: var(--tp-numeric);
}

.tpv2-table .center {
  text-align: center;
}

.tpv2-table .mono {
  font-family: var(--tp-font-mono);
  font-size: var(--tp-font-size-2);
}

/* Rank list: two columns, no head. Same table, one modifier — a rank list is
   not its own component (plan 0010, D-001). */
.tpv2-table-headless tbody tr:first-child td {
  padding-top: var(--tp-density-cell-y);
}

/* Footer row (totals) */
.tpv2-table tfoot tr {
  background-color: var(--tp-table-header-bg, #f6f8fa);
  border-top: 1px solid var(--tp-border, #e1e1e1);
}

.tpv2-table tfoot td {
  font-weight: var(--tp-font-weight-bold);
  border-bottom: none;
}

.tpv2-table .total-label {
  text-align: right;
  font-weight: var(--tp-font-weight-semibold);
}

.tpv2-table .total {
  font-weight: var(--tp-font-weight-bold);
}

/* ===========================
   Modifiers
   =========================== */
.tpv2-table-bordered {
  border: 1px solid var(--tp-border, #e1e1e1);
  border-radius: var(--tp-radius-md);
  overflow: hidden;
}

/* Maps onto the density set instead of hard-coding a second padding pair:
   stays one step denser than the current density, so the modifier still
   works on pages that opt into data-density="comfortable". */
.tpv2-table-compact {
  --tp-density-cell-y: var(--tp-space-1-5);
  --tp-density-cell-x: var(--tp-space-3);
}

/* Zebra has its own tone. Borrowing --tp-table-header-bg (the D-002b stopgap)
   made every second body row look exactly like the head row, so the head
   stopped reading as a head. --tp-table-row-alt is themed and *opaque*: the
   sticky first column paints --tp-surface over the row, so a translucent
   stripe would stop at the sticky column and the zebra would break exactly
   where the eye needs it (plan 0010, D-003). */
.tpv2-table-zebra tbody tr:nth-child(even) {
  background-color: var(--tp-table-row-alt, #fafbfc);
}

.tpv2-tscroll-stick .tpv2-table-zebra tbody tr:nth-child(even) td:first-child {
  background-color: var(--tp-table-row-alt, #fafbfc);
}

/* Tone or rules, not both: with the stripe doing the separating the hairline
   is a second, redundant signal. */
.tpv2-table-zebra tbody td {
  border-bottom-color: transparent;
}

.tpv2-table-hover tbody tr {
  cursor: pointer;
  transition: background-color 0.1s;
}
.tpv2-table-hover tbody tr:hover {
  background-color: var(--tp-table-row-hover, #f9fafb);
}

/* ── Row-Conditional Tones (DataHub source.row_conditional) ─────────────── */
/* The tone travels in a custom property so the sticky first column can repaint
   it, and the selector is specific enough to beat the zebra stripe: a stripe
   groups, a tone judges, and the judgement must not vanish on every second row
   (plan 0010, D-001c — three tinted rows were invisible under zebra). */
.tpv2-row-danger  { --tpv2-row-tone: rgba(220, 38, 38, 0.06); }
.tpv2-row-warning { --tpv2-row-tone: rgba(234, 179, 8, 0.07); }
.tpv2-row-success { --tpv2-row-tone: rgba(22, 163, 74, 0.06); }
.tpv2-row-info    { --tpv2-row-tone: rgba(59, 130, 246, 0.06); }
.tpv2-row-neutral { --tpv2-row-tone: rgba(107, 114, 128, 0.04); }

.tpv2-table tbody tr.tpv2-row-danger,
.tpv2-table tbody tr.tpv2-row-warning,
.tpv2-table tbody tr.tpv2-row-success,
.tpv2-table tbody tr.tpv2-row-info,
.tpv2-table tbody tr.tpv2-row-neutral {
  background-color: var(--tpv2-row-tone);
}

.tpv2-row-danger:hover  { --tpv2-row-tone: rgba(220, 38, 38, 0.10); }
.tpv2-row-warning:hover { --tpv2-row-tone: rgba(234, 179, 8, 0.12); }
.tpv2-row-success:hover { --tpv2-row-tone: rgba(22, 163, 74, 0.10); }
.tpv2-row-info:hover    { --tpv2-row-tone: rgba(59, 130, 246, 0.10); }

/* The sticky column paints an opaque --tp-surface over the row, so a
   translucent tone stopped at its edge and left a white block in the middle of
   a marked row. The tone goes on as a background *image* on top of that
   surface: the column stays opaque against the scrolled content underneath and
   still carries the mark. Rows without a tone resolve to `transparent`. */
.tpv2-tscroll-stick tbody tr > td:first-child {
  background-image: linear-gradient(
    var(--tpv2-row-tone, transparent),
    var(--tpv2-row-tone, transparent)
  );
}

/* Akzent-Strich links für danger — zieht den Blick zur Problem-Zeile. */
.tpv2-row-danger > td:first-child {
  border-left: 3px solid #dc2626;
  /* Compensates the 3px accent border so the cell keeps column alignment. */
  padding-left: calc(var(--tp-density-cell-x) - 3px);
}

.tpv2-table-sticky thead th {
  position: sticky;
  top: 0;
  z-index: 2;
}

/* ===========================
   Cell helpers
   =========================== */

/* Link cell — a *name* that links (sales channel, customer, service). Reads in
   the body font: `.cell-link` used to force monospace, which turned every
   channel name into a code fragment (plan 0010, D-001 gap list).
   The color is the themed link role; --tp-primary sits at ~2:1 on the dark
   surface, which made table links barely readable in dark mode. */
.tpv2-table .cell-link a {
  color: var(--tp-prose-link, #0969da);
  text-decoration: none;
}
.tpv2-table .cell-link a:hover {
  text-decoration: underline;
}

/* Id cell — an *identifier* (SKU, order number, UUID). The monospace face
   comes from `.mono` on the value itself, so a sub line under a SKU stays in
   the body font; the cell class only carries the link colour. */
.tpv2-table .cell-id a {
  color: var(--tp-prose-link, #0969da);
  text-decoration: none;
}
.tpv2-table .cell-id a:hover {
  text-decoration: underline;
}

/* Subtitle row (small grey text under a cell value) */
.tpv2-table .cell-sub {
  display: block;
  font-size: var(--tp-font-size-1);
  color: var(--tp-text-secondary, #6d7175);
  margin-top: var(--tp-space-0-5);
  font-weight: var(--tp-font-weight-normal);
}

/* Same information, no second line. A short key next to the name — "Webshop
   TP2.0  Kundengruppe 1011" — cost the channel table half its rows before
   D-009: 24 channels at double row height means twelve fit where twenty-four
   should. The block form stays for a sub line that is prose (the product name
   under a SKU); this one is for a short token that only qualifies the value. */
.tpv2-table .cell-sub-inline {
  display: inline;
  margin-top: 0;
  margin-left: var(--tp-space-2);
}

/* Prose column that must not set the row height. A 95-character product name
   wrapped to three lines and stretched every figure in the row with it; the
   full text stays reachable in the cell's title (plan 0010, D-001b).
   ch, not px: the cut follows the text size, so the comfortable density does
   not clip a character the compact one still showed. */
.tpv2-table .cell-clamp {
  max-width: var(--tpv2-clamp-w, 34ch);
}

.tpv2-table .cell-clamp-v {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Rank list: the *name* column yields first, the figure never breaks.
   In a ~330px dashboard third the 34ch clamp plus the figure column exceeded
   the card: the value lost its unit ("26.666,68" without "€") and the note
   wrapped — exactly the D-001d §3 break the Currency cell exists to prevent.
   width:100% + max-width:0 is the auto-layout squeeze: the name column absorbs
   whatever the figure column leaves over and ellipsizes via .cell-clamp-v; the
   figure column sizes to its nowrap content, so value, unit and note stay
   whole at every card width (plan 0010, D-017). */
.tpv2-table-headless .cell-clamp {
  width: 100%;
  max-width: 0;
}

.tpv2-table-headless td.num {
  width: 1%;
  white-space: nowrap;
}

/* Row-actions cell (right-aligned hover-revealed actions) */
.tpv2-table .cell-actions {
  text-align: right;
  white-space: nowrap;
}

/* Monospace chip for short identifiers inside a cell. */
.tpv2-code {
  font-family: var(--tp-font-mono);
  font-size: var(--tp-font-size-2);
  background-color: var(--tp-hover-bg);
  padding: var(--tp-space-0-5) var(--tp-space-1-5);
  border-radius: var(--tp-radius-xs);
}

/* Empty body — the empty state sits in the table, not next to it, so the
   header row keeps explaining what would be there. */
.tpv2-table .cell-none {
  padding: 0;
  border-bottom: none;
}

/* Sentence under a table: why a total is not the sum of its column, what the
   list is capped at. Part of the table, not a card footer. */
.tpv2-table-note {
  margin: var(--tp-space-2) var(--tp-density-cell-x) 0;
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  color: var(--tp-text-secondary, #6d7175);
}

/* Table name for screen readers only — a visible caption would duplicate the
   card title above it. */
.tpv2-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ===========================
   Scroll frame
   ===========================

   A wide table keeps its shape on a narrow screen and scrolls sideways; the
   first column sticks so the reader still knows which row is which.

   The sticky column used to *half* cover the right-aligned amounts: at
   scrollLeft 200 the turnover column read "5 €", "1 €", "0 €" — the tails of
   21.643,95 €, 21.886,01 € and 16.487,00 €. A wrong number is worse than a
   cut-off one, so the frame snaps column by column: every rest position puts a
   column boundary exactly at the sticky column's edge, and a figure is either
   fully readable or fully hidden. (plan 0010, D-001d §4.1)

   That only works with a *known* sticky width, hence --tpv2-tstick-w and the
   .tpv2-tstick-in wrapper: content-driven widths would move the snap offset. */

.tpv2-tscroll-wrap {
  position: relative;
  /* The frame is the reference for the sticky-column budget below. */
  container-type: inline-size;
  container-name: tptable;
}

/* "There is more to the right" — set by the guard script, never permanently:
   a hint that is always on claims a cut even where the table fits. The fade
   also makes the figure the frame edge cuts *look* cut, which a hard border
   does not (plan 0010, D-001b). */
.tpv2-tscroll-wrap::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  /* As wide as a cell's own padding, no wider: a column that fits must not be
     dimmed, only one that is genuinely cut. */
  width: var(--tp-space-4);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.12s;
  background: linear-gradient(to left, var(--tp-surface, #fff), transparent);
}

.tpv2-tscroll-wrap[data-more='1']::after {
  opacity: 1;
}

.tpv2-tscroll {
  --tpv2-tstick-w: 168px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

/* The sticky width is a budget, and the budget follows the *frame*, not the
   viewport. At a flat 168 px a five-column table that fits at 1280 px still cut
   "Telekom USB Stick 1 GB silber" after 30 characters with 700 px of white to
   its right — while the same 240 px inside a half-width card swallowed a whole
   figure column (plan 0010, D-001c). A media query cannot tell the two apart,
   so the frame asks its own width. It stays a fixed number, not a content
   width: the column-wise snapping below depends on knowing it. */
@container tptable (min-width: 700px) {
  .tpv2-tscroll {
    --tpv2-tstick-w: 240px;
  }
}

/* On a phone the sticky column is not a column, it is a budget: 168 px of
   390 px left 172 px for six figure columns — one at a time, with the next
   one always cut. The name gets less room and keeps its sub line, which is
   what actually distinguishes two channels called "Webshop" (D-001b). */

.tpv2-tscroll-stick {
  scroll-snap-type: x mandatory;
  /* Snap targets align to the right edge of the sticky column, not to the
     frame edge — cell padding included, borders deliberately not (the sticky
     column separates with a shadow, which takes no width). */
  scroll-padding-left: calc(var(--tpv2-tstick-w) + 2 * var(--tp-density-cell-x));
}

.tpv2-tscroll-stick th:not(:first-child),
.tpv2-tscroll-stick td:not(:first-child) {
  scroll-snap-align: start;
  /* Never fling past a column: skipping one is how you end up between two. */
  scroll-snap-stop: always;
}

.tpv2-tscroll-stick th:first-child,
.tpv2-tscroll-stick td:first-child {
  position: sticky;
  left: 0;
  z-index: 2;
  background-color: var(--tp-surface, #fff);
  box-shadow: 1px 0 0 var(--tp-border, #e1e1e1);
}

.tpv2-tscroll-stick thead th:first-child,
.tpv2-tscroll-stick tfoot td:first-child {
  z-index: 4;
  background-color: var(--tp-table-header-bg, #f6f8fa);
}

/* The one column the sticky column cuts into (set by the guard script in
   shopcloud-ui's DataTable). Hidden outright: the space stays, the half figure
   goes. The cell keeps its own background — `visibility: hidden` on the cell
   punched a white notch through the sticky totals row (plan 0010, D-001b). */
.tpv2-tcut {
  color: transparent;
}

.tpv2-tcut * {
  visibility: hidden;
}

/* Fixed width for the sticky column — the snap offset above depends on it. */
.tpv2-tstick-in {
  display: block;
  width: var(--tpv2-tstick-w, 168px);
  min-width: 0;
}

.tpv2-tstick-in > * {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A cell whose sub line is inline has one line, not two — so the sticky
   wrapper must not put its children on rows of their own. The clipping moves
   up to the wrapper, which is the only element that knows the budget. */
.tpv2-tstick-in:has(> .cell-sub-inline) {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tpv2-tstick-in:has(> .cell-sub-inline) > * {
  display: inline;
  overflow: visible;
}

/* Capped height + sticky head: with 24 rows the column titles are long gone by
   the time you scroll sideways (D-001d §4.2). */
.tpv2-tscroll-tall {
  max-height: var(--tpv2-tscroll-h, 70vh);
  overflow-y: auto;
}

.tpv2-tscroll-tall thead th {
  position: sticky;
  top: 0;
  z-index: 3;
  background-color: var(--tp-table-header-bg, #f6f8fa);
  /* border-collapse drops real borders on sticky cells — inset shadow instead. */
  box-shadow: inset 0 -1px 0 var(--tp-border, #e1e1e1);
}

.tpv2-tscroll-tall.tpv2-tscroll-stick thead th:first-child {
  z-index: 5;
  box-shadow:
    inset 0 -1px 0 var(--tp-border, #e1e1e1),
    1px 0 0 var(--tp-border, #e1e1e1);
}

/* Totals stay in view: in a capped table the summary row is the first thing
   that scrolls out of reach, and it is the one row nobody wants to hunt for. */
.tpv2-tscroll-tall tfoot td {
  position: sticky;
  bottom: 0;
  z-index: 3;
  background-color: var(--tp-table-header-bg, #f6f8fa);
  box-shadow: inset 0 1px 0 var(--tp-border, #e1e1e1);
}

.tpv2-tscroll-tall.tpv2-tscroll-stick tfoot td:first-child {
  z-index: 5;
  box-shadow:
    inset 0 1px 0 var(--tp-border, #e1e1e1),
    1px 0 0 var(--tp-border, #e1e1e1);
}

@media (max-width: 767px) {
  .tpv2-tscroll {
    --tpv2-tstick-w: 116px;
    --tpv2-clamp-w: 20ch;
  }

  /* One scroller per axis. The capped height buys a sticky head and a sticky
     totals row — but a sticky head sticks to the *frame*, and on a phone the
     frame slides under the app bar as soon as you read the third row, so the
     head is gone anyway. What is left is a second vertical scroller inside the
     page scroller: 24 rows to wade through before the page moves on. The cap
     is a desktop device (plan 0010, D-001b). */
  .tpv2-tscroll-tall {
    max-height: none;
  }
}

/* ===========================
   List-table chrome (shopcloud-admin-table)
   ===========================

   Sort links, filter drawer, pagination bar and loading overlay of the HTMX
   list tables. Used to be Tailwind utilities inside the crate's templates,
   which only exist where a hub happens to run a Tailwind build — the catalog
   ships the plain `@tp/ui` bundle and rendered them unstyled. */

.tpv2-table-card {
  position: relative;
}

.tpv2-table-sort {
  display: inline-flex;
  align-items: center;
  gap: var(--tp-space-1);
  color: inherit;
  text-decoration: none;
}

.tpv2-table-sort:hover {
  color: var(--tp-text, #202223);
}

.tpv2-table-sort i {
  font-size: var(--tp-font-size-1);
  color: var(--tp-accent, #1d4b8e);
}

.tpv2-table-sort-idle {
  color: var(--tp-text-secondary, #6d7175);
  opacity: 0.4;
}

/* --- Filter drawer --- */
.tpv2-table-filters {
  border-bottom: 1px solid var(--tp-border, #e1e1e1);
}

.tpv2-table-filters-toggle {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tp-space-2);
  padding: var(--tp-space-2) var(--tp-density-pad-x);
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
  font-size: var(--tp-font-size-2);
  color: var(--tp-text-secondary, #6d7175);
}

.tpv2-table-filters-toggle:hover {
  background-color: var(--tp-hover-bg, #f3f4f6);
  color: var(--tp-text, #202223);
}

.tpv2-table-filters-toggle > span {
  display: inline-flex;
  align-items: center;
  gap: var(--tp-space-2);
}

.tpv2-table-filters-open {
  transform: rotate(180deg);
}

.tpv2-table-filters-body {
  padding: 0 var(--tp-density-pad-x) var(--tp-density-pad-y);
}

.tpv2-table-filters-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--tp-space-3);
}

.tpv2-table-filters-actions {
  margin-top: var(--tp-space-3);
}

.tpv2-table-filters-radios {
  display: flex;
  align-items: center;
  gap: var(--tp-space-4);
  min-height: var(--tp-density-control-h);
  font-size: var(--tp-font-size-2);
  color: var(--tp-text, #202223);
}

.tpv2-table-filters-radios label {
  display: inline-flex;
  align-items: center;
  gap: var(--tp-space-1-5);
}

/* --- Bar under the rows: counts, page size, density, paging --- */
.tpv2-table-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--tp-space-2) var(--tp-space-3);
  padding: var(--tp-space-2) var(--tp-density-pad-x);
  border-top: 1px solid var(--tp-border, #e1e1e1);
  font-size: var(--tp-font-size-2);
}

.tpv2-table-bar-group {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--tp-space-3);
}

.tpv2-table-bar-count {
  color: var(--tp-text-secondary, #6d7175);
  font-variant-numeric: var(--tp-numeric);
}

.tpv2-table-bar-field {
  display: inline-flex;
  align-items: center;
}

/* --- Loading overlay (hx-indicator on the row container) --- */
.tpv2-table-loading {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background-color: var(--tp-loading-overlay, rgba(255, 255, 255, 0.6));
  z-index: 6;
  pointer-events: none;
}

.tpv2-table-card:has(.htmx-request) .tpv2-table-loading {
  display: flex;
}

.tpv2-table-loading i {
  font-size: var(--tp-font-size-4);
  color: var(--tp-accent, #1d4b8e);
  animation: tpv2-table-spin 0.9s linear infinite;
}

@keyframes tpv2-table-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .tpv2-table-loading i {
    animation: none;
  }
}
/**
 * TP V2 — Date Range Picker
 * Trigger + panel (presets, two calendars, footer) for the Alpine
 * `dateRangePicker` widget (`@tp/ui/js/date-range-picker`). Used by
 * `shopcloud-ui` DateRangeField and admin-table filters.
 *
 * Port of the V1 block onto the D-00T scale. The trigger no longer carries
 * Tailwind utility classes: it is a control like any other and takes its height
 * from `--tp-density-control-h`, so it lines up with button and input.
 *
 * Usage: see crates/shopcloud-ui/templates/primitives/date_range_field.html
 */

.tpv2-date-range {
  position: relative;
  width: 100%;
  z-index: 1;
  font-family: inherit;
}

/* Raise above sibling filters / table rows when open */
.tpv2-date-range--open {
  z-index: 80;
}

/* ----- Trigger ----- */
.tpv2-date-range__trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tp-space-2);
  width: 100%;
  min-height: var(--tp-density-control-h);
  padding: 0 var(--tp-space-3);
  border: 1px solid var(--tp-input-border, #d1d5db);
  border-radius: var(--tp-radius-sm);
  background-color: var(--tp-input-bg, #fff);
  color: var(--tp-text, #202223);
  font-family: inherit;
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  cursor: pointer;
  text-align: left;
}

.tpv2-date-range__trigger:hover {
  border-color: var(--tp-text-secondary, #6d7175);
}

.tpv2-date-range__trigger:focus-visible {
  outline: none;
  border-color: var(--tp-accent, #1d4b8e);
  box-shadow: 0 0 0 3px var(--tp-focus-ring, rgba(29, 75, 142, 0.2));
}

.tpv2-date-range__trigger-label {
  display: inline-flex;
  align-items: center;
  gap: var(--tp-space-2);
  min-width: 0;
}

.tpv2-date-range__trigger-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tpv2-date-range__trigger svg {
  flex-shrink: 0;
  color: var(--tp-text-secondary, #6d7175);
}

/* ----- Panel ----- */
.tpv2-date-range__panel {
  position: absolute;
  left: 0;
  top: calc(100% + var(--tp-space-1-5));
  z-index: 90;
  min-width: min(620px, calc(100vw - var(--tp-space-8)));
  max-width: calc(100vw - var(--tp-space-6));
  background: var(--tp-surface, #fff);
  border: 1px solid var(--tp-border, #e1e1e1);
  border-radius: var(--tp-radius-lg);
  box-shadow: var(--tp-elev-2);
}

/* Right-anchored variant: the trigger sits at the right edge of its bar (a
   page header's chip row), so the panel has to grow leftwards to stay in the
   viewport. Set on the component — or on any ancestor of it, which is how a
   consumer that only wraps the picker (ConsoleHub's `chip-pop` form) opts in
   without a builder flag. The narrow-viewport rule lives with it, at the
   component's own breakpoint. */
.tpv2-date-range--align-end .tpv2-date-range__panel {
  left: auto;
  right: 0;
}

.tpv2-date-range__panel-inner {
  display: flex;
  align-items: stretch;
  max-height: min(480px, 75vh);
}

/* ----- Presets column ----- */
.tpv2-date-range__presets {
  width: 168px;
  flex-shrink: 0;
  border-right: 1px solid var(--tp-border, #e1e1e1);
  padding: var(--tp-space-1-5) 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.tpv2-date-range__preset {
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--tp-space-1-5) var(--tp-space-4);
  font-family: inherit;
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  border: none;
  background: transparent;
  color: var(--tp-text, #202223);
  cursor: pointer;
  transition: background 0.1s, color 0.1s;
}

.tpv2-date-range__preset:hover {
  background: var(--tp-hover-bg, #f3f4f6);
}

.tpv2-date-range__preset.is-active {
  background: color-mix(in srgb, var(--tp-accent, #1d4b8e) 12%, transparent);
  color: var(--tp-accent, #1d4b8e);
  font-weight: var(--tp-font-weight-semibold);
}

.tpv2-date-range__divider {
  border-top: 1px solid var(--tp-border, #e1e1e1);
  margin: var(--tp-space-1-5) 0;
}

.tpv2-date-range__last-n {
  padding: 0 var(--tp-space-3) var(--tp-space-2);
}

.tpv2-date-range__last-n-label {
  font-size: var(--tp-font-size-2);
  font-weight: var(--tp-font-weight-medium);
  color: var(--tp-text-secondary, #6d7175);
  margin: 0 0 var(--tp-space-1-5);
}

.tpv2-date-range__last-n-row {
  display: flex;
  gap: var(--tp-space-1-5);
  align-items: center;
  margin-bottom: var(--tp-space-1-5);
}

.tpv2-date-range__last-n-input,
.tpv2-date-range__last-n-unit {
  border: 1px solid var(--tp-border, #e1e1e1);
  border-radius: var(--tp-radius-sm);
  padding: var(--tp-space-1) var(--tp-space-1-5);
  font-family: inherit;
  font-size: var(--tp-font-size-2);
  background: var(--tp-surface, #fff);
  color: var(--tp-text, #202223);
}

.tpv2-date-range__last-n-input {
  width: 52px;
  text-align: center;
}

.tpv2-date-range__last-n-unit {
  flex: 1;
  min-width: 0;
}

.tpv2-date-range__include-today {
  display: flex;
  align-items: center;
  gap: var(--tp-space-1-5);
  font-size: var(--tp-font-size-2);
  color: var(--tp-text-secondary, #6d7175);
  cursor: pointer;
  user-select: none;
}

.tpv2-date-range__include-today input {
  width: 14px;
  height: 14px;
  margin: 0;
}

/* ----- Calendar column ----- */
.tpv2-date-range__cals {
  flex: 1;
  min-width: 0;
  padding: var(--tp-space-4) var(--tp-space-4) var(--tp-space-3);
  display: flex;
  flex-direction: column;
}

.tpv2-date-range__nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--tp-space-2);
}

.tpv2-date-range__nav-btn {
  padding: var(--tp-space-1) var(--tp-space-2);
  border-radius: var(--tp-radius-sm);
  border: none;
  background: transparent;
  cursor: pointer;
  color: var(--tp-text-secondary, #6d7175);
  font-family: inherit;
  font-size: var(--tp-font-size-3);
  line-height: var(--tp-line-none);
}

.tpv2-date-range__nav-btn:hover {
  background: var(--tp-hover-bg, #f3f4f6);
  color: var(--tp-text, #202223);
}

.tpv2-date-range__months {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--tp-space-5);
  flex: 1;
  min-height: 0;
}

.tpv2-date-range__month-label {
  text-align: center;
  font-size: var(--tp-font-size-2);
  font-weight: var(--tp-font-weight-semibold);
  color: var(--tp-text, #202223);
  margin-bottom: var(--tp-space-1-5);
  text-transform: capitalize;
}

.tpv2-date-range__wdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  margin-bottom: var(--tp-space-0-5);
}

.tpv2-date-range__wdays span {
  text-align: center;
  font-size: var(--tp-font-size-1);
  font-weight: var(--tp-font-weight-medium);
  color: var(--tp-text-secondary, #6d7175);
  padding: var(--tp-space-0-5) 0;
}

.tpv2-date-range__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
}

.tpv2-date-range__cell {
  aspect-ratio: 1;
  min-width: 0;
}

.tpv2-date-range__day {
  width: 100%;
  height: 100%;
  min-height: var(--tp-density-control-h);
  font-family: inherit;
  font-size: var(--tp-font-size-2);
  font-variant-numeric: var(--tp-numeric);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  border-radius: var(--tp-radius-sm);
  background: transparent;
  color: var(--tp-text, #202223);
  font-weight: var(--tp-font-weight-normal);
  padding: 0;
  line-height: var(--tp-line-none);
}

.tpv2-date-range__day:hover {
  background: var(--tp-hover-bg, #f3f4f6);
}

.tpv2-date-range__day.is-in-range {
  background: color-mix(in srgb, var(--tp-accent, #1d4b8e) 12%, transparent);
  border-radius: 0;
}

.tpv2-date-range__day.is-selected {
  background: var(--tp-primary, #1d4b8e);
  color: #fff;
  font-weight: var(--tp-font-weight-semibold);
  border-radius: var(--tp-radius-sm);
}

.tpv2-date-range__day.is-today {
  border-color: var(--tp-accent, #1d4b8e);
  color: var(--tp-accent, #1d4b8e);
  font-weight: var(--tp-font-weight-semibold);
}

.tpv2-date-range__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tp-space-3);
  margin-top: var(--tp-space-3);
  padding-top: var(--tp-space-3);
  border-top: 1px solid var(--tp-border, #e1e1e1);
  flex-wrap: wrap;
}

.tpv2-date-range__summary {
  font-size: var(--tp-font-size-2);
  color: var(--tp-text-secondary, #6d7175);
  min-width: 0;
}

.tpv2-date-range__actions {
  display: flex;
  gap: var(--tp-space-1-5);
  flex-shrink: 0;
  margin-left: auto;
}

/* Narrow viewports: stack presets above calendars */
@media (max-width: 640px) {
  .tpv2-date-range__panel {
    min-width: min(100vw - var(--tp-space-6), 360px);
    left: 50%;
    transform: translateX(-50%);
  }

  /* Narrow: the bar wraps and the trigger starts at the left again — the
     panel hangs under it instead of centred. */
  .tpv2-date-range--align-end .tpv2-date-range__panel {
    left: 0;
    right: auto;
    transform: none;
  }

  /* The stacked content overflows the capped panel, so it must scroll —
     otherwise the footer with the submit button is unreachable. dvh keeps
     the bottom edge visible under the iOS address bar (vh line = fallback). */
  .tpv2-date-range__panel-inner {
    flex-direction: column;
    max-height: min(75vh, 560px);
    max-height: min(75dvh, 560px);
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  .tpv2-date-range__presets {
    width: 100%;
    border-right: none;
    border-bottom: 1px solid var(--tp-border, #e1e1e1);
    max-height: 140px;
    display: flex;
    flex-wrap: wrap;
    gap: var(--tp-space-0-5);
    padding: var(--tp-space-2);
  }

  .tpv2-date-range__preset {
    width: auto;
    border-radius: var(--tp-radius-pill);
    padding: var(--tp-space-1-5) var(--tp-space-3);
  }

  .tpv2-date-range__divider,
  .tpv2-date-range__last-n {
    width: 100%;
  }

  .tpv2-date-range__months {
    grid-template-columns: 1fr;
  }

  /* One month is enough on a phone screen; the nav arrows still page it. */
  .tpv2-date-range__months > div:nth-child(2) {
    display: none;
  }

  /* Keep the apply button in view while the panel body scrolls. */
  .tpv2-date-range__footer {
    position: sticky;
    bottom: 0;
    z-index: 1;
    background: var(--tp-surface, #fff);
    padding-bottom: var(--tp-space-2);
  }
}
/**
 * TP V2 — Detail Page
 *
 * The shell of a page about **one object**: a head that names it, a lens
 * strip, a main column in process order, and a right rail of master data that
 * does not move (plan 0057, W1).
 *
 * **The head is not a card** (plan 0057, E13). It lies on the page ground —
 * no plate, no border, no shadow — and it carries the way back as its first
 * item instead of a breadcrumb line above it: the list's mark and its name in
 * one link, a `›`, then the identifier (plan 0074).
 *
 * The parts already existed and had no composition rule between them:
 * `tpv2-identity-strip` brought its own plate, `tpv2-layout-rail-right` a
 * second one, and `tpv2-rail-card` a third padding on top of the slot's. This
 * file is only that rule — it invents no new surface and no new colour.
 *
 * **Every rule here that overrides another file carries `.tpv2-detail` as an
 * ancestor.** The catalog bundles with `cat v2/components/*.css`, so
 * alphabetically; `src/css/v2/main.css` bundles in a curated order. This file
 * comes *after* `identity-strip.css` and `layout.css` there and *before* them
 * in the `cat`. An equally specific rule would therefore win in one bundle and
 * lose in the other — same class, two results.
 *
 * Usage:
 *   <div class="tpv2-detail">
 *     <div class="tpv2-detail-head">
 *       <div class="tpv2-detail-title-row">
 *         <a class="tpv2-detail-back" href="/vorgaenge"><i …></i>Vorgänge</a>
 *         <span class="tpv2-detail-crumb">&rsaquo;</span>
 *         <div class="tpv2-identity-strip-item tpv2-detail-id">
 *           <span class="tpv2-detail-h1">TP-DE-241087</span>
 *           <span class="tpv2-pill tpv2-pill-gray tpv2-pill-sm">Bezahlt</span>
 *         </div>
 *         <div class="tpv2-detail-actions"><a class="tpv2-btn tpv2-btn-sm">…</a></div>
 *       </div>
 *       <p class="tpv2-detail-sum">3. September 2026 um 14:22 · …</p>
 *     </div>
 *     <div class="tpv2-detail-tabs"><nav class="tpv2-view-tabs">…</nav></div>
 *     <div class="tpv2-layout-body tpv2-layout-body-aside">
 *       <main class="tpv2-layout-main tpv2-detail-main">…</main>
 *       <aside class="tpv2-layout-rail-right tpv2-detail-rail">…</aside>
 *     </div>
 *   </div>
 *
 * The page sets two offsets, because how much chrome stands above the page is
 * a property of the app and not of the pattern:
 *   --tpv2-detail-anchor-top   how far an anchored section drops below it
 *   --tpv2-detail-rail-top     where the sticky rail comes to rest
 *
 * Modifiers:
 *   .tpv2-detail-head       the whole head — no plate, no border, no shadow
 *   .tpv2-detail-title-row  crumb glyph + identifier + pills + actions
 *   .tpv2-detail-back       the way back to the list — its mark and its name
 *   .tpv2-detail-crumb      the `›` between the two
 *   .tpv2-detail-h1         the identifier that is the page title
 *   .tpv2-detail-id         the strip item that carries it
 *   .tpv2-detail-actions    right edge of the title row
 *   .tpv2-detail-notes      warum eine Tat gesperrt ist (K-51)
 *   .tpv2-detail-sum        the second line, one grey sentence
 *   .tpv2-detail-pairs      a legacy identity strip under the sentence
 *   .tpv2-detail-rail       the right slot — grey ground with floating cards
 */

.tpv2-detail {
  display: flex;
  flex-direction: column;
  min-width: 0;
  --tpv2-rail-right-w: 336px;

  /* ===========================
     Dichte: die geräumige Sprosse, ohne einen zweiten Schalter
     ===========================
     Plan 0057, E9 §1 will 16–20 px Polsterung in der Karte; die Vorgabe ist
     12 px („compact"). Beides steht schon in tokens.css — die geräumige
     Sprosse ist ausdrücklich für „form and detail pages" gedacht. Die Seite
     nimmt sie deshalb hier, statt `data-density="comfortable"` an ein
     `<body>` zu hängen, das jede App selbst schreibt: eine Detailseite ist
     geräumig, weil sie eine Detailseite ist, nicht weil jemand daran gedacht
     hat.

     Die ZELLE bleibt kompakt. Eine Tabellenzeile und eine Faktenzeile mit
     12 px statt 8 px kosten auf einer Belegkette mit zwanzig Zeilen einen
     halben Bildschirm — und die Klage war Enge zwischen den Karten, nicht in
     ihnen. */
  --tp-density-pad-y: var(--tp-space-4);
  --tp-density-pad-x: var(--tp-space-4);
  --tp-density-gap: var(--tp-space-4);
  --tp-density-section-gap: var(--tp-space-5);

  /* Die Kante einer schwebenden Karte — einmal benannt, weil sie an vier
     Stellen steht und im dunklen Thema eine andere ist. Zwei gleich
     spezifische Regeln für dieselbe Farbe hätten sich je nach Bündel-
     Reihenfolge unterschiedlich entschieden. */
  --tpv2-detail-card-border: color-mix(in srgb, var(--tp-border, #e1e1e1) 55%, transparent);
}

/* Im dunklen Thema ist `--tp-elev-1` `none` — dort trägt der Rahmen allein,
   also wird er nicht zurückgenommen. */
[data-theme='dark'] .tpv2-detail {
  --tpv2-detail-card-border: var(--tp-border, #374151);
}

/* ===========================
   Die Karte schwebt (Plan 0057, E9 §1)
   =========================== */
/* Der Grund der Seite ist grau, die Karte weiß. Vorher stand Weiß auf Weiß,
   getrennt durch eine Haarlinie — und was sich nur durch eine Linie
   unterscheidet, liest sich als Tabelle und nicht als Sachverhalt.

   **Seit E13 gilt das Grau auch für den Kopf.** Die Ausnahme von W1 („der
   Kopfbereich behält seine weiße Platte") ist zurückgenommen: ein Kasten um
   den Titel macht ihn zu einem Abschnitt unter vielen, er ist aber die
   Überschrift der Seite. Der Grund steht deshalb am Wurzelelement; die Regel
   für den Rumpf bleibt, weil eine Seite die Hülle auch ohne Kopf einsetzen
   kann. */
.tpv2-detail {
  background-color: var(--tp-content-bg, #f4f5f7);
}

.tpv2-detail > .tpv2-layout-body {
  background-color: var(--tp-content-bg, #f4f5f7);
}

/* Der Rahmen weicht dem Schatten. Nicht beides: eine 1-px-Kante UND eine
   Erhebung sind zwei Aussagen über dieselbe Kante, und die Kante gewinnt
   immer. Die Sprosse bleibt Nummer 1 — es kommt kein dritter Schatten dazu
   (card.css). */
.tpv2-detail .tpv2-card {
  border-color: var(--tpv2-detail-card-border);
}

/* ===========================
   The identifier is the title
   =========================== */
/* A detail page needs no page header above the strip: the strip already says
   what the object is, and a second title would say it twice. The identifier is
   set in the mono face because it *is* an identifier — the same reason
   `Cell::Id` is mono in a table. Size 4, not 5: the display step is reserved
   for the one hero metric of a dashboard, and an order number is not one. */
.tpv2-detail-h1 {
  font-family: inherit;
  /* **20 px** (Abnahme 20.09.) — die Größe, in der Shopify eine
     Bestellnummer setzt. Vierundzwanzig waren eine Überschrift für eine
     Seite, die nur aus dem Kopf besteht; hier steht darunter eine ganze
     Akte, und der Titel muss sie nicht überstimmen, sondern benennen. */
  font-size: 20px;
  overflow-wrap: anywhere;
  font-weight: var(--tp-font-weight-semibold);
  line-height: var(--tp-line-tight);
  color: var(--tp-text, #202223);
  letter-spacing: -0.025em;
}

/* Das Element, das die Kennung trägt, ist kein Leistenpaar mehr, sondern der
   Titelblock der Zeile. Es legt deshalb ab, was `identity-strip.css` jedem
   Paar mitgibt: die seitliche Polsterung (sie riss ein 16-px-Loch zwischen
   `›` und Kennung) und den senkrechten Strich rechts — der stand hinter der
   letzten Pille, weil die Knopfgruppe daneben auch ein `div` ist und dem Item
   sein `:last-of-type` nahm.

   Die Paare stehen auf der Grundlinie, die Kennung ist höher als alle — also
   zentriert dieses Element, sonst hingen die Pillen an der Unterlänge. */
.tpv2-detail .tpv2-identity-strip-item.tpv2-detail-id {
  align-items: center;
  gap: var(--tp-space-2);
  padding: 0;
  border-right: none;
  min-width: 0;
  flex-wrap: wrap;
}

/* ===========================
   Der Kopf ist keine Karte (Plan 0057, E13 §1)
   =========================== */
/* Kein Grund, kein Rahmen, kein Schatten — der Kopf liegt AUF der Seite, wie
   bei Shopify. Was er von der alten Platte übernimmt, ist allein deren
   seitlicher Einzug, und zwar exakt den der Hauptspalte darunter
   (`--tp-density-pad-x`, layout.css): sobald die Platte weg ist, sieht man
   jeden Pixel Versatz zwischen Titel und erster Karte.

   Oben Luft in Sprossenhöhe, unten nur wenig: die Reiterzeile gehört noch zum
   Kopf, und ihre eigene Unterkante zieht die Grenze zum Inhalt. */
.tpv2-detail-head {
  /* **Eng** (Abnahme 20.09.). Der Kopf trug oben eine Sprossenhöhe (20 px)
     und unten zwölf; mit der Reiterzeile darunter waren das über fünfzig
     Pixel, bevor der erste Inhalt kam — auf einem Laptop ein Sechstel der
     Seite für drei Zeilen Text. Der Vergleich mit dem CockpitHub, an dem
     der Tisch seit Jahren arbeitet, war eindeutig: dort steht mehr auf
     dem Schirm, und deshalb liest es sich besser.

     Die Schriftgrößen bleiben, wo sie sind — Dichte ist Polsterung und
     Abstand, nie Schriftgrad (tokens.css, A3). */
  padding: var(--tp-space-2) var(--tp-density-pad-x) var(--tp-space-1);
  background-color: transparent;
  border: 0;
  box-shadow: none;
}

/* Erste Zeile: Rückweg, Kennung, Pillen — und rechts die Aktionen. Sie bricht
   um, statt seitlich zu scrollen: was seitlich hinausscrollt, sind genau die
   Knöpfe, und ein unerreichbarer Knopf ist keiner. */
.tpv2-detail-title-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--tp-space-2);
  min-width: 0;
}

/* Der Rückweg trägt seinen Namen sichtbar (Plan 0074). E13 §2 hatte ihn auf
   einen nackten Glyph zusammengezogen und den Namen der Liste in `title` und
   `aria-label` gelegt; das ist widerrufen, weil ein `title` einen Mauszeiger
   braucht, der verweilt — auf dem Tablet, mit dem diese Konsole auch bedient
   wird, gibt es ihn nicht.

   Was E13 §2 richtig gesehen hat, bleibt: es ist KEINE eigene Zeile über dem
   Kopf, die die Kennung klein und grau wiederholte. Die Krume steht IN der
   Titelzeile, vor derselben Kennung, die dort einmal groß steht.

   Die Höhe bleibt bei 32 px, also fingergroß; nur die feste Breite fällt, weil
   jetzt ein Wort darin steht. Die seitliche Polsterung ist genau die halbe
   alte Breite — damit beginnt das Zeichen an derselben Stelle wie vorher und
   der Kopf rückt gegenüber den Karten darunter nicht ein Stück nach rechts.

   Das Wort ist eine Stufe kleiner als der Fließtext: die Krume ist der Weg
   hierher und nicht der Gegenstand, und sie soll die Kennung neben sich nicht
   überstimmen. */
.tpv2-detail-back {
  display: inline-flex;
  align-items: center;
  gap: var(--tp-space-1-5);
  min-height: var(--tp-space-8);
  padding: 0 var(--tp-space-2);
  /* Auf einem schmalen Schirm bricht die Zeile um (`flex-wrap` oben), statt
     dass die Krume überläuft — und länger als die Zeile kann sie nicht
     werden, weil ein sehr langer Listenname dann mit Auslassung endet. */
  max-width: 100%;
  min-width: 0;
  flex-shrink: 0;
  border-radius: var(--tp-radius-md);
  color: var(--tp-text-secondary, #6d7175);
  text-decoration: none;
  font-size: var(--tp-font-size-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: background-color 0.12s, color 0.12s;
}

/* **Weiß wäre eine Karte** (Abnahme 20.09.). Der Grund dieser Seite ist
   grau, und eine weiße Fläche darauf heißt hier „Karte" — die Krume sah
   beim Überfahren aus, als klappte ein Kasten auf, während der Titel
   daneben schlichter Text bleibt. Ein Hauch dunkler statt heller: die
   Fläche sagt „anklickbar", ohne eine zweite Bauart zu behaupten. */
.tpv2-detail-back:hover {
  background-color: var(--tp-hover-bg, #f3f4f6);
  color: var(--tp-text, #202223);
}

.tpv2-detail-back:focus-visible {
  outline: 2px solid var(--tp-accent, #1d4b8e);
  outline-offset: 2px;
}

/* Das Trennzeichen zur Kennung. Es ist keine Krume mehr, sondern das Gelenk
   zwischen Liste und Ding — deshalb so leise wie eine Rahmenlinie. */
.tpv2-detail-crumb {
  width: var(--tp-space-4);
  flex-shrink: 0;
  text-align: center;
  color: color-mix(in srgb, var(--tp-text-secondary, #6d7175) 55%, transparent);
  font-size: var(--tp-font-size-4);
  line-height: 1;
  user-select: none;
}

/* Die Leiste, wenn eine Seite noch Beschriftung-Wert-Paare meldet: sie steht
   unter dem Satz, ohne Platte und ohne Unterkante — der Kopf hat keine mehr,
   die sie fortsetzen könnte. */
.tpv2-detail .tpv2-detail-pairs {
  flex-wrap: wrap;
  overflow-x: visible;
  row-gap: var(--tp-space-2);
  background-color: transparent;
  border-bottom: 0;
  padding: var(--tp-space-2) 0 0;
}

/* ===========================
   Actions at the right edge
   =========================== */
/* `tpv2-identity-strip-alert` is one button; a detail page carries a small
   group next to it. Same `margin-left:auto`, so alert and group can stand side
   by side without either of them winning the edge twice.

   Die Gruppe selbst bricht um (E13 §3): auf einem schmalen Schirm fallen die
   Knöpfe untereinander, statt aus der Zeile zu laufen. `flex-shrink: 0` gälte
   sonst für den ganzen Block und machte aus zwei Knöpfen einen waagerechten
   Überlauf. */
.tpv2-detail-actions {
  margin-left: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--tp-space-2);
  padding-left: var(--tp-space-4);
}

/* Stehen Alarm UND Knopfgruppe in der Zeile, hätten zwei `margin-left: auto`
   den freien Platz unter sich aufgeteilt — der Alarm landete in der Mitte der
   Zeile und sah aus, als gehörte er zur Kennung. Die Gruppe hängt sich
   deshalb an den Alarm, und der eine Rand ist der rechte. */
.tpv2-detail .tpv2-identity-strip-alert + .tpv2-detail-actions {
  margin-left: 0;
}

/* The alert in identity-strip.css is a `<button>`. On a reading page it leads
   somewhere instead of doing something — then it is a link, and it does not
   need the underline it already carries as a surface. */
.tpv2-detail a.tpv2-identity-strip-alert {
  text-decoration: none;
}

/* ===========================
   Die Reiterzeile unter dem Kopf
   =========================== */
/* Die Reiterzeile trägt NUR Reiter (E13 §4) und steht auf demselben Grund wie
   der Kopf darüber. In W1 bekam sie die weiße Platte der Kennungsleiste, damit
   sie nicht zwischen zwei Platten schwebte; ohne Platte über ihr ist genau das
   hinfällig, und `view-tabs.css` liefert die Ruhelage — durchsichtig, mit
   einer Unterkante, auf der der aktive Strich reitet. Der Umschlag trägt nur
   noch den seitlichen Einzug: die `<nav>` kommt aus `ViewTabs` und weiß nichts
   von Seiten. */
.tpv2-detail-tabs {
  background-color: transparent;
  padding: 0 var(--tp-density-pad-x);
}

/* **Der Kopf ist so eng wie sein Inhalt** (Abnahme 20.09.). Die Reiter
   dieser Seite stehen dichter als die einer Liste: dort trennen sie zwei
   Arbeitsvorräte, hier zwei Blicke auf dasselbe Ding, und zwischen Kennung
   und erster Karte soll nicht ein Drittel des Schirms liegen. */
.tpv2-detail-tabs .tpv2-view-tab {
  padding-top: var(--tp-space-1);
  padding-bottom: var(--tp-space-1);
}

/* ===========================
   Main column
   =========================== */
/* `padding-top` überschreibt das der Spalte aus `layout.css`: dieselbe
   Kraft (eine Klasse), aber diese Datei kommt später — kein
   `.tpv2-detail …` davor nötig, und die nächste Ausnahme fängt damit
   wieder bei einer Klasse an statt bei zweien. */
.tpv2-detail-main,
.tpv2-detail-rail {
  /* Der Inhalt beginnt gleich unter den Reitern statt eine Sprossenhöhe
     später. Nur auf dieser Seite: eine Liste trägt über ihrer ersten Zeile
     eine Werkzeugleiste, die den Abstand braucht. */
  padding-top: var(--tp-space-2);
}

.tpv2-detail-main {
  min-width: 0;
}

/* Shipments and split units are sections with an anchor, not pages of their
   own (plan 0057, E3). Every app that shows this page has fixed chrome above
   it, so a bare `#f-…` jump parks the section under the topbar. Inert until
   something is targeted. */
.tpv2-detail [id] {
  scroll-margin-top: var(--tpv2-detail-anchor-top, var(--tp-space-6));
}

/* ===========================
   Right rail — ONE surface, not cards on a plate
   =========================== */
/* **Die Regel aus W1 ist zurückgenommen, und das mit Grund.** Sie hieß „die
   Schiene ist EINE Fläche, nicht Karten auf einer Platte" und war richtig,
   solange die Hauptspalte auch weiß war: ein Rahmen im Rahmen bezahlt die
   Polsterung zweimal. Sie wurde falsch, als der Grund grau wurde — eine weiße
   Säule neben grauem Grund ist eine zweite Sprache auf einer Seite, und genau
   sie las sich als Datenblatt (E9 §1). Die Schiene bekommt deshalb denselben
   Grund und dieselben schwebenden Karten wie die Hauptspalte. */
.tpv2-detail .tpv2-detail-rail {
  background-color: transparent;
  border-left: none;
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x)
    var(--tp-density-pad-y) 0;
  align-self: start;
  position: sticky;
  top: var(--tpv2-detail-rail-top, 0);
  max-height: calc(100vh - var(--tpv2-detail-rail-top, 0px));
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Jeder Block der Schiene ist eine Karte wie in der Hauptspalte: weiße
   Fläche, runde Ecken, Sprosse 1, Luft dazwischen. */
.tpv2-detail-rail .tpv2-rail-card {
  background-color: var(--tp-surface, #fff);
  border: 1px solid var(--tpv2-detail-card-border);
  border-radius: var(--tp-radius-md);
  box-shadow: var(--tp-elev-1);
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
}

.tpv2-detail-rail .tpv2-rail-card + .tpv2-rail-card {
  margin-top: var(--tp-density-gap);
}

/* right-rail.css nimmt der letzten Karte den unteren Rahmen — sinnvoll, als
   die Karten eine Säule bildeten. Die Regel dort ist GENAUSO spezifisch wie
   die hier (`.tpv2-rail-card:last-child` zählt wie zwei Klassen), also
   entschiede die Bündel-Reihenfolge: im `cat` des Katalogs gewönne sie, in
   `main.css` diese. Eine Klasse mehr macht die Entscheidung eindeutig. */
.tpv2-detail-rail .tpv2-rail-card:last-child {
  border-bottom: 1px solid var(--tpv2-detail-card-border);
}

/* Der Alarm steht über den Karten und ist selbst eine — sonst klebte er als
   randlose Farbfläche am oberen Rand des grauen Grundes. */
.tpv2-detail-rail .tpv2-rail-alert {
  border: 1px solid color-mix(in srgb, currentColor 25%, transparent);
  border-radius: var(--tp-radius-md);
  margin-bottom: var(--tp-density-gap);
}

/* Contact values have a full line; long email addresses must not squeeze
   their label down to one syllable. */
.tpv2-detail-rail .tpv2-fact {
  flex-direction: column;
  align-items: stretch;
  gap: var(--tp-space-1);
}

.tpv2-detail-rail .tpv2-fact-v {
  text-align: left;
}

.tpv2-detail-rail .tpv2-fact-k {
  font-size: var(--tp-font-size-2);
}

.tpv2-detail-rail .tpv2-fact:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}

/* A ledger inside a rail card must not add the card's padding a second time. */
.tpv2-detail-rail .tpv2-facts {
  column-gap: var(--tp-space-3);
}

/* ===========================
   Tablet and phone
   =========================== */
/* Below lg the layout drops the rail under the main column (layout.css). A
   sticky element there would follow the reader up and down the footer, and a
   max-height would cut the addresses off. */
@media (max-width: 1023px) {
  .tpv2-detail .tpv2-detail-rail {
    position: static;
    max-height: none;
    overflow-y: visible;
    /* layout.css tauscht unterhalb von lg den linken Rahmen gegen einen
       oberen. Auf grauem Grund wäre das ein Strich quer durch nichts — die
       Karten trennen sich selbst. */
    border-top: none;
    padding: 0 var(--tp-density-pad-x) var(--tp-density-pad-y);
  }
}

@media (max-width: 767px) {
  /* Auf dem Telefon nimmt jedes Paar die ganze Zeile — dann trennen die
     senkrechten Striche nichts mehr. */
  .tpv2-detail .tpv2-identity-strip-item {
    padding-left: 0;
    padding-right: 0;
    border-right: none;
    flex-basis: 100%;
  }
  /* Der Titelblock bleibt neben dem Rückweg-Zeichen und bricht INNEN um: die
     Pillen fallen unter die Kennung. Ohne `flex: 1 1 0` wandert der ganze
     Block in eine neue Zeile, sobald Kennung plus Pillen breiter sind als der
     Rest der Zeile — und das Zeichen stünde allein in einer Zeile für sich. */
  .tpv2-detail .tpv2-identity-strip-item.tpv2-detail-id {
    flex-wrap: wrap;
    flex: 1 1 0;
    min-width: 0;
  }
  .tpv2-detail .tpv2-identity-strip-alert {
    margin-left: 0;
  }
  /* Die Knöpfe fallen unter die Kennung und stehen dort links, nicht rechts:
     eine rechtsbündige Knopfgruppe unter einem linksbündigen Titel wäre eine
     zweite Kante auf 390 px. Sie bleiben Knöpfe in ihrer Eigenbreite — ein
     Knopf über die volle Zeilenbreite ist auf einer lesenden Seite ein
     Formular-Absenden. */
  .tpv2-detail-actions {
    margin-left: 0;
    padding-left: 0;
    width: 100%;
  }
  /* Der Kopf nimmt den engeren Einzug der Seite. */
  .tpv2-detail-head {
    padding: var(--tp-density-gap) var(--tp-space-3) var(--tp-space-3);
  }
  .tpv2-detail-tabs {
    padding: 0 var(--tp-space-3);
  }
}

/* ===========================
   Der Kopf trägt zwei Zeilen (Plan 0057, E12 §1)
   =========================== */
/* Oben die Nummer und ihre Zustandspillen, darunter EIN Satz statt einer
   Faktenleiste: „3. September 2026 um 14:22 · Kanal shopify-tp-de · Elektro
   Brenner GmbH · zuletzt bewegt 05.09.2026 um 09:41". Als Beschriftung-Wert-
   Paare neben der Nummer war der Kopf eine Tabelle — drei
   Großbuchstaben-Etiketten, bevor die Seite anfängt.

   Seit E13 ist er eine eigene Zeile des Kopfes und kein umgebrochenes Kind
   einer Leiste mehr. Er beginnt an der Kante der Seite — auf derselben Linie
   wie Rückweg-Zeichen, Reiter und Karten (Plan 0065, Befund R-2): ein Einzug
   bis unter die Kennung gab dem Kopf eine zweite Kante, die keine Zeile
   darunter fortsetzt. */
.tpv2-detail-sum {
  margin: 0;
  /* Der Satz gehört zur Kennung darüber und nicht zu den Reitern darunter
     — er rückt entsprechend näher an sie heran (Abnahme 20.09.). */
  padding-top: var(--tp-space-1);
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  color: var(--tp-text-secondary, #6d7175);
  white-space: normal;
}

/* Warum eine Tat gerade nicht geht (Plan 0064 K-51): an derselben Kante wie
   der Satz darunter, damit die drei Zeilen des Kopfes eine Kante haben. Der
   Inhalt kommt aus der Anwendung (`tp-deed-locks`); hier steht nur, wo er
   hingehört. */
.tpv2-detail-notes {
  padding-top: var(--tp-space-2);
}

.tpv2-detail-sum .mono {
  font-family: var(--tp-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
}

/* Ein stiller Hinweis im Körper einer Aufklappzeile — der Kopftext eines
   Belegs (Abnahme 20.09.). Kein Alarm: blau hiesse „das musst du wissen",
   und die Widerrufsbelehrung auf jedem zweiten Beleg ist das nicht. Er
   liest sich wie Fliesstext, nur eine Stufe leiser, und behält seine
   Zeilenumbrüche — Sage setzt sie mit Absicht. */
.tpv2-detail .tpv2-note {
  margin: 0;
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  color: var(--tp-text-secondary, #6d7175);
  white-space: pre-line;
}

/* **Die Taten eines Belegs sind seine Werkzeugleiste** (Abnahme 20.09.).
   Aufgeklappt stand der eine Knopf „Beleg schließen" nackt auf dem grauen
   Grund des Körpers, über den weißen Kästen darunter — er las sich wie ein
   Rest und nicht wie die Leiste, die er ist. Eine Kante darunter macht ihn
   zu einer: dieselbe Trennung, die der CockpitHub unter seiner Knopfreihe
   zieht.

   Sie steht hier und nicht in `accordion.css`, weil nur die Detailseite
   Taten in einer Aufklappzeile hat. */
.tpv2-detail .tpv2-acc-b > .tp-deed-row {
  padding-bottom: var(--tp-density-gap);
  border-bottom: 1px solid var(--tp-border, #e1e1e1);
}

/* ===========================
   Ein Akkordeon in der Hauptspalte
   =========================== */
/* Die Belegakte ist eine Reihe von Karten, die sich öffnen (E11) — also
   schweben sie wie jede andere Karte dieser Seite, mit derselben Kante und
   derselben Sprosse. `accordion.css` kennt die Detailseite nicht und setzt
   die Rahmenfarbe des Bündels; hier bekommt sie die der schwebenden Karte.
   Mit `.tpv2-detail` als Vorfahre, weil diese Datei in beiden Bündeln nach
   `accordion.css` steht, aber nur mit einer Klasse mehr auch gewinnt. */
.tpv2-detail .tpv2-acc {
  border-color: var(--tpv2-detail-card-border);
  box-shadow: var(--tp-elev-1);
}

/* In einer Karte gilt das nicht: dort ist die Zeile eine Zeile (E7 §5), und
   ein Schatten je Zeile machte aus der Liste wieder eine Kartenwand. */
.tpv2-detail .tpv2-acc-list > .tpv2-acc {
  box-shadow: none;
}

/* Der Abstand zwischen den Belegen ist der Abstand zwischen Karten. */
.tpv2-detail .tpv2-acc + .tpv2-acc {
  margin-top: var(--tp-density-gap);
}

.tpv2-detail .tpv2-acc-list > .tpv2-acc + .tpv2-acc {
  margin-top: 0;
}

.tpv2-detail-more { position: relative; }
.tpv2-detail-more > summary { list-style: none; cursor: pointer; }
.tpv2-detail-more > summary::-webkit-details-marker { display: none; }
.tpv2-detail-more-panel {
  position: absolute; right: 0; top: calc(100% + var(--tp-space-2));
  z-index: 50; padding: var(--tp-space-2); width: max-content; max-width: 85vw;
  background: var(--tp-surface); border: 1px solid var(--tp-border);
  border-radius: var(--tp-radius-md); box-shadow: var(--tp-elev-2);
}

/* ===========================
   Mit Dock (Plan 0118, v2/dock.css)
   ===========================
   Das Dock selbst gehört dock.css; hier steht nur, wie die Detailseite
   Platz macht. */

/* Die Hauptspalte darf schrumpfen, sonst schiebt sie die Schiene aus dem
   Bild, sobald das Dock Platz nimmt. Nur, solange Hauptspalte und Schiene
   nebeneinander stehen — darunter gilt die einspaltige Regel aus
   layout.css, und diese hier darf sie nicht überstimmen. */
@media (min-width: 1024px) {
  .tpv2-detail:has(> .tpv2-dock) .tpv2-layout-body-aside {
    grid-template-columns: minmax(0, 1fr) var(--tpv2-rail-right-w, 300px);
  }
}

/* 1024–1439 px mit offenem Dock: Hauptspalte, Schiene und Fläche passen
   nicht nebeneinander — die Schiene rückt unter die Hauptspalte, ihre
   Karten nebeneinander. */
@media (min-width: 1024px) and (max-width: 1439px) {
  .tpv2-detail:has(> .tpv2-dock.is-open) .tpv2-layout-body-aside {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "main" "right";
  }

  .tpv2-detail:has(> .tpv2-dock.is-open) .tpv2-detail-rail {
    position: static;
    max-height: none;
    overflow: visible;
    padding-left: var(--tp-density-pad-x);
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
    gap: var(--tp-density-gap);
    align-items: start;
  }

  .tpv2-detail:has(> .tpv2-dock.is-open) .tpv2-detail-rail .tpv2-rail-card {
    margin: 0;
  }
}
/**
 * Dock — die Seitenleiste am rechten Bildschirmrand (Plan 0118)
 *
 * Eine Symbolleiste (wie VS Code, Slack) mit n Einträgen; je Eintrag eine
 * Fläche, die animiert herausgleitet. Zu bleibt nur die Leiste. Die Seite
 * rückt mit, nichts liegt über ihr — ab 1024 px. Darunter legt sich die
 * Fläche über die Seite, unter 768 px wird sie ein Vollbild-Blatt und die
 * Leiste eine schwebende Knopfreihe unten rechts.
 *
 * Markup (DockItem/Dock in shopcloud-ui schreiben es):
 *
 *   <div class="… tpv2-dock-host">                    Seite, die mitrückt
 *     …Seiteninhalt…
 *     <aside class="tpv2-dock is-open" data-dock-scope="orders">
 *       <nav class="tpv2-dock-bar" role="tablist" aria-orientation="vertical">
 *         <button class="tpv2-dock-tab" role="tab" aria-selected="true" …>
 *           <i class="fas fa-…"></i>
 *           <span class="tpv2-dock-count" id="dock-count-{key}">6</span>
 *           <span class="tpv2-dock-tab-l">Chronik</span>
 *         </button>
 *       </nav>
 *       <div class="tpv2-dock-sheet">
 *         <section class="tpv2-dock-pane" role="tabpanel" …>
 *           <header class="tpv2-dock-pane-h">…</header>
 *           <div class="tpv2-dock-pane-b">…</div>
 *         </section>
 *       </div>
 *     </aside>
 *   </div>
 *
 * Tokens:
 *   --tpv2-chrome-top   wo die Leisten der Hülle enden (setzt die Konsole)
 *   --tpv2-dock-bar-w   Breite der Symbolleiste
 *   --tpv2-dock-pane-w  Breite der Fläche
 *   --tpv2-dock-speed   Dauer der Bewegung (0 bei prefers-reduced-motion)
 *
 * Abschnitte in einer Fläche: .tpv2-dock-part (+ -h, -muted) — flach, die
 * Fläche ist schon die Platte.
 */

.tpv2-dock-host {
  --tpv2-dock-bar-w: 44px;
  --tpv2-dock-pane-w: 400px;
  --tpv2-dock-speed: 220ms;
}

@media (prefers-reduced-motion: reduce) {
  .tpv2-dock-host {
    --tpv2-dock-speed: 0ms;
  }
}

/* ===========================
   ≥ 1024 px: die Leiste schiebt die Seite
   =========================== */
.tpv2-dock-host:has(> .tpv2-dock) {
  margin-right: var(--tpv2-dock-bar-w);
  transition: margin-right var(--tpv2-dock-speed) ease;
}

.tpv2-dock-host:has(> .tpv2-dock.is-open) {
  margin-right: calc(var(--tpv2-dock-bar-w) + var(--tpv2-dock-pane-w));
}

.tpv2-dock {
  position: fixed;
  top: var(--tpv2-chrome-top, 0px);
  right: 0;
  bottom: 0;
  z-index: 30;
  display: flex;
  flex-direction: row-reverse;
  background-color: var(--tp-sidebar-bg);
  border-left: 1px solid var(--tp-border);
}

/* Die Symbolleiste steht am äußersten Rand und bewegt sich nie — die
   Symbole springen beim Öffnen nicht. */
.tpv2-dock-bar {
  flex: 0 0 var(--tpv2-dock-bar-w);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--tp-space-1);
  padding: var(--tp-space-2) 0;
}

.tpv2-dock.is-open .tpv2-dock-bar {
  border-left: 1px solid var(--tp-border);
}

.tpv2-dock-tab {
  all: unset;
  box-sizing: border-box;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--tp-space-1);
  padding: var(--tp-space-2) 0;
  cursor: pointer;
  color: var(--tp-text-secondary);
  border-left: 2px solid transparent;
}

.tpv2-dock-tab:hover {
  background-color: var(--tp-hover-bg);
  color: var(--tp-text);
}

.tpv2-dock-tab:focus-visible {
  outline: 2px solid var(--tp-accent);
  outline-offset: -2px;
}

.tpv2-dock-tab[aria-selected="true"] {
  color: var(--tp-accent);
  border-left-color: var(--tp-accent);
}

.tpv2-dock-tab > i {
  font-size: var(--tp-font-size-4);
}

/* Zu: die Beschriftung steht senkrecht unter dem Symbol. Offen genügt das
   Symbol — die Fläche nennt ihren Titel selbst. */
.tpv2-dock-tab-l {
  writing-mode: vertical-rl;
  font-size: var(--tp-font-size-1);
  font-weight: var(--tp-font-weight-medium);
  letter-spacing: 0.02em;
}

.tpv2-dock.is-open .tpv2-dock-tab-l {
  display: none;
}

.tpv2-dock-count {
  min-width: 1.25rem;
  padding: 0 var(--tp-space-1);
  border-radius: 999px;
  background-color: var(--tp-accent);
  color: var(--tp-surface);
  font-size: var(--tp-font-size-1);
  font-weight: var(--tp-font-weight-semibold);
  line-height: 1.25rem;
  text-align: center;
}

.tpv2-dock-count:empty {
  display: none;
}

/* Die Fläche gleitet: ihre Breite wächst, ihr Inhalt behält seine — er
   wird beim Gleiten beschnitten, nicht bei jedem Pixel neu umgebrochen. */
.tpv2-dock-sheet {
  flex: 0 0 auto;
  width: 0;
  overflow: hidden;
  transition: width var(--tpv2-dock-speed) ease;
}

.tpv2-dock.is-open .tpv2-dock-sheet {
  width: var(--tpv2-dock-pane-w);
}

.tpv2-dock-pane {
  width: var(--tpv2-dock-pane-w);
  height: 100%;
  display: flex;
  flex-direction: column;
}

.tpv2-dock-pane[hidden] {
  display: none;
}

.tpv2-dock-pane-h {
  display: flex;
  align-items: center;
  gap: var(--tp-space-2);
  height: 3rem;
  flex-shrink: 0;
  padding: 0 var(--tp-space-2) 0 var(--tp-density-pad-x);
  border-bottom: 1px solid var(--tp-border);
}

.tpv2-dock-pane-h h2 {
  flex: 1;
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--tp-space-2);
  font-size: var(--tp-font-size-3);
  font-weight: var(--tp-font-weight-semibold);
  color: var(--tp-text);
}

.tpv2-dock-pane-h h2 > i {
  color: var(--tp-text-secondary);
}

/* Zurück gibt es nur auf dem Blatt (unter 768 px). */
.tpv2-dock-back {
  display: none;
}

.tpv2-dock-pane-b {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.tpv2-dock .tpv2-tl-glyph {
  box-shadow: 0 0 0 3px var(--tp-sidebar-bg);
}

/* Abschnitte einer Fläche. */
.tpv2-dock-part + .tpv2-dock-part {
  border-top: 1px solid var(--tp-border);
}

.tpv2-dock-part-h {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tp-space-2);
  padding: var(--tp-space-3) var(--tp-density-pad-x) var(--tp-space-1);
  font-size: var(--tp-font-size-1);
  font-weight: var(--tp-font-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--tp-text-secondary);
}

.tpv2-dock-part-muted .tpv2-tl-b,
.tpv2-dock-part-muted .tpv2-tl-glyph {
  opacity: 0.72;
}

/* ===========================
   1024–1439 px: schmalere Fläche
   =========================== */
@media (max-width: 1439px) {
  .tpv2-dock-host {
    --tpv2-dock-pane-w: 380px;
  }
}

/* ===========================
   768–1023 px: die Fläche legt sich über die Seite
   =========================== */
@media (max-width: 1023px) {
  .tpv2-dock-host:has(> .tpv2-dock.is-open) {
    margin-right: var(--tpv2-dock-bar-w);
  }

  .tpv2-dock.is-open {
    box-shadow: var(--tp-elev-3, 0 8px 24px rgba(0, 0, 0, 0.4));
  }

  .tpv2-dock.is-open .tpv2-dock-sheet {
    width: min(var(--tpv2-dock-pane-w), calc(100vw - var(--tpv2-dock-bar-w)));
  }
}

/* ===========================
   < 768 px: Knopfreihe unten rechts, Fläche als Vollbild-Blatt
   =========================== */
@media (max-width: 767px) {
  .tpv2-dock-host:has(> .tpv2-dock),
  .tpv2-dock-host:has(> .tpv2-dock.is-open) {
    margin-right: 0;
  }

  .tpv2-dock {
    top: auto;
    left: auto;
    bottom: var(--tp-space-4);
    right: var(--tp-space-4);
    border: 1px solid var(--tp-border);
    border-radius: 999px;
    box-shadow: var(--tp-elev-3, 0 8px 24px rgba(0, 0, 0, 0.4));
  }

  .tpv2-dock-bar {
    flex-direction: row;
    flex-basis: auto;
    padding: var(--tp-space-1);
  }

  .tpv2-dock.is-open .tpv2-dock-bar {
    border-left: 0;
  }

  .tpv2-dock-tab {
    flex-direction: row;
    padding: var(--tp-space-2) var(--tp-space-3);
    border-left: 0;
    border-radius: 999px;
  }

  .tpv2-dock-tab-l {
    display: none;
  }

  /* Das Blatt steht über allem und kommt von unten. */
  .tpv2-dock-sheet {
    position: fixed;
    inset: var(--tpv2-chrome-top, 0px) 0 0 0;
    width: auto;
    background-color: var(--tp-sidebar-bg);
    transform: translateY(100%);
    visibility: hidden;
    transition:
      transform var(--tpv2-dock-speed) ease,
      visibility 0s linear var(--tpv2-dock-speed);
  }

  .tpv2-dock.is-open .tpv2-dock-sheet {
    width: auto;
    transform: none;
    visibility: visible;
    transition: transform var(--tpv2-dock-speed) ease;
  }

  .tpv2-dock-pane {
    width: 100%;
  }

  .tpv2-dock-back {
    display: inline-flex;
  }
}
/* Dropzone — die Fläche, auf die Dateien gezogen werden, und das
 * Vorschaubild, das dabei entsteht (Plan 0065, B-3, E14).
 *
 * Beides in einer Datei, weil es dieselbe Sache ist: „ein Bild annehmen"
 * und „ein Bild zeigen". Der Beleg, der hier entsteht, steht eine Karte
 * weiter als `.tpv2-thumb` in der Faktenliste — zwei Dateien wären zwei
 * Orte für dieselbe Größe, denselben Rahmen und denselben Klick.
 *
 * Keine neue Farbe und keine neue Radiusstufe: alles aus tokens.css.
 */
.tpv2-dropzone {
  display: flex;
  flex-direction: column;
  gap: var(--tp-space-2);
}

/* Die Fläche selbst. Gestrichelt, weil sie eine Aufforderung ist und kein
 * Feld: ein durchgezogener Rahmen sähe aus wie ein Eingabefeld, in das
 * man tippt. */
.tpv2-dropzone-face {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--tp-space-1);
  padding: var(--tp-space-5) var(--tp-space-4);
  border: 1px dashed var(--tp-border);
  border-radius: var(--tp-radius-md);
  background: var(--tp-surface-subtle, var(--tp-surface));
  color: var(--tp-text-secondary);
  text-align: center;
  cursor: pointer;
}

.tpv2-dropzone-face:hover {
  border-color: var(--tp-accent);
  color: var(--tp-text-primary);
}

/* Über der Fläche, solange eine Datei darüber schwebt — die einzige
 * Rückmeldung, die ein Mensch beim Ziehen bekommt. */
.tpv2-dropzone.is-over .tpv2-dropzone-face {
  border-color: var(--tp-accent);
  border-style: solid;
  background: var(--tp-surface);
}

/* Während der Upload läuft: gedämpft und nicht gesperrt — wer ein zweites
 * Bild danebenlegen will, soll nicht warten müssen. */
.tpv2-dropzone.is-busy .tpv2-dropzone-face {
  opacity: 0.6;
}

.tpv2-dropzone-icon {
  font-size: var(--tp-font-size-5);
  color: var(--tp-text-secondary);
}

.tpv2-dropzone-prompt {
  font-size: var(--tp-font-size-2);
  font-weight: var(--tp-font-weight-medium);
  color: var(--tp-text-primary);
}

.tpv2-dropzone-limit,
.tpv2-dropzone-hint {
  font-size: var(--tp-font-size-1);
  color: var(--tp-text-secondary);
}

.tpv2-dropzone-hint {
  margin: 0;
}

/* Der Dateiwähler bleibt im Markup — er ist es, den die Beschriftung
 * anfasst —, aber er wird nicht gezeigt: zwei Bedienelemente für dieselbe
 * Sache nebeneinander sind eine Frage zu viel. `display: none` wäre für
 * die Tastatur unerreichbar. */
.tpv2-dropzone-pick {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.tpv2-dropzone-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--tp-space-2);
}

.tpv2-dropzone-item {
  position: relative;
}

/* Der Knopf zum Entfernen sitzt AM Bild und nicht darunter: eine Reihe
 * von Bildern mit einer Reihe von Knöpfen darunter lässt offen, welcher
 * zu welchem gehört. */
.tpv2-dropzone-drop {
  position: absolute;
  top: calc(-1 * var(--tp-space-1));
  right: calc(-1 * var(--tp-space-1));
  width: 1.25rem;
  height: 1.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--tp-border);
  border-radius: 50%;
  background: var(--tp-surface);
  color: var(--tp-text-secondary);
  font-size: var(--tp-font-size-1);
  line-height: 1;
  cursor: pointer;
}

.tpv2-dropzone-drop:hover {
  color: var(--tp-text-primary);
  border-color: var(--tp-accent);
}

/* Die Absage der Gegenseite — an der Fläche und nicht in der Konsole des
 * Browsers. */
.tpv2-dropzone-say:empty {
  display: none;
}

.tpv2-dropzone-say {
  margin: 0;
  font-size: var(--tp-font-size-1);
  color: var(--tp-text-warning, var(--tp-text-primary));
}

/* Ein gescheiterter Upload hält das Formular an (Plan 0103, E) — und
 * sagt es rot, am Feld. */
.tpv2-dropzone.is-failed .tpv2-dropzone-say {
  color: var(--tp-danger);
}

.tpv2-dropzone-skip {
  border: 0;
  padding: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}

/* Ein Beleg — in der Dropzone und in jeder Faktenkarte derselbe. */
.tpv2-thumb {
  display: block;
  width: 4.5rem;
  height: 4.5rem;
  border: 1px solid var(--tp-border);
  border-radius: var(--tp-radius-sm);
  overflow: hidden;
  background: var(--tp-surface);
}

.tpv2-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.tpv2-thumb:hover {
  border-color: var(--tp-accent);
}
/**
 * TP V2 — Empty State
 * Placeholder for a list, table or card body with zero rows. Designed, not
 * white: icon medallion, one sentence, one next step.
 *
 * Usage:
 *   <div class="tpv2-empty">
 *     <div class="tpv2-empty-inner">
 *       <div class="tpv2-empty-icon"><i class="fas fa-inbox"></i></div>
 *       <h3 class="tpv2-empty-title">Keine Aufträge im Zeitraum</h3>
 *       <p class="tpv2-empty-desc">Wähle einen längeren Zeitraum.</p>
 *       <div class="tpv2-empty-action"><button class="tpv2-btn">…</button></div>
 *     </div>
 *   </div>
 *
 * Modifiers:
 *   .tpv2-empty-flat    — no border/background (inside a card body)
 *   .tpv2-empty-inline  — short version for a small tile, no min-height
 */

.tpv2-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 220px;
  /* Twice the standard padding — roomy, but still follows the density switch. */
  padding: calc(var(--tp-density-pad-y) * 2) var(--tp-density-pad-x);
  border: 1px solid var(--tp-border, #e1e1e1);
  border-radius: var(--tp-radius-md);
  background-color: var(--tp-surface, #fff);
  font-family: inherit;
}

.tpv2-empty-inner {
  text-align: center;
  max-width: 320px;
}

.tpv2-empty-icon {
  width: 44px;
  height: 44px;
  margin: 0 auto var(--tp-space-3);
  border-radius: var(--tp-radius-pill);
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--tp-hover-bg, #f3f4f6);
  color: var(--tp-text-secondary, #6d7175);
  font-size: var(--tp-font-size-4);
}

.tpv2-empty-title {
  margin: 0 0 var(--tp-space-1-5);
  font-size: var(--tp-font-size-3);
  font-weight: var(--tp-font-weight-semibold);
  line-height: var(--tp-line-tight);
  color: var(--tp-text, #202223);
}

.tpv2-empty-desc {
  margin: 0 0 var(--tp-space-3);
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-base);
  color: var(--tp-text-secondary, #6d7175);
}

.tpv2-empty-desc:last-child {
  margin-bottom: 0;
}

.tpv2-empty-action {
  display: inline-flex;
  justify-content: center;
  gap: var(--tp-space-2);
}

/* Inside a card body the card already draws the frame. */
.tpv2-empty-flat {
  border: none;
  background: transparent;
  padding: calc(var(--tp-density-pad-y) * 1.5) var(--tp-density-pad-x);
}

.tpv2-empty-inline {
  min-height: 0;
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
}

.tpv2-empty-inline .tpv2-empty-icon {
  width: 32px;
  height: 32px;
  font-size: var(--tp-font-size-3);
  margin-bottom: var(--tp-space-2);
}
/**
 * TP V2 — Fact Grid
 * The master-data block of a detail page: many label/value pairs about *one*
 * object, laid out as a ledger in one to three columns.
 *
 * A dashboard answers "how are we doing" with few big figures; a detail page
 * answers "what is this" with many small ones. The KPI tile is the wrong tool
 * there — twenty tiles are twenty frames — and the data table is the wrong
 * tool too, because there is only one row (plan 0010, D-001c).
 *
 * Usage:
 *   <dl class="tpv2-facts tpv2-facts-3">
 *     <div class="tpv2-fact">
 *       <dt class="tpv2-fact-k">Artikelnummer</dt>
 *       <dd class="tpv2-fact-v"><span class="mono">10087339</span></dd>
 *     </div>
 *     <div class="tpv2-fact tpv2-fact-wide">
 *       <dt class="tpv2-fact-k">Bezeichnung</dt>
 *       <dd class="tpv2-fact-v">TP Vorratsglas mit Bambus Deckel …</dd>
 *     </div>
 *     <div class="tpv2-fact">
 *       <dt class="tpv2-fact-k">Hersteller</dt>
 *       <dd class="tpv2-fact-v tpv2-fact-none">
 *         — <span class="tpv2-fact-src">SageHub GetArticleCoreData</span>
 *       </dd>
 *     </div>
 *   </dl>
 *
 * Modifiers:
 *   .tpv2-facts-2 / -3   column count (collapses to one below 768 px)
 *   .tpv2-fact-wide      pair spans the full width, value under the label
 *   .tpv2-fact-none      the source has no value for this field
 *   .tpv2-fact-empty     same pair, one step quieter (on the row, not the value)
 */

/* The frame decides how many columns fit, not the viewport: a ledger in a
   320-px rail on a 1440-px desk breaks exactly the way it breaks on a phone,
   and a media query cannot see the difference (plan 0010, D-001c). */
.tpv2-facts-wrap {
  container-type: inline-size;
  container-name: tpfacts;
  min-width: 0;
}

.tpv2-facts {
  display: grid;
  grid-template-columns: 1fr;
  /* No row gap on purpose: the rule under each pair does the separating, and a
     gap on top of it turns a ledger into a list of floating pairs. */
  row-gap: 0;
  column-gap: var(--tp-space-8);
  margin: 0;
  font-size: var(--tp-font-size-3);
  line-height: var(--tp-line-snug);
  color: var(--tp-text, #202223);
  min-width: 0;
}

/* Mobile first: one column until the frame can pay for a second. */
@container tpfacts (min-width: 520px) {
  .tpv2-facts-2,
  .tpv2-facts-3 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@container tpfacts (min-width: 780px) {
  .tpv2-facts-3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.tpv2-fact {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--tp-space-4);
  padding: var(--tp-density-cell-y) 0;
  border-bottom: 1px solid var(--tp-border, #e1e1e1);
  min-width: 0;
}

.tpv2-fact-k {
  color: var(--tp-text-secondary, #6d7175);
  font-weight: var(--tp-font-weight-normal);
  /* The label is the anchor of the row and must stay on one line; the value
     side is the one that may wrap. */
  flex: 0 1 auto;
  min-width: 0;
}

.tpv2-fact-v {
  margin: 0;
  color: var(--tp-text, #202223);
  font-weight: var(--tp-font-weight-medium);
  font-variant-numeric: var(--tp-numeric);
  text-align: right;
  min-width: 0;
  overflow-wrap: anywhere;
}

.tpv2-fact-v .mono {
  font-family: var(--tp-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: var(--tp-font-size-2);
}

.tpv2-fact-v a {
  color: var(--tp-accent, #1d4b8e);
  text-decoration: none;
}

.tpv2-fact-v a:hover {
  text-decoration: underline;
}

/* ===========================
   Wide pair — long prose, an address, a description
   =========================== */
.tpv2-fact-wide {
  grid-column: 1 / -1;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--tp-space-0-5);
}

.tpv2-fact-wide .tpv2-fact-v {
  text-align: left;
  font-weight: var(--tp-font-weight-normal);
  line-height: var(--tp-line-base);
}

/* ===========================
   No value — and where one would come from
   =========================== */
/* An empty field on a detail page is information: it says the source does not
   answer it. Rendered as a bare em dash it is indistinguishable from a value
   of zero, so the origin is named next to it (plan 0010, D-001c). */
.tpv2-fact-none {
  color: var(--tp-text-secondary, #6d7175);
  font-weight: var(--tp-font-weight-normal);
}

/* Abwesenheit ist leiser als Anwesenheit (Plan 0057, E7 §6).
   Die erste Fassung der Vorgangsseite hatte drei „— kein Feld am Vorgang“ in
   einer Karte, jedes als Satz in derselben Größe und Farbe wie die
   Beschriftung daneben — die Seite sah kaputt aus statt ehrlich. Die Quelle
   steht deshalb im **Monospace-Register**: ein Feldpfad wie
   `Fulfillment.deliveredAt` liest sich damit als technische Randnotiz und
   nicht als Entschuldigung, und ein Aufrufer, der einen Satz hineinschreibt,
   sieht sofort, dass er im falschen Register schreibt.
   Der Kontrast bleibt, wo er war: leiser heißt hier anderes Register und
   kleinerer Grad, nicht ausgegraut. */
.tpv2-fact-src {
  display: inline-block;
  margin-left: var(--tp-space-2);
  font-family: var(--tp-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: var(--tp-font-size-1);
  color: var(--tp-text-secondary, #6d7175);
  /* Eine Quelle wie `GetArticleCoreData` hat keine Leerzeichen und bleibt
     dadurch von selbst auf einer Zeile. Eine wie
     `Fulfillment.deliveredAt — keine Zustellmeldung` hat welche und muss
     umbrechen dürfen: mit `nowrap` lief sie in einer breiten Karte über den
     Kartenrand hinaus und wurde abgeschnitten (Plan 0057, W1). */
  max-width: 100%;
}

/* Die ganze Zeile ist leiser, nicht nur ihr Wert: der Gedankenstrich verliert
   das Mittelgewicht, das eine echte Angabe trägt, und die Zeile fällt beim
   Überfliegen aus dem Raster heraus. Die Beschriftung behält ihre Farbe — was
   FEHLT, muss lesbar bleiben, sonst ist die Ehrlichkeit dekorativ. */
.tpv2-fact-empty .tpv2-fact-v {
  font-weight: var(--tp-font-weight-normal);
}

/* ===========================
   Anschriftenblock — mehrere Zeilen ohne Beschriftung
   =========================== */
/* Ein Umschlag trägt keine Feldnamen. `Name | Straße | Hausnummer | Ort |
   Land` als fünf Beschriftung-Wert-Paare ist die Datenbank, nicht die
   Anschrift (Plan 0057, E9 §4) — und eine Schiene voller solcher Paare liest
   sich als Datenblatt.

   Bewusst allgemein gehalten: was hier steht, ist „mehrzeiliger Text aus
   Feldern". Eine Anschriftform müsste wissen, was eine Straße ist, und die
   nächste Karte mit einer Bankverbindung bekäme ihre eigene. */
/* **Eine Anschrift hat keine Lücken zwischen ihren Zeilen** (Abnahme
   20.09.). Der Kasten erbte die Sprossenweite der Karte (12 px) als
   `gap` und stand damit zwischen Name, Straße, Ort und Land je zwölf
   Pixel auseinander — vier Zeilen, die zusammengehören, auf anderthalb
   Kastenhöhen verteilt. Was sie trennt, ist die Zeilenhöhe; sie brauchen
   keinen Abstand darüber hinaus, so wenig wie auf einem Umschlag. */
.tpv2-fact-lines {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: var(--tp-density-cell-y) 0;
  border-bottom: 1px solid var(--tp-border, #e1e1e1);
  min-width: 0;
}

/* Steht der Anschriftenblock allein in seinem Kasten, entfällt die Linie
   darunter: sie liefe parallel zur Kante des Kastens und trennte nichts mehr.
   Folgt ein Paar (die E-Mail in der Schiene), bleibt sie (Plan 0057, E12 §3). */
.tpv2-fact-lines:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

/* **Eine Anschrift steht eng** (Abnahme 20.09.). Sie ist EIN Block und
   keine Liste von Angaben: mit dem normalen Zeilenmaß standen Name,
   Straße, Ort und Land so weit auseinander, dass der Kasten doppelt so
   hoch war wie das, was drinsteht — auf einem Umschlag stehen sie
   untereinander, nicht übereinander verteilt. */
.tpv2-fact-line {
  line-height: var(--tp-line-snug);
  overflow-wrap: anywhere;
}

/* Die erste Zeile ist die, nach der gesucht wird — der Name auf dem
   Umschlag. */
.tpv2-fact-line:first-child {
  font-weight: var(--tp-font-weight-medium);
}

/* ===========================
   Die eine Zeile, auf die es ankommt
   =========================== */
/* Ein Kontoauszug hat genau eine Summe. Sie steht nicht in einer eigenen
   Karte und nicht in einer Fußzeile, sondern in derselben Spalte wie die
   Posten, die sie bildet — nur schwerer (Plan 0057, E9 §5). */
.tpv2-fact-strong .tpv2-fact-k {
  color: var(--tp-text, #202223);
  font-weight: var(--tp-font-weight-medium);
}

.tpv2-fact-strong .tpv2-fact-v {
  font-weight: var(--tp-font-weight-semibold);
  font-size: var(--tp-font-size-4);
}

/* Kein zweiter Strich: die Zeile darüber trägt schon eine Haarlinie, und
   zwei davon nebeneinander sehen aus wie ein Rendering-Fehler. Was die Summe
   heraushebt, ist ihr Gewicht und ihr Grad. */
.tpv2-fact-strong {
  padding-top: var(--tp-space-3);
}

/* ===========================
   Group heading inside a grid of facts
   =========================== */
/* A group breaks the column flow: its heading owns a full row, and the pairs
   after it start a fresh set of columns. */
.tpv2-facts-group {
  grid-column: 1 / -1;
  margin-top: var(--tp-density-pad-y);
}

.tpv2-facts-group:first-child {
  margin-top: 0;
}

/* The group heading owns a full row, so the section header's usual
   space-between would fling its note to the far edge of a three-column ledger,
   a screen away from the heading it belongs to. */
.tpv2-facts-group .tpv2-section-h {
  justify-content: flex-start;
  gap: var(--tp-space-3);
  margin-bottom: 0;
}

/* ===========================
   Loading — the shape of a ledger, not a grey block
   =========================== */
.tpv2-fact-sk {
  display: block;
  width: 100%;
}

/* ===========================
   Narrow frame
   =========================== */
/* Label and value side by side survive one column; what does not survive is a
   long label meeting a long source name. In a narrow frame the source wraps
   instead of pushing the label out of its own row. */
@container tpfacts (max-width: 519px) {
  .tpv2-fact {
    gap: var(--tp-space-3);
  }
  .tpv2-fact-src {
    margin-left: 0;
  }
}
/**
 * TP V2 — Feed
 * Dated entries in one column, newest first: release notes, an activity log,
 * an audit trail. Each entry is a headline you can open, with the where, the
 * what kind and the when above it.
 *
 * A table would give the headline a column width it cannot live in; a tile
 * grid loses the order, which is the whole point of a feed (plan 0010, D-008).
 *
 * Usage:
 *   <ol class="tpv2-feed">
 *     <li class="tpv2-feed-item">
 *       <div class="tpv2-feed-meta">
 *         <span>warehousebrain</span>
 *         <span class="tpv2-pill tpv2-pill-success">Feature</span>
 *         <time class="tpv2-feed-time">30.05.2026</time>
 *       </div>
 *       <a class="tpv2-feed-title" href="…">Eingang gegen Ausgang</a>
 *       <p class="tpv2-feed-ex">Die aus dem alten CockpitHub bekannte …</p>
 *     </li>
 *   </ol>
 *
 * Modifiers:
 *   .tpv2-feed-sep    dated divider ("Älter als 3 Tage")
 *   .tpv2-feed-sk     placeholder entry while loading
 */

/* Same rule as the fact grid: the meta line breaks against the width of the
   card it sits in, not the width of the window (plan 0010, D-001c). */
.tpv2-feed-wrap {
  container-type: inline-size;
  container-name: tpfeed;
  min-width: 0;
}

.tpv2-feed {
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: 0;
}

.tpv2-feed-item {
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
  border-bottom: 1px solid var(--tp-border, #e1e1e1);
  min-width: 0;
}

.tpv2-feed-item:last-child {
  border-bottom: 0;
}

.tpv2-feed-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--tp-space-2);
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  color: var(--tp-text-secondary, #6d7175);
  margin-bottom: var(--tp-space-1-5);
}

.tpv2-feed-time {
  margin-left: auto;
  white-space: nowrap;
  font-variant-numeric: var(--tp-numeric);
}

.tpv2-feed-title {
  display: block;
  font-size: var(--tp-font-size-3);
  font-weight: var(--tp-font-weight-medium);
  line-height: var(--tp-line-snug);
  color: var(--tp-text, #202223);
  text-decoration: none;
}

.tpv2-feed-title:hover {
  color: var(--tp-accent, #1d4b8e);
  text-decoration: underline;
}

.tpv2-feed-title:focus-visible {
  outline: 2px solid var(--tp-accent, #1d4b8e);
  outline-offset: 2px;
}

/* Two lines of the body text, cut on a line, not mid-word. The full entry is
   one click away — the excerpt only has to say whether the click is worth it. */
.tpv2-feed-ex {
  margin: var(--tp-space-1) 0 0;
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-base);
  color: var(--tp-text-secondary, #6d7175);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ===========================
   Divider — why the entries below are old
   =========================== */
/* The page this replaces faded old entries to 50 % opacity: it made the text
   fail contrast in both themes and still never said *why* they were faded. A
   dated divider says it once, and every entry stays readable. */
.tpv2-feed-sep {
  display: flex;
  align-items: center;
  gap: var(--tp-space-2);
  padding: var(--tp-space-2) var(--tp-density-pad-x);
  border-bottom: 1px solid var(--tp-border, #e1e1e1);
  background-color: var(--tp-table-header-bg, #f6f8fa);
  font-size: var(--tp-font-size-1);
  font-weight: var(--tp-font-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--tp-text-secondary, #6d7175);
}

/* ===========================
   Loading
   =========================== */
.tpv2-feed-sk {
  display: flex;
  flex-direction: column;
  gap: var(--tp-space-2);
}

/* ===========================
   Narrow frame
   =========================== */
/* Source, kind and date on one line need about 300 px. Below that the date
   drops onto its own line instead of squeezing the pill out of the row. */
@container tpfeed (max-width: 340px) {
  .tpv2-feed-time {
    margin-left: 0;
    flex-basis: 100%;
  }
}
/**
 * TP V2 — Filter bar & chips
 * Row of mutually independent filters above a list. Unlike the segmented
 * control, several chips can be active at the same time; the active state is a
 * link target, so it is expressed with `aria-current="page"` where the chip is
 * a link.
 *
 * Usage:
 *   <div class="tpv2-filter-bar">
 *     <div class="tpv2-filter-bar-row">
 *       <span class="tpv2-filter-bar-label">Typ</span>
 *       <a class="tpv2-filter-chip" href="?type=feature">Feature</a>
 *       <a class="tpv2-filter-chip tpv2-filter-chip-active" href="?">Alle</a>
 *     </div>
 *   </div>
 */

.tpv2-filter-bar {
  background-color: var(--tp-surface, #fff);
  border: 1px solid var(--tp-border, #e1e1e1);
  border-radius: var(--tp-radius-md);
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
  font-family: inherit;
}

.tpv2-filter-bar-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--tp-space-2);
}

.tpv2-filter-bar-label {
  font-size: var(--tp-font-size-1);
  font-weight: var(--tp-font-weight-bold);
  color: var(--tp-text-secondary, #6d7175);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-right: var(--tp-space-1);
}

.tpv2-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--tp-space-1-5);
  padding: 0 var(--tp-space-3);
  min-height: calc(var(--tp-density-control-h) - var(--tp-space-1));
  border: 1px solid var(--tp-border, #e1e1e1);
  border-radius: var(--tp-radius-pill);
  background-color: var(--tp-surface, #fff);
  color: var(--tp-text-secondary, #6d7175);
  font-size: var(--tp-font-size-2);
  font-weight: var(--tp-font-weight-medium);
  line-height: var(--tp-line-none);
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.12s, border-color 0.12s, color 0.12s;
}

.tpv2-filter-chip:hover {
  background-color: var(--tp-hover-bg, #f3f4f6);
  color: var(--tp-text, #202223);
}

.tpv2-filter-chip:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--tp-focus-ring, rgba(29, 75, 142, 0.2));
}

.tpv2-filter-chip-active,
.tpv2-filter-chip[aria-current="page"] {
  background-color: var(--tp-primary, #1d4b8e);
  border-color: var(--tp-primary, #1d4b8e);
  color: #fff;
  font-weight: var(--tp-font-weight-semibold);
}

.tpv2-filter-chip-active:hover,
.tpv2-filter-chip[aria-current="page"]:hover {
  background-color: var(--tp-primary-hover, #174080);
  border-color: var(--tp-primary-hover, #174080);
  color: #fff;
}
/**
 * TP V2 — Form field
 * Label · control · hint/error. Control height comes from the density set, so
 * an input lines up with a button and a segmented control in the same toolbar.
 *
 * Usage:
 *   <div class="tpv2-field">
 *     <label class="tpv2-label tpv2-label-req" for="f-title">Titel</label>
 *     <input id="f-title" class="tpv2-input" type="text">
 *     <span class="tpv2-hint">Erscheint in der Liste.</span>
 *   </div>
 *
 * Error state: add `.tpv2-input-error` to the control and render the message
 * as `.tpv2-hint.tpv2-hint-error` (the hint slot is the same, the tone differs).
 *
 * Sizes: .tpv2-input-sm | .tpv2-input-lg (also on select/textarea)
 */

.tpv2-field {
  margin-bottom: var(--tp-density-gap);
  font-family: inherit;
}

.tpv2-field:last-child {
  margin-bottom: 0;
}

.tpv2-label {
  display: block;
  margin-bottom: var(--tp-space-1-5);
  font-size: var(--tp-font-size-2);
  font-weight: var(--tp-font-weight-medium);
  line-height: var(--tp-line-snug);
  color: var(--tp-text, #202223);
}

.tpv2-label-req::after {
  content: ' *';
  color: var(--tp-danger, #ef4444);
}

.tpv2-input,
.tpv2-select,
.tpv2-textarea {
  width: 100%;
  padding: 0 var(--tp-space-3);
  min-height: var(--tp-density-control-h);
  font-family: inherit;
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  color: var(--tp-text, #202223);
  background-color: var(--tp-input-bg, #fff);
  border: 1px solid var(--tp-input-border, #d1d5db);
  border-radius: var(--tp-radius-sm);
  transition: border-color 0.12s, box-shadow 0.12s;
}

/* Textareas grow with content — no fixed control height. */
.tpv2-textarea {
  padding: var(--tp-space-2) var(--tp-space-3);
  min-height: calc(var(--tp-density-control-h) * 3);
  line-height: var(--tp-line-base);
  resize: vertical;
}

.tpv2-select {
  cursor: pointer;
  padding-right: var(--tp-space-6);
}

/* A select without a field wrapper stands in a control row, not in a form: it
   takes the width of its longest option instead of the width of the row. Full
   width there pushed every control behind it onto its own line (plan 0010,
   D-001b). */
.tpv2-select-auto {
  width: auto;
  max-width: 100%;
}

.tpv2-input::placeholder,
.tpv2-textarea::placeholder {
  color: var(--tp-text-secondary, #6d7175);
}

.tpv2-input:focus,
.tpv2-select:focus,
.tpv2-textarea:focus {
  outline: none;
  border-color: var(--tp-accent, #1d4b8e);
  box-shadow: 0 0 0 3px var(--tp-focus-ring, rgba(29, 75, 142, 0.2));
}

.tpv2-input:disabled,
.tpv2-select:disabled,
.tpv2-textarea:disabled {
  background-color: var(--tp-hover-bg, #f3f4f6);
  color: var(--tp-text-secondary, #6d7175);
  cursor: not-allowed;
}

/* ===========================
   Sizes — variants reach into the scale, not into the density set
   =========================== */
.tpv2-input-sm {
  min-height: calc(var(--tp-density-control-h) - var(--tp-space-1));
  padding-left: var(--tp-space-2);
  padding-right: var(--tp-space-2);
  font-size: var(--tp-font-size-1);
}

.tpv2-input-lg {
  min-height: calc(var(--tp-density-control-h) + var(--tp-space-1));
  font-size: var(--tp-font-size-3);
}

/* ===========================
   States
   =========================== */
.tpv2-input-error {
  border-color: var(--tp-danger, #ef4444);
}

.tpv2-input-error:focus {
  border-color: var(--tp-danger, #ef4444);
  box-shadow: 0 0 0 3px var(--tp-delta-bad-bg, #fee2e2);
}

.tpv2-hint {
  display: block;
  margin-top: var(--tp-space-1);
  font-size: var(--tp-font-size-1);
  line-height: var(--tp-line-snug);
  color: var(--tp-text-secondary, #6d7175);
}

.tpv2-hint[hidden] {
  display: none;
}

.tpv2-hint-error {
  color: var(--tp-delta-bad, #991b1b);
}

/* Inline row of fields (filter toolbars). */
.tpv2-field-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--tp-space-3);
}

.tpv2-field-row .tpv2-field {
  margin-bottom: 0;
}
/**
 * TP V2 — Rahmen-Look (Plan 0124)
 *
 * Dunkler Rahmen, der Inhalt liegt als rundes Blatt darin. Wirkt auf das
 * Gerüst, das jeder Hub hinter ConsoleHub baut:
 *
 *   #consolehub-root > .tp-topbar      schon dunkel
 *   .tp-service-bar                    → Rahmen
 *   .tp-admin > aside.tp-sidebar       → Rahmen
 *   .tp-admin > .tp-admin-content      → Blatt
 *
 * Alles hängt an `html[data-look="frame"]` — gesetzt vom Kopf-Skript jedes
 * Hubs (`theme_init`), umgeschaltet im Topbar-Fragment. Ohne das Attribut
 * ändert diese Datei nichts.
 *
 * Die Leisten werden nicht Eigenschaft für Eigenschaft umgefärbt, sondern
 * bekommen dunkle Werte für dieselben Tokens, mit denen sie gemalt sind.
 * Was darin steht (Badges, Zähler, Links), folgt damit von selbst.
 */

[data-look="frame"] {
  --tp-frame-bg: #1a1a1a;
  --tp-frame-gap: 8px;
  --tp-sheet-radius: 14px;
  --tp-radius-sm: 8px;
  --tp-radius-md: 12px;
  /* Spiegel von dock.css — das Blatt liegt AUSSERHALB von .tpv2-dock-host
     und kann dessen Variablen nicht lesen. */
  --tp-frame-dock-bar-w: 44px;
  --tp-frame-dock-pane-w: 400px;
  /* Was aus der Leiste herausklappt (WarehouseBrains Roherlös-Popover), ist
     Seite, nicht Rahmen. Dafür merkt sich `html` die Seiten-Tokens: ein
     `var()` in einer Custom Property wird dort aufgelöst, wo sie steht —
     die Kopie trägt also den Wert des aktuellen Themes, nicht den dunklen
     Rahmenwert. */
  --tp-page-surface: var(--tp-surface);
  --tp-page-border: var(--tp-border);
  --tp-page-hover-bg: var(--tp-hover-bg);
  --tp-page-text: var(--tp-text);
  --tp-page-text-secondary: var(--tp-text-secondary);
  --tp-page-accent: var(--tp-accent);
}

@media (max-width: 1439px) {
  [data-look="frame"] { --tp-frame-dock-pane-w: 380px; }
}

/* Dunkles Theme: der Rahmen muss DUNKLER sein als das Blatt (#1f2937),
   sonst verschwimmt die Kante. Topbar (sonst --tp-nav-bg #111827) zieht
   mit, damit Topbar, Leiste und Sidebar eine Fläche sind; ein Hauch Licht
   am Blattrand ersetzt den Kontrast, den Hell über die Helligkeit hat. */
[data-theme="dark"][data-look="frame"] {
  --tp-frame-bg: #0b0f17;
  --tp-nav-bg: var(--tp-frame-bg);
  --tp-frame-ring: 0 0 0 1px rgba(255, 255, 255, 0.07);

  /* Eine Dark-Palette für alle Hubs. 13 Hubs bringen in ihrer app.css eine
     alte GitHub-Palette mit (`[data-theme="dark"] { --tp-content-bg: #0d1117 … }`);
     welche gewann, hing an der Ladereihenfolge — bei AgentHub war das Blatt
     so dunkel wie der Rahmen. Zwei Attribute schlagen ein Attribut, egal
     wo die Regel steht. Werte = tokens.css, Block `[data-theme="dark"]`. */
  --tp-primary: #1d4b8e;
  --tp-primary-hover: #2563eb;
  --tp-secondary: #9ca3af;
  --tp-dark: #111827;
  --tp-sidebar-bg: #1b2330;
  --tp-content-bg: #1f2937;
  --tp-surface: #273142;
  --tp-border: #374151;
  --tp-hover-bg: #323d4f;
  --tp-text: #f3f4f6;
  --tp-text-secondary: #a3acb9;
  --tp-highlight: #1e3a5f;
  --tp-input-bg: #1f2937;
  --tp-input-border: #4b5563;
  --tp-focus-ring: rgba(88, 166, 255, 0.4);
  --tp-success: #18cf72;
  --tp-warning: #d29922;
  --tp-danger: #f85149;
  --tp-info: #58a6ff;
}

[data-look="frame"] .tp-topbar {
  background-color: var(--tp-frame-bg);
  border-bottom-color: transparent;
}

/* Beim Laden gesetzt (Topbar-Fragment), zwei Bilder lang: der Wechsel
   in den Look springt, statt Sidebar und Blatt einfahren zu lassen. */
.tp-look-instant,
.tp-look-instant * {
  transition: none !important;
}

[data-look="frame"],
[data-look="frame"] body,
[data-look="frame"] .tp-admin {
  background-color: var(--tp-frame-bg);
}

/* ── Rahmen: Service-Leiste + Sidebar tragen dunkle Tokens ─────────────── */
[data-look="frame"] .tp-service-bar,
[data-look="frame"] .tp-sidebar,
[data-look="frame"] .apps-shell-sidebar,
[data-look="frame"] .app-sidebar,
[data-look="frame"] .tpv2-dock-bar {
  --apps-rail-bg: var(--tp-frame-bg);
  --tp-surface: var(--tp-frame-bg);
  --tp-sidebar-bg: var(--tp-frame-bg);
  --tp-border: rgba(255, 255, 255, 0.09);
  --tp-hover-bg: rgba(255, 255, 255, 0.06);
  --tp-text: #e3e3e3;
  --tp-text-secondary: #9a9a9a;
  --tp-accent: #7aa7ff;
  color-scheme: dark;
}

/* Was aus der Leiste herausklappt, ist Seite, nicht Rahmen: holt die
   Seiten-Tokens zurück, die sich `html` gemerkt hat (`--tp-page-*`). */
[data-look="frame"] .tp-service-bar [class*="popover"] {
  --tp-surface: var(--tp-page-surface);
  --tp-border: var(--tp-page-border);
  --tp-hover-bg: var(--tp-page-hover-bg);
  --tp-text: var(--tp-page-text);
  --tp-text-secondary: var(--tp-page-text-secondary);
  --tp-accent: var(--tp-page-accent);
  color-scheme: normal;
}

/* Einige Hubs malen die Leiste per Inline-`<style>` mit festen Farben
   (`tp-service-bar-critical`, gegen Flackern vor main.css) — auch mit
   `[data-theme="dark"]` davor, darum die doppelte Klasse. */
[data-look="frame"] .tp-service-bar,
[data-look="frame"] .tp-service-bar.tp-service-bar-critical {
  background-color: var(--tp-frame-bg);
  border-bottom-color: transparent;
}

/* Trennlinien aus Tailwind-Klassen der Hubs (`border-tp-border`) sind zur
   Bauzeit feste Farben und lesen den umgefärbten Token nicht. */
[data-look="frame"] .tp-sidebar [class*="border"] {
  border-color: var(--tp-border);
}

[data-look="frame"] .tp-sidebar {
  border-right: 0;
}

[data-look="frame"] .tp-sidebar-link.active {
  background-color: rgba(255, 255, 255, 0.1);
}

/* ── Blatt ──────────────────────────────────────────────────────────────
   Das Blatt ist sein eigener Scroll-Container. Nur so bleiben alle vier
   Ecken rund stehen; scrollte die Seite, liefen die oberen Ecken unter
   die feste Service-Leiste. Sticky-Köpfe im Blatt kleben an seiner
   Oberkante statt am Fenster — das ist gewollt. */
@media (min-width: 1024px) {
  /* Die Hubs rücken den Inhalt mit `margin-top` unter die Leisten — mit
     wechselndem Gewicht, teils aus Inline-`<style>` (SupportHub). Das feste
     Blatt misst von `top`, jeder Rand darauf verschiebt es doppelt; darum
     hier ausnahmsweise `!important`. */
  [data-look="frame"] .tp-admin .tp-admin-content {
    position: fixed;
    top: 6.5rem;
    left: 13rem;
    right: var(--tp-frame-gap);
    bottom: var(--tp-frame-gap);
    margin: 0 !important;
    min-height: 0;
    overflow-y: auto;
    border-radius: var(--tp-sheet-radius);
    box-shadow: var(--tp-frame-ring, none);
  }

  [data-look="frame"] .tp-admin.sidebar-collapsed .tp-admin-content {
    left: 4rem;
  }

  /* Hubs ohne eigene Navigation (SupportHub-Inbox): Blatt bis zum Rand. */
  [data-look="frame"] .tp-admin:not(:has(> .tp-sidebar)) .tp-admin-content {
    left: var(--tp-frame-gap);
  }

  /* Das Chronik-Dock wird Teil des Rahmens: die Symbolleiste steht auf
     Schwarz, die offene Fläche ist ein zweites rundes Blatt. Das Blatt
     weicht ihm aus, statt dass der Dock-Host es innen einrückt. */
  [data-look="frame"] .tp-admin .tp-admin-content:has(.tpv2-dock) {
    right: var(--tp-frame-dock-bar-w);
  }

  [data-look="frame"] .tp-admin .tp-admin-content:has(.tpv2-dock.is-open) {
    right: calc(var(--tp-frame-dock-bar-w) + var(--tp-frame-dock-pane-w) + var(--tp-frame-gap));
  }

  /* `auto` statt `0`: die Detailseite (unten) ist auf 1060 px begrenzt und
     steht mittig — ein rechter Rand von 0 schöbe sie an die Kante. */
  [data-look="frame"] .tp-admin .tpv2-dock-host:has(> .tpv2-dock) {
    margin-inline: auto;
  }

  [data-look="frame"] .tpv2-dock {
    bottom: var(--tp-frame-gap);
    background-color: var(--tp-frame-bg);
    border-left: 0;
  }

  [data-look="frame"] .tpv2-dock.is-open .tpv2-dock-bar {
    border-left: 0;
  }

  [data-look="frame"] .tpv2-dock-sheet {
    border-radius: var(--tp-sheet-radius);
    background-color: var(--tp-surface);
    box-shadow: var(--tp-frame-ring, none);
  }
}

/* Handy/Tablet: Sidebar ist Schublade, das Blatt füllt die Breite und
   behält oben die runden Ecken — die Seite scrollt normal. */
@media (max-width: 1023.98px) {
  [data-look="frame"] .tp-admin .tp-admin-content {
    border-radius: var(--tp-sheet-radius) var(--tp-sheet-radius) 0 0;
    min-height: calc(100dvh - 6.5rem);
  }
}

/* ── Grundfarbe und Scrollbalken der Blätter ─────────────────────────────
   Das Blatt scrollt selbst; ohne Angabe malt das System einen hellen
   Balken, der im Dark-Theme wie ein Strich durchs Bild steht. */
[data-look="frame"] .tp-admin .tp-admin-content,
[data-look="frame"] .apps-page-grid,
[data-look="frame"] .app-main {
  background-color: var(--tp-content-bg);
  scrollbar-width: thin;
  scrollbar-color: rgba(128, 128, 128, 0.35) transparent;
}

[data-theme="dark"][data-look="frame"] .tp-admin .tp-admin-content,
[data-theme="dark"][data-look="frame"] .apps-page-grid {
  color-scheme: dark;
}

/* ── Detailseite (shopcloud-ui `detail_page`) ────────────────────────────
   Shopify-Muster: die Überschrift eines Abschnitts steht ÜBER seiner
   Karte, nicht in ihr; Posten und Summen sind je eine eigene Karte; die
   rechte Spalte hat keine Karten, nur Daten mit Haarlinien dazwischen. */
/* Das Blatt ist breit, die Seite nicht: wie `mockup.html` 1060 px mittig.
   Auf 2000 px liefen Tabellen sonst über 1700 px, Beschriftung links und
   Wert rechts fanden nicht mehr zusammen. */
[data-look="frame"] .tpv2-detail {
  max-width: 1060px;
  margin-inline: auto;
}

[data-look="frame"] .tpv2-detail-main .tpv2-card-flush {
  background: transparent;
  border: 0;
  box-shadow: none;
  overflow: visible;
}

[data-look="frame"] .tpv2-detail-main .tpv2-card-flush > .tpv2-card-h {
  background: transparent;
  border: 0;
  padding: 0 0 var(--tp-space-2, 0.5rem);
}

[data-look="frame"] .tpv2-detail-main .tpv2-card-flush > .tpv2-card-b,
[data-look="frame"] .tpv2-detail-main .tpv2-card-flush > .tpv2-card-part {
  background-color: var(--tp-surface);
  border: 1px solid var(--tp-border);
  border-radius: var(--tp-radius-md);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.04);
}

[data-look="frame"] .tpv2-detail-main .tpv2-card-flush > .tpv2-card-part {
  margin-top: var(--tp-space-3, 0.75rem);
}

/* Summen: keine Linie unter jeder Zeile, nur Abstand. */
[data-look="frame"] .tpv2-detail-main .tpv2-card-part .tpv2-fact {
  border-bottom: 0;
}

[data-look="frame"] .tpv2-rail-card {
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--tp-border);
  border-radius: 0;
  box-shadow: none;
  padding: var(--tp-space-4, 1rem) 0;
}

[data-look="frame"] .tpv2-rail-card:first-of-type {
  padding-top: 0;
}

[data-look="frame"] .tpv2-rail-card .tpv2-fact {
  border-bottom: 0;
}

[data-look="frame"] .tpv2-rail-card-header {
  color: var(--tp-text-secondary);
  font-weight: 550;
}

/* ── ConsoleHub `/apps/*` (consolehub-layout, apps-shell.css) ─────────────
   Anderes Gerüst, gleiche Idee: `.apps-shell-sidebar` ist Rahmen, das
   Seiten-Grid daneben ist das Blatt. Das Grid bleibt im Fluss (es ist auf
   1820 px zentriert, ein festes Blatt würde daneben liegen) — dafür
   scrollen Sidebar und Blatt je für sich, die Seite selbst nicht. */
[data-look="frame"] .apps-shell-sidebar {
  border-right: 0;
}

[data-look="frame"] .apps-sidebar-item:hover {
  background: rgba(255, 255, 255, 0.06);
}

[data-look="frame"] .apps-sidebar-item.is-active {
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
}

[data-look="frame"] .apps-sidebar-item.is-active .apps-sidebar-icon {
  background: none;
  color: #fff;
}

[data-look="frame"] .apps-page-grid {
  box-shadow: var(--tp-frame-ring, none);
}

@media (min-width: 1024px) {
  /* Ohne Deckel: apps-shell.css zentriert die Shell auf 1820 px — im
     Rahmen stünde das Blatt dann als Insel in der Mitte, Sidebar und
     Blatt losgelöst vom linken Rand. Der Rahmen füllt das Fenster. */
  [data-look="frame"] .apps-shell-grid {
    height: calc(100dvh - 6.5rem);
    min-height: 0;
    max-width: none;
  }

  [data-look="frame"] .apps-page-grid {
    height: calc(100dvh - 6.5rem - var(--tp-frame-gap));
    min-height: 0;
    overflow-y: auto;
    margin: 0 var(--tp-frame-gap) var(--tp-frame-gap);
    border-radius: var(--tp-sheet-radius);
  }

  [data-look="frame"] .apps-shell-grid > .apps-page-grid {
    margin-left: 0;
  }
}

@media (max-width: 1023px) {
  [data-look="frame"] .apps-page-grid {
    border-radius: var(--tp-sheet-radius) var(--tp-sheet-radius) 0 0;
  }
}

/* ── Hubs ohne tp-admin-Gerüst (QueryHub: Leiste + nacktes `<main>`) ─────
   Kein eigener Selektor je Hub: jedes `<main>` direkt unter `body` neben
   einer Service-Leiste ist der Inhalt und wird Blatt über die volle Breite. */
[data-look="frame"] body:has(> .tp-service-bar) > main {
  background-color: var(--tp-content-bg);
  box-shadow: var(--tp-frame-ring, none);
  scrollbar-width: thin;
  scrollbar-color: rgba(128, 128, 128, 0.35) transparent;
}

@media (min-width: 1024px) {
  [data-look="frame"] body:has(> .tp-service-bar) > main {
    position: fixed;
    top: 6.5rem;
    left: var(--tp-frame-gap);
    right: var(--tp-frame-gap);
    bottom: var(--tp-frame-gap);
    margin: 0 !important;
    /* RecordHub rückt sein `<main>` per padding-top um die Leistenhöhe ein —
       im Blatt, das selbst unter den Leisten beginnt, wäre das doppelt. */
    padding-top: 0 !important;
    overflow-y: auto;
    border-radius: var(--tp-sheet-radius);
  }
}

@media (max-width: 1023.98px) {
  [data-look="frame"] body:has(> .tp-service-bar) > main {
    border-radius: var(--tp-sheet-radius) var(--tp-sheet-radius) 0 0;
    min-height: calc(100dvh - 6.5rem);
  }
}

/* QueryHub malt Name und Reiter seiner Leiste mit festen Farben (Inline-
   `<style>` in queryhub-web base.html) — auf dem Rahmen unsichtbar. */
[data-look="frame"] .tp-service-bar .qh-brand {
  color: var(--tp-text);
  border-right-color: var(--tp-border);
}

[data-look="frame"] .tp-service-bar .qh-nav-item {
  color: var(--tp-text-secondary);
}

[data-look="frame"] .tp-service-bar .qh-nav-item:hover {
  color: var(--tp-text);
}

[data-look="frame"] .tp-service-bar .qh-nav-item.active {
  color: var(--tp-accent);
  border-bottom-color: var(--tp-accent);
}

/* ── StorefrontHub-Admin: eigenes Gerüst ohne Service-Leiste ──────────────
   `aside.app-sidebar` + `main.app-main` in einer Flex-Zeile unter der
   Topbar. Sidebar wird Rahmen, `main` Blatt; eigene Breakpoints (64rem). */
[data-look="frame"] .app-sidebar {
  background-color: var(--tp-frame-bg);
  border-right: 0;
}

[data-look="frame"] .app-sidebar .sidebar-link.active {
  background-color: rgba(255, 255, 255, 0.1);
}

[data-look="frame"] .app-main {
  box-shadow: var(--tp-frame-ring, none);
}

@media (min-width: 64rem) {
  [data-look="frame"] .app-main {
    height: calc(100dvh - 3.5rem - var(--tp-frame-gap));
    overflow-y: auto;
    margin: 0 var(--tp-frame-gap) var(--tp-frame-gap) 0;
    border-radius: var(--tp-sheet-radius);
  }
}

@media (max-width: 63.9rem) {
  [data-look="frame"] .app-main {
    border-radius: var(--tp-sheet-radius) var(--tp-sheet-radius) 0 0;
  }
}

/* Schrift in der Füllfarbe: viele Hubs setzen Links, aktive Einträge und
   Icons mit Tailwinds `text-tp-primary` — im Dark-Mode #1d4b8e auf #1f2937
   (2:1). Schrift gehört in die On-Surface-Rolle `--tp-accent` (tokens.css). */
[data-theme="dark"][data-look="frame"] .text-tp-primary,
[data-theme="dark"][data-look="frame"] .hover\:text-tp-primary:hover {
  color: var(--tp-accent);
}

/* ── Handy (< 768 px): Text mit Rand, Karten randlos ──────────────────────
   v1 nimmt dem Inhalt am Handy den seitlichen Abstand, damit Tabellen die
   Breite haben — dann klebt aber jede Überschrift an der Blattkante. Wie
   bei Shopify: der Inhalt bekommt 16 px Rand, Flächen (Liste, Karte,
   Detail-Karte) laufen mit negativem Rand bis an die Kante, eckig und
   ohne Seitenlinien. Verschachtelte Karten bleiben, wie sie sind. */
@media (max-width: 767px) {
  [data-look="frame"] .tp-admin-content-inner {
    padding: 1.25rem var(--tp-frame-mobile-gutter, 16px) 2rem !important;
  }

  [data-look="frame"] .tp-admin-content-inner .tpv2-rlist,
  [data-look="frame"] .tp-admin-content-inner .tpv2-card:not(.tpv2-card-nest):not(.tpv2-card-flush),
  [data-look="frame"] .tp-admin-content-inner .tp-card,
  /* Tailwind-Karten der Hubs (WarehouseBrain, IssueHub): nur wenn sie die
     volle Breite haben — nicht als Kachel in einem mehrspaltigen Raster. */
  [data-look="frame"] .tp-admin-content-inner :is(div, section).bg-tp-surface.border:not(.grid > *):not(.flex > *),
  [data-look="frame"] .tp-admin-content-inner .grid-cols-1 > :is(div, section).bg-tp-surface.border,
  [data-look="frame"] .tpv2-detail-main .tpv2-card-flush > .tpv2-card-b,
  [data-look="frame"] .tpv2-detail-main .tpv2-card-flush > .tpv2-card-part {
    margin-inline: calc(-1 * var(--tp-frame-mobile-gutter, 16px));
    border-radius: 0;
    border-left: 0;
    border-right: 0;
  }

  /* Die Ansichts-Reiter der Liste erben sonst die obere Rundung. */
  [data-look="frame"] .tp-admin-content-inner .tpv2-rlist-views {
    border-radius: 0;
  }
}
/**
 * TP V2 — Glyph
 *
 * Das farbige Feld mit einem Zeichen darin, das **den Zustand seiner Fläche
 * ist** (Plan 0057, E12 §2): gelb „nicht ausgeführt", grün „versendet", rot
 * „fehlgeschlagen". Es sitzt links im Kopf einer Karte oder einer
 * Aufklapp-Zeile, vor der Überschrift.
 *
 * **Es ersetzt keine Pille.** Die Pille sagt den Zustand in Worten und ist die
 * Auskunft; das Glyph macht ihn auffindbar, bevor jemand liest. Wer nur eins
 * von beiden setzt, setzt die Pille.
 *
 * **Kein zweites Tönungssystem.** Fläche und Zeichen kommen aus denselben
 * `--tp-badge-*`-Tokens wie `tpv2-pill`, also sieht „versendet" hier aus wie
 * „Versendet" daneben, und beide drehen im dunklen Thema mit. Ein Literal
 * (`style="background:#dcfce7"`, wie der erste Entwurf des Akkordeons ihn
 * vorschlug) wäre eine helle Insel im Dunkeln.
 *
 * Verwendung:
 *   <span class="tpv2-glyph tpv2-glyph-success" aria-hidden="true">
 *     <i class="fas fa-truck-fast"></i>
 *   </span>
 *
 * Modifikatoren:
 *   .tpv2-glyph-success / -warning / -danger / -info / -purple
 *   ohne Modifikator: grau — die Ruhelage, kein Zustand
 */

.tpv2-glyph {
  width: 26px;
  height: 26px;
  flex: none;
  display: inline-grid;
  place-items: center;
  border-radius: var(--tp-radius-sm);
  background-color: var(--tp-badge-gray-bg, #f3f4f6);
  color: var(--tp-badge-gray-text, #374151);
  font-size: var(--tp-font-size-2);
  line-height: 1;
}

.tpv2-glyph-success {
  background-color: var(--tp-badge-success-bg, #dcfce7);
  color: var(--tp-badge-success-text, #166534);
}

.tpv2-glyph-warning {
  background-color: var(--tp-badge-warning-bg, #fef3c7);
  color: var(--tp-badge-warning-text, #92400e);
}

.tpv2-glyph-danger {
  background-color: var(--tp-badge-danger-bg, #fee2e2);
  color: var(--tp-badge-danger-text, #991b1b);
}

.tpv2-glyph-info {
  background-color: var(--tp-badge-info-bg, #dbeafe);
  color: var(--tp-badge-info-text, #1e40af);
}

.tpv2-glyph-purple {
  background-color: var(--tp-badge-purple-bg, #f3e8ff);
  color: var(--tp-badge-purple-text, #6b21a8);
}
/**
 * TP Design System - Identity Strip
 * Persistent horizontal strip of label/value pairs shown at the top of
 * detail-cockpit pages. Each "item" is a {key, value} pair separated by
 * a vertical divider. Optional alert pill on the right edge.
 *
 * Usage:
 *   <div class="tpv2-identity-strip">
 *     <div class="tpv2-identity-strip-item">
 *       <span class="tpv2-identity-strip-key">Vorgang</span>
 *       <span class="tpv2-identity-strip-value mono">3205410</span>
 *     </div>
 *     <div class="tpv2-identity-strip-item">
 *       <span class="tpv2-identity-strip-key">Kunde</span>
 *       <span class="tpv2-identity-strip-value">Zoltan Szoradi</span>
 *       <span class="tpv2-identity-strip-value muted">78224 Singen</span>
 *     </div>
 *     ...
 *     <button class="tpv2-identity-strip-alert">
 *       <i class="fas fa-magnifying-glass"></i>
 *       NF-2026-0813 · 26 h / 48 h
 *     </button>
 *   </div>
 *
 * Modifier classes on .tpv2-identity-strip-value:
 *   .mono   monospace (IDs, refs, codes)
 *   .muted  secondary text colour
 *   .link   underline-on-hover anchor styling
 */

/* ===========================
   Container
   =========================== */
.tpv2-identity-strip {
  background-color: var(--tp-surface, #fff);
  border-bottom: 1px solid var(--tp-border, #e1e1e1);
  padding: 11px 18px;
  display: flex;
  align-items: center;
  gap: 0;
  font-size: 12px;
  color: var(--tp-text, #202223);
  flex-wrap: nowrap;
  overflow-x: auto;
  font-family: inherit;
}

/* ===========================
   Item (column)
   =========================== */
.tpv2-identity-strip-item {
  display: flex;
  align-items: baseline;
  gap: 7px;
  padding: 0 16px;
  border-right: 1px solid var(--tp-border, #e1e1e1);
  white-space: nowrap;
}

.tpv2-identity-strip-item:first-child {
  padding-left: 0;
}

/* Last "item" loses its divider — but we use :last-of-type because
   the alert button often comes after. */
.tpv2-identity-strip-item:last-of-type {
  border-right: none;
}

/* ===========================
   Key (label)
   =========================== */
/* The label was a literal #9ca3af — measured 2.54:1 on --tp-surface in the
   RecordHub bundle, and it never changed with the theme. It is secondary
   text, so it takes the secondary-text role (5.32:1 light / 5.62:1 dark). */
.tpv2-identity-strip-key {
  font-size: 9.5px;
  font-weight: 700;
  color: var(--tp-text-secondary, #6d7175);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* ===========================
   Value (primary text)
   =========================== */
.tpv2-identity-strip-value {
  color: var(--tp-text, #202223);
  font-weight: 500;
}

.tpv2-identity-strip-value.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
}

.tpv2-identity-strip-value.muted {
  color: var(--tp-text-secondary, #6d7175);
  font-weight: 400;
}

.tpv2-identity-strip-value.link {
  color: var(--tp-accent, #1d4b8e);
  text-decoration: none;
}

.tpv2-identity-strip-value.link:hover {
  text-decoration: underline;
}

/* ===========================
   Alert (right-aligned call-to-action)
   =========================== */
.tpv2-identity-strip-alert {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  background-color: var(--tp-badge-danger-bg, #fef2f2);
  color: var(--tp-badge-danger-text, #991b1b);
  border: 1px solid #fecaca;
  border-radius: var(--tp-radius-pill);
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
  font-family: inherit;
}

.tpv2-identity-strip-alert:hover {
  background-color: #fee2e2;
}

.tpv2-identity-strip-alert i {
  font-size: 10px;
}

/* Variants for non-danger alerts */
.tpv2-identity-strip-alert-warning {
  background-color: var(--tp-badge-warning-bg, #fef3c7);
  color: var(--tp-badge-warning-text, #92400e);
  border-color: #fde68a;
}

.tpv2-identity-strip-alert-info {
  background-color: var(--tp-badge-info-bg, #dbeafe);
  color: var(--tp-badge-info-text, #1e40af);
  border-color: #bfdbfe;
}
/**
 * TP V2 — KPI Tile
 * Metric card for dashboards and overview pages. Label · value · sub
 * + optional trend chip.
 *
 * Usage:
 *   <div class="tpv2-kpi">
 *     <div class="tpv2-kpi-l"><i class="fas fa-coins" style="color:#16a34a"></i> Auftragswert</div>
 *     <div class="tpv2-kpi-v">794,99 €</div>
 *     <div class="tpv2-kpi-s">davon 5,99 € Versand</div>
 *   </div>
 *
 *   <div class="tpv2-kpi">
 *     <div class="tpv2-kpi-l">Marge</div>
 *     <div class="tpv2-kpi-v">20,0 %</div>
 *     <div class="tpv2-kpi-s">
 *       <span class="tpv2-kpi-trend tpv2-kpi-trend-up">
 *         <i class="fas fa-arrow-up"></i> +2,1 %
 *       </span>
 *       vs. Kategorie
 *     </div>
 *   </div>
 *
 * Grid usage:
 *   <div class="tpv2-kpi-grid">…</div>     auto-fit minmax(170px, 1fr)
 */

.tpv2-kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: var(--tp-density-gap);
}

.tpv2-kpi {
  background-color: var(--tp-surface, #fff);
  border: 1px solid var(--tp-border, #e1e1e1);
  border-radius: var(--tp-radius-md);
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
  font-family: inherit;
  color: var(--tp-text, #202223);
  box-shadow: var(--tp-elev-1);
}

.tpv2-kpi-l {
  font-size: var(--tp-font-size-1);
  font-weight: var(--tp-font-weight-bold);
  color: var(--tp-text-secondary, #6d7175);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: var(--tp-space-1-5);
  display: flex;
  align-items: center;
  gap: var(--tp-space-1-5);
}

.tpv2-kpi-l i {
  font-size: var(--tp-font-size-1);
}

.tpv2-kpi-v {
  font-size: var(--tp-font-size-metric);
  font-weight: var(--tp-font-weight-bold);
  line-height: var(--tp-line-tight);
  font-variant-numeric: var(--tp-numeric);
  /* tabular figures are set on a fixed advance width, which reads loose at
     display sizes; the same tracking a print house would use on a table */
  letter-spacing: -0.015em;
  color: var(--tp-text, #202223);
}

.tpv2-kpi-s {
  font-size: var(--tp-font-size-2);
  color: var(--tp-text-secondary, #6d7175);
  margin-top: var(--tp-space-0-5);
  display: flex;
  align-items: center;
  gap: var(--tp-space-1-5);
}

.tpv2-kpi-trend {
  display: inline-flex;
  align-items: center;
  gap: var(--tp-space-0-5);
  font-size: var(--tp-font-size-1);
  font-weight: var(--tp-font-weight-semibold);
  padding: 1px var(--tp-space-1-5);
  border-radius: var(--tp-radius-xs);
}

.tpv2-kpi-trend i {
  font-size: var(--tp-font-size-1);
}

/* Trend chips carry the delta color — the value itself stays --tp-text. */
.tpv2-kpi-trend-up {
  background-color: var(--tp-delta-good-bg, #dcfce7);
  color: var(--tp-delta-good, #166534);
}

.tpv2-kpi-trend-down {
  background-color: var(--tp-delta-bad-bg, #fee2e2);
  color: var(--tp-delta-bad, #991b1b);
}

.tpv2-kpi-trend-flat {
  background-color: var(--tp-delta-neutral-bg, #f3f4f6);
  color: var(--tp-delta-neutral, #374151);
}

/* Variant: large hero KPI (used at top of dashboards).
   Variants reach into the scale directly, not into the density set. */
.tpv2-kpi-lg .tpv2-kpi-v {
  font-size: var(--tp-font-size-metric-lg);
}

.tpv2-kpi-lg {
  padding: var(--tp-space-4) var(--tp-space-5);
}

/* Variant: frameless. Tiles inside a card drew a second frame around
   themselves — box in box (plan 0010, D-001). Without the frame the grid gap
   has to do the separating, so the flat grid opens up. */
.tpv2-kpi-flat {
  background-color: transparent;
  border-color: transparent;
  padding: 0;
  box-shadow: none;
}

.tpv2-kpi-grid-flat {
  gap: var(--tp-density-gap) var(--tp-space-8);
}

/* Fixed hero rows: auto-fit put two 24 px metrics side by side at 390 px and
   broke "2.136.925,68" before the € (D-001). Both step down to two and then
   to one, so the hero figure never has to share a phone row. */
.tpv2-kpi-grid-3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.tpv2-kpi-grid-4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

@media (max-width: 1023px) {
  .tpv2-kpi-grid-3,
  .tpv2-kpi-grid-4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 767px) {
  .tpv2-kpi-grid-3,
  .tpv2-kpi-grid-4 {
    grid-template-columns: 1fr;
  }
}
/**
 * TP Design System — V2 · Layout Shells
 *
 * Named multi-region shells that pages of ConsoleHub-managed apps drop into.
 * One shell per LayoutKind from spec 177 §3.5:
 *
 *   .tpv2-layout-body-stack     — single column
 *   .tpv2-layout-body-sidebar   — left rail + main
 *   .tpv2-layout-body-aside     — main + right rail
 *   .tpv2-layout-body-both      — left + main + right
 *   (DashboardGrid is "stack" with .tpv2-dash-grid sub-grid inside main)
 *
 * Each layout wraps in:
 *
 *   <div class="tpv2-layout">
 *     <div class="tpv2-layout-header tpv2-layout-header-flow">…</div>     [opt]
 *     <div class="tpv2-layout-body tpv2-layout-body-{kind}">
 *       <aside class="tpv2-layout-rail-left">…</aside>                    [opt]
 *       <main  class="tpv2-layout-main">…</main>
 *       <aside class="tpv2-layout-rail-right">…</aside>                   [opt]
 *     </div>
 *     <div class="tpv2-layout-footer tpv2-layout-footer-sticky">…</div>   [opt]
 *   </div>
 *
 * Responsive (mobile-first):
 *   • <md (≤767px): right rail collapses under main; left rail becomes drawer
 *   • <lg (≤1023px): right rail collapses under main; left rail stays
 *   • ≥lg (≥1024px): full layout
 */

/* ===========================
   Layout container
   =========================== */
.tpv2-layout {
  display: flex;
  flex-direction: column;
}

/* ===========================
   Header slot

   THE SURFACE RULE (plan 0010, D-009). A surface — white plate, border,
   rung-1 elevation — belongs to *one closed content*: a table, a list, a
   figure, a state. Everything that only names or groups such surfaces stays
   on the page ground. The page header names the page, so it has no plate of
   its own; it is the topmost section mark, not a section.

   The one exception has a reason: a header that sticks has to occlude the
   content scrolling underneath it. There the plate does work, so `-sticky`
   brings it back. `-flow` is the default and adds nothing.
   =========================== */
.tpv2-layout-header {
  background-color: transparent;
  border-bottom: 0;
  /* Air above the page title comes from the section rhythm, so the header sits
     on the same grid as everything below it. */
  padding: var(--tp-density-section-gap) var(--tp-density-pad-x)
    calc(var(--tp-density-section-gap) - var(--tp-density-pad-y));
  display: flex;
  align-items: center;
  gap: var(--tp-density-gap);
  flex-wrap: wrap;
}

.tpv2-layout-header-sticky {
  position: sticky;
  top: 0;
  z-index: 9;
  background-color: var(--tp-surface, #fff);
  border-bottom: 1px solid var(--tp-border, #e1e1e1);
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
}

.tpv2-layout-header-flow {
  position: static;
}

/* A page header dropped into the header slot fills it — the slot owns the
   composition rule, the component does not need to know where it sits. The
   slot also owns the distance to the content, so the component's own bottom
   margin would double it. */
.tpv2-layout-header > .tpv2-page-h {
  flex: 1 1 auto;
  min-width: 0;
  margin-bottom: 0;
}

/* ===========================
   Body — grid for left/main/right
   =========================== */
.tpv2-layout-body {
  flex: 1 1 auto;
  display: grid;
  min-width: 0;
}

.tpv2-layout-body-stack {
  grid-template-columns: 1fr;
  grid-template-areas: "main";
}

.tpv2-layout-body-sidebar {
  grid-template-columns: var(--tpv2-rail-left-w, 240px) 1fr;
  grid-template-areas: "left main";
}

.tpv2-layout-body-aside {
  grid-template-columns: 1fr var(--tpv2-rail-right-w, 300px);
  grid-template-areas: "main right";
}

.tpv2-layout-body-both {
  grid-template-columns: var(--tpv2-rail-left-w, 240px) 1fr var(--tpv2-rail-right-w, 300px);
  grid-template-areas: "left main right";
}

/* ===========================
   Slot wrappers
   =========================== */
.tpv2-layout-rail-left {
  grid-area: left;
  /* --tp-surface-soft never existed; the literal fallback painted a near-white
     rail into dark mode. --tp-sidebar-bg is the token for exactly this role. */
  background-color: var(--tp-sidebar-bg);
  border-right: 1px solid var(--tp-border, #e1e1e1);
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
  min-width: 0;
}

.tpv2-layout-rail-right {
  grid-area: right;
  background-color: var(--tp-surface, #fff);
  border-left: 1px solid var(--tp-border, #e1e1e1);
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
  min-width: 0;
}

.tpv2-layout-main {
  grid-area: main;
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
  min-width: 0;
}

/* Slot whose single child is one embedded surface (an iframe dashboard): no
   gutter, the child owns the full height. */
.tpv2-layout-main-flush {
  padding: 0;
  display: flex;
  flex-direction: column;
}

/* ===========================
   Footer slot — same rule as the header slot: the plate only where it works.
   =========================== */
.tpv2-layout-footer {
  background-color: transparent;
  border-top: 0;
  padding: var(--tp-space-2) var(--tp-density-pad-x);
  display: flex;
  align-items: center;
  gap: var(--tp-space-3);
  flex-wrap: wrap;
  min-height: 36px;
}

.tpv2-layout-footer-sticky {
  position: sticky;
  bottom: 0;
  z-index: 9;
  background-color: var(--tp-surface, #fff);
  border-top: 1px solid var(--tp-border, #e1e1e1);
}

.tpv2-layout-footer-flow {
  position: static;
}

/* ===========================
   Mobile drawer trigger (placeholder — JS toggles is-open in real app)
   =========================== */
.tpv2-layout-drawer-trigger {
  display: none;
  background: transparent;
  border: 1px solid var(--tp-border, #e1e1e1);
  border-radius: var(--tp-radius-sm);
  width: var(--tp-density-control-h);
  height: var(--tp-density-control-h);
  cursor: pointer;
  align-items: center;
  justify-content: center;
}

/* ===========================
   Dashboard sub-grid utility — for DashboardGrid layout's main slot
   =========================== */
/* Two rhythms, not one. The outer grid separates *sections* of a page, the
   nested grid separates cards that belong together. Before D-003 both were
   --tp-density-gap, so a section heading sat as far from its own content as
   from the section above it and nothing grouped (plan 0010, lever 1). */
.tpv2-dash-grid {
  display: grid;
  gap: var(--tp-density-section-gap);
}

.tpv2-dash-grid .tpv2-dash-grid,
.tpv2-dash-grid-tight {
  gap: var(--tp-density-gap);
}

/* Grid items default to min-width:auto, so one wide child (a table that
   scrolls inside its own container) would stretch the track and push the
   whole page sideways. Same guard the layout slots above already use. */
.tpv2-dash-grid > * {
  min-width: 0;
}

.tpv2-dash-grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.tpv2-dash-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.tpv2-dash-grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

.tpv2-dash-grid-cell-span-2 { grid-column: span 2; }
.tpv2-dash-grid-cell-span-3 { grid-column: span 3; }
.tpv2-dash-grid-cell-span-full { grid-column: 1 / -1; }

/* ===========================
   Responsive: tablet (<lg)
   Right rail drops under main. Left rail (in -both) stays.
   =========================== */
@media (max-width: 1023px) {
  .tpv2-layout-body-aside {
    grid-template-columns: 1fr;
    grid-template-areas:
      "main"
      "right";
  }
  .tpv2-layout-body-both {
    grid-template-columns: var(--tpv2-rail-left-w, 240px) 1fr;
    grid-template-areas:
      "left main"
      "left right";
  }
  .tpv2-layout-rail-right {
    border-left: none;
    border-top: 1px solid var(--tp-border, #e1e1e1);
  }
  .tpv2-dash-grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tpv2-dash-grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ===========================
   Responsive: mobile (<md)
   Left rail becomes drawer (hidden by default, .is-open shows it as overlay).
   Right rail stays under main. Everything stacks.
   =========================== */
@media (max-width: 767px) {
  /* A left rail that carries *content* stacks under main. It used to be
     `display:none` for every left rail, which made its cells unreachable on a
     phone — there is no trigger for a rail that is not the shell navigation.
     Only `.tpv2-layout-rail-drawer` (navigation, with a trigger in the chrome)
     hides itself. */
  .tpv2-layout-body-sidebar,
  .tpv2-layout-body-both {
    grid-template-columns: 1fr;
    grid-template-areas:
      "main"
      "right"
      "left";
  }
  .tpv2-layout-body-sidebar {
    grid-template-areas:
      "main"
      "left";
  }

  .tpv2-layout-rail-left {
    border-right: none;
    border-top: 1px solid var(--tp-border, #e1e1e1);
  }

  .tpv2-layout-rail-drawer {
    display: none;
  }
  .tpv2-layout-rail-drawer.is-open {
    display: block;
    position: fixed;
    inset: 0 auto 0 0;
    width: min(280px, 86vw);
    z-index: 50;
    box-shadow: var(--tp-elev-2);
    border-right: 1px solid var(--tp-border, #e1e1e1);
  }
  .tpv2-layout-drawer-trigger {
    display: inline-flex;
  }
  .tpv2-dash-grid-2,
  .tpv2-dash-grid-3,
  .tpv2-dash-grid-4 {
    grid-template-columns: 1fr;
  }
  .tpv2-dash-grid-cell-span-2,
  .tpv2-dash-grid-cell-span-3,
  .tpv2-dash-grid-cell-span-full {
    grid-column: span 1;
  }
  .tpv2-layout-main { padding: var(--tp-density-pad-y) var(--tp-density-pad-x); }
  .tpv2-layout-rail-right { padding: var(--tp-density-pad-y) var(--tp-density-pad-x); }
}

/* ===========================
   Mobile drawer scrim (when left rail is open)
   =========================== */
@media (max-width: 767px) {
  .tpv2-layout-drawer-scrim {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    z-index: 49;
  }
  .tpv2-layout-drawer-scrim.is-open {
    display: block;
  }
}

/* ===========================
   ConsoleHub cell span (Plan 0010, D-005)

   A page cell says how much of its row it takes — `span: half` in YAML, never
   raw grid CSS. The widths are percentages of the *slot*, so a cell in a
   narrow rail wraps without anyone declaring a breakpoint: it never needs to
   know how wide its slot is.
   =========================== */
.apps-cell {
  flex: 1 1 100%;
  min-width: 0;
}
.apps-cell.tpv2-cell-half {
  flex-basis: calc(50% - 0.5rem);
}
.apps-cell.tpv2-cell-third {
  flex-basis: calc(33.3333% - 0.6667rem);
}
.apps-cell.tpv2-cell-quarter {
  flex-basis: calc(25% - 0.75rem);
}

/* Below the phone breakpoint every cell takes the row: two KPI tiles side by
   side are unreadable at 390px. */
@media (max-width: 640px) {
  .apps-cell.tpv2-cell-half,
  .apps-cell.tpv2-cell-third,
  .apps-cell.tpv2-cell-quarter {
    flex-basis: 100%;
  }
}
/**
 * TP V2 — Line Items
 *
 * Die Waren eines Vorgangs mit ihrem **Arbeitszustand** (Plan 0057, E8). Bei
 * Shopify leben die Artikel *in* der Ausführungskarte, und das ist der Grund,
 * warum die Seite trotz vieler Fakten ruhig wirkt: die Klammer ist der
 * Zustand, nicht die Tabelle.
 *
 * Nicht `tpv2-table`. Eine Tabelle richtet Spalten über alle Zeilen aus und
 * bezahlt das mit einer Kopfzeile; eine Warenzeile hat einen Bildplatz, eine
 * zweizeilige Bezeichnung und zwei Beträge, und ihre „Spalten" sind für jede
 * Gruppe dieselben drei. Die Fassung als Tabelle brauchte neun Spalten für das,
 * was hier in vier Feldern steht (Plan 0057, E7 §5 andersherum gelesen: die
 * Kartenwand wird zur Tabelle, die Tabelle mit neun Spalten zur Zeile).
 *
 * Nicht `tpv2-facts`. Ein Faktengitter beschreibt EIN Objekt mit vielen
 * Beschriftungen. Hier stehen viele gleichartige Objekte ohne Beschriftung.
 *
 * Verwendung:
 *   <div class="tpv2-li-wrap">
 *     <section class="tpv2-li-g" id="f-904418">
 *       <div class="tpv2-li-gh">
 *         <span class="tpv2-pill tpv2-pill-success tpv2-pill-sm">Versendet</span>
 *         <span class="tpv2-li-gm">DHL · 04.09.2026</span>
 *         <a class="tpv2-li-ga mono" href="#verfolgung">00340434161094876543</a>
 *       </div>
 *       <ul class="tpv2-li-rows">
 *         <li class="tpv2-li-row">
 *           <span class="tpv2-li-thumb" title="Kein Bild am Positions-Schnappschuss">
 *             <i class="fas fa-box"></i>
 *           </span>
 *           <div class="tpv2-li-main">
 *             <a class="tpv2-li-t" href="/produkte/10044218">Warmwasserspeicher 200 l</a>
 *             <p class="tpv2-li-m"><span class="mono">10044218</span> · Physisch</p>
 *             <p class="tpv2-li-st">
 *               <span class="tpv2-pill tpv2-pill-success tpv2-pill-sm">Versendet · DHL</span>
 *               <span class="tpv2-li-stm">04.09.2026 · Nummer aus Sage</span>
 *               <a class="tpv2-li-sa mono" href="#verfolgung">00340434161094876543</a>
 *             </p>
 *             <p class="tpv2-li-n">− 150,00 € Rabatt</p>
 *           </div>
 *           <div class="tpv2-li-p">749,00 € × 1</div>
 *           <div class="tpv2-li-s">749,00 €</div>
 *         </li>
 *       </ul>
 *     </section>
 *   </div>
 *
 * Diese Datei überschreibt keine andere Datei und braucht deshalb keinen
 * `.tpv2-detail`-Vorfahren (anders als detail-page.css): jede Klasse hier ist
 * neu, und die zwei geliehenen — `.tpv2-pill` im Gruppenkopf und `.mono` in
 * der Meta-Zeile — werden nur gelesen, nicht umgeschrieben.
 *
 * Modifikatoren:
 *   .tpv2-li-ga     die eine Kennung der Gruppe (Sendungsnummer), rechts außen
 *   .tpv2-li-st     die Zustandsmarke AN DER ZEILE — „Versendet · DHL"
 *   .tpv2-li-n      ein Hinweis, der GENAU DIESE Zeile betrifft (E7 §2)
 *
 * Der Zustand steht auf zwei Höhen, und die Seite wählt eine (Plan 0057, E8,
 * dritte Fassung): ordnet sie nach WARE, trägt `.tpv2-li-st` die Marke und der
 * Gruppenkopf entfällt; ordnet sie nach EINHEIT — die ERP-Sicht —, ist
 * `.tpv2-li-gh` die Klammer und die Zeilen tragen keine Marke. Beides zugleich
 * sagte denselben Zustand zweimal.
 */

/* Die Zeilen brechen gegen die Breite der Karte, in der sie stehen, nicht
   gegen das Fenster — dieselbe Regel wie beim Faktengitter und der Chronik. */
.tpv2-li-wrap {
  container-type: inline-size;
  container-name: tpli;
  min-width: 0;
}

/* ===========================
   Die Gruppe — ein Arbeitszustand
   =========================== */
/* Der Kopf ist die Aussage der Gruppe; die Zeilen darunter sind ihr Beleg.
   Ohne Kopf — eine Karte, eine Menge — bleibt die Gruppe flach: eine Box um
   den ganzen Karteninhalt wäre ein Rahmen im Rahmen. */
.tpv2-li-g {
  border-top: 1px solid var(--tp-border, #e3e6ea);
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
}

.tpv2-li-g:first-child {
  border-top: none;
}

/* ===========================
   Die Gruppe MIT Kopf — eine Box in der Karte (Plan 0057, E9 §2)
   =========================== */
/* Eine Haarlinie kann „kommt danach" sagen und „gehört zusammen" nicht. Die
   Sendung ist deshalb eine eigene Fläche in der Ausführungskarte, und ihre
   Positionen sind eine Fläche darin: der Zustand oben klammert die Ware
   darunter sichtbar, statt sie nur anzukündigen. */
.tpv2-li-box {
  border-top: none;
  margin: var(--tp-density-pad-y) var(--tp-density-pad-x);
  padding: var(--tp-space-3);
  border: 1px solid var(--tp-border, #e3e6ea);
  border-radius: var(--tp-radius-md);
  /* `--tp-hover-bg` und nicht `--tp-table-header-bg`: im dunklen Thema ist
     der Tabellenkopf **derselbe** Wert wie die Kartenfläche (#161b22), und
     die Klammer wäre dort nur ein Rahmen ohne Fläche. */
  background-color: var(--tp-hover-bg, #f3f4f6);
}

/* Die Ware selbst steht auf der Kartenfläche, damit die Box um sie herum als
   Klammer lesbar bleibt und nicht als zweiter Grauwert. */
.tpv2-li-box > .tpv2-li-rows {
  background-color: var(--tp-surface, #fff);
  border: 1px solid var(--tp-border, #e3e6ea);
  border-radius: var(--tp-radius-sm);
  padding: var(--tp-space-3);
}

.tpv2-li-box > .tpv2-li-gh {
  margin-bottom: var(--tp-space-2);
  padding: 0 var(--tp-space-0-5);
}

.tpv2-li-gh {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--tp-space-2);
  margin-bottom: var(--tp-space-2);
  min-width: 0;
}

/* Was den Zustand qualifiziert: Dienstleister, Datum, Frist. Kurz, sonst
   steht hier ein Satz und die Pille daneben ist nicht mehr die Aussage. */
.tpv2-li-gm {
  font-size: var(--tp-font-size-2);
  color: var(--tp-text-secondary, #61666b);
  min-width: 0;
}

/* Die eine Kennung der Gruppe — die Sendungsnummer. Sie steht rechts außen,
   weil sie das ist, was jemand kopiert. */
.tpv2-li-ga {
  margin-left: auto;
  font-size: var(--tp-font-size-2);
  color: var(--tp-accent, #1d4b8e);
  text-decoration: none;
  overflow-wrap: anywhere;
}

.tpv2-li-ga:hover {
  text-decoration: underline;
}

/* Ohne Verweis bleibt derselbe Platz — eine Nummer ohne Verfolgungsadresse
   ist trotzdem die Nummer. */
span.tpv2-li-ga {
  color: var(--tp-text-secondary, #61666b);
}

/* ===========================
   Die Zeile — eine Ware
   =========================== */
.tpv2-li-rows {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--tp-space-2);
}

.tpv2-li-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto auto;
  align-items: start;
  column-gap: var(--tp-space-3);
  row-gap: var(--tp-space-1);
  min-width: 0;
}

/* Die Zeilen einer Gruppe trennt eine Haarlinie, nicht ein Abstand: vier
   Positionen mit Bildplatz brauchen eine Kante, um als Liste zu lesen. */
.tpv2-li-rows > .tpv2-li-row + .tpv2-li-row {
  border-top: 1px solid var(--tp-border, #e3e6ea);
  padding-top: var(--tp-space-2);
}

/* Der Bildplatz. `OrderLineItem` führt KEIN Bild — die Position ist ein
   Schnappschuss des Bestellzeitpunkts, ein Bild käme über `variantGid` aus dem
   Katalog und wäre eine modulübergreifende Abfrage (Plan 0057, E8). Der Platz
   steht trotzdem: er trägt die Art der Ware als Glyph, und er hält die Spalte,
   in die das Bild später einzieht, ohne dass die Zeile umspringt. */
.tpv2-li-thumb {
  width: 48px;
  height: 48px;
  flex: none;
  display: grid;
  place-items: center;
  border: 1px solid var(--tp-border, #e3e6ea);
  border-radius: var(--tp-radius-sm);
  background-color: var(--tp-hover-bg, #f3f4f6);
  color: var(--tp-text-secondary, #61666b);
  font-size: var(--tp-font-size-2);
}

.tpv2-li-main {
  min-width: 0;
}

.tpv2-li-t {
  display: block;
  font-size: var(--tp-font-size-3);
  font-weight: var(--tp-font-weight-semibold);
  line-height: var(--tp-line-snug);
  color: var(--tp-text, #202223);
  text-decoration: none;
  overflow-wrap: anywhere;
}

a.tpv2-li-t {
  color: var(--tp-accent, #1d4b8e);
}

a.tpv2-li-t:hover {
  text-decoration: underline;
}

.tpv2-li-m {
  margin: var(--tp-space-0-5) 0 0;
  font-size: var(--tp-font-size-2);
  color: var(--tp-text-secondary, #61666b);
  overflow-wrap: anywhere;
}

.tpv2-li-m .mono {
  font-family: var(--tp-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
}

/* ===========================
   Die Marke an der Zeile
   =========================== */
/* Vier Positionen in EINER Karte, jede mit ihrem Zustand: die Marken stehen
   linksbündig unter den Bezeichnungen und bilden dadurch eine Spalte, die man
   herunterlesen kann, ohne die Bestellung in Kisten zerlegt zu bekommen. Sie
   sitzt UNTER der Meta-Zeile, weil sie sich ändert und die SKU nicht — was
   sich bewegt, steht näher am Rand des Blocks. */
.tpv2-li-st {
  margin: var(--tp-space-1) 0 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--tp-space-2);
  min-width: 0;
}

/* Was die Marke qualifiziert: Zeitpunkt, Herkunft der Nummer, Frist. */
.tpv2-li-stm {
  font-size: var(--tp-font-size-2);
  color: var(--tp-text-secondary, #61666b);
  min-width: 0;
}

/* Die Kennung des Zustands — die Sendungsnummer. Anders als am Gruppenkopf
   steht sie NICHT rechts außen: an der Zeile ist rechts die Betragsspalte, und
   eine Nummer, die dorthin ausbricht, liest sich wie ein Betrag. */
.tpv2-li-sa {
  font-size: var(--tp-font-size-2);
  color: var(--tp-accent, #1d4b8e);
  text-decoration: none;
  overflow-wrap: anywhere;
}

.tpv2-li-sa:hover {
  text-decoration: underline;
}

/* Ohne Verfolgungsadresse bleibt derselbe Platz — ein `untracked:`-Platzhalter
   ist trotzdem das, was im Kanal steht. */
span.tpv2-li-sa {
  color: var(--tp-text-secondary, #61666b);
}

/* Ein Hinweis erscheint, wenn DIESE Zeile etwas Ungewöhnliches hat, und steht
   dann an ihr (Plan 0057, E7 §2). Der Punkt davor ist die Marke, an der man
   ihn von der Meta-Zeile unterscheidet, ohne dass er laut wird. */
.tpv2-li-n {
  margin: var(--tp-space-0-5) 0 0;
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  color: var(--tp-text-secondary, #61666b);
}

.tpv2-li-n::before {
  content: '·';
  margin-right: var(--tp-space-1);
}

/* ===========================
   Die Menge als Chip
   =========================== */
/* Die Menge ist die eine Zahl, die jemand sucht, wenn er eine Position gegen
   eine Sendung hält. Als „129,00 € × 2" steht sie hinter dem Betrag und ist
   nicht mehr abzählbar; als Chip bildet sie eine Spalte, die man
   herunterliest (Plan 0057, E9 §3). */
.tpv2-li-q {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.5rem;
  height: 1.5rem;
  padding: 0 var(--tp-space-1-5);
  border-radius: var(--tp-radius-pill);
  background-color: var(--tp-hover-bg, #f3f4f6);
  border: 1px solid var(--tp-border, #e3e6ea);
  color: var(--tp-text-secondary, #61666b);
  font-size: var(--tp-font-size-2);
  font-variant-numeric: var(--tp-numeric);
  line-height: 1;
}

/* ===========================
   Die beiden Beträge
   =========================== */
/* Einzelpreis steht links von der Summe, weil man es liest wie eine Rechnung:
   erst die Aufgabe, dann das Ergebnis. */
.tpv2-li-p,
.tpv2-li-s {
  font-size: var(--tp-font-size-3);
  font-variant-numeric: var(--tp-numeric);
  text-align: right;
  white-space: nowrap;
}

.tpv2-li-p {
  color: var(--tp-text-secondary, #61666b);
}

/* Kein `min-width`: `OrderLineItem` führt keinen Zeilenbetrag, und eine
   reservierte Spalte ohne Inhalt ist ein 88 px breites Loch am rechten Rand
   (Plan 0057, E9 §3 gegen E4 gehalten). */
.tpv2-li-s {
  font-weight: var(--tp-font-weight-semibold);
  color: var(--tp-text, #202223);
}

/* ===========================
   Enger Rahmen
   =========================== */
/* Unter 480 px im Rahmen fallen die Beträge unter die Bezeichnung: vier
   Spalten nebeneinander lassen für den Titel 90 px übrig, und ein
   Produktname in 90 px ist keine Bezeichnung mehr. Der Bildplatz behält
   seine Spalte über beide Zeilen — sonst wandert die Summe unter ihn. */
@container tpli (max-width: 479px) {
  .tpv2-li-row {
    grid-template-columns: auto minmax(0, 1fr);
  }
  .tpv2-li-thumb {
    grid-row: span 2;
  }
  .tpv2-li-qw,
  .tpv2-li-p,
  .tpv2-li-s {
    grid-column: 2;
    text-align: left;
  }
  /* Der Chip steht dann unter der Bezeichnung, der Betrag darunter: in
     einem 390-px-Rahmen ist neben einer zweizeiligen Bezeichnung kein Platz
     für eine dritte Spalte. */
  .tpv2-li-qw {
    margin-top: var(--tp-space-1);
  }
}

/* ===========================
   Lädt
   =========================== */
.tpv2-li-sk .tpv2-li-thumb {
  border-style: dashed;
}
/**
 * TP V2 — Numeric values
 *
 * One shape for every figure on a dashboard: KPI tile, table cell, card
 * footer, rank list. Split out of kpi-tile/data-table because both render the
 * same thing and the rules must not drift apart.
 *
 * Usage:
 *   <span class="tpv2-num">21.643,95 <span class="tpv2-num-u">€</span></span>
 *   <span class="tpv2-num tpv2-num-bad">−1.391,09 <span class="tpv2-num-u">€</span></span>
 *
 * Rules:
 *   • value and unit are one unbreakable pair — "−1.391,09" over "€" in a
 *     narrow rank list broke the row rhythm (plan 0010, D-001d §4.3);
 *   • color carries judgement only: a figure is colored when it is a loss,
 *     never to decorate a positive number (D-001 "Farbe nur, wo sie etwas
 *     bedeutet");
 *   • the unit is dimmed so the digits win the eye.
 */

.tpv2-num {
  white-space: nowrap;
  font-variant-numeric: var(--tp-numeric);
}

/* The unit steps down as well as dimming: at equal size the "€" competed with
   the digits for the same 24 px of attention. Relative (em), so one rule holds
   from the 10 px table cell to the hero tile — a fixed value would need a
   sixth step in a scale that is capped at five (D-00T §3). */
.tpv2-num-u {
  color: var(--tp-num-unit);
  font-weight: var(--tp-font-weight-normal);
  /* floored at the micro step: under a 10 px sub line 0.8em would have put
     "Stk" at 8 px, which is smaller than anything else in the system */
  font-size: max(var(--tp-font-size-1), 0.8em);
  padding-left: 0.05em;
}

.tpv2-num-bad {
  color: var(--tp-delta-bad);
}

.tpv2-num-good {
  color: var(--tp-delta-good);
}

.tpv2-num-muted {
  color: var(--tp-text-secondary);
}

/* ===========================
   Share bar — a figure plus its share of the total
   =========================== */

.tpv2-share {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--tp-space-2);
  min-width: 0;
}

.tpv2-share-track {
  flex: 0 1 72px;
  height: 6px;
  min-width: 24px;
  border-radius: var(--tp-radius-pill);
  background-color: var(--tp-hover-bg);
  overflow: hidden;
}

.tpv2-share-fill {
  display: block;
  height: 100%;
  /* A share that exists is visible. The element is only rendered for a share
     above zero, so this floor cannot turn nothing into something. */
  min-width: 2px;
  border-radius: inherit;
  /* the on-surface role, not the fill role: this bar *is* read against the
     surface, and --tp-primary sits at 2.02:1 on #161b22 */
  background-color: var(--tp-accent);
}

.tpv2-share-l {
  flex: 0 0 auto;
  font-size: var(--tp-font-size-2);
  font-variant-numeric: var(--tp-numeric);
  color: var(--tp-text);
  white-space: nowrap;
}

/* Left-aligned columns put the bar behind the label instead of in front. */
.tpv2-share-start {
  justify-content: flex-start;
}
/**
 * TP V2 — Page Header
 * Title row of a page: headline, description, actions. The V1 twin
 * (`tp-page-header`) stays where it is; this one runs on the D-00T scale so
 * a page header sits on the same rhythm as card, table and KPI tile.
 *
 * Usage:
 *   <div class="tpv2-page-h">
 *     <div class="tpv2-page-h-text">
 *       <h1 class="tpv2-page-h-title">Händler-Startseite</h1>
 *       <p class="tpv2-page-h-desc">Verkaufsübersicht und Kennzahlen</p>
 *     </div>
 *     <div class="tpv2-page-h-actions">…</div>
 *   </div>
 *
 * Modifier:
 *   .tpv2-page-h-compact  — title only, no bottom margin (inside a toolbar)
 */

.tpv2-page-h {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--tp-density-gap);
  margin-bottom: var(--tp-space-5);
  font-family: inherit;
}

.tpv2-page-h-text {
  min-width: 0;
}

/* Both slots may hold something that does not wrap (a long headline, a
   segmented track). Without min-width:0 that content sets the header width and
   pushes the whole page sideways on a phone. */
.tpv2-page-h,
.tpv2-page-h-actions {
  min-width: 0;
}

.tpv2-page-h-title {
  margin: 0;
  font-size: 24px;
  letter-spacing: -0.025em;
  overflow-wrap: anywhere;
  font-weight: var(--tp-font-weight-semibold);
  line-height: var(--tp-line-tight);
  color: var(--tp-text, #202223);
}

.tpv2-page-h-desc {
  margin: var(--tp-space-0-5) 0 0;
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  color: var(--tp-text-secondary, #6d7175);
}

/* Actions keep their own line on narrow viewports (see media query). */
.tpv2-page-h-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--tp-space-2);
  margin-left: auto;
}

.tpv2-page-h-compact .tpv2-page-h-title {
  font-size: var(--tp-font-size-4);
}

.tpv2-page-h-compact {
  margin-bottom: 0;
}

@media (max-width: 767px) {
  .tpv2-page-h-actions {
    margin-left: 0;
    width: 100%;
  }
}
/**
 * TP Design System - Pill (Badge / Status)
 * The one small status indicator: table cells, detail views, status bars,
 * identity strips, rail cards and primary entity state.
 *
 * Usage:
 *   <span class="tpv2-pill tpv2-pill-success">Bezahlt</span>
 *   <span class="tpv2-pill tpv2-pill-warning"><i class="fas fa-truck"></i> In Zustellung</span>
 *   <span class="tpv2-pill tpv2-pill-success tpv2-pill-live"><span class="tpv2-pill-dot"></span> Live</span>
 *   <span class="tpv2-pill tpv2-pill-info tpv2-pill-sm">3</span>
 *   <span class="tpv2-pill-status tpv2-pill-status-open"><i class="fas fa-dot-circle"></i> Offen</span>
 *
 * Variants:
 *   tpv2-pill-success | -warning | -danger | -info | -purple | -gray (default)
 *
 * Modifiers:
 *   tpv2-pill-sm    smaller (count chips, tab badges)
 *   tpv2-pill-lg    larger  (prominent status)
 *   tpv2-pill-live  pulsing dot for live indicators
 *   tpv2-pill-dot   inline live dot inside a pill
 *
 * ── ÜBERGANG (Plan 0010, D-002c) ────────────────────────────────────────────
 * Every rule below carries the V1 names `.tp-badge*` / `.tp-status-badge*` as
 * additional selectors, and `@tp/ui/badge` resolves to THIS file. Reason: 20
 * web crates import `@tp/ui/badge` and 57 hand-written badges sit in their
 * Askama templates. One definition, two vocabularies — so a hub can never show
 * an unstyled or a second-shaped badge, no matter when it is rebuilt.
 * Removal: delete the `.tp-badge*` / `.tp-status-badge*` selectors here, the
 * `./badge` export and this block once no template writes a V1 name any more
 * (rollout plan in plans/0010-…/increments/D-002c-badge-v2.md).
 * `.tp-badge-neutral|-gray|-blue|-green` are aliases too: -gray/-blue/-green
 * were never defined in V1 and rendered colourless in 4 hubs.
 * ────────────────────────────────────────────────────────────────────────────
 */

/* ===========================
   Base Pill
   =========================== */
.tpv2-pill,
.tp-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--tp-space-1, 4px);
  padding: var(--tp-space-0-5, 2px) var(--tp-space-2, 8px);
  border-radius: var(--tp-radius-pill, 999px);
  font-size: var(--tp-font-size-2, 12px);
  font-weight: var(--tp-font-weight-semibold, 600);
  line-height: var(--tp-line-snug, 1.4);
  white-space: nowrap;
  border: 1px solid transparent;
  font-family: inherit;
  /* Default = gray */
  background-color: var(--tp-badge-gray-bg, #f3f4f6);
  color: var(--tp-badge-gray-text, #374151);
}

/* Inline icon inside a pill (FontAwesome) */
.tpv2-pill i,
.tp-badge i {
  font-size: var(--tp-font-size-1, 10px);
}

/* Button-flavoured pill (clickable) */
button.tpv2-pill,
button.tp-badge {
  cursor: pointer;
}
button.tpv2-pill:hover,
button.tp-badge:hover {
  filter: brightness(0.97);
}

/* ===========================
   Semantic Variants
   =========================== */
.tpv2-pill-success,
.tp-badge-success,
.tp-badge-green {
  background-color: var(--tp-badge-success-bg, #dcfce7);
  color: var(--tp-badge-success-text, #166534);
}

.tpv2-pill-warning,
.tp-badge-warning {
  background-color: var(--tp-badge-warning-bg, #fef3c7);
  color: var(--tp-badge-warning-text, #92400e);
}

.tpv2-pill-danger,
.tp-badge-danger {
  background-color: var(--tp-badge-danger-bg, #fee2e2);
  color: var(--tp-badge-danger-text, #991b1b);
}

.tpv2-pill-info,
.tp-badge-info,
.tp-badge-blue {
  background-color: var(--tp-badge-info-bg, #dbeafe);
  color: var(--tp-badge-info-text, #1e40af);
}

.tpv2-pill-purple {
  background-color: var(--tp-badge-purple-bg, #f3e8ff);
  color: var(--tp-badge-purple-text, #6b21a8);
}

.tpv2-pill-gray,
.tp-badge-neutral,
.tp-badge-gray {
  background-color: var(--tp-badge-gray-bg, #f3f4f6);
  color: var(--tp-badge-gray-text, #374151);
}

/* Optional outlined variant (when used in status-bar). The border is mixed
   from the tone's own text color, so it follows the theme. */
.tpv2-pill-outlined.tpv2-pill-success {
  border-color: color-mix(in srgb, var(--tp-badge-success-text, #166534) 35%, transparent);
}
.tpv2-pill-outlined.tpv2-pill-warning {
  border-color: color-mix(in srgb, var(--tp-badge-warning-text, #92400e) 35%, transparent);
}
.tpv2-pill-outlined.tpv2-pill-danger {
  border-color: color-mix(in srgb, var(--tp-badge-danger-text, #991b1b) 35%, transparent);
}
.tpv2-pill-outlined.tpv2-pill-info {
  border-color: color-mix(in srgb, var(--tp-badge-info-text, #1e40af) 35%, transparent);
}
.tpv2-pill-outlined.tpv2-pill-purple {
  border-color: color-mix(in srgb, var(--tp-badge-purple-text, #6b21a8) 35%, transparent);
}

/* ===========================
   Size Modifiers
   =========================== */
.tpv2-pill-sm {
  padding: var(--tp-space-0-5, 2px) var(--tp-space-2, 8px);
  font-size: var(--tp-font-size-2, 12px);
}

.tpv2-pill-lg {
  padding: var(--tp-space-1, 4px) var(--tp-space-3, 12px);
  font-size: var(--tp-font-size-2, 12px);
}
.tpv2-pill-lg i { font-size: var(--tp-font-size-2, 12px); }

/* ===========================
   Live Indicator
   Pulsing dot for "live updating" pills.
   =========================== */
.tpv2-pill-live .tpv2-pill-dot {
  background-color: currentColor;
  animation: tpv2-pill-blink 1.4s ease-in-out infinite;
}

.tpv2-pill-dot,
.tp-badge-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background-color: currentColor;
  flex-shrink: 0;
}

/* Hollow ring instead of a filled dot (Plan 0053).
   Die Punktform trägt Bedeutung neben der Farbe: gefüllt = abgeschlossener
   Zustand, hohl = noch offen. Wer Bernstein von Grau nicht trennen kann, liest
   den Unterschied trotzdem — Farbe allein wäre die einzige Aussage. */
.tpv2-pill-dot-hollow {
  background-color: transparent;
  box-shadow: inset 0 0 0 1.5px currentColor;
}

@keyframes tpv2-pill-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.3; }
}

/* ===========================
   Status Pill
   Primary entity state (issue open/closed, job running). Solid on purpose —
   it is the loudest thing on a detail page, where a tinted cell badge would
   disappear. Both fill and glyph come from tokens, so the pair inverts in
   dark mode instead of leaving a dark island on a dark page.
   =========================== */
.tpv2-pill-status,
.tp-status-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--tp-space-1-5, 6px);
  padding: var(--tp-space-1-5, 6px) var(--tp-space-3, 12px);
  border-radius: var(--tp-radius-pill, 999px);
  font-size: var(--tp-font-size-3, 14px);
  font-weight: var(--tp-font-weight-semibold, 600);
  line-height: var(--tp-line-snug, 1.4);
  white-space: nowrap;
  border: 1px solid transparent;
  font-family: inherit;
  background-color: var(--tp-status-draft, #6e7681);
  color: var(--tp-status-fg, #ffffff);
}

.tpv2-pill-status-icon,
.tp-status-badge-icon {
  font-size: var(--tp-font-size-2, 12px);
}

.tpv2-pill-status-open,
.tp-status-badge-open {
  background-color: var(--tp-status-open, #238636);
}

.tpv2-pill-status-closed,
.tp-status-badge-closed {
  background-color: var(--tp-status-closed, #8957e5);
}

.tpv2-pill-status-merged,
.tp-status-badge-merged {
  background-color: var(--tp-status-merged, #8957e5);
}

.tpv2-pill-status-draft,
.tp-status-badge-draft {
  background-color: var(--tp-status-draft, #6e7681);
}

.tpv2-pill-status-in-progress,
.tp-status-badge-in-progress {
  background-color: var(--tp-status-progress, #1f6feb);
}

.tpv2-pill-status-resolved,
.tp-status-badge-resolved {
  background-color: var(--tp-status-resolved, #9a4c07);
}

/* ===========================
   Pill Group
   Use to space multiple pills side-by-side.
   =========================== */
.tpv2-pill-group,
.tp-badge-group {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--tp-space-1-5, 6px);
  align-items: center;
}
/**
 * TP V2 — Resource List (Plan 0053, W1)
 * Die Liste einer Ressource: gespeicherte Sichten, Token-Filter, Sortierung,
 * Zeilenauswahl, Seitenwechsel. Vorbild ist die Bestellliste des Shopify-Admin.
 *
 * Diese Datei baut KEINE zweite Tabelle. Der Rumpf ist `.tpv2-table` aus
 * data-table.css (plus `-compact`, `-hover`, `-sticky`); hier stehen nur die
 * Dinge, die eine Liste über eine Tabelle hinaus hat — die Auswahl-Spalte, die
 * gedimmte erledigte Zeile, die Massenleiste über der Kopfzeile.
 *
 * Markup (gerendert von shopcloud_ui::patterns::ResourceList):
 *   <section class="tpv2-rlist" id="orders">
 *     <nav class="tpv2-rlist-views">…</nav>
 *     <form class="tpv2-rlist-bar">…</form>
 *     <div class="tpv2-rlist-applied">…</div>
 *     <div class="tpv2-rlist-tablewrap">
 *       <div class="tpv2-rlist-bulk">…</div>
 *       <div class="tpv2-rlist-scroll"><table class="tpv2-table tpv2-rlist-table">…</table></div>
 *     </div>
 *     <div class="tpv2-rlist-foot">…</div>
 *   </section>
 *
 * Stellschrauben:
 *   --tpv2-rlist-head-h    Höhe der Kopfzeile; die Massenleiste deckt genau
 *                          diese Höhe ab, sonst springt die Tabelle beim Haken.
 *   --tpv2-rlist-max-h     Höhe, ab der der Zeilenbereich selbst scrollt.
 *                          Ungesetzt scrollt die Seite und die Kopfzeile
 *                          klebt nicht — der Grund steht bei .tpv2-rlist-scroll.
 */

.tpv2-rlist {
  --tpv2-rlist-head-h: calc(var(--tp-density-control-h) + var(--tp-space-1-5));
  background-color: var(--tp-surface, #fff);
  border: 1px solid var(--tp-border, #e3e6ea);
  border-radius: var(--tp-radius-md);
  box-shadow: var(--tp-elev-1);
  font-family: inherit;
  color: var(--tp-text, #202223);
}

/* ===========================
   Sichten-Reiter
   Der aktive Reiter ist eine weiße Pille auf grauem Grund — die Fläche ist
   das, was ihn vom bloßen Text der anderen trennt; Farbe allein trüge das bei
   einem Wort Unterschied nicht.
   =========================== */
.tpv2-rlist-views {
  display: flex;
  align-items: center;
  gap: var(--tp-space-0-5);
  padding: var(--tp-space-1-5) var(--tp-space-2);
  background-color: var(--tp-surface, #fff);
  border-bottom: 1px solid var(--tp-border, #e3e6ea);
  border-radius: var(--tp-radius-md) var(--tp-radius-md) 0 0;
  overflow-x: auto;
  scrollbar-width: none;
}

.tpv2-rlist-views::-webkit-scrollbar {
  display: none;
}

.tpv2-rlist-view {
  display: inline-flex;
  align-items: center;
  gap: var(--tp-space-1-5);
  padding: var(--tp-space-1) var(--tp-space-3);
  border-radius: var(--tp-radius-sm);
  color: var(--tp-text-secondary, #61666b);
  font-size: var(--tp-font-size-2);
  font-weight: var(--tp-font-weight-medium);
  line-height: var(--tp-line-snug);
  white-space: nowrap;
  text-decoration: none;
  transition: background-color 0.12s, color 0.12s;
}

.tpv2-rlist-view:hover {
  background-color: var(--tp-hover-bg, #f3f4f6);
  color: var(--tp-text, #202223);
}

.tpv2-rlist-view-active,
.tpv2-rlist-view[aria-current="page"] {
  background-color: var(--tp-hover-bg, #f3f4f6);
  color: var(--tp-text, #202223);
  font-weight: var(--tp-font-weight-semibold);
  box-shadow: var(--tp-elev-1);
}

/* Der Zähler ist ein Versprechen: er steht nur da, wo die Zahl bekannt ist. */
.tpv2-rlist-view-ct {
  font-size: var(--tp-font-size-1);
  font-weight: var(--tp-font-weight-medium);
  font-variant-numeric: var(--tp-numeric);
  color: var(--tp-text-secondary, #61666b);
}

.tpv2-rlist-view-add {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tp-space-6);
  height: var(--tp-space-6);
  margin-left: var(--tp-space-1);
  border-radius: var(--tp-radius-sm);
  color: var(--tp-text-secondary, #61666b);
  font-size: var(--tp-font-size-2);
  text-decoration: none;
}

.tpv2-rlist-view-add:hover {
  background-color: var(--tp-hover-bg, #f3f4f6);
  color: var(--tp-text, #202223);
}

.tpv2-rlist-view:focus-visible,
.tpv2-rlist-view-add:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--tp-focus-ring, rgba(29, 75, 142, 0.2));
}

/* ===========================
   Filterleiste
   Eine Fläche, nicht sechs Felder: Bereich, Suche, Token und das Leeren
   gehören zur selben Frage und stehen deshalb in einem Rahmen.
   =========================== */
.tpv2-rlist-bar {
  display: flex;
  align-items: center;
  gap: var(--tp-space-2);
  padding: var(--tp-space-2);
  border-bottom: 1px solid var(--tp-border, #e3e6ea);
}

.tpv2-rlist-field {
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  gap: var(--tp-space-1-5);
  min-width: 0;
  min-height: var(--tp-density-control-h);
  padding: var(--tp-space-0-5) var(--tp-space-1-5) var(--tp-space-0-5) 0;
  background-color: var(--tp-surface, #fff);
  border: 1px solid var(--tp-input-border, #d1d5db);
  border-radius: var(--tp-radius-md);
  flex-wrap: wrap;
}

.tpv2-rlist-field:focus-within {
  border-color: var(--tp-accent, #1d4b8e);
  box-shadow: 0 0 0 3px var(--tp-focus-ring, rgba(29, 75, 142, 0.2));
}

.tpv2-rlist-scope {
  display: inline-flex;
  align-items: center;
  gap: var(--tp-space-1);
  align-self: stretch;
  padding: 0 var(--tp-space-3);
  border-right: 1px solid var(--tp-border, #e3e6ea);
  color: var(--tp-text-secondary, #61666b);
  font-size: var(--tp-font-size-2);
  font-weight: var(--tp-font-weight-medium);
  white-space: nowrap;
}

.tpv2-rlist-search {
  flex: 0 1 220px;
  min-width: 80px;
  border: 0;
  background: transparent;
  color: var(--tp-text, #202223);
  font-family: inherit;
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  padding: var(--tp-space-1) 0;
}

.tpv2-rlist-search:focus {
  outline: none;
}

.tpv2-rlist-search::placeholder {
  color: var(--tp-text-secondary, #61666b);
}

/* Die Token stehen links neben der Suche und nicht am rechten Rand: sie
   lesen sich als Satz („Zahlungsstatus ist Bezahlt“) und ein Satz beginnt
   dort, wo das Auge ohnehin steht. */
.tpv2-rlist-tokens {
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  gap: var(--tp-space-1-5);
  flex-wrap: wrap;
  min-width: 0;
}

.tpv2-rlist-clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tp-space-5);
  height: var(--tp-space-5);
  margin-left: auto;
  border: 0;
  border-radius: var(--tp-radius-pill);
  background: transparent;
  color: var(--tp-text-secondary, #61666b);
  font-size: var(--tp-font-size-2);
  cursor: pointer;
  text-decoration: none;
}

.tpv2-rlist-clear:hover {
  background-color: var(--tp-hover-bg, #f3f4f6);
  color: var(--tp-text, #202223);
}

/* ===========================
   Token — „Zahlungsstatus ist Bezahlt“
   Ein gesetzter Filter liest sich als Satz. Der Operator steht mager zwischen
   Feld und Wert, damit die drei Teile nicht zu einem Wort verschmelzen.
   =========================== */
.tpv2-rlist-token {
  position: relative;
}

.tpv2-rlist-token-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--tp-space-1-5);
  max-width: 100%;
  padding: 0 var(--tp-space-2);
  min-height: calc(var(--tp-density-control-h) - var(--tp-space-1-5));
  border: 1px solid var(--tp-border, #e3e6ea);
  border-radius: var(--tp-radius-pill);
  background-color: var(--tp-surface, #fff);
  color: var(--tp-text, #202223);
  font-family: inherit;
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-none);
  white-space: nowrap;
  cursor: pointer;
}

.tpv2-rlist-token-btn:hover {
  background-color: var(--tp-hover-bg, #f3f4f6);
}

.tpv2-rlist-token-btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--tp-focus-ring, rgba(29, 75, 142, 0.2));
}

.tpv2-rlist-token-btn[aria-expanded="true"] {
  border-color: var(--tp-accent, #1d4b8e);
}

/* Gesetzt: die Pille bekommt Ton, damit sie sich von den ungenutzten
   Filterknöpfen daneben trennt. */
.tpv2-rlist-token-on .tpv2-rlist-token-btn {
  background-color: var(--tp-badge-info-bg, #dbeafe);
  border-color: transparent;
  color: var(--tp-badge-info-text, #1e40af);
  font-weight: var(--tp-font-weight-medium);
}

.tpv2-rlist-token-op {
  color: var(--tp-text-secondary, #61666b);
  font-weight: var(--tp-font-weight-normal);
}

.tpv2-rlist-token-on .tpv2-rlist-token-op {
  color: inherit;
  opacity: 0.75;
}

.tpv2-rlist-token-v {
  font-weight: var(--tp-font-weight-semibold);
  overflow: hidden;
  text-overflow: ellipsis;
}

.tpv2-rlist-token-btn i {
  font-size: var(--tp-font-size-1);
  opacity: 0.7;
}

.tpv2-rlist-search-icon {
  margin-left: var(--tp-space-3);
  color: var(--tp-text-secondary, #61666b);
  font-size: var(--tp-font-size-2);
  flex-shrink: 0;
}

/* Der Zeitraum bringt sein eigenes Feld mit (DateRangeField) und wird als
   Token eingehängt statt nachgebaut. Es ist als Formularfeld gedacht und
   deshalb 100 % breit — in der Token-Reihe muss es sich auf seine
   Beschriftung zurückziehen. */
.tpv2-rlist-tokens .tpv2-date-range {
  flex: 0 0 auto;
  width: auto;
}

.tpv2-rlist-tokens .tpv2-date-range__trigger {
  width: auto;
  justify-content: flex-start;
}

/* ===========================
   Auswahlfeld eines Tokens
   =========================== */
.tpv2-rlist-pop {
  position: absolute;
  z-index: 30;
  top: calc(100% + var(--tp-space-1));
  left: 0;
  min-width: 240px;
  max-width: 320px;
  padding: var(--tp-space-1) 0;
  background-color: var(--tp-surface, #fff);
  border: 1px solid var(--tp-border, #e3e6ea);
  border-radius: var(--tp-radius-lg);
  box-shadow: var(--tp-elev-2);
}

.tpv2-rlist-pop-list {
  max-height: 260px;
  overflow-y: auto;
  padding: var(--tp-space-1) var(--tp-space-1);
}

.tpv2-rlist-opt {
  display: flex;
  align-items: center;
  gap: var(--tp-space-2);
  padding: var(--tp-space-1-5) var(--tp-space-2);
  border-radius: var(--tp-radius-sm);
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  color: var(--tp-text, #202223);
  cursor: pointer;
}

.tpv2-rlist-opt:hover {
  background-color: var(--tp-hover-bg, #f3f4f6);
}

.tpv2-rlist-opt input {
  margin: 0;
  flex-shrink: 0;
  accent-color: var(--tp-primary, #1d4b8e);
}

.tpv2-rlist-pop-sep {
  height: 1px;
  margin: var(--tp-space-1) 0;
  background-color: var(--tp-border, #e3e6ea);
}

/* Ist / Ist nicht — der aktive Modus trägt ein Häkchen, nicht nur eine
   Färbung: „ist nicht“ dreht die Bedeutung der Liste darüber um. */
.tpv2-rlist-mode {
  display: flex;
  align-items: center;
  gap: var(--tp-space-2);
  padding: var(--tp-space-1-5) var(--tp-space-3);
  font-size: var(--tp-font-size-2);
  color: var(--tp-text, #202223);
  cursor: pointer;
}

.tpv2-rlist-mode:hover {
  background-color: var(--tp-hover-bg, #f3f4f6);
}

.tpv2-rlist-mode input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.tpv2-rlist-mode-tick {
  width: var(--tp-space-3);
  color: var(--tp-accent, #1d4b8e);
  font-size: var(--tp-font-size-2);
  visibility: hidden;
}

.tpv2-rlist-mode input:checked ~ .tpv2-rlist-mode-tick {
  visibility: visible;
}

.tpv2-rlist-mode:focus-within {
  background-color: var(--tp-hover-bg, #f3f4f6);
}

.tpv2-rlist-pop-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--tp-space-2);
  padding: var(--tp-space-2) var(--tp-space-3) var(--tp-space-1);
  border-top: 1px solid var(--tp-border, #e3e6ea);
}

.tpv2-rlist-pop-text {
  padding: var(--tp-space-2) var(--tp-space-3);
}

/* ===========================
   Angewandte Filter
   =========================== */
.tpv2-rlist-applied {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--tp-space-2);
  padding: var(--tp-space-2) var(--tp-space-2) var(--tp-space-2)
    var(--tp-space-3);
  border-bottom: 1px solid var(--tp-border, #e3e6ea);
}

.tpv2-rlist-chip {
  background-color: var(--tp-badge-info-bg, #dbeafe);
  border-color: transparent;
  color: var(--tp-badge-info-text, #1e40af);
  padding-right: var(--tp-space-1);
}

.tpv2-rlist-chip-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tp-space-4);
  height: var(--tp-space-4);
  border-radius: var(--tp-radius-pill);
  color: inherit;
  font-size: var(--tp-font-size-1);
  text-decoration: none;
}

.tpv2-rlist-chip-x:hover {
  background-color: color-mix(
    in srgb,
    var(--tp-badge-info-text, #1e40af) 18%,
    transparent
  );
}

/* ===========================
   Tabelle
   =========================== */
.tpv2-rlist-tablewrap {
  position: relative;
}

/* Zehn Spalten passen nicht auf jedes Fenster, also scrollt der Rahmen
   seitwärts. Das kostet die am Fenster klebende Kopfzeile: sobald ein
   Vorfahr die Achse abschneidet, ist ER der Bezug von `position: sticky`,
   nicht das Fenster — mit `overflow-y: clip` stand die Kopfzeile deshalb um
   die Topbar-Höhe versetzt mitten in den Zeilen.
   Deshalb die Stellschraube: ungesetzt scrollt die Seite und die Kopfzeile
   läuft mit hinaus; setzt die Seite --tpv2-rlist-max-h, scrollt der
   Zeilenbereich selbst und die Kopfzeile klebt an seinem oberen Rand. Beides
   ohne eine Zeile JavaScript ist in CSS nicht zu haben. */
.tpv2-rlist-scroll {
  overflow: auto;
  max-height: var(--tpv2-rlist-max-h, none);
}

.tpv2-rlist-table {
  width: 100%;
}

/* Spaltentitel wie in der Vorlage: normale Groß-/Kleinschreibung, ungesperrt,
   gedämpftes Grau auf der Kartenfläche mit einer Trennlinie darunter.
   `.tpv2-table th` schreibt Versalien mit Sperrung — das ist die Sprache einer
   Kennzahlentabelle; über einer Liste, die man Zeile für Zeile liest, ist es
   der laute Teil eines Streifens, den niemand liest. Die Fläche muss deckend
   bleiben: die Kopfzeile klebt und schiebt Zeilen unter sich hindurch. */
.tpv2-rlist-table thead th {
  top: 0;
  height: var(--tpv2-rlist-head-h);
  padding-top: 0;
  padding-bottom: 0;
  background-color: var(--tp-surface, #fff);
  border-bottom: 1px solid var(--tp-border, #e3e6ea);
  color: var(--tp-text-secondary, #61666b);
  font-size: var(--tp-font-size-2);
  font-weight: var(--tp-font-weight-medium);
  text-transform: none;
  letter-spacing: normal;
}

/* `.tpv2-table thead tr` färbt die ganze Zeile; die Kopfzeile der Liste steht
   auf der Kartenfläche, sonst schimmert der graue Streifen zwischen den
   Zellen durch. */
.tpv2-rlist-table thead tr {
  background-color: var(--tp-surface, #fff);
  border-bottom: none;
}

.tpv2-rlist-table td {
  font-size: var(--tp-font-size-3);
  color: var(--tp-text, #202223);
  padding-top: var(--tp-space-3);
  padding-bottom: var(--tp-space-3);
  vertical-align: middle;
  white-space: nowrap;
}

.tpv2-rlist-table tbody tr:last-child td {
  border-bottom: 1px solid var(--tp-border, #e3e6ea);
}

/* Die Kennung ist der Einstieg in die Zeile — halbfett, damit das Auge beim
   Überfliegen an ihr hängen bleibt. */
.tpv2-rlist-table .cell-id a {
  font-family: var(--tp-font-sans, inherit);
  color: var(--tp-text, #202223);
  font-weight: var(--tp-font-weight-semibold);
}

.tpv2-rlist-table .cell-sub {
  display: block;
  margin-top: var(--tp-space-1);
  font-size: var(--tp-font-size-2);
  color: var(--tp-text-secondary, #61666b);
  /* Die Zelle bricht nicht um (`td { white-space: nowrap }`), die zweite
     Zeile darf es (Plan 0064 K-47): unter „Fehlgeschlagen" steht der Grund,
     und ein Satz, der aus der Spalte hinausläuft, ist keine Auskunft. Die
     Breite ist gedeckelt, damit ein langer Satz die Nachbarspalten nicht
     wegschiebt; der ganze Text steht im `title`. */
  white-space: normal;
  max-width: 22rem;
}

/* Erledigt. Die ganze Zeile tritt zurück, statt einen Haken irgendwo hinten
   zu tragen: was fertig ist, muss beim Überfliegen nicht gelesen werden.
   Die Kennung dimmt mit — sie ist der auffälligste Wert der Zeile, und eine
   blaue Kennung in einer grauen Zeile zöge das Auge genau dorthin zurück.
   Die Selektoren sind absichtlich lang: `.tpv2-table .cell-id a` färbt sonst
   stärker. */
.tpv2-rlist-row-muted td,
.tpv2-rlist-row-muted td .tpv2-num,
.tpv2-rlist-table .tpv2-rlist-row-muted td a {
  color: var(--tp-text, #202223);
}

.tpv2-rlist-row-muted td .tpv2-pill {
  opacity: 1;
}

.tpv2-rlist-check {
  width: 1%;
  padding-right: 0 !important;
}

.tpv2-rlist-check input {
  display: block;
  margin: 0;
  accent-color: var(--tp-primary, #1d4b8e);
}

.tpv2-rlist-time {
  white-space: nowrap;
}

/* Tags bleiben in einer Zeile. Umgebrochen wäre die Zeile mit zwei Tags
   doppelt so hoch wie die daneben, und eine Liste, deren Zeilenhöhe vom
   Inhalt der letzten Spalte abhängt, liest sich nicht mehr im Überflug. */
.tpv2-rlist-badges {
  display: inline-flex;
  flex-wrap: nowrap;
  gap: var(--tp-space-1);
  align-items: center;
}

/* ===========================
   Massenauswahl
   Deckt genau die Kopfzeile ab. Eine Leiste, die die Kopfzeile verschiebt,
   ließe die Tabelle beim ersten Haken springen.
   =========================== */
.tpv2-rlist-bulk {
  position: absolute;
  z-index: 3;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  gap: var(--tp-space-3);
  height: var(--tpv2-rlist-head-h);
  padding: 0 var(--tp-density-cell-x);
  background-color: var(--tp-highlight, #e3f2fd);
  border-bottom: 1px solid var(--tp-border, #e3e6ea);
}

.tpv2-rlist-bulk-count {
  font-size: var(--tp-font-size-2);
  font-weight: var(--tp-font-weight-semibold);
  font-variant-numeric: var(--tp-numeric);
  color: var(--tp-text, #202223);
  white-space: nowrap;
}

.tpv2-rlist-bulk-actions {
  display: flex;
  align-items: center;
  gap: var(--tp-space-2);
  margin-left: auto;
  flex-wrap: nowrap;
  overflow-x: auto;
}

/* ===========================
   Fuß — Blättern
   =========================== */
/* Drei Spalten statt einer zentrierten Flex-Reihe.
   Die Pfeile stehen in der mittleren Spalte und damit optisch mittig — die
   beiden 1fr-Spalten links und rechts sind immer gleich breit, egal ob in
   einer davon etwas steht. Mit `justify-content: center` und einem
   `margin-right: auto` am linken Element säße die Mitte genau dann falsch,
   wenn links etwas steht: ein automatischer Rand schluckt den freien Raum
   ganz und schöbe die Pfeile nach rechts. */
.tpv2-rlist-foot {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--tp-space-3);
  padding: var(--tp-space-2) var(--tp-space-3);
  border-top: 1px solid var(--tp-border, #e3e6ea);
}

/* Der Steuer-Slot: Seitengröße, später vielleicht ein Export. Was darin
   steht, baut der Aufrufer aus Komponenten dieser Bibliothek. */
.tpv2-rlist-foot-lead {
  grid-column: 1;
  justify-self: start;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--tp-space-2);
}

.tpv2-rlist-pager {
  grid-column: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--tp-space-3);
}

.tpv2-rlist-range {
  font-size: var(--tp-font-size-2);
  font-variant-numeric: var(--tp-numeric);
  color: var(--tp-text-secondary, #61666b);
  white-space: nowrap;
}

/* ===========================
   Leer und Laden
   =========================== */
.tpv2-rlist-empty {
  padding: var(--tp-space-8) var(--tp-space-4);
}

.tpv2-rlist[aria-busy="true"] .tpv2-rlist-table tbody {
  pointer-events: none;
}

@media (max-width: 700px) {
  /* Drei Spalten auf einem Telefon heißt drei zu schmale Spalten. */
  .tpv2-rlist-foot {
    grid-template-columns: 1fr;
    justify-items: center;
    gap: var(--tp-space-2);
  }

  .tpv2-rlist-foot-lead,
  .tpv2-rlist-pager {
    grid-column: 1;
    justify-self: center;
  }

  .tpv2-rlist-bar {
    align-items: stretch;
  }

  .tpv2-rlist-field {
    flex-wrap: wrap;
  }

  .tpv2-rlist-pop {
    left: auto;
    right: 0;
  }
}

/* Search stays visible; less frequent filters disclose as one group. */
.tpv2-rlist-bar { flex-wrap: wrap; padding: var(--tp-space-3); }
.tpv2-rlist-search { flex: 1 1 220px; min-height: 32px; font-size: var(--tp-font-size-3); }
.tpv2-rlist-filters { min-width: 0; }
.tpv2-rlist-filters > summary {
  display: flex; align-items: center; gap: var(--tp-space-2);
  list-style: none; cursor: pointer; min-height: 36px;
  padding: 0 var(--tp-space-3); border: 1px solid var(--tp-border);
  border-radius: var(--tp-radius-md); color: var(--tp-text);
  font-size: var(--tp-font-size-2); font-weight: var(--tp-font-weight-semibold);
}
.tpv2-rlist-filters > summary::-webkit-details-marker { display: none; }
.tpv2-rlist-filters > summary:focus-visible { outline: 2px solid var(--tp-accent); outline-offset: 2px; }
.tpv2-rlist-filters[open] { flex-basis: 100%; }
.tpv2-rlist-filters[open] > summary { width: fit-content; background: var(--tp-hover-bg); margin-bottom: var(--tp-space-3); }
.tpv2-rlist-filters .tpv2-rlist-tokens { gap: var(--tp-space-2); }
/**
 * TP Design System - Right Rail
 * Sticky right-side panel shown next to the main content of a detail
 * cockpit. Contains a stack of small cards showing the entity's live
 * state ("right now"), linkages, recent activity, recommended actions.
 *
 * Typically used inside a 2-column grid:
 *   grid-template-columns: 1fr 320px;
 *
 * Usage:
 *   <aside class="tpv2-rail">
 *     <div class="tpv2-rail-header">
 *       <i class="fas fa-bullseye"></i> Right Now
 *       <span class="tpv2-pill tpv2-pill-success tpv2-pill-live tpv2-pill-sm">
 *         <span class="tpv2-pill-dot"></span>Live
 *       </span>
 *     </div>
 *
 *     <div class="tpv2-rail-body">
 *       <div class="tpv2-rail-alert">
 *         <i class="fas fa-magnifying-glass"></i>
 *         <div><strong>NF-2026-0813</strong><br>26 h / 48 h SLA</div>
 *       </div>
 *
 *       <div class="tpv2-rail-card">
 *         <div class="tpv2-rail-card-header">
 *           <span>Kunde</span>
 *           <a href="#">Profil →</a>
 *         </div>
 *         <div class="tpv2-rail-row">
 *           <span class="k">Lifetime</span>
 *           <span class="v">4 Bestellungen</span>
 *         </div>
 *       </div>
 *     </div>
 *   </aside>
 *
 * Sticky offset: the parent page sets --tpv2-rail-offset to push the rail
 * below the topbar / identity-strip / view-tabs. Default is 0.
 */

/* ===========================
   Rail Container
   =========================== */
.tpv2-rail {
  position: sticky;
  top: var(--tpv2-rail-offset, 0);
  background-color: var(--tp-surface, #fff);
  border: 1px solid var(--tp-border, #e1e1e1);
  border-radius: var(--tp-radius-md);
  overflow: hidden;
  align-self: start;
  max-height: calc(100vh - var(--tpv2-rail-offset, 0) - 16px);
  display: flex;
  flex-direction: column;
  font-family: inherit;
  color: var(--tp-text, #202223);
}

/* ===========================
   Rail Header (top label + live pill)
   =========================== */
.tpv2-rail-header {
  padding: 11px 14px;
  font-size: 10px;
  font-weight: 700;
  color: var(--tp-text-secondary, #6d7175);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  border-bottom: 1px solid var(--tp-border, #e1e1e1);
  display: flex;
  align-items: center;
  gap: 6px;
}

.tpv2-rail-header > .tpv2-pill,
.tpv2-rail-header > [class*="tpv2-pill"] {
  margin-left: auto;
  text-transform: none;
  letter-spacing: 0;
}

/* ===========================
   Rail Body (scrollable container)
   =========================== */
.tpv2-rail-body {
  flex: 1;
  overflow-y: auto;
  padding: 0;
}

/* ===========================
   Alert Strip (top of body when something needs attention)
   =========================== */
.tpv2-rail-alert {
  padding: 11px 14px;
  background-color: var(--tp-badge-danger-bg, #fef2f2);
  border-bottom: 1px solid #fecaca;
  color: var(--tp-badge-danger-text, #991b1b);
  font-size: 11.5px;
  line-height: 1.45;
  display: flex;
  gap: 8px;
  align-items: flex-start;
}

.tpv2-rail-alert i {
  font-size: 13px;
  margin-top: 1px;
}

.tpv2-rail-alert-warning {
  background-color: var(--tp-badge-warning-bg, #fef3c7);
  color: var(--tp-badge-warning-text, #92400e);
  border-bottom-color: #fde68a;
}

.tpv2-rail-alert-info {
  background-color: var(--tp-badge-info-bg, #dbeafe);
  color: var(--tp-badge-info-text, #1e40af);
  border-bottom-color: #bfdbfe;
}

/* ===========================
   Rail Card (stackable content block)
   =========================== */
.tpv2-rail-card {
  padding: 12px 14px;
  border-bottom: 1px solid var(--tp-border, #e1e1e1);
}

.tpv2-rail-card:last-child {
  border-bottom: none;
}

.tpv2-rail-card-header {
  font-size: var(--tp-font-size-3);
  font-weight: var(--tp-font-weight-semibold);
  color: var(--tp-text, #202223);
  text-transform: none;
  letter-spacing: normal;
  margin-bottom: var(--tp-space-3);
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.tpv2-rail-card-header > a,
.tpv2-rail-card-header > button {
  color: var(--tp-accent, #1d4b8e);
  text-decoration: none;
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  font-size: 10.5px;
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
  padding: 0;
}

.tpv2-rail-card-header > a:hover,
.tpv2-rail-card-header > button:hover {
  text-decoration: underline;
}

/* ===========================
   Rail Row (key/value inside a card)
   =========================== */
.tpv2-rail-row {
  display: flex;
  justify-content: space-between;
  font-size: 11.5px;
  padding: 2.5px 0;
  line-height: 1.4;
}

.tpv2-rail-row > .k {
  color: var(--tp-text-secondary, #6d7175);
}

.tpv2-rail-row > .v {
  color: var(--tp-text, #202223);
  font-weight: 500;
  text-align: right;
}

.tpv2-rail-row > .v.num {
  font-variant-numeric: tabular-nums;
}

/* Divider between row groups inside a card */
.tpv2-rail-row-sep {
  border-top: 1px dashed var(--tp-border, #e1e1e1);
  margin-top: 4px;
  padding-top: 6px;
}
/**
 * TP V2 — Section Header
 *
 * The mark that names a part of a page. Two roles, one class — the difference
 * is *where it sits*, and the surface it sits in decides, not the caller
 * (plan 0010, D-009).
 *
 *   on the page ground   the page's own outline level. Same size, weight and
 *                        colour as a card title (`.tpv2-card-h h3`), because
 *                        the two are the same rank: "Arbeitsbereiche" over a
 *                        grid of tiles and "What's New" on a card are siblings
 *                        and must not be set 11 px caps-grey against 14 px
 *                        semibold-black. Ground versus surface already tells
 *                        them apart.
 *
 *   inside a surface     a group *within* one card — "Identität", "Stammdaten"
 *                        in a ledger of facts, "Positionen" in a rail card.
 *                        One rank below the card's own title, so it keeps the
 *                        micro-caps it always had.
 *
 * A surface flips the role for everything inside it by setting the five
 * --tpv2-label-* properties once; they inherit. Nothing has to be passed down.
 *
 * Usage:
 *   <div class="tpv2-section-h">Positionen</div>
 *   <div class="tpv2-section-h">
 *     <span>Verknüpfungen · 3</span>
 *     <a class="tpv2-section-action" href="#">+ verknüpfen</a>
 *   </div>
 */

.tpv2-section-h {
  font-size: var(--tpv2-label-size, var(--tp-font-size-3));
  font-weight: var(--tpv2-label-weight, var(--tp-font-weight-semibold));
  text-transform: var(--tpv2-label-transform, none);
  letter-spacing: var(--tpv2-label-tracking, normal);
  color: var(--tpv2-label-color, var(--tp-text));
  line-height: var(--tp-line-tight);
  margin: 0 0 var(--tp-space-2);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tp-space-1-5);
  flex-wrap: wrap;
}

/* Inside a surface the mark steps down to a group label. Listed once here so
   the two roles stay next to each other; a new surface opts in by adding
   itself to this selector, not by inventing a modifier. */
.tpv2-card,
.tpv2-card-soft,
.tpv2-tile,
.tpv2-layout-rail-left,
.tpv2-layout-rail-right {
  --tpv2-label-size: var(--tp-font-size-1);
  --tpv2-label-weight: var(--tp-font-weight-bold);
  --tpv2-label-transform: uppercase;
  --tpv2-label-tracking: 0.06em;
  --tpv2-label-color: var(--tp-text-secondary);
}

/* The note on the right states a fact about the section; set as micro-caps it
   read as a second heading and the eye could not tell which one to follow. */
.tpv2-section-note {
  text-transform: none;
  letter-spacing: 0;
  font-weight: var(--tp-font-weight-normal);
  font-size: var(--tp-font-size-2);
  color: var(--tp-text-secondary);
}

/* Inline action (e.g. "+ Hinzufügen" link) right-aligned in a section header */
.tpv2-section-action {
  color: var(--tp-accent, #1d4b8e);
  text-decoration: none;
  font-weight: var(--tp-font-weight-medium);
  font-size: var(--tp-font-size-2);
  text-transform: none;
  letter-spacing: 0;
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
  padding: 0;
}

.tpv2-section-action:hover {
  text-decoration: underline;
}

/* Two long labels ("Kennzahlen · 11.07.2026 00:00 bis 10.08.2026 12:00" and
   "Vergleichszeitraum: keine Quelle") both wrapped to two lines at 390 px and
   interleaved. Below the breakpoint they stack (plan 0010, D-001d §4.4). */
@media (max-width: 767px) {
  .tpv2-section-h {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--tp-space-0-5);
  }
}
/**
 * TP V2 — Segmented Control
 * One choice out of a short, fixed set (period presets, view switches). Not a
 * button group: the segments share one track, and the selected one is a state,
 * not an action. The state lives in `aria-pressed` (buttons) or
 * `aria-current="page"` (links) — CSS reads the attribute, so markup and
 * screen-reader output cannot drift apart.
 *
 * Usage:
 *   <div class="tpv2-seg" role="group" aria-label="Zeitraum">
 *     <button type="button" class="tpv2-seg-item" aria-pressed="false">Heute</button>
 *     <button type="button" class="tpv2-seg-item" aria-pressed="true">30 Tage</button>
 *   </div>
 *
 *   <nav class="tpv2-seg" aria-label="Ansicht">
 *     <a class="tpv2-seg-item" href="?v=liste" aria-current="page">Liste</a>
 *   </nav>
 *
 * Modifiers:
 *   .tpv2-seg-sm     — denser segments (toolbars)
 *   .tpv2-seg-block  — full width, segments share the space equally
 *   .tpv2-seg-sep    — trailing item detached from the track (e.g. "Eigener Zeitraum")
 */

.tpv2-seg {
  display: inline-flex;
  align-items: center;
  gap: var(--tp-space-0-5);
  padding: var(--tp-space-0-5);
  border: 1px solid var(--tp-border, #e1e1e1);
  border-radius: var(--tp-radius-sm);
  background-color: var(--tp-hover-bg, #f3f4f6);
  font-family: inherit;
  max-width: 100%;
  /* Many presets on a narrow screen: scroll the track, never wrap it —
     a wrapped segmented control reads as two separate controls. */
  overflow-x: auto;
  scrollbar-width: none;
}

.tpv2-seg::-webkit-scrollbar {
  display: none;
}

.tpv2-seg-item {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--tp-space-1-5);
  flex: 0 0 auto;
  padding: 0 var(--tp-space-3);
  min-height: calc(var(--tp-density-control-h) - var(--tp-space-1));
  border: 1px solid transparent;
  border-radius: var(--tp-radius-xs);
  background: transparent;
  color: var(--tp-text-secondary, #6d7175);
  font-family: inherit;
  font-size: var(--tp-font-size-2);
  font-weight: var(--tp-font-weight-medium);
  line-height: var(--tp-line-none);
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.1s, color 0.1s, border-color 0.1s;
}

.tpv2-seg-item i {
  font-size: var(--tp-font-size-1);
}

.tpv2-seg-item:hover {
  color: var(--tp-text, #202223);
}

.tpv2-seg-item:focus-visible {
  outline: none;
  border-color: var(--tp-accent, #1d4b8e);
  box-shadow: 0 0 0 3px var(--tp-focus-ring, rgba(29, 75, 142, 0.2));
}

/* Selected — state comes from the accessibility attribute, not a class. */
.tpv2-seg-item[aria-pressed="true"],
.tpv2-seg-item[aria-current="page"],
.tpv2-seg-item[aria-current="true"] {
  background-color: var(--tp-surface, #fff);
  border-color: var(--tp-border, #e1e1e1);
  color: var(--tp-text, #202223);
  font-weight: var(--tp-font-weight-semibold);
}

.tpv2-seg-item:disabled,
.tpv2-seg-item[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ===========================
   Modifiers
   =========================== */
.tpv2-seg-sm .tpv2-seg-item {
  padding: 0 var(--tp-space-2);
  font-size: var(--tp-font-size-1);
  min-height: calc(var(--tp-density-control-h) - var(--tp-space-2));
}

.tpv2-seg-block {
  display: flex;
  width: 100%;
}

.tpv2-seg-block .tpv2-seg-item {
  flex: 1 1 0;
}

/* Trailing escape hatch (e.g. a custom range) — visually attached, but its own
   control: it opens something instead of selecting a segment. */
.tpv2-seg-sep {
  margin-left: var(--tp-space-1);
  padding-left: var(--tp-space-2);
  border-left: 1px solid var(--tp-border, #e1e1e1);
}
/**
 * TP V2 — Skeleton
 * Placeholder bars that hold the height of a block while it loads. Sizes come
 * from the scale, so a skeleton line is as tall as the text it stands in for.
 *
 * Usage:
 *   <div class="tpv2-sk-stack" aria-hidden="true">
 *     <span class="tpv2-sk tpv2-sk-label"></span>
 *     <span class="tpv2-sk tpv2-sk-metric tpv2-sk-w-60"></span>
 *     <span class="tpv2-sk tpv2-sk-line tpv2-sk-w-40"></span>
 *   </div>
 *
 * Heights:  -label (micro) · -line (body) · -metric (KPI value) · -title
 * Widths:   -w-40 · -w-60 · -w-80 (default: full width)
 * Shapes:   -circle (avatar/medallion), -pill (chip)
 *
 * The wrapper carries `aria-hidden`; the loading state itself belongs on the
 * region (`aria-busy="true"`), not on the bars.
 */

.tpv2-sk {
  display: block;
  width: 100%;
  height: var(--tp-font-size-3);
  border-radius: var(--tp-radius-xs);
  /* Tinted from the secondary text color: reads as a placeholder on a white
     card and on a dark one, without a second set of theme values. */
  background-color: color-mix(in srgb, var(--tp-text-secondary, #6d7175) 22%, transparent);
  background-image: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--tp-surface, #fff) 55%, transparent) 50%,
    transparent 100%
  );
  background-size: 200% 100%;
  background-repeat: no-repeat;
  animation: tpv2-sk-shimmer 1.4s ease-in-out infinite;
}

@keyframes tpv2-sk-shimmer {
  0%   { background-position: 150% 0; }
  100% { background-position: -50% 0; }
}

/* Respect the OS setting — a shimmering page is unusable for some users. */
@media (prefers-reduced-motion: reduce) {
  .tpv2-sk {
    animation: none;
    background-image: none;
  }
}

/* ===========================
   Heights — mirror the type scale
   =========================== */
.tpv2-sk-label  { height: var(--tp-font-size-1); }
.tpv2-sk-line   { height: var(--tp-font-size-2); }
.tpv2-sk-title  { height: var(--tp-font-size-4); }
.tpv2-sk-metric { height: var(--tp-font-size-metric-lg); }

/* ===========================
   Widths
   =========================== */
.tpv2-sk-w-40 { width: 40%; }
.tpv2-sk-w-60 { width: 60%; }
.tpv2-sk-w-80 { width: 80%; }

/* ===========================
   Shapes
   =========================== */
.tpv2-sk-circle {
  width: 32px;
  height: 32px;
  border-radius: var(--tp-radius-pill);
}

.tpv2-sk-pill {
  width: 72px;
  height: var(--tp-font-size-3);
  border-radius: var(--tp-radius-pill);
}

/* ===========================
   Stack — vertical rhythm between bars
   =========================== */
.tpv2-sk-stack {
  display: flex;
  flex-direction: column;
  gap: var(--tp-space-2);
}

/* One skeleton row per table row (use inside <td>). */
.tpv2-sk-row {
  display: flex;
  align-items: center;
  gap: var(--tp-density-cell-x);
}
/* StackedBar — ein Balken aus mehreren Abschnitten (Plan 0086).
 *
 * Der `tpv2-share`-Balken zeigt EINEN Anteil; hier stehen mehrere Töpfe in
 * einer Spur nebeneinander. Die Breiten kommen als `style` aus der Komponente,
 * weil sie aus Daten stammen — hier steht nur die Form.
 *
 * Keine neue Farbe: alle vier Tönungen aus tokens.css.
 */
.tpv2-stackbar {
  display: grid;
  grid-template-columns: 7.5rem minmax(8rem, 1fr) 4rem;
  align-items: center;
  gap: var(--tp-space-3) var(--tp-space-4);
  padding: var(--tp-space-2) 0;
  border-top: 1px solid var(--tp-border);
}

/* Der erste Balken einer Gruppe braucht keine Trennlinie nach oben. */
.tpv2-stackbar:first-of-type {
  border-top: 0;
}

.tpv2-stackbar-k {
  font-weight: 600;
  font-size: var(--tp-font-size-3);
}

.tpv2-stackbar-track {
  display: flex;
  height: 0.625rem;
  border-radius: var(--tp-radius-pill);
  overflow: hidden;
  background-color: var(--tp-hover-bg);
}

/* Ein Abschnitt, den es gibt, ist zu sehen: die Komponente zeichnet ihn nur
 * für einen Wert über null, deshalb kann diese Untergrenze aus nichts nichts
 * machen. */
.tpv2-stackbar-track > span {
  display: block;
  height: 100%;
  min-width: 2px;
}

.tpv2-stackbar-good {
  background-color: var(--tp-success);
}

.tpv2-stackbar-neutral {
  background-color: var(--tp-text-secondary);
  opacity: 0.45;
}

.tpv2-stackbar-warn {
  background-color: var(--tp-warning);
}

.tpv2-stackbar-bad {
  background-color: var(--tp-danger);
}

.tpv2-stackbar-v {
  text-align: right;
  font-weight: 600;
  font-variant-numeric: var(--tp-numeric);
}

/* Die Zahlen stehen unter der Spur, in deren Spalten — zusammen gelesen. */
.tpv2-stackbar-n {
  grid-column: 2 / 4;
  margin-top: -0.25rem;
  color: var(--tp-text-secondary);
  font-size: var(--tp-font-size-2);
}

.tpv2-stackbar-sum {
  opacity: 0.7;
}

.tpv2-stackbar-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--tp-space-1) var(--tp-space-5);
  margin-bottom: var(--tp-space-3);
  color: var(--tp-text-secondary);
  font-size: var(--tp-font-size-2);
}

.tpv2-stackbar-dot {
  display: inline-block;
  width: 0.625rem;
  height: 0.625rem;
  margin-right: 0.35rem;
  border-radius: var(--tp-radius-pill);
  vertical-align: -1px;
}
/**
 * TP Design System - Status Bar
 * Horizontal bar pinned to the bottom of a cockpit / detail page. Contains:
 *   • status pills on the left (live state, alerts, key facts)
 *   • a centered command pill (⌘K invitation)
 *   • tool icons + entity IDs on the right
 *
 * Status pills inside the bar reuse `tpv2-pill` for variants.
 *
 * Usage:
 *   <footer class="tpv2-status-bar">
 *     <span class="tpv2-pill tpv2-pill-success tpv2-pill-outlined tpv2-pill-live">
 *       <span class="tpv2-pill-dot"></span> Live
 *     </span>
 *     <button class="tpv2-pill tpv2-pill-danger tpv2-pill-outlined" data-tab="stream">
 *       <i class="fas fa-magnifying-glass"></i> 1 Nachforschung
 *     </button>
 *     <span class="tpv2-pill tpv2-pill-outlined">
 *       <i class="fas fa-credit-card"></i> 794,99 €
 *     </span>
 *
 *     <button class="tpv2-status-bar-cmd">
 *       <i class="fas fa-magnifying-glass"></i>
 *       <span>Frag den Vorgang …</span>
 *       <kbd>⌘</kbd><kbd>K</kbd>
 *     </button>
 *
 *     <div class="tpv2-status-bar-end">
 *       <button class="tpv2-status-bar-tool"><i class="fas fa-terminal"></i></button>
 *       <button class="tpv2-status-bar-tool"><i class="fas fa-link"></i></button>
 *       <span class="tpv2-status-bar-info">VorID 3205410</span>
 *     </div>
 *   </footer>
 */

/* ===========================
   Bar Container
   =========================== */
.tpv2-status-bar {
  background-color: var(--tp-surface, #fff);
  border-top: 1px solid var(--tp-border, #e1e1e1);
  height: 36px;
  display: flex;
  align-items: center;
  padding: 0 14px;
  position: sticky;
  bottom: 0;
  z-index: 10;
  gap: 6px;
  font-size: 11px;
  color: var(--tp-text, #202223);
  font-family: inherit;
}

/* ===========================
   Command Pill (centered ⌘K invitation)
   =========================== */
.tpv2-status-bar-cmd {
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 14px;
  background-color: var(--tp-hover-bg, #f3f4f6);
  border-radius: var(--tp-radius-md);
  border: none;
  cursor: pointer;
  color: var(--tp-text-secondary, #6d7175);
  font-size: 11px;
  max-width: 540px;
  flex: 1;
  justify-content: center;
  font-family: inherit;
}

.tpv2-status-bar-cmd:hover {
  background-color: #e5e7eb;
}

.tpv2-status-bar-cmd code {
  background-color: var(--tp-surface, #fff);
  padding: 1px 6px;
  border-radius: var(--tp-radius-xs);
  font-size: 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.tpv2-status-bar-cmd kbd {
  padding: 1px 5px;
  background-color: var(--tp-surface, #fff);
  border: 1px solid var(--tp-border, #e1e1e1);
  border-radius: var(--tp-radius-xs);
  font-size: 9.5px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--tp-text, #202223);
  line-height: 1;
}

/* ===========================
   End Section (tools + info)
   =========================== */
.tpv2-status-bar-end {
  display: flex;
  align-items: center;
  gap: 0;
  font-size: 10.5px;
  color: var(--tp-text-secondary, #6d7175);
}

.tpv2-status-bar-tool {
  padding: 6px 8px;
  border-radius: var(--tp-radius-sm);
  background: none;
  border: none;
  cursor: pointer;
  color: var(--tp-text-secondary, #6d7175);
  font-family: inherit;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.tpv2-status-bar-tool:hover {
  background-color: var(--tp-hover-bg, #f3f4f6);
  color: var(--tp-text, #202223);
}

.tpv2-status-bar-tool i {
  font-size: 10px;
}

.tpv2-status-bar-info {
  padding: 6px 10px;
  font-family: inherit;
  color: var(--tp-text-secondary, #6d7175);
}
/**
 * TP V2 — Tile Grid
 * A grid of tiles that each go somewhere: workspaces, services, entry points.
 *
 * Not a KPI row (a tile there carries a figure and goes nowhere) and not a
 * table (a tile is a destination, not a record). The ConsoleHub start page had
 * two of these hand-written in Tailwind — one roomy with a description, one
 * dense with just a name — and neither survived a dark theme or the density
 * switch (plan 0010, D-008).
 *
 * Usage:
 *   <div class="tpv2-tiles-wrap">
 *     <div class="tpv2-tiles">
 *       <a class="tpv2-tile" href="/warehousebrain/…">
 *         <span class="tpv2-tile-h">
 *           <span class="tpv2-tile-ico"><i class="fas fa-warehouse"></i></span>
 *           <span class="tpv2-tile-t">WarehouseBrain</span>
 *         </span>
 *         <span class="tpv2-tile-d">Lager, Einkauf &amp; Bestandsplanung</span>
 *       </a>
 *     </div>
 *   </div>
 *
 * Modifiers:
 *   .tpv2-tiles-compact / .tpv2-tile-compact   icon over name, no description
 *   .tpv2-tile-sk                              placeholder while loading
 */

/* How many tiles fit is a question about the slot, not about the window: the
   same grid sits full-width on the start page and 320 px wide in a rail, and a
   media query cannot tell the two apart (plan 0010, D-001c). */
.tpv2-tiles-wrap {
  container-type: inline-size;
  container-name: tptiles;
  min-width: 0;
}

.tpv2-tiles {
  /* The tile floor, not a column count: a fixed `grid-cols-6` put six 90-px
     stumps into a wide slot and clipped names in a narrow one. */
  --tpv2-tile-min: 232px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--tpv2-tile-min), 100%), 1fr));
  gap: var(--tp-density-gap);
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: 0;
}

.tpv2-tiles-compact {
  /* Wide enough for the longest name in the house — `CommunicationHub` broke
     mid-word at 136 px, and a service tile whose label reads "CommunicationHu
     b" is worse than one column less (plan 0010, D-010). */
  --tpv2-tile-min: 152px;
}

.tpv2-tile {
  display: flex;
  flex-direction: column;
  gap: var(--tp-space-2);
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
  background-color: var(--tp-surface, #fff);
  border: 1px solid var(--tp-border, #e1e1e1);
  border-radius: var(--tp-radius-md);
  box-shadow: var(--tp-elev-1);
  color: var(--tp-text, #202223);
  text-decoration: none;
  min-width: 0;
  /* Same recipe as .tpv2-card-hover — a tile is a card you can click, so it
     must not invent a second hover language. */
  transition: border-color 0.12s, box-shadow 0.12s;
}

.tpv2-tile:hover {
  border-color: color-mix(in srgb, var(--tp-accent, #1d4b8e) 35%, var(--tp-border, #e1e1e1));
  box-shadow: var(--tp-elev-2);
}

.tpv2-tile:focus-visible {
  outline: 2px solid var(--tp-accent, #1d4b8e);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .tpv2-tile {
    transition: none;
  }
}

.tpv2-tile-h {
  display: flex;
  align-items: center;
  gap: var(--tp-space-3);
  min-width: 0;
}

.tpv2-tile-ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: var(--tp-radius-sm);
  background-color: var(--tp-hover-bg, #f3f4f6);
  color: var(--tp-text-secondary, #6d7175);
  font-size: var(--tp-font-size-3);
}

.tpv2-tile:hover .tpv2-tile-ico {
  color: var(--tp-accent, #1d4b8e);
}

.tpv2-tile-t {
  font-size: var(--tp-font-size-3);
  font-weight: var(--tp-font-weight-semibold);
  line-height: var(--tp-line-snug);
  min-width: 0;
  overflow-wrap: anywhere;
}

.tpv2-tile-d {
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-base);
  color: var(--tp-text-secondary, #6d7175);
}

/* ===========================
   Compact — the name is the whole tile
   =========================== */
.tpv2-tile-compact {
  align-items: center;
  text-align: center;
  gap: var(--tp-space-2);
}

.tpv2-tile-compact .tpv2-tile-h {
  flex-direction: column;
  gap: var(--tp-space-2);
}

.tpv2-tile-compact .tpv2-tile-t {
  font-size: var(--tp-font-size-2);
  font-weight: var(--tp-font-weight-medium);
}

/* In a narrow slot the centred tile wastes the width it has: two columns of
   stacked icon-over-name eat four rows for what fits in two. Below this the
   tile turns into a row and the grid into a list — one decision, taken by the
   frame, not by the window. */
@container tptiles (max-width: 420px) {
  .tpv2-tiles-compact {
    grid-template-columns: 1fr;
  }
  .tpv2-tile-compact {
    align-items: stretch;
    text-align: left;
  }
  .tpv2-tile-compact .tpv2-tile-h {
    flex-direction: row;
    align-items: center;
  }
}

/* ===========================
   Loading — the shape of the grid, not a grey block
   =========================== */
.tpv2-tile-sk {
  cursor: default;
  pointer-events: none;
  box-shadow: none;
}

.tpv2-tile-sk .tpv2-tile-ico {
  background-color: color-mix(in srgb, var(--tp-text-secondary, #6d7175) 22%, transparent);
}

/* The placeholder bar takes the width the name would have had. */
.tpv2-tile-sk .tpv2-sk-stack {
  flex: 1 1 auto;
  min-width: 0;
}

.tpv2-tile-sk.tpv2-tile-compact .tpv2-tile-h {
  align-self: stretch;
}
/**
 * TP V2 — Timeline
 *
 * The chronicle of one object: what happened to it, newest first, on a thread
 * that runs through the whole page (plan 0057, W1).
 *
 * Not `tpv2-feed`. A feed is a list of *documents* — a headline you open, a
 * source, a date. A chronicle is a list of *facts about this object*: each one
 * has a kind (which the glyph carries), an actor, a registered event type and
 * usually two or three values from its payload. The feed has no glyph column,
 * no thread and no place for those values, and giving it all three would make
 * it a second component wearing the first one's name.
 *
 * Not `tp-timeline` either. The V1 file is a GitHub comment thread — avatars,
 * comment cards with a speech-bubble arrow, emoji reactions. It has no Rust
 * component, `tp-*` cannot live on a `tpv2-*` page, and none of what it draws
 * is what a system log needs.
 *
 * Usage:
 *   <div class="tpv2-tl-wrap">
 *     <ol class="tpv2-tl">
 *       <li class="tpv2-tl-day">Heute · Freitag, 5. September 2026</li>
 *       <li class="tpv2-tl-e">
 *         <span class="tpv2-tl-glyph tpv2-tl-warning">
 *           <i class="fas fa-rotate-left"></i>
 *         </span>
 *         <div class="tpv2-tl-b">
 *           <p class="tpv2-tl-t">
 *             Retoure angemeldet
 *             <time class="tpv2-tl-time">09:41</time>
 *           </p>
 *           <p class="tpv2-tl-line">2 × Montageset · Widerruf des Kunden</p>
 *           <p class="tpv2-tl-m">
 *             <span>Support · mkoehler</span>
 *             <code>de.talk-point.orders.return.created.v1</code>
 *           </p>
 *           <dl class="tpv2-tl-d">
 *             <div><dt>Menge</dt><dd>2</dd></div>
 *           </dl>
 *         </div>
 *       </li>
 *     </ol>
 *   </div>
 *
 * Tones:  .tpv2-tl-success .tpv2-tl-warning .tpv2-tl-danger .tpv2-tl-info
 *         (no class = neutral)
 */

/* The detail rows break against the width of the card the chronicle sits in,
   not the window — same rule as the fact grid and the feed. */
.tpv2-tl-wrap {
  container-type: inline-size;
  container-name: tptl;
  min-width: 0;
}

.tpv2-tl {
  --tpv2-tl-glyph: 22px;
  margin: 0;
  padding: var(--tp-density-pad-y) var(--tp-density-pad-x);
  list-style: none;
  min-width: 0;
}

/* ===========================
   The thread
   =========================== */
/* One rule on every child, clipped at the first and the last. Drawn per entry
   instead of once on the list, because a list that ends in a divider or in a
   loading placeholder would otherwise trail a line into empty space. The day
   marks keep the thread on purpose: a break there would read as a gap in the
   record, and there is none. */
.tpv2-tl > li {
  position: relative;
  padding-left: calc(var(--tpv2-tl-glyph) + var(--tp-space-3));
  min-width: 0;
}

.tpv2-tl > li::before {
  content: "";
  position: absolute;
  left: calc(var(--tpv2-tl-glyph) / 2);
  top: 0;
  bottom: 0;
  width: 1px;
  background-color: var(--tp-border, #e1e1e1);
}

.tpv2-tl > li:first-child::before {
  top: calc(var(--tpv2-tl-glyph) / 2);
}

.tpv2-tl > li:last-child::before {
  bottom: auto;
  height: calc(var(--tpv2-tl-glyph) / 2);
}

/* ===========================
   Entry
   =========================== */
.tpv2-tl-e {
  padding-bottom: var(--tp-space-4);
}

.tpv2-tl-e:last-child {
  padding-bottom: 0;
}

/* ===========================
   Glyph — the kind of the fact, in the tokens the pills use
   =========================== */
/* The kind is a shape and a tint, never a bare colour: a chronicle read in the
   dark theme, or by someone who cannot separate amber from red, still has the
   icon. The disc is opaque so the thread passes behind it, not through it. */
.tpv2-tl-glyph {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--tpv2-tl-glyph);
  height: var(--tpv2-tl-glyph);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--tp-font-size-1);
  background-color: var(--tp-badge-gray-bg, #f1f2f4);
  color: var(--tp-badge-gray-text, #5c5f62);
  box-shadow: 0 0 0 3px var(--tp-surface, #fff);
}

.tpv2-tl-success {
  background-color: var(--tp-badge-success-bg);
  color: var(--tp-badge-success-text);
}

.tpv2-tl-warning {
  background-color: var(--tp-badge-warning-bg);
  color: var(--tp-badge-warning-text);
}

.tpv2-tl-danger {
  background-color: var(--tp-badge-danger-bg);
  color: var(--tp-badge-danger-text);
}

.tpv2-tl-info {
  background-color: var(--tp-badge-info-bg);
  color: var(--tp-badge-info-text);
}

/* ===========================
   Body
   =========================== */
.tpv2-tl-b {
  min-width: 0;
  padding-top: 1px;
}

.tpv2-tl-t {
  margin: 0;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--tp-space-3);
  font-size: var(--tp-font-size-3);
  font-weight: var(--tp-font-weight-medium);
  line-height: var(--tp-line-snug);
  color: var(--tp-text, #202223);
  min-width: 0;
}

.tpv2-tl-t > span {
  min-width: 0;
}

.tpv2-tl-t strong {
  font-weight: var(--tp-font-weight-semibold);
}

.tpv2-tl-time {
  flex-shrink: 0;
  font-size: var(--tp-font-size-2);
  font-weight: var(--tp-font-weight-normal);
  font-variant-numeric: var(--tp-numeric);
  color: var(--tp-text-secondary, #6d7175);
  white-space: nowrap;
}

/* Der Satz zur Überschrift — worum es in diesem Eintrag ging (Plan 0062,
   K-13). Dunkler als die Meta-Zeile darunter und heller als die
   Überschrift darüber: er ist die Auskunft, nicht die Herkunft. Zahlen in
   Tabellenziffern, weil untereinander stehende Beträge sonst tanzen. */
.tpv2-tl-line {
  margin: var(--tp-space-0-5) 0 0;
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  color: var(--tp-text, #202223);
  font-variant-numeric: var(--tp-numeric);
  overflow-wrap: anywhere;
  min-width: 0;
}

/* Who caused it and under which registered type. The type is the value a
   developer needs to find the same fact in the log, so it stays readable —
   and it wraps rather than pushing the actor out of the row. */
.tpv2-tl-m {
  margin: var(--tp-space-0-5) 0 0;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--tp-space-1) var(--tp-space-2);
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  color: var(--tp-text-secondary, #6d7175);
  min-width: 0;
}

.tpv2-tl-m code {
  font-family: var(--tp-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: var(--tp-font-size-2);
  color: var(--tp-text-secondary, #6d7175);
  overflow-wrap: anywhere;
}

/* Der Titel wird Link, wenn der Eintrag ein Ziel hat (Plan 0105 C2). */
.tpv2-tl-m a,
.tpv2-tl-t a {
  color: var(--tp-accent, #1d4b8e);
  text-decoration: none;
}

.tpv2-tl-m a:hover,
.tpv2-tl-t a:hover {
  text-decoration: underline;
}

/* ===========================
   Detail rows — the payload values the entry is about
   =========================== */
/* Two to four pairs, in the row the entry already owns. The alternative was a
   sentence per entry ("Menge 2, Erstattungsanteil 258,00 €"), which reads
   fine once and is unscannable ten entries down. */
/* Flex und nicht Grid: die Paare sind unterschiedlich breit — „Versuch 2"
   neben einer 24-stelligen Sendungsnummer —, und eine Spaltenbreite für beide
   reißt hinter dem kurzen ein Loch auf. Sie packen links und brechen um. */
.tpv2-tl-d {
  margin: var(--tp-space-1-5) 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--tp-space-0-5) var(--tp-space-5);
  font-size: var(--tp-font-size-2);
  line-height: var(--tp-line-snug);
  min-width: 0;
}

.tpv2-tl-d > div {
  display: flex;
  align-items: baseline;
  gap: var(--tp-space-2);
  min-width: 0;
}

.tpv2-tl-d dt {
  color: var(--tp-text-secondary, #6d7175);
  white-space: nowrap;
}

.tpv2-tl-d dd {
  margin: 0;
  color: var(--tp-text, #202223);
  font-variant-numeric: var(--tp-numeric);
  overflow-wrap: anywhere;
  min-width: 0;
}

.tpv2-tl-d dd .mono {
  font-family: var(--tp-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: var(--tp-font-size-2);
}

/* ===========================
   Day mark
   =========================== */
/* The chronicle of a busy order runs over days, and a bare "04.09. 05:58" on
   every entry makes the reader re-read the date nine times. The day is said
   once and the entries below carry only the clock. */
.tpv2-tl-day {
  padding-top: var(--tp-space-1);
  padding-bottom: var(--tp-space-3);
  font-size: var(--tp-font-size-2);
  font-weight: var(--tp-font-weight-semibold);
  text-transform: none;
  letter-spacing: normal;
  color: var(--tp-text-secondary, #6d7175);
}

.tpv2-tl-day:first-child {
  padding-top: 0;
}

/* ===========================
   Loading
   =========================== */
.tpv2-tl-sk {
  padding-bottom: var(--tp-space-4);
}

.tpv2-tl-sk .tpv2-tl-glyph {
  background-color: var(--tp-border, #e1e1e1);
  animation: tpv2-skeleton 1.4s ease-in-out infinite;
}

@keyframes tpv2-skeleton {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.45;
  }
}

@media (prefers-reduced-motion: reduce) {
  .tpv2-tl-sk .tpv2-tl-glyph {
    animation: none;
  }
}

/* ===========================
   Narrow frame
   =========================== */
/* Under ~380 px the clock cannot share the headline row with a sentence
   without squeezing it to two words per line. It drops under the headline and
   joins the meta row's rhythm. */
@container tptl (max-width: 380px) {
  .tpv2-tl-t {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--tp-space-0-5);
  }
  .tpv2-tl-d > div {
    flex: 1 1 100%;
  }
}

.tpv2-tl-technical { margin-top: var(--tp-space-1); font-size: var(--tp-font-size-2); color: var(--tp-text-secondary); }
.tpv2-tl-technical summary { cursor: pointer; width: fit-content; }
.tpv2-tl-technical code { display: block; margin-top: var(--tp-space-2); overflow-wrap: anywhere; }
.tpv2-tl-technical summary:focus-visible { outline: 2px solid var(--tp-accent); outline-offset: 2px; }
/**
 * TP V2 — Toolbar
 *
 * The strip between page header and content that says *what* is being shown
 * (period, scope) and offers the controls that change it. Before this existed
 * the merchant dashboard hung its period presets into the page header's action
 * slot, where a long period label and six preset buttons collided at 390 px
 * (plan 0010, D-001 gap list).
 *
 * Usage:
 *   <div class="tpv2-toolbar">
 *     <div class="tpv2-toolbar-lead">
 *       <span class="tpv2-toolbar-label">Zeitraum</span>
 *       <span class="tpv2-toolbar-period">11.07.2026 bis 10.08.2026</span>
 *       <span class="tpv2-toolbar-note">Stand 12:00</span>
 *     </div>
 *     <div class="tpv2-toolbar-actions"> … segmented control, buttons … </div>
 *   </div>
 *
 * Modifiers:
 *   .tpv2-toolbar-boxed   surface + border (standalone strip, not in a header)
 *   [aria-busy="true"]    shows .tpv2-toolbar-busy
 */

.tpv2-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--tp-space-2) var(--tp-density-gap);
  min-width: 0;
  font-family: inherit;
  color: var(--tp-text);
}

.tpv2-toolbar-boxed {
  background-color: var(--tp-surface);
  border: 1px solid var(--tp-border);
  border-radius: var(--tp-radius-md);
  padding: var(--tp-space-2) var(--tp-density-pad-x);
}

.tpv2-toolbar-lead {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--tp-space-1) var(--tp-space-3);
  min-width: 0;
}

.tpv2-toolbar-label {
  font-size: var(--tp-font-size-1);
  font-weight: var(--tp-font-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--tp-text-secondary);
}

.tpv2-toolbar-period {
  font-size: var(--tp-font-size-3);
  font-weight: var(--tp-font-weight-semibold);
  font-variant-numeric: var(--tp-numeric);
  color: var(--tp-text);
}

.tpv2-toolbar-note {
  font-size: var(--tp-font-size-2);
  color: var(--tp-text-secondary);
}

.tpv2-toolbar-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--tp-space-2);
  min-width: 0;
}

/* Loading indicator — the toolbar owns it because the toolbar is what
   triggered the reload. Hidden unless the strip announces aria-busy. */
.tpv2-toolbar-busy {
  display: none;
  align-items: center;
  gap: var(--tp-space-1-5);
  font-size: var(--tp-font-size-2);
  color: var(--tp-text-secondary);
}

.tpv2-toolbar[aria-busy="true"] .tpv2-toolbar-busy {
  display: inline-flex;
}

.tpv2-toolbar-busy i {
  animation: tpv2-toolbar-spin 0.9s linear infinite;
}

@keyframes tpv2-toolbar-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .tpv2-toolbar-busy i {
    animation: none;
  }
}

/* ===========================
   Narrow: label block over control block, controls full width
   =========================== */
@media (max-width: 767px) {
  .tpv2-toolbar {
    flex-direction: column;
    align-items: stretch;
  }

  .tpv2-toolbar-actions {
    justify-content: flex-start;
  }
}
/**
 * TP V2 — View Tabs
 * Horizontal tab strip used as primary navigation between "lenses" of one
 * entity on a detail page. Underline-style active indicator. Each tab may
 * carry an icon + label + optional count chip.
 *
 * The tabs are links, not switches: every lens is its own address. That is
 * what separates them from `tpv2-seg` — a segmented control narrows the view
 * you are already on (plan 0010, D-001c).
 *
 * Usage:
 *   <nav class="tpv2-view-tabs" aria-label="Ansichten">
 *     <a class="tpv2-view-tab tpv2-view-tab-active" href="…" aria-current="page">
 *       <i class="fas fa-file-lines"></i> Belege <span class="ct">3</span>
 *     </a>
 *     <a class="tpv2-view-tab" href="…">
 *       <i class="fas fa-gauge-high"></i> Übersicht
 *     </a>
 *     ...
 *     <div class="tpv2-view-tabs-end">Live · vor 8 s</div>
 *   </nav>
 *
 * The strip is a control, not a content surface, so at rest it sits on the
 * page ground like the toolbar and the page header do (plan 0010, D-009). What
 * it keeps is the bottom rule — that is the rail the active underline rides on
 * and the line between the lenses and what they show. The plate comes back
 * with `-sticky`, where it has work to do: occluding the content that scrolls
 * beneath it.
 *
 * Modifiers:
 *   .tpv2-view-tabs-sticky  stick to the top of a cockpit shell; takes the
 *                           surface and the shell's side inset with it
 *   .tpv2-view-tabs-scroll  scroll sideways instead of wrapping (mobile)
 */

.tpv2-view-tabs {
  background-color: transparent;
  border-bottom: 1px solid var(--tp-border, #e1e1e1);
  display: flex;
  gap: var(--tp-space-0-5);
  align-items: flex-end;
  font-family: inherit;
}

.tpv2-view-tab {
  padding: var(--tp-density-cell-y) var(--tp-space-3);
  border: none;
  background: none;
  font-size: var(--tp-font-size-3);
  color: var(--tp-text-secondary, #6d7175);
  cursor: pointer;
  font-family: inherit;
  text-decoration: none;
  border-bottom: 2px solid transparent;
  font-weight: var(--tp-font-weight-medium);
  display: inline-flex;
  align-items: center;
  gap: var(--tp-space-1-5);
  line-height: var(--tp-line-snug);
  white-space: nowrap;
}

.tpv2-view-tab:hover {
  color: var(--tp-text, #202223);
}

.tpv2-view-tab-active {
  color: var(--tp-accent, #1d4b8e);
  border-bottom-color: var(--tp-accent, #1d4b8e);
  font-weight: var(--tp-font-weight-semibold);
}

/* A lens with nothing behind it yet: still visible, so the reader knows the
   entity has that side, but not offered as a destination. */
.tpv2-view-tab-disabled,
.tpv2-view-tab[aria-disabled="true"] {
  color: var(--tp-text-secondary, #6d7175);
  opacity: 0.55;
  cursor: default;
  pointer-events: none;
}

.tpv2-view-tab i {
  font-size: var(--tp-font-size-2);
}

/* Count chip inside a tab */
.tpv2-view-tab .ct {
  font-size: var(--tp-font-size-1);
  color: var(--tp-text-secondary, #6d7175);
  background-color: var(--tp-hover-bg, #f3f4f6);
  padding: 1px var(--tp-space-1-5);
  border-radius: var(--tp-radius-pill);
  font-weight: var(--tp-font-weight-semibold);
  font-variant-numeric: var(--tp-numeric);
}

.tpv2-view-tab-active .ct {
  color: var(--tp-accent, #1d4b8e);
  background-color: var(--tp-badge-info-bg, #dbeafe);
}

/* End slot — pushed to the right (e.g. "Live · vor 8 s") */
.tpv2-view-tabs-end {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--tp-space-1-5);
  font-size: var(--tp-font-size-2);
  color: var(--tp-text-secondary, #6d7175);
  padding-bottom: var(--tp-density-cell-y);
  white-space: nowrap;
}

/* Sticky when used inside a cockpit shell: it spans the shell, so it takes
   the shell's inset and the surface that lets it cover what scrolls under it. */
.tpv2-view-tabs-sticky {
  position: sticky;
  top: var(--tpv2-view-tabs-top, 0);
  z-index: 7;
  background-color: var(--tp-surface, #fff);
  padding: 0 var(--tp-density-pad-x);
}

/* Scrolling strip. Six lenses do not fit on a phone; wrapping them into two
   rows moves the underline away from the row the reader is on, so the strip
   scrolls instead and the end slot drops below it. */
.tpv2-view-tabs-scroll {
  overflow-x: auto;
  scrollbar-width: none;
}

.tpv2-view-tabs-scroll::-webkit-scrollbar {
  display: none;
}

@media (max-width: 767px) {
  .tpv2-view-tabs {
    flex-wrap: wrap;
  }
  .tpv2-view-tabs-scroll {
    flex-wrap: nowrap;
  }
  .tpv2-view-tabs-end {
    margin-left: 0;
    width: 100%;
    padding-bottom: var(--tp-space-2);
  }
  .tpv2-view-tabs-scroll .tpv2-view-tabs-end {
    width: auto;
    margin-left: var(--tp-space-2);
    padding-bottom: var(--tp-density-cell-y);
  }
}
