Files
flimmer/src/app/layout.tsx
T
Florian Wilde f613136e8a feat: Bedienelemente für Bibliothek und Episodenfortschritt
Server-Aktionen prüfen die Sitzung jeweils selbst — auf Angaben aus dem
Formular ist beim Zugriffsschutz kein Verlass.

Häkchen reagieren sofort und werden zurückgedreht, wenn der Server
widerspricht: Abhaken soll sich anfühlen wie ein Schalter, nicht wie ein
Formular. Jede Sichtungsaktion meldet sich unten kurz zurück und lässt sich
von dort zurücknehmen — die Wirkung passiert weit weg vom Klick.
2026-09-01 19:24:51 +02:00

82 lines
2.5 KiB
TypeScript

import type { Metadata, Viewport } from "next";
import { Outfit } from "next/font/google";
import { BrandDefs } from "@/components/brand/FlimmerLogo";
import { ToastProvider } from "@/components/Toasts";
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 (
<html lang="de" data-theme="dark" className={outfit.variable} suppressHydrationWarning>
<head>
<script dangerouslySetInnerHTML={{ __html: THEME_BOOTSTRAP }} />
</head>
<body className="antialiased">
<BrandDefs />
<a
href="#inhalt"
className="sr-only focus:not-sr-only focus:fixed focus:left-4 focus:top-4 focus:z-50 fl-btn fl-btn-primary"
>
Zum Inhalt springen
</a>
<ToastProvider>{children}</ToastProvider>
</body>
</html>
);
}