turn the terminal/Gruvbox theme into the skeuo console
- rename the theme everywhere: id terminal -> skeuo, desktop sim skeuo -> desktop, skeleton terminal shell -> SkeuoLayout, page.css -> shared.css (shared components), old skeuo.css -> desktop.css, storage key theme -> dmx-theme (with migration) - rebuild the theme as a walnut-and-aluminium home-lab console: brass plates, patch bay, analogue gym gauges, clipboard, stitched leather logbook, service rack with pilot lamps, framed print, chart recorder, cartridge, software box, pegboard tags, card-index drawer - all materials are CSS + inline-SVG noise (no texture images); Barlow Semi Condensed + Courier Prime via next/font with fallbacks - desktop mode is now phone-only (<=768px): switch hidden above that, stored desktop preference overridden, sim never mounted on wide viewports - drop the ASCII train background and its gruvbox.webp asset
This commit is contained in:
parent
b2244da743
commit
d3f953e4a2
17 changed files with 4439 additions and 2834 deletions
66
README.md
66
README.md
|
|
@ -2,16 +2,25 @@
|
||||||
|
|
||||||
> The source code for my personal website, **[dvorakmortonxd.dev](https://dvorakmortonxd.dev)**.
|
> 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
|
This is not a normal portfolio page. It ships a hand-built **skeuomorphic console** plus a
|
||||||
which one to show based on the saved theme and the device, all from the same content:
|
**phone-only desktop simulation**, both rendering the same content:
|
||||||
|
|
||||||
- **`skeuo`** (default) — a KDE Plasma / Windows-style **desktop simulation**: draggable
|
- **`skeuo`** (default) — the site itself: a walnut-and-aluminium home-lab console on a workshop
|
||||||
snap-to-grid icons, resizable/floating windows, a Start menu, taskbar and live clock.
|
bench. Every section is a physical object — an engraved nameplate, an aluminium patch bay,
|
||||||
On phones it becomes an **iOS-style home screen** with a widget grid, dock, drag-to-reorder
|
analogue load meters for the gym numbers, a clipboard, a leather logbook, a rack of service
|
||||||
and an iOS launch animation.
|
modules with live pilot lamps, a framed screenshot, a chart recorder, a game cartridge, a
|
||||||
- **`terminal`** — a **Gruvbox TUI** version of the site: TUI panels, an animated ASCII
|
software box, a pegboard of manila tags and a card-index drawer. Materials are CSS + inline-SVG
|
||||||
train background, a clickable easter-egg logo, and a working in-browser terminal (Alacritty)
|
noise only: the theme ships **no images of its own**. One light source (top-left), layered
|
||||||
with commands like `help`, `ls`, `cat`, `neofetch`, `emerge`, `ssh` and more.
|
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
|
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
|
up/down health checks**, a hardware collection, coding projects, a **GitDXD contribution
|
||||||
|
|
@ -19,8 +28,16 @@ heatmap**, a gym SBD panel and links to friends' sites.
|
||||||
|
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
- **Two themes / shells** — KDE-style desktop and Gruvbox terminal, toggled from either UI
|
- **Two shells** — the skeuo console (default everywhere) and the KDE-style desktop simulation
|
||||||
and persisted in `localStorage` (applied pre-paint to avoid a flash).
|
(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 `<html data-device>` decides whether
|
- **Phone vs. desktop detection** — a user-agent sniff on `<html data-device>` decides whether
|
||||||
the desktop or the mobile home screen is shown.
|
the desktop or the mobile home screen is shown.
|
||||||
- **Draggable desktop icons** — free placement that snaps to a measured grid, one icon per
|
- **Draggable desktop icons** — free placement that snaps to a measured grid, one icon per
|
||||||
|
|
@ -58,11 +75,14 @@ heatmap**, a gym SBD panel and links to friends' sites.
|
||||||
|
|
||||||
- **Framework:** Next.js 16 (App Router, Turbopack)
|
- **Framework:** Next.js 16 (App Router, Turbopack)
|
||||||
- **Language:** TypeScript, React 19 (client components everywhere interactivity lives)
|
- **Language:** TypeScript, React 19 (client components everywhere interactivity lives)
|
||||||
- **Styling:** hand-written CSS — `src/app/page.css` (terminal theme), `src/app/skeuo.css`
|
- **Styling:** hand-written CSS — `src/app/skeuo.css` (the skeuo console: materials, objects,
|
||||||
(desktop + mobile), `src/app/globals.css` (base/reset). Tailwind v4 is imported for the
|
depth), `src/app/desktop.css` (KDE desktop + mobile home), `src/app/shared.css` (styles for
|
||||||
base layer but the UI is custom CSS.
|
components both shells render: service buttons, heatmap, project cards, logo) and
|
||||||
- **Fonts:** Monocraft (self-hosted in `public/`, used by the terminal theme; the desktop
|
`src/app/globals.css` (base/reset). Tailwind v4 is imported for the base layer but the UI is
|
||||||
theme uses the system sans stack)
|
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`
|
- **Images:** `next/image`
|
||||||
- **Version:** `2.1.0` (see `package.json`)
|
- **Version:** `2.1.0` (see `package.json`)
|
||||||
|
|
||||||
|
|
@ -164,11 +184,12 @@ they contend for `:3000` and `.next/dev/lock`.
|
||||||
|
|
||||||
```
|
```
|
||||||
src/app/
|
src/app/
|
||||||
page.tsx → renders <ThemeRoot/>, imports the two theme stylesheets
|
page.tsx → renders <ThemeRoot/>, imports the three stylesheets
|
||||||
layout.tsx → html shell + metadata, injects ThemeScript
|
layout.tsx → html shell + metadata, loads the two fonts, injects ThemeScript
|
||||||
globals.css → base/reset styles + Tailwind import
|
globals.css → base/reset styles + Tailwind import
|
||||||
page.css → terminal theme styles
|
skeuo.css → the skeuo console: materials, objects, depth
|
||||||
skeuo.css → desktop + mobile home styles
|
desktop.css → KDE desktop + mobile home styles
|
||||||
|
shared.css → styles for components both shells render
|
||||||
data.ts → socials, codingProjects, techProjects, friends, wallpapers
|
data.ts → socials, codingProjects, techProjects, friends, wallpapers
|
||||||
revival-inc/page.tsx → embeds the bundled game at /public/revival-inc/index.html
|
revival-inc/page.tsx → embeds the bundled game at /public/revival-inc/index.html
|
||||||
api/
|
api/
|
||||||
|
|
@ -179,18 +200,17 @@ src/app/
|
||||||
ThemeRoot.tsx → picks which shell(s) to mount
|
ThemeRoot.tsx → picks which shell(s) to mount
|
||||||
ThemeScript.tsx → pre-paint script: theme + device detection
|
ThemeScript.tsx → pre-paint script: theme + device detection
|
||||||
useTheme.ts → theme state via useSyncExternalStore
|
useTheme.ts → theme state via useSyncExternalStore
|
||||||
TerminalLayout.tsx → the Gruvbox terminal shell
|
SkeuoLayout.tsx → the skeuo console shell
|
||||||
WindowsDesktop.tsx → desktop shell: icons, windows, taskbar, Start menu
|
WindowsDesktop.tsx → desktop shell: icons, windows, taskbar, Start menu
|
||||||
MobileHome.tsx → phone shell: iOS home screen + app view
|
MobileHome.tsx → phone shell: iOS home screen + app view
|
||||||
WinWindow.tsx → one draggable/resizable window
|
WinWindow.tsx → one draggable/resizable window
|
||||||
apps.tsx → APPS registry, window contents, in-app terminal
|
apps.tsx → APPS registry, window contents, in-app terminal
|
||||||
useDesktopIcons.ts → desktop icon grid + drag/drop logic
|
useDesktopIcons.ts → desktop icon grid + drag/drop logic
|
||||||
useDragOrder.ts → list reordering (mobile grid/dock)
|
useDragOrder.ts → list reordering (mobile grid/dock)
|
||||||
ServiceButton.tsx → service link + live status dot
|
ServiceButton.tsx → service link + live status lamp
|
||||||
ContributionHeatmap.tsx → heatmap + recent commits widget
|
ContributionHeatmap.tsx → heatmap + recent commits widget
|
||||||
Clock.tsx → live clock
|
Clock.tsx → live clock
|
||||||
JokerLogo.tsx → logo with click easter egg
|
JokerLogo.tsx → logo with click easter egg
|
||||||
SlBackground.tsx → animated ASCII train background
|
|
||||||
scripts/
|
scripts/
|
||||||
dev-server.mjs → custom dev server: live updates without the dev-only debug surface
|
dev-server.mjs → custom dev server: live updates without the dev-only debug surface
|
||||||
deploy/
|
deploy/
|
||||||
|
|
|
||||||
82
agents.md
82
agents.md
|
|
@ -34,13 +34,18 @@ When in doubt, prefer updating. A stale README **or** agents.md is treated as a
|
||||||
## What this project is
|
## What this project is
|
||||||
|
|
||||||
Personal portfolio site for **dvorakmortonxd.dev**. It's not a normal website —
|
Personal portfolio site for **dvorakmortonxd.dev**. It's not a normal website —
|
||||||
it ships **two fully interactive desktop/OS simulations** plus a terminal, and
|
it ships a hand-built **skeuomorphic console** (`skeuo`, the default) plus a
|
||||||
picks which one to show based on theme and device.
|
KDE-style **desktop simulation** that is only offered on phone-width viewports.
|
||||||
|
|
||||||
- Framework: **Next.js 16** (App Router, Turbopack), **React 19**, TypeScript.
|
- 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 — `skeuo.css` (the console), `desktop.css` (KDE sim +
|
||||||
utilities in the components (Tailwind is present via PostCSS but the look is
|
mobile home), `shared.css` (components both shells render) and `globals.css`
|
||||||
hand-written CSS).
|
(base/reset). No CSS-in-JS, no Tailwind utilities in the components (Tailwind
|
||||||
|
is present via PostCSS but the look is hand-written CSS). **Materials are CSS
|
||||||
|
+ inline-SVG noise only — no texture images.**
|
||||||
|
- Fonts: Barlow Semi Condensed (metal labels, headings, controls) + Courier Prime
|
||||||
|
(paper, readouts) via `next/font/google` with explicit fallbacks; Monocraft
|
||||||
|
stays self-hosted in `public/` for the desktop's terminal windows.
|
||||||
- Path alias: `@/*` → `src/*`.
|
- Path alias: `@/*` → `src/*`.
|
||||||
- Version: `2.1.0` (see `package.json`).
|
- Version: `2.1.0` (see `package.json`).
|
||||||
|
|
||||||
|
|
@ -73,12 +78,13 @@ token.** Do not print it, copy it into new files, or push it anywhere else.
|
||||||
|
|
||||||
```
|
```
|
||||||
src/app/
|
src/app/
|
||||||
page.tsx → renders <ThemeRoot/>, imports page.css + skeuo.css
|
page.tsx → renders <ThemeRoot/>, imports shared.css + skeuo.css + desktop.css
|
||||||
layout.tsx → html shell, metadata, ThemeScript
|
layout.tsx → html shell, metadata, fonts, ThemeScript
|
||||||
globals.css → base/reset styles + Tailwind import
|
globals.css → base/reset styles + Tailwind import
|
||||||
data.ts → socials, codingProjects, techProjects, friends, wallpapers
|
data.ts → socials, codingProjects, techProjects, friends, wallpapers
|
||||||
page.css → base / terminal theme styles
|
skeuo.css → the skeuo console: materials, objects, depth
|
||||||
skeuo.css → Windows/Plasma desktop + mobile home styles
|
desktop.css → Windows/Plasma desktop + mobile home styles
|
||||||
|
shared.css → styles for components both shells render
|
||||||
revival-inc/page.tsx → fullscreen iframe embedding /public/revival-inc/index.html
|
revival-inc/page.tsx → fullscreen iframe embedding /public/revival-inc/index.html
|
||||||
api/
|
api/
|
||||||
heatmap/route.ts → proxy for git.dvorakmortonxd.dev contribution heatmap
|
heatmap/route.ts → proxy for git.dvorakmortonxd.dev contribution heatmap
|
||||||
|
|
@ -87,9 +93,9 @@ src/app/
|
||||||
frame/route.ts → header-stripping proxy so an unframeable friend site loads in a window; host allowlist
|
frame/route.ts → header-stripping proxy so an unframeable friend site loads in a window; host allowlist
|
||||||
components/
|
components/
|
||||||
ThemeRoot.tsx → picks which shell to show (the router of the UI)
|
ThemeRoot.tsx → picks which shell to show (the router of the UI)
|
||||||
TerminalLayout.tsx → "terminal" theme (Gruvbox)
|
SkeuoLayout.tsx → "skeuo" theme (the console): every section as an object
|
||||||
WindowsDesktop.tsx → "skeuo" theme, desktop: icons, windows, taskbar
|
WindowsDesktop.tsx → "desktop" theme, desktop: icons, windows, taskbar
|
||||||
MobileHome.tsx → "skeuo" theme, phone: app grid + dock
|
MobileHome.tsx → "desktop" theme, phone: app grid + dock
|
||||||
WinWindow.tsx → a single draggable/resizable window
|
WinWindow.tsx → a single draggable/resizable window
|
||||||
apps.tsx → APPS registry + the content of each app + in-app terminal
|
apps.tsx → APPS registry + the content of each app + in-app terminal
|
||||||
+ embedded-site windows + the Minecraft-style SMP menu
|
+ embedded-site windows + the Minecraft-style SMP menu
|
||||||
|
|
@ -101,23 +107,31 @@ src/app/
|
||||||
ContributionHeatmap.tsx → heatmap + recent-commit widget (uses the two proxies)
|
ContributionHeatmap.tsx → heatmap + recent-commit widget (uses the two proxies)
|
||||||
Clock.tsx → live clock (useSyncExternalStore + interval)
|
Clock.tsx → live clock (useSyncExternalStore + interval)
|
||||||
JokerLogo.tsx → site logo with a click easter egg
|
JokerLogo.tsx → site logo with a click easter egg
|
||||||
SlBackground.tsx → animated ASCII train background (terminal theme)
|
|
||||||
scripts/
|
scripts/
|
||||||
dev-server.mjs → custom dev server: live updates minus the dev-only debug surface
|
dev-server.mjs → custom dev server: live updates minus the dev-only debug surface
|
||||||
```
|
```
|
||||||
|
|
||||||
### How UI selection works
|
### How UI selection works
|
||||||
1. `ThemeScript` runs before paint. It reads `localStorage.theme` and sets
|
1. `ThemeScript` runs before paint. It reads `localStorage['dmx-theme']`,
|
||||||
`data-theme` (`skeuo` | `terminal`) and `data-device` (`phone` | `desktop`)
|
migrating the legacy `theme` key once (`terminal` → `skeuo`, `skeuo` →
|
||||||
on `<html>` from a user-agent sniff.
|
`desktop`), then sets `data-theme` (`skeuo` | `desktop`) and `data-device`
|
||||||
|
(`phone` | `desktop`, from a user-agent sniff) on `<html>`. Viewports wider
|
||||||
|
than 768 px always get `skeuo`.
|
||||||
2. `useTheme()` (in `useTheme.ts`) reads `data-theme` via
|
2. `useTheme()` (in `useTheme.ts`) reads `data-theme` via
|
||||||
`useSyncExternalStore` and dispatches a `theme-change` event on toggle.
|
`useSyncExternalStore` and dispatches a `theme-change` event on toggle.
|
||||||
3. `ThemeRoot` shows the terminal when `theme === "terminal"`; otherwise it
|
`setTheme("desktop")` is refused when the viewport is wider than 768 px, so
|
||||||
mounts **both** `WindowsDesktop` and `MobileHome` and lets CSS
|
nothing can leave a desktop visitor stuck in the sim.
|
||||||
(`html[data-device="phone"]`) decide which is visible. The desktop stays
|
3. `ThemeRoot` always renders `SkeuoLayout` (kept mounted, just hidden, while
|
||||||
mounted while hidden so the Welcome window isn't recreated.
|
the sim is up so its scroll position survives). On phone-width viewports it
|
||||||
|
additionally mounts **both** `WindowsDesktop` and `MobileHome` and lets CSS
|
||||||
|
(`html[data-device="phone"]`) decide which is visible; they stay mounted while
|
||||||
|
hidden so the Welcome window isn't recreated. The sim is never mounted wider
|
||||||
|
than that.
|
||||||
|
|
||||||
Themes: `skeuo` = KDE Plasma / Windows desktop, `terminal` = Gruvbox terminal.
|
Themes: `skeuo` = the site itself (walnut + aluminium console), `desktop` = the
|
||||||
|
phone-only KDE Plasma / Windows simulation. The physical switch between them sits
|
||||||
|
on the nameplate (`.sk-desktop-switch`, styled as `.sk-switch`) and is only
|
||||||
|
visible at ≤ 768 px.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -217,7 +231,31 @@ list reordering, not free placement. Don't confuse the two.
|
||||||
- Components are client components (`"use client"` at the top) — they use
|
- Components are client components (`"use client"` at the top) — they use
|
||||||
pointer events and window APIs freely.
|
pointer events and window APIs freely.
|
||||||
- CSS classes are prefixed by area: `win-` (desktop), `mobile-` (phone),
|
- CSS classes are prefixed by area: `win-` (desktop), `mobile-` (phone),
|
||||||
`app-` / `kickoff` / `terminal` etc. Grep by prefix to find styles.
|
`app-` / `kickoff` / `terminal` etc. Grep by prefix to find styles. The skeuo
|
||||||
|
theme's own classes all start with `sk-` (`skeuo.css`) and that prefix is
|
||||||
|
reserved for it — `desktop.css` only ever uses `skeuo-*` for its two roots.
|
||||||
|
- **The skeuo theme is CSS-only.** No texture images, no `url()` beyond the
|
||||||
|
social SVG masks: wood, aluminium, brass, leather, bakelite, paper, glass and
|
||||||
|
rubber are gradients + `color-mix()` + an inline-SVG `feTurbulence` grain
|
||||||
|
(`--sk-noise`). Build new surfaces from those tokens (`--sk-raise-1/2`,
|
||||||
|
`--sk-sink-1`, `--sk-alu-*`, `--sk-brass-*`, `--sk-wood-*`, `--sk-paper-*`)
|
||||||
|
rather than inventing new colours, and keep the light coming from the
|
||||||
|
top-left. Simplify (drop noise, flatten shadows) at ≤ 768 px.
|
||||||
|
- **`shared.css` is shared.** Both shells render `ServiceButton`,
|
||||||
|
`ContributionHeatmap`, `JokerLogo`, the project cards and the social buttons.
|
||||||
|
Change their base styles only if you check the desktop (`.app-surface` /
|
||||||
|
`.win-body` in `desktop.css` layers its own overrides on top). The skeuo theme
|
||||||
|
restyles them from `skeuo.css`, scoped to `html[data-theme="skeuo"]`, so the
|
||||||
|
desktop keeps its look. The `:root` block in `shared.css` is only the fallback
|
||||||
|
palette those components inherit.
|
||||||
|
- **Motion stays cheap.** Transitions are 100–150 ms and only ever animate
|
||||||
|
`transform` / `opacity`; pressed states move down and swap their shadow
|
||||||
|
without animating it. Anything animated respects
|
||||||
|
`@media (prefers-reduced-motion: reduce)`.
|
||||||
|
- **The desktop sim is phone-only.** `.sk-switch` is hidden above 768 px and
|
||||||
|
`setTheme("desktop")` refuses to apply there; `ThemeRoot` also force-switches
|
||||||
|
back to `skeuo` when the viewport grows. Don't add another way into the sim
|
||||||
|
without keeping those three guards in place.
|
||||||
- Several places deliberately use **imperative DOM work** (drag ghost,
|
- Several places deliberately use **imperative DOM work** (drag ghost,
|
||||||
transform-origin for the mobile launch animation). This is intentional for
|
transform-origin for the mobile launch animation). This is intentional for
|
||||||
performance; prefer matching the existing pattern over "Reactifying" it.
|
performance; prefer matching the existing pattern over "Reactifying" it.
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 234 KiB |
|
|
@ -8,7 +8,7 @@ import { NextResponse } from 'next/server';
|
||||||
//
|
//
|
||||||
// Host matching is exact or by subdomain (e.g. anything under
|
// Host matching is exact or by subdomain (e.g. anything under
|
||||||
// dvorakmortonxd.dev). Keep this in sync with the ServiceButton `url`/`checkUrl`
|
// dvorakmortonxd.dev). Keep this in sync with the ServiceButton `url`/`checkUrl`
|
||||||
// values in components/apps.tsx and components/TerminalLayout.tsx, and with the
|
// values in components/apps.tsx and components/SkeuoLayout.tsx, and with the
|
||||||
// friend list in data.ts.
|
// friend list in data.ts.
|
||||||
const ALLOWED_HOSTS = [
|
const ALLOWED_HOSTS = [
|
||||||
'dvorakmortonxd.dev', // ai / jelly / navidrome / git / proxmox / mc (SMP)
|
'dvorakmortonxd.dev', // ai / jelly / navidrome / git / proxmox / mc (SMP)
|
||||||
|
|
|
||||||
519
src/app/components/SkeuoLayout.tsx
Normal file
519
src/app/components/SkeuoLayout.tsx
Normal file
|
|
@ -0,0 +1,519 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import Image from "next/image";
|
||||||
|
import type { CSSProperties, ReactNode } from "react";
|
||||||
|
import JokerLogo from "./JokerLogo";
|
||||||
|
import ServiceButton from "./ServiceButton";
|
||||||
|
import ContributionHeatmap from "./ContributionHeatmap";
|
||||||
|
import { setTheme } from "./useTheme";
|
||||||
|
import { socials, codingProjects, techProjects, friends } from "../data";
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ parts */
|
||||||
|
|
||||||
|
/** A small engraved brass / enamel plate — every object is labelled like a machine. */
|
||||||
|
function Plate({
|
||||||
|
children,
|
||||||
|
tone = "brass",
|
||||||
|
className = "",
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
tone?: "brass" | "red" | "ivory";
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return <span className={`sk-tag sk-tag--${tone} ${className}`}>{children}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A recessed dark-glass window: where the console prints machine output. */
|
||||||
|
function Readout({ children, className = "" }: { children: ReactNode; className?: string }) {
|
||||||
|
return (
|
||||||
|
<div className={`sk-readout ${className}`}>
|
||||||
|
<span className="sk-readout__glare" aria-hidden="true" />
|
||||||
|
<span className="sk-readout__text">{children}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Weight → needle angle on a 240° dial. */
|
||||||
|
const GAUGE_MAX = 180;
|
||||||
|
const needle = (kg: number) => `${(-120 + (kg / GAUGE_MAX) * 240).toFixed(1)}deg`;
|
||||||
|
|
||||||
|
const GYM = [
|
||||||
|
{ key: "squat", label: "SQUAT", weight: 125, date: "5.1.2025", icon: "/squat.svg" },
|
||||||
|
{ key: "bench", label: "BENCH", weight: 111, date: "29.5.2026", icon: "/bench.svg" },
|
||||||
|
{ key: "deadlift", label: "DEADLIFT", weight: 165, date: "17.12.2024", icon: "/deadlift.svg" },
|
||||||
|
];
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------- page */
|
||||||
|
|
||||||
|
export default function SkeuoLayout() {
|
||||||
|
return (
|
||||||
|
<div className="sk-world">
|
||||||
|
<div className="sk-console">
|
||||||
|
{/* ------------------------------------------------------ nameplate */}
|
||||||
|
<header className="sk-nameplate">
|
||||||
|
<span className="sk-bezel sk-bezel--logo">
|
||||||
|
<JokerLogo />
|
||||||
|
</span>
|
||||||
|
<h1 className="sk-nameplate__name">dvorakmortonxd.dev</h1>
|
||||||
|
|
||||||
|
<span className="sk-nameplate__deco" aria-hidden="true">
|
||||||
|
<span className="sk-nameplate__vent" />
|
||||||
|
<span className="sk-nameplate__lamps">
|
||||||
|
<i className="sk-lamp sk-lamp--amber sk-lamp--blink" />
|
||||||
|
<i className="sk-lamp sk-lamp--green" />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="sk-switch sk-desktop-switch"
|
||||||
|
onClick={() => setTheme("desktop")}
|
||||||
|
title="Boot the desktop simulation"
|
||||||
|
>
|
||||||
|
<span className="sk-switch__label">DESKTOP</span>
|
||||||
|
<span className="sk-switch__track" aria-hidden="true">
|
||||||
|
<span className="sk-switch__knob" />
|
||||||
|
</span>
|
||||||
|
<span className="sk-lamp sk-lamp--amber" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main className="sk-stack">
|
||||||
|
{/* ------------------------------------------------------ welcome */}
|
||||||
|
<section className="sk-panel sk-panel--welcome">
|
||||||
|
<span className="sk-panel__screws" aria-hidden="true">
|
||||||
|
<i className="sk-screw" />
|
||||||
|
<i className="sk-screw" />
|
||||||
|
</span>
|
||||||
|
<Plate className="sk-panel__plate">README.MD</Plate>
|
||||||
|
|
||||||
|
<div className="sk-welcome__body">
|
||||||
|
<Readout className="sk-welcome__prompt">
|
||||||
|
<span className="sk-readout__ok">martin@dvorakmortonxd</span>
|
||||||
|
<span className="sk-readout__dim">:~$</span> whoami
|
||||||
|
<span className="sk-caret" aria-hidden="true" />
|
||||||
|
</Readout>
|
||||||
|
<p className="sk-emboss sk-welcome__copy">
|
||||||
|
Welcome my personal website! Here I show off my projects and hardware I own,
|
||||||
|
aswell as my self-hosted services :)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="sk-lamp sk-lamp--amber sk-lamp--blink sk-welcome__lamp" aria-hidden="true" />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ------------------------------------------------------ socials */}
|
||||||
|
<section className="sk-panel sk-panel--socials">
|
||||||
|
<span className="sk-panel__screws" aria-hidden="true">
|
||||||
|
<i className="sk-screw" />
|
||||||
|
<i className="sk-screw" />
|
||||||
|
</span>
|
||||||
|
<Plate className="sk-panel__plate">SOCIALS.JS</Plate>
|
||||||
|
|
||||||
|
<p className="sk-caption">Patch bay — every line is a live feed</p>
|
||||||
|
|
||||||
|
<div className="social-buttons sk-patchbay">
|
||||||
|
{socials.map((s) => (
|
||||||
|
<a
|
||||||
|
key={s.name}
|
||||||
|
href={s.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="social-button"
|
||||||
|
>
|
||||||
|
<Image src={s.icon} alt={s.name} width={16} height={16} className="social-icon" />
|
||||||
|
<span className="social-label">{s.name}</span>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="mail-contact sk-mail">
|
||||||
|
<span className="sk-mail__tape" aria-hidden="true" />
|
||||||
|
mail if u ever wanna contact me:{" "}
|
||||||
|
<a href="mailto:dvorak@dvorakmortonxd.dev">dvorak@dvorakmortonxd.dev</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ---------------------------------------------------------- gym */}
|
||||||
|
<section className="sk-panel sk-panel--gym">
|
||||||
|
<span className="sk-panel__screws" aria-hidden="true">
|
||||||
|
<i className="sk-screw" />
|
||||||
|
<i className="sk-screw" />
|
||||||
|
</span>
|
||||||
|
<Plate tone="ivory" className="sk-panel__plate">
|
||||||
|
GYM_SBD_AT_73KG_BW.DB
|
||||||
|
</Plate>
|
||||||
|
|
||||||
|
<p className="sk-caption">Analogue load meters — best lifts, bodyweight 73 kg</p>
|
||||||
|
|
||||||
|
<div className="gym-grid">
|
||||||
|
{GYM.map((g) => (
|
||||||
|
<div key={g.key} className="gym-item">
|
||||||
|
<div className="sk-gauge" style={{ "--needle": needle(g.weight) } as CSSProperties}>
|
||||||
|
<span className="sk-gauge__face">
|
||||||
|
<span className="sk-gauge__ticks" aria-hidden="true" />
|
||||||
|
<span className="sk-gauge__needle" aria-hidden="true" />
|
||||||
|
<span className="sk-gauge__hub" aria-hidden="true" />
|
||||||
|
<Image src={g.icon} alt={g.label} width={48} height={48} className="gym-icon" />
|
||||||
|
</span>
|
||||||
|
<span className="sk-gauge__glass" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<div className="gym-label">{g.label}</div>
|
||||||
|
<div className="gym-weight">
|
||||||
|
<span className="sk-num">{g.weight}</span>kg
|
||||||
|
</div>
|
||||||
|
<div className="gym-date">{g.date}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ---------------------------------------------------------- now */}
|
||||||
|
<section className="sk-panel sk-panel--now">
|
||||||
|
<span className="sk-panel__screws" aria-hidden="true">
|
||||||
|
<i className="sk-screw" />
|
||||||
|
<i className="sk-screw" />
|
||||||
|
</span>
|
||||||
|
<Plate tone="ivory" className="sk-panel__plate">
|
||||||
|
NOW.TXT
|
||||||
|
</Plate>
|
||||||
|
|
||||||
|
<div className="sk-clipboard">
|
||||||
|
<span className="sk-clipboard__clip" aria-hidden="true" />
|
||||||
|
<span className="sk-clipboard__board" aria-hidden="true" />
|
||||||
|
<div className="sk-sheet sk-sheet--note">
|
||||||
|
<span className="sk-sheet__tab" aria-hidden="true">
|
||||||
|
<span className="sk-sheet__hole" />
|
||||||
|
NOW
|
||||||
|
</span>
|
||||||
|
<Readout className="sk-inline-readout">
|
||||||
|
<span className="sk-readout__ok">martin@dvorakmortonxd</span>
|
||||||
|
<span className="sk-readout__dim">:~$</span> cat now.txt
|
||||||
|
</Readout>
|
||||||
|
<p className="sk-type">
|
||||||
|
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{" "}
|
||||||
|
<a href="https://skopec.dev">Pepa</a> are working on an education app so we can study
|
||||||
|
more efficiently, It's called{" "}
|
||||||
|
<a href="https://git.dvorakmortonxd.dev/edu/maturita.c">maturita.c</a>!
|
||||||
|
</p>
|
||||||
|
<span className="sk-caret sk-caret--on-paper" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* -------------------------------------------------- about me */}
|
||||||
|
<section className="sk-ledger">
|
||||||
|
<span className="sk-ledger__stitch" aria-hidden="true" />
|
||||||
|
<Plate className="sk-ledger__plate">README.MD</Plate>
|
||||||
|
<span className="sk-ledger__corner" aria-hidden="true" />
|
||||||
|
|
||||||
|
<div className="sk-sheet sk-sheet--page">
|
||||||
|
<Readout className="sk-inline-readout">
|
||||||
|
<span className="sk-readout__ok">martin@dvorakmortonxd</span>
|
||||||
|
<span className="sk-readout__dim">:~$</span> cat about_me.md
|
||||||
|
</Readout>
|
||||||
|
<p className="sk-type">
|
||||||
|
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{" "}
|
||||||
|
<a href="https://sssvt.cz">sssvt.cz</a>. 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 <i>literally everything</i>,
|
||||||
|
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.
|
||||||
|
<span className="sk-caret sk-caret--on-paper" aria-hidden="true" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ----------------------------------------------------- services */}
|
||||||
|
<section className="sk-rack">
|
||||||
|
<div className="sk-rack__head">
|
||||||
|
<Plate className="sk-rack__plate">SERVICES.HTML</Plate>
|
||||||
|
<span className="sk-lamp sk-lamp--green sk-lamp--blink" aria-hidden="true" />
|
||||||
|
<span className="sk-rack__hint">health checked from this page</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="services-buttons services-main sk-rack__rail">
|
||||||
|
<ServiceButton
|
||||||
|
url="https://proxmox.dvorakmortonxd.dev/"
|
||||||
|
className="proxmox sk-module"
|
||||||
|
icon="🖥️"
|
||||||
|
name="Proxmox"
|
||||||
|
desc="Server Management"
|
||||||
|
access="private"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://ai.dvorakmortonxd.dev/"
|
||||||
|
className="ai sk-module"
|
||||||
|
icon="🦾"
|
||||||
|
name="Llama.cpp"
|
||||||
|
desc="Local AI Server"
|
||||||
|
access="on_request"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://jelly.dvorakmortonxd.dev/"
|
||||||
|
className="jellyfin sk-module"
|
||||||
|
icon="🎬"
|
||||||
|
name="Jellyfin"
|
||||||
|
desc="Local Media Server"
|
||||||
|
access="on_request"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://discord.gg/vyvhk7d8bX"
|
||||||
|
checkUrl="minecraft://mc.dvorakmortonxd.dev:25565"
|
||||||
|
className="pterodactyl sk-module"
|
||||||
|
icon="🎮"
|
||||||
|
name="SMP"
|
||||||
|
desc="My Survival Minecraft Server"
|
||||||
|
access="on_request"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://navidrome.dvorakmortonxd.dev/"
|
||||||
|
className="funkwhale sk-module"
|
||||||
|
icon="🎸"
|
||||||
|
name="Navidrome"
|
||||||
|
desc="Music Streaming"
|
||||||
|
access="private"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://git.dvorakmortonxd.dev"
|
||||||
|
className="gitdxd sk-module"
|
||||||
|
icon="🐙"
|
||||||
|
name="GitDXD"
|
||||||
|
desc="Best Git Hosting OAT"
|
||||||
|
access="public"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ------------------------------------------------------- gitdxd */}
|
||||||
|
<section className="sk-panel sk-panel--feature">
|
||||||
|
<span className="sk-panel__screws" aria-hidden="true">
|
||||||
|
<i className="sk-screw" />
|
||||||
|
<i className="sk-screw" />
|
||||||
|
</span>
|
||||||
|
<Plate className="sk-panel__plate">GITDXD.GIT</Plate>
|
||||||
|
|
||||||
|
<div className="sk-feature">
|
||||||
|
<h2 className="sk-engrave sk-feature__title">GitDXD</h2>
|
||||||
|
<p className="sk-emboss sk-feature__sub">My Self-hosted Git Instance</p>
|
||||||
|
|
||||||
|
<figure className="sk-frame">
|
||||||
|
<span className="sk-frame__glass" aria-hidden="true" />
|
||||||
|
<Image src="/gitdxd.webp" alt="GitDXD Preview" width={1280} height={720} className="sk-frame__img" />
|
||||||
|
<figcaption className="sk-frame__caption">forgejo · git.dvorakmortonxd.dev</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<div className="sk-sheet sk-sheet--strip">
|
||||||
|
<p className="sk-type">
|
||||||
|
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{" "}
|
||||||
|
<a href="https://git.dvorakmortonxd.dev">git.dvorakmortonxd.dev</a>.
|
||||||
|
<span className="sk-caret sk-caret--on-paper" aria-hidden="true" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ------------------------------------------------ contributions */}
|
||||||
|
<section className="sk-recorder">
|
||||||
|
<div className="sk-recorder__head">
|
||||||
|
<Plate tone="ivory" className="sk-recorder__plate">
|
||||||
|
CONTRIBUTIONS@GITDXD.GIT
|
||||||
|
</Plate>
|
||||||
|
<span className="sk-recorder__roller" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<div className="sk-recorder__paper">
|
||||||
|
<ContributionHeatmap />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ----------------------------------------------------- revival */}
|
||||||
|
<section className="sk-panel sk-panel--feature">
|
||||||
|
<span className="sk-panel__screws" aria-hidden="true">
|
||||||
|
<i className="sk-screw" />
|
||||||
|
<i className="sk-screw" />
|
||||||
|
</span>
|
||||||
|
<Plate tone="ivory" className="sk-panel__plate">
|
||||||
|
REVIVAL.INC
|
||||||
|
</Plate>
|
||||||
|
|
||||||
|
<div className="sk-feature sk-feature--cart">
|
||||||
|
<div className="sk-cart">
|
||||||
|
<span className="sk-cart__ridge" aria-hidden="true" />
|
||||||
|
<Image
|
||||||
|
src="/revivalinc.webp"
|
||||||
|
alt="revival inc thumbnail"
|
||||||
|
width={768}
|
||||||
|
height={576}
|
||||||
|
className="sk-cart__art"
|
||||||
|
/>
|
||||||
|
<span className="sk-cart__rib" aria-hidden="true" />
|
||||||
|
<h2 className="sk-cart__title">Revival inc.</h2>
|
||||||
|
<span className="sk-screw sk-cart__screw" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="sk-sheet sk-sheet--strip">
|
||||||
|
<p className="sk-engrave sk-feature__sub">What is it?</p>
|
||||||
|
<p className="sk-type">
|
||||||
|
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{" "}
|
||||||
|
<a href="https://martin-dv.itch.io/revival-inc">itch.io</a> or play{" "}
|
||||||
|
<a href="https://dvorakmortonxd.dev/revival-inc">here</a>.
|
||||||
|
<span className="sk-caret sk-caret--on-paper" aria-hidden="true" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* --------------------------------------------------- rustywolf */}
|
||||||
|
<section className="sk-panel sk-panel--feature">
|
||||||
|
<span className="sk-panel__screws" aria-hidden="true">
|
||||||
|
<i className="sk-screw" />
|
||||||
|
<i className="sk-screw" />
|
||||||
|
</span>
|
||||||
|
<Plate className="sk-panel__plate">RUSTYWOLF.RS</Plate>
|
||||||
|
|
||||||
|
<div className="sk-feature sk-feature--box">
|
||||||
|
<div className="sk-box">
|
||||||
|
<span className="sk-box__spine" aria-hidden="true">
|
||||||
|
RUSTYWOLF
|
||||||
|
</span>
|
||||||
|
<Image
|
||||||
|
src="/RustyWolfPreview.webp"
|
||||||
|
alt="RustyWolf Browser Preview"
|
||||||
|
width={1280}
|
||||||
|
height={720}
|
||||||
|
className="sk-box__art"
|
||||||
|
/>
|
||||||
|
<span className="sk-box__edge" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="sk-sheet sk-sheet--strip">
|
||||||
|
<p className="sk-engrave sk-feature__sub">Introducing RustyWolf — what makes it so cool???</p>
|
||||||
|
<p className="sk-type">
|
||||||
|
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{" "}
|
||||||
|
<a href="https://git.dvorakmortonxd.dev/dvorakmortonxd/RustyWolf">GitDXD</a>.
|
||||||
|
<span className="sk-caret sk-caret--on-paper" aria-hidden="true" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ----------------------------------------------------- hardware */}
|
||||||
|
<section className="sk-board">
|
||||||
|
<div className="sk-board__head">
|
||||||
|
<Plate className="sk-board__plate">TECH_LIST.JSON</Plate>
|
||||||
|
<h2 className="sk-paint sk-section-title">All my hardware equipment :)</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="sk-board__face">
|
||||||
|
<div className="project-grid sk-board__tags">
|
||||||
|
{techProjects.map((project) => (
|
||||||
|
<div key={project.title} className="project-card sk-tagcard">
|
||||||
|
<span className="sk-tagcard__hook" aria-hidden="true" />
|
||||||
|
<span className="sk-tagcard__eyelet" aria-hidden="true" />
|
||||||
|
<div
|
||||||
|
className="project-image sk-tagcard__photo"
|
||||||
|
style={{ backgroundImage: `url(${project.image})` }}
|
||||||
|
>
|
||||||
|
<span className="sk-tagcard__clip" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<h4 className="project-title">{project.title}</h4>
|
||||||
|
<p className="project-desc">{project.desc}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ----------------------------------------------------- projects */}
|
||||||
|
<section className="sk-drawer">
|
||||||
|
<div className="sk-drawer__head">
|
||||||
|
<Plate tone="ivory" className="sk-drawer__plate">
|
||||||
|
PROJECTS.DOCX
|
||||||
|
</Plate>
|
||||||
|
<h2 className="sk-paint sk-section-title">All the stuff I've worked on :))</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="sk-drawer__body">
|
||||||
|
<div className="sk-drawer__inner">
|
||||||
|
<div className="project-grid sk-drawer__cards">
|
||||||
|
{codingProjects.map((project, idx) => (
|
||||||
|
<a
|
||||||
|
key={idx}
|
||||||
|
href={project.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="project-card sk-indexcard"
|
||||||
|
>
|
||||||
|
<div className="sk-indexcard__tab" aria-hidden="true" />
|
||||||
|
<div
|
||||||
|
className="project-image sk-indexcard__photo"
|
||||||
|
style={{ backgroundImage: `url(${project.img})` }}
|
||||||
|
/>
|
||||||
|
<h4 className="project-title">{project.title}</h4>
|
||||||
|
<p className="project-desc">{project.desc}</p>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="sk-drawer__front">
|
||||||
|
<span className="sk-drawer__pull" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ------------------------------------------------------ friends */}
|
||||||
|
<section className="sk-panel sk-panel--friends">
|
||||||
|
<span className="sk-panel__screws" aria-hidden="true">
|
||||||
|
<i className="sk-screw" />
|
||||||
|
<i className="sk-screw" />
|
||||||
|
</span>
|
||||||
|
<Plate className="sk-panel__plate">FRIENDS.TSX</Plate>
|
||||||
|
<h2 className="sk-paint sk-section-title sk-section-title--center">
|
||||||
|
Friend's websites and projects
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="services-buttons sk-callcards">
|
||||||
|
{friends.map((friend, idx) => (
|
||||||
|
<ServiceButton
|
||||||
|
key={idx}
|
||||||
|
url={friend.realUrl || friend.url}
|
||||||
|
className="friend sk-callcard"
|
||||||
|
icon={<Image src={friend.img} alt={friend.name} width={68} height={68} className="friend-icon" />}
|
||||||
|
name={friend.name}
|
||||||
|
desc={friend.desc}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* -------------------------------------------------------- footer */}
|
||||||
|
<footer className="sk-baseplate">
|
||||||
|
<span className="sk-baseplate__screw sk-baseplate__screw--l" aria-hidden="true" />
|
||||||
|
<span className="sk-baseplate__screw sk-baseplate__screw--r" aria-hidden="true" />
|
||||||
|
<span className="sk-baseplate__version">v2.1.0</span>
|
||||||
|
<p className="sk-baseplate__legal">
|
||||||
|
© 2025-2026 Martin Dvořák. All rights reserved (not really I dont care, just follow the
|
||||||
|
GPL3.0 License)
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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 (
|
|
||||||
<>
|
|
||||||
<style>
|
|
||||||
{`
|
|
||||||
@keyframes rainbowShift {
|
|
||||||
0% { background-position: 0% 50%; }
|
|
||||||
50% { background-position: 100% 50%; }
|
|
||||||
100% { background-position: 0% 50%; }
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</style>
|
|
||||||
<div
|
|
||||||
className="sl-background"
|
|
||||||
style={{
|
|
||||||
position: "fixed",
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
pointerEvents: "none",
|
|
||||||
zIndex: 0,
|
|
||||||
overflow: "hidden",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "flex-end",
|
|
||||||
paddingBottom: "5vh",
|
|
||||||
justifyContent: "center",
|
|
||||||
userSelect: "none",
|
|
||||||
backgroundImage: "linear-gradient(rgba(29, 32, 33, 0.94), rgba(29, 32, 33, 0.94)), url('/gruvbox.webp')",
|
|
||||||
backgroundSize: "cover",
|
|
||||||
backgroundPosition: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<pre
|
|
||||||
style={{
|
|
||||||
margin: 0,
|
|
||||||
whiteSpace: "pre",
|
|
||||||
fontFamily: "var(--font-custom-mono), monospace",
|
|
||||||
fontSize: "1rem",
|
|
||||||
transform: "translateX(" + pos + "vw)",
|
|
||||||
willChange: "transform",
|
|
||||||
background: "linear-gradient(90deg, var(--red), var(--accent2), var(--accent), var(--green), var(--aqua), var(--blue), var(--purple))",
|
|
||||||
backgroundSize: "200% auto",
|
|
||||||
WebkitBackgroundClip: "text",
|
|
||||||
WebkitTextFillColor: "transparent",
|
|
||||||
animation: "rainbowShift 3s linear infinite",
|
|
||||||
opacity: 0.35,
|
|
||||||
filter: "drop-shadow(0 0 12px rgba(250, 189, 47, 0.5))",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{TRAIN_ASCII}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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 (
|
|
||||||
<>
|
|
||||||
<SlBackground />
|
|
||||||
<div className="container">
|
|
||||||
<header className="header">
|
|
||||||
<div className="header-center">
|
|
||||||
<JokerLogo />
|
|
||||||
<h1 className="site-name">dvorakmortonxd.dev</h1>
|
|
||||||
<button type="button" className="header-theme-btn" onClick={toggle}>
|
|
||||||
Desktop
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main className="hero">
|
|
||||||
<div className="hero-panel" style={panelStyle("var(--accent)")}>
|
|
||||||
<span
|
|
||||||
className="hero-panel-label"
|
|
||||||
style={{ color: "var(--accent)", borderColor: "var(--accent)" }}
|
|
||||||
>
|
|
||||||
README.MD
|
|
||||||
</span>
|
|
||||||
<h2 className="hero-title">$ whoami</h2>
|
|
||||||
<p className="hero-subtitle">
|
|
||||||
Welcome my personal website! Here I show off my projects and hardware I own,
|
|
||||||
aswell as my self-hosted services :)<span className="cursor-blink"></span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="social-section">
|
|
||||||
<div className="tui-panel" style={panelStyle("var(--accent)")}>
|
|
||||||
<span
|
|
||||||
className="tui-panel-label"
|
|
||||||
style={{ color: "var(--accent)", borderColor: "var(--accent)" }}
|
|
||||||
>
|
|
||||||
SOCIALS.JS
|
|
||||||
</span>
|
|
||||||
<div className="social-buttons">
|
|
||||||
{socials.map((s) => (
|
|
||||||
<a
|
|
||||||
key={s.name}
|
|
||||||
href={s.url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="social-button"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src={s.icon}
|
|
||||||
alt={s.name}
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className="social-icon"
|
|
||||||
/>
|
|
||||||
<span className="social-label">{s.name}</span>
|
|
||||||
</a>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="mail-contact">
|
|
||||||
mail if u ever wanna contact me:{" "}
|
|
||||||
<a href="mailto:dvorak@dvorakmortonxd.dev">dvorak@dvorakmortonxd.dev</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="gym-section">
|
|
||||||
<div className="tui-panel" style={panelStyle("var(--white)")}>
|
|
||||||
<span
|
|
||||||
className="tui-panel-label"
|
|
||||||
style={{ color: "var(--white)", borderColor: "var(--white)" }}
|
|
||||||
>
|
|
||||||
GYM_SBD_AT_73KG_BW.DB
|
|
||||||
</span>
|
|
||||||
<div className="gym-grid">
|
|
||||||
<div className="gym-item">
|
|
||||||
<Image src="/squat.svg" alt="Squat" width={48} height={48} className="gym-icon" />
|
|
||||||
<div className="gym-label">SQUAT</div>
|
|
||||||
<div className="gym-weight">125kg</div>
|
|
||||||
<div className="gym-date">5.1.2025</div>
|
|
||||||
</div>
|
|
||||||
<div className="gym-item">
|
|
||||||
<Image src="/bench.svg" alt="Bench" width={48} height={48} className="gym-icon" />
|
|
||||||
<div className="gym-label">BENCH</div>
|
|
||||||
<div className="gym-weight">111kg</div>
|
|
||||||
<div className="gym-date">29.5.2026</div>
|
|
||||||
</div>
|
|
||||||
<div className="gym-item">
|
|
||||||
<Image
|
|
||||||
src="/deadlift.svg"
|
|
||||||
alt="Deadlift"
|
|
||||||
width={48}
|
|
||||||
height={48}
|
|
||||||
className="gym-icon"
|
|
||||||
/>
|
|
||||||
<div className="gym-label">DEADLIFT</div>
|
|
||||||
<div className="gym-weight">165kg</div>
|
|
||||||
<div className="gym-date">17.12.2024</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="about-section">
|
|
||||||
<div className="tui-panel" style={panelStyle("var(--white)")}>
|
|
||||||
<span
|
|
||||||
className="tui-panel-label"
|
|
||||||
style={{ color: "var(--white)", borderColor: "var(--white)" }}
|
|
||||||
>
|
|
||||||
NOW.TXT
|
|
||||||
</span>
|
|
||||||
<div className="about-content">
|
|
||||||
<p>
|
|
||||||
<span style={{ color: "var(--green)" }}>martin@dvorakmortonxd</span>:
|
|
||||||
<span className="text-blue">~</span>$ cat now.txt
|
|
||||||
</p>
|
|
||||||
<div style={{ color: "#a3a3a3" }}>
|
|
||||||
<p>
|
|
||||||
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 <a href="https://skopec.dev">Pepa</a> are working on an
|
|
||||||
education app so we can study more efficiently, It's called{" "}
|
|
||||||
<a href="https://git.dvorakmortonxd.dev/edu/maturita.c">maturita.c</a>!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="about-section">
|
|
||||||
<div className="tui-panel" style={panelStyle("var(--accent)")}>
|
|
||||||
<span
|
|
||||||
className="tui-panel-label"
|
|
||||||
style={{ color: "var(--accent)", borderColor: "var(--accent)" }}
|
|
||||||
>
|
|
||||||
README.MD
|
|
||||||
</span>
|
|
||||||
<div className="about-content">
|
|
||||||
<p>
|
|
||||||
<span className="text-green">martin@dvorakmortonxd</span>:
|
|
||||||
<span className="text-blue">~</span>$ cat about_me.md
|
|
||||||
</p>
|
|
||||||
<div style={{ color: "#a3a3a3" }}>
|
|
||||||
<p>
|
|
||||||
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 <a href="https://sssvt.cz">sssvt.cz</a>. 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{" "}
|
|
||||||
<i>literally everything</i>, 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.
|
|
||||||
<span className="cursor-blink"></span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="services-section">
|
|
||||||
<div className="tui-panel" style={panelStyle("var(--accent)")}>
|
|
||||||
<span
|
|
||||||
className="tui-panel-label"
|
|
||||||
style={{ color: "var(--accent)", borderColor: "var(--accent)" }}
|
|
||||||
>
|
|
||||||
SERVICES.HTML
|
|
||||||
</span>
|
|
||||||
<div className="services-buttons services-main">
|
|
||||||
<ServiceButton
|
|
||||||
url="https://proxmox.dvorakmortonxd.dev/"
|
|
||||||
className="proxmox"
|
|
||||||
icon="🖥️"
|
|
||||||
name="Proxmox"
|
|
||||||
desc="Server Management"
|
|
||||||
access="private"
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
<ServiceButton
|
|
||||||
url="https://ai.dvorakmortonxd.dev/"
|
|
||||||
className="ai"
|
|
||||||
icon="🦾"
|
|
||||||
name="Llama.cpp"
|
|
||||||
desc="Local AI Server"
|
|
||||||
access="on_request"
|
|
||||||
/>
|
|
||||||
<ServiceButton
|
|
||||||
url="https://jelly.dvorakmortonxd.dev/"
|
|
||||||
className="jellyfin"
|
|
||||||
icon="🎬"
|
|
||||||
name="Jellyfin"
|
|
||||||
desc="Local Media Server"
|
|
||||||
access="on_request"
|
|
||||||
/>
|
|
||||||
<ServiceButton
|
|
||||||
url="https://discord.gg/vyvhk7d8bX"
|
|
||||||
checkUrl="minecraft://mc.dvorakmortonxd.dev:25565"
|
|
||||||
className="pterodactyl"
|
|
||||||
icon="🎮"
|
|
||||||
name="SMP"
|
|
||||||
desc="My Survival Minecraft Server"
|
|
||||||
access="on_request"
|
|
||||||
/>
|
|
||||||
<ServiceButton
|
|
||||||
url="https://navidrome.dvorakmortonxd.dev/"
|
|
||||||
className="funkwhale"
|
|
||||||
icon="🎸"
|
|
||||||
name="Navidrome"
|
|
||||||
desc="Music Streaming"
|
|
||||||
access="private"
|
|
||||||
/>
|
|
||||||
<ServiceButton
|
|
||||||
url="https://git.dvorakmortonxd.dev"
|
|
||||||
className="gitdxd"
|
|
||||||
icon="🐙"
|
|
||||||
name="GitDXD"
|
|
||||||
desc="Best Git Hosting OAT"
|
|
||||||
access="public"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="gitdxd-section" style={{ marginTop: "20px" }}>
|
|
||||||
<div className="tui-panel" style={panelStyle("var(--white)")}>
|
|
||||||
<span
|
|
||||||
className="tui-panel-label"
|
|
||||||
style={{ color: "var(--white)", borderColor: "var(--white)" }}
|
|
||||||
>
|
|
||||||
GITDXD.GIT
|
|
||||||
</span>
|
|
||||||
<div style={{ padding: "24px 16px 16px" }}>
|
|
||||||
<h2
|
|
||||||
style={{
|
|
||||||
fontSize: "2.4rem",
|
|
||||||
fontWeight: "bold",
|
|
||||||
color: "var(--white)",
|
|
||||||
marginBottom: "16px",
|
|
||||||
textAlign: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
GitDXD
|
|
||||||
</h2>
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
fontSize: "1.8rem",
|
|
||||||
color: "var(--white)",
|
|
||||||
fontWeight: "500",
|
|
||||||
textAlign: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
My Self-hosted Git Instance
|
|
||||||
</p>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
marginTop: "16px",
|
|
||||||
marginBottom: "16px",
|
|
||||||
borderRadius: "8px",
|
|
||||||
overflow: "hidden",
|
|
||||||
border: "1px solid rgba(255, 255, 255, 0.4)",
|
|
||||||
boxShadow: "0 0 30px rgba(180, 255, 125, 0.15)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src="/gitdxd.webp"
|
|
||||||
alt="GitDXD Preview"
|
|
||||||
width={1280}
|
|
||||||
height={720}
|
|
||||||
style={{ width: "100%", height: "auto", display: "block" }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
color: "var(--gray)",
|
|
||||||
marginTop: "12px",
|
|
||||||
lineHeight: "1.6",
|
|
||||||
textAlign: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
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{" "}
|
|
||||||
<span className="text-purple-500">
|
|
||||||
<a href="https://git.dvorakmortonxd.dev">git.dvorakmortonxd.dev</a>
|
|
||||||
</span>
|
|
||||||
.<span className="cursor-blink"></span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="commits-section" style={{ marginTop: "20px" }}>
|
|
||||||
<div className="tui-panel" style={panelStyle("var(--accent)")}>
|
|
||||||
<span
|
|
||||||
className="tui-panel-label"
|
|
||||||
style={{ color: "var(--accent)", borderColor: "var(--accent)" }}
|
|
||||||
>
|
|
||||||
CONTRIBUTIONS@GITDXD.GIT
|
|
||||||
</span>
|
|
||||||
<div className="commits-content" style={{ padding: "24px 12px 16px" }}>
|
|
||||||
<ContributionHeatmap />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="revival-section" style={{ marginTop: "20px" }}>
|
|
||||||
<div className="tui-panel" style={panelStyle("var(--white)")}>
|
|
||||||
<span
|
|
||||||
className="tui-panel-label"
|
|
||||||
style={{ color: "var(--white)", borderColor: "var(--white)" }}
|
|
||||||
>
|
|
||||||
REVIVAL.INC
|
|
||||||
</span>
|
|
||||||
<div style={{ padding: "24px 16px 16px" }}>
|
|
||||||
<h2
|
|
||||||
style={{
|
|
||||||
fontSize: "2.4rem",
|
|
||||||
fontWeight: "bold",
|
|
||||||
color: "var(--white)",
|
|
||||||
marginBottom: "32px",
|
|
||||||
textAlign: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Revival inc.
|
|
||||||
</h2>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
marginTop: "12px",
|
|
||||||
marginBottom: "12px",
|
|
||||||
borderRadius: "8px",
|
|
||||||
overflow: "hidden",
|
|
||||||
border: "1px solid rgba(0, 0, 0, 0.4)",
|
|
||||||
boxShadow: "0 0 30px rgba(255, 255, 255, 0.35)",
|
|
||||||
width: "80%",
|
|
||||||
margin: "12px auto",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src="/revivalinc.webp"
|
|
||||||
alt="revival inc thumbnail"
|
|
||||||
width={768}
|
|
||||||
height={576}
|
|
||||||
style={{ width: "100%", height: "auto", display: "block" }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
fontSize: "1.8rem",
|
|
||||||
color: "var(--white)",
|
|
||||||
fontWeight: "500",
|
|
||||||
textAlign: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
What is it?
|
|
||||||
</p>
|
|
||||||
<div style={{ color: "var(--gray)", marginTop: "12px", lineHeight: "1.6" }}>
|
|
||||||
<p>
|
|
||||||
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{" "}
|
|
||||||
<span className="text-purple-500">
|
|
||||||
<a href="https://martin-dv.itch.io/revival-inc">itch.io</a>
|
|
||||||
</span>{" "}
|
|
||||||
or play{" "}
|
|
||||||
<span className="text-purple-500">
|
|
||||||
<a href="https://dvorakmortonxd.dev/revival-inc">here</a>
|
|
||||||
</span>
|
|
||||||
.<span className="cursor-blink"></span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="rustywolf-section" style={{ marginTop: "20px" }}>
|
|
||||||
<div className="tui-panel" style={panelStyle("var(--white)")}>
|
|
||||||
<span
|
|
||||||
className="tui-panel-label"
|
|
||||||
style={{ color: "var(--white)", borderColor: "var(--white)" }}
|
|
||||||
>
|
|
||||||
RUSTYWOLF.RS
|
|
||||||
</span>
|
|
||||||
<div style={{ padding: "24px 16px 16px" }}>
|
|
||||||
<h2
|
|
||||||
style={{
|
|
||||||
fontSize: "2.4rem",
|
|
||||||
fontWeight: "bold",
|
|
||||||
color: "var(--white)",
|
|
||||||
marginBottom: "32px",
|
|
||||||
textAlign: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Introducing RustyWolf
|
|
||||||
</h2>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
marginTop: "12px",
|
|
||||||
marginBottom: "12px",
|
|
||||||
borderRadius: "8px",
|
|
||||||
overflow: "hidden",
|
|
||||||
border: "1px solid rgba(255, 255, 255, 0.4)",
|
|
||||||
boxShadow: "0 0 30px rgba(255, 255, 255, 0.2)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src="/RustyWolfPreview.webp"
|
|
||||||
alt="RustyWolf Browser Preview"
|
|
||||||
width={1280}
|
|
||||||
height={720}
|
|
||||||
style={{ width: "100%", height: "auto", display: "block" }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
fontSize: "1.8rem",
|
|
||||||
color: "var(--white)",
|
|
||||||
fontWeight: "500",
|
|
||||||
textAlign: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
What makes it so cool???
|
|
||||||
</p>
|
|
||||||
<div style={{ color: "var(--gray)", marginTop: "12px", lineHeight: "1.6" }}>
|
|
||||||
<p>
|
|
||||||
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{" "}
|
|
||||||
<span style={{ color: "var(--accent2)" }}>
|
|
||||||
<a href="https://git.dvorakmortonxd.dev/dvorakmortonxd/RustyWolf">
|
|
||||||
GitDXD
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
.<span className="cursor-blink"></span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<section className="projects">
|
|
||||||
<div className="subsection-wrapper">
|
|
||||||
<div className="subsection-panel" style={panelStyle("var(--white)")}>
|
|
||||||
<span
|
|
||||||
className="subsection-panel-label"
|
|
||||||
style={{ color: "var(--white)", borderColor: "var(--white)" }}
|
|
||||||
>
|
|
||||||
TECH_LIST.JSON
|
|
||||||
</span>
|
|
||||||
<h2
|
|
||||||
style={{
|
|
||||||
fontSize: "2rem",
|
|
||||||
fontWeight: "bold",
|
|
||||||
textAlign: "center",
|
|
||||||
margin: "4px 0 20px",
|
|
||||||
color: "var(--white)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
All my hardware equipment :)
|
|
||||||
</h2>
|
|
||||||
<div className="project-grid">
|
|
||||||
{techProjects.map((project) => (
|
|
||||||
<div key={project.title} className="project-card">
|
|
||||||
<div
|
|
||||||
className="project-image"
|
|
||||||
style={{ backgroundImage: `url(${project.image})` }}
|
|
||||||
/>
|
|
||||||
<h4 className="project-title">{project.title}</h4>
|
|
||||||
<p className="project-desc">{project.desc}</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="subsection-wrapper">
|
|
||||||
<div className="subsection-panel coding-panel" style={panelStyle("var(--accent)")}>
|
|
||||||
<span
|
|
||||||
className="subsection-panel-label"
|
|
||||||
style={{ color: "var(--accent)", borderColor: "var(--accent)" }}
|
|
||||||
>
|
|
||||||
PROJECTS.DOCX
|
|
||||||
</span>
|
|
||||||
<h2
|
|
||||||
style={{
|
|
||||||
fontSize: "2rem",
|
|
||||||
fontWeight: "bold",
|
|
||||||
textAlign: "center",
|
|
||||||
margin: "4px 0 20px",
|
|
||||||
color: "var(--accent)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
All the stuff I've worked on :))
|
|
||||||
</h2>
|
|
||||||
<div className="project-grid">
|
|
||||||
{codingProjects.map((project, idx) => (
|
|
||||||
<a
|
|
||||||
key={idx}
|
|
||||||
href={project.url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="project-card"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="project-image"
|
|
||||||
style={{ backgroundImage: `url(${project.img})` }}
|
|
||||||
/>
|
|
||||||
<h4 className="project-title">{project.title}</h4>
|
|
||||||
<p className="project-desc">{project.desc}</p>
|
|
||||||
</a>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="subsection-wrapper">
|
|
||||||
<div
|
|
||||||
className="subsection-panel friends-panel"
|
|
||||||
style={panelStyle("var(--white)", { backgroundColor: "rgba(255, 255, 255, 0.02)" })}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="subsection-panel-label"
|
|
||||||
style={{ color: "var(--white)", borderColor: "var(--white)" }}
|
|
||||||
>
|
|
||||||
FRIENDS.TSX
|
|
||||||
</span>
|
|
||||||
<h2
|
|
||||||
style={{
|
|
||||||
fontSize: "2rem",
|
|
||||||
fontWeight: "bold",
|
|
||||||
textAlign: "center",
|
|
||||||
margin: "4px 0 20px",
|
|
||||||
color: "var(--white)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Friend's websites and projects
|
|
||||||
</h2>
|
|
||||||
<div className="services-buttons" style={{ gap: "1rem" }}>
|
|
||||||
{friends.map((friend, idx) => (
|
|
||||||
<ServiceButton
|
|
||||||
key={idx}
|
|
||||||
url={friend.realUrl || friend.url}
|
|
||||||
className="friend"
|
|
||||||
icon={
|
|
||||||
<Image
|
|
||||||
src={friend.img}
|
|
||||||
alt={friend.name}
|
|
||||||
width={68}
|
|
||||||
height={68}
|
|
||||||
className="friend-icon"
|
|
||||||
style={{
|
|
||||||
borderRadius: "50%",
|
|
||||||
filter:
|
|
||||||
"brightness(0) invert(65%) hue-rotate(70deg) saturate(100%)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name={friend.name}
|
|
||||||
desc={friend.desc}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<footer className="footer">
|
|
||||||
<span className="footer-label">v2.1.0</span>
|
|
||||||
© 2025-2026 Martin Dvořák. All rights reserved (not really I dont care, just follow
|
|
||||||
the GPL3.0 License)
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +1,57 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useTheme } from "./useTheme";
|
import { useEffect, useState } from "react";
|
||||||
import TerminalLayout from "./TerminalLayout";
|
import { setTheme, useTheme } from "./useTheme";
|
||||||
|
import SkeuoLayout from "./SkeuoLayout";
|
||||||
import WindowsDesktop from "./WindowsDesktop";
|
import WindowsDesktop from "./WindowsDesktop";
|
||||||
import MobileHome from "./MobileHome";
|
import MobileHome from "./MobileHome";
|
||||||
|
|
||||||
export default function ThemeRoot() {
|
export default function ThemeRoot() {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const terminal = theme === "terminal";
|
const [phone, setPhone] = useState(false);
|
||||||
|
|
||||||
|
// The desktop simulation only exists on phone-sized viewports. On anything
|
||||||
|
// wider the page is always the skeuo theme, and a stale "desktop"
|
||||||
|
// preference is overridden here so nobody is stuck in a mode whose switch
|
||||||
|
// they cannot reach.
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window.matchMedia !== "function") return;
|
||||||
|
const mq = window.matchMedia("(max-width: 768px)");
|
||||||
|
const sync = () => {
|
||||||
|
setPhone(mq.matches);
|
||||||
|
if (!mq.matches && document.documentElement.getAttribute("data-theme") === "desktop") {
|
||||||
|
setTheme("skeuo");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sync();
|
||||||
|
mq.addEventListener("change", sync);
|
||||||
|
return () => mq.removeEventListener("change", sync);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const desktop = theme === "desktop";
|
||||||
|
|
||||||
// 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
{/*
|
||||||
className="skeuo-desktop-root"
|
The skeuo theme stays mounted (just hidden) while the desktop is up so
|
||||||
style={terminal ? { display: "none" } : undefined}
|
its scroll position survives a toggle. The desktop is mounted once the
|
||||||
>
|
viewport is known to be a phone and then kept mounted, so the Welcome
|
||||||
<WindowsDesktop />
|
window is only created on demand and isn't reopened by toggling.
|
||||||
|
*/}
|
||||||
|
<div className="sk-shell" style={desktop ? { display: "none" } : undefined}>
|
||||||
|
<SkeuoLayout />
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
className="skeuo-mobile-root"
|
{phone ? (
|
||||||
style={terminal ? { display: "none" } : undefined}
|
<>
|
||||||
>
|
<div className="skeuo-desktop-root" style={desktop ? undefined : { display: "none" }}>
|
||||||
<MobileHome />
|
<WindowsDesktop />
|
||||||
</div>
|
</div>
|
||||||
{terminal && <TerminalLayout />}
|
<div className="skeuo-mobile-root" style={desktop ? undefined : { display: "none" }}>
|
||||||
|
<MobileHome />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,27 @@ import { useServerInsertedHTML } from "next/navigation";
|
||||||
// without a flash. It is injected with useServerInsertedHTML so the <script>
|
// without a flash. It is injected with useServerInsertedHTML so the <script>
|
||||||
// lives in the SSR HTML stream instead of inside the React tree — React 19
|
// lives in the SSR HTML stream instead of inside the React tree — React 19
|
||||||
// logs an error for script tags rendered by components on the client.
|
// logs an error for script tags rendered by components on the client.
|
||||||
const INIT_SCRIPT = `(function(){try{var t=localStorage.getItem('theme');if(t==='terminal'||t==='skeuo'){document.documentElement.setAttribute('data-theme',t);}var ua=navigator.userAgent||'';var phone=/(iPhone|iPod|Android.+Mobile|Windows Phone|IEMobile|BlackBerry|Opera Mini|Mobile)/i.test(ua);document.documentElement.setAttribute('data-device',phone?'phone':'desktop');}catch(e){}})();`;
|
//
|
||||||
|
// Theme ids were renamed (skeuo = the site, desktop = the KDE sim) and the
|
||||||
|
// storage key moved from `theme` to `dmx-theme`, so this migrates the old
|
||||||
|
// values once: terminal -> skeuo, skeuo -> desktop.
|
||||||
|
const INIT_SCRIPT = `(function(){try{
|
||||||
|
var d=document.documentElement;
|
||||||
|
var mq=window.matchMedia('(max-width:768px)');
|
||||||
|
var stored=null;
|
||||||
|
try{stored=localStorage.getItem('dmx-theme');}catch(e){}
|
||||||
|
if(stored!=='skeuo'&&stored!=='desktop'){
|
||||||
|
var legacy=null;
|
||||||
|
try{legacy=localStorage.getItem('theme');}catch(e){}
|
||||||
|
if(legacy==='terminal'||legacy==='skeuo')stored=legacy==='terminal'?'skeuo':'desktop';
|
||||||
|
if(legacy!==null){try{localStorage.setItem('dmx-theme',stored||'skeuo');localStorage.removeItem('theme');}catch(e){}}
|
||||||
|
}
|
||||||
|
var theme=(stored==='desktop'&&mq.matches)?'desktop':'skeuo';
|
||||||
|
d.setAttribute('data-theme',theme);
|
||||||
|
var ua=navigator.userAgent||'';
|
||||||
|
var phone=/(iPhone|iPod|Android.+Mobile|Windows Phone|IEMobile|BlackBerry|Opera Mini|Mobile)/i.test(ua);
|
||||||
|
d.setAttribute('data-device',phone?'phone':'desktop');
|
||||||
|
}catch(e){}})();`;
|
||||||
|
|
||||||
export default function ThemeScript() {
|
export default function ThemeScript() {
|
||||||
// useServerInsertedHTML can fire several times during a single render pass;
|
// useServerInsertedHTML can fire several times during a single render pass;
|
||||||
|
|
|
||||||
|
|
@ -577,10 +577,10 @@ export default function WindowsDesktop() {
|
||||||
type="button"
|
type="button"
|
||||||
className="win-theme-toggle"
|
className="win-theme-toggle"
|
||||||
onClick={toggle}
|
onClick={toggle}
|
||||||
title={`Switch to ${theme === "skeuo" ? "gruvbox" : "Desktop"} theme`}
|
title={`Switch to ${theme === "desktop" ? "skeuo" : "Desktop"} theme`}
|
||||||
>
|
>
|
||||||
<span aria-hidden="true">{theme === "skeuo" ? "▚" : "❖"}</span>
|
<span aria-hidden="true">{theme === "desktop" ? "▚" : "❖"}</span>
|
||||||
<span>{theme === "skeuo" ? "Gruvbox" : "Desktop"}</span>
|
<span>{theme === "desktop" ? "Skeuo" : "Desktop"}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Clock className="win-clock" />
|
<Clock className="win-clock" />
|
||||||
|
|
|
||||||
|
|
@ -46,14 +46,14 @@ function WelcomeApp() {
|
||||||
<li>Drag windows around by their title bar.</li>
|
<li>Drag windows around by their title bar.</li>
|
||||||
<li>Use the buttons at the top-right to minimize, maximize or close.</li>
|
<li>Use the buttons at the top-right to minimize, maximize or close.</li>
|
||||||
<li>Open apps from the Gentoo launcher, bottom-left.</li>
|
<li>Open apps from the Gentoo launcher, bottom-left.</li>
|
||||||
<li>Switch back to the Gruvbox theme from the panel tray, bottom-right.</li>
|
<li>Switch back to the skeuo theme from the panel tray, bottom-right.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p className="app-preference">
|
<p className="app-preference">
|
||||||
If you'd prefer a normal website{" "}
|
If you'd prefer a normal website{" "}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="app-inline-link"
|
className="app-inline-link"
|
||||||
onClick={() => setTheme("terminal")}
|
onClick={() => setTheme("skeuo")}
|
||||||
>
|
>
|
||||||
click here
|
click here
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,15 @@
|
||||||
|
|
||||||
import { useSyncExternalStore } from "react";
|
import { useSyncExternalStore } from "react";
|
||||||
|
|
||||||
export type Theme = "skeuo" | "terminal";
|
/** `skeuo` is the site itself; `desktop` is the phone-only KDE simulation. */
|
||||||
|
export type Theme = "skeuo" | "desktop";
|
||||||
|
|
||||||
|
/** Width at or below which the desktop-mode switch is offered. */
|
||||||
|
export const DESKTOP_MODE_MAX_WIDTH = 768;
|
||||||
|
|
||||||
|
/** Renamed with the theme (was `theme`); ThemeScript migrates the old key. */
|
||||||
|
export const THEME_KEY = "dmx-theme";
|
||||||
|
export const LEGACY_THEME_KEY = "theme";
|
||||||
|
|
||||||
const EVENT = "theme-change";
|
const EVENT = "theme-change";
|
||||||
|
|
||||||
|
|
@ -16,20 +24,27 @@ function subscribe(callback: () => void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSnapshot(): Theme {
|
function getSnapshot(): Theme {
|
||||||
return document.documentElement.getAttribute("data-theme") === "terminal"
|
return document.documentElement.getAttribute("data-theme") === "desktop" ? "desktop" : "skeuo";
|
||||||
? "terminal"
|
|
||||||
: "skeuo";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getServerSnapshot(): Theme {
|
function getServerSnapshot(): Theme {
|
||||||
return "skeuo";
|
return "skeuo";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** True only on viewports that are allowed to run the desktop simulation. */
|
||||||
|
export function allowsDesktopMode() {
|
||||||
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
||||||
|
return window.matchMedia(`(max-width: ${DESKTOP_MODE_MAX_WIDTH}px)`).matches;
|
||||||
|
}
|
||||||
|
|
||||||
export function setTheme(theme: Theme) {
|
export function setTheme(theme: Theme) {
|
||||||
if (typeof document === "undefined") return;
|
if (typeof document === "undefined") return;
|
||||||
document.documentElement.setAttribute("data-theme", theme);
|
// Wide viewports can never enter (or stay in) the desktop sim: the switch is
|
||||||
|
// hidden there, so there would be no way back.
|
||||||
|
const next = theme === "desktop" && !allowsDesktopMode() ? "skeuo" : theme;
|
||||||
|
document.documentElement.setAttribute("data-theme", next);
|
||||||
try {
|
try {
|
||||||
localStorage.setItem("theme", theme);
|
localStorage.setItem(THEME_KEY, next);
|
||||||
} catch {
|
} catch {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
}
|
}
|
||||||
|
|
@ -40,6 +55,7 @@ export function useTheme() {
|
||||||
const theme = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
const theme = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
||||||
return {
|
return {
|
||||||
theme,
|
theme,
|
||||||
toggle: () => setTheme(theme === "skeuo" ? "terminal" : "skeuo"),
|
setTheme,
|
||||||
|
toggle: () => setTheme(theme === "skeuo" ? "desktop" : "skeuo"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2230
src/app/desktop.css
Normal file
2230
src/app/desktop.css
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,8 +1,28 @@
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
// Removed unused google fonts since we use local Custom Mono
|
import { Barlow_Semi_Condensed, Courier_Prime } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import ThemeScript from "./components/ThemeScript";
|
import ThemeScript from "./components/ThemeScript";
|
||||||
|
|
||||||
|
// The skeuo theme's two voices: an engraved-panel grotesque for metal
|
||||||
|
// (labels, headings, controls) and a typewriter for anything on paper.
|
||||||
|
// Both get an explicit fallback stack so the page still reads correctly
|
||||||
|
// if the webfont never arrives.
|
||||||
|
const panel = Barlow_Semi_Condensed({
|
||||||
|
variable: "--font-panel",
|
||||||
|
subsets: ["latin"],
|
||||||
|
weight: ["500", "600", "700"],
|
||||||
|
display: "swap",
|
||||||
|
fallback: ["Arial Narrow", "Helvetica Neue", "Arial", "sans-serif"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const typewriter = Courier_Prime({
|
||||||
|
variable: "--font-typewriter",
|
||||||
|
subsets: ["latin"],
|
||||||
|
weight: ["400", "700"],
|
||||||
|
display: "swap",
|
||||||
|
fallback: ["Courier New", "Courier", "monospace"],
|
||||||
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "dvorakmortonxd.dev",
|
title: "dvorakmortonxd.dev",
|
||||||
description: "Martin Dvořák's personal website.",
|
description: "Martin Dvořák's personal website.",
|
||||||
|
|
@ -25,7 +45,12 @@ export default function RootLayout({
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" data-theme="skeuo" suppressHydrationWarning>
|
<html
|
||||||
|
lang="en"
|
||||||
|
data-theme="skeuo"
|
||||||
|
suppressHydrationWarning
|
||||||
|
className={`${panel.variable} ${typewriter.variable}`}
|
||||||
|
>
|
||||||
<body className="antialiased">
|
<body className="antialiased">
|
||||||
<ThemeScript />
|
<ThemeScript />
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
import './page.css';
|
import './shared.css';
|
||||||
import './skeuo.css';
|
import './skeuo.css';
|
||||||
|
import './desktop.css';
|
||||||
import ThemeRoot from './components/ThemeRoot';
|
import ThemeRoot from './components/ThemeRoot';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,21 @@
|
||||||
|
/* ============================================================
|
||||||
|
SHARED COMPONENT SHEET
|
||||||
|
------------------------------------------------------------
|
||||||
|
Styles for the components both shells render: ServiceButton,
|
||||||
|
ContributionHeatmap, JokerLogo, the project cards and the
|
||||||
|
social buttons.
|
||||||
|
|
||||||
|
- the skeuo theme (skeuo.css) overrides them scoped to
|
||||||
|
`html[data-theme="skeuo"]`
|
||||||
|
- the KDE desktop mode (desktop.css) overrides them scoped to
|
||||||
|
`.app-surface` / `.win-body` inside its windows
|
||||||
|
|
||||||
|
The `:root` block below is only the fallback those components
|
||||||
|
inherit where a shell overrides nothing; each shell defines
|
||||||
|
its own palette on top of it.
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
/* Monocraft is loaded for the desktop mode's terminal-style windows. */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Monocraft';
|
font-family: 'Monocraft';
|
||||||
src: url('/Monocraft.ttf') format('truetype');
|
src: url('/Monocraft.ttf') format('truetype');
|
||||||
|
|
@ -20,21 +38,22 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* Gruvbox Dark Theme */
|
/* fallback palette for the shared components (never seen on its own —
|
||||||
|
the skeuo theme and the desktop mode both define their own) */
|
||||||
--bg: #1d2021;
|
--bg: #1d2021;
|
||||||
--bg-soft: #282828;
|
--bg-soft: #282828;
|
||||||
--bg-hard: #32302f;
|
--bg-hard: #32302f;
|
||||||
--fg: #ebdbb2;
|
--fg: #ebdbb2;
|
||||||
--gray: #a89984;
|
--gray: #a89984;
|
||||||
--prettypurple: #733999;
|
--prettypurple: #733999;
|
||||||
--accent: #fabd2f; /* Gruvbox Yellow */
|
--accent: #fabd2f; /* fallback amber */
|
||||||
--accent2: #fe8019; /* Gruvbox Orange */
|
--accent2: #fe8019; /* fallback orange */
|
||||||
--accent3: #d79921; /* Gruvbox Dark Yellow */
|
--accent3: #d79921; /* fallback deep amber */
|
||||||
--green: #b8bb26; /* Gruvbox Green */
|
--green: #b8bb26; /* fallback green */
|
||||||
--aqua: #8ec07c; /* Gruvbox Aqua */
|
--aqua: #8ec07c; /* fallback aqua */
|
||||||
--blue: #83a598; /* Gruvbox Blue */
|
--blue: #83a598; /* fallback blue */
|
||||||
--purple: #d3869b; /* Gruvbox Purple */
|
--purple: #d3869b; /* fallback purple */
|
||||||
--red: #fb4934; /* Gruvbox Red */
|
--red: #fb4934; /* fallback red */
|
||||||
--darkpink: #ff0043; /* Custom Dark Pink */
|
--darkpink: #ff0043; /* Custom Dark Pink */
|
||||||
--font-custom-mono: 'Monocraft', 'JetBrains Mono', 'Fira Code', 'Source Code Pro', 'Courier New', monospace;
|
--font-custom-mono: 'Monocraft', 'JetBrains Mono', 'Fira Code', 'Source Code Pro', 'Courier New', monospace;
|
||||||
--card-bg: rgba(40, 40, 40, 0.65);
|
--card-bg: rgba(40, 40, 40, 0.65);
|
||||||
|
|
@ -1234,23 +1253,3 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================
|
|
||||||
HEADER THEME SWITCH (terminal theme)
|
|
||||||
============================================ */
|
|
||||||
.header-theme-btn {
|
|
||||||
margin-left: auto;
|
|
||||||
padding: 4px 12px;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid var(--gray);
|
|
||||||
color: var(--fg);
|
|
||||||
font-family: var(--font-custom-mono), 'JetBrains Mono', monospace;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-theme-btn:hover {
|
|
||||||
border-color: var(--accent);
|
|
||||||
color: var(--accent);
|
|
||||||
box-shadow: 0 0 8px rgba(250, 189, 47, 0.35);
|
|
||||||
}
|
|
||||||
3679
src/app/skeuo.css
3679
src/app/skeuo.css
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue