Manifest, Icons (auch maskable und Apple-Touch) und ein Service Worker. Auf dem Handy laesst sich Bricktrack damit auf den Startbildschirm legen und startet ohne Browserleiste; die Verknuepfungen springen direkt zu "Fehlende Sets" und zur Sammlung. Der Service Worker liefert Seiten nach dem Muster "erst Netz, dann Cache": mit Verbindung ist alles aktuell, ohne Verbindung bleibt lesbar, was schon einmal geladen war - genau der Katalog, den man im Keller oder im Laden braucht. Statische Build-Dateien und Set-Bilder kommen aus dem Cache, weil ihre Namen ohnehin unveraenderlich sind. Bewusst nie aus dem Cache: /api/auth, /api/export und der Healthcheck, und nichts ausser GET - eine offline "reservierte" Wunschliste waere eine Luege. Beim Abmelden raeumt die App den Cache, damit auf einem geteilten Geraet keine lesbaren Seiten zurueckbleiben. Icons entstehen reproduzierbar aus einer SVG-Quelle (node scripts/generate-icons.mjs). Refs Phase 2 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
49 lines
1.5 KiB
TypeScript
49 lines
1.5 KiB
TypeScript
import type { Metadata, Viewport } from 'next';
|
|
import { ServiceWorkerRegistration } from '@/components/layout/service-worker';
|
|
import { Toaster } from '@/components/ui/sonner';
|
|
import './globals.css';
|
|
|
|
export const metadata: Metadata = {
|
|
title: {
|
|
default: 'Bricktrack',
|
|
template: '%s · Bricktrack',
|
|
},
|
|
description: 'Sammlung und Wunschlisten für Spielzeug-Sets',
|
|
applicationName: 'Bricktrack',
|
|
manifest: '/manifest.webmanifest',
|
|
icons: {
|
|
icon: [{ url: '/favicon.png', type: 'image/png', sizes: '32x32' }],
|
|
apple: [{ url: '/icons/apple-touch-icon.png', sizes: '180x180' }],
|
|
},
|
|
appleWebApp: {
|
|
capable: true,
|
|
title: 'Bricktrack',
|
|
statusBarStyle: 'default',
|
|
},
|
|
// A private, self-hosted instance never belongs in a search index - and the
|
|
// public wishlist links are meant to be shared, not found.
|
|
robots: { index: false, follow: false },
|
|
};
|
|
|
|
export const viewport: Viewport = {
|
|
width: 'device-width',
|
|
initialScale: 1,
|
|
viewportFit: 'cover',
|
|
themeColor: [
|
|
{ media: '(prefers-color-scheme: light)', color: '#ffffff' },
|
|
{ media: '(prefers-color-scheme: dark)', color: '#0a0a0a' },
|
|
],
|
|
};
|
|
|
|
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
|
return (
|
|
<html lang="de" suppressHydrationWarning>
|
|
<body className="min-h-dvh bg-background text-foreground antialiased">
|
|
{children}
|
|
<Toaster position="top-center" richColors closeButton />
|
|
<ServiceWorkerRegistration />
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|