Skip to content

Zen-Garden-Contract

Gleiches HTML, anderes Aussehen: Die Admin-GUI (Settings > Themes, customCss plus Token-Variablen) steuert das Frontend-Design, ohne Templates oder Svelte-Komponenten anzufassen. Vorbild: CSS Zen Garden und die im Blog The CSS Zen Garden dream, finally shipped beschriebene Praxis - Design-Token und natives CSS statt Preprozessor-Hacks.

  1. Theme liegt in der DB (themes: tokens.cssVars + customCss).
  2. frontend/src/lib/server/themeCss.ts baut daraus SSR-CSS (:root-Variablen plus sanitisiertes Custom-CSS) - buildThemeCss() / sanitizeCustomCss().
  3. frontend/src/routes/+layout.server.ts (fetchThemeState) loest das aktive Theme auf: theme_preset-Cookie (nur wenn aktiv) > Instanz-Default. Antwort als themeCss-String im Load-Data, TTL-cached (60 s).
  4. frontend/src/routes/+layout.svelte injiziert <style data-theme-ssr> im <svelte:head> - wirksam ab dem ersten HTML-Byte, kein FOUC.
  5. Der Client (themePreset.ts) uebernimmt nur noch Nutzerwechsel per documentElement und Cookie; SSR-Werte werden nicht ueberschrieben, solange kein Wechsel passiert.

Kombination: Demo-Alias, Domain-Config, Themes

Section titled “Kombination: Demo-Alias, Domain-Config, Themes”

Drei Admin-Bereiche wirken auf dasselbe SSR-Theme zusammen:

Bereich Admin-Pfad Wirkung
Themes Settings > Themes Liefert Token + Custom-CSS; Vorschau-Link ?preview-theme=<slug> (nie persistiert)
Demo-Alias Content > Demo-Aliasse ?demo=<slug>; optionaler Params-Key "theme" mit Theme-Slug
Domain-Config Content > Domain-Konfigurationen Feld Theme pinnt einen Theme-Slug je Domain; leer = Instanz-Default

Aufloesungsreihenfolge (resolveThemeSlug() in themeCss.ts):

?preview-theme= > Demo-Alias-theme > theme_preset-Cookie (Nutzerwahl) > Domain-themeSlug > Instanz-Default.

Begruendung: Explizite Vorschau gewinnt immer; geteilte Sales-Demos (?demo=) sehen bei allen Empfaengern identisch aus und schlagen deshalb ein ggf. veraltetes Cookie; die bewusste Nutzerwahl schlaegt den Domain-Default. Unbekannte oder inaktive Slugs fallen jeweils auf die naechste Stufe zurueck - nie auf einen Fehler und nie auf Flackern. Die Primarfarben-Overlays (primary aus Alias oder Domain) werden weiterhin als CSS-Variablen ueber das Theme gelegt.

Layout-Slots (Admin: Settings > Layout-Slots) liefern den Inhalt, Themes das Design - dieselbe Zen-Garden-Trennung auf Komponentenebene:

Slot Position Komponente Config (Inhalt) Design (Theme-CSS)
global.banner unter der Topbar, jede Seite core.banner text, href, variant [data-slot="global.banner"]
content.before über dem Seiteninhalt core.banner wie oben [data-slot="content.before"]
content.after unter dem Seiteninhalt core.banner wie oben [data-slot="content.after"]

core.banner rendert text (Pflicht, leer = kein Banner), optional als Link (href), in primary- oder muted-Variante. Inaktive Slots liefern die Public-API nicht aus und rendern nichts - der leere [data-slot]-Hook mit data-empty="true" bleibt als CSS-Anker bestehen. Slots laden clientseitig nach (loadLayoutSlots()), das Theme-CSS gilt SSR-first: Inhalt erscheint progressiv, Design sofort.

Admin-CSS darf sich nur auf diese Hooks verlassen. Tailwind-Klassen, Tag-Namen und Komponenten-Interna sind keine API und koennen sich aendern.

