revamp projects/hardware as grouped index lists, drop emoji tiles, add Freezmo feature section

This commit is contained in:
Martin Dvořák 2026-10-11 23:22:30 +02:00
commit 95dd0128b8
10 changed files with 730 additions and 398 deletions

View file

@ -7,9 +7,9 @@ from the saved theme, all from the same content:
- **`web`** (default) — a light, frosted-glass **website**: a sticky full-height sidebar
(section nav, light/dark switch, desktop switch) beside a column of glass cards — hero with
a socials row, quick tiles, the Revival Inc. / RustyWolf feature panels (drawn as little
CSS windows, not screenshots), live services,
activity heatmap, projects and hardware (a masonry gallery), and friends. Dark by default
a socials row, quick tiles, the Freezmo feature panel (a real app screenshot) above the
Revival Inc. / RustyWolf CSS mock windows, live services,
activity heatmap, projects and hardware (grouped index lists), and friends. Dark by default
with a persisted light mode, and fully responsive, so phones and desktops get the same shell.
- **`skeuo`** — an alternate KDE Plasma / Windows-style **desktop simulation** (desktop
only): draggable snap-to-grid icons, resizable/floating windows, a Start menu, taskbar and
@ -36,11 +36,12 @@ more.
hand-built CSS mock windows instead of screenshots: a little necromancy game scene whose
blinking "PRESS START" launches the game, and a little browser window with tabs in the
title bar, a toolbar and a start page.
- **Asset-free cards & wallpaper** — every project/hardware card is a CSS gradient tile with
an emoji glyph (no screenshots), the desktop shell's app previews are the same CSS mock
- **Asset-free index & wallpaper** — Projects and Hardware are grouped repo-style index lists
(category headers, status/language chips, language-tinted dots on project rows, years — no emoji, no
screenshots), the desktop shell's app previews are the same CSS mock
windows (a git-forge page for GitDXD), and its wallpaper is a layered CSS gradient. The
only real images left are the logo, the social SVGs, the Joker easter egg and the Gentoo
mark.
only real images left are the logo, the social SVGs, the Freezmo screenshot, the Joker
easter egg and the Gentoo mark.
- **Draggable desktop icons** — free placement that snaps to a measured grid, one icon per
cell, with a follow-the-cursor drag ghost. Icons shrink on cramped viewports so they never
collide.

View file

@ -157,15 +157,25 @@ Themes: `web` = default frosted-glass website (sidebar + content, light/dark),
rectangle/square and never ends in an orphan "L" row (a 4th friend made the old
3-column grid do that). The same `friend.emoji` drives the desktop friend icon
(Josef is 🧌 goblin, the rest 🧙 — the old per-friend SVGs are gone).
Projects/hardware use a CSS multi-column
**masonry** gallery (`.project-grid`) so short cards don't leave half-empty
grid slots; on phones it drops to 2 columns with a shorter tile. Cards are
**asset-free**: each renders a `.project-thumb` — a `.thumb-N` gradient
(N = index % 6) with the item's `emoji` glyph centred — instead of a
screenshot. `codingProjects` / `techProjects` in `data.ts` carry `emoji`
(no `img`/`image`). The two feature panels (`.web-split`) stretch
Projects/hardware render as a grouped repo-listing-style
**index** (`ProjectIndex.tsx`, `.index-*` classes): group headers
(Games & game jams / Linux & desktop / Bots, servers & plugins / Apps &
experiments; Desktops & servers / Laptops / Phones / Consoles & handhelds),
then rows: coding rows lead with a language-coloured dot, title and a
status pill over the description, with the language/host tag right-aligned;
hardware rows are plain (no dot) with the year right-aligned. Coding rows are
external links with a hover highlight + arrow; hardware rows are static. The
rows are **asset-free** (no emoji, no screenshots). `codingProjects` in
`data.ts` carries `slug` / `group` / `status?` / `tag` / `accent`;
`techProjects` carries `slug` / `group` / `year?`
(no `img`/`image`/`emoji`/`accent`). Status pill colors come from the `--status-*`
tokens in `web.css`, overridden for the dark desktop windows in `skeuo.css`
along with the row hairlines (`--index-line`). Above the split, the full-width
**Freezmo** feature panel (`.web-feature` + `.web-feature-shot`) is the one
place a **real screenshot** is used (`public/freezmo-app.webp`, the live
study app). The two remaining feature panels (`.web-split`) stretch
to equal height and share a fixed `16 / 10` `.fw` window (the `FeatureWindow`
component) — a decorative, **asset-free CSS mock-up** rather than a screenshot:
component) — decorative, **asset-free CSS mock-ups** rather than screenshots:
Revival Inc. is a little necromancy game scene (night sky, moon, tombstones,
blinking "PRESS START") and RustyWolf is a little browser window (tabs in the
title bar, toolbar, address bar, start page). The browser window is `aria-hidden`
@ -178,11 +188,13 @@ Themes: `web` = default frosted-glass website (sidebar + content, light/dark),
Revival Inc. → game, RustyWolf → browser), so there are no app-preview
screenshots. The old stacked TUI panel layout and the `SlBackground` ASCII
train were removed earlier.
- **No raster UI assets.** Every card, feature panel, app preview and the desktop
wallpaper is drawn in CSS: project/hardware cards are `.project-thumb` gradient
tiles with an `emoji`, the feature/app previews are `FeatureWindow` mock windows,
- **No raster UI assets.** Every list, feature panel mock, app preview and the
desktop wallpaper is drawn in CSS: project/hardware entries are `.index-row`
list rows (accent dot, status pill, tag/year, hairline separators), the
feature/app previews are `FeatureWindow` mock windows,
and `.win-desktop` carries a layered gradient "wallpaper" (there is no
`wallpaper.jpg`). Only the social SVGs, the site logo (`src/app/icon.png`), the
`wallpaper.jpg`). Only the social SVGs, the site logo (`src/app/icon.png`),
the Freezmo screenshot (`public/freezmo-app.webp`), the
Joker easter egg (`joker.webp`) and the Gentoo mark (`gentoo.svg`) remain as
real image files.
- **Not a terminal.** The web theme deliberately has no TUI chrome: the hero is
@ -207,7 +219,8 @@ Themes: `web` = default frosted-glass website (sidebar + content, light/dark),
alphas makes dark look far more gradient-heavy. Tune the two schemes
separately, not by eye against each other.
- Interactive surfaces (`.web-ctl`, `.service-button`, `.social-button`,
`.project-card`, `.gym-item`, `.contribution-entry`, `.heatmap-container`) read
`.index-list`, `a.index-row:hover`, `.gym-item`, `.contribution-entry`,
`.heatmap-container`) read
the `--btn-bg` / `--btn-bg-hi` / `--btn-border` / `--btn-shadow` tokens with
the glass tokens as **fallbacks** (`var(--btn-bg, var(--glass-lo))`). The
tokens are only defined under `html[data-theme="web"]`, so the desktop theme

