Commit graph

6 commits

Author SHA1 Message Date
8557b04621 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.
2026-10-10 16:53:55 +00:00
4369ba1a17 Native-feel pass: PWA integration, system back, offline, haptics, undo
Mobile only (<=639px / coarse pointers); desktop keeps its behaviour.

System integration
- Manifest: id, display_override, monochrome icon, Make my homework
  shortcut and a share target (text/URL via GET, files via POST).
  iOS startup images for the common iPhone sizes. Pinch-zoom is allowed
  again (double-tap zoom is disabled via touch-action instead).
- Share target: /share chooser with New deck / Make my homework; POSTed
  files wait in a 10-minute in-memory store, reach the wizard through
  sessionStorage and never touch disk.
- Web Share: Share rows for decks (JSON file where supported) and the
  homework result, with a clipboard fallback and a toast.
- Offline: a quiet pill appears and Freeze / mode cards are disabled
  with a clear reason; input is never lost (error cards keep Retry).
- State: wizard drafts (text, options, photos up to a size budget)
  survive reloads and OS kills and are cleared on success; re-tapping
  the active tab scrolls to the top; the deck page shows a skeleton
  instead of a spinner.

Navigation and gestures
- System Back closes the topmost layer first: a small history-entry
  stack (lib/use-back-close.ts) covers the drawer, Settings sheet,
  install sheet, deck actions panel and one wizard step; navigations
  are never rewound and stale entries are skipped.
- Destructive-but-reversible actions use Undo toasts: deleting a deck
  (no confirm) and removing a file in the wizard. Irreversible ones
  keep their confirm.

Input and feedback
- Every phone input got inputmode / enterkeyhint / autocomplete /
  autocapitalize / spellcheck; Enter on the last field advances the
  wizard; a clipboard-paste button appears where the API exists.
- Haptics behind a Settings toggle (on by default): tab switches,
  toggles, correct/wrong, completion; Vibration API + the iOS hidden
  switch tick, throttled and silent everywhere else.
- Wake lock during the study feed and homework guide, released on
  leave and re-acquired when the tab returns.

Verified: new native suite 35/35 (manifest/head/viewport, offline,
share target GET + POST, back-gesture layers, undo, share fallback,
tab re-tap, haptics persistence, draft restore), regression 74/74,
mobile 52/52, wizard 80/80, declutter 116/116, animation 8/8,
i18n 25/25. Lint + build clean. SW freezmo-v23.

Needs a real device (documented in the README): iOS haptic tick,
system share sheet entry, wake-lock grant, iPhone startup images,
iOS edge-swipe back, install prompts. Pull-to-refresh and the app
badge were skipped on purpose.
2026-10-10 12:16:03 +00:00
374752c59e Animation pass: one shared motion language for phones
Mobile only (<=639px / coarse pointers); desktop keeps its behaviour.

Foundation
- lib/motion.ts owns the spring presets (snappy/gentle/sheet), durations,
  easings and variants (screens, tabs, wizard steps, list stagger).
- Native-feel CSS: press feedback scales every button, card, chip, tab,
  row and summary to 0.96 with a spring-like cubic-bezier on transform
  (scoped to phones/coarse pointers), tap-highlight off, touch-action
  manipulation, chrome text non-selectable, reduced motion drops it.
- App shell fades in once on launch; the study feed fades in on phones.

Applied
- Tab bar: sliding active pill (shared layoutId) + icon bounce.
- Study Decks/Homework, language toggle, difficulty and size pickers:
  the same gliding highlight, gated so desktop keeps the old styling.
- Route content enters direction-aware (deeper = from the right, back =
  from the left, same-depth = crossfade) via RouteTransition on the
  library/create/homework content columns; library tab switches
  crossfade; wizard steps slide forward/backward (reduced motion safe).
- Library: cards fade up in a capped stagger on first load, glide with
  layout animations and shrink away on delete (AnimatePresence).
- Study feed: correct feedback pulses, wrong shakes, the progress bar
  settles with a spring curve, score and XP count up on the results.
- Settings sheet uses the sheet spring; the drawer now follows the
  finger and closes on release velocity, like the sheet.
- Generation progress: the stage message fades/slides between stages;
  the icon keeps its gentle wobble; the feed fades in on hand-off.

Skipped, on purpose: LazyMotion (the sliding highlights need layout
animations = domMax, same features the current import already has);
route exit animations (the App Router unmounts the outgoing page —
exits are handled by in-page AnimatePresence); replacing the study
feed's native scroll-snap with finger-follow drag (would fight the
platform scroller); finger-follow height animation for native <details>
expanders.

Verified: animation suite 8/8 (reduced motion + 4x-CPU-throttled trace:
no long tasks, CLS 0.0000), regression 74/74, mobile 52/52, declutter
116/116, i18n 25/25. Lint + build clean. SW freezmo-v21.
2026-10-10 11:01:29 +00:00
618ad5fb9c Add an English/Czech interface language switch
English stays the default; Czech is a full translation. Only the app chrome
is translated - notes and generated decks keep their own language, which is a
core feature, not a setting.

i18n
- lib/i18n-dict.ts: flat en/cs dictionaries (441 keys each), translate() and
  translatePlural() with real Czech plural rules (1 sada / 2-4 sady / 5+ sad).
  English is the default and the fallback for a missing key.
