Frontend

Erstellt: 2025-12-27 | Aktualisiert: 2025-12-27

Dokumentation zur Frontend-Architektur des Campus-Systems.

Prinzipien

  1. Server-Side Rendering - PHP rendert HTML
  2. Progressive Enhancement - HTMX für Interaktivität
  3. Kein SPA-Framework - Kein React, Vue, Angular
  4. Minimales JavaScript - Nur wo nötig

Struktur

/src/View/           # PHP-Templates
/public/css/         # Stylesheets
/public/js/          # JavaScript (minimal)

Unterthemen

Verwandte Themen

CSS Architektur

Erstellt: 2025-12-27 | Aktualisiert: 2025-12-31

Übersicht

Das CSS-System basiert auf CSS Custom Properties (Design Tokens) und einer modularen Dateistruktur.

Dateien

DateiZweck
designtokens.cssCSS-Variablen (Farben, Spacing, etc.)
style.cssBasis-Styles und Docs-Komponenten
admin.cssAdmin-Komponenten (Buttons, Forms, Cards)
nav.cssNavigation
chat-redesign.cssChat-UI spezifisch
graph.cssGraph-Visualisierungen
home.cssHomepage-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

BreakpointVerwendung
max-width: 1024pxTablet - Config-Panel ausblenden
max-width: 900pxGrid 2-spaltig → 1-spaltig
max-width: 768pxMobile - Sidebar als Overlay
max-width: 600pxSmall 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

UI Components

Erstellt: 2025-12-27 | Aktualisiert: 2025-12-27

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>&laquo;</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">&raquo;</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

HTMX

Erstellt: 2025-12-27 | Aktualisiert: 2025-12-31

HTMX ist die einzige erlaubte Methode für AJAX-Interaktionen im Campus-System.

Warum HTMX?

AspektHTMXfetch()/XHR
CSRF-EnforcementAutomatisch via ContractManuell, fehleranfällig
KonsistenzDeklarativ im HTMLImperativ in JS verstreut
Server-AutoritätJaClient-State-Chaos möglich
Progressive EnhancementJaNein
DebuggingHTML-Attribute sichtbarDevTools nötig

Contract #14: htmx-patterns

Alle HTMX-Regeln sind in Contract #14 definiert.

Kritische Regeln

IDRegelEnforcement
HTMX-C1hx-post MUSS hx-headers mit X-CSRF-TOKEN habenContract-Validierung
HTMX-C2hx-delete MUSS hx-headers mit X-CSRF-TOKEN habenContract-Validierung
HTMX-C3hx-patch MUSS hx-headers mit X-CSRF-TOKEN habenContract-Validierung
HTMX-C4hx-delete MUSS hx-confirm Attribut habenContract-Validierung
HTMX-C5hx-put MUSS hx-headers mit X-CSRF-TOKEN habenContract-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)

IDRegelEnforcement
HTMX-R1Kein fetch() in View-DateienContract-Validierung
HTMX-R2hx-indicator für Loading-StatesDokumentation
HTMX-R3hx-swap explizit angebenDokumentation
HTMX-R4hx-disabled-elt für Button-StatesDokumentation

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:

Für diese Fälle: Explizite Ausnahme dokumentieren und minimal JS verwenden.

Verwandte Themen