Selektor Ort
[data-region="topbar"] TopBar.svelte (header#topbar)
[data-region="main"] +layout.svelte (main#app-main)
[data-region="content"] +layout.svelte (Inhalts-Spalte; Flex-Container fuer Sektions-order)
[data-region="footer"] Footer.svelte (footer)
[data-slot="global.banner"], [data-slot="content.before"], [data-slot="content.after"] LayoutSlotHost.svelte (Wrapper mit display: contents, leer mit data-empty="true")
[data-section="hero"] [data-hero="cta-primary"], [data-hero="cta-secondary"], [data-hero="visual"], [data-hero="content"] +page.svelte (Hero-CTAs, Visual-Rahmen, Textcontainer)
[data-section="news"] [data-news="grid"] +page.svelte (News-Kartenraster)
[data-region="topbar"] [data-topbar="brand"], [data-topbar="search"], [data-topbar="actions"] TopBar.svelte (Topbar-Bereiche; Flex-order erlaubt Umstellen)
[data-region="catalog"] [data-catalog="header"], [data-catalog="filters"], [data-catalog="grid"] products/+page.svelte (Katalogtitel, Filterleiste, Produktraster)
[data-card="media"], [data-card="body"] ProductCard.svelte (Kartenbild, Kartentext; z.B. horizontale Listen-Zeilen)
[data-region="footer"] [data-footer="columns"], [data-footer="brand"], [data-footer="heading"] Footer.svelte (Spaltenraster, Markenspalte, Ueberschriften)
[data-testid^="product-card-"] ProductCard.svelte (Produktkarten, stabiler Test-Hook)
[data-section="hero"], [data-section="featured"], [data-section="top-selling"], [data-section="news"], [data-section="products"] +page.svelte (Startseiten-Sektionen)
:root CSS-Variablen (--primary, --accent, --radius, --font-sans, …) layout.css Token-Layer + DB-cssVars

Regeln:

  • Contract-Attribute nie entfernen oder umbenennen - nur ergaenzen.
  • Neues Admin-CSS nutzt Contract-Selektoren (Snippets im Theme-Dialog tun das bereits).
  • Checkout-, Preis- und Pflichtseiten (Impressum, Datenschutz, Widerruf) duerfen per CSS nicht versteckt oder unbedienbar gemacht werden (ADR 005 D7).

sanitizeCustomCss() entfernt: </style-Breakouts, externe @import-Regeln (http:/url(), javascript:-, expression(- und -moz-binding-Vektoren. Laengenlimit 50.000 Zeichen (Backend-Validierung identisch). CSP erlaubt style-src 'self' 'unsafe-inline' - Inline-<style> aus SSR ist vorgesehen.

Drei Referenz-Themes, gleiches HTML, kein Svelte-Eingriff. Seeds unter infrastructure/seeds/theme-*.yaml, der Slot-Inhalt unter infrastructure/seeds/layout-slot-showcase.yaml, Vorschau per ?preview-theme=<slug>:

Theme Slug Charakter
Aurora Showcase aurora-showcase Weiche Aurora-Gradients, Glas-Topbar, Ink-Footer
Beton Brutal beton-brutal Neo-Brutalismus, per CSS-order umsortierte Sektionen (Shop zuerst), fette Typo, Graustufen-Bilder, CSS-Counter
Feuilleton feuilleton Zeitungs-Layout: Serifen, Katalog als einspaltiges Verzeichnis mit Sticky-Filtern, horizontale Produktzeilen, News zuerst, Topbar umgestellt
  • Pro-Domain-Theme (DomainConfig.theme_slug) ist implementiert. Als naechste Pipeline-Stufe folgen weitere Layout-Tokens (Spacing, Container, Header-Hoehe, Dichte).
  • Sektions-Reihenfolge und Slot-Belegung sind bereits Admin-datengetrieben (homeSections, LayoutSlot); der Contract macht sie zusaetzlich CSS-adressierbar.