From b001e62695dbe6a76c6690df7c03f40736709c85 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Tue, 1 Sep 2026 18:46:47 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20App-H=C3=BClle=20mit=20Header,=20Theme-?= =?UTF-8?q?Umschalter=20und=20=C3=9Cbersicht?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Layout mit Inline-Skript, das data-theme setzt, bevor der erste Pixel gemalt wird — sonst blitzt beim Laden das falsche Farbschema auf. Logo als React-Komponente statt als Bilddatei: nimmt Theme-Farben an, spart einen Netzwerkabruf und die Verläufe liegen einmalig im Root-Layout. Im hellen Modus greifen eigene Tokens für Verlaufstext und Wortmarke; der vollfarbige Signaturverlauf fällt auf warmem Weiß sonst unter WCAG AA. --- src/app/globals.css | 18 ++- src/app/layout.tsx | 80 ++++++++++++ src/app/manifest.ts | 33 +++++ src/app/page.tsx | 83 ++++++++++++ src/components/AppShell.tsx | 120 ++++++++++++++++++ src/components/NavLink.tsx | 37 ++++++ src/components/ThemeToggle.tsx | 95 ++++++++++++++ src/components/UserMenu.tsx | 115 +++++++++++++++++ src/components/brand/FlimmerLogo.tsx | 181 +++++++++++++++++++++++++++ src/i18n/de.ts | 76 +++++++++++ src/i18n/index.ts | 43 +++++++ 11 files changed, 880 insertions(+), 1 deletion(-) create mode 100644 src/app/layout.tsx create mode 100644 src/app/manifest.ts create mode 100644 src/app/page.tsx create mode 100644 src/components/AppShell.tsx create mode 100644 src/components/NavLink.tsx create mode 100644 src/components/ThemeToggle.tsx create mode 100644 src/components/UserMenu.tsx create mode 100644 src/components/brand/FlimmerLogo.tsx create mode 100644 src/i18n/de.ts create mode 100644 src/i18n/index.ts diff --git a/src/app/globals.css b/src/app/globals.css index 3dc2f39..e3c366f 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -41,7 +41,15 @@ --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 */ + /* Markenstops fuer SVG-Verlaeufe (Logo). Als Variablen, damit das Logo im + Light Mode dunkler wird und lesbar bleibt, ohne die Identitaet zu aendern. */ + --fl-brand-1: #ffc46b; + --fl-brand-2: #ffb454; + --fl-brand-3: #ff4d9d; + --fl-gradient: linear-gradient(115deg, var(--fl-amber-soft) 0%, var(--fl-amber) 38%, var(--fl-magenta) 100%); + /* Fuer Text: im Dunkeln identisch, im Hellen deutlich abgedunkelt (AA). */ + --fl-gradient-ink: var(--fl-gradient); --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; @@ -80,7 +88,15 @@ --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 */ + /* Logo im Hellen: dieselbe Bernstein-zu-Magenta-Bewegung, nur tiefer + angesetzt — sonst verschwindet die Wortmarke auf dem warmen Weiss. */ + --fl-brand-1: #c26a00; + --fl-brand-2: #b35d00; + --fl-brand-3: #c9186f; + --fl-gradient: linear-gradient(115deg, var(--fl-amber-soft) 0%, var(--fl-amber) 38%, var(--fl-magenta) 100%); + /* Verlaufstext im Hellen: beide Enden erfuellen AA auf --fl-bg. */ + --fl-gradient-ink: linear-gradient(115deg, #8a4b00 0%, #a33a2a 45%, #b01262 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; @@ -181,7 +197,7 @@ -------------------------------------------------------------------------- */ @utility fl-gradient-text { - background-image: var(--fl-gradient); + background-image: var(--fl-gradient-ink); background-clip: text; -webkit-background-clip: text; color: transparent; diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..1fe4283 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,80 @@ +import type { Metadata, Viewport } from "next"; +import { Outfit } from "next/font/google"; +import { BrandDefs } from "@/components/brand/FlimmerLogo"; +import { env } from "@/lib/env"; +import "./globals.css"; + +/** + * Outfit wird beim Bauen heruntergeladen und mit ausgeliefert — die laufende + * Instanz fragt also niemals bei Google an. + */ +const outfit = Outfit({ + subsets: ["latin"], + display: "swap", + variable: "--font-outfit", +}); + +export const metadata: Metadata = { + metadataBase: new URL(env.appUrl), + title: { + default: "Flimmer", + template: "%s · Flimmer", + }, + description: "Serien und Filme tracken — selbst gehostet, ohne fremde Konten.", + applicationName: "Flimmer", + manifest: "/manifest.webmanifest", + icons: { + icon: [ + { url: "/favicon.svg", type: "image/svg+xml" }, + { url: "/favicon.ico", sizes: "48x48" }, + ], + apple: "/icons/apple-touch-icon.png", + }, + openGraph: { + title: "Flimmer", + description: "Serien und Filme tracken — selbst gehostet, ohne fremde Konten.", + images: ["/og-image.png"], + type: "website", + }, +}; + +export const viewport: Viewport = { + themeColor: [ + { media: "(prefers-color-scheme: dark)", color: "#0c0a14" }, + { media: "(prefers-color-scheme: light)", color: "#faf7f5" }, + ], + width: "device-width", + initialScale: 1, + viewportFit: "cover", +}; + +/** + * Setzt data-theme, bevor der erste Pixel gemalt wird — sonst blitzt beim + * Laden kurz das falsche Farbschema auf. + */ +const THEME_BOOTSTRAP = `(function(){try{ +var stored=localStorage.getItem("flimmer-theme"); +var mode=stored==="light"||stored==="dark"?stored: + (stored==="system"||!stored)&&window.matchMedia("(prefers-color-scheme: light)").matches?"light":"dark"; +document.documentElement.setAttribute("data-theme",mode); +}catch(e){document.documentElement.setAttribute("data-theme","dark");}})();`; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + +