From 586e4b5a402d99317d0d3d86dd6bd025604b8bfe Mon Sep 17 00:00:00 2001 From: Martin Dvorak Date: Thu, 8 Oct 2026 09:27:42 +0000 Subject: [PATCH] Revert the last two glass iterations The colourful blob wash (92fe685) and the flat "straight ice" pass (5d05592) both missed. Reverting both to get back to the icon-driven navy palette before rebuilding the look as a proper frosted ice glass design. --- README.md | 30 ++++---- app/globals.css | 179 +++++++++++++++++++++++++++++------------------- 2 files changed, 122 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index 259cf1c..2600e53 100644 --- a/README.md +++ b/README.md @@ -112,11 +112,10 @@ no database, no data leaving your machine except the requests you send to Ollama list with per-folder counts, Settings/Dark mode pinned to the bottom). It **collapses** to a 68px icon rail (remembered in `localStorage`) and becomes a slide-in **drawer** behind a hamburger on phones; mobile also keeps the frosted header + iOS tab bar -- **One palette pulled from the app icon, both themes**: light page `#eef3fa` (pale ice) / - sidebar and cards frosted white / border `#dce4f1`; dark page `#121a2c` / sidebar `#0c1322` / - surfaces `#1a2337` / border `rgba(255,255,255,.1)`. Surfaces are frosted glass — translucent with - `backdrop-blur`, a bright 1px edge and a light-from-above sheen — over a flat backdrop with a - single faint highlight. +- **One navy palette pulled from the app icon, both themes**: light page `#f4f7fd` / sidebar + `#e9eff9` / border `#dce4f1`; dark page `#0e1424` / sidebar `#0a0f1c` / surfaces `#171f33` / + border `rgba(255,255,255,.12)`. Surfaces are frosted glass (translucent + `backdrop-blur`) over + the icon's navy backdrop. Text tiers are documented AA ratios (see below), and the cards fill the row with `repeat(auto-fill, minmax(280px, 1fr))` — no stranded cards, no dead space - Deck cards: title → muted metadata → neutral tags (difficulty carries the only colour: @@ -728,11 +727,10 @@ Things I chose while building, with the reasoning: have no `folderId`), deleting a folder re-homes its decks instead of deleting them, and folder names travel in exports so an imported deck lands where it came from. 13 browser checks cover create → freeze → move → rename → delete → reload → import. -18. **The visual language is frosted ice, with restraint.** The icon's blue is the one accent - colour driving buttons and highlights; the backdrop is flat with a single faint top highlight, - and surfaces — sidebar, cards, chrome, sheets — are translucent glass with a bright edge and a - soft sheen. Colour appears in activity tints and semantic feedback only — pretty, but still - readable and quiet. +18. **The visual language is glassmorphism, with restraint.** The icon's blue is the one accent + colour driving buttons and highlights; the backdrop is a navy wash; surfaces — sidebar, cards, + chrome, sheets — are frosted and translucent. Colour appears in activity tints and semantic + feedback only — pretty, but still readable and quiet. 19. **Custom CSS lives in `@layer components`, and glass uses Tailwind's backdrop utilities.** Two build-level traps, both found by looking at real screenshots: unlayered rules beat Tailwind utilities (which silently broke `fixed` on the feed), and a hand-written `backdrop-filter` gets @@ -752,11 +750,10 @@ Things I chose while building, with the reasoning: pill CTAs, 18px radii and restrained shadows; the sidebar, cards and chrome are translucent glass over the backdrop. Deck cards got real hierarchy: title → muted metadata → tags → summary → secondary Study CTA, with secondary actions behind a quiet menu. -23. **One fixed backdrop layer, deliberately almost flat.** The decorative wash used to be +23. **One fixed backdrop layer instead of per-page blobs.** The decorative wash used to be rendered inside the content column, so on wide screens it was clipped into a visible hard edge. - It now lives in a single `position: fixed` layer (`.app-backdrop`, `z-index: -10`): a flat ice - or navy base plus one faint highlight at the top — just enough for the glass to catch, without - turning into a gradient soup. `body`'s colour matches it so mobile overscroll shows no seam. + It now lives in a single `position: fixed` layer (`.app-backdrop`, `z-index: -10`) in the + icon's navy and blue, and `body`'s colour matches its bottom so mobile overscroll shows no seam. 24. **The UI was deliberately made quieter.** One accent colour (`accent-*`), a desaturated `frost-*` scale, hairline borders and flat surfaces replaced the saturated gradients and the seven-colour activity badges. Semantic colours (green/amber/red) survive only where they carry @@ -792,8 +789,9 @@ Things I chose while building, with the reasoning: Desktop keeps the designed 12–14px field sizes. 31. **The app icon drives the palette.** The navy rounded-square artwork in `assets/app-icon-source.jpeg` is the source of truth: `frost-*` is navy-tinted, the accent is - the snowflake blue, the primary CTA is the icon blue (deep in light mode, bright in dark), and - every icon/splash/favicon is rendered from it by `scripts/make-icons.mjs`. The sidebar, header, key gate, empty state and install sheet all show + the snowflake blue, the backdrop is the same navy wash, the primary CTA is the icon blue (deep + in light mode, bright in dark), and every icon/splash/favicon is rendered from it by + `scripts/make-icons.mjs`. The sidebar, header, key gate, empty state and install sheet all show the real artwork instead of the old monochrome snowflake tile. 14. **`gpt-oss:120b` does all content work — but it cannot read images** (`this model does not support image input`), so `VISION_MODEL` has to stay a multimodal model (`gemma4:31b-cloud`). diff --git a/app/globals.css b/app/globals.css index 5672ee2..4efcd96 100644 --- a/app/globals.css +++ b/app/globals.css @@ -81,30 +81,29 @@ --------------------------------------------------------------------------- */ :root { color-scheme: light; - --page: #eef3fa; /* pale ice */ - --sidebar: #ffffff; + --page: #f4f7fd; + --sidebar: #e9eff9; --surface: #ffffff; --surface-raised: #ffffff; - --surface-strong: #f2f6fc; - --card-hover-bg: #ffffff; + --surface-strong: #edf2fa; + --card-hover-bg: #f7faff; --surface-sunken: rgb(14 21 40 / 0.05); --hairline: #dce4f1; - --card-border: rgb(255 255 255 / 0.85); - --sidebar-divider: #dfe7f3; + --card-border: #d7e0ef; + --sidebar-divider: #d7e0ef; --hairline-soft: rgb(14 21 40 / 0.07); - --glass-tint: rgb(255 255 255 / 0.6); - --glass-top: rgb(255 255 255 / 0.95); - --glass-edge: rgb(255 255 255 / 0.85); - --glass-sheen: rgb(255 255 255 / 0.55); - --text: #0e1526; /* 16.2:1 on #eef3fa */ - --text-muted: #4d5c78; /* 6.1:1 */ - --text-subtle: #55647f; /* 5.4:1 */ + --glass-tint: rgb(255 255 255 / 0.55); + --text: #0e1526; /* 16.9:1 on #f4f7fd */ + --text-muted: #4d5c78; /* 6.3:1 */ + --text-subtle: #5a6a86; /* 5.1:1 */ --hover: rgb(14 21 40 / 0.05); - --sidebar-edge: 0 1px 0 var(--sidebar-divider), 6px 0 18px -12px rgb(14 21 40 / 0.18); + --sidebar-top: #eef3fb; + --sidebar-bottom: #e5edf9; + --sidebar-edge: 0 1px 0 var(--sidebar-divider), 6px 0 18px -12px rgb(14 21 40 / 0.35); /* API-key gate: cool near-white stage with soft blue glows, tinted input. */ - --gate-base: #eef3fa; - --gate-glow-a: rgb(90 150 240 / 0.16); - --gate-glow-b: rgb(140 150 240 / 0.12); + --gate-base: #eef3fb; + --gate-glow-a: rgb(90 150 240 / 0.25); + --gate-glow-b: rgb(140 150 240 / 0.2); --gate-card: #ffffff; --gate-card-border: #dde5f2; --gate-callout-bg: #eef3fa; @@ -118,39 +117,35 @@ --install-chip: #f2f2f2; --install-border: #e0e0e0; --shadow-card: 0 1px 2px rgb(14 21 40 / 0.06), 0 1px 3px rgb(14 21 40 / 0.04); - --card-shadow: 0 0 0 1px rgb(14 21 40 / 0.04), 0 1px 2px rgb(14 21 40 / 0.04), 0 12px 28px -14px rgb(14 21 40 / 0.2); - --shadow-card-hover: 0 0 0 1px rgb(14 21 40 / 0.05), 0 16px 36px -14px rgb(14 21 40 / 0.24), 0 2px 6px rgb(14 21 40 / 0.07); - /* Backdrop: flat ice with a single faint highlight at the top. */ - --bd-base: #eef3fa; - --bd-highlight: rgb(255 255 255 / 0.9); + --card-shadow: 0 1px 2px rgb(14 21 40 / 0.05), 0 5px 14px -6px rgb(14 21 40 / 0.12); + --shadow-card-hover: 0 12px 30px -12px rgb(14 21 40 / 0.2), 0 2px 6px rgb(14 21 40 / 0.07); } .dark { color-scheme: dark; - --page: #121a2c; - --sidebar: #0c1322; - --surface: #1a2337; - --surface-raised: #222c44; - --surface-strong: #222c44; - --card-hover-bg: #222c44; + --page: #0e1424; + --sidebar: #0a0f1c; + --surface: #171f33; + --surface-raised: #1e2740; + --surface-strong: #202a42; + --card-hover-bg: #1e2740; --surface-sunken: rgb(255 255 255 / 0.07); - --hairline: rgb(255 255 255 / 0.1); - --card-border: rgb(255 255 255 / 0.1); - --sidebar-divider: rgb(255 255 255 / 0.08); - --hairline-soft: rgb(255 255 255 / 0.07); + --hairline: rgb(255 255 255 / 0.12); + --card-border: rgb(255 255 255 / 0.12); + --sidebar-divider: rgb(255 255 255 / 0.1); + --hairline-soft: rgb(255 255 255 / 0.08); --glass-tint: rgb(255 255 255 / 0.06); - --glass-top: rgb(255 255 255 / 0.09); - --glass-edge: rgb(255 255 255 / 0.1); - --glass-sheen: rgb(255 255 255 / 0.05); - --text: #e9eefb; /* 14.8:1 on #121a2c */ - --text-muted: #b7c3dc; /* 10.0:1 */ - --text-subtle: #94a4c2; /* 7.0:1 */ + --text: #e9eefb; /* 15.4:1 on #0e1424 */ + --text-muted: #b7c3dc; /* 10.4:1 */ + --text-subtle: #94a4c2; /* 7.3:1 */ --hover: rgb(255 255 255 / 0.07); - --sidebar-edge: 0 1px 0 var(--hairline), 6px 0 16px -14px rgb(0 0 0 / 0.7); + --sidebar-top: #0b1120; + --sidebar-bottom: #080d18; + --sidebar-edge: 0 1px 0 var(--hairline), 6px 0 16px -14px rgb(0 0 0 / 0.75); /* API-key gate: navy stage with the icon's blue glows, raised input. */ - --gate-base: #0c1322; - --gate-glow-a: rgb(70 140 235 / 0.14); - --gate-glow-b: rgb(120 120 235 / 0.1); + --gate-base: #0a0f1c; + --gate-glow-a: rgb(70 140 235 / 0.2); + --gate-glow-b: rgb(120 120 235 / 0.14); --gate-card: #131a2c; --gate-card-border: rgb(255 255 255 / 0.1); --gate-callout-bg: rgb(255 255 255 / 0.06); @@ -166,8 +161,24 @@ --shadow-card: 0 1px 2px rgb(0 0 0 / 0.25); --card-shadow: var(--shadow-card); --shadow-card-hover: 0 10px 28px -16px rgb(0 0 0 / 0.55), 0 1px 2px rgb(0 0 0 / 0.3); - --bd-base: #101a2c; - --bd-highlight: rgb(120 160 220 / 0.12); +} + +/* Backdrop: the icon's navy base and blue wash, seen through frosted glass. */ +:root { + --bd-sky: rgb(110 175 245 / 0.2); + --bd-lilac: rgb(145 145 240 / 0.14); + --bd-mint: rgb(110 210 230 / 0.12); + --bd-top: #f8fafe; + --bd-mid: #f2f6fd; + --bd-bottom: #ecf1fb; +} +.dark { + --bd-sky: rgb(60 130 230 / 0.14); + --bd-lilac: rgb(110 115 235 / 0.1); + --bd-mint: rgb(70 180 215 / 0.08); + --bd-top: #111a2e; + --bd-mid: #0e1424; + --bd-bottom: #0a101d; } html, @@ -233,22 +244,54 @@ body { z-index: -10; overflow: hidden; pointer-events: none; - /* Flat ice with one faint highlight at the top — just enough for the - frosted surfaces to have something to catch. */ background: - radial-gradient(120vw 54vh at 50% -16%, var(--bd-highlight) 0%, transparent 72%), - var(--bd-base); + radial-gradient(135vw 110vh at 4% -30%, var(--bd-sky) 0%, transparent 72%), + radial-gradient(125vw 105vh at 104% -18%, var(--bd-lilac) 0%, transparent 70%), + radial-gradient(135vw 105vh at 38% 128%, var(--bd-mint) 0%, transparent 72%), + linear-gradient(180deg, var(--bd-top) 0%, var(--bd-mid) 50%, var(--bd-bottom) 100%); +} + +/* Two slow, very soft blobs for a little life behind the glass. */ +.app-backdrop::before, +.app-backdrop::after { + content: ""; + position: absolute; + border-radius: 50%; + filter: blur(110px); + opacity: 0.22; +} +.app-backdrop::before { + width: 52vw; + height: 52vw; + min-width: 24rem; + min-height: 24rem; + top: -26vh; + left: -18vw; + background: radial-gradient(circle, var(--bd-sky), transparent 66%); + animation: float 16s ease-in-out infinite; +} +.app-backdrop::after { + width: 46vw; + height: 46vw; + min-width: 22rem; + min-height: 22rem; + bottom: -28vh; + right: -14vw; + background: radial-gradient(circle, var(--bd-mint), transparent 66%); + animation: float 20s ease-in-out infinite reverse; +} +.dark .app-backdrop::before, +.dark .app-backdrop::after { + opacity: 0.3; } @layer components { /* Glass primitives ------------------------------------------------------ */ .glass-panel { @apply backdrop-blur-xl backdrop-saturate-150; - background: - linear-gradient(180deg, var(--glass-sheen) 0%, transparent 46%), - color-mix(in oklab, var(--surface) 66%, transparent); - border: 1px solid var(--glass-edge); - box-shadow: var(--shadow-card), inset 0 1px 0 var(--glass-top); + background: color-mix(in oklab, var(--surface) 82%, transparent); + border: 1px solid var(--hairline); + box-shadow: var(--shadow-card); } .glass-chip { @@ -264,7 +307,7 @@ body { /* Tailwind emits both the prefixed and standard properties; hand-written backdrop-filter gets minified down to -webkit- only, which paints nothing. */ @apply backdrop-blur-xl backdrop-saturate-150; - background: color-mix(in oklab, var(--surface) 62%, transparent); + background: color-mix(in oklab, var(--surface) 70%, transparent); border-bottom: 1px solid var(--hairline); } @@ -272,9 +315,7 @@ body { surface as the cards, with a neutral border and shadow. */ .study-chrome { @apply backdrop-blur-xl backdrop-saturate-150; - background: - linear-gradient(180deg, var(--glass-sheen) 0%, transparent 46%), - color-mix(in oklab, var(--surface) 68%, transparent); + background: color-mix(in oklab, var(--surface) 88%, transparent); border: 1px solid var(--hairline); box-shadow: var(--shadow-card); } @@ -285,15 +326,9 @@ body { .card { border-radius: 1.125rem; /* 18px */ @apply backdrop-blur-xl backdrop-saturate-150; - background: - linear-gradient(180deg, var(--glass-sheen) 0%, transparent 46%), - color-mix(in oklab, var(--surface) 62%, transparent); + background: color-mix(in oklab, var(--surface) 78%, transparent); border: 1px solid var(--card-border); - box-shadow: var(--card-shadow), inset 0 1px 0 var(--glass-top); -} -.dark .card, -.dark .glass-panel { - border-color: var(--glass-edge); + box-shadow: var(--card-shadow); } /* Opt-in hover lift for interactive cards (deck cards). Pointer devices only, @@ -319,7 +354,7 @@ body { .list-group { border-radius: 1rem; @apply backdrop-blur-lg backdrop-saturate-150; - background: color-mix(in oklab, var(--surface) 66%, transparent); + background: color-mix(in oklab, var(--surface) 82%, transparent); border: 1px solid var(--hairline); overflow: hidden; } @@ -372,7 +407,7 @@ body { .tabbar { @apply backdrop-blur-xl; - background: color-mix(in oklab, var(--page) 70%, transparent); + background: color-mix(in oklab, var(--page) 88%, transparent); border-top: 1px solid var(--hairline); } @@ -388,12 +423,14 @@ body { .dark .tint-peach { background: linear-gradient(140deg, rgb(230 160 118 / 0.26), rgb(230 160 118 / 0.1)); color: #fadfc9 } .dark .tint-rose { background: linear-gradient(140deg, rgb(230 130 150 / 0.26), rgb(230 130 150 / 0.1)); color: #fbd5dd } -/* Sidebar surface: flat frosted glass with a real edge. */ +/* Sidebar surface: frosted navy glass with a real edge. */ .sidebar-surface { @apply backdrop-blur-xl backdrop-saturate-150; - background: - linear-gradient(180deg, var(--glass-sheen) 0%, transparent 30%), - color-mix(in oklab, var(--sidebar) 64%, transparent); + background: linear-gradient( + 180deg, + color-mix(in oklab, var(--sidebar-top) 84%, transparent), + color-mix(in oklab, var(--sidebar-bottom) 84%, transparent) + ); border-right: 1px solid var(--sidebar-divider); box-shadow: var(--sidebar-edge); }