Theming-System
Inhalt
- Die 6 Ebenen
- Ebene 1: Primitive Tokens
- Ebene 2: Semantische Tokens
- Ebene 3: Admin Live-Editor
- Ebene 4: Preset-Themes
- Ebene 5: Custom CSS
- Ebene 6: Layout-Blöcke
- Live-Update-Mechanismus
- Token-Mapping-Beispiel
- Verfügbare Presets
- Logo-Filter-Presets
- Custom-CSS-Tipps
- Hintergrundfarbverlauf auf der Seite
- Schrift-Override
- Radius-Stufe anpassen
- Vollbreiter Hero-Hintergrund
ITSWEBER Play besitzt eine 6-Ebenen-Token-Architektur für Live-Theme-Bearbeitung ohne Seitenreloads. Jede visuelle Eigenschaft — Farben, Radien, Schatten, Schriften — durchläuft eine strukturierte Kette vom Rohwert bis zum gerenderten Pixel.
Die 6 Ebenen
Ebene 1: Primitive Tokens
Speicherort: packages/theme/tokens.json
Rohwerte ohne semantische Bedeutung: Farben, Schriftfamilien, Rahmenradien, Schattendefinitionen. Diese bilden das Fundament — sie werden niemals direkt in Komponenten verwendet. Komponenten referenzieren immer semantische Tokens (Ebene 2).
{
"color": {
"neutral": { "50": "#f6f8fa", "…": "…", "900": "#0e1116" },
"teal": { "400": "#3ba7a7", "500": "#3ba7a7", "600": "#2d8a8a", "700": "#236e6e" },
"red": { "500": "#d9534f", "600": "#b43935" },
"amber": { "500": "#f0b429", "600": "#cb8e14" },
"green": { "500": "#3fb950", "600": "#2ea043" }
},
"radius": {
"sm": "6px",
"md": "10px",
"lg": "16px",
"full": "9999px"
},
"shadow": {
"card": "0 4px 20px rgba(0,0,0,.35)",
"lg": "0 12px 40px rgba(0,0,0,.45)"
},
"font": {
"sans": "'Geist', 'Geist Variable', system-ui, -apple-system, 'Segoe UI', sans-serif",
"mono": "'Geist Mono', 'Geist Mono Variable', ui-monospace, 'SF Mono', monospace"
}
}
Ebene 2: Semantische Tokens
Speicherort: packages/theme/styles/semantic.css (für JavaScript gespiegelt in packages/theme/src/semantic.ts)
Semantische Tokens ordnen benannte Rollen — bg, fg, brand, danger, warning — den Primitives zu. Tailwind-Klassen und Komponentenstile referenzieren ausschließlich diese Tokens, niemals rohe Hex-Werte.
| Semantischer Token | Rolle | Zeigt auf (Dark-Standard) |
|---|---|---|
--color-background |
Seitenhintergrund | --color-neutral-900 |
--color-surface |
Karten-/Panel-Hintergrund | --color-neutral-800 |
--color-foreground |
Primärer Text | --color-neutral-100 |
--color-muted |
Sekundärer Text | --color-neutral-300 |
--color-brand |
Akzent / Interaktiv | --color-teal-500 |
--color-brand-hover |
Hover-Zustand | --color-teal-400 |
--color-danger |
Fehler, destruktive Aktionen | --color-red-500 |
--color-warning |
Warnungen | --color-amber-500 |
--shadow-card |
Karten-Elevation | fester Wert in semantic.css |
// packages/theme/src/semantic.ts (Auszug)
export const semantic = {
bg: { base: "var(--color-neutral-900)", surface: "var(--color-neutral-800)" },
fg: { primary: "var(--color-neutral-50)", muted: "var(--color-neutral-400)" },
brand: { default: "var(--color-teal-500)", hover: "var(--color-teal-600)" },
intent: { danger: "var(--color-red-500)", warning: "var(--color-amber-500)" },
} as const;
Ebene 3: Admin Live-Editor
Pfad: /admin/theme
Eine Colorpicker- und Schieberegler-Oberfläche ermöglicht Admins die Anpassung semantischer Tokens zur Laufzeit. Änderungen werden:
- Im Prisma-Modell
ThemeSettings, FeldtokensOverride, gespeichert - Über Redis Pub/Sub und Server-Sent Events (
/api/theme/events) an alle offenen Tabs übertragen
Kein Deployment oder Neustart erforderlich. Siehe Live-Update-Mechanismus weiter unten.
Ebene 4: Preset-Themes
Eingebaute benannte Presets aus packages/theme/presets/. Im Admin-Panel per Klick umschaltbar. Eigene Presets werden nicht in der Datenbank verwaltet, der aktuelle Stand lässt sich aber als JSON exportieren und wieder importieren.
| Preset | Beschreibung | Ideal für |
|---|---|---|
itsweber-dark |
Standard-Dark-Theme mit Teal-Akzent | Allgemeine Nutzung |
itsweber-light |
Helles Gegenstück zu itsweber-dark |
Helle Umgebungen |
itsweber-brand |
Dunkles Navy mit grünem Akzent nach itsweber.de | Offizielle Instanz |
high-contrast |
Maximaler Kontrast bei Sehschwäche | Barrierefreiheit |
retro |
80er-Synthwave mit Magenta und Cyan auf Violett | Persönliche/Nischen-Instanzen |
Ebene 5: Custom CSS
Freies CSS-Eingabefeld in der Admin-Sandbox. Wird nach allen Token-Ebenen angewendet und bietet volle Override-Möglichkeit. Merkmale:
- Versioniert: Letzte 20 Revisionen werden gespeichert, jede einzeln wiederherstellbar
- Geprüft: Vor dem Speichern wird auf verbotene Konstrukte geprüft (
@import,url(javascript:…),expression(),</style>,<script,behavior:) und die Größe auf 100 KB begrenzt. Eine Syntaxprüfung findet nicht statt - Scoped: Innerhalb eines einzelnen
<style>-Tags angewendet, keine Framework-Klassen erforderlich
Siehe Custom-CSS-Tipps für häufige Muster.
Ebene 6: Layout-Blöcke
Die Startseite setzt sich aus per Drag-and-drop sortierbaren Blöcken zusammen, gepflegt unter /admin/page-blocks. Verfügbare Block-Typen:
| Block | Beschreibung |
|---|---|
| Hero | Vollbreites Banner mit Titel, Untertitel und CTA-Button |
| Category-Chips | Scrollbare Kategoriefilter-Zeile |
| Video-Grid | Konfigurierbares Raster aus aktuellen/hervorgehobenen Videos |
| Shorts-Row | Zeile mit Kurzvideos |
| Channel-Row | Zeile mit Kanalkarten |
| CTA-Banner | Call-to-Action-Streifen mit eigenem Text und Link |
| Community-Row | Zeile mit Community-Beiträgen |
Drag-and-drop-Neuordnung wird unterstützt. Änderungen treten sofort ohne Seitenreload in Kraft.
Live-Update-Mechanismus
Wenn ein Admin eine Theme-Änderung speichert, wird die Aktualisierung sofort an alle offenen Browser-Sessions weitergegeben:
Admin-Änderung → tRPC theme.update → DB-Speicherung + Redis-Publish → SSE /api/theme/events
Frontend: ersetzt <style id="theme-vars"> mit neuen CSS-Variablen
Ergebnis: Sofortiges Update, kein React-Rerender nötig
Das Frontend hält eine EventSource-Verbindung zu /api/theme/events. Beim Empfang eines theme:updated-Events lädt es das aktuelle Theme per tRPC nach und tauscht den Inhalt von <style id="theme-vars"> und <style id="theme-custom"> aus. Da CSS-Variablen kaskadieren, aktualisiert sich jedes Element, das diese Variablen verwendet, an Ort und Stelle — ohne Komponenten-State-Änderung, ohne Navigation.
Token-Mapping-Beispiel
// tokens.json (Primitives)
{
"color": {
"teal": { "500": "#3ba7a7" }
}
}
/* styles/semantic.css (Semantisches Mapping) */
--color-brand: var(--color-teal-500);
/* aus tokens.json entsteht: --color-teal-500: #3ba7a7 */
/* Komponentenverwendung */
.btn-primary {
background-color: var(--color-brand);
}
.btn-primary:hover {
background-color: var(--color-brand-hover);
}
Overrides adressieren die Primitives in Punktnotation. Ein Admin, der im Live-Editor color.teal.500 auf #3b82f6 (Blau) setzt, schreibt { "color.teal.500": "#3b82f6" } in ThemeSettings.tokensOverride. Daraus wird --color-teal-500: #3b82f6 in <style id="theme-vars">, und --color-brand übernimmt den Wert über die Kaskade.
Verfügbare Presets
| Name | Beschreibung | Ideal für |
|---|---|---|
itsweber-dark |
Standard-Dark mit Teal-Akzent | Allgemeine Nutzung |
itsweber-light |
Helles Gegenstück zu itsweber-dark |
Helle Umgebungen, Büros |
itsweber-brand |
Dunkles Navy mit grünem Akzent nach itsweber.de | Offizielle Instanz |
high-contrast |
Maximaler Kontrast bei Sehschwäche | Barrierefreiheit |
retro |
80er-Synthwave mit Magenta und Cyan auf Violett | Persönliche oder Nischen-Instanzen |
Logo-Filter-Presets
Das Instanz-Logo unterstützt 10 CSS-Filter-Presets, angewendet über var(--logo-filter). Dies ermöglicht eine Anpassung des Logos an helle/dunkle Hintergründe ohne separate Bild-Assets.
| Preset | CSS-Filter-Wert | Verwendungszweck |
|---|---|---|
| none | none |
Keine Änderung |
| glow | drop-shadow(0 0 12px var(--color-teal-500)) |
Leuchten in Akzentfarbe |
| softGlow | drop-shadow(0 0 6px rgba(59,167,167,.6)) |
Dezentes Leuchten |
| duotone | grayscale(1) brightness(1.1) sepia(.3) hue-rotate(140deg) saturate(3) |
Einfarbig getönt |
| shadow | drop-shadow(0 4px 8px rgba(0,0,0,.5)) |
Schatten für mehr Tiefe |
| brightness0 | brightness(0) |
Komplett schwarz |
| invert | invert(1) |
Farben umkehren |
| invertGlow | invert(1) drop-shadow(0 0 12px var(--color-teal-500)) |
Umgekehrt mit Leuchten |
| mono | grayscale(1) contrast(1.2) brightness(1.05) |
Graustufen |
| onlyWhite | brightness(0) invert(1) |
Komplett weiß |
Custom-CSS-Tipps
Hintergrundfarbverlauf auf der Seite
body {
background: linear-gradient(135deg, var(--color-background) 0%, #1a0a00 100%);
}
Schrift-Override
:root {
--font-sans: 'Nunito', system-ui, sans-serif;
}
Radius-Stufe anpassen
:root {
--radius-md: 1.5rem;
}
Vollbreiter Hero-Hintergrund
/* Selektor an das echte Markup anpassen, eine Klasse .hero-block setzt der Code nicht */
.hero-block {
background-image: url('/s3/play-assets/hero-bg.jpg');
background-size: cover;
background-position: center;
}
Sicherheit: Custom CSS mit @import (egal wohin), url(javascript:…), url(data:text/html…), expression(), behavior:, <script oder </style> wird beim Speichern mit Fehlermeldung abgelehnt, nicht stillschweigend bereinigt. Eine Syntaxprüfung gibt es nicht, fehlerhaftes CSS wird gespeichert und wirkt dann einfach nicht.