web: replace Revival Inc./RustyWolf feature screenshots with asset-free CSS windows

This commit is contained in:
dvorakmortonxd 2026-10-05 12:28:18 +02:00
commit 7f6ac177b4
7 changed files with 520 additions and 52 deletions

View file

@ -7,7 +7,8 @@ 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, live services,
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
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
@ -31,6 +32,9 @@ more.
- **Glassmorphic web layout** — a sticky sidebar with section navigation beside a column of
frosted-glass panels (subtle gradients + `backdrop-filter` blur), reorganised into
hero / quick-tile / section cards.
- **Asset-free feature windows** — the two feature panels (Revival Inc. / RustyWolf) are
hand-built CSS mock windows instead of screenshots: a little necromancy game scene with a
blinking "PRESS START", and a little browser window with a tab strip, toolbar and start page.
- **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.
@ -72,7 +76,7 @@ more.
- **Fonts:** the web theme uses the system sans stack; Monocraft (self-hosted in `public/`) is
used by the desktop theme's SMP menu and the in-desktop terminal.
- **Images:** `next/image`
- **Version:** `2.2.8` (see `package.json`)
- **Version:** `2.2.9` (see `package.json`)
- **Dependencies:** `npm audit` reports **0 vulnerabilities**. `next` is kept at a patched
release (`>=16.3.6`) for the `next/og` RCE, and `brace-expansion` is force-upgraded via
`overrides`. `braces` (a transitive ESLint/`fast-glob` dependency) has **no upstream fix**,
@ -194,6 +198,7 @@ src/app/
useTheme.ts → theme state via useSyncExternalStore
useColorScheme.ts → light/dark state via useSyncExternalStore
WebLayout.tsx → default web shell: sidebar + glass content
FeatureWindow.tsx → decorative CSS windows for the two feature panels
WindowsDesktop.tsx → desktop shell: icons, windows, taskbar, Start menu
WinWindow.tsx → one draggable/resizable window
apps.tsx → APPS registry, window contents, in-app terminal

View file

