The code for the website https://dvorakmortonxd.dev
  • HTML 56.5%
  • TypeScript 25%
  • CSS 16.3%
  • JavaScript 1.2%
  • Shell 1%
Find a file
2026-09-30 09:59:00 +00:00
deploy run the dev server as a system-wide systemd unit (boots without login/linger) 2026-09-29 15:46:31 +00:00
public redraw the desk as cartoon skeuomorphism: CSS-only materials (no textures), light cream desk, per-item hues, cork board, receipt, scoreboard 2026-09-30 09:59:00 +00:00
scripts secure dev tunnel: strip /__nextjs debug endpoints while keeping HMR 2026-09-24 20:31:37 +00:00
src redraw the desk as cartoon skeuomorphism: CSS-only materials (no textures), light cream desk, per-item hues, cork board, receipt, scoreboard 2026-09-30 09:59:00 +00:00
.gitignore small changes regarding my proxmox maintenance checks 2026-09-21 00:02:54 +02:00
agents.md redraw the desk as cartoon skeuomorphism: CSS-only materials (no textures), light cream desk, per-item hues, cork board, receipt, scoreboard 2026-09-30 09:59:00 +00:00
DEPLOYMENT.md full on update with new npm and instructions and maintenance update 2026-09-13 22:11:06 +02:00
eslint.config.mjs full on update with new npm and instructions and maintenance update 2026-09-13 22:11:06 +02:00
install-dependencies-deb.sh script support for doas added (actually now) 2026-09-24 07:12:14 +00:00
LICENSE Initial commit 2026-05-13 13:17:17 +02:00
next.config.ts secure dev tunnel: strip /__nextjs debug endpoints while keeping HMR 2026-09-24 20:31:37 +00:00
package-lock.json version bump, 2.1.0, some bug fixes and animations and a full on working terminal (WiP) 2026-09-23 14:40:21 +02:00
package.json redraw the desk as cartoon skeuomorphism: CSS-only materials (no textures), light cream desk, per-item hues, cork board, receipt, scoreboard 2026-09-30 09:59:00 +00:00
postcss.config.mjs first commit/push 2026-05-13 13:22:58 +02:00
README.md redraw the desk as cartoon skeuomorphism: CSS-only materials (no textures), light cream desk, per-item hues, cork board, receipt, scoreboard 2026-09-30 09:59:00 +00:00
tsconfig.json full on update with new npm and instructions and maintenance update 2026-09-13 22:11:06 +02:00

👾 dvorakmortonxd.dev

The source code for my personal website, dvorakmortonxd.dev.

It's a cartoon-skeuomorphic personal site: a warm cream desk with chunky, brightly coloured hardware resting on it, drawn entirely in CSS — no textures, no images — with the whole KDE desktop simulation kept behind a physical power switch. Two modes, one content set:

  • Web (default) — the desk. A monitor carries the headline with a candy-keycap keyboard in front of it and a mini rack beside it; services are 1U rack units with coloured strips and health LEDs, the 18 coding projects are labelled drives in a drawer, the machines are cards pinned to a cork board, the contribution heatmap is printed on a receipt, friends are business cards, the gym numbers are a scoreboard and the socials are a patch panel with coloured ports. /credits lists the type and nothing else — there are no asset licences because there are no assets.
  • Desktop mode — the KDE Plasma / Windows-style desktop simulation: draggable snap-to-grid icons, resizable/floating windows, a Start menu, taskbar, live clock and a working in-browser terminal (Alacritty). Reached from the power switch and hidden on phones.

Both modes surface the same personal content: socials, self-hosted services with live up/down health checks, a hardware collection, coding projects, a GitDXD contribution heatmap, gym SBD and links to friends' sites.

