web: replace Revival Inc./RustyWolf feature screenshots with asset-free CSS windows
This commit is contained in:
parent
ef9d44f7d3
commit
7f6ac177b4
7 changed files with 520 additions and 52 deletions
103
src/app/components/FeatureWindow.tsx
Normal file
103
src/app/components/FeatureWindow.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 "Mini Jame Gam #55", 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'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>
|
||||
|
|
|
|||
400
src/app/web.css
400
src/app/web.css
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue