From d947ecfb1ac8a09c2cd7f5102c263741bf18e2c9 Mon Sep 17 00:00:00 2001 From: Martin Dvorak Date: Wed, 30 Sep 2026 11:09:57 +0000 Subject: [PATCH] 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 --- README.md | 62 +- agents.md | 91 +- package-lock.json | 4 +- package.json | 2 +- src/app/components/MobileHome.tsx | 232 ----- src/app/components/SlBackground.tsx | 106 --- src/app/components/TerminalLayout.tsx | 603 ------------ src/app/components/ThemeRoot.tsx | 30 +- src/app/components/ThemeScript.tsx | 11 +- src/app/components/WebLayout.tsx | 386 ++++++++ src/app/components/WindowsDesktop.tsx | 8 +- src/app/components/apps.tsx | 15 +- src/app/components/useColorScheme.ts | 51 + src/app/components/useDragOrder.ts | 83 -- src/app/components/useTheme.ts | 21 +- src/app/data.ts | 3 - src/app/layout.tsx | 2 +- src/app/page.css | 1256 ------------------------- src/app/page.tsx | 2 +- src/app/skeuo.css | 587 +----------- src/app/web.css | 1126 ++++++++++++++++++++++ 21 files changed, 1705 insertions(+), 2976 deletions(-) delete mode 100644 src/app/components/MobileHome.tsx delete mode 100644 src/app/components/SlBackground.tsx delete mode 100644 src/app/components/TerminalLayout.tsx create mode 100644 src/app/components/WebLayout.tsx create mode 100644 src/app/components/useColorScheme.ts delete mode 100644 src/app/components/useDragOrder.ts delete mode 100644 src/app/page.css create mode 100644 src/app/web.css diff --git a/README.md b/README.md index d0a1674..18abd96 100644 --- a/README.md +++ b/README.md @@ -2,27 +2,35 @@ > The source code for my personal website, **[dvorakmortonxd.dev](https://dvorakmortonxd.dev)**. -This is not a normal portfolio page. It ships **two fully interactive shells** and picks -which one to show based on the saved theme and the device, all from the same content: +This is not a normal portfolio page. It ships **two shells** and picks which one to show +from the saved theme, all from the same content: -- **`skeuo`** (default) — 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. -- **`terminal`** — a **Gruvbox TUI** version of the site: TUI panels, an animated ASCII - train background, a clickable easter-egg logo, and a working in-browser terminal (Alacritty) - with commands like `help`, `ls`, `cat`, `neofetch`, `emerge`, `ssh` and more. +- **`web`** (default) — a light, frosted-glass **website**: a sticky sidebar (section nav, + light/dark switch, desktop switch) beside a column of glass cards — hero, quick tiles, + live services, activity heatmap, projects, hardware, features and friends. Light by + default with a persisted dark mode, and fully responsive, so phones and desktops get the + same shell. +- **`skeuo`** — an alternate KDE Plasma / Windows-style **desktop simulation** (desktop + only): draggable snap-to-grid icons, resizable/floating windows, a Start menu, taskbar and + live clock. Reachable from the web theme's sidebar switch. 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. +heatmap**, a gym SBD panel and links to friends' sites. The desktop shell additionally embeds +an animated ASCII train background, a clickable easter-egg logo and a working in-browser +terminal (Alacritty) with commands like `help`, `ls`, `cat`, `neofetch`, `emerge`, `ssh` and +more. ## ✨ Features -- **Two themes / shells** — KDE-style desktop and Gruvbox terminal, toggled from either UI - and persisted in `localStorage` (applied pre-paint to avoid a flash). -- **Phone vs. desktop detection** — a user-agent sniff on `` decides whether - the desktop or the mobile home screen is shown. +- **Two themes / shells** — a default frosted-glass **web** shell and an alternate KDE-style + **desktop** shell, toggled from either UI and persisted in `localStorage` (applied pre-paint + to avoid a flash). Phones always get the web shell. +- **Light / dark mode** — the web shell has a light/dark switch (light by default, remembered + in `localStorage`, applied pre-paint). The desktop shell keeps its own Breeze Dark look. +- **Glassmorphic web layout** — a sticky sidebar with section navigation beside a column of + frosted-glass panels (subtle gradients + `backdrop-filter` blur), reorganised into + hero / quick-tile / section cards. - **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. @@ -58,13 +66,13 @@ heatmap**, a gym SBD panel and links to friends' sites. - **Framework:** Next.js 16 (App Router, Turbopack) - **Language:** TypeScript, React 19 (client components everywhere interactivity lives) -- **Styling:** hand-written CSS — `src/app/page.css` (terminal theme), `src/app/skeuo.css` - (desktop + mobile), `src/app/globals.css` (base/reset). Tailwind v4 is imported for the - base layer but the UI is custom CSS. -- **Fonts:** Monocraft (self-hosted in `public/`, used by the terminal theme; the desktop - theme uses the system sans stack) +- **Styling:** hand-written CSS — `src/app/web.css` (web theme: frosted glass + light/dark), + `src/app/skeuo.css` (desktop theme), `src/app/globals.css` (base/reset). Tailwind v4 is + imported for the base layer but the UI is custom CSS. +- **Fonts:** the web theme uses the system sans stack; Monocraft (self-hosted in `public/`) is + used by the desktop theme's SMP menu and the in-desktop terminal. - **Images:** `next/image` -- **Version:** `2.1.0` (see `package.json`) +- **Version:** `2.2.0` (see `package.json`) ## 🚀 Running Locally @@ -167,8 +175,8 @@ src/app/ page.tsx → renders , imports the two theme stylesheets layout.tsx → html shell + metadata, injects ThemeScript globals.css → base/reset styles + Tailwind import - page.css → terminal theme styles - skeuo.css → desktop + mobile home styles + web.css → web theme styles (frosted glass, light + dark) + skeuo.css → desktop theme styles (KDE/Plasma shell) data.ts → socials, codingProjects, techProjects, friends, wallpapers revival-inc/page.tsx → embeds the bundled game at /public/revival-inc/index.html api/ @@ -176,21 +184,19 @@ src/app/ 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 + ThemeRoot.tsx → picks which shell to mount (web or desktop) + ThemeScript.tsx → pre-paint script: theme + light/dark scheme useTheme.ts → theme state via useSyncExternalStore - TerminalLayout.tsx → the Gruvbox terminal shell + useColorScheme.ts → light/dark state via useSyncExternalStore + WebLayout.tsx → default web shell: sidebar + glass content 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 dot ContributionHeatmap.tsx → heatmap + recent commits widget Clock.tsx → live clock JokerLogo.tsx → logo with click easter egg - SlBackground.tsx → animated ASCII train background scripts/ dev-server.mjs → custom dev server: live updates without the dev-only debug surface deploy/ diff --git a/agents.md b/agents.md index d947705..97825bd 100644 --- a/agents.md +++ b/agents.md @@ -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 , imports page.css + skeuo.css + page.tsx → renders , 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 `` 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 ``, 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 diff --git a/package-lock.json b/package-lock.json index 9b37bea..13c50b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dvorakmortonxd", - "version": "2.1.0", + "version": "2.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dvorakmortonxd", - "version": "2.1.0", + "version": "2.2.0", "dependencies": { "next": "^16.3.5", "react": "^19.3.0", diff --git a/package.json b/package.json index 0c02d90..ba678da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dvorakmortonxd", - "version": "2.1.0", + "version": "2.2.0", "private": true, "scripts": { "dev": "node scripts/dev-server.mjs", diff --git a/src/app/components/MobileHome.tsx b/src/app/components/MobileHome.tsx deleted file mode 100644 index f72f167..0000000 --- a/src/app/components/MobileHome.tsx +++ /dev/null @@ -1,232 +0,0 @@ -"use client"; - -import { - useEffect, - useRef, - useState, - useSyncExternalStore, - type CSSProperties, - type MouseEvent, -} from "react"; -import { APPS, APP_BY_ID } from "./apps"; -import Clock from "./Clock"; -import { useDragOrder } from "./useDragOrder"; -import { MOBILE_WALLPAPER } from "../data"; - -const DOCK_IDS = ["projects", "services", "socials", "contributions"]; - -function subscribe(callback: () => void) { - const id = window.setInterval(callback, 1000); - return () => window.clearInterval(id); -} - -/** Digital iOS-style clock widget: big time, small day/date underneath. */ -function HomeWidget() { - const now = useSyncExternalStore( - subscribe, - () => { - const d = new Date(); - return [ - d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }), - d.toLocaleDateString([], { weekday: "long", day: "numeric", month: "short" }), - ].join("\n"); - }, - () => "\n" - ); - const [time, day] = now.split("\n"); - - return ( -
-
- {time} -
-
- {day} -
-
- ); -} - -interface LaunchOrigin { - x: number; - y: number; - scale: number; -} - -export default function MobileHome() { - const [openId, setOpenId] = useState(null); - const [origin, setOrigin] = useState(null); - const [closing, setClosing] = useState(false); - const openApp = openId ? APP_BY_ID[openId] : null; - const closeTimer = useRef(null); - - const [gridApps, setGridApps] = useState(APPS); - const [dockApps, setDockApps] = useState(() => DOCK_IDS.map((id) => ({ id }))); - const gridDrag = useDragOrder(setGridApps); - const dockDrag = useDragOrder(setDockApps); - - const clearCloseTimer = () => { - if (closeTimer.current !== null) { - window.clearTimeout(closeTimer.current); - closeTimer.current = null; - } - }; - - // Fallback for when the close animation never reports completion (e.g. - // reduced-motion, throttled tabs or a missed animationend). Without this the - // full-screen app view can linger and swallow every tap on the home screen. - const closeApp = () => { - setClosing(true); - clearCloseTimer(); - closeTimer.current = window.setTimeout(() => { - setOpenId(null); - setClosing(false); - closeTimer.current = null; - }, 600); - }; - - useEffect(() => () => clearCloseTimer(), []); - - // Compute a transform-origin so the scaled-down app view sits exactly on top - // of the tapped icon, then let it grow to fill the screen (iOS style). - const launch = (appId: string, e: MouseEvent) => { - const rect = e.currentTarget.getBoundingClientRect(); - const icon = rect.width > 0 ? rect.width : rect.height; - // Guard against a zero/degenerate measured icon (e.g. hidden layout) so the - // scale tween never collapses to 0 and the open animation can always run. - const scale = icon > 0 ? Math.min(icon / window.innerWidth, 0.95) : 0.2; - const cx = rect.left + rect.width / 2; - const cy = rect.top + rect.height / 2; - const denom = 1 - scale || 1; - setOrigin({ - x: (cx - (scale * window.innerWidth) / 2) / denom, - y: (cy - (scale * window.innerHeight) / 2) / denom, - scale, - }); - setClosing(false); - setOpenId(appId); - }; - - const renderIcon = (appId: string, inDock: boolean, drag: ReturnType) => { - const app = APP_BY_ID[appId]; - if (!app) return null; - return ( - - ); - }; - - // The transform origin is exposed as a CSS variable (not an inline - // transform-origin) so only the opening keyframe uses the tapped icon as its - // pivot. The closing animation keeps a centred origin and always slides down. - const viewStyle = origin - ? ({ - "--app-origin": `${origin.x}px ${origin.y}px`, - "--app-scale": String(origin.scale), - } as CSSProperties) - : undefined; - - return ( -
-
-
- - - - -
- -
- - {gridApps.map((app) => renderIcon(app.id, false, gridDrag))} -
- -
- {dockApps.map((d) => renderIcon(d.id, true, dockDrag))} -
- - -
- - {openApp && ( -
{ - if (e.target !== e.currentTarget) return; - if (closing) { - clearCloseTimer(); - setOpenId(null); - setClosing(false); - } - }} - > -
- - {openApp.title} -
-
{openApp.content}
-
- )} -
- ); -} diff --git a/src/app/components/SlBackground.tsx b/src/app/components/SlBackground.tsx deleted file mode 100644 index 4f9ad36..0000000 --- a/src/app/components/SlBackground.tsx +++ /dev/null @@ -1,106 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; - -const TRAIN_ASCII = ` ==== ________ ___________ - _D _| |_______/ \\__I_I_____===__|_________| - |(_)--- | H\\________/ | | =|___ ___| _________________ _________________ _________________ _________________ _________________ _________________ - / | | H | | | | ||_| |_|| _| \\_____| \\_____| \\_____| \\_____| \\_____| \\_____A - | | | H |__--------------------| [___] | =| | | | | | | - | ________|___H__/__|_____/[][]~\\_______| | -| | | | | | | - |/ | |-----------I_____I [][] [] D |=======|____|________________________|_______________________|_______________________|_______________________|_______________________|_______________________|_ -__/ =| o |=-~~\\ /~~\\ /~~\\ /~~\\ ____Y___________|__|__________________________|_______________________|_______________________|_______________________|_______________________|_______________________| - |/-=|___|= || || || |_____/~\\___/ |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| - \\_/ \\O=====O=====O=====O_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/`; - -export default function SlBackground() { - const [pos, setPos] = useState(150); - - useEffect(() => { - let animationFrameId: number; - let lastTime = 0; - - const animate = (timestamp: number) => { - if (!lastTime) lastTime = timestamp; - const delta = timestamp - lastTime; - - // Update position every ~30ms to create a retro sliding terminal refresh feel - if (delta > 45) { - setPos((prev) => { - if (prev <= -150) return 150; - return prev - 1.5; - }); - lastTime = timestamp; - } - animationFrameId = requestAnimationFrame(animate); - }; - - animationFrameId = requestAnimationFrame(animate); - return () => cancelAnimationFrame(animationFrameId); - }, []); - - return ( - <> - -
-
-          {TRAIN_ASCII}
-        
-
- - ); -} - - - - - - - - - - diff --git a/src/app/components/TerminalLayout.tsx b/src/app/components/TerminalLayout.tsx deleted file mode 100644 index e48b482..0000000 --- a/src/app/components/TerminalLayout.tsx +++ /dev/null @@ -1,603 +0,0 @@ -import Image from "next/image"; -import type { CSSProperties } from "react"; -import SlBackground from "./SlBackground"; -import JokerLogo from "./JokerLogo"; -import ServiceButton from "./ServiceButton"; -import ContributionHeatmap from "./ContributionHeatmap"; -import { useTheme } from "./useTheme"; -import { socials, codingProjects, techProjects, friends } from "../data"; - -type PanelStyle = CSSProperties & { "--panel-color"?: string }; - -const panelStyle = (color: string, style: CSSProperties = {}): PanelStyle => ({ - ...style, - "--panel-color": color, -}); - -export default function TerminalLayout() { - const { toggle } = useTheme(); - - return ( - <> - -
-
-
- -

dvorakmortonxd.dev

- -
-
- -
-
- - README.MD - -

$ whoami

-

- Welcome my personal website! Here I show off my projects and hardware I own, - aswell as my self-hosted services :) -

-
- -
-
- - SOCIALS.JS - -
- {socials.map((s) => ( - - {s.name} - {s.name} - - ))} -
-
- mail if u ever wanna contact me:{" "} - dvorak@dvorakmortonxd.dev -
-
-
- -
-
- - GYM_SBD_AT_73KG_BW.DB - -
-
- Squat -
SQUAT
-
125kg
-
5.1.2025
-
-
- Bench -
BENCH
-
111kg
-
29.5.2026
-
-
- Deadlift -
DEADLIFT
-
165kg
-
17.12.2024
-
-
-
-
- -
-
- - NOW.TXT - -
-

- martin@dvorakmortonxd: - ~$ cat now.txt -

-
-

- I'm currently hosting an SMP (dm me on discord). Also I'm pretty - much cooking daily, because im bulking through out the non-summer seasons. - And me and my pal Pepa are working on an - education app so we can study more efficiently, It's called{" "} - maturita.c! -

-
-
-
-
- -
-
- - README.MD - -
-

- martin@dvorakmortonxd: - ~$ cat about_me.md -

-
-

- Hey, I'm Martin Dvořák — a 17(soon to be 18)-year-old IT student based - in the Czech Republic, specializing in network management and - administration at sssvt.cz. I'm a - sophomore and I have wanted to go to this school since 7th grade (2022) so - I'm happy I got in in 2025, the school is pretty cool so if you are in - middle school and interested in IT, make sure to check it out. I'm a - homelabber and developer heavily focused on self-hosting{" "} - literally everything, writing code (for example games for gamejams), - tinkering with GNU/Linux or Musl/Linux systems and retro hardware. I own a - small collection of laptops ranging from 2009 to 2014 (and ofcourse my 2025 - macbook), most of these found in ewaste. In my free time I mostly spend - time with my gf {"<"}3, I also spend a bit of time setting up my self - hosted stuff (like jellyfin or gitdxd), and also when I'm in my gaming - mood I play terraria (around 1300hr playtime btw) and minecraft modpacks - with mekanism and/or create. I also go to the gym around 3-5 times a week, - running a PPL split focused on strength/powerlifting and especially SBD. I - take a big focus on my health and physique. - -

-
-
-
-
- -
-
- - SERVICES.HTML - -
- - - - - - -
-
-
- -
-
- - GITDXD.GIT - -
-

- GitDXD -

-

- My Self-hosted Git Instance -

-
- GitDXD Preview -
-
-

- My very own forgejo git instance, originally started as a private git - instance for me and my friends to boycott GitHub but I've made it - public now so. Join at{" "} - - git.dvorakmortonxd.dev - - . -

-
-
-
-
- -
-
- - CONTRIBUTIONS@GITDXD.GIT - -
- -
-
-
- -
-
- - REVIVAL.INC - -
-

- Revival inc. -

-
- revival inc thumbnail -
-

- What is it? -

-
-

- It's a game I made for the game jam "Mini Jame Gam #55", the - theme was Necromancy and the special item was Milk, the game also includes - a tutorial so if you wanna try it do it. Check it out at{" "} - - itch.io - {" "} - or play{" "} - - here - - . -

-
-
-
-
- -
-
- - RUSTYWOLF.RS - -
-

- Introducing RustyWolf -

-
- RustyWolf Browser Preview -
-

- What makes it so cool??? -

-
-

- A lightweight browser made in Rust with WebKitGTK. It's the successor - to my previous project Venturesome, aiming to provide a fast and minimalist - browsing experience. It's a perfect browser for old machines that - struggle with modern browsers. Check it out at{" "} - - - GitDXD - - - . -

-
-
-
-
-
- -
-
-
- - TECH_LIST.JSON - -

- All my hardware equipment :) -

-
- {techProjects.map((project) => ( -
-
-

{project.title}

-

{project.desc}

-
- ))} -
-
-
- -
-
- - PROJECTS.DOCX - -

- All the stuff I've worked on :)) -

-
- {codingProjects.map((project, idx) => ( - - -
-
- -
-
- - FRIENDS.TSX - -

- Friend's websites and projects -

-
- {friends.map((friend, idx) => ( - - } - name={friend.name} - desc={friend.desc} - /> - ))} -
-
-
-
- -
- v2.1.0 - © 2025-2026 Martin Dvořák. All rights reserved (not really I dont care, just follow - the GPL3.0 License) -
-
- - ); -} diff --git a/src/app/components/ThemeRoot.tsx b/src/app/components/ThemeRoot.tsx index 8b49383..d4086a2 100644 --- a/src/app/components/ThemeRoot.tsx +++ b/src/app/components/ThemeRoot.tsx @@ -1,32 +1,16 @@ "use client"; import { useTheme } from "./useTheme"; -import TerminalLayout from "./TerminalLayout"; import WindowsDesktop from "./WindowsDesktop"; -import MobileHome from "./MobileHome"; +import WebLayout from "./WebLayout"; export default function ThemeRoot() { const { theme } = useTheme(); - const terminal = theme === "terminal"; - // The desktop is kept mounted (just hidden) while the Gruvbox/terminal theme - // is active. That way the Welcome window is only created on a fresh page - // load and isn't reopened by toggling themes. - return ( - <> -
- -
-
- -
- {terminal && } - - ); + // The desktop shell is mounted lazily, only while it is selected. Its icon + // grid is measured from real rendered boxes, so hiding it with `display:none` + // would measure zero-sized cells and break the grid when it comes back. + // Phones never see the desktop shell at all — they always get the web theme. + if (theme === "skeuo") return ; + return ; } diff --git a/src/app/components/ThemeScript.tsx b/src/app/components/ThemeScript.tsx index 2546edf..3780e42 100644 --- a/src/app/components/ThemeScript.tsx +++ b/src/app/components/ThemeScript.tsx @@ -3,11 +3,12 @@ import { useRef } from "react"; import { useServerInsertedHTML } from "next/navigation"; -// Runs before paint so the saved theme and the phone/desktop split are applied -// without a flash. It is injected with useServerInsertedHTML so the