Navigation restructure: five-tab bottom bar, no phone drawer

Drawer removed (phones only; desktop rail untouched)
- MobileSidebar, sidebarOpen/useSidebarOpen, MenuIcon and the drawer's
  back-gesture layer are gone; the shared nav content now serves only
  the desktop rail (dead mobile branches deleted). No drawer-specific
  CSS existed; the three drawer strings (header.openMenu,
  sidebar.closeMenu, common.menu) and library.importAria are removed.
- The header drops the hamburger: logo + name on the left, the single
  theme chip on the right.

Five-tab bottom bar
- Library / Progress / New deck (prominent centre +) / Exam / Settings,
  in that order; Progress and Exam reuse the existing /stats and /exam
  screens, Settings keeps opening the dialog. Sliding pill, icon bounce,
  press feedback, re-tap-to-top and safe-area padding kept; every item
  is >=44px (72px wide at 360px).
- New short tab.* keys (EN + CS) so all five labels stay on one line at
  360px — 'Zkouškový režim' became 'Zkouška'. No ellipsis anywhere.
- The bar is mounted once in AppHeader so library, progress, exam and
  share get it; the wizards keep their own bottom action bar and the
  tab bar steps aside there, exactly as before.

Re-homed drawer destinations
- Smart Review card loses the Exam link (it is a tab now); Review due
  and Mistakes stay.
- New deck screen (and /homework) get a Study deck / Make my homework
  segmented switch; the library's homework empty-state button stays.
- The phone import icon becomes a ... button opening a sheet with
  Import JSON and New folder (back-gesture and Escape close it); folder
  chips still appear only once at least one folder exists, and folder
  creation stays in the New deck options step.

Verified: mobile suite 134 checks (no hamburger/drawer, tab order and
44px targets, label matrix at 360/390 x EN/CS x dark/light x reduced
motion, per-tab routing, deep links, Back from every tab, the ... sheet
with folder creation) plus regression 74/74, wizard 83/83, declutter
116/116, animation 8/8, native 35/35, i18n 25/25, phase1 38/38, phase2
43/43, phase3 20/20, freeze-loading 6/6. Key parity 740/740. Lint +
build clean. SW freezmo-v28.
This commit is contained in:
Martin Dvořák 2026-10-10 16:53:55 +00:00
commit 8557b04621
19 changed files with 357 additions and 325 deletions

View file

