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
+184
View File
@@ -0,0 +1,184 @@
/* ============================================================
RetroBlog — globals
Shared, theme-agnostic skeleton. Visual identity lives in the
per-theme files imported below, each scoped to
[data-theme="..."]. This file only handles reset + layout
structure that every skin reuses.
============================================================ */
@import "../themes/xp.css";
@import "../themes/ninex.css";
@import "../themes/ps2.css";
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
body {
min-height: 100vh;
}
a {
color: inherit;
}
img {
max-width: 100%;
}
/* ---- structural layout (positioning only; themes paint) ---- */
.rb-desktop {
min-height: 100vh;
display: flex;
flex-direction: column;
padding: 0 0 40px; /* room for the fixed taskbar */
}
.rb-window {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
margin: 24px auto;
width: min(920px, calc(100% - 32px));
}
.rb-titlebar {
display: flex;
align-items: center;
gap: 8px;
flex: 0 0 auto;
user-select: none;
}
.rb-titlebar-text {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.rb-titlebar-buttons {
display: flex;
gap: 2px;
}
.rb-menubar {
display: flex;
align-items: center;
gap: 4px;
flex: 0 0 auto;
}
.rb-spacer {
flex: 1;
}
.rb-content {
flex: 1;
min-height: 0;
overflow: auto;
}
.rb-statusbar {
display: flex;
gap: 4px;
flex: 0 0 auto;
}
.rb-status-grow {
flex: 1;
text-align: right;
}
/* taskbar pinned to viewport bottom */
.rb-taskbar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 40px;
display: flex;
align-items: center;
gap: 6px;
padding: 0 6px;
z-index: 50;
}
.rb-tasks {
flex: 1;
display: flex;
gap: 4px;
overflow: hidden;
}
.rb-tray {
display: flex;
align-items: center;
}
/* ---- post list ---- */
.rb-post-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 16px;
}
.rb-post-card-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.rb-post-card-title {
margin: 0;
}
.rb-post-meta {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.rb-post-tags {
display: inline-flex;
gap: 6px;
flex-wrap: wrap;
}
.rb-readmore {
margin-left: auto;
}
.rb-article-meta {
display: flex;
gap: 14px;
flex-wrap: wrap;
}
.rb-switcher {
display: flex;
align-items: center;
gap: 6px;
}
:root {
color-scheme: light;
}