- lib/i18n.ts: browser store (freezmo.locale.v1), useI18n() hook, and the
  x-freezmo-locale header. The hook's t/plural are bound to the locale React
  is rendering with, so the first client render matches the server HTML and
  then re-renders in the stored language; module-level t is only for event
  handlers and non-React helpers.
- <html lang> follows the choice (inline script + setLocale).

UI
- Settings -> Language segmented control (English / Cestina), plus an EN/CS
  button on the key gate for first run.
- Every screen translated: sidebar, header, tab bar, library, deck cards,
  folder bar, create flow, all seven activities, study chrome, results,
  settings + health panel, API-key gate and help, install sheet, offline and
  the deck-not-found page.
- Plural-aware counts everywhere (decks, folders, activities, runs, photos,
  documents, pages, words, wrong attempts).

Server
- API routes and guards (generate, grade, health, http-guard, rate-limit,
  ollama error mapping) answer in the request's language, so error cards and
  the health panel never fall back to English mid-sentence.

Fixes
- A hydration mismatch when a non-default locale was stored silently dropped
  the .dark class after hydration; render-time t() is now bound to the
  hook's locale (UploadScreen.describeExtraction, ResultsScreen.answerFor,
  DeckCard's folder fallback).
- Bumped the service worker cache to freezmo-v4 for this deploy.

Tests: i18n 27/27 (gate toggle, persistence, html lang, Czech plurals,
settings switch) and a rebuilt regression suite 72/72 (English default,
library, create, all 7 activity types, results, settings, install sheet,
offline, PWA, security, Czech smoke).
2026-10-08 13:56:32 +00:00
52cce42cde Frosted ice glass restyle
Rebuilds the look around the spec: an ice scene, one shared glass
recipe, icy accents and glass on every large surface. Visual only —
layout, behaviour, copy and functionality are untouched.

Background
- .ice-scene: fixed, z-index -1, pointer-events none. Navy base
  (#070b16 to #0c1426) in dark, pale (#f4f9ff to #e6f1fb) in light,
  four drifting glacier blobs (#38bdf8/#60a5fa/#818cf8, 30-60s
  transform-only loops), five glossy ice orbs (40-220px radial
  gradients with a highlight at 30% 25% and a blue shadow) placed so the
  glass blurs them, and an SVG feTurbulence grain at 4-5%.
- prefers-reduced-motion stops the drift; phones get two blobs and two
  orbs fewer.

Glass
- One shared recipe (.glass-panel, .card, .sidebar-surface,
  .study-chrome, .tabbar, .glass, .gate-card): rgba(255,255,255,.06)
  dark / .55 light, blur(24px) saturate(140%) (16px on phones), a bright
  1px rim, an inset top highlight and a blue-tinted outer shadow.
- Nested glass (chips, inputs, secondary buttons) is more opaque and
  never adds a second blur layer. @supports fallback for no
  backdrop-filter. Radii: 24-28px cards/sheets, 14px inputs/buttons,
  pills for chips. Cards hover brighter + cyan rim + 2px lift, 180ms.

Colour and type
- Accent is glacier cyan (#38bdf8) to ice blue (#0ea5e9); progress bars
  and selected states use the gradient; the active sidebar item is a
  frosted highlight with a cyan rim.
- Primary CTA: icy gradient — white to #bfe3ff with ink text in dark,
  deep blue in light (biased so the label sits on the darker half and
  keeps 5.6:1).
- Leftover slate/black greys replaced with navy-tinted frost tokens.
- Headings are semibold with tighter tracking.

Screens
- Dashboard: the sidebar is one tall inset rounded glass panel; deck
  cards are glass.
- Gate: one centred glass card over the scene, orbs visible behind it.
- Sheets/modals: glass over the dimmed, lightly blurred backdrop.
- Study view: question card more opaque (.glass-solid); top bar and
  pager are slim glass pills.

Perf: measured on a phone viewport with 4x CPU throttling. The animated
blobs' filter: blur() was the whole cost (150ms vs 33ms per frame), so
they are soft radial gradients with no filter now; idle 60fps, ~17ms
median while scrolling.

Tests updated for the new tokens: contrast 18/18 AA, deck chrome 15/15,
key gate 49/49, install sheet 64/64, deck fixes 15/15, retry 14/14,
zoom 6/6, final ux 12/12, security 8/8, pwa 16/16.
2026-10-08 10:11:55 +00:00
3ea2cea100 Freezmo: self-hosted notes-to-activities study app
Next.js 15 app that turns photos, PDFs, DOCX/PPTX and text into a swipeable
feed of AI-generated activities (flashcards, multiple choice, true/false,
fill-in-the-blank, matching, ordering, short answer) using the user's own
Ollama models.

Highlights:
- All AI calls run in server route handlers; the browser never talks to Ollama.
- Bring-your-own-key mode (default): the key lives only in the browser and is
  sent per-request; server, local-daemon and shared-key modes also supported.
- Client-side document extraction (pdf.js, mammoth, jszip) with scanned-PDF
  page rendering for the vision model; notes stay in their original language.
- ChatGPT-style shell: collapsible sidebar / mobile drawer, neutral token-based
  light + dark themes, folder organisation, PWA (manifest, SW, offline page).
- Strict JSON validation with zod + auto-repair, rate limiting, and request
  guards (cross-origin, body-size, API-key sanitising).
- setup.sh bootstraps deps, .env.local and a production build.

Gitignore excludes .env*, the generated pdf.js worker and local scratch files.
2026-10-06 20:56:55 +00:00