✨ Features

  • Web theme is the landing view — the site always opens on the desk; the theme is deliberately not persisted, so a reload never drops you into the desktop.
  • CSS-only skeuomorphism — every surface is a flat gradient with a thick bottom edge, a soft cast shadow and color-mix() tints: slate chassis, cream plastic, paper, cork, black glass, LEDs, screws, pushpins. The web theme ships no images at all.
  • Not afraid of colour — a hue per item (orange / teal / blue / lilac / mint / coral / yellow) cycles through every list, so the desk reads as separate pieces of hardware instead of one grey wall.
  • Desktop mode (desktop-only) — a physical power switch in the top rail (and in the footer) flips to the KDE-style desktop for the session. Phones never get it: the switch is hidden, so the old iOS-style phone home screen is gone.
  • Draggable desktop icons — free placement that snaps to a measured grid, one icon per cell, with a follow-the-cursor drag ghost. Icons shrink on cramped viewports so they never collide.
  • Multi-window manager — draggable, resizable, minimizable, maximizable, focus/z-order and animated open/close (with reduced-motion support). The taskbar shrinks its window buttons as more apps open and scrolls when they still don't fit, so every open app stays reachable. Windows are clamped to the viewport so they never open off-screen on a small monitor and can't be dragged off any edge.
  • Working terminal app — command input with history (↑/↓, Ctrl+L to clear) and a hidden ssh easter egg.
  • Live service status — each self-hosted service is health-checked through the site's own API; Minecraft is checked through mcstatus.io.
  • Embedded live services — opening a frameable self-hosted service (Llama.cpp/Open WebUI, Jellyfin, Navidrome, GitDXD) from the desktop shows the real site inside the window (an iframe). A service must not send a restrictive X-Frame-Options: Navidrome needs ND_HTTPHEADERS_FRAMEOPTIONS=ALLOWALL and Forgejo needs [cors] X_FRAME_OPTIONS = unset. A spinner covers the frame while the site boots. Discord sends DENY and keeps a link card instead.
  • Friend sites in windows — clicking a friend's desktop icon opens their site in a window. bukovinafilip.com and vlcak.me send no framing headers, so they load directly; skopec.dev sends X-Frame-Options: SAMEORIGIN, so it loads through /api/frame, a header-stripping proxy that serves the live page from our origin. The www button always opens the real site.
  • Minecraft-style SMP window — the SMP shortcut opens a join menu modelled on Minecraft's multiplayer screen, with live status, player count, MOTD and version pulled from mcstatus.io through the site's own ping API.
  • Live GitDXD stats — contribution heatmap + latest commit activity proxied server-side from the self-hosted Forgejo instance.
  • Extras — a playable Revival Inc. game embedded at /revival-inc, llms.txt, robots.txt and a sitemap.xml.

🛠️ Tech Stack

  • Framework: Next.js 16 (App Router, Turbopack)
  • Language: TypeScript, React 19 (client components everywhere interactivity lives)
  • Styling: src/app/globals.css holds the palette tokens, base styles, the Tailwind v4 @theme/@utility classes and the scroll reveals. src/app/web.css is the whole desk: the materials (slate chassis, cream plastic, paper, cork, black glass), the details (screws, LEDs, pushpins, rack rails, barcode, torn receipt edge) and every object layout, all CSS with color-mix() for tints and shades. page.css keeps the shared component styles the desktop apps still use, and skeuo.css is the KDE desktop.
  • Fonts: Nunito for hardware labels and headings, Newsreader for anything printed on paper, IBM Plex Sans/Mono for body copy and terminal text — all via next/font/google. The desktop keeps its system sans stack.
  • Assets: the web theme ships no images. The desktop keeps its own wallpaper and icons.
  • Version: 4.0.0 (see package.json)

🚀 Running Locally

Requires Node.js >= 20.9.0 (Next.js 16).

git clone https://git.dvorakmortonxd.dev/dvorakmortonxd/dvorakmortonxd.dev
cd dvorakmortonxd.dev
npm install
npm run dev

Then open http://localhost:3000.

One-shot setup on Debian/Ubuntu

install-dependencies-deb.sh installs the system packages, Node.js LTS (>= 20.9.0) and npm, then runs npm install and npm run build:

./install-dependencies-deb.sh              # system deps + npm install + build
./install-dependencies-deb.sh --no-build   # system deps + npm install only
./install-dependencies-deb.sh --no-npm     # system packages only

Scripts

npm run dev      # node scripts/dev-server.mjs (dev + live updates)
npm run dev:next # next dev --turbopack (raw dev server, unprotected)
npm run build    # next build --turbopack
npm run start    # next start (production)
npm run lint     # eslint

Handy checks before shipping: npx tsc --noEmit, npx eslint <files>, npm run build.

Testing from a phone on your LAN

next.config.ts sets allowedDevOrigins so the dev server can be reached from other devices on the local network and from the beta.dvorakmortonxd.dev tunnel. Without it Next.js rejects the dev asset requests and the HMR WebSocket, which leaves the page un-hydrated (stacked icons, dead buttons).

