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

@ -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;