@ -54,7 +54,7 @@ no database, no data leaving your machine except the requests you send to Ollama
the safe area (the tab bar hides inside the wizard), and it is portalled to `<body>` so the
screen entrance animation can never displace it. The desktop page is unchanged.
- **Native-feel touches (phones)**: the Android Back gesture closes the topmost layer first
(sheet, drawer, actions panel, then one wizard step); deleting a deck shows an *Undo*
(sheet, actions sheet, then one wizard step); deleting a deck shows an *Undo*
toast instead of a confirm; the deck menu can share a deck as JSON through the system share
sheet (clipboard fallback); content shared from other apps opens a chooser and lands
pre-filled in either wizard; haptics (Settings → Haptics, on by default) tick on tab
@ -67,8 +67,8 @@ no database, no data leaving your machine except the requests you send to Ollama
1 day, then 3, then interval × ease; wrong ones after ten minutes. The Library shows a **Smart
review** card with the due count, a **Mistakes** count (activities whose latest answer was wrong —
they clear themselves once answered correctly) and today's **daily goal** progress. A **Progress**
screen (drawer) has the day streak, a five-week calendar, accuracy per subject and per deck and
the weakest topics. **Exam mode** (drawer) takes one or more decks, a question count and an
tab has the day streak, a five-week calendar, accuracy per subject and per deck and
the weakest topics. **Exam mode** (its own tab) takes one or more decks, a question count and an
optional timer, hides hints and shows a per-deck/per-topic breakdown at the end. **Daily
reminders** appear in Settings only when the server has VAPID keys configured; subscriptions are
stored as endpoint + keys only, and `node scripts/send-reminders.mjs` (cron/systemd timer) does
@ -172,9 +172,9 @@ no database, no data leaving your machine except the requests you send to Ollama
- **Two pools, never mixed**: a **Study Decks** tab (the folder + card library) and a **Homework**
tab with saved solutions and step-by-step guides — each with its own empty state and call to
action, and the active tab lives in the URL (`/?tab=homework`)
- **Phone library, decluttered**: no stats line, the folder row and “Unorganized” heading only
appear once folders exist (the drawer has *New folder*), and Import is a small icon button in
the tab row. Deck cards shrink to title + one meta line (activities · difficulty · best
- **Phone library, decluttered**: no stats line, the folder chips and “Unorganized” heading only
appear once folders exist, and a **⋯** button next to the pool switcher opens a small sheet with
**Import JSON** and **New folder**. Deck cards shrink to title + one meta line (activities · difficulty · best
score) + a one-line description that expands on tap, with the whole card tappable and a small
**Study** pill; homework cards match.
- Decks saved locally with title, subject, folder, activity count, date, play count and best score
@ -188,10 +188,14 @@ no database, no data leaving your machine except the requests you send to Ollama
**Design**
- **ChatGPT-style shell**: a persistent left **sidebar** (brand, a quiet "New deck" row, a *Folders*
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
- **ChatGPT-style shell on desktop, five tabs on phones**: desktop keeps the persistent left
**sidebar** (brand, a quiet "New deck" row, a *Folders* list with per-folder counts,
Settings/Dark mode pinned to the bottom), which **collapses** to a 68px icon rail (remembered in
`localStorage`). Phones have no drawer and no hamburger: a frosted header (logo + theme chip) sits
above an iOS-style bottom tab bar — **Library · Progress · New deck · Exam · Settings** — with the
New deck button as the prominent centre action and Settings opening the sheet. The wizards keep
their own bottom action bar instead of the tab bar, and the in-page segmented switch moves between
the deck and homework wizards.
- **Frosted ice glass, both themes**: light page `#eef5fd` (pale ice) with white glass panels;
dark page `#0a1120` with `rgba(255,255,255,.06)` glass. Every large surface shares one recipe —
translucent tint, 24px `backdrop-blur` (16px on phones), a bright 1px rim, an inner top highlight
@ -209,11 +213,11 @@ no database, no data leaving your machine except the requests you send to Ollama
- Micro-interactions: 160–200ms colour/transform transitions, `active:scale-[0.985]`, skeleton
shimmer, hairline hover washes on every row and card
- **Motion (phones)**: one shared language (`lib/motion.ts`) — every button, card, chip, tab and
drawer row scales to 0.96 on press with a spring release; the tab bar, segmented controls and
nav row scales to 0.96 on press with a spring release; the tab bar, segmented controls and
difficulty/size pickers glide their highlight with a shared `layoutId`; wizard steps slide
forward and backward and page content enters direction-aware; the Settings sheet and drawer
follow the finger and settle on release velocity; library cards fade up in a capped stagger and
animate out on delete; score and XP count up; correct answers pulse, wrong ones shake;
forward and backward and page content enters direction-aware; the Settings sheet and the library
actions sheet follow the finger and settle on release velocity; library cards fade up in a capped
stagger and animate out on delete; score and XP count up; correct answers pulse, wrong ones shake;
`prefers-reduced-motion` collapses everything to short fades. Only transform and opacity
animate — never blur.
- **iOS-flavoured PWA**: standalone mode, safe-area insets, bottom tab bar, sheet-style dialogs with
@ -537,8 +541,8 @@ on Android Chrome a captured `beforeinstallprompt` turns into a real **Install**
can be reopened any time from **Settings → Install app** (mobile only).
It launches full-screen in standalone mode (no browser chrome), respects the notch/home-indicator
safe areas, and has an iOS-style bottom tab bar (Library · New deck · Settings) with sheet-style
dialogs. Android Chrome also offers "Install app" from the ⋮ menu; Freezmo works offline for the
safe areas, and has an iOS-style bottom tab bar (Library · Progress · New deck · Exam · Settings)
with sheet-style dialogs. Android Chrome also offers "Install app" from the ⋮ menu; Freezmo works offline for the
parts that don't need a model.
What's included:
@ -1018,6 +1022,22 @@ Things I chose while building, with the reasoning:
phone and desktop check flows, the shorter follow-up with restore, the guide's explain action,
and the deck hand-off).
43. **Phones navigate with five tabs, not a drawer.** The drawer, its hamburger, its
drag-to-close gesture and its back-gesture layer are gone (`MobileSidebar`, `sidebarOpen`,
`MenuIcon` and the three drawer strings deleted); the desktop rail is untouched and is now the
only consumer of the shared nav content. The bottom bar is **Library · Progress · New deck (the
prominent centre +) · Exam · Settings**, keeping the sliding pill, press feedback, re-tap-to-top
and safe-area padding. Tab labels use dedicated short `tab.*` keys because the old names
(“Zkouškový režim”) can't fit five-up at 360px; the matrix in the mobile suite checks every
label against clipping at 360/390, EN/CS, dark/light and with reduced motion on and off. The
wizards keep their own bottom action bar, so the tab bar steps aside there, exactly as before.
Drawer-only destinations were re-homed: Progress and Exam became tabs, the New deck screen got
a **Study deck ⇄ Make my homework** segmented switch (also on `/homework`), and the library's
phone row gained a **⋯** sheet with Import JSON + New folder (folder chips still appear only
once a folder exists). Verified by the mobile suite (134 checks: no hamburger/drawer, tab order,
the label matrix, per-tab routing, deep links, Back from every tab and the ⋯ sheet) plus the
other ten standing suites.
36. **The mobile declutter pass reshapes, it doesn't fork.** Phones (≤639px) get a 3-step deck
flow and a 2-step homework flow that share the exact state and handlers with the desktop
cards (one `UploadScreen` / one `HomeworkInput`, two layouts switched by `useIsPhone()`);
@ -1046,7 +1066,7 @@ Things I chose while building, with the reasoning:
transform on an ancestor becomes the containing block for `position: fixed`, so the bar
used to paint inside the content and jump to the viewport bottom a second later. It now
only fades in, pinned to the bottom from its first frame — verified by sampling its
position across the entrance (5 frames, 60–450ms) from the tab bar, the drawer and the
position across the entrance (5 frames, 60–450ms) from the tab bar, the mode switch and the
empty state, at 360/390, both themes, both languages and with reduced motion.
39. **Native feel is layered onto the platform, never around it.** The Back gesture closes the
@ -1059,7 +1079,7 @@ Things I chose while building, with the reasoning:
Wizard drafts and the last URL are per-browser; the share target hands POSTed files to the
client through a ten-minute in-memory store so nothing touches disk. Verified by the native
suite (35 checks: manifest/head/viewport, offline banner + disabled generate, share target GET
and POST, back-gesture closing for drawer/sheet/wizard, undo toast, share fallback, tab
and POST, back-gesture closing for sheet/wizard, undo toast, share fallback, tab
re-tap, haptics persistence, draft restore) plus the six standing suites.
**Needs a real device to verify** (emulation can't do these honestly): the iOS hidden-switch