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)**.
It's a **skeuomorphic personal site**: a dark homelab desk built from anodised aluminium, OLED
glass, carbon fibre and paper, with the whole KDE desktop simulation kept behind a physical
power switch. Two modes, one content set:
It's a **skeuomorphic personal site**: a walnut desk with graphite hardware, cream paper and
brass details resting on it, with the whole KDE desktop simulation kept behind a physical power
switch. Two modes, one content set:
- **Web** (default) — the homelab desk. A 4U rack carries the headline on an engraved nameplate
and a live console readout; services are rack units with status LEDs, the 18 coding projects
are labelled SSD caddies in a drive drawer, hardware hangs on a pegboard, the contribution
heatmap is an LED matrix and the socials are a patch panel. Textures are CC0 — see
[`/credits`](http://localhost:3000/credits).
- **Web** (default) — the desk. A monitor carries the headline with a keyboard in front of it
and a small rack beside it; services are 1U rack units with health LEDs, the 18 coding
projects are labelled drives in a drawer, the machines hang as paper tags on a pegboard, the
contribution heatmap is printed on continuous dot-matrix paper, friends are business cards,
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
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.
@ -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
deliberately not persisted, so a reload never drops you into the desktop.
- **Skeuomorphic hardware** — rack, LEDs, rocker switch, screws, vents and panels are CSS
materials over five CC0 textures; every section is a real-world object rather than a card.
- **Skeuomorphic materials** — walnut desk, graphite faceplates, cream paper, brass plaques and
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)
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.
@ -64,15 +66,15 @@ heatmap**, gym SBD and links to friends' sites.
- **Framework:** Next.js 16 (App Router, Turbopack)
- **Language:** TypeScript, React 19 (client components everywhere interactivity lives)
- **Styling:** `src/app/globals.css` holds the palette tokens, base styles, the Tailwind v4
`@theme`/`@utility` classes and the scroll reveals. `src/app/web.css` is the skeuomorphic
material system (metal, OLED glass, paper, carbon, rubber, plastic, LEDs, screws, vents,
switches) and the object layouts. `page.css` keeps the shared component styles the desktop
apps still use, and `skeuo.css` is the KDE desktop.
`@theme`/`@utility` classes and the scroll reveals. `src/app/web.css` is the material system
(walnut desk, graphite metal, cream paper, brass, OLED glass, LEDs, screws, rack rails, rocker
switch) and the object layouts. `page.css` keeps the shared component styles the desktop apps
still use, and `skeuo.css` is the KDE desktop.
- **Fonts:** Newsreader (headlines, paper) + IBM Plex Sans/Mono (body, machine labels) via
`next/font/google`; the desktop keeps its system sans stack.
- **Textures:** five CC0 materials from ambientCG, downscaled to 512 px in `public/textures`.
Attribution is on `/credits`.
- **Version:** `3.1.0` (see `package.json`)
- **Textures:** five CC0 materials from ambientCG — `desk` (walnut), `metal`, `paper`, `carbon`,
`plastic` — downscaled to 512 px in `public/textures`. Attribution is on `/credits`.
- **Version:** `3.2.0` (see `package.json`)
## 🚀 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
Personal portfolio site for **dvorakmortonxd.dev**. The default view is a
**skeuomorphic homelab desk** (a rack, LEDs, paper, anodised metal); a physical
power switch flips to a **KDE Plasma desktop simulation**. Phones only ever get
the desk.
**skeuomorphic desk**: a walnut surface with a monitor, keyboard, rack, drive
drawer, pegboard, dot-matrix report, business cards, clipboard and patch panel
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.
- Styling: **Tailwind v4** for layout plus **`web.css`**, the skeuomorphic
material system (metal, OLED glass, paper, carbon, rubber, plastic, LEDs,
screws, vents, switches) over five CC0 textures in `public/textures`
- Styling: **Tailwind v4** for layout plus **`web.css`**, the material system
(walnut desk, graphite metal, cream paper, brass, OLED glass, LEDs, screws,
rack rails, rocker switch) over five CC0 textures in `public/textures`
(credited on `/credits`). `globals.css` holds the tokens, base styles and the
reveals; `skeuo.css` and `page.css` are the desktop and the shared
desktop-app styles.
- Path alias: `@/*` → `src/*`.
- Version: `3.1.0` (see `package.json`).
- Version: `3.2.0` (see `package.json`).
### Scripts
```bash
@ -123,11 +124,11 @@ scripts/
desktop is up so its scroll position survives; the desktop mounts on demand
(phones can never reach `skeuo`).
Modes: `web` = the skeuomorphic homelab desk (default/landing), `skeuo` = KDE
Plasma desktop.
Modes: `web` = the skeuomorphic desk (default/landing), `skeuo` = KDE Plasma
desktop.
The desk uses **five CC0 textures** from ambientCG (`public/textures`: metal,
paper, carbon, rubber, plastic), downscaled to 512 px and credited on
The desk uses **five CC0 textures** from ambientCG (`public/textures`: desk
walnut, metal, paper, carbon, plastic), downscaled to 512 px and credited on
`/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`
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
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
(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
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

View file

@ -1,6 +1,6 @@
{
"name": "dvorakmortonxd",
"version": "3.1.0",
"version": "3.2.0",
"private": true,
"scripts": {
"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";
import { useState } from "react";
import { useState, type CSSProperties } from "react";
import { useTheme } from "./useTheme";
import { codingProjects, friends, socials, techProjects } from "../data";
import {
Bay,
ContributionsPanel,
Keyboard,
Led,
MiniRack,
Monitor,
Plaque,
Report,
Reveal,
SectionHead,
ServiceUnit,
SocialGlyph,
type ServiceAccess,
@ -17,10 +21,10 @@ import {
const NAV = [
{ href: "#about", label: "about" },
{ href: "#services", label: "rack" },
{ href: "#services", label: "services" },
{ href: "#projects", label: "projects" },
{ href: "#hardware", label: "inventory" },
{ href: "#contact", label: "ports" },
{ href: "#hardware", label: "machines" },
{ href: "#contact", label: "contact" },
];
const SERVICES: {
@ -40,20 +44,9 @@ const SERVICES: {
];
const LIFTS = [
{ lift: "squat", weight: "125", unit: "kg", date: "05 Jan 2025" },
{ lift: "bench", weight: "111", unit: "kg", date: "29 May 2026" },
{ lift: "deadlift", weight: "165", unit: "kg", 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"],
{ lift: "squat", weight: "125", date: "05 Jan 2025" },
{ lift: "bench", weight: "111", date: "29 May 2026" },
{ lift: "deadlift", weight: "165", date: "17 Dec 2024" },
];
/** 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 };
}
function statusKind(status: string | null): "green" | "amber" | "red" | "off" {
function statusKind(status: string | null): "on" | "amber" | "off" {
if (!status) return "off";
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";
return "red";
return "off";
}
/* ------------------------------------------------------------------ page */
@ -76,30 +69,19 @@ export default function WebSite() {
const { setTheme } = useTheme();
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 (
<div className="w-root">
{/* ----------------------------------------------------------- rail */}
<header className="w-rail w-metal-flat" style={{ borderRadius: 0 }}>
<div className="shell flex h-16 items-center gap-5">
<a href="#top" className="flex shrink-0 items-center gap-3" aria-label="Back to top">
<Led kind="green" pulse />
<span className="w-rack__title">dvorakmortonxd.local</span>
<div className="w-page">
{/* -------------------------------------------------------------- rail */}
<header className="w-rail w-mt-metal-flush" style={{ borderRadius: 0 }}>
<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-2.5" aria-label="Back to top">
<Led kind="on" pulse slow />
<span className="w-wordmark w-engrave">dvorakmortonxd</span>
</a>
<nav className="ml-auto hidden items-center gap-6 md:flex" aria-label="Main">
{NAV.map((item) => (
<a
key={item.href}
href={item.href}
className="w-navlink"
>
<a key={item.href} href={item.href} className="w-navlink">
{item.label}
</a>
))}
@ -109,15 +91,16 @@ export default function WebSite() {
<button
type="button"
aria-label="Switch to Desktop mode"
data-desktop-mode="off"
onClick={() => setTheme("skeuo")}
className="desktop-toggle flex items-center gap-3"
title="Flip to Desktop mode"
>
<span className="w-plate">desktop</span>
<span className="w-switch">
<span className="w-switch__knob" />
<Led kind="off" className="w-switch__led" />
<span className="w-mono hidden text-[0.6rem] tracking-[0.2em] text-[var(--ftext-faint)] sm:inline">
DESKTOP
</span>
<span className="w-rocker">
<span className="w-rocker__knob" />
<Led kind="off" className="w-rocker__led" />
</span>
</button>
@ -127,7 +110,7 @@ export default function WebSite() {
onClick={() => setMenuOpen((v) => !v)}
aria-expanded={menuOpen}
aria-label={menuOpen ? "Close menu" : "Open menu"}
className="w-btn px-3 py-2"
className="w-keybtn !px-3 !py-2"
>
{menuOpen ? "close" : "menu"}
</button>
@ -136,13 +119,16 @@ export default function WebSite() {
</div>
{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) => (
<a
key={item.href}
href={item.href}
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}
</a>
@ -153,177 +139,205 @@ export default function WebSite() {
<span id="top" />
<main className="pt-16">
{/* --------------------------------------------------------- hero */}
<section className="shell pt-12 pb-14 sm:pt-16">
<Reveal>
<div className="w-rack w-metal">
<div className="w-rack__head">
<Led kind="green" pulse />
<span className="w-rack__title">dvorakmortonxd.local</span>
<span className="w-unit__meta ml-auto hidden sm:block">4U · asus p9x79 · 24/7</span>
<span className="w-screw ml-2 hidden sm:block" />
<span className="w-screw hidden sm:block" />
<main className="pt-24 sm:pt-28">
{/* ------------------------------------------------------------ hero */}
<section className="mx-auto w-full max-w-[1120px] px-5 sm:px-8">
<div className="grid gap-10 lg:grid-cols-[minmax(0,1.5fr)_minmax(0,1fr)] lg:items-end lg:gap-8">
<Reveal>
<Monitor>
<p className="w-tft">
<span className="a">martin@dvorakmortonxd</span>:~$ whoami
<span className="w-caret ml-2" />
</p>
<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>
</Reveal>
<div className="grid gap-3 lg:grid-cols-[minmax(0,1.3fr)_minmax(0,1fr)]">
<div className="w-nameplate min-w-0">
<p className="w-kicker">homelab · czech republic · online</p>
<h1 className="mt-4 text-[2rem] leading-[1.05] sm:text-[2.6rem] lg:text-[3rem]">
My own little internet — one rack, zero subscriptions.
</h1>
<p className="w-prose mt-5 max-w-[48ch]">
I’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>
<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>
<Reveal delay={120}>
<div className="flex flex-col gap-6">
<MiniRack />
<div className="grid grid-cols-2 gap-3">
<Plaque value={String(SERVICES.length)} label="services" />
<Plaque value={String(codingProjects.length)} label="projects" />
<Plaque value={String(techProjects.length)} label="machines" />
<Plaque value="401" label="kg best SBD" />
</div>
</div>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
{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>
</Reveal>
</div>
</section>
<div className="shell flex flex-col gap-16 pb-20 sm:gap-24">
{/* -------------------------------------------------------- about */}
<Bay id="about" index="01" label="about" title="Who is behind the rack">
<div className="grid gap-6 md:grid-cols-2">
<Reveal delay={40}>
<article className="w-paper w-card h-full">
<div className="flex items-center gap-3">
<span className="w-grommet" />
<span className="w-kicker !text-[var(--paper-ink-soft)]">manifest.txt</span>
<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 */}
<section id="about" className="scroll-mt-28">
<Reveal>
<SectionHead index="01" label="about" title="Who is behind the rack" />
</Reveal>
<div className="grid gap-7 lg:grid-cols-[minmax(0,1.55fr)_minmax(0,1fr)] lg:items-start">
<Reveal delay={60}>
<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>
<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>
</Reveal>
<Reveal delay={130}>
<article className="w-paper w-card w-note--tilt h-full">
<div className="flex items-center gap-3">
<span className="w-grommet" />
<span className="w-kicker !text-[var(--paper-ink-soft)]">inventory.txt</span>
</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.
<Reveal delay={150}>
<aside className="w-mt-paper w-cardindex lg:mt-12">
<p className="w-mono text-[0.62rem] tracking-[0.16em] text-[var(--ink-soft)]">
INDEX CARD
</p>
<p className="w-prose mt-3">
Away from the rack: PPL training 3–5× a week, Terraria (~1300 h), Minecraft modpacks
and the occasional game jam.
<p className="w-serif mt-3 text-[1.15rem] text-[var(--ink)]">
&ldquo;If it has a fan, I&rsquo;ll find a use for it.&rdquo;
</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>
</div>
</Bay>
</section>
{/* ---------------------------------------------------------- now */}
<Bay id="now" index="02" label="now" title="On the bench right now">
<Reveal delay={40}>
<div className="grid gap-4 lg:grid-cols-[minmax(0,1.25fr)_minmax(0,1fr)]">
<article className="w-paper w-note min-w-0">
<span className="w-kicker !text-[var(--paper-ink-soft)]">now.txt · updated 09/2026</span>
<p className="w-prose mt-4">
I’m hosting an SMP — message me on Discord if you want in. I’m also cooking most days,
bulking through the non-summer seasons.
{/* ----------------------------------------------------------- now */}
<section id="now" className="scroll-mt-28">
<Reveal>
<SectionHead index="02" label="now" title="On the bench right now" />
</Reveal>
<div className="grid gap-7 lg:grid-cols-[minmax(0,1.3fr)_minmax(0,1fr)] lg:items-start">
<Reveal delay={60}>
<div className="w-sticky">
<p className="w-mono text-[0.62rem] tracking-[0.16em] text-[#6b6047]">
NOW.TXT · UPDATED 09/2026
</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{" "}
<a className="w-link" href="https://skopec.dev" target="_blank" rel="noopener noreferrer">
Pepa
</a>{" "}
are building an education app so we can study more efficiently. It’s called{" "}
<a className="w-link" href="https://git.dvorakmortonxd.dev/edu/maturita.c" target="_blank" rel="noopener noreferrer">
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"
>
maturita.c
</a>
.
</p>
</article>
</div>
</Reveal>
<div className="w-panel min-w-0 p-4">
<div className="w-rack__head !border-b-0 !pb-2">
<span className="w-rack__title">status</span>
<Led kind="green" pulse className="ml-auto" />
<Reveal delay={140}>
<div className="flex flex-col gap-6">
<div className="w-mt-paper w-cardindex w-cardindex--alt">
<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 className="w-screen-text">
<p><span className="k">smp</span> <span className="v">accepting players</span></p>
<p><span className="k">bulk</span> <span className="v">in progress</span></p>
<p><span className="k">maturita.c</span> <span className="v">in development</span></p>
<p><span className="k">gym</span> <span className="v">PPL · 3–5×/week</span></p>
<i className="w-pencil ml-6 w-44" aria-hidden="true" />
</div>
</Reveal>
</div>
</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>
<span className="w-rails" />
</div>
</div>
</Reveal>
</Bay>
</section>
{/* ----------------------------------------------------- services */}
<Bay id="services" index="03" label="rack" title="Rack elevation — live status">
<Reveal delay={40}>
<div className="w-rack w-metal">
<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>
{/* ------------------------------------------------------ projects */}
<section id="projects" className="scroll-mt-28">
<Reveal>
<SectionHead index="04" label="drawer" title="Eighteen drives, all labelled" />
</Reveal>
</Bay>
{/* ----------------------------------------------------- projects */}
<Bay id="projects" index="04" label="projects" title="The drive drawer">
<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">
<Reveal delay={60}>
<div className="w-mt-metal w-drawer">
<div className="w-mt-plastic w-drawer__inner w-scroll">
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
{codingProjects.map((p, i) => {
const { name, status } = splitStatus(p.title);
return (
@ -332,131 +346,164 @@ export default function WebSite() {
href={p.url}
target="_blank"
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}
>
<Led kind={statusKind(status)} />
<span className="min-w-0 flex-1">
<span className="w-caddy__label block truncate">{name}</span>
<span className="w-caddy__status block truncate">
<span className="w-drive__port" aria-hidden="true" />
<span className="w-drive__sticker">
<span className="w-drive__name">{name}</span>
<span className="w-drive__meta">
{status ?? "unlabelled"} · {p.desc}
</span>
</span>
<Led kind={statusKind(status)} className="w-drive__led" />
</a>
);
})}
</div>
</div>
</div>
</Reveal>
</Bay>
{/* ----------------------------------------------------- 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 className="w-mt-metal-flush w-drawer__front">
<i className="w-screw" aria-hidden="true" />
<span className="w-drawer__handle" aria-hidden="true" />
<i className="w-screw" aria-hidden="true" />
</div>
</div>
</Reveal>
</Bay>
</section>
{/* -------------------------------------------------- contributions */}
<Bay id="gitdxd" index="06" label="gitdxd" title="Forge telemetry">
<Reveal delay={40}>
<ContributionsPanel />
{/* ------------------------------------------------------ hardware */}
<section id="hardware" className="scroll-mt-28">
<Reveal>
<SectionHead index="05" label="machines" title="Tagged on the pegboard" />
</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 */}
<Bay id="friends" index="07" label="friends" title="Cards on the desk">
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{/* -------------------------------------------------- activity feed */}
<section id="gitdxd" className="scroll-mt-28">
<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) => (
<Reveal key={f.name} delay={40 + i * 70}>
<Reveal key={f.name} delay={60 + i * 70}>
<a
href={f.realUrl || f.url}
target="_blank"
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="mt-1 font-mono text-[0.72rem] text-[var(--paper-ink-soft)]">{f.desc}</p>
<p className="w-serif text-[1.15rem] text-[var(--ink)]">{f.name}</p>
<p className="w-mono mt-1 text-[0.66rem] text-[var(--ink-soft)]">{f.desc}</p>
</a>
</Reveal>
))}
</div>
</Bay>
</section>
{/* ------------------------------------------------------------ gym */}
<Bay id="gym" index="08" label="strength" title="On the platform">
<Reveal delay={40}>
<div className="w-metal p-5">
<div className="grid gap-3 sm:grid-cols-3">
{LIFTS.map((l) => (
<div key={l.lift} className="w-panel p-4 text-center">
<div className="w-digits text-[2.2rem] leading-none">
{l.weight}
<span className="ml-1 text-[1rem] opacity-70">{l.unit}</span>
{/* ----------------------------------------------------------- gym */}
<section id="gym" className="scroll-mt-28">
<Reveal>
<SectionHead index="08" label="strength" title="Numbers on the clipboard" />
</Reveal>
<Reveal delay={60}>
<div className="w-clipboard">
<div className="w-mt-metal w-clipboard__clip" aria-hidden="true" />
<div className="w-mt-paper w-logsheet">
<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 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>
</Reveal>
</Bay>
</section>
{/* -------------------------------------------------------- contact */}
<Bay id="contact" index="09" label="ports" title="Patch panel">
<Reveal delay={40}>
<div className="w-patch w-metal">
<div className="w-rack__head">
<span className="w-rack__title">socials</span>
<span className="w-unit__meta ml-auto">12 ports</span>
</div>
<div className="mt-4 grid gap-2 sm:grid-cols-2 lg:grid-cols-3">
{socials.map((s) => (
<a key={s.name} href={s.url} target="_blank" rel="noopener noreferrer" className="w-port">
<span className="w-port__jack" />
<span className="text-[var(--faint)]">
<SocialGlyph name={s.name} icon={s.icon} />
</span>
<span className="w-port__label min-w-0 truncate">{s.name}</span>
<Led kind="green" className="ml-auto" />
{/* ------------------------------------------------------- contact */}
<section id="contact" className="scroll-mt-28">
<Reveal>
<SectionHead index="09" label="patch panel" title="Twelve ports, all live" />
</Reveal>
<Reveal delay={60}>
<div className="w-mt-metal w-patch">
<div className="w-mt-metal-flush w-patch__face">
<div className="w-patch__head">
<i className="w-screw" aria-hidden="true" />
<span className="w-engrave text-[0.62rem] tracking-[0.2em]">PATCH 01 · SOCIALS</span>
<i className="w-screw ml-auto" aria-hidden="true" />
</div>
<div className="grid gap-2.5 sm:grid-cols-2 xl:grid-cols-3">
{socials.map((s) => (
<a key={s.name} href={s.url} target="_blank" rel="noopener noreferrer" className="w-port">
<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>
))}
</p>
</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>
</Reveal>
</Bay>
</section>
</div>
</main>
{/* --------------------------------------------------------- footer */}
<footer className="shell pb-14 pt-10">
{/* ------------------------------------------------------------ footer */}
<footer className="mx-auto w-full max-w-[1120px] px-5 pb-16 sm:px-8">
<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="flex items-center gap-3">
<Led kind="green" />
<span className="w-rack__title">dvorakmortonxd.local</span>
</div>
<p className="w-unit__meta mt-2">
built with Next.js · textures CC0 via ambientCG ·{" "}
<p className="w-mono text-[0.72rem] tracking-[0.2em]">DVORAKMORTONXD.LOCAL</p>
<p className="w-mono mt-1.5 text-[0.62rem] opacity-80">
v3.2.0 · GPL-3.0-only · built with Next.js · textures CC0 via{" "}
<a className="w-link" href="/credits">
credits
</a>
@ -465,17 +512,17 @@ export default function WebSite() {
<button
type="button"
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"
>
<Led kind="amber" /> desktop mode
</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 className="mt-4 flex flex-col gap-1 font-mono text-[0.68rem] text-[var(--faint)] sm:flex-row sm:justify-between">
<span>v3.1.0 · © 2025–2026 Martin Dvořák</span>
<span>GPL-3.0-only — follow it or don’t, I don’t mind</span>
</div>
<p className="w-mono mt-4 text-[0.62rem] text-[var(--ftext-faint)]">
© 2025–2026 Martin Dvořák — follow the licence or don&rsquo;t, I don&rsquo;t mind
</p>
</Reveal>
</footer>
</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();
@ -58,51 +58,132 @@ export function Reveal({
/* ------------------------------------------------------------- hardware */
export function Led({
kind = "green",
kind = "on",
pulse = false,
slow = false,
className = "",
title,
style,
}: {
kind?: "green" | "amber" | "red" | "blue" | "off";
kind?: "on" | "amber" | "off";
pulse?: boolean;
slow?: boolean;
className?: string;
title?: string;
style?: CSSProperties;
}) {
return (
<span
className={`w-led w-led--${kind}${pulse ? " w-led--pulse" : ""} ${className}`}
title={title}
<i
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 Bay({
id,
index,
label,
title,
children,
}: {
id: string;
index: string;
label: string;
title: ReactNode;
children: ReactNode;
}) {
export function Screws({ count = 2, brass = false, className = "" }: { count?: number; brass?: boolean; className?: string }) {
return (
<section id={id} className="scroll-mt-28">
<Reveal>
<div className="flex flex-wrap items-center gap-x-4 gap-y-3">
<span className="w-plate">
{index} <span className="opacity-50">/</span> {label}
<span className={`w-unit__screws ${className}`}>
{Array.from({ length: count }).map((_, i) => (
<i key={i} className={`w-screw${brass ? " w-screw--brass" : ""}`} />
))}
</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>
<h2 className="w-serif text-[1.6rem] sm:text-[2rem]">{title}</h2>
</div>
</Reveal>
<div className="mt-8">{children}</div>
</section>
</div>
<div className="w-monitor__stand" aria-hidden="true">
<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]);
const effective = disabled ? "up" : status;
const kind = effective === "up" ? "green" : effective === "down" ? "red" : "off";
const statusLabel = disabled
? "standby"
: effective === "up"
? "online"
: effective === "down"
? "offline"
: "checking";
const accessLabel =
access === "public" ? "public" : access === "private" ? "private" : access === "on_request" ? "on request" : null;
const live = disabled ? "up" : status;
const kind = live === "up" ? "on" : live === "down" ? "off" : "amber";
const label = disabled ? "STANDBY" : live === "up" ? "ONLINE" : live === "down" ? "OFFLINE" : "POLLING";
const accessLabel = access === "public" ? "public" : access === "private" ? "private" : access === "on_request" ? "on request" : null;
const host = (() => {
try {
@ -165,42 +239,39 @@ export function ServiceUnit({
const inner = (
<>
<span className="w-unit__leds">
<Led kind={kind} pulse={effective === "up"} />
<Led kind="amber" pulse={effective === "up"} />
</span>
<span className="min-w-0 flex-1">
<span className="w-unit__name block truncate">{name}</span>
<span className="w-unit__meta block truncate">
{host} {accessLabel ? `· ${accessLabel}` : ""}
<span className="w-unit__grip" aria-hidden="true" />
<span className="w-unit__grille w-grille" aria-hidden="true" />
<span className="w-unit__id">
<span className="w-unit__name w-engrave">{name}</span>
<span className="w-tape mt-1">
{host}
{accessLabel ? ` · ${accessLabel}` : ""}
</span>
<span className="sr-only">{desc}</span>
</span>
<span className="hidden text-right sm:block">
<span className="w-unit__meta block max-w-[26ch] truncate">{desc}</span>
</span>
<span className="w-plate shrink-0">
<Led kind={kind} /> {statusLabel}
<span className="w-unit__state">
<Led kind={kind} pulse={live === "up"} />
<span className="w-unit__status w-engrave">{label}</span>
</span>
<Screws />
</>
);
const cls = "w-unit w-metal-flat";
if (disabled) {
return (
<span className={cls} aria-disabled="true">
<span className="w-unit" aria-disabled="true">
{inner}
</span>
);
}
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}
</a>
);
}
/* -------------------------------------------------------- contributions */
/* -------------------------------------------------------- activity report */
interface HeatmapPoint {
timestamp: number;
@ -213,7 +284,9 @@ interface Activity {
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 [total, setTotal] = useState(0);
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 (
<div className="w-dashboard w-metal">
<div className="w-rack__head">
<span className="w-rack__title">gitdxd.activity</span>
<span className="ml-auto flex items-center gap-2">
<Led kind={state === "error" ? "red" : "green"} pulse={state === "ready"} />
<span className="w-unit__meta">{state === "error" ? "link down" : "streaming"}</span>
</span>
</div>
<div className="w-printout">
<div className="w-mt-paper w-printout__paper">
<span className="w-printout__perf w-printout__perf--l" aria-hidden="true" />
<span className="w-printout__perf w-printout__perf--r" aria-hidden="true" />
<div className="mt-4 flex flex-wrap items-end justify-between gap-3">
<div className="w-digits text-[2rem] leading-none sm:text-[2.5rem]">{total}</div>
<div className="w-kicker">contributions / last year</div>
</div>
<p className="w-print__head">
<span>GITDXD // ACTIVITY REPORT</span>
<span className="ml-auto">{state === "ready" ? "PRINTED LIVE" : state === "error" ? "PRINTER OFFLINE" : "SPOOLING…"}</span>
</p>
{state === "loading" ? (
<p className="w-screen-text mt-5 opacity-70">reading matrix…</p>
) : state === "error" ? (
<p className="w-screen-text mt-5 opacity-70">could not reach the forge.</p>
) : (
<>
<div className="w-ledgrid mt-5">
{weeks.map((w, i) => (
<div key={i} className="w-ledgrid__col">
{w.map((d) => (
<div key={d.date} className="w-ledgrid__cell" data-on={levelOn(d.level)} title={`${d.contribs} on ${d.date}`} />
))}
{state === "error" ? (
<p className="w-mono mt-5 text-[0.72rem] text-[#4a4133]">could not reach the forge.</p>
) : (
<>
<div className="mt-5 flex flex-wrap items-end gap-x-6 gap-y-2">
<div>
<p className="w-mono text-[0.62rem] tracking-[0.16em] text-[#4a4133]">TOTAL / 12 MONTHS</p>
<p className="w-serif text-[2.6rem] leading-none text-[var(--ink)]">
{state === "loading" ? "—" : total}
</p>
</div>
))}
</div>
<div className="mt-3 flex items-center justify-end gap-2">
<span className="w-unit__meta">less</span>
{[4, 3, 2, 1, 0].map((l) => (
<span key={l} className="w-ledgrid__cell" data-on={levelOn(l)} />
))}
<span className="w-unit__meta">more</span>
</div>
<p className="w-mono text-[0.62rem] leading-relaxed text-[#6f6450]">
each dot = one day
<br />
darkest = busiest
</p>
</div>
{activities.length > 0 ? (
<div className="w-panel mt-5 p-3">
<div className="w-screen-text">
<div className="w-dotgrid w-scroll" aria-hidden={state === "loading"}>
{weeks.map((w, i) => (
<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) => {
let msg = "commit";
try {
@ -353,15 +428,15 @@ export function ContributionsPanel() {
}
return (
<p key={i} className="truncate">
<span className="k">$</span> {a.repo?.full_name || "unknown"} — {msg}
&gt; {a.repo?.full_name || "unknown"} — {msg}
</p>
);
})}
</div>
</div>
) : null}
</>
)}
) : null}
</>
)}
</div>
</div>
);
}

View file

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

File diff suppressed because it is too large Load diff