diff --git a/frontend/src/styles/DEVLOG.md b/frontend/src/styles/DEVLOG.md new file mode 100644 index 0000000..60fbb83 --- /dev/null +++ b/frontend/src/styles/DEVLOG.md @@ -0,0 +1,17 @@ +# styles – Dev Log + +## 2026-09-08 01:31 – 01:35 (4m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- .claude/agents/accessibility-expert.md | 5 ++--- +- .claude/agents/css-expert.md | 2 +- +- .claude/agents/html-expert.md | 2 +- +- .claude/agents/tailwind-expert.md | 4 +++- +- .claude/agents/ui-visual-validator.md | 6 ++++-- + +--- diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css deleted file mode 100644 index 879b036..0000000 --- a/frontend/src/styles/global.css +++ /dev/null @@ -1,871 +0,0 @@ -:root { - /* Redesign 2026-09-05, zweiter Durchgang: bewusst schlichter/flacher - (Nutzer-Vorgabe "schlichtes Webdesign wie OpenFleet") - Schatten fast - weg, Radius kleiner, kein Hover-Glow mehr, Farben ruhiger. Alle - bestehenden Klassennamen bleiben unverändert. Mobile-Anpassungen aus - dem vorigen Durchgang bleiben bestehen. */ - --color-bg: #f4f5f7; - --color-surface: #ffffff; - --color-surface-sunken: #f8f9fa; - --color-border: #e1e4e8; - --color-border-strong: #c9ced4; - --color-text: #1c2126; - --color-text-muted: #64707a; - --color-primary: #0a4d8c; - --color-primary-hover: #073a6b; - --color-primary-soft: #eaf1fa; - --color-danger: #b3261e; - --color-danger-bg: #fbeaea; - --color-warning: #9a6700; - --color-warning-bg: #fff3d6; - --color-success: #1e7a34; - --color-success-bg: #e7f5ea; - --color-muted-bg: #eef0f3; - --color-ready: #1e7a34; - --color-ready-bg: #e7f5ea; - --color-limited: #9a6700; - --color-limited-bg: #fff3d6; - --color-not-ready: #b3261e; - --color-not-ready-bg: #fbeaea; - --color-unknown: #55606b; - --color-unknown-bg: #e9ebee; - /* Dashboard-Redesign: einheitliche Statusfarben-Namen für Kacheln - (ok/warn/unknown/critical), als Alias auf bestehende Tokens - ändern sich - automatisch mit, wenn Light/Dark die --color-ready/... Werte neu setzen. */ - --status-ok: var(--color-ready); - --status-warn: var(--color-limited); - --status-unknown: var(--color-unknown); - --status-critical: var(--color-not-ready); - --radius: 8px; - --radius-sm: 6px; - --shadow-sm: none; - --shadow: none; - --shadow-hover: none; - --font-sans: "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; -} - -/* UI-001 (Epic 20): Dark Mode - Systemeinstellung per prefers-color-scheme, - manueller Override per [data-theme] auf (ThemeToggle-Komponente, - Präferenz in localStorage). Reihenfolge wichtig: System-Media-Query zuerst, - [data-theme="dark"] danach, damit der manuelle Schalter in beide Richtungen - gewinnt. */ -@media (prefers-color-scheme: dark) { - :root:not([data-theme="light"]) { - --color-bg: #14171a; - --color-surface: #1c2024; - --color-surface-sunken: #22262b; - --color-border: #333941; - --color-border-strong: #454c55; - --color-text: #e8eaed; - --color-text-muted: #9aa4ae; - --color-primary: #5b9bd8; - --color-primary-hover: #7bb0e2; - --color-primary-soft: #1e2c3a; - --color-danger: #e37c76; - --color-danger-bg: #3a2224; - --color-warning: #e0b34d; - --color-warning-bg: #3a3120; - --color-success: #6cc386; - --color-success-bg: #1f3324; - --color-muted-bg: #262b31; - --color-ready: #6cc386; - --color-ready-bg: #1f3324; - --color-limited: #e0b34d; - --color-limited-bg: #3a3120; - --color-not-ready: #e37c76; - --color-not-ready-bg: #3a2224; - --color-unknown: #9aa4ae; - --color-unknown-bg: #262b31; - } -} - -:root[data-theme="dark"] { - --color-bg: #14171a; - --color-surface: #1c2024; - --color-surface-sunken: #22262b; - --color-border: #333941; - --color-border-strong: #454c55; - --color-text: #e8eaed; - --color-text-muted: #9aa4ae; - --color-primary: #5b9bd8; - --color-primary-hover: #7bb0e2; - --color-primary-soft: #1e2c3a; - --color-danger: #e37c76; - --color-danger-bg: #3a2224; - --color-warning: #e0b34d; - --color-warning-bg: #3a3120; - --color-success: #6cc386; - --color-success-bg: #1f3324; - --color-muted-bg: #262b31; - --color-ready: #6cc386; - --color-ready-bg: #1f3324; - --color-limited: #e0b34d; - --color-limited-bg: #3a3120; - --color-not-ready: #e37c76; - --color-not-ready-bg: #3a2224; - --color-unknown: #9aa4ae; - --color-unknown-bg: #262b31; -} - -body { - transition: background-color 0.15s ease, color 0.15s ease; -} - -* { - box-sizing: border-box; -} - -html, -body, -#root { - height: 100%; -} - -body { - margin: 0; - font-family: var(--font-sans); - background: var(--color-bg); - color: var(--color-text); - -webkit-font-smoothing: antialiased; - text-rendering: optimizeLegibility; -} - -h1, -h2, -h3 { - margin: 0 0 0.75rem; - line-height: 1.25; - font-weight: 700; - letter-spacing: -0.01em; -} - -h1 { - font-size: 1.6rem; -} - -h2 { - font-size: 1.2rem; -} - -h3 { - font-size: 1.05rem; -} - -p { - margin: 0 0 0.75rem; -} - -a { - color: var(--color-primary); -} - -:focus-visible { - outline: 2px solid var(--color-primary); - outline-offset: 2px; -} - -/* -- Layout ------------------------------------------------------------- */ - -/* Sidebar-Layout (Redesign 3. Durchgang, Vorbild OpenFleet-Screenshot): - .app-shell steht ab Desktop-Breite als Reihe (Sidebar links, Seite rechts), - .topbar ist trotz Klassennamen die Sidebar selbst - kein JSX-Umbau nötig, - AppShell.tsx bleibt unverändert, nur die Werte hier drehen das Layout. - Unter 768px kippt es zurück auf die bisherige horizontale Topbar-Leiste - (siehe Media-Query unten), da der Mobile-Durchgang das schon gut löste. */ - -.app-shell { - min-height: 100%; - display: flex; - flex-direction: row; -} - -.topbar { - background: var(--color-surface); - border-right: 1px solid var(--color-border); - padding: 1.25rem 1rem; - display: flex; - flex-direction: column; - align-items: stretch; - gap: 1.5rem; - width: 230px; - flex: 0 0 230px; - position: sticky; - top: 0; - height: 100vh; - overflow-y: auto; - transition: width 0.18s ease, flex-basis 0.18s ease, padding 0.18s ease; -} - -/* Sidebar einklappbar - Nutzer-Wunsch (mehr Platz für breite Dashboard-Raster - auf Desktop-Monitoren). Nur der Toggle-Button bleibt sichtbar. */ -.topbar-eingeklappt { - width: 48px; - flex: 0 0 48px; - padding: 1.25rem 0.5rem; - align-items: center; - overflow: visible; -} - -.topbar-toggle { - background: none; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - color: var(--color-text-muted); - cursor: pointer; - font-size: 0.9rem; - line-height: 1; - padding: 0.4rem 0.55rem; - align-self: flex-end; -} - -.topbar-eingeklappt .topbar-toggle { - align-self: center; -} - -.topbar-toggle:hover { - background: var(--color-surface-sunken); - color: var(--color-text); -} - -.topbar-brand { - font-weight: 800; - font-size: 1.15rem; - letter-spacing: -0.02em; - color: var(--color-primary); - text-decoration: none; - padding: 0 0.5rem; -} - -.topbar-nav { - display: flex; - flex-direction: column; - align-items: stretch; - gap: 0.15rem; -} - -.topbar-nav a { - color: var(--color-text-muted); - text-decoration: none; - padding: 0.55rem 0.75rem; - border-radius: var(--radius-sm); - font-size: 0.92rem; - font-weight: 600; - transition: background-color 0.15s ease, color 0.15s ease; -} - -.topbar-nav a:hover { - background: var(--color-surface-sunken); - color: var(--color-text); -} - -.topbar-nav a.active { - background: var(--color-primary-soft); - color: var(--color-primary); -} - -.page { - flex: 1; - width: 100%; - max-width: 640px; - margin: 0 auto; - padding: 1.5rem 1.25rem 2.5rem; -} - -.page-narrow { - max-width: 380px; -} - -/* Verwaltung (Admin/Material-/Leitungsverantwortliche) läuft am Desktop - - Tabellen (Änderungslog) und mehrspaltige Formulare brauchen mehr Platz als - die mobile 640px-Kontroll-Ansicht der Mitarbeiter. Nutzer-Vorgabe: links - ausgerichtet statt zentriert - bei 1200px sonst viel verschenkter Platz - rechts/links auf breiten Monitoren. */ -.page-wide { - flex: 1; - width: 100%; - max-width: 1240px; - margin: 0; - padding: 1.75rem 2rem 3rem; -} - -/* Status-Monitor-Modus (Dashboard dauerhaft auf Wandmonitor, Betrachtung aus - mehreren Metern Entfernung) - alles per zoom vergrößern statt einzelne - Tailwind-rem-Klassen zu duplizieren (rem ist immer relativ zum Root-, - Container-Schriftgröße ändern hätte also keine Wirkung auf Tailwind-Text- - /Abstandsklassen). zoom skaliert das gesamte gerenderte Layout inkl. - react-grid-layout-Raster gemeinsam mit den echten Pixelmaßen. */ -[data-monitor="true"] { - max-width: none; - zoom: 1.3; -} - -/* -- Cards ---------------------------------------------------------------- */ - -.card { - background: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: var(--radius); - box-shadow: var(--shadow-sm); - padding: 1.1rem 1.2rem; - margin-bottom: 0.75rem; - transition: box-shadow 0.15s ease, border-color 0.15s ease; -} - -.card-list { - list-style: none; - padding: 0; - margin: 0; - display: flex; - flex-direction: column; - gap: 0.6rem; -} - -.card-link { - display: block; - text-decoration: none; - color: inherit; -} - -.card-link:hover { - border-color: var(--color-primary); - box-shadow: var(--shadow-hover); -} - -.card-title-row { - display: flex; - justify-content: space-between; - align-items: center; - gap: 0.5rem; -} - -.card-subtitle { - font-size: 0.85rem; - color: var(--color-text-muted); - margin-top: 0.15rem; -} - -/* -- Forms ------------------------------------------------------------ */ - -.field { - display: flex; - flex-direction: column; - gap: 0.35rem; - margin-bottom: 1rem; -} - -.field label { - font-size: 0.88rem; - color: var(--color-text-muted); - font-weight: 600; -} - -.input:not([type="checkbox"]):not([type="radio"]), -select.input, -textarea.input { - width: 100%; - font-size: 0.98rem; - padding: 0.6rem 0.75rem; - min-height: 44px; - border: 1px solid var(--color-border-strong); - border-radius: var(--radius-sm); - background: var(--color-surface); - color: var(--color-text); - font-family: inherit; - transition: border-color 0.15s ease, box-shadow 0.15s ease; -} - -.input:focus, -select.input:focus { - outline: none; - border-color: var(--color-primary); - box-shadow: 0 0 0 3px var(--color-primary-soft); -} - -.input-search { - font-size: 1.02rem; - padding: 0.8rem 1rem; -} - -/* -- Buttons ------------------------------------------------------------ */ - -.btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 0.4rem; - font-size: 0.95rem; - font-weight: 600; - padding: 0.6rem 1.05rem; - /* Mindestens 44x44px Touch-Ziel (WCAG 2.5.5) - im Feld/Lager oft mit - Handschuhen bedient. */ - min-height: 44px; - border-radius: var(--radius-sm); - border: 1px solid transparent; - cursor: pointer; - transition: background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease, box-shadow 0.15s ease; - font-family: inherit; -} - -.btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.btn-primary { - background: var(--color-primary); - color: #fff; - box-shadow: var(--shadow-sm); -} - -.btn-primary:hover:not(:disabled) { - background: var(--color-primary-hover); -} - -.btn-secondary { - background: var(--color-surface); - color: var(--color-text); - border-color: var(--color-border-strong); -} - -.btn-secondary:hover:not(:disabled) { - background: var(--color-surface-sunken); - border-color: var(--color-text-muted); -} - -.btn-danger { - background: var(--color-surface); - color: var(--color-danger); - border-color: var(--color-danger); -} - -.btn-danger:hover:not(:disabled) { - background: var(--color-danger-bg); -} - -.btn-block { - width: 100%; -} - -/* -- Badges / Alerts ------------------------------------------------------ */ - -.badge { - display: inline-flex; - align-items: center; - font-size: 0.76rem; - font-weight: 700; - padding: 0.22rem 0.65rem; - border-radius: 999px; - white-space: nowrap; - letter-spacing: 0.01em; -} - -.badge-neutral { - background: var(--color-muted-bg); - color: var(--color-text-muted); -} - -.badge-warning { - background: var(--color-warning-bg); - color: var(--color-warning); -} - -.badge-success { - background: var(--color-success-bg); - color: var(--color-success); -} - -.badge-danger { - background: var(--color-danger-bg); - color: var(--color-danger); -} - -/* UI-003 (Epic 20): Einsatzbereitschafts-Status - eigene Klassen statt - Wiederverwendung von badge-success/warning/danger, weil "unbekannt" fachlich - NIE wie "einsatzbereit" aussehen darf (siehe ReadinessBadge-Komponente). */ -.badge-ready { - background: var(--color-ready-bg); - color: var(--color-ready); -} - -.badge-limited { - background: var(--color-limited-bg); - color: var(--color-limited); -} - -.badge-not-ready { - background: var(--color-not-ready-bg); - color: var(--color-not-ready); -} - -.badge-unknown { - background: var(--color-unknown-bg); - color: var(--color-unknown); -} - -.theme-toggle { - background: transparent; - border: 1px solid var(--color-border-strong); - color: var(--color-text-muted); - border-radius: var(--radius-sm); - padding: 0.4rem 0.6rem; - cursor: pointer; - font-size: 0.85rem; - line-height: 1; -} - -.theme-toggle:hover { - background: var(--color-surface-sunken); - color: var(--color-text); -} - -/* UI-002 (Epic 20): Command Palette (Ctrl/Cmd+K) */ -.command-palette-overlay { - position: fixed; - inset: 0; - background: rgba(10, 14, 20, 0.5); - display: flex; - align-items: flex-start; - justify-content: center; - padding-top: 12vh; - z-index: 200; -} - -.command-palette { - width: min(90vw, 32rem); - background: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: var(--radius); - box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25); - padding: 0.75rem; -} - -.command-palette-list { - list-style: none; - margin: 0.5rem 0 0; - padding: 0; - max-height: 60vh; - overflow-y: auto; -} - -.command-palette-empty { - padding: 0.6rem 0.5rem; -} - -.command-palette-item { - width: 100%; - display: flex; - justify-content: space-between; - align-items: center; - gap: 0.5rem; - background: none; - border: none; - text-align: left; - padding: 0.55rem 0.5rem; - border-radius: var(--radius-sm); - font-size: 0.95rem; - color: var(--color-text); - cursor: pointer; - font-family: inherit; -} - -.command-palette-item:hover, -.command-palette-item:focus-visible { - background: var(--color-surface-sunken); -} - -.alert { - border-radius: var(--radius-sm); - padding: 0.75rem 1rem; - margin-bottom: 1rem; - font-size: 0.95rem; - border: 1px solid transparent; -} - -.alert-warning { - background: var(--color-warning-bg); - color: var(--color-warning); - border-color: rgba(154, 103, 0, 0.25); -} - -.alert-danger { - background: var(--color-danger-bg); - color: var(--color-danger); - border-color: rgba(179, 38, 30, 0.25); -} - -.text-muted { - color: var(--color-text-muted); -} - -/* Nur für Screenreader sichtbar - Label-Text ohne visuelle Verschiebung. */ -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} - -.text-success { - color: var(--color-success); -} - -.text-danger { - color: var(--color-danger); -} - -.stack { - display: flex; - flex-direction: column; - gap: 0.6rem; -} - -.row { - display: flex; - gap: 0.6rem; - align-items: center; -} - -.row-between { - display: flex; - justify-content: space-between; - align-items: center; - gap: 0.6rem; -} - -.section { - margin-bottom: 2.25rem; -} - -.section h2 { - padding-bottom: 0.5rem; - border-bottom: 1px solid var(--color-border); -} - -.scanner-overlay { - position: fixed; - inset: 0; - background: rgba(10, 14, 20, 0.88); - display: flex; - align-items: center; - justify-content: center; - z-index: 100; -} - -.scanner-box { - display: flex; - flex-direction: column; - align-items: center; - gap: 0.5rem; - padding: 1rem; -} - -.scanner-video { - width: min(90vw, 480px); - border-radius: var(--radius); - background: #000; -} - -.table { - width: 100%; - border-collapse: collapse; - font-size: 0.85rem; -} - -.table th, -.table td { - text-align: left; - padding: 0.55rem 0.7rem; - border-bottom: 1px solid var(--color-border); - white-space: nowrap; -} - -.table th { - font-weight: 700; - color: var(--color-text-muted); - background: var(--color-surface-sunken); - position: sticky; - top: 0; -} - -.table tbody tr:hover { - background: var(--color-surface-sunken); -} - -/* Objekte-Liste: nach Standort gruppiert, ab 900px Tabelle statt Karten - (mehr Objekte auf einen Blick am Desktop) - Umschaltung per useIsDesktop- - Hook (echte Breiten-Erkennung, reagiert live auf Resize), nicht per CSS. */ -.objekte-gruppe { - margin-bottom: 1.75rem; -} - -.objekte-gruppe-titel { - font-size: 1rem; - margin-bottom: 0.6rem; -} - -/* -- Dashboard-Kacheln (DNA-Referenz Resgrid/OpenFleet/Emergency-Management: - KPI-Statistik-Karten sind dort durchgängig das zentrale Muster) --------- */ - -.card-grid { - display: flex; - gap: 1.25rem; - flex-wrap: wrap; -} - -.card-grid > .card { - flex: 1 1 18rem; -} - -.stat-number { - font-size: 2rem; - font-weight: 800; - letter-spacing: -0.02em; - margin: 0.2rem 0 0.4rem; - line-height: 1; -} - -/* -- Responsive: mobile Durchgang 2026-09-05 ----------------------------- - Formular-Sidebar + Liste (Objekte/Material/Zuständigkeit/Positionen) - nutzten bisher fixe 20rem-Breite nebeneinander - bricht auf Mobile, da - beide Spalten dann zusammen breiter als der Bildschirm sind. */ - -.split-layout { - display: flex; - align-items: flex-start; - gap: 1.25rem; -} - -.split-layout-sidebar { - flex: 0 0 20rem; - width: 20rem; - box-sizing: border-box; -} - -.split-layout-sidebar-sm { - flex: 0 0 12rem; - width: 12rem; - box-sizing: border-box; - padding: 0.5rem; -} - -.split-layout-content { - flex: 1 1 0%; - min-width: 0; -} - -.akte-grid { - display: grid; - gap: 1rem; - grid-template-columns: 1fr; - grid-template-areas: - "kopf" - "aktionen" - "stamm" - "beladung" - "wartung" - "pruefungen" - "verantwortlich" - "maengel" - "dokumente" - "historie"; -} - -.akte-kopf { grid-area: kopf; } -.akte-aktionen { grid-area: aktionen; } -.akte-stamm { grid-area: stamm; } -.akte-beladung { grid-area: beladung; } -.akte-wartung { grid-area: wartung; } -.akte-pruefungen { grid-area: pruefungen; } -.akte-verantwortlich { grid-area: verantwortlich; } -.akte-maengel { grid-area: maengel; } -.akte-dokumente { grid-area: dokumente; } -.akte-historie { grid-area: historie; } - -@media (min-width: 900px) { - .akte-grid { - grid-template-columns: 2fr 1fr; - grid-template-areas: - "kopf kopf" - "aktionen aktionen" - "stamm wartung" - "beladung pruefungen" - "verantwortlich maengel" - "dokumente dokumente" - "historie historie"; - } - - .akte-grid.ist-fahrzeug { - grid-template-areas: - "kopf kopf" - "aktionen aktionen" - "wartung stamm" - "pruefungen beladung" - "maengel verantwortlich" - "dokumente dokumente" - "historie historie"; - } -} - -@media (max-width: 768px) { - .split-layout { - flex-direction: column; - } - - .split-layout-sidebar, - .split-layout-sidebar-sm { - width: 100%; - flex: 1 1 auto; - } - - .page-wide { - padding: 1.25rem 1rem 2rem; - } - - /* Sidebar -> horizontale Topbar zurückklappen (Mobile-Durchgang) */ - .app-shell { - flex-direction: column; - } - - .topbar { - flex-direction: row; - align-items: center; - justify-content: space-between; - width: auto; - flex: 0 0 auto; - height: auto; - position: sticky; - padding: 0.6rem 0.9rem; - border-right: none; - border-bottom: 1px solid var(--color-border); - flex-wrap: wrap; - gap: 0.75rem; - } - - .topbar-nav { - flex-direction: row; - flex-wrap: wrap; - gap: 0.15rem; - } - - .topbar-nav a { - padding: 0.4rem 0.6rem; - font-size: 0.85rem; - } - - h1 { - font-size: 1.35rem; - } - - .card-grid > .card { - flex: 1 1 100%; - } - - .row { - flex-wrap: wrap; - } -} diff --git a/frontend/src/styles/global/akte-grid.css b/frontend/src/styles/global/akte-grid.css new file mode 100644 index 0000000..6f90d23 --- /dev/null +++ b/frontend/src/styles/global/akte-grid.css @@ -0,0 +1,27 @@ +.akte-grid { + display: grid; + gap: 1rem; + grid-template-columns: 1fr; + grid-template-areas: + "kopf" + "aktionen" + "stamm" + "beladung" + "wartung" + "pruefungen" + "verantwortlich" + "maengel" + "dokumente" + "historie"; +} + +.akte-kopf { grid-area: kopf; } +.akte-aktionen { grid-area: aktionen; } +.akte-stamm { grid-area: stamm; } +.akte-beladung { grid-area: beladung; } +.akte-wartung { grid-area: wartung; } +.akte-pruefungen { grid-area: pruefungen; } +.akte-verantwortlich { grid-area: verantwortlich; } +.akte-maengel { grid-area: maengel; } +.akte-dokumente { grid-area: dokumente; } +.akte-historie { grid-area: historie; } diff --git a/frontend/src/styles/global/alert.css b/frontend/src/styles/global/alert.css new file mode 100644 index 0000000..0448dab --- /dev/null +++ b/frontend/src/styles/global/alert.css @@ -0,0 +1,19 @@ +.alert { + border-radius: var(--radius-sm); + padding: 0.75rem 1rem; + margin-bottom: 1rem; + font-size: 0.95rem; + border: 1px solid transparent; +} + +.alert-warning { + background: var(--color-warning-bg); + color: var(--color-warning); + border-color: rgba(154, 103, 0, 0.25); +} + +.alert-danger { + background: var(--color-danger-bg); + color: var(--color-danger); + border-color: rgba(179, 38, 30, 0.25); +} diff --git a/frontend/src/styles/global/badges.css b/frontend/src/styles/global/badges.css new file mode 100644 index 0000000..e90ac9a --- /dev/null +++ b/frontend/src/styles/global/badges.css @@ -0,0 +1,31 @@ +.badge { + display: inline-flex; + align-items: center; + font-size: 0.76rem; + font-weight: 700; + padding: 0.22rem 0.65rem; + border-radius: 999px; + white-space: nowrap; + letter-spacing: 0.01em; +} + +.badge-neutral { + background: var(--color-muted-bg); + color: var(--color-text-muted); +} + +.badge-warning { + background: var(--color-warning-bg); + color: var(--color-warning); +} + +.badge-success { + background: var(--color-success-bg); + color: var(--color-success); +} + +.badge-danger { + background: var(--color-danger-bg); + color: var(--color-danger); +} + diff --git a/frontend/src/styles/global/base.css b/frontend/src/styles/global/base.css new file mode 100644 index 0000000..6662e60 --- /dev/null +++ b/frontend/src/styles/global/base.css @@ -0,0 +1,56 @@ +body { + transition: background-color 0.15s ease, color 0.15s ease; +} + +* { + box-sizing: border-box; +} + +html, +body, +#root { + height: 100%; +} + +body { + margin: 0; + font-family: var(--font-sans); + background: var(--color-bg); + color: var(--color-text); + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +h1, +h2, +h3 { + margin: 0 0 0.75rem; + line-height: 1.25; + font-weight: 700; + letter-spacing: -0.01em; +} + +h1 { + font-size: 1.6rem; +} + +h2 { + font-size: 1.2rem; +} + +h3 { + font-size: 1.05rem; +} + +p { + margin: 0 0 0.75rem; +} + +a { + color: var(--color-primary); +} + +:focus-visible { + outline: 2px solid var(--color-primary); + outline-offset: 2px; +} diff --git a/frontend/src/styles/global/cards.css b/frontend/src/styles/global/cards.css new file mode 100644 index 0000000..c8af637 --- /dev/null +++ b/frontend/src/styles/global/cards.css @@ -0,0 +1,45 @@ +/* -- Cards ---------------------------------------------------------------- */ + +.card { + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius); + box-shadow: var(--shadow-sm); + padding: 1.1rem 1.2rem; + margin-bottom: 0.75rem; + transition: box-shadow 0.15s ease, border-color 0.15s ease; +} + +.card-list { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 0.6rem; +} + +.card-link { + display: block; + text-decoration: none; + color: inherit; +} + +.card-link:hover { + border-color: var(--color-primary); + box-shadow: var(--shadow-hover); +} + +.card-title-row { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.5rem; +} + +.card-subtitle { + font-size: 0.85rem; + color: var(--color-text-muted); + margin-top: 0.15rem; +} + diff --git a/frontend/src/styles/global/command-palette.css b/frontend/src/styles/global/command-palette.css new file mode 100644 index 0000000..dae223a --- /dev/null +++ b/frontend/src/styles/global/command-palette.css @@ -0,0 +1,54 @@ +/* UI-002 (Epic 20): Command Palette (Ctrl/Cmd+K) */ +.command-palette-overlay { + position: fixed; + inset: 0; + background: rgba(10, 14, 20, 0.5); + display: flex; + align-items: flex-start; + justify-content: center; + padding-top: 12vh; + z-index: 200; +} + +.command-palette { + width: min(90vw, 32rem); + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius); + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25); + padding: 0.75rem; +} + +.command-palette-list { + list-style: none; + margin: 0.5rem 0 0; + padding: 0; + max-height: 60vh; + overflow-y: auto; +} + +.command-palette-empty { + padding: 0.6rem 0.5rem; +} + +.command-palette-item { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.5rem; + background: none; + border: none; + text-align: left; + padding: 0.55rem 0.5rem; + border-radius: var(--radius-sm); + font-size: 0.95rem; + color: var(--color-text); + cursor: pointer; + font-family: inherit; +} + +.command-palette-item:hover, +.command-palette-item:focus-visible { + background: var(--color-surface-sunken); +} diff --git a/frontend/src/styles/global/forms.css b/frontend/src/styles/global/forms.css new file mode 100644 index 0000000..a030e01 --- /dev/null +++ b/frontend/src/styles/global/forms.css @@ -0,0 +1,102 @@ +/* -- Forms ------------------------------------------------------------ */ + +.field { + display: flex; + flex-direction: column; + gap: 0.35rem; + margin-bottom: 1rem; +} + +.field label { + font-size: 0.88rem; + color: var(--color-text-muted); + font-weight: 600; +} + +.input:not([type="checkbox"]):not([type="radio"]), +select.input, +textarea.input { + width: 100%; + font-size: 0.98rem; + padding: 0.6rem 0.75rem; + min-height: 44px; + border: 1px solid var(--color-border-strong); + border-radius: var(--radius-sm); + background: var(--color-surface); + color: var(--color-text); + font-family: inherit; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.input:focus, +select.input:focus { + outline: none; + border-color: var(--color-primary); + box-shadow: 0 0 0 3px var(--color-primary-soft); +} + +.input-search { + font-size: 1.02rem; + padding: 0.8rem 1rem; +} + +/* -- Buttons ------------------------------------------------------------ */ + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.4rem; + font-size: 0.95rem; + font-weight: 600; + padding: 0.6rem 1.05rem; + /* Mindestens 44x44px Touch-Ziel (WCAG 2.5.5) - im Feld/Lager oft mit + Handschuhen bedient. */ + min-height: 44px; + border-radius: var(--radius-sm); + border: 1px solid transparent; + cursor: pointer; + transition: background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease, box-shadow 0.15s ease; + font-family: inherit; +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-primary { + background: var(--color-primary); + color: #fff; + box-shadow: var(--shadow-sm); +} + +.btn-primary:hover:not(:disabled) { + background: var(--color-primary-hover); +} + +.btn-secondary { + background: var(--color-surface); + color: var(--color-text); + border-color: var(--color-border-strong); +} + +.btn-secondary:hover:not(:disabled) { + background: var(--color-surface-sunken); + border-color: var(--color-text-muted); +} + +.btn-danger { + background: var(--color-surface); + color: var(--color-danger); + border-color: var(--color-danger); +} + +.btn-danger:hover:not(:disabled) { + background: var(--color-danger-bg); +} + +.btn-block { + width: 100%; +} + diff --git a/frontend/src/styles/global/layout.css b/frontend/src/styles/global/layout.css new file mode 100644 index 0000000..bf9bab5 --- /dev/null +++ b/frontend/src/styles/global/layout.css @@ -0,0 +1,135 @@ +/* -- Layout ------------------------------------------------------------- */ + +/* Sidebar-Layout (Redesign 3. Durchgang, Vorbild OpenFleet-Screenshot): + .app-shell steht ab Desktop-Breite als Reihe (Sidebar links, Seite rechts), + .topbar ist trotz Klassennamen die Sidebar selbst - kein JSX-Umbau nötig, + AppShell.tsx bleibt unverändert, nur die Werte hier drehen das Layout. + Unter 768px kippt es zurück auf die bisherige horizontale Topbar-Leiste + (siehe Media-Query unten), da der Mobile-Durchgang das schon gut löste. */ + +.app-shell { + min-height: 100%; + display: flex; + flex-direction: row; +} + +.topbar { + background: var(--color-surface); + border-right: 1px solid var(--color-border); + padding: 1.25rem 1rem; + display: flex; + flex-direction: column; + align-items: stretch; + gap: 1.5rem; + width: 230px; + flex: 0 0 230px; + position: sticky; + top: 0; + height: 100vh; + overflow-y: auto; + transition: width 0.18s ease, flex-basis 0.18s ease, padding 0.18s ease; +} + +/* Sidebar einklappbar - Nutzer-Wunsch (mehr Platz für breite Dashboard-Raster + auf Desktop-Monitoren). Nur der Toggle-Button bleibt sichtbar. */ +.topbar-eingeklappt { + width: 48px; + flex: 0 0 48px; + padding: 1.25rem 0.5rem; + align-items: center; + overflow: visible; +} + +.topbar-toggle { + background: none; + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); + color: var(--color-text-muted); + cursor: pointer; + font-size: 0.9rem; + line-height: 1; + padding: 0.4rem 0.55rem; + align-self: flex-end; +} + +.topbar-eingeklappt .topbar-toggle { + align-self: center; +} + +.topbar-toggle:hover { + background: var(--color-surface-sunken); + color: var(--color-text); +} + +.topbar-brand { + font-weight: 800; + font-size: 1.15rem; + letter-spacing: -0.02em; + color: var(--color-primary); + text-decoration: none; + padding: 0 0.5rem; +} + +.topbar-nav { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 0.15rem; +} + +.topbar-nav a { + color: var(--color-text-muted); + text-decoration: none; + padding: 0.55rem 0.75rem; + border-radius: var(--radius-sm); + font-size: 0.92rem; + font-weight: 600; + transition: background-color 0.15s ease, color 0.15s ease; +} + +.topbar-nav a:hover { + background: var(--color-surface-sunken); + color: var(--color-text); +} + +.topbar-nav a.active { + background: var(--color-primary-soft); + color: var(--color-primary); +} + +.page { + flex: 1; + width: 100%; + max-width: 640px; + margin: 0 auto; + padding: 1.5rem 1.25rem 2.5rem; +} + +.page-narrow { + max-width: 380px; +} + +/* Verwaltung (Admin/Material-/Leitungsverantwortliche) läuft am Desktop - + Tabellen (Änderungslog) und mehrspaltige Formulare brauchen mehr Platz als + die mobile 640px-Kontroll-Ansicht der Mitarbeiter. Nutzer-Vorgabe: links + ausgerichtet statt zentriert - bei 1200px sonst viel verschenkter Platz + rechts/links auf breiten Monitoren. */ +.page-wide { + flex: 1; + width: 100%; + max-width: 1240px; + margin: 0; + padding: 1.75rem 2rem 3rem; +} + +/* Status-Monitor-Modus (Dashboard dauerhaft auf Wandmonitor, Betrachtung aus + mehreren Metern Entfernung) - alles per zoom vergrößern statt einzelne + Tailwind-rem-Klassen zu duplizieren (rem ist immer relativ zum Root-, + Container-Schriftgröße ändern hätte also keine Wirkung auf Tailwind-Text- + /Abstandsklassen). zoom skaliert das gesamte gerenderte Layout inkl. + react-grid-layout-Raster gemeinsam mit den echten Pixelmaßen. */ +[data-monitor="true"] { + max-width: none; + zoom: 1.3; +} + diff --git a/frontend/src/styles/global/responsive.css b/frontend/src/styles/global/responsive.css new file mode 100644 index 0000000..bf3f6df --- /dev/null +++ b/frontend/src/styles/global/responsive.css @@ -0,0 +1,79 @@ +@media (min-width: 900px) { + .akte-grid { + grid-template-columns: 2fr 1fr; + grid-template-areas: + "kopf kopf" + "aktionen aktionen" + "stamm wartung" + "beladung pruefungen" + "verantwortlich maengel" + "dokumente dokumente" + "historie historie"; + } + + .akte-grid.ist-fahrzeug { + grid-template-areas: + "kopf kopf" + "aktionen aktionen" + "wartung stamm" + "pruefungen beladung" + "maengel verantwortlich" + "dokumente dokumente" + "historie historie"; + } +} + +@media (max-width: 768px) { + .split-layout { + flex-direction: column; + } + + .split-layout-sidebar, + .split-layout-sidebar-sm { + width: 100%; + flex: 1 1 auto; + } + + .page-wide { + padding: 1.25rem 1rem 2rem; + } + + /* Sidebar -> horizontale Topbar zurückklappen (Mobile-Durchgang) */ + .app-shell { + flex-direction: column; + } + + .topbar { + flex-direction: row; + align-items: center; + justify-content: space-between; + width: auto; + flex: 0 0 auto; + height: auto; + position: sticky; + padding: 0.6rem 0.9rem; + border-right: none; + border-bottom: 1px solid var(--color-border); + flex-wrap: wrap; + gap: 0.75rem; + } + + .topbar-nav { + flex-direction: row; + flex-wrap: wrap; + gap: 0.15rem; + } + + .topbar-nav a { + padding: 0.4rem 0.6rem; + font-size: 0.85rem; + } + + h1 { + font-size: 1.35rem; + } + + .row { + flex-wrap: wrap; + } +} diff --git a/frontend/src/styles/global/scanner.css b/frontend/src/styles/global/scanner.css new file mode 100644 index 0000000..e580574 --- /dev/null +++ b/frontend/src/styles/global/scanner.css @@ -0,0 +1,24 @@ +.scanner-overlay { + position: fixed; + inset: 0; + background: rgba(10, 14, 20, 0.88); + display: flex; + align-items: center; + justify-content: center; + z-index: 100; +} + +.scanner-box { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + padding: 1rem; +} + +.scanner-video { + width: min(90vw, 480px); + border-radius: var(--radius); + background: #000; +} + diff --git a/frontend/src/styles/global/split-layout.css b/frontend/src/styles/global/split-layout.css new file mode 100644 index 0000000..b2be881 --- /dev/null +++ b/frontend/src/styles/global/split-layout.css @@ -0,0 +1,23 @@ +.split-layout { + display: flex; + align-items: flex-start; + gap: 1.25rem; +} + +.split-layout-sidebar { + flex: 0 0 20rem; + width: 20rem; + box-sizing: border-box; +} + +.split-layout-sidebar-sm { + flex: 0 0 12rem; + width: 12rem; + box-sizing: border-box; + padding: 0.5rem; +} + +.split-layout-content { + flex: 1 1 0%; + min-width: 0; +} diff --git a/frontend/src/styles/global/table.css b/frontend/src/styles/global/table.css new file mode 100644 index 0000000..bc4b7a6 --- /dev/null +++ b/frontend/src/styles/global/table.css @@ -0,0 +1,38 @@ +.table { + width: 100%; + border-collapse: collapse; + font-size: 0.85rem; +} + +.table th, +.table td { + text-align: left; + padding: 0.55rem 0.7rem; + border-bottom: 1px solid var(--color-border); + white-space: nowrap; +} + +.table th { + font-weight: 700; + color: var(--color-text-muted); + background: var(--color-surface-sunken); + position: sticky; + top: 0; +} + +.table tbody tr:hover { + background: var(--color-surface-sunken); +} + +/* Objekte-Liste: nach Standort gruppiert, ab 900px Tabelle statt Karten + (mehr Objekte auf einen Blick am Desktop) - Umschaltung per useIsDesktop- + Hook (echte Breiten-Erkennung, reagiert live auf Resize), nicht per CSS. */ +.objekte-gruppe { + margin-bottom: 1.75rem; +} + +.objekte-gruppe-titel { + font-size: 1rem; + margin-bottom: 0.6rem; +} + diff --git a/frontend/src/styles/global/theme-toggle.css b/frontend/src/styles/global/theme-toggle.css new file mode 100644 index 0000000..e8eedb8 --- /dev/null +++ b/frontend/src/styles/global/theme-toggle.css @@ -0,0 +1,15 @@ +.theme-toggle { + background: transparent; + border: 1px solid var(--color-border-strong); + color: var(--color-text-muted); + border-radius: var(--radius-sm); + padding: 0.4rem 0.6rem; + cursor: pointer; + font-size: 0.85rem; + line-height: 1; +} + +.theme-toggle:hover { + background: var(--color-surface-sunken); + color: var(--color-text); +} diff --git a/frontend/src/styles/global/tokens.css b/frontend/src/styles/global/tokens.css new file mode 100644 index 0000000..244b38a --- /dev/null +++ b/frontend/src/styles/global/tokens.css @@ -0,0 +1,108 @@ +:root { + /* Redesign 2026-09-05, zweiter Durchgang: bewusst schlichter/flacher + (Nutzer-Vorgabe "schlichtes Webdesign wie OpenFleet") - Schatten fast + weg, Radius kleiner, kein Hover-Glow mehr, Farben ruhiger. Alle + bestehenden Klassennamen bleiben unverändert. Mobile-Anpassungen aus + dem vorigen Durchgang bleiben bestehen. */ + --color-bg: #f4f5f7; + --color-surface: #ffffff; + --color-surface-sunken: #f8f9fa; + --color-border: #e1e4e8; + --color-border-strong: #c9ced4; + --color-text: #1c2126; + --color-text-muted: #64707a; + --color-primary: #0a4d8c; + --color-primary-hover: #073a6b; + --color-primary-soft: #eaf1fa; + --color-danger: #b3261e; + --color-danger-bg: #fbeaea; + --color-warning: #9a6700; + --color-warning-bg: #fff3d6; + --color-success: #1e7a34; + --color-success-bg: #e7f5ea; + --color-muted-bg: #eef0f3; + --color-ready: #1e7a34; + --color-ready-bg: #e7f5ea; + --color-limited: #9a6700; + --color-limited-bg: #fff3d6; + --color-not-ready: #b3261e; + --color-not-ready-bg: #fbeaea; + --color-unknown: #55606b; + --color-unknown-bg: #e9ebee; + /* Dashboard-Redesign: einheitliche Statusfarben-Namen für Kacheln + (ok/warn/unknown/critical), als Alias auf bestehende Tokens - ändern sich + automatisch mit, wenn Light/Dark die --color-ready/... Werte neu setzen. */ + --status-ok: var(--color-ready); + --status-warn: var(--color-limited); + --status-unknown: var(--color-unknown); + --status-critical: var(--color-not-ready); + --radius: 8px; + --radius-sm: 6px; + --shadow-sm: none; + --shadow: none; + --shadow-hover: none; + --font-sans: "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; +} + +/* UI-001 (Epic 20): Dark Mode - Systemeinstellung per prefers-color-scheme, + manueller Override per [data-theme] auf (ThemeToggle-Komponente, + Präferenz in localStorage). Reihenfolge wichtig: System-Media-Query zuerst, + [data-theme="dark"] danach, damit der manuelle Schalter in beide Richtungen + gewinnt. */ +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + --color-bg: #14171a; + --color-surface: #1c2024; + --color-surface-sunken: #22262b; + --color-border: #333941; + --color-border-strong: #454c55; + --color-text: #e8eaed; + --color-text-muted: #9aa4ae; + --color-primary: #5b9bd8; + --color-primary-hover: #7bb0e2; + --color-primary-soft: #1e2c3a; + --color-danger: #e37c76; + --color-danger-bg: #3a2224; + --color-warning: #e0b34d; + --color-warning-bg: #3a3120; + --color-success: #6cc386; + --color-success-bg: #1f3324; + --color-muted-bg: #262b31; + --color-ready: #6cc386; + --color-ready-bg: #1f3324; + --color-limited: #e0b34d; + --color-limited-bg: #3a3120; + --color-not-ready: #e37c76; + --color-not-ready-bg: #3a2224; + --color-unknown: #9aa4ae; + --color-unknown-bg: #262b31; + } +} + +:root[data-theme="dark"] { + --color-bg: #14171a; + --color-surface: #1c2024; + --color-surface-sunken: #22262b; + --color-border: #333941; + --color-border-strong: #454c55; + --color-text: #e8eaed; + --color-text-muted: #9aa4ae; + --color-primary: #5b9bd8; + --color-primary-hover: #7bb0e2; + --color-primary-soft: #1e2c3a; + --color-danger: #e37c76; + --color-danger-bg: #3a2224; + --color-warning: #e0b34d; + --color-warning-bg: #3a3120; + --color-success: #6cc386; + --color-success-bg: #1f3324; + --color-muted-bg: #262b31; + --color-ready: #6cc386; + --color-ready-bg: #1f3324; + --color-limited: #e0b34d; + --color-limited-bg: #3a3120; + --color-not-ready: #e37c76; + --color-not-ready-bg: #3a2224; + --color-unknown: #9aa4ae; + --color-unknown-bg: #262b31; +} diff --git a/frontend/src/styles/global/utilities.css b/frontend/src/styles/global/utilities.css new file mode 100644 index 0000000..ead8b62 --- /dev/null +++ b/frontend/src/styles/global/utilities.css @@ -0,0 +1,53 @@ +.text-muted { + color: var(--color-text-muted); +} + +/* Nur für Screenreader sichtbar - Label-Text ohne visuelle Verschiebung. */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.text-success { + color: var(--color-success); +} + +.text-danger { + color: var(--color-danger); +} + +.stack { + display: flex; + flex-direction: column; + gap: 0.6rem; +} + +.row { + display: flex; + gap: 0.6rem; + align-items: center; +} + +.row-between { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.6rem; +} + +.section { + margin-bottom: 2.25rem; +} + +.section h2 { + padding-bottom: 0.5rem; + border-bottom: 1px solid var(--color-border); +} + diff --git a/frontend/src/styles/tailwind.css b/frontend/src/styles/tailwind.css index 4a7640a..02f8f26 100644 --- a/frontend/src/styles/tailwind.css +++ b/frontend/src/styles/tailwind.css @@ -1,23 +1,44 @@ -/* Nur Theme+Utilities, kein Preflight - global.css bleibt für den Rest der App - die Basis, Tailwind wird gezielt fürs Dashboard-Redesign genutzt. - WICHTIG: global.css muss selbst in einem Layer laufen ("global-css" hier +/* Nur Theme+Utilities, kein Preflight - global/*.css bleibt für den Rest der + App die Basis, Tailwind wird gezielt fürs Dashboard-Redesign genutzt. + WICHTIG: global/*.css muss selbst in einem Layer laufen ("global-css" hier niedriger priorisiert als "utilities") - unlayertes CSS gewinnt in der CSS-Kaskade IMMER gegen jeden Layer, egal wie spezifisch. Ohne diesen Layer-Import hätten Tailwind-Utility-Klassen praktisch keine Wirkung gegen - die bestehenden global.css-Regeln gehabt. */ + die bestehenden global/*.css-Regeln gehabt. + + Reihenfolge der Imports entspricht der ursprünglichen Reihenfolge in der + früheren monolithischen global.css (871 Zeilen, in Einzeldateien nach + Zuständigkeit aufgeteilt) - innerhalb EINES Layers entscheidet bei gleicher + Spezifität weiterhin die Import-Reihenfolge, also nicht umsortieren ohne + Grund. */ @layer global-css, theme, utilities; -@import "./global.css" layer(global-css); +@import "./global/tokens.css" layer(global-css); +@import "./global/base.css" layer(global-css); +@import "./global/layout.css" layer(global-css); +@import "./global/cards.css" layer(global-css); +@import "./global/forms.css" layer(global-css); +@import "./global/badges.css" layer(global-css); +@import "./global/theme-toggle.css" layer(global-css); +@import "./global/command-palette.css" layer(global-css); +@import "./global/alert.css" layer(global-css); +@import "./global/utilities.css" layer(global-css); +@import "./global/scanner.css" layer(global-css); +@import "./global/table.css" layer(global-css); +@import "./global/split-layout.css" layer(global-css); +@import "./global/akte-grid.css" layer(global-css); +@import "./global/responsive.css" layer(global-css); @import "tailwindcss/theme.css" layer(theme); @import "tailwindcss/utilities.css" layer(utilities); /* KEIN @theme-Mapping auf --color-primary/--color-danger/etc.! Das wurde zweimal versucht (auch über Zwischen-Variablen) und ist beide Male an zirkulären CSS-Custom-Property-Referenzen gescheitert: sobald Tailwinds - Theme-Namespace denselben Property-Namen wie global.css belegt, gewinnt der - höher priorisierte Theme-Layer die Kaskade für GENAU diesen Namen - jede - var()-Kette, die (auch indirekt) wieder bei diesem Namen landet, wird zur - "guaranteed-invalid value" und bricht app-weit, nicht nur für Tailwind- - Utilities. Statt eigener Theme-Tokens werden bestehende global.css-Variablen - deshalb überall direkt per Tailwind-Arbitrary-Value-Syntax genutzt, z. B. - `bg-[var(--color-primary)]` statt `bg-primary` - keine Namenskollision mehr - möglich, da kein neuer Property-Name im Theme-Layer definiert wird. */ + Theme-Namespace denselben Property-Namen wie global/tokens.css belegt, + gewinnt der höher priorisierte Theme-Layer die Kaskade für GENAU diesen + Namen - jede var()-Kette, die (auch indirekt) wieder bei diesem Namen + landet, wird zur "guaranteed-invalid value" und bricht app-weit, nicht nur + für Tailwind-Utilities. Statt eigener Theme-Tokens werden bestehende + Variablen aus global/tokens.css deshalb überall direkt per Tailwind- + Arbitrary-Value-Syntax genutzt, z. B. `bg-[var(--color-primary)]` statt + `bg-primary` - keine Namenskollision mehr möglich, da kein neuer + Property-Name im Theme-Layer definiert wird. */