rebuild the web theme as a physical desk (walnut surface, monitor + keyboard, real rack, drive drawer, paper tags, dot-matrix report, brass plaques)

This commit is contained in:
Martin Dvořák 2026-09-30 09:09:47 +00:00
commit cfc256c7bd
9 changed files with 1604 additions and 869 deletions

View file

@ -2,15 +2,16 @@
> The source code for my personal website, **[dvorakmortonxd.dev](https://dvorakmortonxd.dev)**. > The source code for my personal website, **[dvorakmortonxd.dev](https://dvorakmortonxd.dev)**.
It's a **skeuomorphic personal site**: a dark homelab desk built from anodised aluminium, OLED It's a **skeuomorphic personal site**: a walnut desk with graphite hardware, cream paper and
glass, carbon fibre and paper, with the whole KDE desktop simulation kept behind a physical brass details resting on it, with the whole KDE desktop simulation kept behind a physical power
power switch. Two modes, one content set: switch. Two modes, one content set:
- **Web** (default) — the homelab desk. A 4U rack carries the headline on an engraved nameplate - **Web** (default) — the desk. A monitor carries the headline with a keyboard in front of it
and a live console readout; services are rack units with status LEDs, the 18 coding projects and a small rack beside it; services are 1U rack units with health LEDs, the 18 coding
are labelled SSD caddies in a drive drawer, hardware hangs on a pegboard, the contribution projects are labelled drives in a drawer, the machines hang as paper tags on a pegboard, the
heatmap is an LED matrix and the socials are a patch panel. Textures are CC0 — see contribution heatmap is printed on continuous dot-matrix paper, friends are business cards,
[`/credits`](http://localhost:3000/credits). the gym log is a clipboard and the socials are a patch panel. Textures are CC0 — see
[`/credits`](https://dvorakmortonxd.dev/credits).
- **Desktop mode** — the KDE Plasma / Windows-style **desktop simulation**: draggable - **Desktop mode** — the KDE Plasma / Windows-style **desktop simulation**: draggable
snap-to-grid icons, resizable/floating windows, a Start menu, taskbar, live clock and a snap-to-grid icons, resizable/floating windows, a Start menu, taskbar, live clock and a
working in-browser terminal (Alacritty). Reached from the power switch and hidden on phones. working in-browser terminal (Alacritty). Reached from the power switch and hidden on phones.
@ -23,8 +24,9 @@ heatmap**, gym SBD and links to friends' sites.
- **Web theme is the landing view** — the site always opens on the desk; the theme is - **Web theme is the landing view** — the site always opens on the desk; the theme is
deliberately not persisted, so a reload never drops you into the desktop. deliberately not persisted, so a reload never drops you into the desktop.
- **Skeuomorphic hardware** — rack, LEDs, rocker switch, screws, vents and panels are CSS - **Skeuomorphic materials** — walnut desk, graphite faceplates, cream paper, brass plaques and
materials over five CC0 textures; every section is a real-world object rather than a card. amber LEDs, all CSS over five CC0 textures. Every section is a physical object with a contact
shadow; nothing is pure white on black or grey on black.
- **Desktop mode (desktop-only)** — a physical power switch in the top rail (and in the footer) - **Desktop mode (desktop-only)** — a physical power switch in the top rail (and in the footer)
flips to the KDE-style desktop for the session. Phones never get it: the switch is hidden, so flips to the KDE-style desktop for the session. Phones never get it: the switch is hidden, so
the old iOS-style phone home screen is gone. the old iOS-style phone home screen is gone.
@ -64,15 +66,15 @@ heatmap**, gym SBD 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:** `src/app/globals.css` holds the palette tokens, base styles, the Tailwind v4 - **Styling:** `src/app/globals.css` holds the palette tokens, base styles, the Tailwind v4
`@theme`/`@utility` classes and the scroll reveals. `src/app/web.css` is the skeuomorphic `@theme`/`@utility` classes and the scroll reveals. `src/app/web.css` is the material system
material system (metal, OLED glass, paper, carbon, rubber, plastic, LEDs, screws, vents, (walnut desk, graphite metal, cream paper, brass, OLED glass, LEDs, screws, rack rails, rocker
switches) and the object layouts. `page.css` keeps the shared component styles the desktop switch) and the object layouts. `page.css` keeps the shared component styles the desktop apps
apps still use, and `skeuo.css` is the KDE desktop. still use, and `skeuo.css` is the KDE desktop.
- **Fonts:** Newsreader (headlines, paper) + IBM Plex Sans/Mono (body, machine labels) via - **Fonts:** Newsreader (headlines, paper) + IBM Plex Sans/Mono (body, machine labels) via
`next/font/google`; the desktop keeps its system sans stack. `next/font/google`; the desktop keeps its system sans stack.
- **Textures:** five CC0 materials from ambientCG, downscaled to 512 px in `public/textures`. - **Textures:** five CC0 materials from ambientCG — `desk` (walnut), `metal`, `paper`, `carbon`,
Attribution is on `/credits`. `plastic` — downscaled to 512 px in `public/textures`. Attribution is on `/credits`.
- **Version:** `3.1.0` (see `package.json`) - **Version:** `3.2.0` (see `package.json`)
## 🚀 Running Locally ## 🚀 Running Locally

View file

@ -34,19 +34,20 @@ 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**. The default view is a Personal portfolio site for **dvorakmortonxd.dev**. The default view is a
**skeuomorphic homelab desk** (a rack, LEDs, paper, anodised metal); a physical **skeuomorphic desk**: a walnut surface with a monitor, keyboard, rack, drive
power switch flips to a **KDE Plasma desktop simulation**. Phones only ever get drawer, pegboard, dot-matrix report, business cards, clipboard and patch panel
the desk. resting on it. A physical power switch flips to a **KDE Plasma desktop
simulation**. Phones only ever get the desk.
- Framework: **Next.js 16** (App Router, Turbopack), **React 19**, TypeScript. - Framework: **Next.js 16** (App Router, Turbopack), **React 19**, TypeScript.
- Styling: **Tailwind v4** for layout plus **`web.css`**, the skeuomorphic - Styling: **Tailwind v4** for layout plus **`web.css`**, the material system
material system (metal, OLED glass, paper, carbon, rubber, plastic, LEDs, (walnut desk, graphite metal, cream paper, brass, OLED glass, LEDs, screws,
screws, vents, switches) over five CC0 textures in `public/textures` rack rails, rocker switch) over five CC0 textures in `public/textures`
(credited on `/credits`). `globals.css` holds the tokens, base styles and the (credited on `/credits`). `globals.css` holds the tokens, base styles and the
reveals; `skeuo.css` and `page.css` are the desktop and the shared reveals; `skeuo.css` and `page.css` are the desktop and the shared
desktop-app styles. desktop-app styles.
- Path alias: `@/*` → `src/*`. - Path alias: `@/*` → `src/*`.
- Version: `3.1.0` (see `package.json`). - Version: `3.2.0` (see `package.json`).
### Scripts ### Scripts
```bash ```bash
@ -123,11 +124,11 @@ scripts/
desktop is up so its scroll position survives; the desktop mounts on demand desktop is up so its scroll position survives; the desktop mounts on demand
(phones can never reach `skeuo`). (phones can never reach `skeuo`).
Modes: `web` = the skeuomorphic homelab desk (default/landing), `skeuo` = KDE Modes: `web` = the skeuomorphic desk (default/landing), `skeuo` = KDE Plasma
Plasma desktop. desktop.
The desk uses **five CC0 textures** from ambientCG (`public/textures`: metal, The desk uses **five CC0 textures** from ambientCG (`public/textures`: desk
paper, carbon, rubber, plastic), downscaled to 512 px and credited on walnut, metal, paper, carbon, plastic), downscaled to 512 px and credited on
`/credits`. The social SVGs are used as monochrome CSS masks (`.social-mask`); `/credits`. The social SVGs are used as monochrome CSS masks (`.social-mask`);
`linkedin.svg` is a filled badge that masks to a plain square, so `web-ui.tsx` `linkedin.svg` is a filled badge that masks to a plain square, so `web-ui.tsx`
renders an inline mark for it instead. The desktop still uses the image assets. renders an inline mark for it instead. The desktop still uses the image assets.
@ -235,7 +236,12 @@ not free placement. Don't confuse the two.
collided with Tailwind's `w-screen` (`width: 100vw`) and silently stretched collided with Tailwind's `w-screen` (`width: 100vw`) and silently stretched
every panel to the viewport. Also remember `web.css` is unlayered, so its every panel to the viewport. Also remember `web.css` is unlayered, so its
properties beat Tailwind utilities on the same element; use a dedicated class properties beat Tailwind utilities on the same element; use a dedicated class
(e.g. `.w-navlink`) or `!` when a utility must win. (e.g. `.w-navlink`, `.w-corner-tr`) or `!` when a utility must win. (`.w-screw`
sets `position: relative` for its slot, which is why screws are placed with
`.w-corner-*` instead of Tailwind's `absolute`.)
- Angles and offsets that hover states need to override live in CSS custom
properties (e.g. `--tilt` on `.w-drive`), never in inline `style` transforms —
an inline transform wins over `:hover`.
- The desk reveals blocks with `.reveal` (see `Reveal` in `web-ui.tsx`): hidden by - The desk reveals blocks with `.reveal` (see `Reveal` in `web-ui.tsx`): hidden by
default, `.is-in` shows. On load the first screenful cascades top-to-bottom default, `.is-in` shows. On load the first screenful cascades top-to-bottom
(90ms per block, in DOM order); the rest reveal when they scroll in. **Wrap (90ms per block, in DOM order); the rest reveal when they scroll in. **Wrap

View file

@ -1,6 +1,6 @@
{ {
"name": "dvorakmortonxd", "name": "dvorakmortonxd",
"version": "3.1.0", "version": "3.2.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "node scripts/dev-server.mjs", "dev": "node scripts/dev-server.mjs",

BIN
public/textures/desk.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View file

@ -1,13 +1,17 @@
"use client"; "use client";
import { useState } from "react"; import { useState, type CSSProperties } from "react";
import { useTheme } from "./useTheme"; import { useTheme } from "./useTheme";
import { codingProjects, friends, socials, techProjects } from "../data"; import { codingProjects, friends, socials, techProjects } from "../data";
import { import {
Bay, Keyboard,
ContributionsPanel,
Led, Led,
MiniRack,
Monitor,
Plaque,
Report,
Reveal, Reveal,
SectionHead,
ServiceUnit, ServiceUnit,
SocialGlyph, SocialGlyph,
type ServiceAccess, type ServiceAccess,
@ -17,10 +21,10 @@ import {
const NAV = [ const NAV = [
{ href: "#about", label: "about" }, { href: "#about", label: "about" },
{ href: "#services", label: "rack" }, { href: "#services", label: "services" },
{ href: "#projects", label: "projects" }, { href: "#projects", label: "projects" },
{ href: "#hardware", label: "inventory" }, { href: "#hardware", label: "machines" },
{ href: "#contact", label: "ports" }, { href: "#contact", label: "contact" },
]; ];
const SERVICES: { const SERVICES: {
@ -40,20 +44,9 @@ const SERVICES: {
]; ];
const LIFTS = [ const LIFTS = [
{ lift: "squat", weight: "125", unit: "kg", date: "05 Jan 2025" }, { lift: "squat", weight: "125", date: "05 Jan 2025" },
{ lift: "bench", weight: "111", unit: "kg", date: "29 May 2026" }, { lift: "bench", weight: "111", date: "29 May 2026" },
{ lift: "deadlift", weight: "165", unit: "kg", date: "17 Dec 2024" }, { lift: "deadlift", weight: "165", date: "17 Dec 2024" },
];
const NEOFETCH: [string, string][] = [
["host", "Proxmox VE · home rack"],
["os", "Gentoo Linux (x86_64)"],
["cpu", "Xeon E5-2680 v4 · 14c/28t"],
["gpu", "RTX 3070 · passthrough"],
["memory", "68 GB DDR4 RDIMM"],
["storage", "256 GB NVMe · 1 TB · 750 GB"],
["services", "6 containers, 0 subscriptions"],
["uptime", "17 years"],
]; ];
/** Titles carry their state in parentheses, e.g. "dxdwm (Discontinued)". */ /** Titles carry their state in parentheses, e.g. "dxdwm (Discontinued)". */
@ -62,12 +55,12 @@ function splitStatus(title: string) {
return m ? { name: m[1], status: m[2] } : { name: title, status: null }; return m ? { name: m[1], status: m[2] } : { name: title, status: null };
} }
function statusKind(status: string | null): "green" | "amber" | "red" | "off" { function statusKind(status: string | null): "on" | "amber" | "off" {
if (!status) return "off"; if (!status) return "off";
const s = status.toLowerCase(); const s = status.toLowerCase();
if (s.includes("finished") || s.includes("available")) return "green"; if (s.includes("finished") || s.includes("available")) return "on";
if (s.includes("wip") || s.includes("unfinished")) return "amber"; if (s.includes("wip") || s.includes("unfinished")) return "amber";
return "red"; return "off";
} }
/* ------------------------------------------------------------------ page */ /* ------------------------------------------------------------------ page */
@ -76,30 +69,19 @@ export default function WebSite() {
const { setTheme } = useTheme(); const { setTheme } = useTheme();
const [menuOpen, setMenuOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false);
const stats = [
{ value: String(SERVICES.length), label: "self-hosted services" },
{ value: String(codingProjects.length), label: "projects on the shelf" },
{ value: String(techProjects.length), label: "machines logged" },
{ value: "401", label: "kg best SBD total" },
];
return ( return (
<div className="w-root"> <div className="w-page">
{/* ----------------------------------------------------------- rail */} {/* -------------------------------------------------------------- rail */}
<header className="w-rail w-metal-flat" style={{ borderRadius: 0 }}> <header className="w-rail w-mt-metal-flush" style={{ borderRadius: 0 }}>
<div className="shell flex h-16 items-center gap-5"> <div className="mx-auto flex h-16 w-full max-w-[1120px] items-center gap-5 px-5 sm:px-8">
<a href="#top" className="flex shrink-0 items-center gap-3" aria-label="Back to top"> <a href="#top" className="flex shrink-0 items-center gap-2.5" aria-label="Back to top">
<Led kind="green" pulse /> <Led kind="on" pulse slow />
<span className="w-rack__title">dvorakmortonxd.local</span> <span className="w-wordmark w-engrave">dvorakmortonxd</span>
</a> </a>
<nav className="ml-auto hidden items-center gap-6 md:flex" aria-label="Main"> <nav className="ml-auto hidden items-center gap-6 md:flex" aria-label="Main">
{NAV.map((item) => ( {NAV.map((item) => (
<a <a key={item.href} href={item.href} className="w-navlink">
key={item.href}
href={item.href}
className="w-navlink"
>
{item.label} {item.label}
</a> </a>
))} ))}
@ -109,15 +91,16 @@ export default function WebSite() {
<button <button
type="button" type="button"
aria-label="Switch to Desktop mode" aria-label="Switch to Desktop mode"
data-desktop-mode="off"
onClick={() => setTheme("skeuo")} onClick={() => setTheme("skeuo")}
className="desktop-toggle flex items-center gap-3" className="desktop-toggle flex items-center gap-3"
title="Flip to Desktop mode" title="Flip to Desktop mode"
> >
<span className="w-plate">desktop</span> <span className="w-mono hidden text-[0.6rem] tracking-[0.2em] text-[var(--ftext-faint)] sm:inline">
<span className="w-switch"> DESKTOP
<span className="w-switch__knob" /> </span>
<Led kind="off" className="w-switch__led" /> <span className="w-rocker">
<span className="w-rocker__knob" />
<Led kind="off" className="w-rocker__led" />
</span> </span>
</button> </button>
@ -127,7 +110,7 @@ export default function WebSite() {
onClick={() => setMenuOpen((v) => !v)} onClick={() => setMenuOpen((v) => !v)}
aria-expanded={menuOpen} aria-expanded={menuOpen}
aria-label={menuOpen ? "Close menu" : "Open menu"} aria-label={menuOpen ? "Close menu" : "Open menu"}
className="w-btn px-3 py-2" className="w-keybtn !px-3 !py-2"
> >
{menuOpen ? "close" : "menu"} {menuOpen ? "close" : "menu"}
</button> </button>
@ -136,13 +119,16 @@ export default function WebSite() {
</div> </div>
{menuOpen ? ( {menuOpen ? (
<nav className="shell w-metal-flat mb-3 mt-1 flex flex-col gap-1 p-3 md:hidden" aria-label="Main"> <nav
className="w-mt-metal-flush mx-auto mb-3 flex w-[calc(100%-2.5rem)] max-w-[1120px] flex-col gap-1 rounded-lg p-3 md:hidden"
aria-label="Main"
>
{NAV.map((item) => ( {NAV.map((item) => (
<a <a
key={item.href} key={item.href}
href={item.href} href={item.href}
onClick={() => setMenuOpen(false)} onClick={() => setMenuOpen(false)}
className="w-unit__name block border-b border-[rgba(255,255,255,0.06)] py-3" className="w-mono border-b border-[rgba(255,226,180,0.07)] py-3 text-[0.78rem] text-[var(--ftext-dim)] last:border-b-0"
> >
{item.label} {item.label}
</a> </a>
@ -153,177 +139,205 @@ export default function WebSite() {
<span id="top" /> <span id="top" />
<main className="pt-16"> <main className="pt-24 sm:pt-28">
{/* --------------------------------------------------------- hero */} {/* ------------------------------------------------------------ hero */}
<section className="shell pt-12 pb-14 sm:pt-16"> <section className="mx-auto w-full max-w-[1120px] px-5 sm:px-8">
<Reveal> <div className="grid gap-10 lg:grid-cols-[minmax(0,1.5fr)_minmax(0,1fr)] lg:items-end lg:gap-8">
<div className="w-rack w-metal"> <Reveal>
<div className="w-rack__head"> <Monitor>
<Led kind="green" pulse /> <p className="w-tft">
<span className="w-rack__title">dvorakmortonxd.local</span> <span className="a">martin@dvorakmortonxd</span>:~$ whoami
<span className="w-unit__meta ml-auto hidden sm:block">4U · asus p9x79 · 24/7</span> <span className="w-caret ml-2" />
<span className="w-screw ml-2 hidden sm:block" /> </p>
<span className="w-screw hidden sm:block" /> <h1 className="w-serif mt-5 text-[clamp(1.85rem,4.2vw,2.85rem)] text-[var(--ftext)]">
My own little internet — one rack, zero subscriptions.
</h1>
<p className="w-prose mt-5 max-w-[54ch] text-[var(--ftext-dim)]">
I&rsquo;m Martin Dvořák. Git, media, music, an AI box and a Minecraft server all run on a
Proxmox machine in my room — plus the hardware graveyard I keep rescuing from e-waste.
</p>
<p className="mt-7 flex flex-wrap gap-x-8 gap-y-3">
<a href="#services" className="w-cmd">
$ open rack --services
</a>
<a href="#projects" className="w-cmd">
$ open drawer --projects
</a>
</p>
</Monitor>
<div className="-mt-1 px-1 pt-7 sm:px-8">
<Keyboard />
</div> </div>
</Reveal>
<div className="grid gap-3 lg:grid-cols-[minmax(0,1.3fr)_minmax(0,1fr)]"> <Reveal delay={120}>
<div className="w-nameplate min-w-0"> <div className="flex flex-col gap-6">
<p className="w-kicker">homelab · czech republic · online</p> <MiniRack />
<h1 className="mt-4 text-[2rem] leading-[1.05] sm:text-[2.6rem] lg:text-[3rem]"> <div className="grid grid-cols-2 gap-3">
My own little internet — one rack, zero subscriptions. <Plaque value={String(SERVICES.length)} label="services" />
</h1> <Plaque value={String(codingProjects.length)} label="projects" />
<p className="w-prose mt-5 max-w-[48ch]"> <Plaque value={String(techProjects.length)} label="machines" />
I’m Martin Dvořák. Git, media, music, an AI box and a Minecraft server all run on a <Plaque value="401" label="kg best SBD" />
Proxmox machine in my room — plus the hardware graveyard I keep rescuing from e-waste.
</p>
<div className="mt-7 flex flex-wrap items-center gap-3">
<a href="#services" className="w-btn w-btn--accent">
<Led kind="amber" /> see what I run
</a>
<a href="#projects" className="w-btn">
open the drive drawer
</a>
</div>
</div>
<div className="w-panel min-w-0 p-4">
<div className="w-rack__head !border-b-0 !pb-2">
<span className="w-rack__title">console — neofetch</span>
<Led kind="green" className="ml-auto" />
</div>
<div className="w-screen-text">
{NEOFETCH.map(([k, v]) => (
<p key={k} className="flex gap-3">
<span className="k w-[8ch] shrink-0">{k}</span>
<span className="v min-w-0 truncate">{v}</span>
</p>
))}
<p className="mt-1">
<span className="k">$</span> <span className="w-cursor" />
</p>
</div>
</div> </div>
</div> </div>
</Reveal>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4"> </div>
{stats.map((s) => (
<div key={s.label} className="w-unit w-metal-flat">
<Led kind="amber" />
<span className="min-w-0">
<span className="w-digits block text-[1.5rem] leading-none">{s.value}</span>
<span className="w-unit__meta mt-1 block truncate">{s.label}</span>
</span>
</div>
))}
</div>
</div>
</Reveal>
</section> </section>
<div className="shell flex flex-col gap-16 pb-20 sm:gap-24"> <div className="mx-auto flex w-full max-w-[1120px] flex-col gap-24 px-5 pt-28 pb-24 sm:gap-28 sm:px-8">
{/* -------------------------------------------------------- about */} {/* --------------------------------------------------------- about */}
<Bay id="about" index="01" label="about" title="Who is behind the rack"> <section id="about" className="scroll-mt-28">
<div className="grid gap-6 md:grid-cols-2"> <Reveal>
<Reveal delay={40}> <SectionHead index="01" label="about" title="Who is behind the rack" />
<article className="w-paper w-card h-full"> </Reveal>
<div className="flex items-center gap-3"> <div className="grid gap-7 lg:grid-cols-[minmax(0,1.55fr)_minmax(0,1fr)] lg:items-start">
<span className="w-grommet" /> <Reveal delay={60}>
<span className="w-kicker !text-[var(--paper-ink-soft)]">manifest.txt</span> <article className="w-mt-paper w-sheet">
<div className="w-sheet__head">
<span>manifest.txt</span>
<span className="ml-auto">printed on the desk</span>
</div>
<div className="mt-5 grid gap-x-9 gap-y-4 sm:grid-cols-2">
<p className="w-prose">
I&rsquo;m a sophomore at{" "}
<a className="w-link" href="https://sssvt.cz" target="_blank" rel="noopener noreferrer">
sssvt.cz
</a>
, studying network management and administration. I wanted to go there since 7th
grade, so getting in was a good day.
</p>
<p className="w-prose">
I&rsquo;m a homelabber first: my own Git, media, music, AI and game servers, most of it
running on hardware other people threw away.
</p>
<p className="w-prose">
A laptop collection from 2009 to 2025, mostly e-waste rescues — ThinkPads, MacBooks,
a Legion Go, an Xbox with a bad-update exploit and one very dead 2010 unibody.
</p>
<p className="w-prose">
Away from the rack: PPL training 3–5× a week, Terraria (~1300 h), Minecraft modpacks
and the occasional game jam.
</p>
</div> </div>
<p className="w-prose mt-4">
I’m a sophomore at{" "}
<a className="w-link" href="https://sssvt.cz" target="_blank" rel="noopener noreferrer">
sssvt.cz
</a>
, studying network management and administration. I wanted to go there since 7th
grade, so getting in was a good day.
</p>
<p className="w-prose mt-3">
I’m a homelabber first: my own Git, media, music, AI and game servers, most of it
running on hardware other people threw away.
</p>
</article> </article>
</Reveal> </Reveal>
<Reveal delay={130}>
<article className="w-paper w-card w-note--tilt h-full"> <Reveal delay={150}>
<div className="flex items-center gap-3"> <aside className="w-mt-paper w-cardindex lg:mt-12">
<span className="w-grommet" /> <p className="w-mono text-[0.62rem] tracking-[0.16em] text-[var(--ink-soft)]">
<span className="w-kicker !text-[var(--paper-ink-soft)]">inventory.txt</span> INDEX CARD
</div>
<p className="w-prose mt-4">
A laptop collection from 2009 to 2025, mostly e-waste rescues — ThinkPads, MacBooks,
a Legion Go, an Xbox with a bad-update exploit and one very dead 2010 unibody.
</p> </p>
<p className="w-prose mt-3"> <p className="w-serif mt-3 text-[1.15rem] text-[var(--ink)]">
Away from the rack: PPL training 3–5× a week, Terraria (~1300 h), Minecraft modpacks &ldquo;If it has a fan, I&rsquo;ll find a use for it.&rdquo;
and the occasional game jam.
</p> </p>
</article> <div className="w-rule my-4" />
<ul className="w-mono space-y-2 text-[0.66rem] text-[var(--ink-soft)]">
<li>school · sssvt.cz</li>
<li>gym · PPL 3–5×/week</li>
<li>games · Terraria, Minecraft</li>
<li>currently · bulking season</li>
</ul>
</aside>
</Reveal> </Reveal>
</div> </div>
</Bay> </section>
{/* ---------------------------------------------------------- now */} {/* ----------------------------------------------------------- now */}
<Bay id="now" index="02" label="now" title="On the bench right now"> <section id="now" className="scroll-mt-28">
<Reveal delay={40}> <Reveal>
<div className="grid gap-4 lg:grid-cols-[minmax(0,1.25fr)_minmax(0,1fr)]"> <SectionHead index="02" label="now" title="On the bench right now" />
<article className="w-paper w-note min-w-0"> </Reveal>
<span className="w-kicker !text-[var(--paper-ink-soft)]">now.txt · updated 09/2026</span> <div className="grid gap-7 lg:grid-cols-[minmax(0,1.3fr)_minmax(0,1fr)] lg:items-start">
<p className="w-prose mt-4"> <Reveal delay={60}>
I’m hosting an SMP — message me on Discord if you want in. I’m also cooking most days, <div className="w-sticky">
bulking through the non-summer seasons. <p className="w-mono text-[0.62rem] tracking-[0.16em] text-[#6b6047]">
NOW.TXT · UPDATED 09/2026
</p> </p>
<p className="w-prose mt-3"> <p className="w-prose mt-4 text-[#4a4232]">
I&rsquo;m hosting an SMP — message me on Discord if you want in. I&rsquo;m also cooking most
days, bulking through the non-summer seasons.
</p>
<p className="w-prose mt-3 text-[#4a4232]">
Me and my friend{" "} Me and my friend{" "}
<a className="w-link" href="https://skopec.dev" target="_blank" rel="noopener noreferrer"> <a className="w-link" href="https://skopec.dev" target="_blank" rel="noopener noreferrer">
Pepa Pepa
</a>{" "} </a>{" "}
are building an education app so we can study more efficiently. It’s called{" "} are building an education app so we can study more efficiently. It&rsquo;s called{" "}
<a className="w-link" href="https://git.dvorakmortonxd.dev/edu/maturita.c" target="_blank" rel="noopener noreferrer"> <a
className="w-link"
href="https://git.dvorakmortonxd.dev/edu/maturita.c"
target="_blank"
rel="noopener noreferrer"
>
maturita.c maturita.c
</a> </a>
. .
</p> </p>
</article> </div>
</Reveal>
<div className="w-panel min-w-0 p-4"> <Reveal delay={140}>
<div className="w-rack__head !border-b-0 !pb-2"> <div className="flex flex-col gap-6">
<span className="w-rack__title">status</span> <div className="w-mt-paper w-cardindex w-cardindex--alt">
<Led kind="green" pulse className="ml-auto" /> <p className="w-mono text-[0.62rem] tracking-[0.16em] text-[var(--ink-soft)]">
BENCH STATUS
</p>
<ul className="w-mono mt-3 space-y-2 text-[0.7rem] text-[var(--ink)]">
<li className="flex items-center gap-2">
<Led kind="on" /> smp · accepting players
</li>
<li className="flex items-center gap-2">
<Led kind="amber" pulse /> maturita.c · in development
</li>
<li className="flex items-center gap-2">
<Led kind="on" /> gym · PPL 3–5×/week
</li>
</ul>
</div> </div>
<div className="w-screen-text"> <i className="w-pencil ml-6 w-44" aria-hidden="true" />
<p><span className="k">smp</span> <span className="v">accepting players</span></p> </div>
<p><span className="k">bulk</span> <span className="v">in progress</span></p> </Reveal>
<p><span className="k">maturita.c</span> <span className="v">in development</span></p> </div>
<p><span className="k">gym</span> <span className="v">PPL · 3–5×/week</span></p> </section>
{/* ------------------------------------------------------ services */}
<section id="services" className="scroll-mt-28">
<Reveal>
<SectionHead index="03" label="rack" title="Six machines, one shelf" />
</Reveal>
<Reveal delay={60}>
<div className="w-mt-metal w-rack">
<div className="w-rack__top">
<Led kind="on" pulse slow />
<span className="w-engrave text-[0.62rem] tracking-[0.2em]">RACK 01 · SERVICES</span>
<span className="w-mono ml-auto hidden text-[0.6rem] tracking-[0.14em] text-[var(--ftext-faint)] sm:inline">
health checked from this page
</span>
<span className="w-rack__vent w-vents" />
</div>
<div className="w-rack__grid">
<span className="w-rails" />
<div>
{SERVICES.map((s) => (
<ServiceUnit key={s.name} {...s} />
))}
</div> </div>
<span className="w-rails" />
</div> </div>
</div> </div>
</Reveal> </Reveal>
</Bay> </section>
{/* ----------------------------------------------------- services */} {/* ------------------------------------------------------ projects */}
<Bay id="services" index="03" label="rack" title="Rack elevation — live status"> <section id="projects" className="scroll-mt-28">
<Reveal delay={40}> <Reveal>
<div className="w-rack w-metal"> <SectionHead index="04" label="drawer" title="Eighteen drives, all labelled" />
<div className="w-rack__head">
<span className="w-rack__title">services</span>
<span className="w-unit__meta ml-auto">checked through this site</span>
</div>
{SERVICES.map((s) => (
<ServiceUnit key={s.name} {...s} />
))}
</div>
</Reveal> </Reveal>
</Bay> <Reveal delay={60}>
<div className="w-mt-metal w-drawer">
{/* ----------------------------------------------------- projects */} <div className="w-mt-plastic w-drawer__inner w-scroll">
<Bay id="projects" index="04" label="projects" title="The drive drawer"> <div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
<Reveal delay={40}>
<div className="w-drawer w-metal">
<div className="w-drawer__handle mx-auto mb-4 w-40" />
<div className="max-h-[560px] overflow-y-auto pr-1">
<div className="grid gap-2 sm:grid-cols-2 xl:grid-cols-3">
{codingProjects.map((p, i) => { {codingProjects.map((p, i) => {
const { name, status } = splitStatus(p.title); const { name, status } = splitStatus(p.title);
return ( return (
@ -332,131 +346,164 @@ export default function WebSite() {
href={p.url} href={p.url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="w-caddy w-carbon" className="w-drive"
style={{ "--tilt": `${i % 2 ? 0.5 : -0.6}deg` } as CSSProperties}
title={p.desc} title={p.desc}
> >
<Led kind={statusKind(status)} /> <span className="w-drive__port" aria-hidden="true" />
<span className="min-w-0 flex-1"> <span className="w-drive__sticker">
<span className="w-caddy__label block truncate">{name}</span> <span className="w-drive__name">{name}</span>
<span className="w-caddy__status block truncate"> <span className="w-drive__meta">
{status ?? "unlabelled"} · {p.desc} {status ?? "unlabelled"} · {p.desc}
</span> </span>
</span> </span>
<Led kind={statusKind(status)} className="w-drive__led" />
</a> </a>
); );
})} })}
</div> </div>
</div> </div>
</div> <div className="w-mt-metal-flush w-drawer__front">
</Reveal> <i className="w-screw" aria-hidden="true" />
</Bay> <span className="w-drawer__handle" aria-hidden="true" />
<i className="w-screw" aria-hidden="true" />
{/* ----------------------------------------------------- hardware */}
<Bay id="hardware" index="05" label="inventory" title="On the pegboard">
<Reveal delay={40}>
<div className="w-pegboard w-metal">
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
{techProjects.map((t) => (
<article key={t.title} className="w-paper w-tag">
<span className="w-tag__ring" />
<h3 className="w-serif text-[1rem]">{t.title}</h3>
<p className="w-prose mt-2 text-[0.8rem]">{t.desc}</p>
</article>
))}
</div> </div>
</div> </div>
</Reveal> </Reveal>
</Bay> </section>
{/* -------------------------------------------------- contributions */} {/* ------------------------------------------------------ hardware */}
<Bay id="gitdxd" index="06" label="gitdxd" title="Forge telemetry"> <section id="hardware" className="scroll-mt-28">
<Reveal delay={40}> <Reveal>
<ContributionsPanel /> <SectionHead index="05" label="machines" title="Tagged on the pegboard" />
</Reveal> </Reveal>
</Bay> <Reveal delay={60}>
<div className="w-mt-metal w-pegboard">
<div className="w-pegboard__face">
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
{techProjects.map((t) => (
<article key={t.title} className="w-mt-paper w-hangtag">
<span className="w-hangtag__hole" aria-hidden="true" />
<h3 className="w-serif text-[1.02rem] text-[var(--ink)]">{t.title}</h3>
<p className="w-prose mt-2 text-[0.8rem] text-[var(--ink-soft)]">{t.desc}</p>
</article>
))}
</div>
</div>
</div>
</Reveal>
</section>
{/* -------------------------------------------------------- friends */} {/* -------------------------------------------------- activity feed */}
<Bay id="friends" index="07" label="friends" title="Cards on the desk"> <section id="gitdxd" className="scroll-mt-28">
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> <Reveal>
<SectionHead index="06" label="gitdxd" title="The printer spits out the forge" />
</Reveal>
<Reveal delay={60}>
<Report />
</Reveal>
</section>
{/* ------------------------------------------------------- friends */}
<section id="friends" className="scroll-mt-28">
<Reveal>
<SectionHead index="07" label="friends" title="Cards on the desk" />
</Reveal>
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
{friends.map((f, i) => ( {friends.map((f, i) => (
<Reveal key={f.name} delay={40 + i * 70}> <Reveal key={f.name} delay={60 + i * 70}>
<a <a
href={f.realUrl || f.url} href={f.realUrl || f.url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className={`w-paper w-card block transition-transform hover:-translate-y-1 ${i % 2 ? "w-note--tilt" : ""}`} className="w-mt-paper w-bizcard"
style={{ "--tilt": `${i === 1 ? 0.9 : i === 2 ? -1 : -1.4}deg` } as CSSProperties}
> >
<h3 className="w-serif text-[1.1rem]">{f.name}</h3> <p className="w-serif text-[1.15rem] text-[var(--ink)]">{f.name}</p>
<p className="mt-1 font-mono text-[0.72rem] text-[var(--paper-ink-soft)]">{f.desc}</p> <p className="w-mono mt-1 text-[0.66rem] text-[var(--ink-soft)]">{f.desc}</p>
</a> </a>
</Reveal> </Reveal>
))} ))}
</div> </div>
</Bay> </section>
{/* ------------------------------------------------------------ gym */} {/* ----------------------------------------------------------- gym */}
<Bay id="gym" index="08" label="strength" title="On the platform"> <section id="gym" className="scroll-mt-28">
<Reveal delay={40}> <Reveal>
<div className="w-metal p-5"> <SectionHead index="08" label="strength" title="Numbers on the clipboard" />
<div className="grid gap-3 sm:grid-cols-3"> </Reveal>
{LIFTS.map((l) => ( <Reveal delay={60}>
<div key={l.lift} className="w-panel p-4 text-center"> <div className="w-clipboard">
<div className="w-digits text-[2.2rem] leading-none"> <div className="w-mt-metal w-clipboard__clip" aria-hidden="true" />
{l.weight} <div className="w-mt-paper w-logsheet">
<span className="ml-1 text-[1rem] opacity-70">{l.unit}</span> <div className="w-sheet__head">
<span>training log</span>
<span className="ml-auto">73 kg bodyweight</span>
</div>
<div className="mt-3">
{LIFTS.map((l) => (
<div key={l.lift} className="w-logrow">
<span className="w-logrow__lift">{l.lift}</span>
<span className="w-logrow__weight">{l.weight} kg</span>
<span className="w-logrow__date">{l.date}</span>
</div> </div>
<div className="w-kicker mt-3">{l.lift}</div> ))}
<div className="w-unit__meta mt-1">{l.date} · 73 kg BW</div> </div>
</div> <p className="w-mono mt-4 text-[0.62rem] tracking-[0.14em] text-[var(--ink-soft)]">
))} BEST SBD TOTAL · 401 KG · 165 + 111 + 125
</p>
</div> </div>
</div> </div>
</Reveal> </Reveal>
</Bay> </section>
{/* -------------------------------------------------------- contact */} {/* ------------------------------------------------------- contact */}
<Bay id="contact" index="09" label="ports" title="Patch panel"> <section id="contact" className="scroll-mt-28">
<Reveal delay={40}> <Reveal>
<div className="w-patch w-metal"> <SectionHead index="09" label="patch panel" title="Twelve ports, all live" />
<div className="w-rack__head"> </Reveal>
<span className="w-rack__title">socials</span> <Reveal delay={60}>
<span className="w-unit__meta ml-auto">12 ports</span> <div className="w-mt-metal w-patch">
</div> <div className="w-mt-metal-flush w-patch__face">
<div className="mt-4 grid gap-2 sm:grid-cols-2 lg:grid-cols-3"> <div className="w-patch__head">
{socials.map((s) => ( <i className="w-screw" aria-hidden="true" />
<a key={s.name} href={s.url} target="_blank" rel="noopener noreferrer" className="w-port"> <span className="w-engrave text-[0.62rem] tracking-[0.2em]">PATCH 01 · SOCIALS</span>
<span className="w-port__jack" /> <i className="w-screw ml-auto" aria-hidden="true" />
<span className="text-[var(--faint)]"> </div>
<SocialGlyph name={s.name} icon={s.icon} /> <div className="grid gap-2.5 sm:grid-cols-2 xl:grid-cols-3">
</span> {socials.map((s) => (
<span className="w-port__label min-w-0 truncate">{s.name}</span> <a key={s.name} href={s.url} target="_blank" rel="noopener noreferrer" className="w-port">
<Led kind="green" className="ml-auto" /> <span className="w-port__jack" aria-hidden="true">
<span className="w-port__slot" />
</span>
<span className="w-port__icon">
<SocialGlyph name={s.name} icon={s.icon} />
</span>
<span className="w-port__label">{s.name}</span>
</a>
))}
</div>
<p className="w-mono mt-5 text-[0.64rem] tracking-[0.06em] text-[var(--ftext-faint)]">
fastest reply is Discord or Instagram · mail{" "}
<a className="w-link" href="mailto:dvorak@dvorakmortonxd.dev">
dvorak@dvorakmortonxd.dev
</a> </a>
))} </p>
</div> </div>
<p className="w-unit__meta mt-5">
fastest reply is Discord or Instagram · mail{" "}
<a className="w-link" href="mailto:dvorak@dvorakmortonxd.dev">
dvorak@dvorakmortonxd.dev
</a>
</p>
</div> </div>
</Reveal> </Reveal>
</Bay> </section>
</div> </div>
</main> </main>
{/* --------------------------------------------------------- footer */} {/* ------------------------------------------------------------ footer */}
<footer className="shell pb-14 pt-10"> <footer className="mx-auto w-full max-w-[1120px] px-5 pb-16 sm:px-8">
<Reveal> <Reveal>
<div className="w-metal-flat flex flex-col gap-5 p-5 md:flex-row md:items-center md:justify-between"> <div className="w-mt-brass relative flex flex-col gap-5 px-6 py-5 sm:flex-row sm:items-center sm:justify-between">
<div className="min-w-0"> <div className="min-w-0">
<div className="flex items-center gap-3"> <p className="w-mono text-[0.72rem] tracking-[0.2em]">DVORAKMORTONXD.LOCAL</p>
<Led kind="green" /> <p className="w-mono mt-1.5 text-[0.62rem] opacity-80">
<span className="w-rack__title">dvorakmortonxd.local</span> v3.2.0 · GPL-3.0-only · built with Next.js · textures CC0 via{" "}
</div>
<p className="w-unit__meta mt-2">
built with Next.js · textures CC0 via ambientCG ·{" "}
<a className="w-link" href="/credits"> <a className="w-link" href="/credits">
credits credits
</a> </a>
@ -465,17 +512,17 @@ export default function WebSite() {
<button <button
type="button" type="button"
onClick={() => setTheme("skeuo")} onClick={() => setTheme("skeuo")}
className="w-btn desktop-toggle shrink-0" className="w-keybtn desktop-toggle shrink-0 self-start sm:self-auto"
title="Flip to Desktop mode" title="Flip to Desktop mode"
> >
<Led kind="amber" /> desktop mode <Led kind="amber" /> desktop mode
</button> </button>
<i className="w-screw w-screw--brass w-corner-tr" aria-hidden="true" />
<i className="w-screw w-screw--brass w-corner-bl" aria-hidden="true" />
</div> </div>
<p className="w-mono mt-4 text-[0.62rem] text-[var(--ftext-faint)]">
<div className="mt-4 flex flex-col gap-1 font-mono text-[0.68rem] text-[var(--faint)] sm:flex-row sm:justify-between"> © 2025–2026 Martin Dvořák — follow the licence or don&rsquo;t, I don&rsquo;t mind
<span>v3.1.0 · © 2025–2026 Martin Dvořák</span> </p>
<span>GPL-3.0-only — follow it or don’t, I don’t mind</span>
</div>
</Reveal> </Reveal>
</footer> </footer>
</div> </div>

View file

@ -31,7 +31,7 @@ export function Reveal({
} }
} }
}, },
{ rootMargin: "0px 0px -12% 0px", threshold: 0.08 } { rootMargin: "0px 0px -12% 0px", threshold: 0.06 }
); );
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();
@ -58,51 +58,132 @@ export function Reveal({
/* ------------------------------------------------------------- hardware */ /* ------------------------------------------------------------- hardware */
export function Led({ export function Led({
kind = "green", kind = "on",
pulse = false, pulse = false,
slow = false,
className = "", className = "",
title, style,
}: { }: {
kind?: "green" | "amber" | "red" | "blue" | "off"; kind?: "on" | "amber" | "off";
pulse?: boolean; pulse?: boolean;
slow?: boolean;
className?: string; className?: string;
title?: string; style?: CSSProperties;
}) { }) {
return ( return (
<span <i
className={`w-led w-led--${kind}${pulse ? " w-led--pulse" : ""} ${className}`}
title={title}
aria-hidden="true" aria-hidden="true"
className={`w-led w-led--${kind}${pulse ? (slow ? " w-led--pulse-slow" : " w-led--pulse") : ""} ${className}`}
style={style}
/> />
); );
} }
/** Section header, mounted on the chassis. */ export function Screws({ count = 2, brass = false, className = "" }: { count?: number; brass?: boolean; className?: string }) {
export function Bay({
id,
index,
label,
title,
children,
}: {
id: string;
index: string;
label: string;
title: ReactNode;
children: ReactNode;
}) {
return ( return (
<section id={id} className="scroll-mt-28"> <span className={`w-unit__screws ${className}`}>
<Reveal> {Array.from({ length: count }).map((_, i) => (
<div className="flex flex-wrap items-center gap-x-4 gap-y-3"> <i key={i} className={`w-screw${brass ? " w-screw--brass" : ""}`} />
<span className="w-plate"> ))}
{index} <span className="opacity-50">/</span> {label} </span>
);
}
/** Section heading: a brass tab screwed to the desk + painted title. */
export function SectionHead({ index, label, title }: { index: string; label: string; title: string }) {
return (
<div className="w-sechead">
<span className="w-mt-brass w-sechead__tab">{index} · {label}</span>
<h2 className="w-serif w-paint w-sechead__title">{title}</h2>
</div>
);
}
/* ----------------------------------------------------------- the machine */
export function Monitor({ children, className = "" }: { children: ReactNode; className?: string }) {
return (
<div className={`w-monitor ${className}`}>
<div className="w-monitor__frame">
<div className="w-mt-glass w-monitor__glass">{children}</div>
<div className="w-monitor__chin">
<Led kind="amber" />
<span className="w-monitor__brand">DXD</span>
<span className="w-monitor__osd" aria-hidden="true">
<i className="w-osdkey" />
<i className="w-osdkey" />
<i className="w-osdkey" />
</span> </span>
<h2 className="w-serif text-[1.6rem] sm:text-[2rem]">{title}</h2>
</div> </div>
</Reveal> </div>
<div className="mt-8">{children}</div> <div className="w-monitor__stand" aria-hidden="true">
</section> <i className="w-monitor__neck" />
<i className="w-monitor__base" />
</div>
</div>
);
}
const KEY_ROWS = ["1234567890-=".split(""), "qwertyuiop[]".split(""), "asdfghjkl;'".split("")];
export function Keyboard() {
return (
<div className="w-keyboard" aria-hidden="true">
<div className="w-keyboard__deck">
{KEY_ROWS.map((row, i) => (
<div key={i} className="w-keyboard__row">
{row.map((k) => (
<span key={k} className="w-key">
{k}
</span>
))}
</div>
))}
<div className="w-keyboard__row">
<span className="w-key w-key--space" />
</div>
</div>
</div>
);
}
/** A small rack that sits on the desk in the hero. */
export function MiniRack({ label = "DXD-01" }: { label?: string }) {
return (
<div className="w-rack w-mt-metal" aria-hidden="true">
<div className="w-rack__top">
<Led kind="on" pulse />
<span className="w-engrave text-[0.62rem] tracking-[0.2em]">{label}</span>
<span className="w-rack__vent w-vents" />
</div>
<div className="w-rack__grid">
<span className="w-rails" />
<div>
{[0, 1, 2, 3].map((i) => (
<div key={i} className="w-unit !min-h-[46px] !gap-[10px] !py-2">
<span className="w-unit__grille !h-6 !w-9" />
<Led kind="on" pulse style={{ animationDelay: `${i * 0.5}s` }} />
<Led kind="amber" pulse slow style={{ animationDelay: `${i * 0.3}s` }} />
<span className="ml-auto w-engrave text-[0.58rem] tracking-[0.14em]">
{["pxe", "aio", "nas", "web"][i]}
</span>
</div>
))}
</div>
<span className="w-rails" />
</div>
</div>
);
}
/** A brass plate with one number stamped into it. */
export function Plaque({ value, label }: { value: string; label: string }) {
return (
<div className="w-mt-brass w-plaque relative">
<span className="w-plaque__value">{value}</span>
<span className="w-plaque__label">{label}</span>
<i className="w-screw w-screw--brass w-plaque__screw" />
</div>
); );
} }
@ -143,17 +224,10 @@ export function ServiceUnit({
}; };
}, [url, checkUrl, disabled]); }, [url, checkUrl, disabled]);
const effective = disabled ? "up" : status; const live = disabled ? "up" : status;
const kind = effective === "up" ? "green" : effective === "down" ? "red" : "off"; const kind = live === "up" ? "on" : live === "down" ? "off" : "amber";
const statusLabel = disabled const label = disabled ? "STANDBY" : live === "up" ? "ONLINE" : live === "down" ? "OFFLINE" : "POLLING";
? "standby" const accessLabel = access === "public" ? "public" : access === "private" ? "private" : access === "on_request" ? "on request" : null;
: effective === "up"
? "online"
: effective === "down"
? "offline"
: "checking";
const accessLabel =
access === "public" ? "public" : access === "private" ? "private" : access === "on_request" ? "on request" : null;
const host = (() => { const host = (() => {
try { try {
@ -165,42 +239,39 @@ export function ServiceUnit({
const inner = ( const inner = (
<> <>
<span className="w-unit__leds"> <span className="w-unit__grip" aria-hidden="true" />
<Led kind={kind} pulse={effective === "up"} /> <span className="w-unit__grille w-grille" aria-hidden="true" />
<Led kind="amber" pulse={effective === "up"} /> <span className="w-unit__id">
</span> <span className="w-unit__name w-engrave">{name}</span>
<span className="min-w-0 flex-1"> <span className="w-tape mt-1">
<span className="w-unit__name block truncate">{name}</span> {host}
<span className="w-unit__meta block truncate"> {accessLabel ? ` · ${accessLabel}` : ""}
{host} {accessLabel ? `· ${accessLabel}` : ""}
</span> </span>
<span className="sr-only">{desc}</span>
</span> </span>
<span className="hidden text-right sm:block"> <span className="w-unit__state">
<span className="w-unit__meta block max-w-[26ch] truncate">{desc}</span> <Led kind={kind} pulse={live === "up"} />
</span> <span className="w-unit__status w-engrave">{label}</span>
<span className="w-plate shrink-0">
<Led kind={kind} /> {statusLabel}
</span> </span>
<Screws />
</> </>
); );
const cls = "w-unit w-metal-flat";
if (disabled) { if (disabled) {
return ( return (
<span className={cls} aria-disabled="true"> <span className="w-unit" aria-disabled="true">
{inner} {inner}
</span> </span>
); );
} }
return ( return (
<a href={url} target="_blank" rel="noopener noreferrer" className={`${cls} w-unit--link`}> <a href={url} target="_blank" rel="noopener noreferrer" className="w-unit w-unit--link" title={desc}>
{inner} {inner}
</a> </a>
); );
} }
/* -------------------------------------------------------- contributions */ /* -------------------------------------------------------- activity report */
interface HeatmapPoint { interface HeatmapPoint {
timestamp: number; timestamp: number;
@ -213,7 +284,9 @@ interface Activity {
repo?: { full_name?: string }; repo?: { full_name?: string };
} }
export function ContributionsPanel() { const DOT_OPACITY = [0.07, 0.3, 0.48, 0.64, 0.82];
export function Report() {
const [weeks, setWeeks] = useState<{ date: string; contribs: number; level: number }[][]>([]); const [weeks, setWeeks] = useState<{ date: string; contribs: number; level: number }[][]>([]);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [activities, setActivities] = useState<Activity[]>([]); const [activities, setActivities] = useState<Activity[]>([]);
@ -291,50 +364,52 @@ export function ContributionsPanel() {
}; };
}, []); }, []);
// level 4 = brightest LED, level 0 = off
const levelOn = (l: number) => (l <= 0 ? "0" : String(5 - l));
return ( return (
<div className="w-dashboard w-metal"> <div className="w-printout">
<div className="w-rack__head"> <div className="w-mt-paper w-printout__paper">
<span className="w-rack__title">gitdxd.activity</span> <span className="w-printout__perf w-printout__perf--l" aria-hidden="true" />
<span className="ml-auto flex items-center gap-2"> <span className="w-printout__perf w-printout__perf--r" aria-hidden="true" />
<Led kind={state === "error" ? "red" : "green"} pulse={state === "ready"} />
<span className="w-unit__meta">{state === "error" ? "link down" : "streaming"}</span>
</span>
</div>
<div className="mt-4 flex flex-wrap items-end justify-between gap-3"> <p className="w-print__head">
<div className="w-digits text-[2rem] leading-none sm:text-[2.5rem]">{total}</div> <span>GITDXD // ACTIVITY REPORT</span>
<div className="w-kicker">contributions / last year</div> <span className="ml-auto">{state === "ready" ? "PRINTED LIVE" : state === "error" ? "PRINTER OFFLINE" : "SPOOLING…"}</span>
</div> </p>
{state === "loading" ? ( {state === "error" ? (
<p className="w-screen-text mt-5 opacity-70">reading matrix…</p> <p className="w-mono mt-5 text-[0.72rem] text-[#4a4133]">could not reach the forge.</p>
) : state === "error" ? ( ) : (
<p className="w-screen-text mt-5 opacity-70">could not reach the forge.</p> <>
) : ( <div className="mt-5 flex flex-wrap items-end gap-x-6 gap-y-2">
<> <div>
<div className="w-ledgrid mt-5"> <p className="w-mono text-[0.62rem] tracking-[0.16em] text-[#4a4133]">TOTAL / 12 MONTHS</p>
{weeks.map((w, i) => ( <p className="w-serif text-[2.6rem] leading-none text-[var(--ink)]">
<div key={i} className="w-ledgrid__col"> {state === "loading" ? "—" : total}
{w.map((d) => ( </p>
<div key={d.date} className="w-ledgrid__cell" data-on={levelOn(d.level)} title={`${d.contribs} on ${d.date}`} />
))}
</div> </div>
))} <p className="w-mono text-[0.62rem] leading-relaxed text-[#6f6450]">
</div> each dot = one day
<div className="mt-3 flex items-center justify-end gap-2"> <br />
<span className="w-unit__meta">less</span> darkest = busiest
{[4, 3, 2, 1, 0].map((l) => ( </p>
<span key={l} className="w-ledgrid__cell" data-on={levelOn(l)} /> </div>
))}
<span className="w-unit__meta">more</span>
</div>
{activities.length > 0 ? ( <div className="w-dotgrid w-scroll" aria-hidden={state === "loading"}>
<div className="w-panel mt-5 p-3"> {weeks.map((w, i) => (
<div className="w-screen-text"> <span key={i} className="w-dotgrid__col">
{w.map((d) => (
<span
key={d.date}
className="w-dot"
style={{ opacity: DOT_OPACITY[d.level] }}
title={`${d.contribs} on ${d.date}`}
/>
))}
</span>
))}
</div>
{activities.length > 0 ? (
<div className="w-print__log">
{activities.map((a, i) => { {activities.map((a, i) => {
let msg = "commit"; let msg = "commit";
try { try {
@ -353,15 +428,15 @@ export function ContributionsPanel() {
} }
return ( return (
<p key={i} className="truncate"> <p key={i} className="truncate">
<span className="k">$</span> {a.repo?.full_name || "unknown"} — {msg} &gt; {a.repo?.full_name || "unknown"} — {msg}
</p> </p>
); );
})} })}
</div> </div>
</div> ) : null}
) : null} </>
</> )}
)} </div>
</div> </div>
); );
} }

View file

@ -8,57 +8,58 @@ export const metadata: Metadata = {
}; };
const TEXTURES = [ const TEXTURES = [
{ file: "metal.jpg", asset: "Metal 051 C", id: "Metal051C" }, { file: "desk.jpg", asset: "Wood 027", id: "Wood027", use: "the desk the page sits on" },
{ file: "paper.jpg", asset: "Paper 003", id: "Paper003" }, { file: "metal.jpg", asset: "Metal 051 C", id: "Metal051C", use: "rack, monitor, panel hardware" },
{ file: "carbon.jpg", asset: "Fabric 004", id: "Fabric004" }, { file: "paper.jpg", asset: "Paper 003", id: "Paper003", use: "sheets, labels, tape, the report" },
{ file: "rubber.jpg", asset: "Rubber 004", id: "Rubber004" }, { file: "carbon.jpg", asset: "Fabric 004", id: "Fabric004", use: "the keyboard deck" },
{ file: "plastic.jpg", asset: "Plastic 012 A", id: "Plastic012A" }, { file: "plastic.jpg", asset: "Plastic 012 A", id: "Plastic012A", use: "the drawer interior" },
]; ];
const FONTS = [ const FONTS = [
{ {
name: "Newsreader", name: "Newsreader",
by: "Production Type", by: "Production Type",
use: "headlines, paper copy", use: "printed headlines, sheet copy, stamped numbers",
href: "https://fonts.google.com/specimen/Newsreader", href: "https://fonts.google.com/specimen/Newsreader",
}, },
{ {
name: "IBM Plex Sans", name: "IBM Plex Sans",
by: "IBM", by: "IBM",
use: "body text", use: "body text on the desk",
href: "https://fonts.google.com/specimen/IBM+Plex+Sans", href: "https://fonts.google.com/specimen/IBM+Plex+Sans",
}, },
{ {
name: "IBM Plex Mono", name: "IBM Plex Mono",
by: "IBM", by: "IBM",
use: "labels, readouts, LEDs", use: "engraved labels, terminal text, index cards",
href: "https://fonts.google.com/specimen/IBM+Plex+Mono", href: "https://fonts.google.com/specimen/IBM+Plex+Mono",
}, },
]; ];
export default function Credits() { export default function Credits() {
return ( return (
<div className="w-root"> <div className="w-page">
<main className="shell pb-20 pt-24"> <main className="mx-auto w-full max-w-[1120px] px-5 pt-24 pb-20 sm:px-8">
<p className="w-kicker">credits</p> <div className="w-sechead">
<h1 className="w-serif mt-3 text-[2rem] sm:text-[2.6rem]">Everything borrowed, credited.</h1> <span className="w-mt-brass w-sechead__tab">credits</span>
<p className="w-prose mt-5 max-w-[62ch]"> <h1 className="w-serif w-paint w-sechead__title">Everything borrowed, credited.</h1>
The web theme is built out of a few CC0 textures and open-licensed typefaces. Nothing here is </div>
scraped or unlicensed — everything below is free to reuse, and the authors are named even where <p className="w-prose w-paint max-w-[64ch] text-[var(--ftext-dim)]">
the licence does not require it. The desk is built out of five CC0 textures and three open-licensed typefaces. Nothing is scraped
or unlicensed — the authors are named even where the licence does not require it.
</p> </p>
<section className="mt-12"> <section className="mt-14">
<div className="flex flex-wrap items-center gap-4"> <div className="w-sechead !mb-6">
<span className="w-plate">textures</span> <span className="w-mt-brass w-sechead__tab">textures</span>
<h2 className="w-serif text-[1.3rem]">ambientCG — CC0 1.0 (public domain)</h2> <h2 className="w-serif w-paint w-sechead__title !text-[1.3rem]">ambientCG — CC0 1.0</h2>
</div> </div>
<p className="w-prose mt-4 max-w-[62ch]"> <p className="w-prose w-paint max-w-[64ch] text-[var(--ftext-dim)]">
All five materials come from{" "} All five materials come from{" "}
<a className="w-link" href="https://ambientcg.com" target="_blank" rel="noopener noreferrer"> <a className="w-link" href="https://ambientcg.com" target="_blank" rel="noopener noreferrer">
ambientCG ambientCG
</a>{" "} </a>{" "}
(Struffel Productions), released under the{" "} (Struffel Productions) under the{" "}
<a <a
className="w-link" className="w-link"
href="https://creativecommons.org/publicdomain/zero/1.0/" href="https://creativecommons.org/publicdomain/zero/1.0/"
@ -71,11 +72,11 @@ export default function Credits() {
tint them in CSS. tint them in CSS.
</p> </p>
<ul className="mt-6 grid gap-3 sm:grid-cols-2"> <ul className="mt-7 grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
{TEXTURES.map((t) => ( {TEXTURES.map((t) => (
<li key={t.id} className="w-paper w-card"> <li key={t.id} className="w-mt-paper w-cardindex">
<div className="w-serif text-[1.02rem]">{t.asset}</div> <p className="w-serif text-[1.05rem] text-[var(--ink)]">{t.asset}</p>
<div className="mt-1 font-mono text-[0.7rem] leading-relaxed text-[var(--paper-ink-soft)]"> <p className="w-mono mt-2 text-[0.64rem] leading-relaxed text-[var(--ink-soft)]">
source:{" "} source:{" "}
<a <a
className="w-link" className="w-link"
@ -86,50 +87,54 @@ export default function Credits() {
ambientCG / {t.id} ambientCG / {t.id}
</a> </a>
<br /> <br />
licence: CC0 1.0 · used as <code>{`/textures/${t.file}`}</code> licence: CC0 1.0
</div> <br />
used as <code>{`/textures/${t.file}`}</code> — {t.use}
</p>
</li> </li>
))} ))}
</ul> </ul>
</section> </section>
<section className="mt-14"> <section className="mt-16">
<div className="flex flex-wrap items-center gap-4"> <div className="w-sechead !mb-6">
<span className="w-plate">type</span> <span className="w-mt-brass w-sechead__tab">type</span>
<h2 className="w-serif text-[1.3rem]">Google Fonts — SIL Open Font License 1.1</h2> <h2 className="w-serif w-paint w-sechead__title !text-[1.3rem]">Google Fonts — SIL OFL 1.1</h2>
</div> </div>
<ul className="mt-6 grid gap-3 sm:grid-cols-3"> <ul className="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
{FONTS.map((f) => ( {FONTS.map((f) => (
<li key={f.name} className="w-paper w-card"> <li key={f.name} className="w-mt-paper w-cardindex w-cardindex--alt">
<div className="w-serif text-[1.02rem]">{f.name}</div> <p className="w-serif text-[1.05rem] text-[var(--ink)]">{f.name}</p>
<div className="mt-1 font-mono text-[0.7rem] leading-relaxed text-[var(--paper-ink-soft)]"> <p className="w-mono mt-2 text-[0.64rem] leading-relaxed text-[var(--ink-soft)]">
by {f.by} · {f.use} by {f.by}
<br />
{f.use}
<br /> <br />
<a className="w-link" href={f.href} target="_blank" rel="noopener noreferrer"> <a className="w-link" href={f.href} target="_blank" rel="noopener noreferrer">
fonts.google.com fonts.google.com
</a>{" "} </a>{" "}
· OFL 1.1 · OFL 1.1
</div> </p>
</li> </li>
))} ))}
</ul> </ul>
</section> </section>
<section className="mt-14"> <section className="mt-16">
<div className="flex flex-wrap items-center gap-4"> <div className="w-sechead !mb-6">
<span className="w-plate">notes</span> <span className="w-mt-brass w-sechead__tab">notes</span>
<h2 className="w-serif text-[1.3rem]">Other marks</h2> <h2 className="w-serif w-paint w-sechead__title !text-[1.3rem]">Other marks</h2>
</div> </div>
<p className="w-prose mt-4 max-w-[62ch]"> <p className="w-prose w-paint max-w-[64ch] text-[var(--ftext-dim)]">
The social buttons use brand marks that remain the property of their owners and are used only The social ports use brand marks that remain the property of their owners and are used only to
to link to those accounts. The project and hardware text is my own. The desktop mode (KDE link to those accounts. Everything written on the paper is mine. The desktop mode (the KDE
sim) uses separate assets credited inside it. simulation) uses its own separate assets.
</p> </p>
</section> </section>
<p className="mt-14"> <p className="mt-16">
<Link href="/" className="w-link font-mono text-[0.8rem]"> <Link href="/" className="w-link w-mono text-[0.78rem]">
← back to the rack ← back to the desk
</Link> </Link>
</p> </p>
</main> </main>

File diff suppressed because it is too large Load diff