Files
Epicnabbo-Catalogus-Updated…/src/app/layout.tsx
T
simoleo89 8cedf5614e UI gaps: radio player widget, logo generator, public room page
Phase D of the parity push:
- Radio player: fixed bottom-right widget (port of atom's radio-player.blade)
  that streams the hotel radio via <audio>, with play/pause, volume, current
  DJ / now-playing and live listener count, polling the public API every 15s.
  A server gate (RadioPlayerGate) only mounts it when radio is enabled + a
  stream URL is set, so no widget JS ships when off. Mounted in the layout.
- Logo generator (/logo): client tool — hotel name + font/colour/size pickers
  with a live preview and "download PNG" via canvas.
- Public room page (/room/[id]): renders an emulator room (name, owner,
  users/max, state, category) in a ContentCard.

Verified live (prod, amx_test): /logo renders the generator, /room/50 shows
the real room "Dark Elegant Bundle", and with radio enabled the player
mounts fixed bottom-right (audio + play/pause + Test FM); reverted the test
settings. tsc 0, vitest 49/49, next build 0.
2026-06-28 21:48:42 +02:00

67 lines
2.4 KiB
TypeScript

import type { Metadata } from "next";
import { NextIntlClientProvider } from "next-intl";
import { getLocale, getMessages } from "next-intl/server";
import { Nunito } from "next/font/google";
import type { ReactNode } from "react";
import { Navigation } from "@/components/navigation";
import RadioPlayerGate from "@/components/public/radio-player-gate";
import { SiteFooter } from "@/components/site-footer";
import { SiteHeader } from "@/components/site-header";
import { ThemeVars } from "@/components/theme-vars";
import { TopHeader } from "@/components/top-header";
import { enforceSiteAccess } from "@/lib/access-guard";
import { siteSettings } from "@/lib/services/site-settings";
import "./globals.css";
const nunito = Nunito({
subsets: ["latin"],
weight: ["400", "600", "700", "800"],
display: "swap",
variable: "--font-nunito",
});
export async function generateMetadata(): Promise<Metadata> {
const hotel = (await siteSettings.get("hotel_name", "Atom")) ?? "Atom";
return {
title: { default: hotel, template: `%s · ${hotel}` },
description: `${hotel} — a Habbo retro hotel. Hang out, collect furni and meet friends.`,
};
}
export default async function RootLayout({ children }: { children: ReactNode }) {
await enforceSiteAccess();
const locale = await getLocale();
const messages = await getMessages();
return (
<html lang={locale} className={`app ${nunito.variable}`}>
<head>
{/* Apply the saved theme before first paint to avoid a light→dark flash. */}
<script
dangerouslySetInnerHTML={{
__html:
"try{if(localStorage.getItem('theme')==='dark')document.documentElement.classList.add('dark');}catch(e){}",
}}
/>
</head>
<body
className="flex min-h-screen flex-col site-bg"
style={{ backgroundColor: "var(--color-background)" }}
>
<NextIntlClientProvider locale={locale} messages={messages}>
<ThemeVars />
<TopHeader />
<SiteHeader />
<Navigation />
<main className="overflow-hidden site-bg">
<div className="mx-auto mt-10 grid max-w-7xl grid-cols-12 gap-x-3 gap-y-8 p-6 md:mt-0">
<div className="col-span-12">{children}</div>
</div>
</main>
<SiteFooter />
<RadioPlayerGate />
</NextIntlClientProvider>
</body>
</html>
);
}