rework the site: gruvbox editorial default with a desktop-mode toggle; drop the terminal layout and phone desktop

This commit is contained in:
Martin Dvořák 2026-09-30 07:49:31 +00:00
commit 86692637de
19 changed files with 1350 additions and 1664 deletions

View file

@ -33,16 +33,18 @@ When in doubt, prefer updating. A stale README **or** agents.md is treated as a
## What this project is
Personal portfolio site for **dvorakmortonxd.dev**. It's not a normal website —
it ships **two fully interactive desktop/OS simulations** plus a terminal, and
picks which one to show based on theme and device.
Personal portfolio site for **dvorakmortonxd.dev**. The default view is a
**gruvbox editorial page** (a technical-print layout, no images); a "Desktop mode"
button swaps to a **KDE Plasma desktop simulation**. Phones only ever get the
editorial page.
- Framework: **Next.js 16** (App Router, Turbopack), **React 19**, TypeScript.
- Styling: plain CSS files (`page.css`, `skeuo.css`), no CSS-in-JS, no Tailwind
utilities in the components (Tailwind is present via PostCSS but the look is
hand-written CSS).
- Styling: **Tailwind v4** for the editorial page — `globals.css` is the design
system (CSS custom properties, `@theme` tokens, `@utility` classes) — plus plain
CSS for the desktop (`skeuo.css`) and the shared component styles the desktop
apps still use (`page.css`).
- Path alias: `@/*` → `src/*`.
- Version: `2.1.0` (see `package.json`).
- Version: `3.0.0` (see `package.json`).
### Scripts
```bash
@ -73,12 +75,13 @@ token.** Do not print it, copy it into new files, or push it anywhere else.
```
src/app/
page.tsx → renders <ThemeRoot/>, imports page.css + skeuo.css
layout.tsx → html shell, metadata, ThemeScript
globals.css → base/reset styles + Tailwind import
page.tsx → renders <ThemeRoot/>, imports the theme stylesheets
layout.tsx → html shell, metadata, next/font, ThemeScript
globals.css → gruvbox editorial design system (tokens, @utility, base, reveals)
editorial.css → editorial page helpers (social masks, heatmap)
page.css → shared component styles used by the desktop apps
skeuo.css → KDE Plasma desktop styles
data.ts → socials, codingProjects, techProjects, friends, wallpapers
page.css → base / terminal theme styles
skeuo.css → Windows/Plasma desktop + mobile home styles
revival-inc/page.tsx → fullscreen iframe embedding /public/revival-inc/index.html
api/
heatmap/route.ts → proxy for git.dvorakmortonxd.dev contribution heatmap
@ -86,38 +89,44 @@ src/app/
ping/route.ts → service health check (HTTP + minecraft:// via mcstatus.io); host allowlist
frame/route.ts → header-stripping proxy so an unframeable friend site loads in a window; host allowlist
components/
ThemeRoot.tsx → picks which shell to show (the router of the UI)
TerminalLayout.tsx → "terminal" theme (Gruvbox)
ThemeRoot.tsx → editorial page by default, desktop when theme === "skeuo"
EditorialSite.tsx → the gruvbox editorial page (nav, hero, sections)
editorial-ui.tsx → editorial primitives: Reveal, Section, ServiceRow, Contributions, …
WindowsDesktop.tsx → "skeuo" theme, desktop: icons, windows, taskbar
MobileHome.tsx → "skeuo" theme, phone: app grid + dock
WinWindow.tsx → a single draggable/resizable window
apps.tsx → APPS registry + the content of each app + in-app terminal
+ embedded-site windows + the Minecraft-style SMP menu
useDesktopIcons.ts → desktop icon grid + drag/drop (THE KEY FILE, see below)
useDragOrder.ts → simple list reordering (mobile home screen only)
useDragOrder.ts → simple list reordering (used inside desktop apps)
useTheme.ts → theme state via useSyncExternalStore
ThemeScript.tsx → inline pre-hydration script: theme + device detection
ServiceButton.tsx → service link + live status dot (pings /api/ping)
ContributionHeatmap.tsx → heatmap + recent-commit widget (uses the two proxies)
ServiceButton.tsx → desktop service link + live status dot (pings /api/ping)
ContributionHeatmap.tsx → desktop heatmap + recent-commit widget (uses the two proxies)
Clock.tsx → live clock (useSyncExternalStore + interval)
JokerLogo.tsx → site logo with a click easter egg
SlBackground.tsx → animated ASCII train background (terminal theme)
scripts/
dev-server.mjs → custom dev server: live updates minus the dev-only debug surface
```
### How UI selection works
1. `ThemeScript` runs before paint. It reads `localStorage.theme` and sets
`data-theme` (`skeuo` | `terminal`) and `data-device` (`phone` | `desktop`)
on `<html>` from a user-agent sniff.
2. `useTheme()` (in `useTheme.ts`) reads `data-theme` via
`useSyncExternalStore` and dispatches a `theme-change` event on toggle.
3. `ThemeRoot` shows the terminal when `theme === "terminal"`; otherwise it
mounts **both** `WindowsDesktop` and `MobileHome` and lets CSS
(`html[data-device="phone"]`) decide which is visible. The desktop stays
mounted while hidden so the Welcome window isn't recreated.
`data-theme` (`gruvbox` | `skeuo`) and `data-device` (`phone` | `desktop`) on
`<html>` from a user-agent sniff. On a phone it forces `gruvbox` and CSS hides
every `.desktop-toggle`, so the desktop is unreachable there.
2. `useTheme()` (in `useTheme.ts`) reads `data-theme` via `useSyncExternalStore`;
`setTheme` writes the attribute, persists to `localStorage`, and dispatches
`theme-change`.
3. `ThemeRoot` renders `EditorialSite` unless `theme === "skeuo"`, in which case it
mounts `WindowsDesktop` instead. The editorial page stays mounted (hidden) while
the desktop is up so its scroll position survives; the desktop mounts on demand
(phones can never reach `skeuo`).
Themes: `skeuo` = KDE Plasma / Windows desktop, `terminal` = Gruvbox terminal.
Modes: `gruvbox` = the default editorial site, `skeuo` = KDE Plasma desktop.
The editorial page is **asset-free** apart from the social SVGs, which are used as
monochrome CSS masks (`.social-mask` in `editorial.css`). `linkedin.svg` is a filled
badge that masks to a plain square, so `editorial-ui.tsx` renders an inline mark for
it instead. The desktop still uses the image assets.
---
@ -180,9 +189,9 @@ Guard (already in place, keep it that way):
`e.target` is always the button.
### Related
`useDragOrder.ts` is a **different, simpler** hook used only by `MobileHome` for
reordering the phone app grid/dock (drag one item over another to swap). It is
list reordering, not free placement. Don't confuse the two.
`useDragOrder.ts` is a **different, simpler** hook used inside the desktop apps
for reordering lists (drag one item over another to swap). It is list reordering,
not free placement. Don't confuse the two.
---
@ -216,11 +225,12 @@ list reordering, not free placement. Don't confuse the two.
- Components are client components (`"use client"` at the top) — they use
pointer events and window APIs freely.
- CSS classes are prefixed by area: `win-` (desktop), `mobile-` (phone),
`app-` / `kickoff` / `terminal` etc. Grep by prefix to find styles.
- Several places deliberately use **imperative DOM work** (drag ghost,
transform-origin for the mobile launch animation). This is intentional for
performance; prefer matching the existing pattern over "Reactifying" it.
- CSS classes are prefixed by area: `win-` (desktop), `app-` / `kickoff` etc.
(desktop windows), plus the editorial design-system classes (`shell`, `display`,
`label`, `rule*`, `glass*`). Grep by prefix to find styles.
- Several places deliberately use **imperative DOM work** (e.g. the drag ghost).
This is intentional for performance; prefer matching the existing pattern over
"Reactifying" it.
- `setState` inside an effect is blocked by the ESLint config
(`react-hooks/set-state-in-effect`). The one intentional exception is the
grid init in `useDesktopIcons.ts`, which carries a scoped