JavaScript 56.7%
TypeScript 42.6%
1import type { Metadata, Viewport } from "next";2import localFont from "next/font/local";3import "./globals.css";4import ThemeProvider from "@/components/theme/ThemeProvider";5import { DynamicToaster } from "@/components/theme/DynamicToaster";67const geist = localFont({8 src: "./fonts/GeistVF.woff",9 variable: "--font-geist",10 weight: "100 900",11 display: "swap",12});1314const geistMono = localFont({15 src: "./fonts/GeistMonoVF.woff",16 variable: "--font-geist-mono",17 weight: "100 900",18 display: "swap",19});2021export const metadata: Metadata = {22 title: { default: "SPB Cloud", template: "%s · SPB Cloud" },23 description: "Cloud personnel privé, chiffré et local-first.",24 applicationName: "SPB Cloud",25 manifest: "/manifest.webmanifest",26 appleWebApp: {27 capable: true,28 title: "SPB Cloud",29 statusBarStyle: "black-translucent",30 },31 icons: {32 icon: [{ url: "/icons/icon-192.png", sizes: "192x192", type: "image/png" }],33 apple: [{ url: "/icons/apple-touch-icon.png", sizes: "180x180" }],34 },35 formatDetection: { telephone: false },36};3738export const viewport: Viewport = {39 width: "device-width",40 initialScale: 1,41 viewportFit: "cover",42 themeColor: [43 { media: "(prefers-color-scheme: light)", color: "#f5f6f8" },44 { media: "(prefers-color-scheme: dark)", color: "#0a0b10" },45 ],46};4748// Appliqué avant le premier rendu : thème, accent, densité, mouvement.49// ⚠️ Garder synchronisé avec lib/prefs.ts (clés) et ThemeProvider.50const PREPAINT = `(function(){try{51var d=document.documentElement,ls=localStorage;52var t=ls.getItem('spb-theme');53var dark=t==='dark'||(t==='system'&&window.matchMedia('(prefers-color-scheme: dark)').matches);54d.classList.toggle('dark',dark);d.classList.toggle('light',!dark);d.style.colorScheme=dark?'dark':'light';55var a=ls.getItem('spb-accent');d.setAttribute('data-accent',a||'aurore');56var de=ls.getItem('spb-density');d.setAttribute('data-density',de==='compact'?'compact':'comfortable');57var m=ls.getItem('spb-motion');d.setAttribute('data-motion',m==='reduced'?'reduced':'normal');58}catch(e){}})();`;5960export default function RootLayout({61 children,62}: Readonly<{63 children: React.ReactNode;64}>) {65 return (66 <html67 lang="fr"68 className={`light ${geist.variable} ${geistMono.variable}`}69 data-accent="aurore"70 data-density="comfortable"71 data-motion="normal"72 suppressHydrationWarning73 >74 <head>75 <script dangerouslySetInnerHTML={{ __html: PREPAINT }} />76 </head>77 <body className="antialiased font-sans">78 <ThemeProvider>79 {children}80 <DynamicToaster />81 </ThemeProvider>82 </body>83 </html>84 );85}86