rename terminal theme to web, make it the default, drop the mobile home screen, and rebuild the web shell as a light/dark glassmorphism layout

This commit is contained in:
Martin Dvořák 2026-09-30 11:09:57 +00:00
commit d947ecfb1a
21 changed files with 1707 additions and 2978 deletions

View file

@ -34,15 +34,16 @@ 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.
it ships **two shells** and renders whichever the saved theme selects: a default
frosted-glass **web** site, and an alternate KDE/Plasma **desktop simulation**
(desktop-only). Phones always get the web shell.
- Framework: **Next.js 16** (App Router, Turbopack), **React 19**, TypeScript.
- Styling: plain CSS files (`page.css`, `skeuo.css`), no CSS-in-JS, no Tailwind
- Styling: plain CSS files (`web.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).
- Path alias: `@/*` → `src/*`.
- Version: `2.1.0` (see `package.json`).
- Version: `2.2.0` (see `package.json`).
### Scripts
```bash
@ -73,12 +74,12 @@ 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
page.tsx → renders <ThemeRoot/>, imports web.css + skeuo.css
layout.tsx → html shell, metadata, ThemeScript
globals.css → base/reset styles + Tailwind import
data.ts → socials, codingProjects, techProjects, friends, wallpapers
page.css → base / terminal theme styles
skeuo.css → Windows/Plasma desktop + mobile home styles
web.css → web theme: frosted glass, light + dark tokens
skeuo.css → Windows/Plasma desktop styles (desktop theme only)
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 +87,62 @@ 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 → picks which shell to mount (the router of the UI)
WebLayout.tsx → "web" theme (default): sticky sidebar + glass content
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)
useTheme.ts → theme state via useSyncExternalStore
ThemeScript.tsx → inline pre-hydration script: theme + device detection
useTheme.ts → theme state (web | skeuo) via useSyncExternalStore
useColorScheme.ts → light/dark scheme state via useSyncExternalStore
ThemeScript.tsx → inline pre-hydration script: theme + scheme
ServiceButton.tsx → service link + live status dot (pings /api/ping)
ContributionHeatmap.tsx → 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` (`web` | `skeuo`) on `<html>`, defaulting to `web`; it also reads
`localStorage.scheme` and sets `data-scheme` (`light` | `dark`), defaulting to
`light`. There is no device sniff anymore.
2. `useTheme()` (in `useTheme.ts`) reads `data-theme` via `useSyncExternalStore`
and dispatches a `theme-change` event on toggle. `useColorScheme()` does the
same for `data-scheme` via a `scheme-change` event.
3. `ThemeRoot` mounts **one** shell at a time: `WebLayout` unless the theme is
`skeuo`, in which case it mounts `WindowsDesktop`. It is deliberately *not*
kept mounted while hidden — the desktop icon grid is measured from real
rendered boxes, so a `display:none` desktop would measure zero-sized cells and
break its grid when shown again.
Themes: `skeuo` = KDE Plasma / Windows desktop, `terminal` = Gruvbox terminal.
Themes: `web` = default frosted-glass website (sidebar + content, light/dark),
`skeuo` = KDE Plasma desktop simulation (desktop-only; the web sidebar's
"Desktop" control switches to it, and it has a "Web" control in its tray).
### Web theme + light/dark (v2.2.0 rework)
- `WebLayout.tsx` is the default shell. Layout is a **sticky sidebar** (brand,
section nav, light/dark switch, desktop switch, contact/clock) beside a single
content column of `.glass` panels (hero, quick tiles, services, activity,
projects, hardware, features, friends). The old stacked TUI panel layout and
the `SlBackground` ASCII train were removed with it.
- Glassmorphism lives in one utility class, `.glass`, in `web.css`: a subtle
`linear-gradient` over `--glass-hi`/`--glass-lo` with
`backdrop-filter: blur(20px) saturate(165%)`. The page blobs behind the panels
are `.web-bg` (a fixed radial-gradient layer at `z-index: 0`; `.web-root` sits
at `z-index: 1`).
- Colours are tokenised in `web.css` under `:root` (light) and
`html[data-theme="web"][data-scheme="dark"]` (dark). The shared token names
(`--fg`, `--gray`, `--accent`, `--green`, …) are reused so `ServiceButton` and
`ContributionHeatmap` need no changes. `skeuo.css` overrides the same names
while the desktop theme is active, so the two themes don't bleed into each
other.
- The desktop switch (`.web-ctl-desktop`) is hidden on phones: under the
`max-width: 960px` breakpoint and under `(hover: none) and (pointer: coarse)`.
There is no device sniff anymore — phones just get the responsive web shell.
---
@ -180,9 +205,8 @@ 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.
Nothing else consumes the desktop grid. `apps.tsx` is the only other place that
knows about icon ids, via `APP_BY_ID`.
---
@ -216,11 +240,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: `web-` (web theme), `glass` (the shared glass
panel utility), `win-` (desktop), `app-` / `kickoff` etc. Grep by prefix to
find styles.
- Several places deliberately use **imperative DOM work** (the desktop 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
@ -326,6 +351,10 @@ list reordering, not free placement. Don't confuse the two.
## Testing notes
- There is **no test suite**. Verification is `tsc --noEmit`, `eslint`, and
`next build`.
- The default shell is the web theme: `curl -s http://localhost:3000/` should
contain `data-theme="web"` / `data-scheme="light"` and the `.web-root` markup.
Theme + scheme are read pre-paint from `localStorage.theme` / `.scheme`, so a
quick check is to set those in devtools and reload.
- A dev server may already be running on port 3000 — on this host it is normally
the **system** `dvorakmortonxd-dev` service, so check
`systemctl status dvorakmortonxd-dev` first. Two dev servers on this repo