@ -34,7 +34,7 @@ When in doubt, prefer updating. A stale README **or** agents.md is treated as a
## Bump the version on EVERY prompt — also do this every time
Another standing requirement: increment the **patch** version (`x.x.+1`, so
`2.2.7` → `2.2.8` → …) as part of every task, even the small ones. Keep all of
`2.2.8` → `2.2.9` → …) as part of every task, even the small ones. Keep all of
these in sync:
- `package.json` → `"version"`
@ -56,7 +56,7 @@ frosted-glass **web** site, and an alternate KDE/Plasma **desktop simulation**
utilities in the components (Tailwind is present via PostCSS but the look is
hand-written CSS).
- Path alias: `@/*` → `src/*`.
- Version: `2.2.8` (see `package.json`).
- Version: `2.2.9` (see `package.json`).
### Scripts
```bash
@ -102,6 +102,7 @@ src/app/
components/
ThemeRoot.tsx → picks which shell to mount (the router of the UI)
WebLayout.tsx → "web" theme (default): sticky sidebar + glass content
FeatureWindow.tsx → decorative CSS "windows" for the two feature panels (game / browser)
WindowsDesktop.tsx → "skeuo" theme, desktop: icons, windows, taskbar
WinWindow.tsx → a single draggable/resizable window
apps.tsx → APPS registry + the content of each app + in-app terminal
@ -157,10 +158,16 @@ Themes: `web` = default frosted-glass website (sidebar + content, light/dark),
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 card image. The two feature panels (`.web-split`) stretch
to equal height and share a fixed `16 / 10` `.web-shot` frame with
`object-fit: cover` (+ `object-position: center top`), so source images with
different aspect ratios still line up. The old stacked TUI panel layout and the
`SlBackground` ASCII train were removed with it.
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:
Revival Inc. is a little necromancy game scene (night sky, moon, tombstones,
blinking "PRESS START") and RustyWolf is a little browser window (tab strip,
toolbar, address bar, start page). Both are `aria-hidden` and purely
presentational; the real copy stays in the `.web-feature-text` paragraph below.
The old raster feature shots (`.web-shot` + `next/image`) were removed from the
web theme only — `revivalinc.webp` / `RustyWolfPreview.webp` are still used by
the desktop theme and the projects gallery, so the files stay. The old stacked
TUI panel layout and the `SlBackground` ASCII train were removed earlier.
- **Not a terminal.** The web theme deliberately has no TUI chrome: the hero is
a name + blurb (no `$ whoami` prompt, no blinking cursor), section eyebrows are
plain words (not `README.MD` / `TECH_LIST.JSON` filenames), and the activity

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "dvorakmortonxd",
"version": "2.2.8",
"version": "2.2.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "dvorakmortonxd",
"version": "2.2.8",
"version": "2.2.9",
"dependencies": {
"next": "^16.3.8",
"react": "^19.3.0",

View file

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

View file

@ -0,0 +1,103 @@
"use client";
import type { ReactNode } from "react";
/**
* Decorative, asset-free mock windows for the two feature panels (Revival Inc.
* and RustyWolf). They replace the old raster screenshots so the panels read as
* a little OS window instead of a flat image. Purely presentational — the real
* copy lives in the `.web-feature-text` paragraph below each window.
*/
function Chrome({ title, children }: { title: string; children: ReactNode }) {
return (
<div className="fw" aria-hidden="true">
<div className="fw-bar">
<span className="fw-dots">
<span className="fw-dot" />
<span className="fw-dot" />
<span className="fw-dot" />
</span>
<span className="fw-title">{title}</span>
</div>
<div className="fw-body">{children}</div>
</div>
);
}
function GameBody() {
return (
<div className="fw-scene">
<span className="fw-stars" />
<span className="fw-moon" />
<span className="fw-hills" />
<div className="fw-graves">
<span className="fw-grave" />
<span className="fw-grave" />
<span className="fw-grave" />
<span className="fw-grave" />
</div>
<span className="fw-scan" />
<div className="fw-hud">
<span className="fw-hearts">♥ ♥ ♥</span>
<span className="fw-score">SCORE 0000</span>
</div>
<span className="fw-start">PRESS START</span>
</div>
);
}
function BrowserBody() {
const tiles = ["G", "Y", "R", "D", "X"];
return (
<div className="fw-browser">
<div className="fw-tabs">
<span className="fw-tab">
<span className="fw-tab-dot" />
New Tab
</span>
<span className="fw-tab-plus">+</span>
</div>
<div className="fw-toolbar">
<span className="fw-nav">‹</span>
<span className="fw-nav">›</span>
<span className="fw-nav fw-nav-reload">⟳</span>
<span className="fw-url">
<span className="fw-lock" />
rustywolf://start
</span>
</div>
<div className="fw-page">
<div className="fw-wordmark">
Rusty<span>Wolf</span>
</div>
<div className="fw-search">
<span className="fw-search-dot" />
<span className="fw-search-text">Search or enter address</span>
</div>
<div className="fw-tiles">
{tiles.map((t) => (
<span key={t} className="fw-tile">
{t}
</span>
))}
</div>
</div>
</div>
);
}
export default function FeatureWindow({ variant }: { variant: "game" | "browser" }) {
if (variant === "game") {
return (
<Chrome title="revival-inc — mini jame gam #55">
<GameBody />
</Chrome>
);
}
return (
<Chrome title="RustyWolf — new tab">
<BrowserBody />
</Chrome>
);
}

View file

@ -5,6 +5,7 @@ import type { ReactNode } from "react";
import JokerLogo from "./JokerLogo";
import ServiceButton from "./ServiceButton";
import ContributionHeatmap from "./ContributionHeatmap";
import FeatureWindow from "./FeatureWindow";
import Clock from "./Clock";
import { useTheme } from "./useTheme";
import { useColorScheme } from "./useColorScheme";
@ -139,24 +140,6 @@ function Gym() {
);
}
function FeatureShot({
src,
alt,
width,
height,
}: {
src: string;
alt: string;
width: number;
height: number;
}) {
return (
<div className="web-shot">
<Image src={src} alt={alt} width={width} height={height} />
</div>
);
}
export default function WebLayout() {
const { toggle } = useTheme();
const { scheme, toggle: toggleScheme } = useColorScheme();
@ -253,12 +236,7 @@ export default function WebLayout() {
<div className="web-split">
<Section eyebrow="REVIVAL.INC" title="Revival Inc." className="web-feature">
<FeatureShot
src="/revivalinc.webp"
alt="Revival Inc. thumbnail"
width={768}
height={576}
/>
<FeatureWindow variant="game" />
<p className="web-feature-text">
A game I made for the game jam &#34;Mini Jame Gam #55&#34;, the theme was
Necromancy and the special item was Milk, the game also includes a tutorial so
@ -269,12 +247,7 @@ export default function WebLayout() {
</Section>
<Section eyebrow="RustyWolf" title="Introducing RustyWolf" className="web-feature">
<FeatureShot
src="/RustyWolfPreview.webp"
alt="RustyWolf Browser Preview"
width={1280}
height={720}
/>
<FeatureWindow variant="browser" />
<p className="web-feature-text">
A lightweight browser made in Rust with WebKitGTK. It&#39;s the successor to my
previous project Venturesome, aiming to provide a fast and minimalist browsing
@ -345,7 +318,7 @@ export default function WebLayout() {
</Section>
<footer className="web-footer glass">
<span className="web-footer-label">v2.2.8</span>
<span className="web-footer-label">v2.2.9</span>
© 2025-2026 Martin Dvořák. All rights reserved (not really I dont care, just
follow the GPL3.0 License)
</footer>

View file

@ -422,7 +422,7 @@ html[data-theme="web"] body {
}
.web-split {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
gap: 26px;
align-items: stretch;
}
@ -430,22 +430,402 @@ html[data-theme="web"] body {
display: flex;
flex-direction: column;
}
.web-shot {
/* One fixed frame for every feature shot, regardless of the source image's
aspect ratio, so the two feature panels line up. */
/* ============================================================
FEATURE WINDOWS
Asset-free decorative mock windows that replace the old
feature screenshots. Same fixed frame as before so the two
feature panels still line up; the "screen" is pure CSS.
============================================================ */
.fw {
--fw-chrome: var(--bg-hard);
display: flex;
flex-direction: column;
aspect-ratio: 16 / 10;
border-radius: 16px;
overflow: hidden;
border: 1px solid var(--glass-border);
margin-bottom: 14px;
background: var(--glass-lo);
box-shadow: 0 18px 36px -22px rgba(23, 32, 60, 0.5);
}
.web-shot img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center top;
.fw-bar {
display: flex;
align-items: center;
gap: 10px;
height: 32px;
padding: 0 12px;
background: var(--fw-chrome);
border-bottom: 1px solid var(--glass-border);
}
.fw-dots {
display: flex;
gap: 6px;
flex: none;
}
.fw-dot {
width: 9px;
height: 9px;
border-radius: 50%;
}
.fw-dot:nth-child(1) {
background: #ff5f57;
}
.fw-dot:nth-child(2) {
background: #febc2e;
}
.fw-dot:nth-child(3) {
background: #28c840;
}
.fw-title {
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 0.68rem;
font-weight: 600;
color: var(--web-muted);
}
.fw-body {
position: relative;
flex: 1;
min-height: 0;
overflow: hidden;
}
/* ---------- Revival Inc.: a little necromancy game scene ---------- */
.fw-scene {
position: absolute;
inset: 0;
overflow: hidden;
background:
radial-gradient(120% 90% at 50% 125%, rgba(124, 58, 237, 0.55), transparent 70%),
linear-gradient(180deg, #090e22 0%, #171a3c 46%, #2b1c48 100%);
}
.fw-stars {
position: absolute;
inset: 0;
background-image:
radial-gradient(1.6px 1.6px at 12% 22%, rgba(255, 255, 255, 0.9), transparent),
radial-gradient(1.4px 1.4px at 28% 12%, rgba(255, 255, 255, 0.7), transparent),
radial-gradient(1.8px 1.8px at 44% 28%, rgba(255, 255, 255, 0.85), transparent),
radial-gradient(1.3px 1.3px at 62% 14%, rgba(255, 255, 255, 0.6), transparent),
radial-gradient(1.6px 1.6px at 78% 30%, rgba(255, 255, 255, 0.8), transparent),
radial-gradient(1.4px 1.4px at 88% 10%, rgba(255, 255, 255, 0.7), transparent),
radial-gradient(1.2px 1.2px at 8% 44%, rgba(255, 255, 255, 0.5), transparent),
radial-gradient(1.5px 1.5px at 36% 40%, rgba(255, 255, 255, 0.55), transparent),
radial-gradient(1.3px 1.3px at 70% 44%, rgba(255, 255, 255, 0.5), transparent);
animation: fw-twinkle 4s ease-in-out infinite alternate;
}
@keyframes fw-twinkle {
from {
opacity: 0.55;
}
to {
opacity: 1;
}
}
.fw-moon {
position: absolute;
top: 13%;
right: 12%;
width: 15%;
aspect-ratio: 1;
border-radius: 50%;
background: radial-gradient(circle at 35% 30%, #fff8dc, #f2e6b0 60%, #d8c778);
box-shadow: 0 0 26px 7px rgba(255, 246, 200, 0.35);
}
.fw-hills {
position: absolute;
left: -12%;
right: -12%;
bottom: 0;
height: 44%;
background: linear-gradient(180deg, #2c2350, #120e26);
border-radius: 50% 50% 0 0 / 42% 42% 0 0;
}
.fw-graves {
position: absolute;
left: 0;
right: 0;
bottom: 7%;
display: flex;
align-items: flex-end;
justify-content: center;
gap: 7%;
}
.fw-grave {
position: relative;
width: 12%;
max-width: 50px;
aspect-ratio: 5 / 7;
background: linear-gradient(160deg, #8b93b0, #575d76);
border-radius: 50% 50% 4px 4px / 32% 32% 4px 4px;
box-shadow:
inset -3px -4px 8px rgba(0, 0, 0, 0.35),
0 6px 12px -6px rgba(0, 0, 0, 0.6);
}
.fw-grave::before,
.fw-grave::after {
content: "";
position: absolute;
left: 50%;
transform: translateX(-50%);
background: rgba(18, 20, 38, 0.62);
border-radius: 2px;
}
.fw-grave::before {
top: 20%;
width: 12%;
height: 42%;
}
.fw-grave::after {
top: 33%;
width: 44%;
height: 11%;
}
.fw-grave:nth-child(2) {
transform: translateY(-7%) scale(1.06);
}
.fw-grave:nth-child(4) {
transform: scale(0.88);
}
.fw-scan {
position: absolute;
inset: 0;
pointer-events: none;
background: repeating-linear-gradient(
0deg,
rgba(0, 0, 0, 0.16) 0 1px,
transparent 1px 3px
);
opacity: 0.45;
}
.fw-hud {
position: absolute;
top: 8%;
left: 5%;
right: 5%;
display: flex;
justify-content: space-between;
font-family: var(--font-custom-mono);
font-size: 0.58rem;
letter-spacing: 0.08em;
color: #ffd9e0;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
}
.fw-hearts {
color: #ff5d7a;
}
.fw-start {
position: absolute;
left: 50%;
top: 45%;
transform: translate(-50%, -50%);
padding: 6px 15px;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.55);
background: rgba(124, 58, 237, 0.4);
color: #fff;
font-family: var(--font-custom-mono);
font-size: 0.6rem;
letter-spacing: 0.16em;
white-space: nowrap;
animation: fw-blink 1.1s steps(2, start) infinite;
}
@keyframes fw-blink {
50% {
opacity: 0.35;
}
}
/* ---------- RustyWolf: a little browser window ---------- */
.fw-browser {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
background: linear-gradient(180deg, var(--glass-hi), var(--glass-lo));
}
.fw-tabs {
display: flex;
align-items: flex-end;
gap: 6px;
height: 32px;
padding: 7px 10px 0;
}
.fw-tab {
display: inline-flex;
align-items: center;
gap: 7px;
max-width: 62%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding: 6px 13px;
border-radius: 9px 9px 0 0;
border: 1px solid var(--btn-border, var(--glass-border));
border-bottom: none;
background: var(--btn-bg, var(--glass-lo));
font-size: 0.66rem;
color: var(--fg);
}
.fw-tab-dot {
flex: none;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--accent);
}
.fw-tab-plus {
padding: 0 6px;
color: var(--web-muted);
font-size: 0.9rem;
line-height: 1;
}
.fw-toolbar {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-top: 1px solid var(--glass-border);
border-bottom: 1px solid var(--glass-border);
background: var(--fw-chrome);
}
.fw-nav {
flex: none;
display: grid;
place-items: center;
width: 22px;
height: 22px;
border-radius: 50%;
background: var(--btn-bg, var(--glass-lo));
color: var(--web-muted);
font-size: 0.85rem;
line-height: 1;
}
.fw-url {
flex: 1;
display: flex;
align-items: center;
gap: 8px;
height: 26px;
padding: 0 12px;
border-radius: 999px;
border: 1px solid var(--glass-border);
background: var(--glass-lo);
font-family: var(--font-custom-mono);
font-size: 0.66rem;
color: var(--web-muted);
}
.fw-lock {
position: relative;
flex: none;
width: 8px;
height: 8px;
border: 1.5px solid currentColor;
border-radius: 2px;
}
.fw-lock::before {
content: "";
position: absolute;
left: 50%;
top: -5px;
width: 6px;
height: 5px;
border: 1.5px solid currentColor;
border-bottom: none;
border-radius: 4px 4px 0 0;
transform: translateX(-50%);
}
.fw-page {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
padding: 12px;
}
.fw-wordmark {
font-size: clamp(1rem, 2.4vw, 1.35rem);
font-weight: 800;
letter-spacing: -0.03em;
color: var(--fg);
}
.fw-wordmark span {
color: #d97757;
}
.fw-search {
display: flex;
align-items: center;
gap: 9px;
width: min(82%, 320px);
padding: 8px 15px;
border-radius: 999px;
border: 1px solid var(--btn-border, var(--glass-border));
background: var(--btn-bg, var(--glass-lo));
box-shadow: var(--btn-shadow, none);
font-size: 0.72rem;
color: var(--web-muted);
}
.fw-search-dot {
position: relative;
flex: none;
width: 11px;
height: 11px;
border: 1.6px solid var(--accent);
border-radius: 50%;
}
.fw-search-dot::after {
content: "";
position: absolute;
right: -4px;
bottom: -3px;
width: 6px;
height: 1.6px;
border-radius: 2px;
background: var(--accent);
transform: rotate(45deg);
}
.fw-tiles {
display: flex;
gap: 11px;
}
.fw-tile {
display: grid;
place-items: center;
width: 38px;
height: 38px;
border-radius: 12px;
color: #fff;
font-size: 0.9rem;
font-weight: 800;
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.3),
0 6px 12px -8px rgba(0, 0, 0, 0.6);
}
.fw-tile:nth-child(1) {
background: linear-gradient(150deg, #6d5efc, #2f8bff);
}
.fw-tile:nth-child(2) {
background: linear-gradient(150deg, #ec4899, #db2777);
}
.fw-tile:nth-child(3) {
background: linear-gradient(150deg, #f59e0b, #d97757);
}
.fw-tile:nth-child(4) {
background: linear-gradient(150deg, #10b981, #0d9488);
}
.fw-tile:nth-child(5) {
background: linear-gradient(150deg, #8b5cf6, #6366f1);
}
@media (prefers-reduced-motion: reduce) {
.fw-stars,
.fw-start {
animation: none;
}
}
.web-feature-text {
flex: 1;