diff --git a/.cursor/rules/context-persist-erkenntnisse.mdc b/.cursor/rules/context-persist-erkenntnisse.mdc
new file mode 100644
index 0000000..7b921de
--- /dev/null
+++ b/.cursor/rules/context-persist-erkenntnisse.mdc
@@ -0,0 +1,23 @@
+---
+description: Erkenntnisse und Konventionen in Cursor-Regeln festhalten, damit der Kontext automatisch erweitert wird
+globs: ".cursor/rules/**/*.mdc"
+alwaysApply: true
+---
+
+# Kontext bei Erkenntnissen anpassen und erweitern
+
+Wenn im Gespräch **wichtige Erkenntnisse** zu diesem Projekt entstehen (Architektur, Konventionen, Patterns, „so machen wir es hier“), diese **sofort in den Cursor-Kontext übernehmen**:
+
+1. **Neue Regel anlegen oder bestehende erweitern** unter `.cursor/rules/`:
+ - Neue Themen → neue `.mdc`-Datei (kurzer, prägnanter Titel, z. B. `translations-i18n.mdc`).
+ - Gehört zu einem bestehenden Thema → die passende Regel-Datei bearbeiten und den Inhalt ergänzen/aktualisieren.
+
+2. **Format:** `.mdc` mit YAML-Frontmatter (`description`, `globs` oder `alwaysApply: true`). Inhalt knapp, unter ~50 Zeilen pro Regel, mit konkreten Beispielen wo sinnvoll.
+
+3. **Wann eine Regel anpassen/erweitern:**
+ - Einverständnis oder expliziter Wunsch des Users (z. B. „füge das in den Cursor-Context hinzu“).
+ - Oder: Wir haben gemeinsam eine wiederkehrende Konvention oder Architektur-Entscheidung geklärt, die für künftige Arbeit relevant ist – dann **proaktiv** eine Regel anlegen oder aktualisieren, ohne extra nachzufragen.
+
+4. **Nicht** in Regeln packen: Einmalige Fixes, reine Code-Änderungen ohne Konventions-Charakter, Dinge die nur für einen einzelnen Task gelten.
+
+**Ziel:** Der Projekt-Kontext wächst mit jeder wichtigen Erkenntnis; spätere Sessions und andere Agent-Durchläufe haben die gleichen Konventionen automatisch zur Verfügung.
diff --git a/.cursor/rules/translations-i18n.mdc b/.cursor/rules/translations-i18n.mdc
new file mode 100644
index 0000000..09db069
--- /dev/null
+++ b/.cursor/rules/translations-i18n.mdc
@@ -0,0 +1,41 @@
+---
+description: Übersetzungssystem (i18n) – Keys, CMS, Nutzung in Astro und Svelte
+globs: "src/lib/translations.ts,**/Layout.astro,src/pages/**/*.astro,src/components/**/*.svelte,src/middleware.ts"
+alwaysApply: false
+---
+
+# Übersetzungssystem (i18n)
+
+Alle sichtbaren UI-Texte kommen aus dem CMS (RustyCMS) über das Translation-Bundle.
+
+## Quelle
+
+- **CMS:** Translation-Bundle über API (Single Source of Truth). Slug `app`, geladen in der Middleware mit `_resolve=all`.
+- **Slug:** Konstante `TRANSLATION_BUNDLE_SLUG = "app"` in `src/lib/constants.ts`.
+- **Middleware:** `src/middleware.ts` lädt das Bundle einmal pro Request und setzt `Astro.locals.translations`.
+
+## Keys definieren
+
+1. **Key in `src/lib/translations.ts`** in das Array `TRANSLATION_KEYS` eintragen (damit `T.key_name` und Typ `TranslationKey` existieren).
+2. **String in RustyCMS** in `content/de/translation_bundle/app.json5` unter `strings` eintragen. Platzhalter: `{{name}}` wird durch `t(..., { name: value })` ersetzt.
+
+## Nutzung
+
+### In Astro (.astro)
+
+- `translations = Astro.locals.translations ?? {}`
+- `t(translations, T.xy)` oder mit Ersetzungen: `t(translations, T.xy, { count: 5 })`
+- Breadcrumbs: optional `translationKey: T.nav_start` statt `label: "Start"`; Layout löst das mit `t(translations, item.translationKey)` auf.
+
+### In Svelte
+
+- **Innerhalb von TranslationProvider** (z. B. Header, Footer): `const t = useTranslate();` dann `t(T.xy)`.
+- **client:load-Inseln (Astro Islands):** `useTranslate()` hat keinen Zugriff auf den Svelte-Context, und beim SSR gibt es kein `window` → Übersetzungen fehlen, es erscheint der Key (z. B. `blog_tag_all`).
+ **Lösung:** Übersetzungen immer als Prop von der Astro-Seite übergeben und die statische `t()` nutzen:
+ - Komponente: Prop `translations?: Translations`; keine `useTranslate()`, sondern lokale Hilfsfunktion `t(key, replacements) => t(translations ?? null, key, replacements)` mit `t` aus `../lib/translations`.
+ - Astro-Seite: `` mit `translations = Astro.locals.translations ?? {}`.
+ - Siehe `BlogOverview.svelte` als Referenz.
+
+### Neue UI-Texte
+
+Keine hartkodierten deutschen/englischen Sätze in Komponenten oder Seiten. Stattdessen: Key in `TRANSLATION_KEYS` + Eintrag in `app.json5`, dann überall `t(translations, T.key)` bzw. `t(T.key)` nutzen.
diff --git a/.gitignore b/.gitignore
index 4dcb20c..ea52020 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
# build output
dist/
+# CMS response cache (Dev-Fallback bei nicht erreichbarem CMS)
+.cache/
# generated types
.astro/
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..90ce9ff
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "[svelte]": {
+ "editor.defaultFormatter": "svelte.svelte-vscode",
+ "editor.formatOnSave": true
+ }
+}
diff --git a/package.json b/package.json
index aa772e4..d3ba7ac 100644
--- a/package.json
+++ b/package.json
@@ -6,12 +6,14 @@
"dev": "yarn generate:api-types; astro dev",
"build": "yarn generate:api-types && astro build && pagefind --site dist && cp -r dist/pagefind public/",
"preview": "astro preview",
+ "cache:warm": "node scripts/warm-cache.mjs",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"astro": "astro",
"generate:api-types": "node scripts/generate-api-types.mjs",
"postinstall": "yarn generate:api-types || true",
"firebase-deploy": "yarn build && firebase deploy --only hosting",
+ "deploy": "yarn firebase-deploy",
"firebase-deploy:nocache": "yarn build && firebase deploy --only hosting"
},
"dependencies": {
@@ -23,6 +25,7 @@
"@iconify/svelte": "^5.2.1",
"@tailwindcss/vite": "^4.1.18",
"astro": "^5.17.1",
+ "json5": "^2.2.3",
"marked": "^17.0.2",
"svelte": "^5.51.2",
"tailwindcss": "^4.1.18"
diff --git a/public/apple-touch-icon.svg b/public/apple-touch-icon.svg
new file mode 100644
index 0000000..269d54b
--- /dev/null
+++ b/public/apple-touch-icon.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/favicon.svg b/public/favicon.svg
index f157bd1..75c6741 100644
--- a/public/favicon.svg
+++ b/public/favicon.svg
@@ -1,9 +1,10 @@
-