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

@ -76,6 +76,7 @@ const en: Record<string, string> = {
"common.folder": "Folder",
"common.folders": "Folders",
"common.newFolder": "New folder",
"common.foldersHint": "Create a folder to organize decks.",
"common.folderName": "Folder name",
"common.newFolderName": "New folder name",
"common.unorganized": "Unorganized",
@ -386,6 +387,7 @@ const en: Record<string, string> = {
"deck.runs.few": "{n} runs",
"deck.runs.many": "{n} runs",
"deck.notPlayed": "Not played yet",
"deck.new": "New",
"deck.best": "Best {n}%",
"deck.bestLabel": "Best",
"deck.study": "Study",
@ -951,6 +953,8 @@ const en: Record<string, string> = {
"chat.modelText": "Text",
"chat.modelVision": "Vision",
"chat.modelBoth": "Text + vision",
"chat.modelSearch": "Search models",
"chat.modelSearchEmpty": "No model matches.",
"chat.modelAvailable": "available",
"chat.modelUnavailable": "not found",
"chat.modelNote": "Photos and documents are transcribed first, so any of these works.",
@ -1013,6 +1017,8 @@ const en: Record<string, string> = {
"consent.ok": "Got it",
"settings.version": "Version",
"settings.chatNotes": "Keep notes text for chat",
"settings.reduceMotion": "Reduce motion",
"settings.reduceMotionNote": "Calms the scene and UI animations. Useful when motion feels distracting.",
"settings.chatNotesNote": "Chat answers from the notes you added (stored with each deck). Turn off to use only the generated study content.",
"server.chatModelNotAllowed": "That chat model is not on the server's allowlist.",
"server.chatNeedsQuestion": "The conversation must end with a user message.",
@ -1033,6 +1039,7 @@ const cs: Record<string, string> = {
"common.folder": "Složka",
"common.folders": "Složky",
"common.newFolder": "Nová složka",
"common.foldersHint": "Vytvořte složku pro uspořádání sad.",
"common.folderName": "Název složky",
"common.newFolderName": "Název nové složky",
"common.unorganized": "Nezařazené",
@ -1343,6 +1350,7 @@ const cs: Record<string, string> = {
"deck.runs.few": "{n} běhy",
"deck.runs.many": "{n} běhů",
"deck.notPlayed": "Zatím nehráno",
"deck.new": "Nové",
"deck.best": "Nejlepší {n} %",
"deck.bestLabel": "Nejlepší",
"deck.study": "Studovat",
@ -1908,6 +1916,8 @@ const cs: Record<string, string> = {
"chat.modelText": "Text",
"chat.modelVision": "Obrázky",
"chat.modelBoth": "Text + obrázky",
"chat.modelSearch": "Hledat modely",
"chat.modelSearchEmpty": "Žádný model neodpovídá.",
"chat.modelAvailable": "dostupný",
"chat.modelUnavailable": "nenalezen",
"chat.modelNote": "Fotky a dokumenty se nejdřív přepíšou na text, takže funguje kterýkoli z nich.",
@ -1970,6 +1980,8 @@ const cs: Record<string, string> = {
"consent.ok": "Rozumím",
"settings.version": "Verze",
"settings.chatNotes": "Uchovávat text poznámek pro chat",
"settings.reduceMotion": "Omezit pohyb",
"settings.reduceMotionNote": "Zklidní scénu i animace rozhraní. Hodí se, když je pohyb rušivý.",
"settings.chatNotesNote": "Chat odpovídá z poznámek, které jsi přidal (jsou uložené u každé sady). Vypnutím bude používat jen vygenerovaný obsah.",
"server.chatModelNotAllowed": "Tento model chatu není v povoleném seznamu serveru.",
"server.chatNeedsQuestion": "Konverzace musí končit zprávou uživatele.",