Mobile declutter pass: step flows, compact cards, simplified settings

Phones only (<=639px); desktop markup is untouched.

- New deck is a 3-step flow: progress (1/3 + bar) and a fixed Back/Next
  bar above the tab bar. Step 1 = Add photos or files / Camera / Paste
  text instead (no drop zone or paste copy on touch), step 2 = folder,
  difficulty and count with defaults plus one collapsed focus/notes row,
  step 3 = one summary line (photos/docs/text · difficulty · count ·
  folder) with Freeze. Going Back keeps every input; step transitions
  respect prefers-reduced-motion.
- Make my homework is a 2-step flow sharing the same pattern; tapping a
  mode card starts it (no separate CTA on phones), with a short preview
  of the assignment on step 2. The hero is now 'Make my homework' /
  'Udělej mi úkol' everywhere ('Solve your homework' is gone) and the
  intro is one line.
- Library: stats line removed, folder row and 'Unorganized' heading
  hidden until folders exist (New folder lives in the drawer), Import is
  a small icon button beside the tabs. Deck and homework cards are
  compact on phones: title, one meta line (activities · difficulty ·
  best), a one-line description that expands on tap, the whole card
  tappable and a small Study/Open pill instead of the full-width button.
- Settings sheet on phones: no .env.local subtitle; Language stays first;
  one Connection card (status dot + 'Connected · 2 models ready', key
  field with the eye inside, Save only when edited, auto re-check on
  save, refresh icon instead of Test connection); privacy one-liner with
  a More expander (daemon tip inside); raw health rows behind one
  collapsed Details list; Remove key as a small danger button at the very
  bottom (same confirmation).
- Decorative orbs on phones moved further off the screen edges so they
  never glow under cards or primary buttons.
- Shared markup for folder/difficulty/count and mode cards keeps one
  source of state; new lib/use-phone.ts gates the layouts.

Verified: new declutter suite 116/116 (360 + 390, EN + CS, photo + PDF +
pasted text, Back keeps input, summaries, settings flow), regression
74/74, mobile 52/52, i18n 25/25. Lint + build clean. SW freezmo-v20.
This commit is contained in:
Martin Dvořák 2026-10-10 10:43:32 +00:00
commit 6564e88138
12 changed files with 1345 additions and 309 deletions

View file

