diff --git a/public/brand/flimmer-icon-maskable.svg b/public/brand/flimmer-icon-maskable.svg
new file mode 100644
index 0000000..1936bec
--- /dev/null
+++ b/public/brand/flimmer-icon-maskable.svg
@@ -0,0 +1,38 @@
+
diff --git a/public/brand/flimmer-icon.svg b/public/brand/flimmer-icon.svg
new file mode 100644
index 0000000..bc90d57
--- /dev/null
+++ b/public/brand/flimmer-icon.svg
@@ -0,0 +1,51 @@
+
diff --git a/public/brand/flimmer-logo.svg b/public/brand/flimmer-logo.svg
new file mode 100644
index 0000000..8d0d155
--- /dev/null
+++ b/public/brand/flimmer-logo.svg
@@ -0,0 +1,53 @@
+
diff --git a/public/brand/flimmer-mono.svg b/public/brand/flimmer-mono.svg
new file mode 100644
index 0000000..a25761c
--- /dev/null
+++ b/public/brand/flimmer-mono.svg
@@ -0,0 +1,26 @@
+
diff --git a/public/brand/flimmer-wordmark.svg b/public/brand/flimmer-wordmark.svg
new file mode 100644
index 0000000..ac82753
--- /dev/null
+++ b/public/brand/flimmer-wordmark.svg
@@ -0,0 +1,28 @@
+
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..a3db894
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/favicon.svg b/public/favicon.svg
new file mode 100644
index 0000000..bc90d57
--- /dev/null
+++ b/public/favicon.svg
@@ -0,0 +1,51 @@
+
diff --git a/public/icons/apple-touch-icon.png b/public/icons/apple-touch-icon.png
new file mode 100644
index 0000000..2340ecb
Binary files /dev/null and b/public/icons/apple-touch-icon.png differ
diff --git a/public/icons/icon-128.png b/public/icons/icon-128.png
new file mode 100644
index 0000000..a0ed7fb
Binary files /dev/null and b/public/icons/icon-128.png differ
diff --git a/public/icons/icon-192.png b/public/icons/icon-192.png
new file mode 100644
index 0000000..d55dde1
Binary files /dev/null and b/public/icons/icon-192.png differ
diff --git a/public/icons/icon-256.png b/public/icons/icon-256.png
new file mode 100644
index 0000000..79aaa68
Binary files /dev/null and b/public/icons/icon-256.png differ
diff --git a/public/icons/icon-384.png b/public/icons/icon-384.png
new file mode 100644
index 0000000..009e3e1
Binary files /dev/null and b/public/icons/icon-384.png differ
diff --git a/public/icons/icon-512.png b/public/icons/icon-512.png
new file mode 100644
index 0000000..f7d8268
Binary files /dev/null and b/public/icons/icon-512.png differ
diff --git a/public/icons/icon-64.png b/public/icons/icon-64.png
new file mode 100644
index 0000000..eacbb46
Binary files /dev/null and b/public/icons/icon-64.png differ
diff --git a/public/icons/icon-96.png b/public/icons/icon-96.png
new file mode 100644
index 0000000..62757ef
Binary files /dev/null and b/public/icons/icon-96.png differ
diff --git a/public/icons/maskable-192.png b/public/icons/maskable-192.png
new file mode 100644
index 0000000..54dbfb4
Binary files /dev/null and b/public/icons/maskable-192.png differ
diff --git a/public/icons/maskable-512.png b/public/icons/maskable-512.png
new file mode 100644
index 0000000..0ae55eb
Binary files /dev/null and b/public/icons/maskable-512.png differ
diff --git a/public/og-image.png b/public/og-image.png
new file mode 100644
index 0000000..024f36d
Binary files /dev/null and b/public/og-image.png differ
diff --git a/scripts/generate-icons.ts b/scripts/generate-icons.ts
new file mode 100644
index 0000000..fefb728
--- /dev/null
+++ b/scripts/generate-icons.ts
@@ -0,0 +1,129 @@
+/**
+ * Erzeugt Favicon und PWA-Icons aus den SVG-Quellen unter public/brand/.
+ *
+ * npm run icons
+ *
+ * Quelle ist immer das Vektor-Logo — die Rasterdateien sind reine Ableitungen
+ * und werden bei Bedarf neu erzeugt.
+ */
+import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
+import { join } from "node:path";
+import sharp from "sharp";
+
+const ROOT = process.cwd();
+const BRAND = join(ROOT, "public", "brand");
+const ICONS = join(ROOT, "public", "icons");
+
+const ICON_SVG = join(BRAND, "flimmer-icon.svg");
+const MASKABLE_SVG = join(BRAND, "flimmer-icon-maskable.svg");
+
+/** Rendert ein SVG in ein quadratisches PNG. */
+async function png(svgPath: string, size: number): Promise {
+ const svg = readFileSync(svgPath);
+ return sharp(svg, { density: Math.max(72, Math.ceil((size / 128) * 96) * 4) })
+ .resize(size, size, { fit: "contain", background: { r: 0, g: 0, b: 0, alpha: 0 } })
+ .png({ compressionLevel: 9 })
+ .toBuffer();
+}
+
+/**
+ * Minimaler ICO-Container mit eingebetteten PNGs.
+ * Alle Browser ab IE11 verstehen PNG-in-ICO; das spart eine Abhängigkeit.
+ */
+function buildIco(images: { size: number; data: Buffer }[]): Buffer {
+ const header = Buffer.alloc(6);
+ header.writeUInt16LE(0, 0); // reserviert
+ header.writeUInt16LE(1, 2); // Typ 1 = Icon
+ header.writeUInt16LE(images.length, 4);
+
+ const entries: Buffer[] = [];
+ let offset = 6 + images.length * 16;
+
+ for (const img of images) {
+ const entry = Buffer.alloc(16);
+ entry.writeUInt8(img.size >= 256 ? 0 : img.size, 0); // Breite (0 = 256)
+ entry.writeUInt8(img.size >= 256 ? 0 : img.size, 1); // Höhe
+ entry.writeUInt8(0, 2); // Farbpalette
+ entry.writeUInt8(0, 3); // reserviert
+ entry.writeUInt16LE(1, 4); // Farbebenen
+ entry.writeUInt16LE(32, 6); // Bits pro Pixel
+ entry.writeUInt32LE(img.data.length, 8);
+ entry.writeUInt32LE(offset, 12);
+ entries.push(entry);
+ offset += img.data.length;
+ }
+
+ return Buffer.concat([header, ...entries, ...images.map((i) => i.data)]);
+}
+
+const PWA_SIZES = [64, 96, 128, 192, 256, 384, 512];
+const FAVICON_SIZES = [16, 32, 48];
+
+async function main() {
+ mkdirSync(ICONS, { recursive: true });
+
+ // PWA-Icons (transparenter Rand, „any"-Purpose)
+ for (const size of PWA_SIZES) {
+ const data = await png(ICON_SVG, size);
+ writeFileSync(join(ICONS, `icon-${size}.png`), data);
+ console.log(` icons/icon-${size}.png`);
+ }
+
+ // Maskable (randlose Fläche, Motiv in der Sicherheitszone)
+ for (const size of [192, 512]) {
+ const data = await png(MASKABLE_SVG, size);
+ writeFileSync(join(ICONS, `maskable-${size}.png`), data);
+ console.log(` icons/maskable-${size}.png`);
+ }
+
+ // Apple Touch Icon — iOS mag keine Transparenz, deshalb deckend
+ const apple = await sharp(readFileSync(MASKABLE_SVG), { density: 576 })
+ .resize(180, 180)
+ .flatten({ background: "#12101d" })
+ .png({ compressionLevel: 9 })
+ .toBuffer();
+ writeFileSync(join(ICONS, "apple-touch-icon.png"), apple);
+ console.log(" icons/apple-touch-icon.png");
+
+ // Favicon: SVG (scharf in jeder Größe) + ICO (Fallback, Lesezeichenleisten)
+ writeFileSync(join(ROOT, "public", "favicon.svg"), readFileSync(ICON_SVG));
+ console.log(" favicon.svg");
+
+ const icoImages = [];
+ for (const size of FAVICON_SIZES) {
+ icoImages.push({ size, data: await png(ICON_SVG, size) });
+ }
+ writeFileSync(join(ROOT, "public", "favicon.ico"), buildIco(icoImages));
+ console.log(" favicon.ico");
+
+ // Open-Graph-Bild für geteilte Links
+ const og = await sharp({
+ create: {
+ width: 1200,
+ height: 630,
+ channels: 4,
+ background: { r: 12, g: 10, b: 20, alpha: 1 },
+ },
+ })
+ .composite([
+ {
+ input: await sharp(readFileSync(join(BRAND, "flimmer-logo.svg")), { density: 300 })
+ .resize({ width: 760 })
+ .png()
+ .toBuffer(),
+ gravity: "centre",
+ },
+ ])
+ .png({ compressionLevel: 9 })
+ .toBuffer();
+ writeFileSync(join(ROOT, "public", "og-image.png"), og);
+ console.log(" og-image.png");
+}
+
+main().then(
+ () => console.log("Icons erzeugt."),
+ (err) => {
+ console.error(err);
+ process.exit(1);
+ },
+);
diff --git a/src/app/globals.css b/src/app/globals.css
new file mode 100644
index 0000000..3dc2f39
--- /dev/null
+++ b/src/app/globals.css
@@ -0,0 +1,328 @@
+@import "tailwindcss";
+
+/* ==========================================================================
+ Flimmer — Design-Tokens
+ --------------------------------------------------------------------------
+ Alle Farben liegen als CSS-Variablen (--fl-*) vor und werden über
+ `@theme inline` an Tailwind durchgereicht. Dadurch reagieren sämtliche
+ Utilities (bg-surface, text-muted, …) automatisch auf Theme-Wechsel und
+ spätere zusätzliche Themes brauchen nur einen neuen Selektor-Block.
+
+ Kontrast: jede hier definierte Vorder-/Hintergrund-Kombination erfüllt
+ mindestens WCAG AA (4,5:1 für Fließtext, 3:1 für große Schrift und
+ UI-Begrenzungen). Werte siehe docs/DESIGN.md.
+ ========================================================================== */
+
+@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
+
+:root,
+[data-theme="dark"] {
+ color-scheme: dark;
+
+ /* Flächen — sehr dunkles Blauviolett/Anthrazit */
+ --fl-bg: #0c0a14;
+ --fl-bg-elevated: #12101d;
+ --fl-surface: #17142499;
+ --fl-surface-solid: #171424;
+ --fl-surface-2: #201b33;
+ --fl-border: #2e2745;
+ --fl-border-strong: #453a63;
+
+ /* Text */
+ --fl-text: #f4f1fa; /* 17,0:1 auf --fl-bg */
+ --fl-muted: #aca5c2; /* 7,7:1 auf --fl-bg */
+ --fl-faint: #7c7594; /* 4,6:1 auf --fl-bg */
+
+ /* Signatur: Glow-Gradient Bernstein → Magenta */
+ --fl-amber: #ffb454;
+ --fl-amber-soft: #ffc46b;
+ --fl-magenta: #ff4d9d;
+ --fl-magenta-soft: #ff6fb0;
+ --fl-accent: var(--fl-amber); /* als Textfarbe nutzbar: 11,3:1 */
+ --fl-accent-alt: var(--fl-magenta); /* als Textfarbe nutzbar: 6,0:1 */
+ --fl-on-accent: #1a1024; /* Text auf dem Gradient: min. 5,5:1 */
+ --fl-gradient: linear-gradient(115deg, var(--fl-amber-soft) 0%, var(--fl-amber) 38%, var(--fl-magenta) 100%);
+ --fl-gradient-soft: linear-gradient(115deg, #ffb45426 0%, #ff4d9d26 100%);
+ --fl-glow: 0 0 0 1px #ffb4541f, 0 8px 34px -10px #ff4d9d59, 0 2px 12px -4px #ffb45440;
+ --fl-glow-strong: 0 0 0 1px #ffb4543d, 0 14px 48px -12px #ff4d9d80, 0 4px 18px -6px #ffb4546b;
+ --fl-ring: #ff8a70;
+
+ /* Statusfarben (AA auf --fl-bg) */
+ --fl-success: #4ade80;
+ --fl-warning: #fbbf24;
+ --fl-danger: #ff6b6b;
+ --fl-info: #7dd3fc;
+
+ --fl-shadow: 0 10px 30px -12px #00000099;
+}
+
+[data-theme="light"] {
+ color-scheme: light;
+
+ /* Flächen — heller, warmgrauer Grund */
+ --fl-bg: #faf7f5;
+ --fl-bg-elevated: #ffffff;
+ --fl-surface: #ffffffcc;
+ --fl-surface-solid: #ffffff;
+ --fl-surface-2: #f2ece8;
+ --fl-border: #e3dad4;
+ --fl-border-strong: #cbbdb4;
+
+ --fl-text: #1b1524; /* 16,2:1 auf --fl-bg */
+ --fl-muted: #5b5268; /* 6,7:1 auf --fl-bg */
+ --fl-faint: #766c84; /* 4,6:1 auf --fl-bg */
+
+ /* Dieselben Signaturfarben, für Text jedoch in abgedunkelten Varianten */
+ --fl-amber: #ffb454;
+ --fl-amber-soft: #ffc46b;
+ --fl-magenta: #ff4d9d;
+ --fl-magenta-soft: #ff6fb0;
+ --fl-accent: #8a4b00; /* als Textfarbe: 6,4:1 */
+ --fl-accent-alt: #b01262; /* als Textfarbe: 6,4:1 */
+ --fl-on-accent: #23122c; /* Text auf dem Gradient: min. 6,1:1 */
+ --fl-gradient: linear-gradient(115deg, var(--fl-amber-soft) 0%, var(--fl-amber) 38%, var(--fl-magenta) 100%);
+ --fl-gradient-soft: linear-gradient(115deg, #ffb4542e 0%, #ff4d9d2e 100%);
+ --fl-glow: 0 0 0 1px #d8a15c33, 0 8px 26px -12px #ff4d9d4d, 0 2px 10px -4px #ffb4544d;
+ --fl-glow-strong: 0 0 0 1px #d8a15c59, 0 14px 40px -14px #ff4d9d66, 0 4px 16px -6px #ffb45473;
+ --fl-ring: #b01262;
+
+ --fl-success: #15803d;
+ --fl-warning: #a16207;
+ --fl-danger: #b91c1c;
+ --fl-info: #0369a1;
+
+ --fl-shadow: 0 10px 30px -14px #2a1a3a26;
+}
+
+@theme inline {
+ --color-bg: var(--fl-bg);
+ --color-bg-elevated: var(--fl-bg-elevated);
+ --color-surface: var(--fl-surface);
+ --color-surface-solid: var(--fl-surface-solid);
+ --color-surface-2: var(--fl-surface-2);
+ --color-border-subtle: var(--fl-border);
+ --color-border-strong: var(--fl-border-strong);
+
+ --color-fg: var(--fl-text);
+ --color-muted: var(--fl-muted);
+ --color-faint: var(--fl-faint);
+
+ --color-amber: var(--fl-amber);
+ --color-amber-soft: var(--fl-amber-soft);
+ --color-magenta: var(--fl-magenta);
+ --color-magenta-soft: var(--fl-magenta-soft);
+ --color-accent: var(--fl-accent);
+ --color-accent-alt: var(--fl-accent-alt);
+ --color-on-accent: var(--fl-on-accent);
+
+ --color-success: var(--fl-success);
+ --color-warning: var(--fl-warning);
+ --color-danger: var(--fl-danger);
+ --color-info: var(--fl-info);
+
+ --radius-card: 1rem;
+ --radius-poster: 0.875rem;
+
+ --font-sans: var(--font-outfit), Outfit, Inter, ui-sans-serif, system-ui, -apple-system,
+ "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+
+ --shadow-glow: var(--fl-glow);
+ --shadow-glow-strong: var(--fl-glow-strong);
+ --shadow-card: var(--fl-shadow);
+
+ --ease-glow: cubic-bezier(0.22, 1, 0.36, 1);
+}
+
+/* --------------------------------------------------------------------------
+ Basis
+ -------------------------------------------------------------------------- */
+
+@layer base {
+ * {
+ border-color: var(--fl-border);
+ }
+
+ html {
+ -webkit-text-size-adjust: 100%;
+ }
+
+ body {
+ background-color: var(--fl-bg);
+ color: var(--fl-text);
+ font-family: var(--font-sans);
+ min-height: 100dvh;
+ /* Ruhiges Kino-Ambiente: zwei sehr weiche Lichtkegel im Hintergrund. */
+ background-image:
+ radial-gradient(60rem 40rem at 12% -8%, color-mix(in oklab, var(--fl-amber) 12%, transparent), transparent 70%),
+ radial-gradient(52rem 38rem at 92% 4%, color-mix(in oklab, var(--fl-magenta) 12%, transparent), transparent 68%);
+ background-attachment: fixed;
+ }
+
+ :focus-visible {
+ outline: 2px solid var(--fl-ring);
+ outline-offset: 2px;
+ border-radius: 4px;
+ }
+
+ ::selection {
+ background: color-mix(in oklab, var(--fl-magenta) 40%, transparent);
+ color: var(--fl-text);
+ }
+
+ /* Dezente Scrollbar im Kino-Look */
+ * {
+ scrollbar-width: thin;
+ scrollbar-color: var(--fl-border-strong) transparent;
+ }
+}
+
+/* --------------------------------------------------------------------------
+ Wiederkehrende Bausteine
+ -------------------------------------------------------------------------- */
+
+@utility fl-gradient-text {
+ background-image: var(--fl-gradient);
+ background-clip: text;
+ -webkit-background-clip: text;
+ color: transparent;
+}
+
+@utility fl-gradient-bg {
+ background-image: var(--fl-gradient);
+}
+
+@layer components {
+ .fl-card {
+ background-color: var(--fl-surface);
+ border: 1px solid var(--fl-border);
+ border-radius: var(--radius-card, 1rem);
+ box-shadow: var(--fl-shadow);
+ backdrop-filter: blur(12px);
+ }
+
+ /* Posterkarte: sanftes Aufglimmen beim Hover, kein hartes Aufblitzen. */
+ .fl-poster-card {
+ position: relative;
+ border-radius: var(--radius-poster, 0.875rem);
+ transition:
+ transform 320ms var(--ease-glow),
+ box-shadow 320ms var(--ease-glow);
+ }
+
+ .fl-poster-card:hover,
+ .fl-poster-card:focus-within {
+ transform: translateY(-3px);
+ box-shadow: var(--fl-glow-strong);
+ }
+
+ .fl-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ border-radius: 0.75rem;
+ padding: 0.55rem 1rem;
+ font-weight: 600;
+ font-size: 0.9375rem;
+ line-height: 1.2;
+ transition:
+ transform 200ms var(--ease-glow),
+ box-shadow 240ms var(--ease-glow),
+ background-color 200ms ease,
+ border-color 200ms ease;
+ cursor: pointer;
+ }
+
+ .fl-btn:disabled {
+ opacity: 0.55;
+ cursor: not-allowed;
+ }
+
+ .fl-btn-primary {
+ background-image: var(--fl-gradient);
+ color: var(--fl-on-accent);
+ box-shadow: var(--fl-glow);
+ }
+
+ .fl-btn-primary:hover:not(:disabled) {
+ box-shadow: var(--fl-glow-strong);
+ transform: translateY(-1px);
+ }
+
+ .fl-btn-ghost {
+ background-color: var(--fl-surface-2);
+ color: var(--fl-text);
+ border: 1px solid var(--fl-border);
+ }
+
+ .fl-btn-ghost:hover:not(:disabled) {
+ border-color: var(--fl-border-strong);
+ background-color: color-mix(in oklab, var(--fl-surface-2) 70%, var(--fl-amber) 8%);
+ }
+
+ .fl-input {
+ width: 100%;
+ background-color: var(--fl-surface-2);
+ border: 1px solid var(--fl-border);
+ border-radius: 0.75rem;
+ padding: 0.6rem 0.85rem;
+ color: var(--fl-text);
+ transition: border-color 200ms ease, box-shadow 200ms ease;
+ }
+
+ .fl-input::placeholder {
+ color: var(--fl-faint);
+ }
+
+ .fl-input:focus {
+ outline: none;
+ border-color: color-mix(in oklab, var(--fl-magenta) 55%, var(--fl-border));
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--fl-magenta) 22%, transparent);
+ }
+
+ /* Fortschrittsbalken im Signatur-Gradient */
+ .fl-progress {
+ height: 0.375rem;
+ border-radius: 999px;
+ background-color: var(--fl-surface-2);
+ overflow: hidden;
+ }
+
+ .fl-progress > span {
+ display: block;
+ height: 100%;
+ border-radius: 999px;
+ background-image: var(--fl-gradient);
+ transition: width 420ms var(--ease-glow);
+ }
+}
+
+/* Mikro-Animation: weiches Aufglimmen, z. B. beim Abhaken einer Episode. */
+@keyframes fl-glimmer {
+ 0% {
+ box-shadow: 0 0 0 0 color-mix(in oklab, var(--fl-amber) 55%, transparent);
+ background-color: color-mix(in oklab, var(--fl-amber) 16%, transparent);
+ }
+ 60% {
+ box-shadow: 0 0 0 10px color-mix(in oklab, var(--fl-magenta) 0%, transparent);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 transparent;
+ background-color: transparent;
+ }
+}
+
+.fl-glimmer {
+ animation: fl-glimmer 900ms var(--ease-glow);
+}
+
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ scroll-behavior: auto !important;
+ }
+}