Files
RetroBlog/src/components/Shell.tsx
T
kawa 307e0b48da Refine PS1/PS2/PS3/NDS themes and add decorative background field
Rework console theme stylesheets and add opt-in .rb-bg cube field
(used by PS2 BIOS bobbing-cube background) wired through Shell.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 03:53:20 +02:00

121 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from "next/link";
import type { CSSProperties, ReactNode } from "react";
import { THEMES, type ThemeId } from "@/themes/registry";
import { getSettings } from "@/lib/settings";
import { isAdmin } from "@/lib/auth";
import ThemeSwitcher from "./ThemeSwitcher";
import Clock from "./Clock";
// Shared, theme-agnostic page chrome. Every theme restyles these `rb-` classes
// to look like its own OS/console. The HTML skeleton never changes.
export default async function Shell({
theme,
title,
children,
}: {
theme: ThemeId;
title: string;
children: ReactNode;
}) {
const settings = getSettings();
const admin = await isAdmin();
// Admins always get the switcher with every skin; the public only sees it when
// enabled, and only the allowed skins.
const showSwitcher = admin || settings.publicThemeToggle;
const switcherThemes = admin
? THEMES
: THEMES.filter((t) => settings.allowedThemes.includes(t.id));
return (
<div className="rb-desktop">
{/* Decorative background field. Hidden by default; themes opt in
(PS2 uses it for the BIOS bobbing-cube field). */}
<div className="rb-bg" aria-hidden>
<div className="rb-bg-grid">
{Array.from({ length: 15 * 13 }).map((_, i) => {
// deterministic pseudo-random thickness + bob phase per cube
const h = 34 + ((i * 53) % 78);
const delay = (((i * 37) % 100) / 100) * 4;
return (
<span
key={i}
className="rb-cube"
style={
{
"--cube-h": `${h}px`,
"--cube-delay": `${delay.toFixed(2)}s`,
} as CSSProperties
}
/>
);
})}
</div>
</div>
<div className="rb-window" role="application">
<div className="rb-titlebar">
<span className="rb-titlebar-icon" aria-hidden />
<span className="rb-titlebar-text">
{title} {settings.title}
</span>
<div className="rb-titlebar-buttons" aria-hidden>
<button className="rb-tb-btn rb-tb-min" tabIndex={-1}>
<span>_</span>
</button>
<button className="rb-tb-btn rb-tb-max" tabIndex={-1}>
<span></span>
</button>
<button className="rb-tb-btn rb-tb-close" tabIndex={-1}>
<span>×</span>
</button>
</div>
</div>
<nav className="rb-menubar" aria-label="Main">
<Link className="rb-menu-link" href="/">
Home
</Link>
<Link className="rb-menu-link" href="/about">
About
</Link>
{admin && (
<Link className="rb-menu-link" href="/admin">
Admin
</Link>
)}
<span className="rb-spacer" />
{showSwitcher && (
<ThemeSwitcher initial={theme} themes={switcherThemes} />
)}
</nav>
<div className="rb-content">{children}</div>
<div className="rb-statusbar">
<span className="rb-status-cell">Ready</span>
<span className="rb-status-cell rb-status-grow">
{settings.footer}
</span>
</div>
</div>
<div className="rb-taskbar">
<button className="rb-start">
<span className="rb-start-logo" aria-hidden />
start
</button>
<div className="rb-tasks">
<button className="rb-task rb-task-active">
<span className="rb-task-icon" aria-hidden />
{settings.title}
</button>
</div>
<div className="rb-tray">
<Clock />
</div>
</div>
</div>
);
}