import type { Metadata, Viewport } from "next"; import { fontVars } from "yt-dlp-transcript-common/styles/fonts"; import { ThemeScript } from "yt-dlp-transcript-common/components/ThemeScript"; import { ThemeProvider } from "yt-dlp-transcript-common/components/ThemeProvider"; import { QueryProvider } from "yt-dlp-transcript-common/components/QueryProvider"; import { customAccentVars, resolveAccent, } from "yt-dlp-transcript-common/lib/accent"; import { BASE_GROUNDS, DEFAULT_ACCENT } from "yt-dlp-transcript-common/lib/brand"; import { ICON_METADATA } from "yt-dlp-transcript-common/lib/brandIconFiles"; import type { ThemeAccent, ThemeBase, } from "yt-dlp-transcript-common/components/themeConfig"; import { currentSite } from "./lib/site"; import { instanceMode, shipsPwa } from "./lib/mode"; import Header from "./components/Header"; import Footer from "./components/Footer"; import { ServiceWorkerRegister } from "./components/ServiceWorkerRegister"; import "./globals.css"; // The reader theme's starting point (plans/brand-and-themes.md). A site opens // on the reader's system base in its OWN accent (site.json `accent`: a named // id, or a custom hex fitted to each ground). The hub is the TOOL, not an // archive: it opens dark in Signal, the family's own accent, exactly as the // homepage does (homepage/app/layout.tsx), so the two read as one product. // Either way the reader cycles the base (ThemeToggle); the accent is the // site's, and a reader's stored pick from before is ignored. // instanceMode() is server-only; this whole file is a server component. function themeDefaults(): { defaultBase: ThemeBase; accent: ThemeAccent; // The inline `--accent-custom-light|dark` a custom-hex site needs // (tokens.css `--swatch-custom` reads them); null for a named accent. accentVars: Record | null; } { if (instanceMode() === "hub") { return { defaultBase: "dark", accent: DEFAULT_ACCENT, accentVars: null }; } const setting = currentSite().accent; return { defaultBase: "system", accent: resolveAccent(setting).id, accentVars: customAccentVars(setting), }; } export function generateMetadata(): Metadata { const s = currentSite(); return { title: { default: s.siteTitle, template: `%s — ${s.siteTitle}`, }, description: s.siteDescription, // Installability is opt-in: only a PWA-shipping instance links the manifest // (and registers the SW below). A dumb instance omits both — it stays // federatable JSON, but not independently installable. ...(shipsPwa() ? { manifest: "/manifest.webmanifest" } : {}), // Rendered per build by app/icons/[file]/route.ts: the site's accent, or // the parent mark on the hub. icons: ICON_METADATA, appleWebApp: { capable: true, statusBarStyle: "black-translucent", title: s.headerTitle || s.siteTitle, }, }; } export function generateViewport(): Viewport { return { // Browser chrome continues the page's GROUND, not its accent. A site // follows the OS (a light/dark pair), the hub opens dark; the pre-paint // ThemeScript then points every theme-color meta at the base in force. themeColor: instanceMode() === "hub" ? BASE_GROUNDS.dark : [ { media: "(prefers-color-scheme: light)", color: BASE_GROUNDS.light }, { media: "(prefers-color-scheme: dark)", color: BASE_GROUNDS.dark }, ], width: "device-width", initialScale: 1, // Let the layout run under the notch/home indicator; the safe-area insets // are then non-zero and `pb-safe`/`pt-safe` (globals.css) keep the sticky // bars — composer, selection toolbar, player control strip — clear of it. // No maximumScale/userScalable: pinch-zoom is never taken away. viewportFit: "cover", // The on-screen keyboard resizes the layout viewport instead of covering // it, so a `sticky bottom-0` composer stays above the keyboard. interactiveWidget: "resizes-content", }; } export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { // The accent is baked onto at prerender, so it is right on first // paint before any script: `data-accent` selects the tokens.css rule, and a // custom hex adds its fitted per-base values inline. Nothing replaces it: a // reader does not pick an accent. The hub's dark base is // server-rendered too (`.dark` + data-base), as on the homepage; a site's // "system" base cannot be resolved on the server, so it renders none and // the pre-paint script sets it. const theme = themeDefaults(); const serverDark = theme.defaultBase === "dark"; return ( {shipsPwa() && }
{children}