A plain next dev server is meant for localhost and exposes unauthenticated debug endpoints (/__nextjs* — open-a-file-in-editor, stack frames, source maps, debugger attach, restart) that you don't want on a tunnel. npm run dev therefore runs scripts/dev-server.mjs, a custom server that keeps the app and live updates (HMR / Fast Refresh) but returns 404 for those endpoints and *.map for any request that isn't a direct localhost connection. Localhost keeps the full dev experience; the LAN and tunnel get a plain, live-reloading site. To force the raw server instead, use npm run dev:next (don't point a tunnel at it).

Running it as a service (survives reboot)

On this host the dev server runs as a system systemd unit, so it starts at boot on its own — no login or linger needed. The unit lives in the repo at deploy/dvorakmortonxd-dev.system.service:

# installs /etc/systemd/system/dvorakmortonxd-dev.service and enables it
sudo bash deploy/install-system-service.sh   # or: doas bash deploy/install-system-service.sh

Manage it with systemctl status|restart|stop dvorakmortonxd-dev and follow logs with journalctl -u dvorakmortonxd-dev -f. It shares .next/dev/lock with a manual npm run dev, so stop the service before starting one by hand.

No root? Use the per-user unit instead

deploy/dvorakmortonxd-dev.service is an equivalent user unit you can install without root:

mkdir -p ~/.config/systemd/user
cp deploy/dvorakmortonxd-dev.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now dvorakmortonxd-dev.service
loginctl enable-linger "$USER"          # once, so it starts at boot

Drive it with systemctl --user … and journalctl --user -u …. Run only one of the two — they contend for :3000 and .next/dev/lock.

📡 API Routes

Route Method What it does
/api/heatmap GET Proxies the user's contribution heatmap from git.dvorakmortonxd.dev.
/api/activities GET Fetches the GitDXD activity feed and keeps only the user's own events.
/api/frame?url=… GET Header-stripping proxy for a friend's site that refuses framing (skopec.dev). Allow-listed hosts only; fetches the HTML server-side (so the site's X-Frame-Options never reaches the browser), injects a <base> for relative assets and a click shim for in-site links. Rejects other hosts, non-http(s) schemes and credentials, and never follows redirects.
/api/ping?url=… GET Health-checks an allow-listed service (the site's own *.dvorakmortonxd.dev hosts and the friends' sites). Handles minecraft://host:port via mcstatus.io; treats any non-5xx response as "up". Other hosts and schemes are rejected and redirects are never followed, so it can't be used to probe the LAN (SSRF guard).

📁 Project Structure

src/app/
  page.tsx                → renders <ThemeRoot/>, imports the theme stylesheets
  layout.tsx              → html shell + metadata; loads the fonts; injects ThemeScript
  globals.css             → palette tokens, base, Tailwind @theme/@utility, scroll reveals
  web.css                 → skeuomorphic material system + object layouts
  page.css                → shared component styles still used by the desktop apps
  skeuo.css               → KDE desktop styles
  data.ts                 → socials, codingProjects, techProjects, friends, wallpapers
  credits/page.tsx        → credit page (type only — the desk ships no assets)
  revival-inc/page.tsx    → embeds the bundled game at /public/revival-inc/index.html
  api/
    heatmap/route.ts      → contribution heatmap proxy
    activities/route.ts   → GitDXD activity feed proxy
    frame/route.ts        → header-stripping proxy for unframeable friend sites
    ping/route.ts         → service health check (HTTP + Minecraft)
  components/
    ThemeRoot.tsx           → web desk vs. Desktop mode
    ThemeScript.tsx         → pre-paint script: device detection
    useTheme.ts             → theme state via useSyncExternalStore
    WebSite.tsx             → the desk: rack hero, drawer, pegboard, patch panel
    web-ui.tsx              → rack units, LED dashboard, social masks, Reveal
    WindowsDesktop.tsx      → desktop shell: icons, windows, taskbar, Start menu
    WinWindow.tsx           → one draggable/resizable window
    apps.tsx                → APPS registry, window contents, in-app terminal
    useDesktopIcons.ts      → desktop icon grid + drag/drop logic
    useDragOrder.ts         → list reordering (used inside apps)
    ServiceButton.tsx       → desktop service link + live status dot
    ContributionHeatmap.tsx → desktop heatmap + recent commits widget
    Clock.tsx               → live clock
    JokerLogo.tsx           → logo with click easter egg
scripts/
    dev-server.mjs          → custom dev server: live updates without the dev-only debug surface
deploy/
    dvorakmortonxd-dev.system.service → systemd unit (system-wide) so the dev server starts at boot
    dvorakmortonxd-dev.service        → equivalent per-user unit (no root; needs linger)
    install-system-service.sh         → installs the system unit (run with sudo/doas)
public/                     → fonts, wallpapers, icons, /revival-inc game, llms.txt, robots.txt, sitemap.xml

🌍 Deploying?

In order to deploy the website you'd want to set up a reverse proxy and something that runs it as a service. Me personally, I use cloudflared and pm2. You could also use something like Vercel, but I prefer self-hosting. Also, if you want to deploy it, you gotta change it enough so that you don't directly copy my website, and also follow the GPL3.0-only license, meaning you gotta make your code open source too.

Detailed step-by-step production instructions for PM2 + npm on Debian and Alpine Linux (including the Cloudflare Tunnel / Nginx reverse-proxy setup) are in DEPLOYMENT.md.

📬 Contact

To see my projects or get in touch, visit the live site or email me at dvorak@dvorakmortonxd.dev.

📄 License

GPL-3.0-only — see LICENSE.