From ce910bdba9ee19b27f46b611ff8a81973c990950 Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Thu, 19 Mar 2026 15:41:41 +0100 Subject: [PATCH] docs: aktualisiere UI Guidelines mit votian-modern Theme --- UI_GUIDELINES.md | 1020 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1020 insertions(+) create mode 100644 UI_GUIDELINES.md diff --git a/UI_GUIDELINES.md b/UI_GUIDELINES.md new file mode 100644 index 0000000..a197b0b --- /dev/null +++ b/UI_GUIDELINES.md @@ -0,0 +1,1020 @@ +# VotianLT – UI Theme & Gestaltungsrichtlinien + +> Gilt für alle Views unter `src/main/java/de/assecutor/votianlt/pages/view/` +> Theme-Datei: `src/main/frontend/themes/votian-modern/styles.css` + +--- + +## 1. Design-Prinzipien + +- **Konsistenz**: Alle Views nutzen dieselben Klassen, Abstände und Farben. +- **Kein Overflow**: Kein View darf horizontal scrollen. Alle Container haben `box-sizing: border-box`, `max-width: 100%` und `min-width: 0`. +- **Kein externes Margin/Padding am View-Root**: Views füllen ihren Container vollständig aus. Abstände zum Bildschirmrand entstehen ausschließlich durch den `view-container`. +- **Lumo-Overrides**: Vaadin-Lumo-Standardstile werden durch gezielte CSS-Regeln mit höherer Spezifizität überschrieben – **nicht** mit inline Styles in Java-Code lösen. + +--- + +## 2. Farben & Custom Properties + +Alle Farben sind als CSS-Custom-Properties auf `html` definiert und **müssen** statt hartcodierter Hex-Werte verwendet werden. + +### Hauptfarben + +| Variable | Wert | Verwendung | +|---|---|---| +| `--app-accent` | `#2563eb` | Primäraktionen, Links, Highlights | +| `--app-accent-strong` | `#1d4ed8` | Hover-Zustand von Primärbuttons | +| `--app-accent-soft` | `rgba(37,99,235,0.12)` | Selektierter Zeilenhintergrund | +| `--app-success` | `#059669` | Erfolgsstatus, abgeschlossene Aufgaben | +| `--app-warning` | `#d97706` | Warnungen | +| `--app-danger` | `#dc2626` | Fehler, kritische Zustände | + +### Oberflächen & Hintergründe + +| Variable | Wert | Verwendung | +|---|---|---| +| `--app-surface` | `rgba(255,255,255,0.78)` | Karten, Panels (Glasmorphismus) | +| `--app-surface-solid` | `#ffffff` | Volldeckende Flächen | +| `--app-surface-muted` | `rgba(247,250,255,0.88)` | Gedämpfte Panels | +| `--app-shell-background` | Radial+Linear-Gradient | Seitenhintergrund (Body) | +| `--app-sidebar-background` | Linear-Gradient dunkelblau | Drawer/Sidebar | + +### Text + +| Variable | Wert | Verwendung | +|---|---|---| +| `--app-text-strong` | `#0f172a` | Überschriften, wichtige Labels | +| `--app-text` | `#1e293b` | Fließtext | +| `--app-text-muted` | `#64748b` | Sekundärtext, Beschreibungen | + +### Ränder + +| Variable | Wert | Verwendung | +|---|---|---| +| `--app-border` | `rgba(148,163,184,0.18)` | Subtile Trennlinien | +| `--app-border-strong` | `rgba(148,163,184,0.28)` | Sichtbare Kartenränder | +| `--app-field-border` | `#d6dde7` | Formularfeld-Rand | +| `--app-field-border-hover` | `#c6d0dd` | Formularfeld-Rand (Hover) | + +### Schatten + +| Variable | Wert | Verwendung | +|---|---|---| +| `--app-shadow-sm` | `0 12px 28px rgba(15,23,42,0.08)` | Kleine Karten | +| `--app-shadow-md` | `0 20px 44px rgba(15,23,42,0.10)` | Mittlere Panels | +| `--app-shadow-lg` | `0 28px 72px rgba(15,23,42,0.12)` | Surface-Panels | +| `--app-shadow-xl` | `0 36px 88px rgba(8,18,36,0.24)` | Hero-Bereiche, Dialoge | + +--- + +## 3. Typografie + +**Schriftart**: `Manrope` (Google Fonts, Gewichte 400–800) +**Fallbacks**: `Avenir Next`, `Segoe UI`, `sans-serif` + +Lumo-Überschreibungen: +```css +--lumo-font-family: "Manrope", "Avenir Next", "Segoe UI", sans-serif; +--lumo-base-color: #f5f7fb; +``` + +Alle `h1`–`h6` haben `letter-spacing: -0.03em` und `color: var(--app-text-strong)`. + +### Responsive Schriftgrößen (`clamp`) + +| Element | Wert | +|---|---| +| `.form-title` | `clamp(1.55rem, 3vw, 2.2rem)`, font-weight 800 | +| `.section-title` | `clamp(1.4rem, 3.4vw, 2rem)`, font-weight 800 | +| `.hero-panel-title` | `clamp(2rem, 5vw, 3.4rem)`, font-weight 800 | +| `.dashboard-title` | `clamp(1.7rem, 3.2vw, 2.5rem)`, font-weight 800 | +| `.login-card-title` | `clamp(1.8rem, 4vw, 2.4rem)`, font-weight 800 | + +**Regel**: Überschriften mit `font-weight: 800` und `letter-spacing: -0.05em` bis `-0.06em` für Großtitel. + +--- + +## 4. Layout-Struktur (App Shell) + +``` +vaadin-app-layout (336px Drawer) +├── [part=drawer] ← Sidebar (Dunkelblau-Gradient) +│ ├── .app-drawer-header +│ ├── .app-drawer-scroll (Scroller, flex: 1) +│ │ └── .app-nav-container +│ │ └── .app-nav-tree (TreeGrid) +│ └── .app-user-menu +└── [Light DOM default slot] + └── .view-container ← Routed View Container + └── +``` + +### `view-container` + +Der Container, in dem alle gerouteten Views dargestellt werden: + +```css +.view-container { + display: flex; + flex-direction: column; + margin: 20px; /* Abstand zum Browserrand */ + width: calc(100% - 40px); + height: calc(100dvh - 40px); + overflow: hidden; + border-radius: 12px; + box-sizing: border-box; +} +``` + +**Wichtig**: Views, die in `.view-container` landen, erhalten automatisch: +```css +flex: 1 1 0; min-height: 0; min-width: 0; max-width: 100%; box-sizing: border-box; +``` + +--- + +## 5. View-Klassen + +Jede View muss **genau eine** der folgenden Root-Klassen tragen: + +| Klasse | Verwendung | Extends | +|---|---|---| +| `data-view` | Listen, Tabellen, Grids | `Main` oder `VerticalLayout` | +| `form-page` | Formulare mit zentriertem Inhalt | `VerticalLayout` | +| `admin-form-view` | Admin-Formulare | `VerticalLayout` | +| `message-hub-view` | Nachrichten-Übersicht | `Main` | +| `statistics-chat-view` | Statistik/Chat | `VerticalLayout` | +| `dashboard-view` | Dashboard-Seiten | `VerticalLayout` | +| `landing-view` | Startseite (unauthentifiziert) | `Main` | +| `login-view` | Login-Seite | `Main` | + +### Kritische Java-Regeln + +```java +// ✅ Richtig für Daten-Views: +addClassName("data-view"); +// KEIN setJustifyContentMode(CENTER) — wird per CSS auf START erzwungen + +// ✅ Richtig für Formular-Views (zentrierter Inhalt): +addClassName("form-page"); +setJustifyContentMode(JustifyContentMode.CENTER); +setDefaultHorizontalComponentAlignment(Alignment.CENTER); + +// ❌ Falsch: setPadding(true) auf View-Root oder form-shell ohne form-card +// → wird per CSS auf 0 zurückgesetzt, erzeugt keine visuelle Wirkung +``` + +--- + +## 6. Interne Layout-Klassen + +### `form-shell` – Vollbreite-Wrapper + +```java +VerticalLayout layout = new VerticalLayout(); +layout.setPadding(false); +layout.setSpacing(true); +layout.setWidthFull(); +layout.addClassName("form-shell"); +``` + +**Regeln**: +- Kein `setPadding(true)` → hat keine Wirkung, da via CSS überschrieben +- Kein horizontales Margin +- `box-sizing: border-box`, `max-width: 100%`, `min-width: 0` + +### `form-card` – Schmales zentriertes Karten-Formular + +```java +container.addClassNames("form-shell", "narrow", "form-card"); +container.setWidth("400px"); +container.setMaxWidth("100%"); +``` + +Bekommt **weißen Hintergrund** (0.9 Alpha), `border-radius: 28px`, `backdrop-filter: blur(18px)`. + +### `surface-panel` – Datenpanel / Tabellenkarte + +```java +Div panel = new Div(grid); +panel.addClassNames("surface-panel", "data-grid-panel"); +panel.setWidthFull(); +``` + +```css +.surface-panel { + border: 1px solid var(--app-border-strong); + background: var(--app-surface); + backdrop-filter: blur(18px); + box-shadow: var(--app-shadow-lg); + border-radius: 28px; + box-sizing: border-box; + max-width: 100%; +} +.data-grid-panel { + padding: 0.8rem; + min-height: 420px; + box-sizing: border-box; + max-width: 100%; +} +``` + +--- + +## 7. ViewToolbar + +Einheitliche Toolbar für alle Seiten-Überschriften: + +```java +add(new ViewToolbar(getTranslation("my.view.title"))); +// Mit Aktionsbutton: +add(new ViewToolbar(getTranslation("my.view.title"), myButton)); +``` + +```css +.view-toolbar { + width: 100%; + padding: 0.75rem 1rem; + box-sizing: border-box; +} +.view-toolbar-title { + color: var(--app-text-strong); + font-weight: 800; + letter-spacing: -0.05em; +} +``` + +--- + +## 8. Daten-Grids + +Standard-Muster für alle Tabellen-Views: + +```java +// View-Root +setSizeFull(); +addClassName("data-view"); + +// ViewToolbar +add(new ViewToolbar(getTranslation("title"), addButton)); + +// Grid +Grid grid = new Grid<>(T.class, false); +grid.setWidthFull(); +grid.setHeightFull(); +grid.addClassName("data-grid"); + +// Panel-Wrapper +Div panel = new Div(grid); +panel.addClassNames("surface-panel", "data-grid-panel"); +panel.setWidthFull(); +add(panel); +``` + +**Grid-Styling**: +- Hintergrund: `rgba(255,255,255,0.88)`, `border-radius: 24px` +- Header: Gedämpftes Grau, Uppercase, `font-weight: 800`, `font-size: 0.76rem` +- Selektierte Zeile: `--app-accent-soft` Hintergrund + +--- + +## 9. Formulare (Form-Views) + +```java +// View-Root +setSizeFull(); +setJustifyContentMode(JustifyContentMode.CENTER); +setDefaultHorizontalComponentAlignment(Alignment.CENTER); +setPadding(true); +addClassName("form-page"); + +// Formular-Container +VerticalLayout container = new VerticalLayout(); +container.setWidth("600px"); +container.setMaxWidth("100%"); +container.setPadding(true); +container.setSpacing(true); +container.addClassNames("form-shell", "form-card"); + +// Formular-Felder +FormLayout form = new FormLayout(); +form.setWidthFull(); +form.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 2)); +``` + +**Formularfelder** haben automatisch: +- `border-radius: 20px` +- `background: #ffffff` +- `border: 1px solid var(--app-field-border)` +- Hover: Blaue Highlight-Farbe +- Fokus: Blauer Ring `rgba(37,99,235,0.18)` + +--- + +## 10. Filter-Panels + +```java +HorizontalLayout filterBar = new HorizontalLayout(); +filterBar.addClassName("filter-panel"); +filterBar.setWidthFull(); +``` + +```css +.filter-panel { + padding: 1rem 1.15rem; + border-radius: 26px; + flex-wrap: wrap; + gap: 1rem; + min-width: 0; + max-width: 100%; + box-sizing: border-box; +} +``` + +**Wichtig**: Filter-Felder **niemals** mit fixen Pixel-Breiten versehen → `flex-wrap: wrap` und `min-width: 0` sorgen für korrektes Responsive-Verhalten. + +--- + +## 11. Sidebar / Navigation + +### Nav-Rows + +```css +.app-nav-row { + border-radius: 18px; + border: 1px solid rgba(255,255,255,0.06); + background: rgba(255,255,255,0.08); + max-width: calc(100% - 4px); + transition: transform 0.18s ease, background-color 0.18s ease; +} +.app-nav-row:hover { + transform: translateX(4px); + background: rgba(255,255,255,0.14); +} +``` + +### User Menu + +```css +.app-user-menu { + margin: auto 0.5rem 0.5rem; + padding: 0.45rem 0.65rem; + border-radius: 22px; + border: 1px solid rgba(255,255,255,0.1); + background: rgba(255,255,255,0.08); + backdrop-filter: blur(14px); +} +``` + +--- + +## 12. Karten-Hierarchie + +| Klasse | Radius | Hintergrund | Einsatz | +|---|---|---|---| +| `surface-panel` | 28px | rgba(255,255,255,0.78) + Blur | Hauptdatenpanels | +| `form-card` | 28px | rgba(255,255,255,0.90) + Blur | Formular-Cards | +| `section-card`, `content-card` | 26px | rgba(255,255,255,0.88) | Abschnittspanels | +| `simple-card` | 26px | rgba(255,255,255,0.88) | Einfache Karten | +| `detail-card` | 24px | rgba(255,255,255,0.88) | Detail-Informationen | +| `hero-panel` | 34px | Dunkelblau-Gradient | Hero-Bereiche | +| `feature-card` | 24px | Linear-Gradient weiß/blau | Feature-Kacheln | +| `message-card` | 22px | Linear-Gradient weiß | Nachrichten | +| `station-tile` | 24px | Linear-Gradient weiß | Station-Kacheln | +| `job-task-card` | 22px | Linear-Gradient weiß | Aufgaben-Karten | + +--- + +## 13. Station Tiles & Kacheln + +### Station Tile + +```css +.station-tile { + border-radius: 24px; + border: 1px solid var(--app-border-strong); + background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(244,248,255,0.9)); + box-shadow: var(--app-shadow-sm); + transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; +} +.station-tile:hover { + transform: translateY(-3px); + box-shadow: var(--app-shadow-md); + border-color: rgba(37,99,235,0.24); +} +.station-tile.validated { + background: linear-gradient(180deg, rgba(236,253,245,0.98), rgba(209,250,229,0.88)); + border-color: rgba(5,150,105,0.26); +} +``` + +### Add Station Tile (gestrichelt) + +```css +.add-station-tile { + background: linear-gradient(180deg, rgba(241,245,249,0.9), rgba(248,250,252,0.98)); + border-style: dashed; +} +``` + +### Stations Grid + +```css +.stations-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 1rem; + width: 100%; +} +``` + +--- + +## 14. Job Task Cards + +```css +.job-task-card { + display: flex; + align-items: center; + gap: 1rem; + padding: 1rem; + border: 1px solid var(--app-border-strong); + border-radius: 22px; + background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(246,249,255,0.9)); + box-shadow: var(--app-shadow-sm); + transition: transform 0.2s ease, box-shadow 0.2s ease; + cursor: pointer; +} +.job-task-card:hover { + transform: translateY(-2px); + box-shadow: var(--app-shadow-md); + border-color: rgba(37,99,235,0.24); +} +.job-task-card.completed { + border-color: rgba(5,150,105,0.22); + background: linear-gradient(180deg, rgba(236,253,245,0.98), rgba(220,252,231,0.88)); +} +``` + +### Task Status Badges + +```css +.job-task-status.open { + background: rgba(220,38,38,0.12); + color: var(--lumo-error-text-color); +} +.job-task-status.completed { + background: rgba(5,150,105,0.12); + color: var(--lumo-success-text-color); +} +``` + +--- + +## 15. Dashboard Stat Cards + +```css +.dashboard-stat-card { + position: relative; + border-radius: 24px; + padding: 1.15rem; + border: 1px solid var(--app-border-strong); + background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(246,249,255,0.92)); + box-shadow: var(--app-shadow-md); +} +.dashboard-stat-card::before { + content: ""; + position: absolute; + inset: 0 auto 0 0; + width: 4px; + background: var(--dashboard-accent, var(--app-accent)); +} +``` + +### Akzent-Farben + +```css +.accent-blue { --dashboard-accent: #2563eb; } +.accent-green { --dashboard-accent: #059669; } +.accent-purple { --dashboard-accent: #7c3aed; } +.accent-orange { --dashboard-accent: #d97706; } +.accent-gray { --dashboard-accent: #64748b; } +.accent-red { --dashboard-accent: #dc2626; } +``` + +--- + +## 16. Message & Chat Components + +### Message Card + +```css +.message-card { + padding: 1rem; + border: 1px solid var(--app-border-strong); + border-radius: 22px; + background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(246,249,255,0.9)); + box-shadow: var(--app-shadow-sm); + transition: transform 0.22s ease, box-shadow 0.22s ease; +} +.message-card:hover { + transform: translateY(-4px); + box-shadow: 0 24px 48px rgba(15,23,42,0.14); +} +``` + +### Chat Bubbles + +```css +.message-bubble { + max-width: min(78%, 720px); + padding: 0.85rem 1rem; + border-radius: 22px; + box-shadow: var(--app-shadow-sm); +} +.message-bubble.client { + background: rgba(255,255,255,0.96); + border: 1px solid var(--app-border-strong); +} +.message-bubble.server { + background: linear-gradient(135deg, rgba(191,219,254,0.8), rgba(167,243,208,0.74)); + border: 1px solid rgba(59,130,246,0.22); +} +``` + +### Chat AI/User Bubbles + +```css +.chat-bubble--user { + background: linear-gradient(135deg, #2563eb, #1d4ed8); + color: #eff6ff; + padding: 0.85rem 1rem; + border-radius: 24px; +} +.chat-bubble--ai { + background: rgba(255,255,255,0.94); + border: 1px solid var(--app-border-strong); + padding: 1rem; + border-radius: 24px; +} +``` + +--- + +## 17. Dialoge + +```css +vaadin-dialog-overlay::part(content) { + border-radius: 28px; + border: 1px solid var(--app-border-strong); + background: rgba(255,255,255,0.94); + backdrop-filter: blur(20px); + box-shadow: var(--app-shadow-xl); +} +``` + +### Dialog Panels + +| Klasse | Verwendung | +|---|---| +| `.dialog-form-panel` | Formulare im Dialog | +| `.dialog-content-panel` | Inhalte im Dialog | +| `.dialog-task-card` | Aufgaben-Karten im Dialog | +| `.dialog-cargo-card` | Fracht-Karten im Dialog | + +```css +.dialog-form-panel, +.dialog-content-panel { + backdrop-filter: blur(12px); + padding: 1rem; + border-radius: 24px; +} +``` + +--- + +## 18. Detail Cards + +```css +.detail-card { + border: 1px solid var(--app-border-strong); + border-radius: 24px; + background: rgba(255,255,255,0.9); + box-shadow: var(--app-shadow-sm); + padding: 1rem; +} +.detail-card--accent { + border-color: rgba(37,99,235,0.22); + background: linear-gradient(180deg, rgba(219,234,254,0.62), rgba(239,246,255,0.9)); +} +.detail-card--code, +.detail-card--comment { + font-family: ui-monospace, "SFMono-Regular", monospace; +} +``` + +--- + +## 19. Route & Summary Cards + +```css +.route-card, +.summary-card { + border: 1px solid var(--app-border-strong); + border-radius: 24px; + background: rgba(255,255,255,0.84); + box-shadow: var(--app-shadow-sm); + padding: 1rem; +} +.services-panel, +.notes-panel { + border: 1px solid var(--app-border-strong); + border-radius: 24px; + background: rgba(255,255,255,0.84); + box-shadow: var(--app-shadow-sm); + padding: 0.75rem 1rem 1rem; +} +``` + +--- + +## 20. Login Seite + +```css +.login-shell { + width: min(1120px, 100%); + display: grid; + grid-template-columns: minmax(0, 1.15fr) minmax(360px, 440px); + gap: 1.5rem; +} +.login-highlight { + position: relative; + overflow: hidden; + min-height: 560px; + padding: clamp(1.8rem, 4vw, 3rem); + border-radius: 32px; + border: 1px solid rgba(255,255,255,0.14); + background: linear-gradient(140deg, #081224 0%, #173d8d 52%, #0f766e 100%); + box-shadow: var(--app-shadow-xl); +} +.login-card { + width: 100%; + padding: clamp(1.35rem, 3vw, 2rem); + border-radius: 32px; + border: 1px solid var(--app-border-strong); + background: rgba(255,255,255,0.88); + backdrop-filter: blur(20px); + box-shadow: var(--app-shadow-lg); +} +``` + +### Eyebrow Chip + +```css +.eyebrow-chip { + display: inline-flex; + align-items: center; + padding: 0.45rem 0.8rem; + border-radius: 999px; + background: rgba(255,255,255,0.14); + color: rgba(248,250,252,0.92); + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; +} +``` + +--- + +## 21. Landing Page + +```css +.landing-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + flex-wrap: wrap; +} +.landing-logo { + font-size: clamp(1.6rem, 4vw, 2.2rem); + font-weight: 800; + letter-spacing: -0.05em; +} +.landing-nav-button, +.landing-language-button { + border-radius: 999px; +} +``` + +### Hero Panel + +```css +.hero-panel { + position: relative; + overflow: hidden; + min-height: 340px; + justify-content: center; + text-align: center; + border-radius: 34px; + border: 1px solid rgba(255,255,255,0.14); + background: linear-gradient(135deg, #081224 0%, #1d4ed8 54%, #0f766e 100%); + box-shadow: var(--app-shadow-xl); +} +.hero-panel-title { + color: #f8fafc; + font-size: clamp(2rem, 5vw, 3.4rem); + font-weight: 800; + letter-spacing: -0.06em; +} +.hero-panel-text { + color: rgba(226,232,240,0.92); + font-size: clamp(1rem, 2vw, 1.15rem); +} +``` + +--- + +## 22. Timeline Components + +```css +.timeline-entry-card { + margin-bottom: 0.75rem; + padding: 1rem; + border: 1px solid var(--app-border-strong); + border-radius: 24px; + background: rgba(255,255,255,0.9); + box-shadow: var(--app-shadow-sm); + border-left: 4px solid var(--timeline-accent, var(--app-accent)); +} +.timeline-reason { + font-weight: 700; + color: var(--app-text-strong); +} +.timeline-timestamp { + color: var(--app-text-muted); + font-size: 0.78rem; +} +``` + +--- + +## 23. Invoice Components + +### Invoice Generator + +```css +.invoice-generator-panel { + backdrop-filter: blur(16px); + border-radius: 24px; + padding: 1rem; + border: 1px solid var(--app-border-strong); + background: rgba(255,255,255,0.9); +} +.invoice-generator-template { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.95rem 1rem; + border: 1px solid var(--app-border-strong); + border-radius: 18px; + background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(246,249,255,0.92)); + cursor: grab; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} +.invoice-generator-template:hover { + transform: translateY(-2px); + box-shadow: var(--app-shadow-sm); +} +.invoice-generator-canvas { + position: relative; + overflow: hidden; + border: 2px dashed rgba(148,163,184,0.5); + border-radius: 24px; + background: + linear-gradient(180deg, rgba(255,255,255,0.95), rgba(241,245,249,0.9)), + linear-gradient(90deg, rgba(148,163,184,0.08) 1px, transparent 1px), + linear-gradient(rgba(148,163,184,0.08) 1px, transparent 1px); + background-size: auto, 28px 28px, 28px 28px; +} +``` + +### Price Table + +```css +.price-table { + width: 100%; +} +.price-row { + display: flex; + justify-content: space-between; + gap: 1rem; + padding: 0.35rem 0; +} +.price-row--strong .price-row-label, +.price-row--strong .price-row-value { + font-weight: 800; + color: var(--app-text-strong); +} +``` + +--- + +## 24. Buttons + +```css +vaadin-button { + font-weight: 700; + border-radius: var(--lumo-border-radius-m); + transition: transform 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease; +} +vaadin-button:not([disabled]):hover { + transform: translateY(-1px); +} +vaadin-button[theme~="primary"] { + box-shadow: 0 8px 20px rgba(37,99,235,0.22); +} +vaadin-button[theme~="primary"][theme~="success"] { + box-shadow: 0 8px 20px rgba(5,150,105,0.24); +} +vaadin-button[theme~="primary"][theme~="error"] { + box-shadow: 0 8px 20px rgba(220,38,38,0.24); +} +vaadin-button[disabled] { + opacity: 0.46; + transform: none !important; +} +``` + +**Pill-Buttons** (Nav, Landing): `border-radius: 999px` + +--- + +## 25. Overflow & Box-Model Regeln + +### Pflicht bei allen Container-Elementen + +```java +component.getStyle() + .set("box-sizing", "border-box") + .set("max-width", "100%") + .set("min-width", "0"); +``` + +### Warum `box-sizing: border-box` kritisch ist + +Ohne `box-sizing: border-box` gilt: +``` +Gesamtbreite = width + padding-left + padding-right + border-left + border-right +``` + +- `surface-panel` hat `border: 1px` → ohne box-sizing ist es `100% + 2px` → Overflow +- `data-grid-panel` hat `padding: 0.8rem` → ohne box-sizing ist es `100% + 1.6rem` → Overflow + +### Warum `min-width: 0` kritisch ist + +In Flex-Containern verhindert die Default-Eigenschaft `min-width: auto`, dass Flex-Items kleiner werden als ihr Inhalt. `min-width: 0` erlaubt das Schrumpfen unter die Inhaltsgröße. + +--- + +## 26. Lumo-Override-Strategie + +### Padding-Override + +```css +/* Lumo: vaadin-vertical-layout[theme~="padding"] → Spezifizität 0,0,1,1 */ +/* Override braucht mindestens 0,0,2,0: */ +vaadin-vertical-layout.form-shell:not(.form-card) { padding: 0; } +``` + +### Inline-Style-Override + +`setJustifyContentMode()` setzt Inline-Styles → nur `!important` überschreibt: + +```css +vaadin-vertical-layout.data-view { + justify-content: flex-start !important; + align-items: stretch !important; +} +``` + +**Regel**: `!important` nur für Inline-Style-Overrides verwenden. + +--- + +## 27. Responsive Verhalten + +### Breakpoints + +| Breakpoint | Änderungen | +|---|---| +| `max-width: 980px` | Login-Shell wird einspaltig | +| `max-width: 720px` | `view-container` Margin reduziert auf 10px, border-radius auf 8px | +| `max-height: 820px` | Drawer-Header und Nav-Rows bekommen kompakteres Padding | + +### Mobile View-Container + +```css +@media (max-width: 720px) { + .view-container { + margin: 10px; + width: calc(100% - 20px); + height: calc(100dvh - 20px); + border-radius: 8px; + } +} +``` + +### Flex-Wrap-Pflicht + +Alle horizontalen Layouts, die Felder oder Buttons enthalten, **müssen** `flex-wrap: wrap` haben: + +```java +layout.getStyle().set("flex-wrap", "wrap"); +``` + +--- + +## 28. Photo Gallery + +```css +.job-photo-gallery { + max-width: 600px; + min-height: 500px; + height: 500px; + position: relative; + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; + background: rgba(255,255,255,0.96); + border-radius: 24px; +} +.job-photo-counter { + position: absolute; + top: var(--lumo-space-s); + right: var(--lumo-space-s); + padding: var(--lumo-space-xs) var(--lumo-space-s); + border-radius: 999px; + background: rgba(15,23,42,0.72); + color: #f8fafc; + font-weight: 700; +} +.job-photo-nav { + position: absolute; + top: 50%; + transform: translateY(-50%); + border-radius: 999px; + background: rgba(255,255,255,0.9); + box-shadow: var(--app-shadow-sm); +} +``` + +--- + +## 29. Empty State + +```css +.empty-state-card { + text-align: center; + color: var(--app-text-muted); + padding: 1rem; + border: 1px dashed var(--app-border-strong); + border-radius: 24px; + background: rgba(248,250,252,0.74); +} +``` + +--- + +## 30. Inline Flash (Fehlermeldungen) + +```css +.inline-flash { + width: 100%; + padding: 0.95rem 1rem; + border-radius: 18px; + border: 1px solid rgba(220,38,38,0.22); + background: rgba(254,242,242,0.92); + color: var(--lumo-error-text-color); + box-sizing: border-box; +} +``` + +--- + +## 31. Checkliste für neue Views + +Beim Erstellen einer neuen View folgende Punkte prüfen: + +- [ ] Root-Element hat genau eine View-Klasse (`data-view`, `form-page`, etc.) +- [ ] **Kein** `setPadding(true)` auf dem View-Root oder `form-shell`-Wrappern (ohne `form-card`) +- [ ] **Kein** `setJustifyContentMode(CENTER)` auf Daten-Views (`data-view`, `admin-form-view`) +- [ ] Alle Container haben `max-width: 100%` und `box-sizing: border-box` +- [ ] Alle Flex-Container mit wechselnden Inhalten haben `flex-wrap: wrap` +- [ ] Formularfelder haben **keine** fixen Pixel-Breiten (`setWidth("200px")` verboten) +- [ ] `ViewToolbar` ist das erste Kind-Element +- [ ] Grid-Panel verwendet `surface-panel data-grid-panel` mit `setWidthFull()` +- [ ] Schmale Formular-Container haben `setWidth("Xpx")` **und** `setMaxWidth("100%")` +- [ ] Inline-Styles nur für dynamische Werte verwenden, alles andere per CSS-Klassen