243
package-lock.json generated
View file

@ -1901,9 +1901,9 @@
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz",
"integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz",
"integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==",
"cpu": [
"arm64"
],
@ -1919,13 +1919,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.3.3"
"@img/sharp-libvips-darwin-arm64": "1.3.4"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz",
"integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz",
"integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==",
"cpu": [
"x64"
],
@ -1941,20 +1941,20 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.3.3"
"@img/sharp-libvips-darwin-x64": "1.3.4"
}
},
"node_modules/@img/sharp-freebsd-wasm32": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz",
"integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz",
"integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==",
"license": "Apache-2.0",
"optional": true,
"os": [
"freebsd"
],
"dependencies": {
"@img/sharp-wasm32": "0.35.4"
"@img/sharp-wasm32": "0.35.5"
},
"engines": {
"node": ">=20.9.0"
@ -1964,9 +1964,9 @@
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz",
"integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz",
"integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==",
"cpu": [
"arm64"
],
@ -1980,9 +1980,9 @@
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz",
"integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz",
"integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==",
"cpu": [
"x64"
],
@ -1996,9 +1996,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz",
"integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz",
"integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==",
"cpu": [
"arm"
],
@ -2015,9 +2015,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz",
"integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz",
"integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==",
"cpu": [
"arm64"
],
@ -2034,9 +2034,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz",
"integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz",
"integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==",
"cpu": [
"ppc64"
],
@ -2053,9 +2053,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-riscv64": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz",
"integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz",
"integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==",
"cpu": [
"riscv64"
],
@ -2072,9 +2072,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz",
"integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz",
"integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==",
"cpu": [
"s390x"
],
@ -2091,9 +2091,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz",
"integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz",
"integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==",
"cpu": [
"x64"
],
@ -2110,9 +2110,9 @@
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz",
"integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz",
"integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==",
"cpu": [
"arm64"
],
@ -2129,9 +2129,9 @@
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz",
"integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz",
"integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==",
"cpu": [
"x64"
],
@ -2148,9 +2148,9 @@
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz",
"integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz",
"integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==",
"cpu": [
"arm"
],
@ -2169,13 +2169,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.3.3"
"@img/sharp-libvips-linux-arm": "1.3.4"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz",
"integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz",
"integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==",
"cpu": [
"arm64"
],
@ -2194,13 +2194,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.3.3"
"@img/sharp-libvips-linux-arm64": "1.3.4"
}
},
"node_modules/@img/sharp-linux-ppc64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz",
"integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz",
"integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==",
"cpu": [
"ppc64"
],
@ -2219,13 +2219,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.3.3"
"@img/sharp-libvips-linux-ppc64": "1.3.4"
}
},
"node_modules/@img/sharp-linux-riscv64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz",
"integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz",
"integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==",
"cpu": [
"riscv64"
],
@ -2244,13 +2244,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-riscv64": "1.3.3"
"@img/sharp-libvips-linux-riscv64": "1.3.4"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz",
"integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz",
"integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==",
"cpu": [
"s390x"
],
@ -2269,13 +2269,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.3.3"
"@img/sharp-libvips-linux-s390x": "1.3.4"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz",
"integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz",
"integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==",
"cpu": [
"x64"
],
@ -2294,13 +2294,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.3.3"
"@img/sharp-libvips-linux-x64": "1.3.4"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz",
"integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz",
"integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==",
"cpu": [
"arm64"
],
@ -2319,13 +2319,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.3.3"
"@img/sharp-libvips-linuxmusl-arm64": "1.3.4"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz",
"integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz",
"integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==",
"cpu": [
"x64"
],
@ -2344,13 +2344,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.3.3"
"@img/sharp-libvips-linuxmusl-x64": "1.3.4"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz",
"integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz",
"integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==",
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
@ -2364,16 +2364,16 @@
}
},
"node_modules/@img/sharp-webcontainers-wasm32": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz",
"integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz",
"integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==",
"cpu": [
"wasm32"
],
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"@img/sharp-wasm32": "0.35.4"
"@img/sharp-wasm32": "0.35.5"
},
"engines": {
"node": ">=20.9.0"
@ -2383,9 +2383,9 @@
}
},
"node_modules/@img/sharp-win32-arm64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz",
"integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz",
"integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==",
"cpu": [
"arm64"
],
@ -2402,9 +2402,9 @@
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz",
"integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz",
"integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==",
"cpu": [
"ia32"
],
@ -2421,9 +2421,9 @@
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz",
"integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz",
"integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==",
"cpu": [
"x64"
],
@ -8052,9 +8052,9 @@
}
},
"node_modules/sharp": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz",
"integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz",
"integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
@ -8069,31 +8069,31 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.35.4",
"@img/sharp-darwin-x64": "0.35.4",
"@img/sharp-freebsd-wasm32": "0.35.4",
"@img/sharp-libvips-darwin-arm64": "1.3.3",
"@img/sharp-libvips-darwin-x64": "1.3.3",
"@img/sharp-libvips-linux-arm": "1.3.3",
"@img/sharp-libvips-linux-arm64": "1.3.3",
"@img/sharp-libvips-linux-ppc64": "1.3.3",
"@img/sharp-libvips-linux-riscv64": "1.3.3",
"@img/sharp-libvips-linux-s390x": "1.3.3",
"@img/sharp-libvips-linux-x64": "1.3.3",
"@img/sharp-libvips-linuxmusl-arm64": "1.3.3",
"@img/sharp-libvips-linuxmusl-x64": "1.3.3",
"@img/sharp-linux-arm": "0.35.4",
"@img/sharp-linux-arm64": "0.35.4",
"@img/sharp-linux-ppc64": "0.35.4",
"@img/sharp-linux-riscv64": "0.35.4",
"@img/sharp-linux-s390x": "0.35.4",
"@img/sharp-linux-x64": "0.35.4",
"@img/sharp-linuxmusl-arm64": "0.35.4",
"@img/sharp-linuxmusl-x64": "0.35.4",
"@img/sharp-webcontainers-wasm32": "0.35.4",
"@img/sharp-win32-arm64": "0.35.4",
"@img/sharp-win32-ia32": "0.35.4",
"@img/sharp-win32-x64": "0.35.4"
"@img/sharp-darwin-arm64": "0.35.5",
"@img/sharp-darwin-x64": "0.35.5",
"@img/sharp-freebsd-wasm32": "0.35.5",
"@img/sharp-libvips-darwin-arm64": "1.3.4",
"@img/sharp-libvips-darwin-x64": "1.3.4",
"@img/sharp-libvips-linux-arm": "1.3.4",
"@img/sharp-libvips-linux-arm64": "1.3.4",
"@img/sharp-libvips-linux-ppc64": "1.3.4",
"@img/sharp-libvips-linux-riscv64": "1.3.4",
"@img/sharp-libvips-linux-s390x": "1.3.4",
"@img/sharp-libvips-linux-x64": "1.3.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.3.4",
"@img/sharp-libvips-linuxmusl-x64": "1.3.4",
"@img/sharp-linux-arm": "0.35.5",
"@img/sharp-linux-arm64": "0.35.5",
"@img/sharp-linux-ppc64": "0.35.5",
"@img/sharp-linux-riscv64": "0.35.5",
"@img/sharp-linux-s390x": "0.35.5",
"@img/sharp-linux-x64": "0.35.5",
"@img/sharp-linuxmusl-arm64": "0.35.5",
"@img/sharp-linuxmusl-x64": "0.35.5",
"@img/sharp-webcontainers-wasm32": "0.35.5",
"@img/sharp-win32-arm64": "0.35.5",
"@img/sharp-win32-ia32": "0.35.5",
"@img/sharp-win32-x64": "0.35.5"
},
"peerDependenciesMeta": {
"@types/node": {
@ -8220,9 +8220,10 @@
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
"integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}

BIN
public/freezmo-app.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View file

@ -0,0 +1,140 @@
import type { CSSProperties, ReactNode } from "react";
import {
codingProjects,
techProjects,
type CodingProject,
type CodingProjectGroup,
type ProjectStatus,
type TechProject,
type TechProjectGroup,
} from "../data";
const STATUS_LABEL: Record<ProjectStatus, string> = {
finished: "Finished",
live: "Live",
wip: "WIP",
unfinished: "Unfinished",
discontinued: "Discontinued",
forgotten: "Forgotten",
};
const PROJECT_GROUPS: { id: CodingProjectGroup; title: string }[] = [
{ id: "games", title: "Games & game jams" },
{ id: "linux", title: "Linux & desktop" },
{ id: "bots", title: "Bots, servers & plugins" },
{ id: "apps", title: "Apps & experiments" },
];
const HARDWARE_GROUPS: { id: TechProjectGroup; title: string }[] = [
{ id: "desktops", title: "Desktops & servers" },
{ id: "laptops", title: "Laptops" },
{ id: "phones", title: "Phones" },
{ id: "consoles", title: "Consoles & handhelds" },
];
/**
* Grouped, repo-listing-style index that replaced the old card gallery.
* Coding rows: language-coloured dot — title (+ status pill) over the
* description — host/language tag. Hardware rows: plain rows with the year
* on the right. Coding rows are links; hardware rows are static.
*/
function Group({
title,
count,
children,
}: {
title: string;
count: number;
children: ReactNode;
}) {
return (
<section className="index-group">
<h3 className="index-group-head">
<span>{title}</span>
<span className="index-group-count">{count}</span>
</h3>
<div className="index-list">{children}</div>
</section>
);
}
function CodingRow({ project }: { project: CodingProject }) {
return (
<a
href={project.url}
target="_blank"
rel="noopener noreferrer"
className="index-row"
style={{ "--index-accent": project.accent } as CSSProperties}
>
<span className="index-dot" aria-hidden="true" />
<span className="index-body">
<span className="index-line1">
<span className="index-title">{project.title}</span>
{project.status && (
<span className={`index-status index-status-${project.status}`}>
{STATUS_LABEL[project.status]}
</span>
)}
</span>
<span className="index-desc">{project.desc}</span>
</span>
<span className="index-meta">
<span>{project.tag}</span>
<span className="index-arrow" aria-hidden="true">
↗
</span>
</span>
</a>
);
}
function HardwareRow({ project }: { project: TechProject }) {
return (
<div className="index-row index-row-plain">
<span className="index-body">
<span className="index-line1">
<span className="index-title">{project.title}</span>
</span>
<span className="index-desc">{project.desc}</span>
</span>
<span className="index-meta">{project.year ?? ""}</span>
</div>
);
}
export function CodingProjectIndex() {
return (
<div className="index-groups">
{PROJECT_GROUPS.map((group) => {
const items = codingProjects.filter((p) => p.group === group.id);
if (!items.length) return null;
return (
<Group key={group.id} title={group.title} count={items.length}>
{items.map((project) => (
<CodingRow key={project.slug} project={project} />
))}
</Group>
);
})}
</div>
);
}
export function HardwareProjectIndex() {
return (
<div className="index-groups">
{HARDWARE_GROUPS.map((group) => {
const items = techProjects.filter((p) => p.group === group.id);
if (!items.length) return null;
return (
<Group key={group.id} title={group.title} count={items.length}>
{items.map((project) => (
<HardwareRow key={project.slug} project={project} />
))}
</Group>
);
})}
</div>
);
}

View file

@ -6,10 +6,11 @@ import JokerLogo from "./JokerLogo";
import ServiceButton from "./ServiceButton";
import ContributionHeatmap from "./ContributionHeatmap";
import FeatureWindow from "./FeatureWindow";
import { CodingProjectIndex, HardwareProjectIndex } from "./ProjectIndex";
import Clock from "./Clock";
import { useTheme } from "./useTheme";
import { useColorScheme } from "./useColorScheme";
import { socials, codingProjects, techProjects, friends } from "../data";
import { socials, friends } from "../data";
const NAV = [
{ id: "overview", label: "Overview" },
@ -234,6 +235,23 @@ export default function WebLayout() {
</article>
</div>
<Section eyebrow="FREEZMO" title="Introducing Freezmo" className="web-feature">
<Image
src="/freezmo-app.webp"
alt="Freezmo quiz screen — a history question about the Schuman plan with AI grading feedback"
width={1941}
height={942}
sizes="(max-width: 900px) 100vw, 1100px"
className="web-feature-shot"
/>
<p className="web-feature-text">
Started working on a new project Freezmo — basically a free version of the
famous Gizmo app, but instead of paying for AI you use your ollama free cloud
API key. Notes in, flashcards and quizzes out. Try it at{" "}
<a href="https://freezmo.dvorakmortonxd.dev">freezmo.dvorakmortonxd.dev</a>.
</p>
</Section>
<div className="web-split">
<Section eyebrow="REVIVAL.INC" title="Revival Inc." className="web-feature">
<FeatureWindow variant="game" />
@ -246,7 +264,7 @@ export default function WebLayout() {
</p>
</Section>
<Section eyebrow="RustyWolf" title="Introducing RustyWolf" className="web-feature">
<Section eyebrow="BROWSER" title="RustyWolf" className="web-feature">
<FeatureWindow variant="browser" />
<p className="web-feature-text">
A lightweight browser made in Rust with WebKitGTK. It&#39;s the successor to my
@ -267,37 +285,11 @@ export default function WebLayout() {
</Section>
<Section id="projects" eyebrow="Portfolio" title="Things I've worked on">
<div className="project-grid">
{codingProjects.map((project, idx) => (
<a
key={idx}
href={project.url}
target="_blank"
rel="noopener noreferrer"
className="project-card"
>
<div className={`project-thumb thumb-${idx % 6}`} aria-hidden="true">
{project.emoji}
</div>
<h4 className="project-title">{project.title}</h4>
<p className="project-desc">{project.desc}</p>
</a>
))}
</div>
<CodingProjectIndex />
</Section>
<Section id="hardware" eyebrow="My gear" title="Hardware I own">
<div className="project-grid">
{techProjects.map((project, idx) => (
<div key={project.title} className="project-card">
<div className={`project-thumb thumb-${idx % 6}`} aria-hidden="true">
{project.emoji}
</div>
<h4 className="project-title">{project.title}</h4>
<p className="project-desc">{project.desc}</p>
</div>
))}
</div>
<HardwareProjectIndex />
</Section>
<Section id="friends" eyebrow="Links" title="Friend's websites and projects">

View file

@ -2,15 +2,11 @@
import Image from "next/image";
import { useEffect, useRef, useState, type KeyboardEvent as ReactKeyboardEvent, type ReactNode } from "react";
import {
socials,
codingProjects,
techProjects,
friends,
} from "../data";
import { socials, friends } from "../data";
import ServiceButton from "./ServiceButton";
import ContributionHeatmap from "./ContributionHeatmap";
import FeatureWindow from "./FeatureWindow";
import { CodingProjectIndex, HardwareProjectIndex } from "./ProjectIndex";
import JokerLogo from "./JokerLogo";
import { setTheme } from "./useTheme";
@ -107,10 +103,14 @@ function NowApp() {
<div className="app-text">
<p>
I&#39;m currently hosting an SMP (dm me on discord). Also I&#39;m pretty much
cooking daily, because im bulking through out the non-summer seasons. And me and
my pal <a href="https://skopec.dev">Pepa</a> are working on an education app so we
can study more efficiently, It&#39;s called{" "}
<a href="https://git.dvorakmortonxd.dev/edu/maturita.c">maturita.c</a>!
cooking daily, because im bulking through out the non-summer seasons. I started
working on a new project{" "}
<a href="https://freezmo.dvorakmortonxd.dev">Freezmo</a>, basically a free
version of the famous Gizmo app but instead of paying for AI you use your ollama
free cloud api key. The education app I started with my pal{" "}
<a href="https://skopec.dev">Pepa</a>, maturita.c, now lives on as{" "}
<a href="https://github.com/pepaskopec-blip/Graduately">Graduately</a> under his
wing.
</p>
</div>
</div>
@ -204,23 +204,7 @@ function ServicesApp() {
function ProjectsApp() {
return (
<div className="app-pad">
<div className="project-grid">
{codingProjects.map((project, idx) => (
<a
key={idx}
href={project.url}
target="_blank"
rel="noopener noreferrer"
className="project-card"
>
<div className={`project-thumb thumb-${idx % 6}`} aria-hidden="true">
{project.emoji}
</div>
<h4 className="project-title">{project.title}</h4>
<p className="project-desc">{project.desc}</p>
</a>
))}
</div>
<CodingProjectIndex />
</div>
);
}
@ -228,17 +212,7 @@ function ProjectsApp() {
function HardwareApp() {
return (
<div className="app-pad">
<div className="project-grid">
{techProjects.map((project, idx) => (
<div key={project.title} className="project-card">
<div className={`project-thumb thumb-${idx % 6}`} aria-hidden="true">
{project.emoji}
</div>
<h4 className="project-title">{project.title}</h4>
<p className="project-desc">{project.desc}</p>
</div>
))}
</div>
<HardwareProjectIndex />
</div>
);
}
@ -480,7 +454,7 @@ function RevivalApp() {
function RustyWolfApp() {
return (
<div className="app-pad app-center">
<h2 className="app-h1">Introducing RustyWolf</h2>
<h2 className="app-h1">RustyWolf</h2>
<FeatureWindow variant="browser" />
<p className="app-h2">What makes it so cool???</p>
<p className="app-text">
@ -513,7 +487,7 @@ const TERMINAL_FILES: Record<string, string> = {
"about_me.md":
"Hey, I'm Martin Dvorak, a 17 year old IT student from the Czech Republic.\nI self-host way too many things and tinker with GNU/Linux.",
"now.txt":
"Currently hosting an SMP and building an education app with Pepa.",
"Currently hosting an SMP. Started working on a new project Freezmo, basically a free version of the famous Gizmo app but instead of paying for AI you use your ollama free cloud api key.",
"resume.txt":
"sssvt.cz - network management & administration (2025-)\nGentoo, SwayFX, RustyWolf, dxdwm, Revival Inc.",
};

View file

@ -4,19 +4,46 @@ export interface Social {
url: string;
}
/** Lifecycle state of a coding project, shown as a chip on its index row. */
export type ProjectStatus =
| "finished"
| "live"
| "wip"
| "unfinished"
| "discontinued"
| "forgotten";
/** Section a coding project is filed under in the Projects index. */
export type CodingProjectGroup = "games" | "linux" | "bots" | "apps";
export interface CodingProject {
title: string;
desc: string;
/** Emoji glyph shown on the card's CSS gradient tile (no image assets). */
emoji: string;
/** Stable key for the index row (also the React key), e.g. `rustywolf`. */
slug: string;
/** Index group the project is listed under. */
group: CodingProjectGroup;
/** Status chip on the row; omitted for grab-bag entries. */
status?: ProjectStatus;
/** Language or host badge on the row. */
tag: string;
/** Hex accent for the row dot + hover tint. */
accent: string;
url: string;
}
/** Section a device is filed under in the Hardware index. */
export type TechProjectGroup = "desktops" | "laptops" | "phones" | "consoles";
export interface TechProject {
title: string;
desc: string;
/** Emoji glyph shown on the card's CSS gradient tile (no image assets). */
emoji: string;
/** Stable key for the index row (also the React key), e.g. `macbook-air-m4`. */
slug: string;
/** Index group the device is listed under. */
group: TechProjectGroup;
/** Model / build year, shown on the right when known. */
year?: string;
}
export interface Friend {
@ -54,190 +81,317 @@ export const socials: Social[] = [
export const codingProjects: CodingProject[] = [
{
title: "Hospital Execution (Unfinished)",
title: "Hospital Execution",
desc: "the first ever real game I made, in pygame. I've abandoned it.",
emoji: "🏥",
slug: "hospital-execution",
group: "games",
status: "unfinished",
tag: "itch.io",
accent: "#fa5c5c",
url: "https://martin-dv.itch.io/hospital-execution-demo",
},
{
title: "Underbaranoia (Finished)",
title: "Underbaranoia",
desc: "basically a undertale like infinite fight simulator that gets progressively harder, made in pygame.",
emoji: "⚔️",
slug: "underbaranoia",
group: "games",
status: "finished",
tag: "itch.io",
accent: "#fa5c5c",
url: "https://martin-dv.itch.io/underbaranoia",
},
{
title: "Offline memer (Finished)",
title: "Offline memer",
desc: "parody of the discord bot \"Dank Memer\" made in batch. (this was technically my first ever game I published).",
emoji: "🤖",
slug: "offline-memer",
group: "games",
status: "finished",
tag: "itch.io",
accent: "#fa5c5c",
url: "https://martin-dv.itch.io/offline-memer",
},
{
title: "4y (Forgotten)",
title: "The Larvae",
desc: "my entry for Mini Jam 212 (theme: Control), made in pygame. you spawn in a well developed city and parasite on people with your manipulative powers until you get so rich you destroy it.",
slug: "the-larvae",
group: "games",
status: "unfinished",
tag: "Python",
accent: "#cf6bd6",
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/the-larvae",
},
{
title: "4y",
desc: "just a renamed fork of the ly login manager (based login manager btw I love it), no extra features, just a rename, its actually outdated so dont use it.",
emoji: "🔐",
slug: "4y",
group: "linux",
status: "forgotten",
tag: "C",
accent: "#7d8590",
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/4y",
},
{
title: "4rch linux (Forgotten)",
title: "4rch linux",
desc: "same as above, basically just a renamed fork of arch linux with calamares installer, also outdated and not maintained at all so no use please.",
emoji: "🐧",
slug: "4rch-linux",
group: "linux",
status: "forgotten",
tag: "Shell",
accent: "#89e051",
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/4rch-linux",
},
{
title: "You_Are_An_Idiot_Virus.py (Finished)",
title: "You_Are_An_Idiot_Virus.py",
desc: "this is just a python script that just spams blinking \"you are an idiot\" windows, made it as a prank I think, not really sure rn.",
emoji: "🪟",
slug: "uranidiot",
group: "apps",
status: "finished",
tag: "Python",
accent: "#3572A5",
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/uranidiot",
},
{
title: "Venturesome (Discontinued)",
title: "Venturesome",
desc: "a browser I wrote in python using PyQtWebEngine with PyQt5, actually pretty cool and modular but I couldnt get past the stage of adding a tab system, basically surpassed by my other project: RustyWolf.",
emoji: "🌐",
slug: "venturesome",
group: "linux",
status: "discontinued",
tag: "Python",
accent: "#3572A5",
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/Venturesome",
},
{
title: "Mandelbrot set in Pygame (Finished)",
title: "Mandelbrot set in Pygame",
desc: "a python pygame script that renders the mandelbrot set, made it when I was fascinated by how it looks and ngl it still looks cool af.",
emoji: "🌀",
slug: "mandelbrot",
group: "apps",
status: "finished",
tag: "Python",
accent: "#8b5cf6",
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/mandelbrot",
},
{
title: "Underpygametale (Unfinished)",
title: "Underpygametale",
desc: "a remake of undertale written in pygame, got it to about the flowey part (at the beginning) and didnt touch it in a long time.",
emoji: "💀",
slug: "underpygametale",
group: "games",
status: "unfinished",
tag: "Python",
accent: "#ef476f",
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/underpygametale",
},
{
title: "Many discord bots...",
desc: "so many of them, once I (allegedly, pls discord dont ban me) even wrote a \"self-bot\" which auto-advertised a minecraft server on minecraft discord server in lfg-hosting every two hours, around 800 people joined in total.",
emoji: "💬",
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/PYGETEX",
desc: "so many of them, once I (allegedly, pls discord dont ban me) even wrote a \"self-bot\" (lfg-bot) which auto-advertised a minecraft server on minecraft discord server in lfg-hosting every two hours, around 800 people joined in total.",
slug: "lfg-bot",
group: "bots",
tag: "Python",
accent: "#5865F2",
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/lfg-bot",
},
{
title: "MMO minecraft plugin (Unfinished)",
title: "MMO minecraft plugin",
desc: "a minceraft plugin with custom bosses, items, and functions. pretty barebones and basic but I did it all manually before vibecoding was even a thing so what.",
emoji: "⛏️",
slug: "mmo-plugin",
group: "bots",
status: "unfinished",
tag: "YouTube",
accent: "#ff3d3d",
url: "https://www.youtube.com/watch?v=cSAX5Ds61ac",
},
{
title: "Many many failed minecraft servers..",
desc: "creative games, softcraft, frostville, eusmp, eurthsmp, softsmp and many other SMPs that died later on. I cant even count how many mc servers I had in total.",
emoji: "🧱",
slug: "smp-graveyard",
group: "bots",
tag: "Discord",
accent: "#62b47a",
url: "https://discord.gg/KtHRm9kRmy",
},
{
title: "Swords and Sandals 2 pygame remake (Unfinished)",
title: "Swords and Sandals 2 pygame remake",
desc: "an unfinished remake of swords and sandals 2 in pygame, I got bored of it and never finished it but ill finish it one day for sure lol.",
emoji: "🗡️",
slug: "sas2py",
group: "games",
status: "unfinished",
tag: "Python",
accent: "#d97706",
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/sas2py",
},
{
title: "Llama.cpp server",
desc: "my llama.cpp vm running on my proxmox with a gpu passthroughed RTX 3070, dm me if u wanna test it lol.",
emoji: "🦾",
slug: "llama-cpp",
group: "bots",
status: "live",
tag: "self-hosted",
accent: "#76b900",
url: "https://ai.dvorakmortonxd.dev/",
},
{
title: "Batch MSDOS Simulator (Finished)",
title: "Batch MSDOS Simulator",
desc: "a freshman school project on making games in batch, it was like an OS simulator with a web browser, games, photos, even a video suprise (youll have to try it to figure that one out lol).",
emoji: "💾",
slug: "dvorakmsdos",
group: "linux",
status: "finished",
tag: "Batchfile",
accent: "#b6d94c",
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/DvorakMSDOS",
},
{
title: "RustyWolf (WIP, Available now!)",
desc: "a browser I made in rust with webkitgtk, I made it to be lightweight and to be able to be ran on macbook unibody 2010 before I realised I got scammed.. It's the successor to my previous project Venturesome :)",
emoji: "🐺",
title: "RustyWolf",
desc: "my privacy focused browser made in rust with webkitgtk, made lightweight so it can run on a macbook unibody 2010 before I realised I got scammed.. It's the successor to my previous project Venturesome :)",
slug: "rustywolf",
group: "linux",
status: "wip",
tag: "Rust",
accent: "#dea584",
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/RustyWolf",
},
{
title: "dxdwm (Discontinued)",
desc: "a small x11 window manager written in rust.",
emoji: "🪟",
title: "dxdwm",
desc: "a small x11 window manager. originally written in rust (dxdwm-rs), now continued in C.",
slug: "dxdwm",
group: "linux",
status: "discontinued",
tag: "C",
accent: "#94a3b8",
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/dxdwm",
},
{
title: "Revival Inc. (Finished)",
title: "Revival Inc.",
desc: "My game written in pygame and packaged with pygbag made for the Mini Jame Gam #55! tho I only made it in 6 hours lmao",
emoji: "🧟",
slug: "revival-inc",
group: "games",
status: "finished",
tag: "Python",
accent: "#10b981",
url: "https://dvorakmortonxd.dev/revival-inc",
},
{
title: "freezmo",
desc: "a free version of the famous Gizmo app: notes in, flashcards and quizzes out — instead of paying for AI you use your own ollama free cloud api key.",
slug: "freezmo",
group: "apps",
status: "wip",
tag: "TypeScript",
accent: "#3178c6",
url: "https://freezmo.dvorakmortonxd.dev",
},
{
title: "qol-tweaks",
desc: "my quality of life plugin for the dvorakmortonxd SMP.",
slug: "qol-tweaks",
group: "bots",
status: "live",
tag: "Java",
accent: "#b07219",
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/qol-tweaks",
},
];
export const techProjects: TechProject[] = [
{
title: "Gentoo Setup",
desc: "my main computer with gentoo and SwayFX. specs: Ryzen 5 3600 (6c/12t), RX 580 8GB ASUS ROG, 16gb of ram and a 240gb sata ssd.",
emoji: "🐧",
slug: "gentoo-swayfx",
group: "desktops",
},
{
title: "Proxmox Server",
desc: "my personal proxmox server (dm me on ig if u need a VPS/hosting), specs: Xeon E5 2680 v4 (14c/28t), RTX 3070 (llama.cpp), 68gb ddr4 rdimm ram (2*16gb, 1*32gb, 1*4gb), 256gb of sata m2 space + 1tb hdd + 750gb hdd.",
emoji: "🖥️",
slug: "proxmox-server",
group: "desktops",
},
{
title: "Xbox 360 S (badupdate)",
desc: "xbox 360 slim (trinity) with badupdate exploit, allowing to boot custom software. (not my photo), the specs are XCGPU (3c/6t), 0.5GB of gddr3 ram and a 500gb hdd.",
emoji: "🎮",
slug: "xbox-360-s",
group: "consoles",
year: "2010",
},
{
title: "Lenovo Legion Go S",
desc: "my gaming handheld which I sometimes use as a computer, it has 32gb of unified lpddr5x memory and 1tb of nvme storage, I dualboot artix linux (openrc) with SwayFX and stock SteamOS on it.",
emoji: "🕹️",
slug: "legion-go-s",
group: "consoles",
year: "2025",
},
{
title: "Macbook Air M4 13\"",
desc: "my main device for basically everything on the go except gaming. specs are Apple M4 (cpu:10c,gpu:8c), 16gb of unified ram (lpddr5x), 256gb Storage.",
emoji: "💻",
slug: "macbook-air-m4",
group: "laptops",
year: "2025",
},
{
title: "Macbook Air 2013 11\"",
desc: "bought this for tinkering lol, specs are intel core i5 (2c/4t), 8gb of ddr3 ram and 250gb ssd, installed arch linux on it with i3wm.",
emoji: "💻",
slug: "macbook-air-2013",
group: "laptops",
year: "2013",
},
{
title: "iPhone 7 128gb",
desc: "somehow found a listing for 4 bucks (refurbed.cz) for it so I got it lol, it has a little dent I marked it on the photo (which isnt mine btw), except cosmetic damage its pretty much fully functional, specs are Apple A10 (4c/4t), 2gb of ram and 128gb of storage",
emoji: "📱",
slug: "iphone-7",
group: "phones",
year: "2016",
},
{
title: "Macbook Unibody 2010",
desc: "found random listing on bazos for 15 bucks but after I bought a power adapter it turned out to be non functioning so I got scammed :(",
emoji: "💻",
slug: "macbook-unibody-2010",
group: "laptops",
year: "2010",
},
{
title: "ThinkPad T400s 14\"",
desc: "got it from a random guy for 20 bucks, its an old laptop from 2009. I put void linux on it and i3wm on it, specs are intel core 2 duo (2c/2t), 8gb ddr3 ram and 800gb hdd.",
emoji: "💻",
slug: "thinkpad-t400s",
group: "laptops",
year: "2009",
},
{
title: "Asus N56JR 15.6\"",
desc: "one of my first laptops my dad got for me when I was like 12, still works but the battery is KO, specs are intel core i7 4700HQ (4c/8t), 8gb ddr3 ram and 240gb ssd, it currently runs ubuntu linux 24.04LTS and my mom uses it for movies and shows.",
emoji: "💻",
slug: "asus-n56jr",
group: "laptops",
year: "2013",
},
{
title: "Small Unknown XP Acer Laptop",
desc: "found that one in ewaste, I think it has like 2gb of ram and an intel atom, not sure what to do with it yet",
emoji: "📟",
slug: "unknown-acer-xp",
group: "laptops",
},
{
title: "Dell Latitude E6320",
desc: "also from ewaste, put mint on it but didnt do anything yet, lowk kinda capable for its time in 2011 so I might put it to use later but the battery is KO.",
emoji: "💻",
slug: "latitude-e6320",
group: "laptops",
year: "2011",
},
{
title: "iPhone 11 128gb",
desc: "my first iphone I got from my parents at 13, rn my dad has it because I switched over to the iphone 16 pro in 2025, specs are Apple A13 (6c/6t), 4gb of ram and 128gb of storage. (NOT MY IMAGE)",
emoji: "📱",
slug: "iphone-11",
group: "phones",
year: "2019",
},
{
title: "iPhone 16 Pro 128gb",
desc: "originally wanted iphone 13 mini (because of the size) but I somehow decided to get 16 pro, which is a bit smaller than my older iphone 11, specs are Apple A18 pro (6c/6t), (btw this chip is the same chip used in the macbook neo so thats cool) and 8gb of ram and 128gb of storage, my main phone.",
emoji: "📱",
slug: "iphone-16-pro",
group: "phones",
year: "2025",
},
{
title: "Xbox one S 2TB",
desc: "my dad bought it for me also in like 2018 or 2019 and I sometimes play the console edition of minecraft because I like to play the gliding minigame on it when someone comes over.",
emoji: "🎮",
slug: "xbox-one-s",
group: "consoles",
year: "2016",
},
];

View file

@ -852,41 +852,6 @@ a.win-icon {
color: #9a9a9a;
}
.app-surface .project-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 12px;
}
.app-surface .project-card {
padding: 10px;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 6px;
background: linear-gradient(to bottom, #ffffff, #f0f4fa);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.14);
}
.app-surface .project-card:hover {
border-color: #7db9e8;
background: #ffffff;
box-shadow: 0 0 0 1px #7db9e8, 0 0 14px rgba(110, 185, 255, 0.5),
0 8px 18px rgba(0, 25, 55, 0.2);
}
.app-surface .project-thumb {
height: 150px;
border-radius: 4px;
border: 1px solid rgba(0, 0, 0, 0.12);
}
.app-surface .project-title {
color: #0e3355;
}
.app-surface .project-card:hover .project-title {
color: #0d5aa7;
}
.app-surface .project-desc {
color: #43607c;
}
.app-surface .project-card:hover .project-desc {
color: #1d3e5c;
}
.app-surface .gym-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
@ -1151,29 +1116,29 @@ a.win-icon {
.win-body .service-button:hover .service-desc {
color: #d3dae3;
}
.win-body .project-card {
border-color: #1b1e20;
background: linear-gradient(to bottom, #31363b, #2a2e32);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
/* Project/Hardware index inside the dark KDE windows. */
.win-body {
--index-line: rgba(255, 255, 255, 0.07);
--status-finished: #4ade80;
--status-live: #60a5fa;
--status-wip: #fbbf24;
--status-unfinished: #fb923c;
--status-discontinued: #f87171;
--status-forgotten: #94a3b8;
}
.win-body .project-card:hover {
border-color: #3daee9;
background: #353b40;
box-shadow: 0 0 0 1px #3daee9, 0 0 14px rgba(61, 174, 233, 0.45);
.win-body .index-list {
background: rgba(255, 255, 255, 0.03);
}
.win-body .project-thumb {
border-color: #1b1e20;
}
.win-body .project-title {
.win-body .index-title {
color: #eff0f1;
}
.win-body .project-card:hover .project-title {
color: #3daee9;
}
.win-body .project-desc {
.win-body .index-desc {
color: #b0b6bc;
}
.win-body .project-card:hover .project-desc {
.win-body a.index-row:hover {
background: rgba(255, 255, 255, 0.06);
}
.win-body a.index-row:hover .index-desc {
color: #d3dae3;
}
@ -1600,9 +1565,6 @@ html[data-theme="skeuo"] .fw {
.app-pad {
padding: 14px;
}
.app-surface .project-grid {
grid-template-columns: 1fr;
}
.app-surface .social-buttons {
grid-template-columns: 1fr 1fr;
}
@ -1613,8 +1575,14 @@ html[data-theme="skeuo"] .fw {
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 6px;
}
.app-surface .project-thumb {
height: 140px;
.app-surface .index-group-head {
font-size: 0.6rem;
}
.app-surface .index-row {
padding: 10px 11px;
}
.app-surface .index-desc {
font-size: 0.75rem;
}
}

View file

@ -44,6 +44,15 @@
--red: #e11d48;
--darkpink: #db2777;
/* Project/Hardware index tokens */
--index-line: rgba(16, 24, 43, 0.1);
--status-finished: #15803d;
--status-live: #1d4ed8;
--status-wip: #b45309;
--status-unfinished: #c2410c;
--status-discontinued: #b91c1c;
--status-forgotten: #64748b;
--web-font: "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-custom-mono: ui-monospace, "JetBrains Mono", "SFMono-Regular", Menlo, Consolas,
@ -84,6 +93,15 @@ html[data-theme="web"][data-scheme="dark"] {
--purple: #c084fc;
--red: #fb7185;
--darkpink: #f472b6;
/* Project/Hardware index tokens */
--index-line: rgba(255, 255, 255, 0.09);
--status-finished: #4ade80;
--status-live: #60a5fa;
--status-wip: #fbbf24;
--status-unfinished: #fb923c;
--status-discontinued: #f87171;
--status-forgotten: #94a3b8;
}
html[data-theme="web"] {
@ -430,6 +448,17 @@ html[data-theme="web"] body {
display: flex;
flex-direction: column;
}
/* Real app screenshot inside a feature panel (Freezmo) — framed like the CSS
mock windows so it sits in the same visual language. */
.web-feature-shot {
display: block;
width: 100%;
height: auto;
margin-bottom: 14px;
border-radius: 16px;
border: 1px solid var(--glass-border);
box-shadow: 0 18px 36px -22px rgba(23, 32, 60, 0.5);
}
/* ============================================================
FEATURE WINDOWS
Asset-free decorative mock windows that replace the old
@ -1161,78 +1190,145 @@ html[data-theme="web"] body {
}
/* ============================================================
PROJECTS / HARDWARE
============================================================ */
.project-grid {
/* Masonry-ish gallery: multi-column packs the cards tightly, so a short card
never leaves a half-empty slot under a tall neighbour. */
columns: 3 250px;
column-gap: 14px;
PROJECTS / HARDWARE INDEX
------------------------------------------------------------
Grouped repo-listing layout: per-section group headers, then
rows of accent dot – title (+ status) over description – tag /
year. Replaced the old card gallery. */
.index-groups {
display: flex;
flex-direction: column;
gap: 22px;
}
.project-card {
display: block;
break-inside: avoid;
-webkit-column-break-inside: avoid;
margin-bottom: 14px;
padding: 12px;
border-radius: 16px;
border: 1px solid var(--btn-border, var(--glass-border));
.index-group-head {
display: flex;
align-items: center;
gap: 10px;
margin: 0 0 8px;
font-family: var(--font-custom-mono);
font-size: 0.66rem;
font-weight: 700;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--gray);
}
.index-group-count {
font-weight: 600;
color: color-mix(in srgb, var(--gray) 55%, transparent);
}
.index-group-head::after {
content: "";
flex: 1;
height: 1px;
background: var(--index-line);
}
.index-list {
border: 1px solid var(--index-line);
border-radius: 12px;
overflow: hidden;
background: var(--btn-bg, var(--glass-lo));
box-shadow: var(--btn-shadow, none);
}
.index-list > * + * {
border-top: 1px solid var(--index-line);
}
.index-row {
display: grid;
grid-template-columns: 10px minmax(0, 1fr) auto;
gap: 14px;
align-items: start;
padding: 11px 14px;
color: inherit;
text-decoration: none;
transition: transform 0.22s ease, border-color 0.22s ease, box-shadow 0.22s ease,
background-color 0.22s ease;
}
.project-card:hover {
transform: translateY(-4px);
border-color: var(--accent-line);
/* Hardware rows: no accent dot (it carried no meaning there), so the body
takes the full first track. */
.index-row-plain {
grid-template-columns: minmax(0, 1fr) auto;
}
a.index-row {
transition: background-color 0.16s ease;
}
a.index-row:hover {
background: var(--btn-bg-hi, var(--glass-hi));
box-shadow: 0 20px 38px -20px rgba(23, 32, 60, 0.55);
}
.project-thumb {
display: grid;
place-items: center;
height: 150px;
width: 100%;
margin-bottom: 12px;
border-radius: 12px;
border: 1px solid var(--glass-border);
font-size: 2.6rem;
line-height: 1;
user-select: none;
text-shadow: 0 3px 10px rgba(0, 0, 0, 0.35);
.index-dot {
width: 9px;
height: 9px;
margin-top: 6px;
border-radius: 50%;
background: var(--index-accent, var(--accent));
box-shadow: 0 0 9px color-mix(in srgb, var(--index-accent, var(--accent)) 55%, transparent);
}
/* Shared gradient palette for the card tiles (no image assets). */
.thumb-0 {
background: linear-gradient(150deg, #6d5efc, #2f8bff);
.index-line1 {
display: flex;
align-items: baseline;
flex-wrap: wrap;
gap: 8px;
}
.thumb-1 {
background: linear-gradient(150deg, #ec4899, #db2777);
}
.thumb-2 {
background: linear-gradient(150deg, #f59e0b, #d97757);
}
.thumb-3 {
background: linear-gradient(150deg, #10b981, #0d9488);
}
.thumb-4 {
background: linear-gradient(150deg, #8b5cf6, #6366f1);
}
.thumb-5 {
background: linear-gradient(150deg, #06b6d4, #3b82f6);
}
.project-title {
font-size: 0.95rem;
.index-title {
font-size: 0.92rem;
font-weight: 700;
color: var(--fg);
}
.project-card:hover .project-title {
color: var(--accent);
a.index-row:hover .index-title {
color: color-mix(in srgb, var(--index-accent, var(--accent)) 72%, var(--fg));
}
.project-desc {
margin-top: 4px;
font-size: 0.8rem;
color: var(--web-muted);
.index-status {
padding: 1px 7px 2px;
border-radius: 999px;
border: 1px solid color-mix(in srgb, currentColor 32%, transparent);
background: color-mix(in srgb, currentColor 10%, transparent);
font-family: var(--font-custom-mono);
font-size: 0.56rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--status-forgotten);
}
.index-status-finished {
color: var(--status-finished);
}
.index-status-live {
color: var(--status-live);
}
.index-status-wip {
color: var(--status-wip);
}
.index-status-unfinished {
color: var(--status-unfinished);
}
.index-status-discontinued {
color: var(--status-discontinued);
}
.index-status-forgotten {
color: var(--status-forgotten);
}
.index-desc {
display: block;
margin-top: 3px;
font-size: 0.78rem;
line-height: 1.5;
color: var(--gray);
}
.index-meta {
display: inline-flex;
align-items: center;
gap: 10px;
padding-top: 3px;
font-family: var(--font-custom-mono);
font-size: 0.62rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--gray);
white-space: nowrap;
}
.index-arrow {
transition: transform 0.16s ease, color 0.16s ease;
}
a.index-row:hover .index-arrow {
transform: translate(2px, -2px);
color: color-mix(in srgb, var(--index-accent, var(--accent)) 72%, var(--fg));
}
/* ============================================================
@ -1750,27 +1846,20 @@ html[data-theme="web"][data-scheme="dark"] .heatmap-legend-cell[data-level="4"]
font-size: 0.82rem;
font-weight: 600;
}
/* Projects/hardware gallery: 2-up on phones instead of one full-width card
per row, with a shorter image + tighter card so a row is compact. */
.project-grid {
columns: 2;
column-gap: 10px;
/* Projects/hardware index: tighter rows on phones. */
.index-groups {
gap: 18px;
}
.project-card {
margin-bottom: 10px;
padding: 10px;
.index-row {
gap: 10px;
padding: 10px 11px;
}
.project-thumb {
height: 90px;
margin-bottom: 8px;
font-size: 1.8rem;
}
.project-title {
font-size: 0.88rem;
}
.project-desc {
.index-desc {
font-size: 0.75rem;
}
.index-meta {
font-size: 0.58rem;
}
.services-buttons,
.services-buttons.services-main {
grid-template-columns: 1fr;