187 lines
6.8 KiB
TypeScript
187 lines
6.8 KiB
TypeScript
import type { Metadata, Viewport } from "next";
|
|
import {
|
|
Architects_Daughter,
|
|
Caveat,
|
|
Figtree,
|
|
Inter,
|
|
JetBrains_Mono,
|
|
Orbitron,
|
|
Righteous,
|
|
VT323,
|
|
} from "next/font/google";
|
|
import "./globals.css";
|
|
|
|
import { ThemeProvider } from "@/components/theme/theme-provider";
|
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
import { Toaster } from "@/components/ui/sonner";
|
|
import { RegisterServiceWorker } from "@/components/pwa/register-sw";
|
|
import { auth } from "@/auth";
|
|
import { prisma } from "@/lib/db";
|
|
import { themeInitScript, type RawTheme } from "@/lib/themes";
|
|
|
|
/** UI type: Inter -- a neutral, highly legible humanist sans that reads
|
|
* cleanly at small sizes (to-do lists, tables, nav). */
|
|
const inter = Inter({
|
|
variable: "--font-sans",
|
|
subsets: ["latin"],
|
|
});
|
|
|
|
/** Display type: Figtree -- a slightly friendlier, rounder geometric sans
|
|
* for headings and wordmarks. Same family, one step of personality. */
|
|
const figtree = Figtree({
|
|
variable: "--font-heading",
|
|
subsets: ["latin"],
|
|
});
|
|
|
|
/** Code: JetBrains Mono, used for the markdown editor's monospace bits. */
|
|
const jetbrainsMono = JetBrains_Mono({
|
|
variable: "--font-mono",
|
|
subsets: ["latin"],
|
|
});
|
|
|
|
/** Blueprint theme's drafting face (see app/globals.css) -- a hand-drawn
|
|
* architectural face, lettered like a drawing's title block. */
|
|
const architectsDaughter = Architects_Daughter({
|
|
variable: "--font-blueprint",
|
|
subsets: ["latin"],
|
|
weight: "400",
|
|
});
|
|
|
|
/** Cyberpunk theme's pixel-terminal face (see app/globals.css). */
|
|
const vt323 = VT323({
|
|
variable: "--font-cyberpunk",
|
|
subsets: ["latin"],
|
|
weight: "400",
|
|
});
|
|
|
|
/** Vaporwave theme's retro face (see app/globals.css) -- a rounded,
|
|
* retro-futuristic face for the pastel synthwave look. */
|
|
const righteous = Righteous({
|
|
variable: "--font-vaporwave",
|
|
subsets: ["latin"],
|
|
weight: "400",
|
|
});
|
|
|
|
/** Starfield theme's sci-fi face (see app/globals.css). */
|
|
const orbitron = Orbitron({
|
|
variable: "--font-starfield",
|
|
subsets: ["latin"],
|
|
});
|
|
|
|
/** Notebook theme's handwriting face (see app/globals.css) -- a casual pen
|
|
* script for the stationery look. */
|
|
const caveat = Caveat({
|
|
variable: "--font-notebook",
|
|
subsets: ["latin"],
|
|
});
|
|
|
|
export const metadata: Metadata = {
|
|
title: "Organize",
|
|
description: "An extended to-do list and notes organizer.",
|
|
manifest: "/manifest.webmanifest",
|
|
icons: {
|
|
icon: [{ url: "/icons/icon-192.png", sizes: "192x192", type: "image/png" }],
|
|
apple: [{ url: "/icons/apple-touch-icon.png", sizes: "180x180", type: "image/png" }],
|
|
},
|
|
appleWebApp: {
|
|
capable: true,
|
|
statusBarStyle: "default",
|
|
title: "Organize",
|
|
},
|
|
};
|
|
|
|
export const viewport: Viewport = {
|
|
themeColor: [
|
|
{ media: "(prefers-color-scheme: light)", color: "#fafafa" },
|
|
{ media: "(prefers-color-scheme: dark)", color: "#22252b" },
|
|
],
|
|
};
|
|
|
|
export default async function RootLayout({ children }: LayoutProps<"/">) {
|
|
// Per-user global theme (stored on the profile row; set from the theme
|
|
// menu, see saveUserTheme in lib/actions/profile.ts). When signed in it
|
|
// is authoritative: the no-FOUC script below applies it before first
|
|
// paint and the ThemeProvider starts from it and persists changes back
|
|
// -- so each account in a linked set keeps its own look in the same
|
|
// browser across account toggles (a toggle is a full navigation, and
|
|
// this layout re-renders for the new user). Anonymous visitors keep the
|
|
// old localStorage-based behavior.
|
|
const session = await auth();
|
|
let userTheme: RawTheme | undefined;
|
|
if (session?.user) {
|
|
// Stored as free text; valid values are enforced by ThemeSchema
|
|
// (lib/validation/profile.ts) at write time, so the assertion is safe
|
|
// -- same pattern as Project.theme.
|
|
const profile = await prisma.user.findUnique({
|
|
where: { id: session.user.id },
|
|
select: { theme: true },
|
|
});
|
|
userTheme = (profile?.theme as RawTheme | null) ?? "system";
|
|
}
|
|
|
|
return (
|
|
<html
|
|
lang="en"
|
|
className={`${inter.variable} ${figtree.variable} ${jetbrainsMono.variable} ${architectsDaughter.variable} ${vt323.variable} ${righteous.variable} ${orbitron.variable} ${caveat.variable} h-full antialiased`}
|
|
suppressHydrationWarning
|
|
>
|
|
<body className="min-h-full flex flex-col">
|
|
{/* Nineteen looks: ten light (Default, Sunset, Meadow, Honey, Rosé,
|
|
Lavender, Slate, Blueprint, Vaporwave, Notebook) and nine dark
|
|
(Dark, Ocean, Pine, Plum, Midnight, Ember, Rosewood, Cyberpunk,
|
|
Starfield) -- complete token sets in app/globals.css; Blueprint,
|
|
Cyberpunk, Vaporwave, Starfield, and Notebook additionally carry
|
|
their own fonts, backgrounds, and animations.
|
|
"system" resolves to Default/Dark by OS preference.
|
|
No-FOUC theme restore: runs before first paint, applies the
|
|
right class + color-scheme to <html>. Signed-in users get
|
|
their profile's stored theme (per account, so it survives
|
|
account toggles); anonymous visitors get the browser's
|
|
localStorage preference -- see themeInitScript in lib/themes.ts. */}
|
|
<script
|
|
dangerouslySetInnerHTML={{
|
|
__html: themeInitScript(userTheme),
|
|
}}
|
|
/>
|
|
{/* Vaporwave scenery (see app/globals.css): the outrun floor grid,
|
|
plus palm silhouettes at either side of the horizon. All fixed
|
|
behind the content, hidden by default; the theme class on <html>
|
|
switches them on. */}
|
|
<div className="vw-floor" aria-hidden />
|
|
<svg className="vw-palm-defs" aria-hidden>
|
|
<symbol id="vw-palm" viewBox="0 0 120 150">
|
|
<g fill="none" stroke="currentColor" strokeLinecap="round">
|
|
<path d="M60 150C57 110 56 80 60 45" strokeWidth="8" />
|
|
<path d="M60 45Q38 30 10 48" strokeWidth="9" />
|
|
<path d="M60 45Q44 18 20 12" strokeWidth="9" />
|
|
<path d="M60 45Q56 14 42 4" strokeWidth="9" />
|
|
<path d="M60 45Q66 12 84 6" strokeWidth="9" />
|
|
<path d="M60 45Q78 16 100 12" strokeWidth="9" />
|
|
<path d="M60 45Q84 30 110 50" strokeWidth="9" />
|
|
</g>
|
|
</symbol>
|
|
</svg>
|
|
<svg className="vw-palm vw-palm--left-main" aria-hidden>
|
|
<use href="#vw-palm" />
|
|
</svg>
|
|
<svg className="vw-palm vw-palm--left-small" aria-hidden>
|
|
<use href="#vw-palm" />
|
|
</svg>
|
|
<svg className="vw-palm vw-palm--right-main" aria-hidden>
|
|
<use href="#vw-palm" />
|
|
</svg>
|
|
<svg className="vw-palm vw-palm--right-small" aria-hidden>
|
|
<use href="#vw-palm" />
|
|
</svg>
|
|
<ThemeProvider userTheme={userTheme}>
|
|
<TooltipProvider delay={200}>
|
|
{children}
|
|
<Toaster />
|
|
<RegisterServiceWorker />
|
|
</TooltipProvider>
|
|
</ThemeProvider>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|