@ -85,6 +85,11 @@ const en: Record<string, string> = {
"common.switchToLight": "Switch to light mode",
"common.switchToDark": "Switch to dark mode",
"common.menu": "Menu",
"common.back": "Back",
"common.next": "Next",
"common.more": "More",
"common.less": "Less",
"common.stepOf": "{n} / {total}",
/* header ---------------------------------------------------------- */
"header.openMenu": "Open menu",
@ -134,6 +139,14 @@ const en: Record<string, string> = {
"settings.installApp": "Install app",
"settings.addToHome": "Add Freezmo to your home screen",
"settings.checking": "Checking Ollama…",
"settings.connection": "Connection",
"settings.connected": "Connected",
"settings.modelsReady.one": "{n} model ready",
"settings.modelsReady.few": "{n} models ready",
"settings.modelsReady.many": "{n} models ready",
"settings.details": "Details",
"settings.privacyLine": "Your key stays in this browser.",
"settings.recheck": "Re-check connection",
/* health ---------------------------------------------------------- */
"health.ok": "Ollama is reachable and both models are available.",
@ -169,9 +182,12 @@ const en: Record<string, string> = {
/* make my homework ------------------------------------------------- */
"homework.nav": "Make my homework",
"homeworkHeroTitle": "Solve your homework",
"homework.subtitle":
"Paste the assignment, snap a photo or add a document — then choose whether I should just do it, or guide you through it.",
"homeworkHeroTitle": "Make my homework",
"homework.subtitle": "Paste it, photograph it or add a file — then pick how I should help.",
"homework.phone.assignment": "The assignment",
"homework.phone.mode": "How should I help?",
"homework.phone.preview": "Assignment preview",
"homework.phone.startHint": "Tap a card to start.",
"homework.source.title": "1. The assignment",
"homework.source.help": "Paste its text, drop photos, or add a document.",
"homework.source.textLabel": "Assignment text",
@ -235,12 +251,13 @@ const en: Record<string, string> = {
/* quick actions (library empty state + /create switcher) ----------- */
"emptyStateCreateDeck": "Create study deck",
"emptyStateHomework": "Solve homework",
"emptyStateHomework": "Make my homework",
/* library --------------------------------------------------------- */
"library.title": "Your icebox",
"library.subtitle": "Decks frozen from your notes, saved in this browser.",
"library.import": "Import JSON",
"library.importAria": "Import deck JSON",
"library.folderCreated": "Folder created — decks you freeze while it's selected land here.",
"library.imported": "Imported “{title}”.",
"library.couldNotRead": "Could not read that file.",
@ -303,8 +320,19 @@ const en: Record<string, string> = {
/* upload ---------------------------------------------------------- */
"upload.title": "Turn your notes into a study feed",
"upload.introShort": "Add your notes — I'll build the deck.",
"upload.intro":
"Snap or drop photos of your notes. Freezmo reads them with your own Ollama vision model and builds a swipeable deck of mini study games.",
"upload.addPhotosFiles": "Add photos or files",
"upload.pasteInstead": "Paste text instead",
"upload.focusExtra": "Add a focus or extra notes (optional)",
"upload.phone.notes": "Your notes",
"upload.phone.options": "Options",
"upload.phone.summary": "Ready to freeze",
"upload.summaryText": "Text",
"upload.photoCount.one": "{n} photo",
"upload.photoCount.few": "{n} photos",
"upload.photoCount.many": "{n} photos",
"upload.errNeedsKey": "API key required",
"upload.errRate": "Slow down a moment",
"upload.errAuth": "Ollama rejected your key",
@ -675,6 +703,11 @@ const cs: Record<string, string> = {
"common.switchToLight": "Přepnout na světlý režim",
"common.switchToDark": "Přepnout na tmavý režim",
"common.menu": "Menu",
"common.back": "Zpět",
"common.next": "Další",
"common.more": "Více",
"common.less": "Méně",
"common.stepOf": "{n} / {total}",
/* header ---------------------------------------------------------- */
"header.openMenu": "Otevřít menu",
@ -724,6 +757,14 @@ const cs: Record<string, string> = {
"settings.installApp": "Nainstalovat aplikaci",
"settings.addToHome": "Přidat Freezmo na domovskou obrazovku",
"settings.checking": "Kontroluji Ollamu…",
"settings.connection": "Připojení",
"settings.connected": "Připojeno",
"settings.modelsReady.one": "{n} model připraven",
"settings.modelsReady.few": "{n} modely připraveny",
"settings.modelsReady.many": "{n} modelů připraveno",
"settings.details": "Podrobnosti",
"settings.privacyLine": "Tvůj klíč zůstává v tomto prohlížeči.",
"settings.recheck": "Zkontrolovat připojení",
/* health ---------------------------------------------------------- */
"health.ok": "Ollama je dostupná a oba modely jsou k dispozici.",
@ -759,9 +800,12 @@ const cs: Record<string, string> = {
/* make my homework ------------------------------------------------- */
"homework.nav": "Udělej mi úkol",
"homeworkHeroTitle": "Vyřeš svůj úkol",
"homework.subtitle":
"Vlož zadání, vyfoť ho nebo přidej dokument — a pak si vyber, jestli to mám rovnou udělat, nebo tě tím provést.",
"homeworkHeroTitle": "Udělej mi úkol",
"homework.subtitle": "Vlož ho, vyfoť nebo přidej soubor — a vyber, jak ti mám pomoct.",
"homework.phone.assignment": "Zadání",
"homework.phone.mode": "Jak ti mám pomoct?",
"homework.phone.preview": "Náhled zadání",
"homework.phone.startHint": "Klepni na kartu a začnu.",
"homework.source.title": "1. Zadání",
"homework.source.help": "Vlož jeho text, přetáhni fotky nebo přidej dokument.",
"homework.source.textLabel": "Text zadání",
@ -825,12 +869,13 @@ const cs: Record<string, string> = {
/* quick actions (library empty state + /create switcher) ----------- */
"emptyStateCreateDeck": "Vytvořit studijní balíček",
"emptyStateHomework": "Vyřešit úkol",
"emptyStateHomework": "Udělej mi úkol",
/* library --------------------------------------------------------- */
"library.title": "Tvoje mraznička",
"library.subtitle": "Sady zmrazené z tvých poznámek, uložené v tomto prohlížeči.",
"library.import": "Importovat JSON",
"library.importAria": "Importovat JSON sady",
"library.folderCreated": "Složka vytvořena — sady, které zmrazíš, když je vybraná, přistanou tady.",
"library.imported": "Importováno „{title}“.",
"library.couldNotRead": "Tento soubor se nepodařilo přečíst.",
@ -893,8 +938,19 @@ const cs: Record<string, string> = {
/* upload ---------------------------------------------------------- */
"upload.title": "Proměň poznámky ve studijní feed",
"upload.introShort": "Přidej poznámky — sadu postavím já.",
"upload.intro":
"Vyfoť nebo přetáhni fotky svých poznámek. Freezmo je přečte tvým vlastním vizuálním modelem Ollama a postaví prolistovatelnou sadu mini her.",
"upload.addPhotosFiles": "Přidat fotky nebo soubory",
"upload.pasteInstead": "Místo toho vložit text",
"upload.focusExtra": "Přidat zaměření nebo poznámky navíc (nepovinné)",
"upload.phone.notes": "Tvoje poznámky",
"upload.phone.options": "Možnosti",
"upload.phone.summary": "Připraveno ke zmrazení",
"upload.summaryText": "Text",
"upload.photoCount.one": "{n} fotka",
"upload.photoCount.few": "{n} fotky",
"upload.photoCount.many": "{n} fotek",
"upload.errNeedsKey": "Je vyžadován API klíč",
"upload.errRate": "Zpomal na chvíli",
"upload.errAuth": "Ollama odmítla tvůj klíč",

22
lib/use-phone.ts Normal file
View file

@ -0,0 +1,22 @@
"use client";
import { useEffect, useState } from "react";
/**
* True on phone-sized viewports (≤639px), false during SSR and on desktop.
* The mobile-only step flows switch on this; everything wider keeps the
* desktop layout untouched.
*/
export function useIsPhone(): boolean {
const [phone, setPhone] = useState(false);
useEffect(() => {
const query = window.matchMedia("(max-width: 639px)");
const sync = () => setPhone(query.matches);
sync();
query.addEventListener("change", sync);
return () => query.removeEventListener("change", sync);
}, []);
return phone;
}