Frontend
Dokumentation zur Frontend-Architektur des Campus-Systems.
Prinzipien
- Server-Side Rendering - PHP rendert HTML
- Progressive Enhancement - HTMX für Interaktivität
- Kein SPA-Framework - Kein React, Vue, Angular
- Minimales JavaScript - Nur wo nötig
Struktur
/src/View/ # PHP-Templates
/public/css/ # Stylesheets
/public/js/ # JavaScript (minimal)
Unterthemen
- HTMX - Interaktivität
- CSS - Styling (geplant)
- Components - Wiederverwendbare Elemente (geplant)
Verwandte Themen
- Regeln - HTMX statt fetch() Regel
- Architektur - MVC/MVP Pattern
CSS Architektur
Übersicht
Das CSS-System basiert auf CSS Custom Properties (Design Tokens) und einer modularen Dateistruktur.
Dateien
| Datei | Zweck |
|---|---|
designtokens.css | CSS-Variablen (Farben, Spacing, etc.) |
style.css | Basis-Styles und Docs-Komponenten |
admin.css | Admin-Komponenten (Buttons, Forms, Cards) |
nav.css | Navigation |
chat-redesign.css | Chat-UI spezifisch |
graph.css | Graph-Visualisierungen |
home.css | Homepage-spezifische Styles |
Design Tokens
Alle Variablen in :root definiert (designtokens.css).
Farben - Primary
--color-primary: #007bff;
--color-primary-hover: #0056b3;
--color-primary-light: #cce5ff;
Farben - Status
--color-success: #28a745;
--color-success-bg: #d4edda;
--color-success-text: #155724;
--color-warning: #ffc107;
--color-warning-bg: #fff3cd;
--color-warning-text: #856404;
--color-danger: #dc3545;
--color-danger-bg: #f8d7da;
--color-danger-text: #721c24;
--color-info: #17a2b8;
--color-info-bg: #cce5ff;
--color-info-text: #004085;
Farben - Neutral
--color-text: #333;
--color-text-muted: #666;
--color-text-light: #999;
--color-heading: #2c3e50;
--color-bg: #fff;
--color-bg-light: #f8f9fa;
--color-bg-muted: #f5f5f5;
--color-bg-dark: #2c3e50;
--color-border: #e0e0e0;
--color-border-light: #eee;
Spacing
--space-xs: 0.25rem; /* 4px */
--space-sm: 0.5rem; /* 8px */
--space-md: 1rem; /* 16px */
--space-lg: 1.5rem; /* 24px */
--space-xl: 2rem; /* 32px */
--space-xxl: 3rem; /* 48px */
Border Radius
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 8px;
--radius-pill: 50px;
Shadows
--shadow-sm: 0 1px 3px rgba(0,0,0,0.1);
--shadow-md: 0 2px 8px rgba(0,0,0,0.1);
--shadow-lg: 0 4px 12px rgba(0,0,0,0.1);
Typography
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-mono: 'SF Mono', Monaco, monospace;
--font-size-xs: 0.75rem; /* 12px */
--font-size-sm: 0.85rem; /* 13.6px */
--font-size-md: 1rem; /* 16px */
--font-size-lg: 1.1rem; /* 17.6px */
--font-size-xl: 1.5rem; /* 24px */
--font-size-xxl: 1.75rem; /* 28px */
--line-height: 1.6;
Transitions
--transition-fast: 0.15s ease;
--transition-normal: 0.2s ease;
--transition-slow: 0.3s ease;
Layout
--max-width: 1200px;
--max-width-narrow: 800px;
--max-width-wide: 1400px;
Responsive Breakpoints
| Breakpoint | Verwendung |
|---|---|
max-width: 1024px | Tablet - Config-Panel ausblenden |
max-width: 900px | Grid 2-spaltig → 1-spaltig |
max-width: 768px | Mobile - Sidebar als Overlay |
max-width: 600px | Small Mobile - Grid 1-spaltig |
Verwendung
In PHP-Templates
<link rel="stylesheet" href="/css/designtokens.css">
<link rel="stylesheet" href="/css/admin.css">
In CSS
.my-element {
padding: var(--space-md);
background: var(--color-bg-light);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
transition: all var(--transition-normal);
}
Verwandte Themen
- Components - UI-Komponenten Referenz
- HTMX - Interaktivität
UI Components
Wiederverwendbare UI-Komponenten aus admin.css.
Buttons
Basis
<button class="btn btn--primary">Primary</button>
<button class="btn btn--secondary">Secondary</button>
<button class="btn btn--success">Success</button>
<button class="btn btn--danger">Danger</button>
<button class="btn btn--info">Info</button>
<button class="btn btn--light">Light</button>
Größen
<button class="btn btn--primary btn--small">Klein</button>
<button class="btn btn--primary">Normal</button>
<button class="btn btn--primary btn--large">Groß</button>
Block (volle Breite)
<button class="btn btn--primary btn--block">Volle Breite</button>
Badges
Status-Badges
<span class="badge badge--pending">Pending</span>
<span class="badge badge--in_progress">In Progress</span>
<span class="badge badge--completed">Completed</span>
<span class="badge badge--failed">Failed</span>
<span class="badge badge--cancelled">Cancelled</span>
Andere Badges
<span class="badge badge--draft">Entwurf</span>
<span class="badge badge--published">Veröffentlicht</span>
<span class="badge badge--success">Erfolg</span>
<span class="badge badge--warning">Warnung</span>
<span class="badge badge--primary">Primary</span>
Alerts
<div class="alert alert--success">Erfolgreich gespeichert.</div>
<div class="alert alert--warning">Achtung: Ungespeicherte Änderungen.</div>
<div class="alert alert--error">Fehler beim Speichern.</div>
<div class="alert alert--info">Information verfügbar.</div>
Forms
Basis-Form
<form class="form">
<div class="form-group">
<label class="form-group__label">Name</label>
<input type="text" class="form-input">
<span class="form-group__hint">Hilfetext</span>
</div>
<div class="form-group">
<label class="form-group__label">Beschreibung</label>
<textarea class="form-textarea"></textarea>
</div>
<div class="form-group">
<label class="form-group__label">Status</label>
<select class="form-select">
<option>Aktiv</option>
<option>Inaktiv</option>
</select>
</div>
<div class="form-actions">
<button type="submit" class="btn btn--primary">Speichern</button>
<button type="button" class="btn btn--light">Abbrechen</button>
</div>
</form>
Form-Row (2 Spalten)
<div class="form-row">
<div class="form-group">
<label>Vorname</label>
<input class="form-input">
</div>
<div class="form-group">
<label>Nachname</label>
<input class="form-input">
</div>
</div>
Cards
<div class="card">
<div class="card__header">
<h2>Titel</h2>
<span class="badge badge--success">Aktiv</span>
</div>
<p>Card-Inhalt hier...</p>
</div>
Stats
<div class="stats-grid">
<div class="stat-card">
<span class="stat-card__value">42</span>
<span class="stat-card__label">Dokumente</span>
</div>
<div class="stat-card stat-card--success">
<span class="stat-card__value">98%</span>
<span class="stat-card__label">Erfolgsrate</span>
</div>
<div class="stat-card stat-card--warning">
<span class="stat-card__value">3</span>
<span class="stat-card__label">Pending</span>
</div>
</div>
Lists
<div class="list">
<div class="list-item">
<div class="list-item__main">
<h3 class="list-item__title">
<a href="#">Item Titel</a>
</h3>
<div class="list-item__meta">
<span>Erstellt: 2025-12-27</span>
<span>5 Einträge</span>
</div>
</div>
<span class="badge badge--completed">Fertig</span>
</div>
</div>
Empty State
<div class="empty-state">
<p>Keine Einträge vorhanden.</p>
<a href="/new" class="btn btn--primary">Erstellen</a>
</div>
<!-- Klein -->
<div class="empty-state empty-state--small">
<p>Keine Daten.</p>
</div>
Pagination
<div class="pagination">
<span class="pagination__info">1-10 von 42</span>
<div class="pagination__buttons">
<button class="pagination__btn" disabled>«</button>
<button class="pagination__btn pagination__btn--active">1</button>
<button class="pagination__btn">2</button>
<button class="pagination__btn">3</button>
<button class="pagination__btn">»</button>
</div>
</div>
Action Bar
<div class="action-bar">
<button class="btn btn--primary">Speichern</button>
<button class="btn btn--danger">Löschen</button>
<a href="/back" class="btn btn--light">Zurück</a>
</div>
HTMX-Integration
Loading Indicator
<button class="btn btn--primary">
<span class="htmx-content">Speichern</span>
<span class="htmx-indicator">Lade...</span>
</button>
HTMX Messages Container
<div class="htmx-messages" id="messages"></div>
Layout-Helper
Page Container
<div class="page-container">...</div>
<div class="page-container page-container--narrow">...</div>
<div class="page-container page-container--wide">...</div>
Grid Layouts
<div class="grid-2">
<div>Hauptinhalt</div>
<div>Sidebar (320px)</div>
</div>
<div class="grid-3">
<div>Spalte 1</div>
<div>Spalte 2</div>
<div>Spalte 3</div>
</div>
<div class="grid-auto">
<!-- Auto-fit mit min 140px -->
</div>
Verwandte Themen
- CSS Architektur - Design Tokens
- HTMX - Interaktivität
- HTMX Patterns - Code-Beispiele
HTMX
HTMX ist die einzige erlaubte Methode für AJAX-Interaktionen im Campus-System.
Warum HTMX?
| Aspekt | HTMX | fetch()/XHR |
|---|---|---|
| CSRF-Enforcement | Automatisch via Contract | Manuell, fehleranfällig |
| Konsistenz | Deklarativ im HTML | Imperativ in JS verstreut |
| Server-Autorität | Ja | Client-State-Chaos möglich |
| Progressive Enhancement | Ja | Nein |
| Debugging | HTML-Attribute sichtbar | DevTools nötig |
Contract #14: htmx-patterns
Alle HTMX-Regeln sind in Contract #14 definiert.
Kritische Regeln
| ID | Regel | Enforcement |
|---|---|---|
| HTMX-C1 | hx-post MUSS hx-headers mit X-CSRF-TOKEN haben | Contract-Validierung |
| HTMX-C2 | hx-delete MUSS hx-headers mit X-CSRF-TOKEN haben | Contract-Validierung |
| HTMX-C3 | hx-patch MUSS hx-headers mit X-CSRF-TOKEN haben | Contract-Validierung |
| HTMX-C4 | hx-delete MUSS hx-confirm Attribut haben | Contract-Validierung |
| HTMX-C5 | hx-put MUSS hx-headers mit X-CSRF-TOKEN haben | Contract-Validierung |
Hinweis: Die Regeln werden derzeit durch Contract-Validierung (manuell oder via contracts_validate) geprüft. Ein automatischer Pre-Hook ist geplant aber noch nicht aktiv.
Empfohlene Regeln (WARN)
| ID | Regel | Enforcement |
|---|---|---|
| HTMX-R1 | Kein fetch() in View-Dateien | Contract-Validierung |
| HTMX-R2 | hx-indicator für Loading-States | Dokumentation |
| HTMX-R3 | hx-swap explizit angeben | Dokumentation |
| HTMX-R4 | hx-disabled-elt für Button-States | Dokumentation |
Standard-Patterns
1. Button mit POST-Action
<button hx-post="/api/endpoint"
hx-headers='{"X-CSRF-TOKEN": "<?= $csrfToken ?>"}'
hx-target="#result"
hx-swap="innerHTML"
hx-indicator="this"
hx-disabled-elt="this">
<span class="htmx-content">Speichern</span>
<span class="htmx-indicator">Wird gespeichert...</span>
</button>
2. Delete mit Confirmation
<button hx-delete="/api/items/<?= $id ?>"
hx-headers='{"X-CSRF-TOKEN": "<?= $csrfToken ?>"}'
hx-confirm="Wirklich löschen?"
hx-target="closest tr"
hx-swap="outerHTML swap:0.3s">
Löschen
</button>
3. Form mit PUT
<form hx-put="/api/items/<?= $id ?>"
hx-headers='{"X-CSRF-TOKEN": "<?= $csrfToken ?>"}'
hx-target="#form-message"
hx-swap="innerHTML"
hx-indicator="#save-btn"
hx-disabled-elt="#save-btn">
<!-- Form fields -->
<button type="submit" id="save-btn">Speichern</button>
</form>
<div id="form-message"></div>
4. Inline-Edit
<input type="text"
name="value"
value="<?= htmlspecialchars($value) ?>"
hx-post="/api/items/<?= $id ?>/field"
hx-headers='{"X-CSRF-TOKEN": "<?= $csrfToken ?>"}'
hx-trigger="blur, keyup[key=='Enter']"
hx-swap="none"
hx-disabled-elt="this"
hx-on::after-request="this.classList.toggle('is-saved', event.detail.successful)">
5. Select mit Auto-Save
<select name="status"
hx-post="/api/items/<?= $id ?>/status"
hx-headers='{"X-CSRF-TOKEN": "<?= $csrfToken ?>"}'
hx-swap="none"
hx-disabled-elt="this">
<option value="draft">Entwurf</option>
<option value="active">Aktiv</option>
</select>
Controller-Integration
Basis-Controller Methoden
// In Framework\Controller
// CSRF validieren (wirft 403 bei Fehler)
$this->requireCsrf();
// Erfolg-Alert (via HX-Retarget zu #htmx-messages)
$this->htmxSuccess('Gespeichert!');
// Fehler-Alert
$this->htmxError('Fehler beim Speichern');
// Redirect (via HX-Redirect Header)
$this->htmxRedirect('/items/' . $id);
Beispiel-Controller
public function update(int $id): void
{
$this->requireCsrf();
try {
$data = $this->getJsonInput();
$this->useCase->update($id, $data);
$this->htmxSuccess('Gespeichert');
} catch (\Exception $e) {
$this->htmxError($e->getMessage());
}
}
Layout-Anforderungen
Globaler Message-Container
In layout.php muss dieser Container existieren:
<main>
<div id="htmx-messages" class="htmx-messages" aria-live="polite"></div>
<?= $content ?>
</main>
CSS für HTMX-States
/* Message-Container */
.htmx-messages {
position: fixed;
top: 80px;
right: var(--space-lg);
z-index: 1000;
max-width: 400px;
}
/* Loading-States */
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator { display: inline; }
.htmx-request .htmx-content { display: none; }
/* Disabled während Request */
.htmx-request[hx-disabled-elt] {
opacity: 0.6;
pointer-events: none;
}
/* Erfolgs-Feedback */
.is-saved {
border-color: var(--color-success);
animation: flash-success 0.5s;
}
Enforcement
Contract-Validierung
Die HTMX-Regeln werden via Contract #14 (htmx-patterns) validiert:
# Manuelle Validierung
contracts_validate(name="htmx-patterns")
# Oder via Script
/var/www/scripts/contract-check.sh
Geplant: Pre-Hook
Ein automatischer Pre-Hook (pre_rules_htmx.py) für Write/Edit-Operationen ist geplant, aber noch nicht in der Hook-Konfiguration aktiviert.
Ausnahmen
HTMX ist nicht geeignet für:
- Echtzeit-Updates (SSE/WebSocket nötig)
- Komplexe Client-Side-Validierung
- Offline-Funktionalität
Für diese Fälle: Explizite Ausnahme dokumentieren und minimal JS verwenden.
Verwandte Themen
- HTMX Patterns Referenz - Alle Patterns im Detail
- HTMX Troubleshooting - Häufige Probleme
- Regeln - HTMX statt fetch() Regel