Navy theme from the app icon, frosted glass, and the new mark in-app

The icon's artwork is now the source of truth for the palette:

- frost-* is a navy-tinted neutral scale and accent-* is the icon's
  snowflake blue; light page #f4f7fd / sidebar #e9eff9 / border
  #dce4f1, dark page #0e1424 / sidebar #0a0f1c / surfaces #171f33.
- Surfaces are frosted glass: .card, .glass-panel, .list-group and
  .sidebar-surface are translucent with backdrop-blur/saturate, and the
  page wrapper no longer paints an opaque --page, so the fixed navy/blue
  backdrop reads through the sidebar, cards, chrome and sheets.
- The primary CTA is the icon's blue (#1d61ab in light, #59a8f7 in dark
  with ink text).
- The gate stage, fields and glows are navy/blue; viewport theme-color
  and the manifest background/theme colour are #0e1424.
- The old monochrome snowflake tile is gone from the brand: sidebar,
  header, key gate and empty state now show the real app icon (the
  install sheet already did). Decorative snowflakes stay.
- --surface-strong is finally defined (it was referenced but never
  declared, so those inputs had no fill).
- Icon URLs carry ?v=2 so browser favicon caches refetch.

Updated the contrast audit (21 pairs) and the deck-chrome token
assertions to the new theme. Verified: contrast 21/21, deck chrome
15/15, key gate 49/49, install sheet 64/64, final ux 12/12, retry
14/14, zoom 6/6, security 8/8, pwa 16/16, deck fixes 15/15.
This commit is contained in:
Martin Dvořák 2026-10-08 06:15:51 +00:00
commit 979f3ddc39
10 changed files with 181 additions and 158 deletions

View file

@ -112,8 +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 neutral palette, both themes**: light page `#ffffff` / sidebar `#f9f9f9` / border `#e5e5e5`;
dark page `#212121` / sidebar `#171717` / surfaces `#2f2f2f` / border `rgba(255,255,255,.1)`.
- **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:
@ -725,9 +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 glassmorphism, with restraint.** One accent colour drives the buttons
and highlights; the backdrop is a soft aurora wash; surfaces are frosted and translucent. 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
@ -738,19 +741,19 @@ Things I chose while building, with the reasoning:
a fixture deck into `localStorage` rather than relying on app code.
21. **Dark mode is token-driven, and contrast is audited.** Text colours come from
`--text` / `--text-muted` / `--text-subtle` (never hardcoded), and a `contrast.mjs` audit checks
every pairing at WCAG AA — currently **20/20 pairs pass** (e.g. dark secondary `#b4b4b4` on
`#212121` = 7.8:1). Difficulty colours were darkened until they cleared 4.5:1 on their pills.
every pairing at WCAG AA — currently **21/21 pairs pass** (e.g. dark secondary `#b7c3dc` on
`#0e1424` = 10.4:1, white on the light blue CTA `#1d61ab` = 6.3:1). Difficulty colours were
darkened until they cleared 4.5:1 on their pills.
This was a real bug: an earlier edit silently failed, so `.dark` never overrode `--text`, leaving
near-black text on a dark page.
22. **The dashboard follows a neutral, ChatGPT-style system.** Near-white/near-black surfaces,
hairline borders, pill CTAs, 18px radii and restrained shadows; the frost survives in the chrome
(header, tab bar, sheets) rather than in every card, which keeps text crisp and the layout
premium. Deck cards got real hierarchy: title → muted metadata → tags → summary → prominent
Study CTA, with secondary actions behind a quiet menu.
23. **One fixed backdrop layer instead of per-page blobs.** The decorative aurora used to be
22. **The dashboard follows the icon's navy system.** Frosted navy/white surfaces, hairline borders,
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 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`) with very low
opacities, and `body`'s colour matches its bottom 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
@ -784,6 +787,12 @@ Things I chose while building, with the reasoning:
`user-scalable=no` for an app-like feel, and a mobile-only rule keeps every input and textarea at
16px so iOS Safari's focus auto-zoom never fires (iOS ignores `user-scalable=no` for that).
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 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`).
Text-only inputs — pasted text, PDFs with a text layer, DOCX/PPTX/ODT — never touch the vision