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 - **`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 (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 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. 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 - **`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 - **Glassmorphic web layout** — a sticky sidebar with section navigation beside a column of
frosted-glass panels (subtle gradients + `backdrop-filter` blur), reorganised into frosted-glass panels (subtle gradients + `backdrop-filter` blur), reorganised into
hero / quick-tile / section cards. 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 - **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 cell, with a follow-the-cursor drag ghost. Icons shrink on cramped viewports so they never
collide. collide.
@ -72,7 +76,7 @@ more.
- **Fonts:** the web theme uses the system sans stack; Monocraft (self-hosted in `public/`) is - **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. used by the desktop theme's SMP menu and the in-desktop terminal.
- **Images:** `next/image` - **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 - **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 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**, `overrides`. `braces` (a transitive ESLint/`fast-glob` dependency) has **no upstream fix**,
@ -194,6 +198,7 @@ src/app/
useTheme.ts → theme state via useSyncExternalStore useTheme.ts → theme state via useSyncExternalStore
useColorScheme.ts → light/dark state via useSyncExternalStore useColorScheme.ts → light/dark state via useSyncExternalStore
WebLayout.tsx → default web shell: sidebar + glass content 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 WindowsDesktop.tsx → desktop shell: icons, windows, taskbar, Start menu
WinWindow.tsx → one draggable/resizable window WinWindow.tsx → one draggable/resizable window
apps.tsx → APPS registry, window contents, in-app terminal 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 ## Bump the version on EVERY prompt — also do this every time
Another standing requirement: increment the **patch** version (`x.x.+1`, so 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: these in sync:
- `package.json` → `"version"` - `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 utilities in the components (Tailwind is present via PostCSS but the look is
hand-written CSS). hand-written CSS).
- Path alias: `@/*` → `src/*`. - Path alias: `@/*` → `src/*`.
- Version: `2.2.8` (see `package.json`). - Version: `2.2.9` (see `package.json`).
### Scripts ### Scripts
```bash ```bash
@ -102,6 +102,7 @@ src/app/
components/ components/
ThemeRoot.tsx → picks which shell to mount (the router of the UI) ThemeRoot.tsx → picks which shell to mount (the router of the UI)
WebLayout.tsx → "web" theme (default): sticky sidebar + glass content 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 WindowsDesktop.tsx → "skeuo" theme, desktop: icons, windows, taskbar
WinWindow.tsx → a single draggable/resizable window WinWindow.tsx → a single draggable/resizable window
apps.tsx → APPS registry + the content of each app + in-app terminal 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 Projects/hardware use a CSS multi-column
**masonry** gallery (`.project-grid`) so short cards don't leave half-empty **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 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 to equal height and share a fixed `16 / 10` `.fw` window (the `FeatureWindow`
`object-fit: cover` (+ `object-position: center top`), so source images with component) — a decorative, **asset-free CSS mock-up** rather than a screenshot:
different aspect ratios still line up. The old stacked TUI panel layout and the Revival Inc. is a little necromancy game scene (night sky, moon, tombstones,
`SlBackground` ASCII train were removed with it. 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 - **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 a name + blurb (no `$ whoami` prompt, no blinking cursor), section eyebrows are
plain words (not `README.MD` / `TECH_LIST.JSON` filenames), and the activity 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", "name": "dvorakmortonxd",
"version": "2.2.8", "version": "2.2.9",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "dvorakmortonxd", "name": "dvorakmortonxd",
"version": "2.2.8", "version": "2.2.9",
"dependencies": { "dependencies": {
"next": "^16.3.8", "next": "^16.3.8",
"react": "^19.3.0", "react": "^19.3.0",

View file

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

View file

@ -422,7 +422,7 @@ html[data-theme="web"] body {
} }
.web-split { .web-split {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
gap: 26px; gap: 26px;
align-items: stretch; align-items: stretch;
} }
@ -430,22 +430,402 @@ html[data-theme="web"] body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.web-shot { /* ============================================================
/* One fixed frame for every feature shot, regardless of the source image's FEATURE WINDOWS
aspect ratio, so the two feature panels line up. */ 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; aspect-ratio: 16 / 10;
border-radius: 16px; border-radius: 16px;
overflow: hidden; overflow: hidden;
border: 1px solid var(--glass-border); border: 1px solid var(--glass-border);
margin-bottom: 14px; margin-bottom: 14px;
background: var(--glass-lo);
box-shadow: 0 18px 36px -22px rgba(23, 32, 60, 0.5); box-shadow: 0 18px 36px -22px rgba(23, 32, 60, 0.5);
} }
.web-shot img { .fw-bar {
display: block; display: flex;
width: 100%; align-items: center;
height: 100%; gap: 10px;
object-fit: cover; height: 32px;
object-position: center top; 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 { .web-feature-text {
flex: 1; flex: 1;