feat: retro blog engine with swappable OS/console themes

Next.js 16 (App Router, TS) + SQLite (better-sqlite3) + marked.

Core: a shared semantic HTML skeleton (rb- classes in Shell.tsx) that
each theme reskins via a scoped [data-theme="..."] CSS file. Theme is
persisted in a cookie, resolved server-side in the root layout, and
swapped live by the client switcher (no reload, no FOUC).

- DB auto-migrates and seeds posts on first run (data/blog.db, gitignored)
- Pages: post list, post detail (markdown), about
- Themes shipped: Windows XP (Luna), Windows 9x, PlayStation 2
- Adding a skin = registry entry + one scoped CSS file

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 00:42:20 +02:00
commit 37ba9b3e19
29 changed files with 5752 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
import Link from "next/link";
import type { ReactNode } from "react";
import { type ThemeId } from "@/themes/registry";
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 function Shell({
theme,
title,
children,
}: {
theme: ThemeId;
title: string;
children: ReactNode;
}) {
return (
<div className="rb-desktop">
<div className="rb-window" role="application">
<div className="rb-titlebar">
<span className="rb-titlebar-icon" aria-hidden />
<span className="rb-titlebar-text">{title} RetroBlog</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>
<a
className="rb-menu-link"
href="https://github.com"
target="_blank"
rel="noreferrer"
>
Links
</a>
<span className="rb-spacer" />
<ThemeSwitcher initial={theme} />
</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">
RetroBlog v0.1
</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 />
RetroBlog
</button>
</div>
<div className="rb-tray">
<Clock />
</div>
</div>
</div>
);
}