redraw the desk as cartoon skeuomorphism: CSS-only materials (no textures), light cream desk, per-item hues, cork board, receipt, scoreboard
This commit is contained in:
parent
a71cfb1731
commit
1880a5b0db
14 changed files with 1165 additions and 989 deletions
50
README.md
50
README.md
|
|
@ -2,16 +2,17 @@
|
|||
|
||||
> The source code for my personal website, **[dvorakmortonxd.dev](https://dvorakmortonxd.dev)**.
|
||||
|
||||
It's a **skeuomorphic personal site**: a near-black desk with anodised hardware, machined
|
||||
aluminium, clean printed labels and a single orange accent, with the whole KDE desktop
|
||||
simulation kept behind a physical power switch. Two modes, one content set:
|
||||
It's a **cartoon-skeuomorphic personal site**: a warm cream desk with chunky, brightly coloured
|
||||
hardware resting on it, drawn entirely in CSS — no textures, no images — with the whole KDE
|
||||
desktop simulation kept behind a physical power switch. Two modes, one content set:
|
||||
|
||||
- **Web** (default) — the desk. A monitor carries the headline with a mechanical 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 are barcoded asset labels on a
|
||||
pegboard, the contribution heatmap is printed on a label-printer sheet, 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).
|
||||
- **Web** (default) — the desk. A monitor carries the headline with a candy-keycap keyboard in
|
||||
front of it and a mini rack beside it; services are 1U rack units with coloured strips and
|
||||
health LEDs, the 18 coding projects are labelled drives in a drawer, the machines are cards
|
||||
pinned to a cork board, the contribution heatmap is printed on a receipt, friends are business
|
||||
cards, the gym numbers are a scoreboard and the socials are a patch panel with coloured ports.
|
||||
[`/credits`](https://dvorakmortonxd.dev/credits) lists the type and nothing else — there are no
|
||||
asset licences because there are no assets.
|
||||
- **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.
|
||||
|
|
@ -24,10 +25,12 @@ 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 materials, modern finish** — near-black desk, dark anodised faceplates,
|
||||
machined aluminium plaques, matte printed labels and mint/orange 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.
|
||||
- **CSS-only skeuomorphism** — every surface is a flat gradient with a thick bottom edge, a soft
|
||||
cast shadow and `color-mix()` tints: slate chassis, cream plastic, paper, cork, black glass,
|
||||
LEDs, screws, pushpins. The web theme ships no images at all.
|
||||
- **Not afraid of colour** — a hue per item (orange / teal / blue / lilac / mint / coral /
|
||||
yellow) cycles through every list, so the desk reads as separate pieces of hardware instead of
|
||||
one grey wall.
|
||||
- **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.
|
||||
|
|
@ -67,15 +70,16 @@ 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 material system
|
||||
(near-black desk, anodised metal, machined aluminium, printed labels, black glass, LEDs, 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:** IBM Plex Sans/Mono for screens, machine labels and headings; Newsreader for things
|
||||
printed on paper; via `next/font/google`. The desktop keeps its system sans stack.
|
||||
- **Textures:** five CC0 materials from ambientCG — `desk` (near-black wood), `metal`, `paper`,
|
||||
`carbon`, `plastic` — downscaled to 512 px in `public/textures`. Attribution is on `/credits`.
|
||||
- **Version:** `3.3.0` (see `package.json`)
|
||||
`@theme`/`@utility` classes and the scroll reveals. `src/app/web.css` is the whole desk: the
|
||||
materials (slate chassis, cream plastic, paper, cork, black glass), the details (screws, LEDs,
|
||||
pushpins, rack rails, barcode, torn receipt edge) and every object layout, all CSS with
|
||||
`color-mix()` for tints and shades. `page.css` keeps the shared component styles the desktop
|
||||
apps still use, and `skeuo.css` is the KDE desktop.
|
||||
- **Fonts:** Nunito for hardware labels and headings, Newsreader for anything printed on paper,
|
||||
IBM Plex Sans/Mono for body copy and terminal text — all via `next/font/google`. The desktop
|
||||
keeps its system sans stack.
|
||||
- **Assets:** the web theme ships **no images**. The desktop keeps its own wallpaper and icons.
|
||||
- **Version:** `4.0.0` (see `package.json`)
|
||||
|
||||
## 🚀 Running Locally
|
||||
|
||||
|
|
@ -182,7 +186,7 @@ src/app/
|
|||
page.css → shared component styles still used by the desktop apps
|
||||
skeuo.css → KDE desktop styles
|
||||
data.ts → socials, codingProjects, techProjects, friends, wallpapers
|
||||
credits/page.tsx → credit page for the textures and fonts
|
||||
credits/page.tsx → credit page (type only — the desk ships no assets)
|
||||
revival-inc/page.tsx → embeds the bundled game at /public/revival-inc/index.html
|
||||
api/
|
||||
heatmap/route.ts → contribution heatmap proxy
|
||||
|
|
|
|||
46
agents.md
46
agents.md
|
|
@ -34,20 +34,24 @@ 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 desk**: a near-black surface with a monitor, mechanical keyboard,
|
||||
rack, drive drawer, pegboard of asset labels, label-printer 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.
|
||||
**cartoon-skeuomorphic desk**: a warm cream surface with a monitor, candy-keycap
|
||||
keyboard, mini rack, drive drawer, cork board of pinned cards, printed receipt,
|
||||
business cards, scoreboard and patch panel resting on it — all drawn in CSS, with
|
||||
no image assets. 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 material system
|
||||
(near-black desk, anodised metal, machined aluminium, printed labels, black
|
||||
glass, LEDs, 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.
|
||||
- Styling: **Tailwind v4** for layout plus **`web.css`**, which is the whole desk
|
||||
— materials (slate chassis, cream plastic, paper, cork, black glass), details
|
||||
(screws, LEDs, pushpins, rails, barcode, torn receipt edge) and object layouts,
|
||||
all CSS with `color-mix()` tints. `globals.css` holds the tokens, base styles
|
||||
and the reveals; `skeuo.css` and `page.css` are the desktop and the shared
|
||||
desktop-app styles. **The web theme ships no images.**
|
||||
- Hue system: list items take a `--tone` custom property from a shared palette
|
||||
(orange, teal, blue, lilac, mint, coral, yellow), set inline, so objects are
|
||||
colourful without per-item CSS.
|
||||
- Path alias: `@/*` → `src/*`.
|
||||
- Version: `3.3.0` (see `package.json`).
|
||||
- Version: `4.0.0` (see `package.json`).
|
||||
|
||||
### Scripts
|
||||
```bash
|
||||
|
|
@ -85,7 +89,7 @@ src/app/
|
|||
page.css → shared component styles used by the desktop apps
|
||||
skeuo.css → KDE Plasma desktop styles
|
||||
data.ts → socials, codingProjects, techProjects, friends, wallpapers
|
||||
credits/page.tsx → credits for the CC0 textures and fonts
|
||||
credits/page.tsx → credits (type only — the desk ships no assets)
|
||||
revival-inc/page.tsx → fullscreen iframe embedding /public/revival-inc/index.html
|
||||
api/
|
||||
heatmap/route.ts → proxy for git.dvorakmortonxd.dev contribution heatmap
|
||||
|
|
@ -124,14 +128,14 @@ scripts/
|
|||
desktop is up so its scroll position survives; the desktop mounts on demand
|
||||
(phones can never reach `skeuo`).
|
||||
|
||||
Modes: `web` = the skeuomorphic desk (default/landing), `skeuo` = KDE Plasma
|
||||
desktop.
|
||||
Modes: `web` = the cartoon-skeuomorphic desk (default/landing), `skeuo` = KDE
|
||||
Plasma desktop.
|
||||
|
||||
The desk uses **five CC0 textures** from ambientCG (`public/textures`: near-black
|
||||
desk wood, metal, paper, carbon, plastic), downscaled to 512 px and credited on
|
||||
`/credits`. The social SVGs are used as monochrome CSS masks (`.social-mask`);
|
||||
The desk ships **no image assets** — every surface is CSS. The only external
|
||||
visual files are the social SVGs, 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.
|
||||
renders an inline mark for it instead. The desktop still uses its own wallpaper
|
||||
and icons.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -242,6 +246,12 @@ not free placement. Don't confuse the two.
|
|||
- 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 web desk is CSS-only by design.** No textures, no images, no `url()`
|
||||
beyond the social masks. Build a surface from a flat gradient plus a thick
|
||||
bottom edge (`0 5px 0 <edge-colour>`) and a soft cast shadow, and shade with
|
||||
`color-mix(in srgb, var(--tone) N%, …)` instead of adding new hues to the
|
||||
stylesheet. New list items take a `--tone` from the shared `TONES` palette in
|
||||
`WebSite.tsx` / `web-ui.tsx` rather than hard-coding a colour.
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "dvorakmortonxd",
|
||||
"version": "3.3.0",
|
||||
"version": "4.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev-server.mjs",
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.4 KiB |
|
|
@ -27,6 +27,17 @@ const NAV = [
|
|||
{ href: "#contact", label: "contact" },
|
||||
];
|
||||
|
||||
/** One hue per item, so every list reads as separate pieces of hardware. */
|
||||
const TONES = [
|
||||
"var(--orange)",
|
||||
"var(--teal)",
|
||||
"var(--blue)",
|
||||
"var(--lilac)",
|
||||
"var(--mint)",
|
||||
"var(--coral)",
|
||||
"var(--yellow)",
|
||||
];
|
||||
|
||||
const SERVICES: {
|
||||
name: string;
|
||||
desc: string;
|
||||
|
|
@ -72,11 +83,11 @@ export default function WebSite() {
|
|||
return (
|
||||
<div className="w-page">
|
||||
{/* -------------------------------------------------------------- rail */}
|
||||
<header className="w-rail w-mt-metal-flush" style={{ borderRadius: 0 }}>
|
||||
<header className="w-rail">
|
||||
<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>
|
||||
<span className="w-wordmark">dvorakmortonxd</span>
|
||||
</a>
|
||||
|
||||
<nav className="ml-auto hidden items-center gap-6 md:flex" aria-label="Main">
|
||||
|
|
@ -95,7 +106,7 @@ export default function WebSite() {
|
|||
className="desktop-toggle flex items-center gap-3"
|
||||
title="Flip to Desktop mode"
|
||||
>
|
||||
<span className="w-mono hidden text-[0.6rem] tracking-[0.2em] text-[var(--ftext-faint)] sm:inline">
|
||||
<span className="w-mono hidden text-[0.6rem] tracking-[0.2em] text-[var(--ink-soft)] sm:inline">
|
||||
DESKTOP
|
||||
</span>
|
||||
<span className="w-rocker">
|
||||
|
|
@ -110,7 +121,7 @@ export default function WebSite() {
|
|||
onClick={() => setMenuOpen((v) => !v)}
|
||||
aria-expanded={menuOpen}
|
||||
aria-label={menuOpen ? "Close menu" : "Open menu"}
|
||||
className="w-keybtn !px-3 !py-2"
|
||||
className="w-keybtn"
|
||||
>
|
||||
{menuOpen ? "close" : "menu"}
|
||||
</button>
|
||||
|
|
@ -120,7 +131,7 @@ export default function WebSite() {
|
|||
|
||||
{menuOpen ? (
|
||||
<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"
|
||||
className="w-plastic mx-auto mb-3 flex w-[calc(100%-2.5rem)] max-w-[1120px] flex-col gap-1 p-3 md:hidden"
|
||||
aria-label="Main"
|
||||
>
|
||||
{NAV.map((item) => (
|
||||
|
|
@ -128,7 +139,7 @@ export default function WebSite() {
|
|||
key={item.href}
|
||||
href={item.href}
|
||||
onClick={() => setMenuOpen(false)}
|
||||
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"
|
||||
className="w-display border-b-2 border-dashed border-[var(--plastic-edge)] py-3 text-[0.9rem] text-[var(--plastic-ink)] last:border-b-0"
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
|
|
@ -149,18 +160,18 @@ export default function WebSite() {
|
|||
<span className="a">martin@dvorakmortonxd</span>:~$ whoami
|
||||
<span className="w-caret ml-2" />
|
||||
</p>
|
||||
<h1 className="w-display mt-5 text-[clamp(1.8rem,4.1vw,2.75rem)] text-[var(--ftext)]">
|
||||
<h1 className="w-display mt-5 text-[clamp(1.8rem,4.1vw,2.75rem)] text-[var(--glass-ink)]">
|
||||
My own little internet — one rack, zero subscriptions.
|
||||
</h1>
|
||||
<p className="w-prose mt-5 max-w-[54ch] text-[var(--ftext-dim)]">
|
||||
<p className="w-prose mt-5 max-w-[54ch] text-[var(--glass-dim)]">
|
||||
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>
|
||||
<p className="mt-7 flex flex-wrap gap-x-8 gap-y-3">
|
||||
<p className="mt-7 flex flex-wrap gap-3">
|
||||
<a href="#services" className="w-cmd">
|
||||
$ open rack --services
|
||||
</a>
|
||||
<a href="#projects" className="w-cmd">
|
||||
<a href="#projects" className="w-cmd w-cmd--quiet">
|
||||
$ open drawer --projects
|
||||
</a>
|
||||
</p>
|
||||
|
|
@ -175,10 +186,10 @@ export default function WebSite() {
|
|||
<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" />
|
||||
<Plaque value={String(SERVICES.length)} label="services" tone="var(--orange)" />
|
||||
<Plaque value={String(codingProjects.length)} label="projects" tone="var(--teal)" />
|
||||
<Plaque value={String(techProjects.length)} label="machines" tone="var(--blue)" />
|
||||
<Plaque value="401" label="kg best SBD" tone="var(--coral)" />
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
|
|
@ -193,7 +204,7 @@ export default function WebSite() {
|
|||
</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">
|
||||
<article className="w-paper w-sheet">
|
||||
<div className="w-sheet__head">
|
||||
<span>manifest.txt</span>
|
||||
<span className="ml-auto">printed on the desk</span>
|
||||
|
|
@ -224,15 +235,13 @@ export default function WebSite() {
|
|||
</Reveal>
|
||||
|
||||
<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-serif mt-3 text-[1.15rem] text-[var(--ink)]">
|
||||
<aside className="w-paper w-cardindex lg:mt-14" style={{ "--tone": "var(--teal)" } as CSSProperties}>
|
||||
<p className="w-kicker">index card</p>
|
||||
<p className="w-serif mt-3 text-[1.2rem] text-[var(--ink)]">
|
||||
“If it has a fan, I’ll find a use for it.”
|
||||
</p>
|
||||
<div className="w-rule my-4" />
|
||||
<ul className="w-mono space-y-2 text-[0.66rem] text-[var(--ink-soft)]">
|
||||
<ul className="w-mono space-y-2 text-[0.68rem] text-[var(--ink-soft)]">
|
||||
<li>school · sssvt.cz</li>
|
||||
<li>gym · PPL 3–5×/week</li>
|
||||
<li>games · Terraria, Minecraft</li>
|
||||
|
|
@ -251,14 +260,13 @@ export default function WebSite() {
|
|||
<div className="grid gap-7 lg:grid-cols-[minmax(0,1.3fr)_minmax(0,1fr)] lg:items-start">
|
||||
<Reveal delay={60}>
|
||||
<div className="w-note">
|
||||
<p className="w-mono text-[0.62rem] tracking-[0.16em] text-[#6b7075]">
|
||||
NOW.TXT · UPDATED 09/2026
|
||||
</p>
|
||||
<p className="w-prose mt-4 text-[#34383b]">
|
||||
<span className="w-pin" aria-hidden="true" />
|
||||
<p className="w-kicker !text-[#8a7420]">now.txt · updated 09/2026</p>
|
||||
<p className="w-prose mt-4 text-[#5c4a1f]">
|
||||
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.
|
||||
</p>
|
||||
<p className="w-prose mt-3 text-[#34383b]">
|
||||
<p className="w-prose mt-3 text-[#5c4a1f]">
|
||||
Me and my friend{" "}
|
||||
<a className="w-link" href="https://skopec.dev" target="_blank" rel="noopener noreferrer">
|
||||
Pepa
|
||||
|
|
@ -278,24 +286,25 @@ export default function WebSite() {
|
|||
</Reveal>
|
||||
|
||||
<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">
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="w-paper w-cardindex w-cardindex--alt" style={{ "--tone": "var(--coral)" } as CSSProperties}>
|
||||
<p className="w-kicker">bench status</p>
|
||||
<ul className="w-display mt-4 space-y-2.5 text-[0.82rem] text-[var(--ink)]">
|
||||
<li className="flex items-center gap-2.5">
|
||||
<Led kind="on" /> smp · accepting players
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<li className="flex items-center gap-2.5">
|
||||
<Led kind="amber" pulse /> maturita.c · in development
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<li className="flex items-center gap-2.5">
|
||||
<Led kind="on" /> gym · PPL 3–5×/week
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<i className="w-pen ml-6 w-44" aria-hidden="true" />
|
||||
<div className="flex items-end gap-6">
|
||||
<i className="w-pen mb-3 w-40" aria-hidden="true" />
|
||||
<span className="w-mug mr-6" aria-hidden="true" />
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
</div>
|
||||
|
|
@ -307,11 +316,13 @@ export default function WebSite() {
|
|||
<SectionHead index="03" label="rack" title="Six machines, one shelf" />
|
||||
</Reveal>
|
||||
<Reveal delay={60}>
|
||||
<div className="w-mt-metal w-rack">
|
||||
<div className="w-chassis 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">
|
||||
<span className="w-display text-[0.7rem] tracking-[0.18em] text-[var(--chassis-ink)]">
|
||||
RACK 01 · SERVICES
|
||||
</span>
|
||||
<span className="w-mono ml-auto hidden text-[0.62rem] tracking-[0.12em] text-[var(--chassis-faint)] sm:inline">
|
||||
health checked from this page
|
||||
</span>
|
||||
<span className="w-rack__vent w-vents" />
|
||||
|
|
@ -319,8 +330,8 @@ export default function WebSite() {
|
|||
<div className="w-rack__grid">
|
||||
<span className="w-rails" />
|
||||
<div>
|
||||
{SERVICES.map((s) => (
|
||||
<ServiceUnit key={s.name} {...s} />
|
||||
{SERVICES.map((s, i) => (
|
||||
<ServiceUnit key={s.name} tone={TONES[i % TONES.length]} {...s} />
|
||||
))}
|
||||
</div>
|
||||
<span className="w-rails" />
|
||||
|
|
@ -335,8 +346,8 @@ export default function WebSite() {
|
|||
<SectionHead index="04" label="drawer" title="Eighteen drives, all labelled" />
|
||||
</Reveal>
|
||||
<Reveal delay={60}>
|
||||
<div className="w-mt-metal w-drawer">
|
||||
<div className="w-mt-plastic w-drawer__inner w-scroll">
|
||||
<div className="w-chassis w-drawer">
|
||||
<div className="w-well 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);
|
||||
|
|
@ -347,7 +358,12 @@ export default function WebSite() {
|
|||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-drive"
|
||||
style={{ "--tilt": `${i % 2 ? 0.5 : -0.6}deg` } as CSSProperties}
|
||||
style={
|
||||
{
|
||||
"--tilt": `${i % 2 ? 0.6 : -0.7}deg`,
|
||||
"--tone": TONES[i % TONES.length],
|
||||
} as CSSProperties
|
||||
}
|
||||
title={p.desc}
|
||||
>
|
||||
<span className="w-drive__port" aria-hidden="true" />
|
||||
|
|
@ -364,7 +380,7 @@ export default function WebSite() {
|
|||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-mt-metal-flush w-drawer__front">
|
||||
<div className="w-chassis-2 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" />
|
||||
|
|
@ -376,18 +392,22 @@ export default function WebSite() {
|
|||
{/* ------------------------------------------------------ hardware */}
|
||||
<section id="hardware" className="scroll-mt-28">
|
||||
<Reveal>
|
||||
<SectionHead index="05" label="machines" title="Tagged on the pegboard" />
|
||||
<SectionHead index="05" label="machines" title="Pinned to the cork board" />
|
||||
</Reveal>
|
||||
<Reveal delay={60}>
|
||||
<div className="w-mt-metal w-pegboard">
|
||||
<div className="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__clip" aria-hidden="true" />
|
||||
<h3 className="w-display text-[1rem] text-[var(--ink)]">{t.title}</h3>
|
||||
<p className="w-prose mb-4 mt-2 text-[0.8rem] text-[var(--ink-soft)]">{t.desc}</p>
|
||||
<span className="w-barcode w-hangtag__bar" aria-hidden="true" />
|
||||
<div className="grid gap-5 sm:grid-cols-2 xl:grid-cols-3">
|
||||
{techProjects.map((t, i) => (
|
||||
<article
|
||||
key={t.title}
|
||||
className="w-paper w-hangtag"
|
||||
style={{ "--tone": TONES[i % TONES.length] } as CSSProperties}
|
||||
>
|
||||
<span className="w-pin" aria-hidden="true" />
|
||||
<h3 className="w-display text-[1.05rem] text-[var(--ink)]">{t.title}</h3>
|
||||
<span className="w-hangtag__rule" aria-hidden="true" />
|
||||
<p className="w-prose text-[0.84rem] text-[var(--ink-soft)]">{t.desc}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -399,7 +419,7 @@ export default function WebSite() {
|
|||
{/* -------------------------------------------------- activity feed */}
|
||||
<section id="gitdxd" className="scroll-mt-28">
|
||||
<Reveal>
|
||||
<SectionHead index="06" label="gitdxd" title="The printer spits out the forge" />
|
||||
<SectionHead index="06" label="gitdxd" title="The receipt for this year's commits" />
|
||||
</Reveal>
|
||||
<Reveal delay={60}>
|
||||
<Report />
|
||||
|
|
@ -418,11 +438,16 @@ export default function WebSite() {
|
|||
href={f.realUrl || f.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-mt-paper w-bizcard"
|
||||
style={{ "--tilt": `${i === 1 ? 0.9 : i === 2 ? -1 : -1.4}deg` } as CSSProperties}
|
||||
className="w-bizcard"
|
||||
style={
|
||||
{
|
||||
"--tilt": `${i === 1 ? 0.9 : i === 2 ? -1 : -1.4}deg`,
|
||||
"--tone": TONES[(i + 1) % TONES.length],
|
||||
} as CSSProperties
|
||||
}
|
||||
>
|
||||
<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>
|
||||
<p className="w-serif text-[1.2rem] text-[var(--ink)]">{f.name}</p>
|
||||
<p className="w-mono mt-1 text-[0.68rem] text-[var(--ink-soft)]">{f.desc}</p>
|
||||
</a>
|
||||
</Reveal>
|
||||
))}
|
||||
|
|
@ -432,29 +457,32 @@ export default function WebSite() {
|
|||
{/* ----------------------------------------------------------- gym */}
|
||||
<section id="gym" className="scroll-mt-28">
|
||||
<Reveal>
|
||||
<SectionHead index="08" label="strength" title="Numbers on the clipboard" />
|
||||
<SectionHead index="08" label="strength" title="Numbers on the scoreboard" />
|
||||
</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>
|
||||
<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 className="w-chassis w-score">
|
||||
<div className="w-score__head">
|
||||
<Led kind="on" pulse slow />
|
||||
<span>TRAINING LOG · 73 KG BODYWEIGHT</span>
|
||||
<span className="ml-auto hidden sm:inline">3 LIFTS</span>
|
||||
</div>
|
||||
<div className="w-score__tiles">
|
||||
{LIFTS.map((l, i) => (
|
||||
<div
|
||||
key={l.lift}
|
||||
className="w-score__tile"
|
||||
style={{ "--tone": TONES[(i + 2) % TONES.length] } as CSSProperties}
|
||||
>
|
||||
<span className="w-score__num">
|
||||
{l.weight}
|
||||
<small>kg</small>
|
||||
</span>
|
||||
<span className="w-score__lift">{l.lift}</span>
|
||||
<span className="w-score__date">{l.date}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="w-score__foot">BEST SBD TOTAL · 401 KG · 165 + 111 + 125</p>
|
||||
</div>
|
||||
</Reveal>
|
||||
</section>
|
||||
|
|
@ -465,16 +493,23 @@ export default function WebSite() {
|
|||
<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-chassis w-patch">
|
||||
<div className="w-chassis-2 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>
|
||||
<span>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">
|
||||
{socials.map((s, i) => (
|
||||
<a
|
||||
key={s.name}
|
||||
href={s.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-port"
|
||||
style={{ "--tone": TONES[i % TONES.length] } as CSSProperties}
|
||||
>
|
||||
<span className="w-port__jack" aria-hidden="true">
|
||||
<span className="w-port__slot" />
|
||||
</span>
|
||||
|
|
@ -485,7 +520,7 @@ export default function WebSite() {
|
|||
</a>
|
||||
))}
|
||||
</div>
|
||||
<p className="w-mono mt-5 text-[0.64rem] tracking-[0.06em] text-[var(--ftext-faint)]">
|
||||
<p className="w-mono mt-5 text-[0.66rem] text-[var(--chassis-faint)]">
|
||||
fastest reply is Discord or Instagram · mail{" "}
|
||||
<a className="w-link" href="mailto:dvorak@dvorakmortonxd.dev">
|
||||
dvorak@dvorakmortonxd.dev
|
||||
|
|
@ -501,11 +536,13 @@ export default function WebSite() {
|
|||
{/* ------------------------------------------------------------ footer */}
|
||||
<footer className="mx-auto w-full max-w-[1120px] px-5 pb-16 sm:px-8">
|
||||
<Reveal>
|
||||
<div className="w-mt-alu relative flex flex-col gap-5 px-6 py-5 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="w-plastic relative flex flex-col gap-5 px-6 py-5 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="min-w-0">
|
||||
<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.3.0 · GPL-3.0-only · built with Next.js · textures CC0 via{" "}
|
||||
<p className="w-display text-[0.8rem] tracking-[0.16em] text-[var(--plastic-ink)]">
|
||||
DVORAKMORTONXD.LOCAL
|
||||
</p>
|
||||
<p className="w-mono mt-1.5 text-[0.64rem] text-[var(--ink-soft)]">
|
||||
v4.0.0 · GPL-3.0-only · built with Next.js · no textures, all CSS ·{" "}
|
||||
<a className="w-link" href="/credits">
|
||||
credits
|
||||
</a>
|
||||
|
|
@ -514,15 +551,15 @@ export default function WebSite() {
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => setTheme("skeuo")}
|
||||
className="w-keybtn desktop-toggle shrink-0 self-start sm:self-auto"
|
||||
className="w-keybtn w-btn-accent desktop-toggle shrink-0 self-start sm:self-auto"
|
||||
title="Flip to Desktop mode"
|
||||
>
|
||||
<Led kind="amber" /> desktop mode
|
||||
<Led kind="on" /> desktop mode
|
||||
</button>
|
||||
<i className="w-screw w-screw--alu w-corner-tr" aria-hidden="true" />
|
||||
<i className="w-screw w-screw--alu w-corner-bl" aria-hidden="true" />
|
||||
<i className="w-screw w-corner-tr" aria-hidden="true" />
|
||||
<i className="w-screw w-corner-bl" aria-hidden="true" />
|
||||
</div>
|
||||
<p className="w-mono mt-4 text-[0.62rem] text-[var(--ftext-faint)]">
|
||||
<p className="w-mono mt-4 text-[0.64rem] text-[var(--ink-soft)]">
|
||||
© 2025–2026 Martin Dvořák — follow the licence or don’t, I don’t mind
|
||||
</p>
|
||||
</Reveal>
|
||||
|
|
|
|||
|
|
@ -79,22 +79,34 @@ export function Led({
|
|||
);
|
||||
}
|
||||
|
||||
export function Screws({ count = 2, alu = false, className = "" }: { count?: number; alu?: boolean; className?: string }) {
|
||||
export function Screws({ count = 2, className = "" }: { count?: number; className?: string }) {
|
||||
return (
|
||||
<span className={`w-unit__screws ${className}`}>
|
||||
{Array.from({ length: count }).map((_, i) => (
|
||||
<i key={i} className={`w-screw${alu ? " w-screw--alu" : ""}`} />
|
||||
<i key={i} className="w-screw" />
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/** Section heading: an etched aluminium tab on the desk + painted title. */
|
||||
/** Section heading: a coloured chip on the desk + a painted title. */
|
||||
export function SectionHead({ index, label, title }: { index: string; label: string; title: string }) {
|
||||
const tones = [
|
||||
"var(--orange)",
|
||||
"var(--teal)",
|
||||
"var(--blue)",
|
||||
"var(--lilac)",
|
||||
"var(--mint)",
|
||||
"var(--coral)",
|
||||
"var(--yellow)",
|
||||
];
|
||||
const n = Number.parseInt(index, 10);
|
||||
const tone = tones[Number.isFinite(n) ? (n - 1) % tones.length : 0];
|
||||
|
||||
return (
|
||||
<div className="w-sechead">
|
||||
<span className="w-mt-alu w-sechead__tab">
|
||||
<b>{index}</b> {label}
|
||||
<span className="w-chip w-sechead__tab" style={{ "--tone": tone } as CSSProperties}>
|
||||
{index} · {label}
|
||||
</span>
|
||||
<h2 className="w-display w-paint w-sechead__title">{title}</h2>
|
||||
</div>
|
||||
|
|
@ -107,9 +119,9 @@ export function Monitor({ children, className = "" }: { children: ReactNode; cla
|
|||
return (
|
||||
<div className={`w-monitor ${className}`}>
|
||||
<div className="w-monitor__frame">
|
||||
<div className="w-mt-glass w-monitor__glass">{children}</div>
|
||||
<div className="w-glass w-monitor__glass">{children}</div>
|
||||
<div className="w-monitor__chin">
|
||||
<Led kind="amber" />
|
||||
<Led kind="on" />
|
||||
<span className="w-monitor__brand">DXD</span>
|
||||
<span className="w-monitor__osd" aria-hidden="true">
|
||||
<i className="w-osdkey" />
|
||||
|
|
@ -127,6 +139,7 @@ export function Monitor({ children, className = "" }: { children: ReactNode; cla
|
|||
}
|
||||
|
||||
const KEY_ROWS = ["1234567890-=".split(""), "qwertyuiop[]".split(""), "asdfghjkl;'".split("")];
|
||||
const CAP_TONES = ["var(--mint)", "var(--blue)", "var(--yellow)", "var(--coral)", "var(--lilac)", "var(--teal)"];
|
||||
|
||||
export function Keyboard() {
|
||||
return (
|
||||
|
|
@ -134,16 +147,20 @@ export function Keyboard() {
|
|||
<div className="w-keyboard__deck">
|
||||
<div className="w-keyboard__row">
|
||||
<span className="w-key w-key--accent">esc</span>
|
||||
{KEY_ROWS[0].map((k) => (
|
||||
<span key={k} className="w-key">
|
||||
{KEY_ROWS[0].map((k, i) => (
|
||||
<span key={k} className="w-key" style={{ "--tone": CAP_TONES[i % CAP_TONES.length] } as CSSProperties}>
|
||||
{k}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
{KEY_ROWS.slice(1).map((row, i) => (
|
||||
<div key={i} className="w-keyboard__row">
|
||||
{row.map((k) => (
|
||||
<span key={k} className="w-key">
|
||||
{KEY_ROWS.slice(1).map((row, r) => (
|
||||
<div key={r} className="w-keyboard__row">
|
||||
{row.map((k, i) => (
|
||||
<span
|
||||
key={k}
|
||||
className="w-key"
|
||||
style={{ "--tone": CAP_TONES[(i + r + 2) % CAP_TONES.length] } as CSSProperties}
|
||||
>
|
||||
{k}
|
||||
</span>
|
||||
))}
|
||||
|
|
@ -158,24 +175,27 @@ export function Keyboard() {
|
|||
);
|
||||
}
|
||||
|
||||
const RACK_TONES = ["var(--mint)", "var(--blue)", "var(--yellow)", "var(--coral)"];
|
||||
|
||||
/** 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-chassis w-rack" 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-display text-[0.68rem] tracking-[0.2em] text-[var(--chassis-ink)]">{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" />
|
||||
<div key={i} className="w-unit !min-h-[48px] !gap-[10px] !py-2" style={{ "--tone": RACK_TONES[i] } as CSSProperties}>
|
||||
<span className="w-unit__grip !h-7 !w-3" />
|
||||
<span className="w-unit__grille !h-7 !w-10" />
|
||||
<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]">
|
||||
<span className="w-display ml-auto text-[0.6rem] tracking-[0.14em] text-[var(--chassis-dim)]">
|
||||
{["pxe", "aio", "nas", "web"][i]}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -187,13 +207,13 @@ export function MiniRack({ label = "DXD-01" }: { label?: string }) {
|
|||
);
|
||||
}
|
||||
|
||||
/** A machined aluminium plate with one number etched into it. */
|
||||
export function Plaque({ value, label }: { value: string; label: string }) {
|
||||
/** A big coloured sticker with one number on it. */
|
||||
export function Plaque({ value, label, tone = "var(--orange)" }: { value: string; label: string; tone?: string }) {
|
||||
return (
|
||||
<div className="w-mt-alu w-plaque">
|
||||
<div className="w-sticker w-plaque" style={{ "--tone": tone } as CSSProperties}>
|
||||
<span className="w-plaque__value">{value}</span>
|
||||
<span className="w-plaque__label">{label}</span>
|
||||
<Led kind="amber" className="w-plaque__dot" />
|
||||
<i className="w-plaque__dot" aria-hidden="true" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -209,6 +229,7 @@ export function ServiceUnit({
|
|||
access,
|
||||
checkUrl,
|
||||
disabled,
|
||||
tone = "var(--teal)",
|
||||
}: {
|
||||
name: string;
|
||||
desc: string;
|
||||
|
|
@ -216,6 +237,7 @@ export function ServiceUnit({
|
|||
access?: ServiceAccess;
|
||||
checkUrl?: string;
|
||||
disabled?: boolean;
|
||||
tone?: string;
|
||||
}) {
|
||||
const [status, setStatus] = useState<"pending" | "up" | "down">("pending");
|
||||
|
||||
|
|
@ -237,6 +259,7 @@ export function ServiceUnit({
|
|||
|
||||
const live = disabled ? "up" : status;
|
||||
const kind = live === "up" ? "on" : live === "down" ? "off" : "amber";
|
||||
const stateClass = disabled ? "is-wait" : live === "up" ? "is-on" : live === "down" ? "is-off" : "is-wait";
|
||||
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;
|
||||
|
||||
|
|
@ -253,8 +276,8 @@ export function ServiceUnit({
|
|||
<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">
|
||||
<span className="w-unit__name">{name}</span>
|
||||
<span className="w-tape mt-1.5">
|
||||
{host}
|
||||
{accessLabel ? ` · ${accessLabel}` : ""}
|
||||
</span>
|
||||
|
|
@ -262,7 +285,7 @@ export function ServiceUnit({
|
|||
</span>
|
||||
<span className="w-unit__state">
|
||||
<Led kind={kind} pulse={live === "up"} />
|
||||
<span className="w-unit__status w-engrave">{label}</span>
|
||||
<span className={`w-unit__status ${stateClass}`}>{label}</span>
|
||||
</span>
|
||||
<Screws />
|
||||
</>
|
||||
|
|
@ -270,13 +293,20 @@ export function ServiceUnit({
|
|||
|
||||
if (disabled) {
|
||||
return (
|
||||
<span className="w-unit" aria-disabled="true">
|
||||
<span className="w-unit" style={{ "--tone": tone } as CSSProperties} aria-disabled="true">
|
||||
{inner}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<a href={url} target="_blank" rel="noopener noreferrer" className="w-unit w-unit--link" title={desc}>
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-unit w-unit--link"
|
||||
style={{ "--tone": tone } as CSSProperties}
|
||||
title={desc}
|
||||
>
|
||||
{inner}
|
||||
</a>
|
||||
);
|
||||
|
|
@ -295,7 +325,7 @@ interface Activity {
|
|||
repo?: { full_name?: string };
|
||||
}
|
||||
|
||||
const DOT_OPACITY = [0.07, 0.3, 0.48, 0.64, 0.82];
|
||||
const DOT_OPACITY = [0.12, 0.35, 0.55, 0.75, 1];
|
||||
|
||||
export function Report() {
|
||||
const [weeks, setWeeks] = useState<{ date: string; contribs: number; level: number }[][]>([]);
|
||||
|
|
@ -377,7 +407,7 @@ export function Report() {
|
|||
|
||||
return (
|
||||
<div className="w-printout">
|
||||
<div className="w-mt-paper w-printout__paper">
|
||||
<div className="w-printout__paper">
|
||||
<p className="w-print__head">
|
||||
<span>GITDXD · ACTIVITY REPORT · 12 MONTHS</span>
|
||||
<span className="ml-auto flex items-center gap-3">
|
||||
|
|
@ -387,20 +417,20 @@ export function Report() {
|
|||
</p>
|
||||
|
||||
{state === "error" ? (
|
||||
<p className="w-mono mt-5 text-[0.72rem] text-[#4a4133]">could not reach the forge.</p>
|
||||
<p className="w-mono mt-5 text-[0.74rem] text-[var(--ink-soft)]">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-display text-[2.6rem] leading-none text-[var(--ink)]">
|
||||
<p className="w-kicker">total / 12 months</p>
|
||||
<p className="w-display mt-1 text-[2.6rem] leading-none text-[var(--ink)]">
|
||||
{state === "loading" ? "—" : total}
|
||||
</p>
|
||||
</div>
|
||||
<p className="w-mono text-[0.62rem] leading-relaxed text-[#6f6450]">
|
||||
each dot = one day
|
||||
<p className="w-mono text-[0.64rem] leading-relaxed text-[var(--ink-soft)]">
|
||||
each square = one day
|
||||
<br />
|
||||
darkest = busiest
|
||||
strongest orange = busiest
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -4,34 +4,32 @@ import "../web.css";
|
|||
|
||||
export const metadata: Metadata = {
|
||||
title: "credits · dvorakmortonxd.dev",
|
||||
description: "Textures, type and other borrowed work used on dvorakmortonxd.dev.",
|
||||
description: "Type and other borrowed work used on dvorakmortonxd.dev.",
|
||||
};
|
||||
|
||||
const TEXTURES = [
|
||||
{ file: "desk.jpg", asset: "Wood 028", id: "Wood028", use: "the near-black desk" },
|
||||
{ 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: "Nunito",
|
||||
by: "Vernon Adams, Cyreal, Jacques Le Bailly",
|
||||
use: "hardware labels, headings, buttons",
|
||||
href: "https://fonts.google.com/specimen/Nunito",
|
||||
},
|
||||
{
|
||||
name: "Newsreader",
|
||||
by: "Production Type",
|
||||
use: "printed headlines, sheet copy, stamped numbers",
|
||||
use: "anything printed on paper",
|
||||
href: "https://fonts.google.com/specimen/Newsreader",
|
||||
},
|
||||
{
|
||||
name: "IBM Plex Sans",
|
||||
by: "IBM",
|
||||
use: "body text on the desk",
|
||||
use: "body copy",
|
||||
href: "https://fonts.google.com/specimen/IBM+Plex+Sans",
|
||||
},
|
||||
{
|
||||
name: "IBM Plex Mono",
|
||||
by: "IBM",
|
||||
use: "engraved labels, terminal text, index cards",
|
||||
use: "terminal text, receipt, technical labels",
|
||||
href: "https://fonts.google.com/specimen/IBM+Plex+Mono",
|
||||
},
|
||||
];
|
||||
|
|
@ -41,70 +39,34 @@ export default function Credits() {
|
|||
<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-alu w-sechead__tab">credits</span>
|
||||
<span className="w-chip w-sechead__tab" style={{ "--tone": "var(--mint)" } as React.CSSProperties}>
|
||||
credits
|
||||
</span>
|
||||
<h1 className="w-display 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 className="w-prose w-paint max-w-[62ch] text-[var(--ink-soft)]">
|
||||
The desk contains <strong>no images at all</strong> — every surface, screw, LED, paper sheet and coffee
|
||||
mug is drawn in CSS. That means there are no photography or texture licences to track: the only
|
||||
borrowed work is type.
|
||||
</p>
|
||||
|
||||
<section className="mt-14">
|
||||
<div className="w-sechead !mb-6">
|
||||
<span className="w-mt-alu w-sechead__tab">textures</span>
|
||||
<h2 className="w-display w-paint w-sechead__title !text-[1.3rem]">ambientCG — CC0 1.0</h2>
|
||||
</div>
|
||||
<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) under the{" "}
|
||||
<a
|
||||
className="w-link"
|
||||
href="https://creativecommons.org/publicdomain/zero/1.0/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
CC0 1.0
|
||||
</a>{" "}
|
||||
public-domain dedication. I downloaded the 1K JPG colour maps, downscaled them to 512 px and
|
||||
tint them in CSS.
|
||||
</p>
|
||||
|
||||
<ul className="mt-7 grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
|
||||
{TEXTURES.map((t) => (
|
||||
<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"
|
||||
href={`https://ambientcg.com/view?id=${t.id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
ambientCG / {t.id}
|
||||
</a>
|
||||
<br />
|
||||
licence: CC0 1.0
|
||||
<br />
|
||||
used as <code>{`/textures/${t.file}`}</code> — {t.use}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="mt-16">
|
||||
<div className="w-sechead !mb-6">
|
||||
<span className="w-mt-alu w-sechead__tab">type</span>
|
||||
<h2 className="w-display w-paint w-sechead__title !text-[1.3rem]">Google Fonts — SIL OFL 1.1</h2>
|
||||
<span className="w-chip w-sechead__tab" style={{ "--tone": "var(--teal)" } as React.CSSProperties}>
|
||||
type
|
||||
</span>
|
||||
<h2 className="w-display w-paint w-sechead__title !text-[1.3rem]">
|
||||
Google Fonts — SIL Open Font License 1.1
|
||||
</h2>
|
||||
</div>
|
||||
<ul className="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
|
||||
{FONTS.map((f) => (
|
||||
<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>
|
||||
<ul className="grid gap-5 sm:grid-cols-2 xl:grid-cols-4">
|
||||
{FONTS.map((f, i) => (
|
||||
<li
|
||||
key={f.name}
|
||||
className="w-paper w-cardindex"
|
||||
style={{ "--tone": ["var(--orange)", "var(--blue)", "var(--lilac)", "var(--mint)"][i % 4] } as React.CSSProperties}
|
||||
>
|
||||
<p className="w-display 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 />
|
||||
|
|
@ -122,13 +84,15 @@ export default function Credits() {
|
|||
|
||||
<section className="mt-16">
|
||||
<div className="w-sechead !mb-6">
|
||||
<span className="w-mt-alu w-sechead__tab">notes</span>
|
||||
<span className="w-chip w-sechead__tab" style={{ "--tone": "var(--lilac)" } as React.CSSProperties}>
|
||||
notes
|
||||
</span>
|
||||
<h2 className="w-display w-paint w-sechead__title !text-[1.3rem]">Other marks</h2>
|
||||
</div>
|
||||
<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 className="w-prose w-paint max-w-[62ch] text-[var(--ink-soft)]">
|
||||
The patch panel uses brand marks that remain the property of their owners and are used only to
|
||||
link to those accounts. Everything written on the paper — and every pixel of the desk itself — is
|
||||
mine. The desktop mode (the KDE simulation) uses its own separate assets.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
|
|
|
|||
|
|
@ -37,15 +37,20 @@
|
|||
--ease-cinematic: cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
/* Beats page.css's :root when the editorial theme is active. */
|
||||
/* Beats page.css's :root when the web theme is active. The desk is a light,
|
||||
colourful, cartoon-skeuomorphic surface — the dark gruvbox values in :root
|
||||
are only the fallback for the desktop mode. */
|
||||
html[data-theme="web"] {
|
||||
color-scheme: dark;
|
||||
--accent: #fe8019;
|
||||
--accent2: #d65d0e;
|
||||
--accent3: #fabd2f;
|
||||
--green: #b8bb26;
|
||||
--red: #fb4934;
|
||||
--gray: #a89984;
|
||||
color-scheme: light;
|
||||
--bg: #f7e7c6;
|
||||
--bg-raised: #fffdf6;
|
||||
--bg-well: #eed9b0;
|
||||
--accent: #f4823a;
|
||||
--accent2: #d4611c;
|
||||
--accent3: #f2c14e;
|
||||
--green: #58bd72;
|
||||
--red: #ea6f6a;
|
||||
--gray: #7f7768;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
|
|
@ -71,7 +76,7 @@ html[data-theme="web"] {
|
|||
}
|
||||
|
||||
html[data-theme="web"] ::selection {
|
||||
background: rgba(254, 128, 25, 0.28);
|
||||
background: rgba(244, 130, 58, 0.3);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
|
|
@ -88,12 +93,12 @@ html[data-theme="web"] ::-webkit-scrollbar-track {
|
|||
background: var(--bg-well);
|
||||
}
|
||||
html[data-theme="web"] ::-webkit-scrollbar-thumb {
|
||||
background: #3c3836;
|
||||
background: #d9c49b;
|
||||
border: 3px solid var(--bg-well);
|
||||
border-radius: 99px;
|
||||
}
|
||||
html[data-theme="web"] ::-webkit-scrollbar-thumb:hover {
|
||||
background: #504945;
|
||||
background: #c9b184;
|
||||
}
|
||||
|
||||
/* Phones never get the desktop-mode button. */
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import type { Metadata } from "next";
|
||||
import { Newsreader, IBM_Plex_Sans, IBM_Plex_Mono } from "next/font/google";
|
||||
import { Newsreader, IBM_Plex_Sans, IBM_Plex_Mono, Nunito } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import ThemeScript from "./components/ThemeScript";
|
||||
|
||||
// Display serif for headlines, Plex for body/labels — same pairing as the
|
||||
// reference editorial site.
|
||||
// The web desk pairs a rounded sans (hardware labels, headings) with a printed
|
||||
// serif (anything on paper) and a mono (terminal text + technical labels).
|
||||
const news = Newsreader({
|
||||
variable: "--font-news",
|
||||
subsets: ["latin"],
|
||||
|
|
@ -24,6 +24,12 @@ const plexMono = IBM_Plex_Mono({
|
|||
weight: ["400", "500"],
|
||||
display: "swap",
|
||||
});
|
||||
const round = Nunito({
|
||||
variable: "--font-round",
|
||||
subsets: ["latin"],
|
||||
weight: ["600", "700", "800"],
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "dvorakmortonxd.dev",
|
||||
|
|
@ -51,7 +57,7 @@ export default function RootLayout({
|
|||
lang="en"
|
||||
data-theme="web"
|
||||
suppressHydrationWarning
|
||||
className={`${news.variable} ${plex.variable} ${plexMono.variable}`}
|
||||
className={`${news.variable} ${plex.variable} ${plexMono.variable} ${round.variable}`}
|
||||
>
|
||||
<body className="antialiased">
|
||||
<noscript>
|
||||
|
|
|
|||
1614
src/app/web.css
1614
src/app/web.css
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue