feat(web): toggle Frutiger Aero mode on Freezmo icon click (v2.2.17)

This commit is contained in:
Martin Dvořák 2026-10-15 23:21:28 +02:00
commit 12e717448b
13 changed files with 454 additions and 64 deletions

View file

@ -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.16` (see `package.json`).
- Version: `2.2.17` (see `package.json`).
### Scripts
```bash
@ -174,7 +174,13 @@ Themes: `web` = default frosted-glass website (sidebar + content, light/dark),
**Freezmo** launch panel (`.web-freezmo` — app-tinted glow, floating orbs,
gradient wordmark + "new" badge, feature bullets, Try/Source CTAs) is the one
place the **real app icon** is used (`public/freezmo.png`, the live
study app), floated beside the copy with a soft glow. The two remaining feature panels (`.web-split`) stretch
study app), floated beside the copy with a soft glow. Clicking the Freezmo icon
toggles **Frutiger Aero mode** (`data-aero="true"` on `<html>`, web shell only;
desktop mode is untouched): the icon swaps to `public/freezmo_aero.png`, the
website background switches to `public/frutigerbackground.png`, floating glossy
bubbles (`.web-aero-bubbles` / `.web-aero-bubble`) rise across the backdrop, and
all `.glass` panels and interactive controls become refractive liquid glass /
gel buttons. The two remaining feature panels (`.web-split`) stretch
to equal height and share a fixed `16 / 10` `.fw` window (the `FeatureWindow`
component) — decorative, **asset-free CSS mock-ups** rather than screenshots:
Revival Inc. is a little necromancy game scene (night sky, moon, tombstones,
@ -195,7 +201,8 @@ Themes: `web` = default frosted-glass website (sidebar + content, light/dark),
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 Freezmo app icon (`public/freezmo.png`), the
the Freezmo app icons (`public/freezmo.png`, `public/freezmo_aero.png`), the
Frutiger Aero wallpaper (`public/frutigerbackground.png`), 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

View file

@ -40,8 +40,14 @@ more.
(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 Freezmo app icon, the Joker
only real images left are the logo, the social SVGs, the Freezmo app icons (`freezmo.png` /
`freezmo_aero.png`), the Frutiger Aero background (`frutigerbackground.png`), the Joker
easter egg and the Gentoo mark.
- **Frutiger Aero easter egg (web shell)** — clicking the Freezmo app icon swaps it to
`freezmo_aero.png` and transforms the web shell into a Frutiger Aero theme:
`frutigerbackground.png` as the full-screen backdrop, floating glossy water/soap bubbles
across the viewport, and refractive liquid-glass panels and gel buttons (leaving the
desktop mode untouched).
- **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.
@ -82,9 +88,11 @@ more.
imported for the base layer but the UI is custom CSS.
- **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` — only the logo (`src/app/icon.png`), the social SVGs, the Joker
easter egg and the Gentoo mark; all cards, feature/app previews and the wallpaper are CSS.
- **Version:** `2.2.16` (see `package.json`)
- **Images:** `next/image` — only the logo (`src/app/icon.png`), the social SVGs, the Freezmo
icons (`public/freezmo.png`, `public/freezmo_aero.png`), the Frutiger Aero wallpaper
(`public/frutigerbackground.png`), the Joker easter egg and the Gentoo mark; all cards,
feature/app previews and the desktop wallpaper are CSS.
- **Version:** `2.2.17` (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**,

View file

@ -3,13 +3,11 @@
"1": "Deployment & Ops Docs",
"2": "braces Package Metadata",
"4": "braces Parsing Engine",
"5": "Project Index Data",
"6": "braces Character Constants",
"7": "Next Config & API Routes",
"8": "TypeScript Compiler Config",
"9": "Root Package Dependencies",
"10": "Root Dev Dependencies",
"11": "Custom Dev Server",
"12": "Debian Dependency Installer",
"13": "brace-expansion Overrides",
"14": "npm Scripts",

View file

@ -1,17 +1,17 @@
# Graph Report - dvorakmortonxd.dev (2026-10-15)
## Corpus Check
- 43 files · ~105,962 words
- 43 files · ~319,246 words
- Verdict: corpus is large enough that graph structure adds value.
- Unclassified: 19 file(s) not represented in the graph (top: (none) 6, .ttf 3, .css 3)
## Summary
- 423 nodes · 591 edges · 42 communities (27 shown, 15 thin omitted)
- 424 nodes · 592 edges · 40 communities (25 shown, 15 thin omitted)
- Extraction: 93% EXTRACTED · 7% INFERRED · 0% AMBIGUOUS · INFERRED: 44 edges (avg confidence: 0.89)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `6702ca8d`
- Built from commit: `20f67910`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
@ -21,13 +21,12 @@
- braces Package Metadata
- WindowsDesktop.tsx
- braces Parsing Engine
- Project Index Data
- apps.tsx
- braces Character Constants
- Next Config & API Routes
- TypeScript Compiler Config
- Root Package Dependencies
- Root Dev Dependencies
- Custom Dev Server
- Debian Dependency Installer
- brace-expansion Overrides
- npm Scripts
@ -54,7 +53,6 @@
- Snapchat Icon Asset
- SoundCloud Icon Asset
- Twitter/X Icon Asset
- useColorScheme.ts
## God Nodes (most connected - your core abstractions)
1. `WebLayout()` - 18 edges
@ -69,6 +67,8 @@
10. `ServiceButton()` - 8 edges
## Surprising Connections (you probably didn't know these)
- `Web theme + light/dark (v2.2 rework)` --references--> `FeatureWindow()` [INFERRED]
AGENTS.md → src/app/components/FeatureWindow.tsx
- `How UI selection works` --references--> `ThemeScript()` [INFERRED]
AGENTS.md → src/app/components/ThemeScript.tsx
- `How UI selection works` --references--> `WebLayout()` [INFERRED]
@ -77,8 +77,6 @@
AGENTS.md → src/app/components/useColorScheme.ts
- `Conventions & things that will bite you` --references--> `braces()` [INFERRED]
AGENTS.md → vendor/braces/index.js
- `Web theme + light/dark (v2.2 rework)` --references--> `ContributionHeatmap()` [INFERRED]
AGENTS.md → src/app/components/ContributionHeatmap.tsx
## Import Cycles
- None detected.
@ -88,11 +86,11 @@
- **** — public_revival_inc_favicon_png_image, public_revival_inc_favicon_png_revival_inc_game, public_revival_inc_favicon_png_game_controller_mark [INFERRED 0.75]
- **Site identity: eye logo used as app icon and in-page logo** — src_app_icon_png_image, src_app_icon_png_eye_logo, src_app_icon_png_favicon, src_app_components_jokerlogo_tsx_logo [INFERRED 0.85]
## Communities (42 total, 15 thin omitted)
## Communities (40 total, 15 thin omitted)
### Community 0 - "Desktop App Catalog"
Cohesion: 0.08
Nodes (33): Web theme + light/dark (v2.2 rework), react, ALACRITTY_APP, ContributionsApp(), FriendsApp(), GitdxdApp(), NEOFETCH, RevivalApp() (+25 more)
Cohesion: 0.11
Nodes (26): Web theme + light/dark (v2.2 rework), react, ContributionsApp(), Clock(), getTime(), subscribe(), Activity, ActivityPayload (+18 more)
### Community 1 - "Deployment & Ops Docs"
Cohesion: 0.09
@ -104,15 +102,15 @@ Nodes (37): ansi-colors, bash-path, gulp-format-md, mocha, author, bugs, url, co
### Community 3 - "WindowsDesktop.tsx"
Cohesion: 0.08
Nodes (34): Conventions & things that will bite you, How UI selection works, APP_BY_ID, AppDef, APPS, createWebsiteApp(), EmbeddedSite(), WebsiteApp() (+26 more)
Nodes (33): Conventions & things that will bite you, How UI selection works, APP_BY_ID, AppDef, APPS, createWebsiteApp(), EmbeddedSite(), WebsiteApp() (+25 more)
### Community 4 - "braces Parsing Engine"
Cohesion: 0.10
Nodes (17): compile, expand, parse, stringify, compile(), fill, utils, append() (+9 more)
### Community 5 - "Project Index Data"
Cohesion: 0.14
Nodes (20): HardwareApp(), ProjectsApp(), CodingProjectIndex(), CodingRow(), Group(), HARDWARE_GROUPS, HardwareProjectIndex(), HardwareRow() (+12 more)
### Community 5 - "apps.tsx"
Cohesion: 0.07
Nodes (36): ALACRITTY_APP, FriendsApp(), GitdxdApp(), HardwareApp(), NEOFETCH, ProjectsApp(), RevivalApp(), RustyWolfApp() (+28 more)
### Community 6 - "braces Character Constants"
Cohesion: 0.11
@ -135,8 +133,8 @@ Nodes (3): {
}, parse(), stringify
### Community 7 - "Next Config & API Routes"
Cohesion: 0.15
Nodes (13): nextConfig, next, ALLOWED_HOSTS, clickShim(), fail(), GET(), isAllowedHost(), ALLOWED_HOSTS (+5 more)
Cohesion: 0.10
Nodes (20): nextConfig, next, app, hasForwardedHeaders(), isDevDebugRequest(), isLocalDebugRequest(), isLoopback(), port (+12 more)
### Community 8 - "TypeScript Compiler Config"
Cohesion: 0.11
@ -150,10 +148,6 @@ Nodes (14): fill-range, name, private, version, autoprefixer, postcss, react-dom
Cohesion: 0.15
Nodes (13): devDependencies, autoprefixer, eslint, eslint-config-next, fill-range, postcss, @svgr/webpack, tailwindcss (+5 more)
### Community 11 - "Custom Dev Server"
Cohesion: 0.29
Nodes (7): app, hasForwardedHeaders(), isDevDebugRequest(), isLocalDebugRequest(), isLoopback(), port, server
### Community 12 - "Debian Dependency Installer"
Cohesion: 0.43
Nodes (6): die(), log(), node_ok(), install-dependencies-deb.sh script, usage(), warn()
@ -210,13 +204,9 @@ Nodes (3): Game Controller Logo Mark, Revival Inc. Favicon (yellow character wit
Cohesion: 0.67
Nodes (3): TikTok, TikTok Logo Icon, SVG Repo
### Community 41 - "useColorScheme.ts"
Cohesion: 0.39
Nodes (7): ColorScheme, DEFAULT_SCHEME, getServerSnapshot(), getSnapshot(), setColorScheme(), subscribe(), useColorScheme()
## Knowledge Gaps
- **54 isolated node(s):** `react-dom`, `@svgr/webpack`, `@tailwindcss/postcss`, `@types/node`, `@types/react` (+49 more)
These have ≤1 connection - possible missing edges or undocumented components. (Counts symbols only; 226 node(s) total have ≤1 connection when file, concept and rationale nodes are included.)
These have ≤1 connection - possible missing edges or undocumented components. (Counts symbols only; 227 node(s) total have ≤1 connection when file, concept and rationale nodes are included.)
- **15 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
@ -229,10 +219,10 @@ _Questions this graph is uniquely positioned to answer:_
- **What connects `react-dom`, `@svgr/webpack`, `@tailwindcss/postcss` to the rest of the system?**
_54 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `Desktop App Catalog` be split into smaller, more focused modules?**
_Cohesion score 0.0797872340425532 - nodes in this community are weakly interconnected._
_Cohesion score 0.10873440285204991 - nodes in this community are weakly interconnected._
- **Why does `braces()` connect `braces Public API Methods` to `WindowsDesktop.tsx`, `braces Parsing Engine`?**
_High betweenness centrality (0.150) - this node is a cross-community bridge._
- **Should `Deployment & Ops Docs` be split into smaller, more focused modules?**
_Cohesion score 0.08735632183908046 - nodes in this community are weakly interconnected._
- **Why does `next` connect `Next Config & API Routes` to `Desktop App Catalog`, `Root Package Dependencies`, `Custom Dev Server`, `WindowsDesktop.tsx`?**
- **Why does `next` connect `Next Config & API Routes` to `Desktop App Catalog`, `Root Package Dependencies`, `WindowsDesktop.tsx`, `apps.tsx`?**
_High betweenness centrality (0.126) - this node is a cross-community bridge._

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -24,9 +24,9 @@
"semantic_hash": "03388a201be8750ddcddb14dfbc34411"
},
"package.json": {
"mtime": 1792097800.6502452,
"seen": 1792097851.621277,
"ast_hash": "52b55982a20e4621bbf5c3d5b71cce72",
"mtime": 1792099231.5153382,
"seen": 1792099263.7669873,
"ast_hash": "bb5daf478e875b6c5d5d2ec7def9b34b",
"semantic_hash": ""
},
"postcss.config.mjs": {
@ -114,9 +114,9 @@
"semantic_hash": "20e25c727b4bb982e5a22d65efece143"
},
"src/app/components/WebLayout.tsx": {
"mtime": 1792097800.6502452,
"seen": 1792097851.62133,
"ast_hash": "052b55c79df46ba5b1e8448149d4fd64",
"mtime": 1792099104.6213684,
"seen": 1792099263.767026,
"ast_hash": "50f87afc8c92b67d8e83deaf0db4f4a6",
"semantic_hash": ""
},
"src/app/components/WinWindow.tsx": {
@ -240,9 +240,9 @@
"semantic_hash": "b81dea2175efa2b0b6c38451ab5a4bbd"
},
"README.md": {
"mtime": 1792097800.6502452,
"seen": 1792097851.6214051,
"ast_hash": "4bb82eae835a1f9fb3bd13b54e9adeaa",
"mtime": 1792099238.3164437,
"seen": 1792099263.7670863,
"ast_hash": "3109d3679249509421774e43bea54df6",
"semantic_hash": ""
},
"public/llms.txt": {
@ -366,9 +366,21 @@
"semantic_hash": "6f69e16823f10d9c7af1625eff2b32e8"
},
"AGENTS.md": {
"mtime": 1792097800.6502452,
"seen": 1792097851.621398,
"ast_hash": "c2418856058d91e442c35b5237ef54de",
"mtime": 1792099254.9847023,
"seen": 1792099263.7670813,
"ast_hash": "401ce39abe80415b6a57cc5e55684160",
"semantic_hash": ""
},
"public/freezmo_aero.png": {
"mtime": 1792098428.3329225,
"seen": 1792099263.7671037,
"ast_hash": "603684d2e73038426c66896749c0c00d",
"semantic_hash": ""
},
"public/frutigerbackground.png": {
"mtime": 1792098606.0566647,
"seen": 1792099263.7671065,
"ast_hash": "78344ac481fd50f9366c30f041c8e135",
"semantic_hash": ""
}
}

4
package-lock.json generated
View file

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

View file

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

BIN
public/freezmo_aero.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

View file

@ -1,7 +1,7 @@
"use client";
import Image from "next/image";
import type { ReactNode } from "react";
import { useEffect, useState, type CSSProperties, type ReactNode } from "react";
import JokerLogo from "./JokerLogo";
import ServiceButton from "./ServiceButton";
import ContributionHeatmap from "./ContributionHeatmap";
@ -21,6 +21,42 @@ const NAV = [
{ id: "friends", label: "Friends" },
];
const AERO_BUBBLES: Array<{
left: string;
size: number;
duration: number;
delay: number;
drift: number;
opacity: number;
}> = [
{ left: "2%", size: 44, duration: 14, delay: -2, drift: 28, opacity: 0.78 },
{ left: "6%", size: 18, duration: 9, delay: -6, drift: -18, opacity: 0.65 },
{ left: "9%", size: 86, duration: 19, delay: -11, drift: 42, opacity: 0.72 },
{ left: "13%", size: 28, duration: 11, delay: -4, drift: -24, opacity: 0.8 },
{ left: "17%", size: 58, duration: 16, delay: -14, drift: 30, opacity: 0.75 },
{ left: "21%", size: 14, duration: 8, delay: -1, drift: 16, opacity: 0.6 },
{ left: "25%", size: 110, duration: 22, delay: -9, drift: -46, opacity: 0.68 },
{ left: "29%", size: 34, duration: 12, delay: -7, drift: 22, opacity: 0.82 },
{ left: "33%", size: 22, duration: 10, delay: -3, drift: -20, opacity: 0.7 },
{ left: "37%", size: 68, duration: 17, delay: -12, drift: 36, opacity: 0.76 },
{ left: "41%", size: 16, duration: 8.5, delay: -5, drift: -14, opacity: 0.62 },
{ left: "45%", size: 48, duration: 15, delay: -10, drift: 26, opacity: 0.8 },
{ left: "49%", size: 94, duration: 21, delay: -16, drift: -38, opacity: 0.7 },
{ left: "53%", size: 26, duration: 11.5, delay: -2.5, drift: 20, opacity: 0.75 },
{ left: "57%", size: 38, duration: 13, delay: -8, drift: -30, opacity: 0.78 },
{ left: "61%", size: 124, duration: 24, delay: -18, drift: 48, opacity: 0.64 },
{ left: "65%", size: 20, duration: 9.5, delay: -4.5, drift: -16, opacity: 0.68 },
{ left: "69%", size: 54, duration: 15.5, delay: -13, drift: 32, opacity: 0.76 },
{ left: "73%", size: 30, duration: 12.5, delay: -6.5, drift: -22, opacity: 0.8 },
{ left: "77%", size: 76, duration: 18, delay: -15, drift: 34, opacity: 0.72 },
{ left: "81%", size: 15, duration: 8, delay: -3.5, drift: 14, opacity: 0.6 },
{ left: "84%", size: 62, duration: 16.5, delay: -11.5, drift: -28, opacity: 0.74 },
{ left: "88%", size: 32, duration: 11, delay: -7.5, drift: 24, opacity: 0.82 },
{ left: "91%", size: 102, duration: 20, delay: -17, drift: -40, opacity: 0.68 },
{ left: "94%", size: 24, duration: 10.5, delay: -1.5, drift: 18, opacity: 0.72 },
{ left: "97%", size: 46, duration: 14.5, delay: -9.5, drift: -26, opacity: 0.76 },
];
function Section({
id,
eyebrow,
@ -145,10 +181,45 @@ export default function WebLayout() {
const { toggle } = useTheme();
const { scheme, toggle: toggleScheme } = useColorScheme();
const dark = scheme === "dark";
const [aero, setAero] = useState(false);
useEffect(() => {
const root = document.documentElement;
if (aero) {
root.setAttribute("data-aero", "true");
} else {
root.removeAttribute("data-aero");
}
return () => {
root.removeAttribute("data-aero");
};
}, [aero]);
return (
<>
<div className="web-bg" aria-hidden="true" />
<div className="web-bg" aria-hidden="true">
{aero && (
<div className="web-aero-bubbles">
{AERO_BUBBLES.map((b, i) => (
<span
key={i}
className="web-aero-bubble"
style={
{
left: b.left,
width: `${b.size}px`,
height: `${b.size}px`,
animationDuration: `${b.duration}s`,
animationDelay: `${b.delay}s`,
opacity: b.opacity,
"--bubble-drift": `${b.drift}px`,
} as CSSProperties
}
/>
))}
</div>
)}
</div>
<div className="web-root">
<aside className="web-sidebar glass">
@ -271,16 +342,26 @@ export default function WebLayout() {
</a>
</div>
</div>
<div className="web-freezmo-icon">
<button
type="button"
className="web-freezmo-icon"
onClick={() => setAero((a) => !a)}
aria-pressed={aero}
title={aero ? "Switch back from Frutiger Aero" : "Toggle Frutiger Aero mode"}
>
<Image
src="/freezmo.png"
alt="Freezmo app icon — a blue snowflake over a stack of cards on a navy squircle"
src={aero ? "/freezmo_aero.png" : "/freezmo.png"}
alt={
aero
? "Freezmo Frutiger Aero app icon — glossy cards with a snowflake over a bubbly sky and ocean scene"
: "Freezmo app icon — a blue snowflake over a stack of cards on a navy squircle"
}
width={512}
height={512}
sizes="(max-width: 900px) 45vw, 200px"
className="web-freezmo-img"
/>
</div>
</button>
</div>
</section>
@ -340,7 +421,7 @@ export default function WebLayout() {
</Section>
<footer className="web-footer glass">
<span className="web-footer-label">v2.2.16</span>
<span className="web-footer-label">v2.2.17</span>
© 2025-2026 Martin Dvořák. All rights reserved (not really I dont care, just
follow the GPL3.0 License)
</footer>

View file

@ -611,6 +611,10 @@ html[data-theme="web"] body {
place-items: center;
min-height: 100%;
padding: clamp(10px, 2.5vw, 26px) 0;
border: none;
background: transparent;
font: inherit;
cursor: pointer;
animation: freezmo-float 7s ease-in-out infinite;
}
.web-freezmo-icon::before {
@ -1940,6 +1944,296 @@ html[data-theme="web"][data-scheme="dark"] .heatmap-legend-cell[data-level="4"]
color: var(--fg);
}
/* ============================================================
FRUTIGER AERO MODE (web shell only, toggled by Freezmo icon)
------------------------------------------------------------
Activated via `<html data-theme="web" data-aero="true">`.
Sets `frutigerbackground.png` as the full-viewport backdrop,
floats glossy water/soap bubbles across the screen, and turns
every panel and button into refractive liquid glass.
============================================================ */
html[data-theme="web"][data-aero="true"],
html[data-theme="web"][data-scheme="dark"][data-aero="true"] {
--web-accent: #0078e8;
--web-accent-2: #00c49a;
--web-page: #0a74cc;
--web-ink: #03223a;
--web-muted: #0d456c;
--glass-hi: rgba(255, 255, 255, 0.72);
--glass-lo: rgba(185, 238, 255, 0.34);
--glass-border: rgba(255, 255, 255, 0.9);
--glass-inset: rgba(255, 255, 255, 0.98);
--pill-bg: rgba(230, 250, 255, 0.9);
--glass-shadow:
0 24px 50px -18px rgba(0, 42, 92, 0.58),
0 4px 16px rgba(0, 170, 255, 0.28);
--hover: rgba(0, 168, 255, 0.22);
--accent-soft: rgba(0, 168, 255, 0.45);
--accent-line: rgba(255, 255, 255, 0.95);
--btn-bg: rgba(255, 255, 255, 0.68);
--btn-bg-hi: rgba(255, 255, 255, 0.92);
--btn-border: rgba(255, 255, 255, 0.92);
--btn-shadow:
0 10px 22px -10px rgba(0, 55, 110, 0.45),
inset 0 1.5px 0 rgba(255, 255, 255, 0.98),
inset 0 -2px 6px rgba(0, 165, 245, 0.28);
--fg: var(--web-ink);
--white: var(--web-ink);
--gray: var(--web-muted);
--bg: var(--web-page);
--bg-hard: rgba(255, 255, 255, 0.42);
--accent: var(--web-accent);
--accent2: var(--web-accent-2);
--accent3: var(--web-accent);
--green: #0ea54a;
--aqua: #009eb8;
--blue: #006ee6;
--purple: #6d4aff;
--red: #e11d48;
--darkpink: #d92682;
--index-line: rgba(255, 255, 255, 0.55);
--status-finished: #0b7a38;
--status-live: #005ec4;
--status-wip: #9a4700;
--status-unfinished: #a83205;
--status-discontinued: #a81313;
--status-forgotten: #2f516c;
}
html[data-theme="web"][data-aero="true"] body {
background: #0a74cc url("/frutigerbackground.png") center / cover fixed no-repeat;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55);
}
html[data-theme="web"][data-aero="true"] .web-bg {
background:
radial-gradient(56rem 38rem at 86% 6%, rgba(255, 255, 255, 0.42), transparent 65%),
radial-gradient(50rem 40rem at 14% 92%, rgba(80, 255, 190, 0.24), transparent 68%),
url("/frutigerbackground.png") center / cover no-repeat;
}
/* ---------- Floating Frutiger Aero water/soap bubbles ---------- */
.web-aero-bubbles {
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
}
.web-aero-bubble {
position: absolute;
bottom: -150px;
border-radius: 50%;
border: 1.2px solid rgba(255, 255, 255, 0.86);
background:
radial-gradient(
circle at 28% 24%,
rgba(255, 255, 255, 0.95) 0%,
rgba(255, 255, 255, 0.45) 12%,
rgba(160, 240, 255, 0.1) 44%,
rgba(0, 195, 255, 0.28) 76%,
rgba(130, 255, 210, 0.62) 92%,
rgba(255, 255, 255, 0.92) 100%
);
box-shadow:
inset 0 -8px 16px rgba(0, 175, 255, 0.36),
inset 0 6px 12px rgba(255, 255, 255, 0.9),
0 6px 22px rgba(0, 120, 215, 0.32),
0 0 14px rgba(175, 250, 255, 0.55);
-webkit-backdrop-filter: blur(2px) saturate(160%);
backdrop-filter: blur(2px) saturate(160%);
animation: aero-bubble-rise linear infinite;
will-change: transform;
}
.web-aero-bubble::before {
content: "";
position: absolute;
top: 13%;
left: 17%;
width: 30%;
height: 18%;
border-radius: 50%;
background: radial-gradient(
ellipse at center,
rgba(255, 255, 255, 0.98) 0%,
rgba(255, 255, 255, 0.45) 65%,
transparent 100%
);
transform: rotate(-32deg);
}
.web-aero-bubble::after {
content: "";
position: absolute;
bottom: 12%;
right: 15%;
width: 22%;
height: 12%;
border-radius: 50%;
background: radial-gradient(
ellipse at center,
rgba(200, 255, 235, 0.85) 0%,
transparent 100%
);
transform: rotate(-28deg);
}
@keyframes aero-bubble-rise {
0% {
transform: translate3d(0, 0, 0) scale(0.88);
}
25% {
transform: translate3d(var(--bubble-drift, 24px), -28vh, 0) scale(0.96);
}
50% {
transform: translate3d(calc(var(--bubble-drift, 24px) * -0.6), -58vh, 0) scale(1.03);
}
75% {
transform: translate3d(var(--bubble-drift, 24px), -88vh, 0) scale(0.98);
}
100% {
transform: translate3d(0, -122vh, 0) scale(1.06);
}
}
/* ---------- Liquid glass panels & glossy gel surfaces ---------- */
html[data-theme="web"][data-aero="true"] .glass {
background-color: rgba(210, 245, 255, 0.36);
background-image: linear-gradient(
180deg,
rgba(255, 255, 255, 0.82) 0%,
rgba(240, 252, 255, 0.52) 44%,
rgba(170, 232, 255, 0.34) 46%,
rgba(130, 220, 255, 0.46) 86%,
rgba(180, 255, 235, 0.56) 100%
);
border: 1.5px solid rgba(255, 255, 255, 0.92);
box-shadow:
0 24px 50px -18px rgba(0, 42, 92, 0.58),
0 3px 14px rgba(0, 175, 255, 0.3),
inset 0 2px 1px rgba(255, 255, 255, 1),
inset 0 18px 28px rgba(255, 255, 255, 0.45),
inset 0 -4px 12px rgba(0, 175, 255, 0.34);
-webkit-backdrop-filter: blur(16px) saturate(200%) brightness(1.06);
backdrop-filter: blur(16px) saturate(200%) brightness(1.06);
}
html[data-theme="web"][data-aero="true"] .web-freezmo {
background:
radial-gradient(60% 90% at 12% 0%, rgba(255, 255, 255, 0.65), transparent 62%),
radial-gradient(52% 82% at 94% 14%, rgba(0, 215, 255, 0.42), transparent 60%),
radial-gradient(48% 72% at 76% 100%, rgba(70, 255, 180, 0.38), transparent 64%),
linear-gradient(
180deg,
rgba(255, 255, 255, 0.85) 0%,
rgba(220, 248, 255, 0.52) 48%,
rgba(145, 225, 255, 0.44) 50%,
rgba(175, 255, 230, 0.58) 100%
);
}
html[data-theme="web"][data-aero="true"] .web-freezmo-orb {
border: 1.2px solid rgba(255, 255, 255, 0.9);
background: radial-gradient(
circle at 30% 26%,
rgba(255, 255, 255, 0.98) 0%,
rgba(255, 255, 255, 0.45) 16%,
rgba(130, 235, 255, 0.2) 52%,
rgba(0, 195, 255, 0.42) 82%,
rgba(160, 255, 220, 0.78) 100%
);
box-shadow:
inset 0 -8px 16px rgba(0, 180, 255, 0.4),
inset 0 6px 12px rgba(255, 255, 255, 0.95),
0 0 36px rgba(100, 230, 255, 0.6);
}
html[data-theme="web"][data-aero="true"] .web-ctl,
html[data-theme="web"][data-aero="true"] .service-button,
html[data-theme="web"][data-aero="true"] .social-button,
html[data-theme="web"][data-aero="true"] .gym-item,
html[data-theme="web"][data-aero="true"] .contribution-entry,
html[data-theme="web"][data-aero="true"] .index-list,
html[data-theme="web"][data-aero="true"] .heatmap-container,
html[data-theme="web"][data-aero="true"] .web-freezmo-ghost {
background-image: linear-gradient(
180deg,
rgba(255, 255, 255, 0.92) 0%,
rgba(230, 250, 255, 0.68) 49%,
rgba(168, 230, 255, 0.52) 51%,
rgba(215, 250, 255, 0.82) 100%
);
border-color: rgba(255, 255, 255, 0.94);
box-shadow:
0 10px 22px -12px rgba(0, 52, 105, 0.48),
inset 0 1.5px 0 rgba(255, 255, 255, 1),
inset 0 -2px 6px rgba(0, 170, 255, 0.28);
}
html[data-theme="web"][data-aero="true"] .web-ctl:hover,
html[data-theme="web"][data-aero="true"] .service-button:hover,
html[data-theme="web"][data-aero="true"] .social-button:hover,
html[data-theme="web"][data-aero="true"] .gym-item:hover,
html[data-theme="web"][data-aero="true"] .web-freezmo-ghost:hover {
background-image: linear-gradient(
180deg,
rgba(255, 255, 255, 0.98) 0%,
rgba(235, 252, 255, 0.82) 49%,
rgba(175, 238, 255, 0.68) 51%,
rgba(210, 255, 242, 0.92) 100%
);
border-color: #ffffff;
box-shadow:
0 14px 28px -12px rgba(0, 95, 175, 0.55),
0 0 14px rgba(80, 225, 255, 0.65),
inset 0 2px 0 #ffffff;
}
html[data-theme="web"][data-aero="true"] .web-freezmo-cta,
html[data-theme="web"][data-aero="true"] .web-freezmo-badge,
html[data-theme="web"][data-aero="true"] .web-switch.on {
background: linear-gradient(
180deg,
#9df8ff 0%,
#28c8ff 48%,
#0088eb 52%,
#00dc9e 100%
);
border: 1px solid rgba(255, 255, 255, 0.95);
box-shadow:
0 12px 26px -10px rgba(0, 95, 175, 0.65),
0 0 12px rgba(0, 210, 255, 0.45),
inset 0 1.5px 0 rgba(255, 255, 255, 0.98);
text-shadow: 0 1px 2px rgba(0, 55, 105, 0.65);
}
html[data-theme="web"][data-aero="true"] .social-icon {
filter: none;
opacity: 0.85;
}
html[data-theme="web"][data-aero="true"] .heatmap-cell[data-level="1"],
html[data-theme="web"][data-aero="true"] .heatmap-legend-cell[data-level="1"] {
background-color: rgba(0, 168, 240, 0.28);
}
html[data-theme="web"][data-aero="true"] .heatmap-cell[data-level="2"],
html[data-theme="web"][data-aero="true"] .heatmap-legend-cell[data-level="2"] {
background-color: rgba(0, 155, 235, 0.5);
}
html[data-theme="web"][data-aero="true"] .heatmap-cell[data-level="3"],
html[data-theme="web"][data-aero="true"] .heatmap-legend-cell[data-level="3"] {
background-color: rgba(0, 140, 225, 0.76);
}
html[data-theme="web"][data-aero="true"] .heatmap-cell[data-level="4"],
html[data-theme="web"][data-aero="true"] .heatmap-legend-cell[data-level="4"] {
background-color: #0078e8;
}
/* ============================================================
RESPONSIVE — sidebar collapses to a header on phones/tablets
============================================================ */