307e0b48da
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>
121 lines
3.7 KiB
TypeScript
121 lines
3.7 KiB
TypeScript
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>
|
||
);
|
||
}
|