Design pass (all themes + chat UI), model picker, reduce-motion toggle

- Aero: blue gradients back on the active segment and every primary
  (Review due, the card's Study button, the composer's send button); a
  frosted backing behind the page heading and the All decks label; a
  bottom-weighted rail tint; chat header/composer/welcome panels; calmer
  orbs; stronger card veil + a stronger blur bake.
- Dark/Light: a dedicated --track token makes progress bars read (the
  first attempt shifted --surface-sunken and the pixel scan caught the
  sheet-contrast regression); brighter dark secondary text; darker light
  segmented labels and difficulty tints; stronger light card border and
  shadow.
- Chat UI: friendly model chip ("Gemma 4 31B") with the raw name in a
  tooltip, larger chips, plain centered disclaimer, vertically centered
  welcome panel, distinct chat-plus icon for new chat.
- Chat models: every chat-capable model from the server's /api/tags is
  selectable (favourites first, search field when long, live-list
  validation in the route); default CHAT_MODEL_DEFAULT or gemma4:31b.
- Reduce motion: Settings toggle (default off) that parks the scene,
  collapses CSS animations and drives framer-motion's calm mode.
- Folders hint, "New" badge on fresh decks.

Contrast: 0 findings, axe clean, mobile 126/126, chat 102/102, wizard
95/95, regression 44/44, i18n 13/13, native 33/33.
Version 0.4.6, SW -> freezmo-v71.
This commit is contained in:
Martin Dvořák 2026-10-14 05:06:02 +00:00
commit 6635b5d405
29 changed files with 468 additions and 90 deletions

View file

@ -127,6 +127,11 @@ export function getServerConfig(): ServerConfig {
const visionModel = process.env.VISION_MODEL?.trim() || "gemma4:31b-cloud";
const textModel = process.env.TEXT_MODEL?.trim() || "gpt-oss:120b-cloud";
// The chat picker opens on this one when the server offers it. Every model
// the server lists is selectable anyway (see /api/health), so trying a free
// Ollama model never needs an .env change.
const chatDefault = process.env.CHAT_MODEL_DEFAULT?.trim() || "gemma4:31b";
// Chat model allowlist: CHAT_MODELS wins, otherwise the configured text and
// vision models. Dedupe on the canonical name so `x` and `x:cloud` collapse.
const requestedChatModels = (process.env.CHAT_MODELS ?? "")
@ -135,7 +140,7 @@ export function getServerConfig(): ServerConfig {
.filter(Boolean);
const chatModels: string[] = [];
const seenModels = new Set<string>();
for (const entry of requestedChatModels.length > 0 ? requestedChatModels : [textModel, visionModel]) {
for (const entry of [chatDefault, ...(requestedChatModels.length > 0 ? requestedChatModels : [textModel, visionModel])]) {
const canonical = canonicalModelName(entry);
if (seenModels.has(canonical)) continue;
seenModels.add(canonical);