# 👾 dvorakmortonxd.dev > The source code for my personal website, **[dvorakmortonxd.dev](https://dvorakmortonxd.dev)**. This is not a normal portfolio page. It ships a hand-built **skeuomorphic console** plus a **phone-only desktop simulation**, both rendering the same content: - **`skeuo`** (default) — the site itself: a walnut-and-aluminium home-lab console on a workshop bench. Every section is a physical object — an engraved nameplate, an aluminium patch bay, analogue load meters for the gym numbers, a clipboard, a leather logbook, a rack of service modules with live pilot lamps, a framed screenshot, a chart recorder, a game cartridge, a software box, a pegboard of manila tags and a card-index drawer. Materials are CSS + inline-SVG noise only: the theme ships **no images of its own**. One light source (top-left), layered shadows for raised/recessed/embossed/stitched parts, and a deliberate two-font pairing: a condensed engraved panel face for metal, a typewriter for anything on paper. - **`desktop`** (phone-width viewports only) — a KDE Plasma / Windows-style **desktop simulation**: draggable snap-to-grid icons, resizable/floating windows, a Start menu, taskbar and live clock. On phones it becomes an **iOS-style home screen** with a widget grid, dock, drag-to-reorder and an iOS launch animation. The desktop simulation is only offered at ≤ 768 px: on anything wider the switch is hidden and the page always renders `skeuo`, so a desktop visitor can never end up stuck in a mode whose switch they cannot reach. Both shells surface the same personal content: socials, self-hosted services with **live up/down health checks**, a hardware collection, coding projects, a **GitDXD contribution heatmap**, a gym SBD panel and links to friends' sites. ## ✨ Features - **Two shells** — the skeuo console (default everywhere) and the KDE-style desktop simulation (phone-width viewports only, flipped by the physical switch on the nameplate). The choice is persisted in `localStorage` under `dmx-theme` and applied pre-paint to avoid a flash; legacy `theme` values are migrated once. - **Desktop mode is phone-only** — at ≤ 768 px the switch appears and both shells are reachable; at 768 px and above it is hidden and any saved `desktop` preference is overridden, so wide viewports can never get stuck. - **CSS-only materials** — wood, brushed aluminium, brass, leather, black bakelite, paper, glass and rubber are built from gradients, `color-mix()` and an inline-SVG `feTurbulence` grain; no texture images ship with the theme. - **Phone vs. desktop detection** — a user-agent sniff on `` decides whether the desktop or the mobile home screen is shown. - **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:** hand-written CSS — `src/app/skeuo.css` (the skeuo console: materials, objects, depth), `src/app/desktop.css` (KDE desktop + mobile home), `src/app/shared.css` (styles for components both shells render: service buttons, heatmap, project cards, logo) and `src/app/globals.css` (base/reset). Tailwind v4 is imported for the base layer but the UI is custom CSS. - **Fonts:** Barlow Semi Condensed (engraved panel labels, headings, controls) + Courier Prime (everything printed or typewritten), both via `next/font/google` with explicit fallback stacks. Monocraft stays self-hosted in `public/` for the desktop's terminal windows. - **Images:** `next/image` - **Version:** `2.1.0` (see `package.json`) ## 🚀 Running Locally Requires **Node.js >= 20.9.0** (Next.js 16). ```bash git clone https://git.dvorakmortonxd.dev/dvorakmortonxd/dvorakmortonxd.dev cd dvorakmortonxd.dev npm install npm run dev ``` Then open [http://localhost:3000](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`: ```bash ./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 ```bash 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 `, `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`](./deploy/dvorakmortonxd-dev.system.service): ```bash # 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`](./deploy/dvorakmortonxd-dev.service) is an equivalent **user** unit you can install without root: ```bash 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 `` 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 , imports the three stylesheets layout.tsx → html shell + metadata, loads the two fonts, injects ThemeScript globals.css → base/reset styles + Tailwind import skeuo.css → the skeuo console: materials, objects, depth desktop.css → KDE desktop + mobile home styles shared.css → styles for components both shells render data.ts → socials, codingProjects, techProjects, friends, wallpapers 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 ping/route.ts → service health check (HTTP + Minecraft) components/ ThemeRoot.tsx → picks which shell(s) to mount ThemeScript.tsx → pre-paint script: theme + device detection useTheme.ts → theme state via useSyncExternalStore SkeuoLayout.tsx → the skeuo console shell WindowsDesktop.tsx → desktop shell: icons, windows, taskbar, Start menu MobileHome.tsx → phone shell: iOS home screen + app view 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 (mobile grid/dock) ServiceButton.tsx → service link + live status lamp ContributionHeatmap.tsx → 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`](./DEPLOYMENT.md). ## 📬 Contact To see my projects or get in touch, visit the live site or email me at [dvorak@dvorakmortonxd.dev](mailto:dvorak@dvorakmortonxd.dev). ## 📄 License GPL-3.0-only — see [`LICENSE`](./LICENSE).