Commit graph

27 commits

Author SHA1 Message Date
d559865a09 Check-my-work attachments, smart review follows deletions, GPL-3.0-only
- "Check my work" now takes photos and documents of the attempt: documents
  are read in the browser into text, photos go to /api/homework as
  solutionImages and are transcribed by the vision model before the check
  ("Reading your solution..."). The finished attempt comes back in
  meta.solutionText and is stored as the record's attempt, so "Redo"
  re-checks the same work without keeping the files.
- Deleting a deck now drops its due schedules and mistakes from the library's
  smart review immediately (the counts resolve against the live deck list);
  Undo brings them back, because the learning data itself is never erased.
- The phone override for the glacier blobs was still at 0.1/0.2: it is now a
  quarter of the original value there too (0.025/0.05), matching the desktop.
- License: GPL-3.0-only (LICENSE, package.json, README section).

Tests: phase-3 photos-only API + desktop attachment checks (28/28), review
suite deck-delete checks (18/18), i18n/mobile/phase-2 green. README:
decisions 57-58. SW -> freezmo-v45.
2026-10-12 16:04:53 +00:00
9d204b85be One-pill study chrome, toggling chat pickers, readable homework steps
UI batch from feedback:

- The study/review/exam/homework chrome is now a single 58px pill: round
  close button, title + position over the progress bar, chips (or the exam
  timer) at the far end — everything on the centre line, so nothing crowds
  the rounded corners any more. Homework guide gets the same recipe.
- The immersive feeds carry z-[45] so they paint over the app chrome (on
  /exam the header used to cover the top of the chrome), and the page chrome
  behind an open overlay (.app-header, .segmented, .tabbar) is hidden while
  the transparent overlay is up, so it can no longer ghost through the glass.
  The guide's disclaimer moved inside the overlay with it.
- Chat: the context and model chips toggle their own popover (pressed state,
  aria-expanded), opening one closes the other, a press outside closes it —
  no more being forced to pick something to get out.
- Homework: the model's plain-text step explanations/hints/verdicts keep
  their line breaks (whitespace-pre-wrap) — a step used to collapse into one
  run-on paragraph with the whole solution inside it; the guide prompt now
  says the explanation is coaching, 2-4 sentences.
- The four glacier blobs are at 25% of their old opacity (0.045 light,
  0.1 dark).

Tests: phase-2 chrome geometry + exam layering checks (62), chat desktop
picker checks (101), the standing suites all green. README: decisions 54-56,
decision 23 updated. SW -> freezmo-v44.
2026-10-12 14:21:11 +00:00
6b6a6ea158 Ice scene: the glossy orbs become rising bubbles, ~38% more transparent
- Orbs rise from below the bottom edge to above the top edge, each with
  its own 36-68s pass (slower on phones), a negative delay (scene already
  populated on load), own horizontal position, a gentle sway and a slight
  scale drift; opacity fades in/out at the edges so the loop never pops.
- Only transform + opacity animate, each orb on its own composited layer
  (will-change: transform); no filter/backdrop-filter, nothing animates
  size/offsets/shadow. The glacier blobs are untouched.
- Opacity lowered ~38% in both themes: 0.55->0.34 light, 0.8->0.5 dark,
  phone overrides 0.32->0.2 / 0.42->0.26.
- prefers-reduced-motion: rise removed, orbs return to the old static
  placements (a paused-from-start CSS animation is not applied in Chrome
  and would park them below the fold); tab hidden: a visibility listener
  parks the whole scene (.ice-paused), blob drift included.
- Measured with a CDP perf trace on a 4x throttled phone profile: p95
  33.4ms, 0 frames over 34ms, no long tasks - identical to the pre-change
  baseline; ~0.6% extra main-thread CPU. Desktop stays compositor-bound
  from the pre-existing blob drift, as before. tests/bubble-perf.mjs
  keeps it repeatable.
- New bubbles suite (34 checks) in the repo harness; the axe contrast
  matrix is now 16 runs (360/390/1280 x dark/light x EN/CS) and passes.
- SW cache -> freezmo-v31; README decision 46.

Full suite: 11/11 suites, 424 checks.
2026-10-11 15:50:17 +00:00
e66141e9a0 Wizard polish: step headlines, gated Next, real summary, portal action bar
Mobile only (<=639px); desktop markup untouched.

Bug fix: the Back/Next bar was fixed-positioned inside the screen
entrance animation's transformed wrapper, so it painted under the content
and jumped to the viewport bottom about a second in. It is now portalled
to <body> and only fades in, pinned to the bottom from the first frame.
The other fixed/sticky elements (tab bar, drawer, sheets, install sheet)
were checked and live outside the animated wrappers.

- The step name is now the large headline with a one-line description;
  the repeated page title is dropped on phones. Thin progress bar + 1/3.
- Step 1: formats/limits hint, one big dashed Add photos or files area
  (plus icon), Camera and Paste text instead as secondaries, and a
  'nothing added yet' placeholder that becomes the thumbnail/chip list.
- Next is disabled until a photo, file or pasted text exists; step 3 can
  never be reached empty.
- Step 1 shows Cancel (back to the Library) instead of a dead Back.
- Step 3 is a real summary: thumbnail/chip strip plus Files, Difficulty,
  Activities and Folder rows, each with an Edit link to its step; Freeze
  is the single primary action.
- Step 2 got a visible Difficulty label and a helper line under every
  group, defaults still preselected.
- Bottom chrome: flat full-width Back/Next bar above the safe area, and
  the tab bar hides inside both wizards. The lower-right orb is hidden on
  phones so it can never glow behind the bar.
- Make my homework matches: headline, hint, big add area, placeholder,
  Cancel, gated Next, flat bar.

Verified: new wizard suite 80/80 (bar position sampled at 5 frames during
the entrance from tab bar/drawer/empty state; 360 + 390, dark + light,
EN + CS, reduced motion on/off; gating, summary Edit links, hidden tab
bar and orb), declutter 116/116, regression 74/74, mobile 52/52,
animation 8/8, i18n 25/25. Lint + build clean. SW freezmo-v22.
2026-10-10 11:35:45 +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
6564e88138 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.
2026-10-10 10:43:32 +00:00
a8c752828e Drop the sticky bar: the Freeze my deck CTA now sits in the flow like the homework one
The last visible difference between the two CTAs was the container: the
deck button lived in a full-bleed sticky strip (translucent background,
hairline top border, its own padding), which also tinted the disabled chip
differently. The homework button sits plainly in the page flow.

- The CTA block is now a plain space-y-2 stack at the end of the form:
  full-width button + centred helper line, exactly like /homework.
- /create's page padding switches from the sticky-era .pb-cta (9rem, phone
  override 4.6rem) to .pb-tabbar, and lg:pb-20 to lg:pb-16 — identical to
  the homework page. The now-unused .pb-cta rules are gone.
- Result: same button width (358px @390 / 688px @1280), height, radius,
  helper placement and trailing space on both pages; neither has a sticky
  ancestor any more.

Service worker: freezmo-v16.

Verified: CTA-flow suite 26/26 (below-the-fold at rest, no sticky ancestor,
full-width, helper centred underneath, width/height/radius/trailing-space
parity at 390px and 1280px), desktop regression 74/74, mobile 51/51,
i18n 27/27. Lint + build clean.
2026-10-09 10:52:23 +00:00
9d6ef90f02 Keep the library header steady across tabs; simplify the empty states
Header & action bar
- Import JSON no longer disappears on the Homework tab: it sits directly
  under the "Your icebox" header/subtitle, above the tab switcher, on every
  tab — so switching pools no longer shifts the layout or hides actions. It
  is one secondary button at all sizes now, not a quiet link when empty.
- The decks-only "Homework Solver" header action is gone; the homework tab's
  empty-state CTA and the drawer cover starting a new solve.

Empty states
- Dropped the "Using your own Ollama models — check the connection in
  Settings." line (and its two dictionary keys) everywhere.
- Both tabs share one structure: ice icon -> heading -> short description ->
  primary CTA button, starting at the same height:
    Study Decks: "No decks yet" -> "Freeze a page of notes into flashcards
    and quizzes." -> "Create study deck"
    Homework: "No homework yet" -> "Solved assignments and step-by-step
    guides show up here." -> "Solve homework"
- CTA labels read as actions on the standardized nouns; removed the now
  unused actionStudyDeck/actionHomework keys.

Tabs
- Link segments are 44px tall at every breakpoint (min-height in the base
  .segmented > a rule).

Service worker: freezmo-v11.

Verified: library-consistency suite 49/49 (Import visible with a fixed
position on both tabs, no Ollama text, empty states at the same y, primary
ice CTA buttons >=44px, deep links, deletion, redirects, desktop, 360px, axe
en+cs light+dark), mobile 51/51, i18n 27/27, desktop regression 74/74.
Lint + build clean.
2026-10-08 21:50:18 +00:00
8c44f47881 Separate Study Decks and Homework in the library; drop the /create mode switch
Library ("Your icebox")
- Two tabs, never mixed: Study Decks (the folder + card library) and Homework
  (saved solutions and step-by-step guides). The active tab lives in the URL
  (/?tab=homework), so the sections are linkable and survive a reload.
- Decks tab keeps Import, the folder bar, stats and its own empty state — now
  a single decks-only CTA.
- Homework tab lists saved results as cards (mode icon, date, step count,
  preview) with Open and delete-with-confirm; its empty state has its own CTA
  ("Homework Solver") that opens the solver directly. Opening a card
  deep-links to /homework?open=<id>, which the screen opens on arrival.
- .segmented now also styles link segments (same control as settings).

/create
- The redundant "Study Deck | Homework Solver" toggle is gone: /create is the
  deck uploader only, and the bottom bar's + goes straight to it. Homework has
  its own screen, reached from the library tab, the drawer and CTAs.

Copy
- New keys (en + cs): tabDecks, tabHomework, homeworkHeroTitle, plus homework
  empty-state/open/step-count strings. The homework hero is now "Solve your
  homework" / "Vyřeš svůj úkol" — no study-deck text in the homework flow.
- Removed the now-unused ModeSwitch component and homework.title key.

Service worker: freezmo-v10.

Verified: library-tabs suite 37/37 (tab switching, context empty states,
deep-link opening, deletion, no switch on /create, heroes, redirects, desktop
grid, 360px + 44px targets, axe contrast en+cs light+dark), mobile 49/49,
i18n 27/27, desktop regression 74/74. Lint + build clean.
2026-10-08 21:26:08 +00:00
4ac2f33e3f Fix the dead space between /create's sticky CTA and the phone tab bar
On phones the "Freeze my deck" strip floats one tab-bar height (4.6rem)
above the viewport bottom, but the page reserved env + 9rem of trailing
room — so at the end of the scroll the strip docked ~70px higher than where
it had been floating, leaving a visible cavity above the tab bar.

Phones now get exactly the sticky offset (env + 4.6rem) as trailing space:
the strip settles where it was floating and the bar is never double-counted.
Tablet/desktop (>=640px) keep the roomier 9rem — unchanged.

Verified at 360/390px: gap CTA-to-bar 70.3px -> 0.3px, ~70px less dead
scroll; tablet 800px and desktop 1280px measurements identical to before.
Service worker: freezmo-v7.
2026-10-08 19:06:36 +00:00
ef2a6a3589 Add “Make my homework”, and finish the mobile polish pass
Homework (/homework — entry points in the drawer and on the create screen)

- Assignment intake reuses the study pipeline: pasted text, photos
  (camera/picker, compressed in the browser) and documents (PDF/DOCX/PPTX/
  ODT/text) through lib/image + lib/documents, the same vision step and the
  same language guard. Raw files never leave the device — only text and JPEG
  data URLs reach the server.
- Two large choice cards: “Just make it” returns the finished homework
  (answer only, plain text, copy + make it again); “Explain how to make this
  homework” builds an interactive guide.
- The guide is one step per screen in the study-feed snap layout:
  explanation, three progressive hints (nudge -> hint -> the answer) and an
  optional checkpoint rendered by the existing activity components (graded
  through /api/grade). The last screen assembles the solution from the step
  answers, with a copy button. Checkpoints carry xp 0 so the guide stays free
  of XP/streak chrome.
- Results live in freezmo.homework.v1 with a small history (open/delete);
  “Make it again” re-runs from the stored assignment text (the vision
  transcription when photos were used) — no original files are stored.
- The answer and the guide are written in the assignment's language, never
  translated into the UI language.

Server

- POST /api/homework: NDJSON stage events like /api/generate, the same
  http-guard, auth checked before parsing, and its own RATE_LIMIT_HOMEWORK
  (documented in .env.example + the README table).
- New prompts in lib/prompts.ts and zod schemas in lib/schemas.ts with the
  same single repair retry; TEXT_MODEL for content, VISION_MODEL only for
  photos. New shared client pieces: useNotesIntake, lib/homework store,
  lib/clipboard, and a shared NDJSON reader in lib/client-api.

Also finishing the mobile polish pass from the previous request

- Phone ice scene: the top-left orb moved behind the glass header (it sat
  behind the page heading/subtitle). The settings sheet's scroll cue is now a
  bare chevron inside a content fade (mask) instead of a chip over the status
  banner, and the API key card's note/disclosure/actions are tightened.
- theme-color now follows the in-app theme (SSR metas still fall back to the
  OS preference); apple-mobile-web-app-status-bar-style stays “default”
  because iOS fixes the status-bar text colour at launch.

Verified: 78/78 homework checks (text/photo/PDF, both modes, EN+CS, dark+light,
360/390px, copy, history persistence, storage privacy, API language, axe), plus
the existing suites re-run (mobile 47/47, pixel contrast 0 below AA, i18n 27/27,
desktop regression 72/72). Lint + build clean; service worker freezmo-v6.
2026-10-08 18:30:33 +00:00
9fb728d735 Mobile UI pass: sheet swipe, ice-scene layering, contrast, touch targets
Phones (<=639px) only for layout/gestures; the theme-level fixes apply
everywhere, as they are token/design changes.

Settings sheet
- Swipe-to-dismiss reusing the InstallSheet gesture: the sheet follows the
  finger, closes past ~90px or on a fast flick, snaps back otherwise. Drag
  starts only from the handle/header and only while the body is scrolled to
  the top. Backdrop tap still closes.
- Fixed header + scrollable body: pb-safe + spacer, so nothing is clipped;
  a scroll cue (chevron) fades in while more content is below and out at
  the end.
- Show/Save stack under the field at 44px on phones; close, Test connection
  and Remove key are 44px too. Remove key is now a rose destructive action
  with a confirmation that the full-screen key setup comes back.
- API key note split into a short privacy line plus a collapsible
  'Using the local daemon?' tip (OLLAMA_AUTH=local / .env.local / signin).
- Fixed the missing space in the English tip ('ollama signin instead.').
- Health: kept the auto-check and banner; added a loading row and a
  'Checked just now' / 'Checked at HH:MM' freshness label (also for errors).

Ice scene / readability
- Verified the orbs were never above content (fixed z-index:-1 layer,
  pointer-events:none); they were just shining through the glass. Phones get
  lower blob/orb opacity and orbs tucked to the edges; the bottom-right orb
  is moved out from under the tab bar and the tab bar paints a more opaque
  surface so its labels hold contrast.

Contrast / themes
- Dark --text-subtle raised to #9db1d0 (8.6:1 page, 7.2:1 glass); helper
  text now uses the tokens.
- Disabled buttons are a visible muted chip (.btn-disabled) instead of a
  40%-opacity ghost.
- One ice primary gradient in both themes; danger/success now actually show
  their colour (bg-none strips the primary background-image that used to
  paint over !bg-rose-600).
- Light mode: cool card rims instead of white-on-white, stronger shadow.

Redundancy / touch
- Removed only the header gear; the hamburger drawer (folders, Settings,
  theme) and the tab-bar Settings stay.
- Theme toggle is a bordered chip with an action aria-label (drawer row too).
- Empty library hides the folder pills, demotes Import to a quiet link and
  keeps a single primary CTA. 44px targets for nav rows, segmented control
  and folder pills on phones.
- Fixed a pre-existing 16px horizontal overflow (header -mx-4 vs a
  padding-less parent) and let info values wrap instead of truncating.

Verified: mobile suite 47/47 (EN+CS, 360/390px), pixel-sampled contrast
audit 0 runs below WCAG AA (8 runs, both themes/locales; axe marks the glass
'incomplete'), i18n 27/27, desktop regression 72/72, lint + build clean.
Service worker cache bumped to freezmo-v5.
2026-10-08 17:37:51 +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
586e4b5a40 Revert the last two glass iterations
The colourful blob wash (92fe685) and the flat "straight ice" pass
(5d05592) both missed. Reverting both to get back to the icon-driven
navy palette before rebuilding the look as a proper frosted ice glass
design.
2026-10-08 09:27:42 +00:00
5d0559212b Straight ice: flat backdrop, crisp translucent panels, barely any gradients
The colourful multi-blob wash was too much and still didn't read as
frosted glass. Replaced with a cleaner material approach:

- Backdrop is now flat with a single faint top highlight (the only
  gradient left in it); the animated blobs and the four colour radials
  are gone.
- Panels are crisper and more translucent: cards 62%, panels 66%,
  sidebar 64% (now flat, no vertical gradient), study chrome 68%, tab
  bar 70% — with a bright 1px edge (--glass-edge), a light-from-above
  sheen (--glass-sheen) and the inset top highlight.
- Light mode is lighter: page #eef3fa pale ice, sidebar and cards
  frosted white; dark mode is a flatter, slightly lighter navy
  (#121a2c page, #1a2337 surfaces).
- Key-gate glows toned right down to match.

Contrast re-audited against the effective frosted backgrounds: 21/21 AA.
Suites: deck chrome 15/15, deck fixes 15/15, final ux 12/12, key gate
49/49, install sheet 64/64, retry 14/14, zoom 6/6, security 8/8, pwa
16/16.
2026-10-08 09:20:22 +00:00
92fe68524b Make the frosted glass actually visible
The wash was far too faint for backdrop-blur to register, so the glass
read as flat navy. Now:

- The backdrop is a real colour field: four saturated blue/periwinkle/
  cyan radial blobs over the navy base, plus two slow animated blobs at
  higher opacity and less pre-blur, placed behind the content column.
- Surfaces are properly translucent instead of near-opaque: cards 62%,
  glass panels 68%, sidebar 68%, list groups 70%, study chrome 72%,
  tab bar 74%, mobile header 62%.
- Glass gets a specular top edge (inset 0 1px 0 --glass-top) so the
  surface catches light like real glass.
- Light --text-subtle darkened to #55647f so text still clears AA on the
  tinted glass (4.84:1 on the most saturated sidebar area).

Audit updated to the tinted backgrounds: contrast 21/21. Suites: deck
chrome 15/15, deck fixes 15/15, final ux 12/12, key gate 49/49, install
sheet 64/64, retry 14/14, zoom 6/6, security 8/8, pwa 16/16.
2026-10-08 08:01:14 +00:00
979f3ddc39 Navy theme from the app icon, frosted glass, and the new mark in-app
The icon's artwork is now the source of truth for the palette:

- frost-* is a navy-tinted neutral scale and accent-* is the icon's
  snowflake blue; light page #f4f7fd / sidebar #e9eff9 / border
  #dce4f1, dark page #0e1424 / sidebar #0a0f1c / surfaces #171f33.
- Surfaces are frosted glass: .card, .glass-panel, .list-group and
  .sidebar-surface are translucent with backdrop-blur/saturate, and the
  page wrapper no longer paints an opaque --page, so the fixed navy/blue
  backdrop reads through the sidebar, cards, chrome and sheets.
- The primary CTA is the icon's blue (#1d61ab in light, #59a8f7 in dark
  with ink text).
- The gate stage, fields and glows are navy/blue; viewport theme-color
  and the manifest background/theme colour are #0e1424.
- The old monochrome snowflake tile is gone from the brand: sidebar,
  header, key gate and empty state now show the real app icon (the
  install sheet already did). Decorative snowflakes stay.
- --surface-strong is finally defined (it was referenced but never
  declared, so those inputs had no fill).
- Icon URLs carry ?v=2 so browser favicon caches refetch.

Updated the contrast audit (21 pairs) and the deck-chrome token
assertions to the new theme. Verified: contrast 21/21, deck chrome
15/15, key gate 49/49, install sheet 64/64, final ux 12/12, retry
14/14, zoom 6/6, security 8/8, pwa 16/16, deck fixes 15/15.
2026-10-08 06:15:51 +00:00
3aae6f3199 Desktop library pass: alignment, card weight, sidebar and theme
Layout (desktop / lg+ only):
- 40px top padding above the page title.
- Deck grid uses minmax(320px, 1fr), so two cards fill the content width
  and share a right edge with the header (same max-width container).
- Group headings only render once real folders exist; with none, a single
  quiet "All decks" label replaces "Unorganized".
- Import JSON becomes a plain text button (mobile keeps the pill).

Deck cards (desktop):
- The whole card is a link; Study is a compact secondary button instead
  of a full-width solid pill.
- The ... control is a 32px ghost button with a real ellipsis icon,
  aligned to the title's centre.
- Meta line always shows the same fields (activities · runs / Not played
  yet); the score is a separate quiet "Best 53%" stat.
- 12px rhythm between title/meta/tags/summary, 16px before the actions.
- Hover: lighter surface, stronger border, soft shadow and a 2px lift in
  light mode (pointer devices only, so touch has no sticky hover).

Sidebar (desktop rail):
- All decks + Unorganized are views above the FOLDERS label; only
  user-created folders sit under it.
- Count only on All decks. The mobile drawer is unchanged.

Theme:
- Dark: sidebar is a step darker (#181818 to #141414 vs #212121 page).
- Light: cards get a slightly stronger border (#e2e2e2) and a soft
  shadow; the sidebar divider and edge shadow are stronger too.
- Medium difficulty darkened to #7a5300 (6.2:1 on the light chip).

Mobile is untouched: layout changes are behind lg: (or the desktop prop);
theme tweaks are shared tokens.

Verified: key-gate 49/49, install sheet 64/64, retry 14/14, zoom 6/6,
deck chrome 15/15, deck fixes 15/15, final ux 12/12, contrast 20/20,
security 8/8, pwa 16/16.
2026-10-07 22:37:29 +00:00
7ab04e9f62 Screens that already fit no longer scroll or rubber-band
- API-key gate is now a fixed, out-of-flow overlay (inset-0, overflow
  hidden, overscroll-none) with its own internal scroller, so the document
  has no scrollable height on phones. It can't be panned or bounced.
- Home/create mains are flex columns with min-h-dvh and a flex-1 content
  area, and the empty state's mobile padding was trimmed, so a short/empty
  library is exactly viewport height instead of overflowing by ~76px.
  Long pages (the create form, decks) still scroll normally.
- html/body get overscroll-behavior: none to stop pull-to-refresh and
  rubber-banding on fitting screens.

Verified with a scroll probe (gate and empty library: scrollHeight ==
viewport; create still scrolls) plus zoom 6/6, install sheet 64/64,
pwa 16/16, final-ux 12/12, retry 14/14, security 8/8, contrast 20/20.
2026-10-07 12:37:46 +00:00
080306f111 Stop phones zooming when a field is focused
- Viewport: maximum-scale=1 and user-scalable=no (app-like, no pinch or
  double-tap zoom on phones).
- Mobile-only rule keeps every input/textarea/select at 16px so iOS
  Safari's focus auto-zoom never triggers (it ignores user-scalable=no for
  that). Deliberately unlayered so it beats the per-component Tailwind
  sizes; desktop keeps the designed 12-14px fields.

New zoom suite (6 checks): viewport meta, gate input, settings key input,
notes textarea and every study-feed field at >= 16px on a phone, desktop
sizes unchanged. Install sheet 64/64, pwa 16/16, final-ux 12/12,
security 8/8, contrast 20/20.
2026-10-07 12:14:10 +00:00
9e4ecd3ee0 Deck chrome: neutral surfaces, borders and text (no navy leftovers)
- The study feed's progress header and bottom pager no longer use the old
  navy glass: new .study-chrome class uses the neutral surface token
  (same gray as the cards, #2f2f2f in dark), the neutral border
  (8% white in dark, #e5e5e5 in light) and a neutral shadow.
- Close tile, streak pill and XP pill now use --surface-sunken /
  --text-muted, matching the app's other buttons and chips; the close
  glyph is the shared XIcon.
- Bottom pager: counter in --text, arrows in --text-muted.
- .glass (mobile header) no longer has the hardcoded #101720 navy; it is
  now color-mix of the surface token. Removed the unused --color-night-*
  tokens and the last night- usage (create screen sticky bar).
- Hidden the 'Swipe up' hint on desktop, where it collided with the
  bottom pager.

New deck-chrome suite: 15/15 (computed surfaces/borders/text in both
themes, no navy); pwa 16/16, final-ux 12/12, security 8/8, contrast 20/20.
2026-10-07 09:06:18 +00:00
f98795436d Fix the iOS install sheet: exact Safari flow, chips, iOS blue, layout
Steps
- iOS now uses the exact flow: Safari users get 4 steps (⋯ → Share/More →
  Add to Home Screen → Add); non-Safari (CriOS/FxiOS/EdgiOS/in-app) get a
  first 'Open this page in Safari' step and 5 total. The vague 'in some
  browsers it's under…' wording is gone.

Chips and icons
- The ⋯ chip renders a single three-dot icon (icon-only pill) instead of
  the icon plus an '⋯' character (which showed two sets of dots).
- The More chip uses a chevron-down icon instead of the rounded-square.
- The accent pill uses real iOS blue: #0A84FF dark / #007AFF light, still
  the only coloured element.
- Steps and chips share one 16px type scale; chip labels are medium weight.

Typography and layout
- Numbers are small muted circles in a fixed 24px column, aligned with the
  first text line via matching line-height.
- Removed trailing periods after chips; steps tightened.
- Bottom padding is calc(24px + env(safe-area-inset-bottom)) and a
  secondary 'Maybe later' button sits below the steps.
- max-height 90dvh with inner scrolling; 36×4px low-contrast handle with
  real swipe-down-to-close (momentum off, spring back).
- App card shows the hostname only (no port), truncated.

Themes
- Dark (default): layered surfaces #1c1c1e sheet / #2c2c2e card / #3a3a3c
  chips with 8% white borders; subtle logo tile instead of solid white;
  backdrop is 50% black with 4px blur.
- Light only when the app theme was explicitly set to light, ignoring the
  system preference; borders #e0e0e0, chip fills #f2f2f2.

Also fixes a crash the test pressure exposed: a 429 (rate-limited)
/api/health response crashed Settings at health.vision.available.
fetchHealth now rejects non-OK/malformed responses so the dialog shows its
error banner instead of the app error screen.

Install-sheet suite now 64 checks; pwa/final-ux/security/contrast green.
2026-10-07 08:49:32 +00:00
969f7a82a0 Gate polish round two: glows, disabled state, help link, modal
- Disabled Continue is now the primary style at 45% opacity with
  cursor: not-allowed; full opacity while validating and once text is
  entered.
- Move "How do I get a key?" below the Continue button (small ? icon,
  muted, underline on hover) and tighten input+button spacing.
- Shorten the subtitle to "One step to freeze your first deck." and
  balance it.
- Replace the faint gradient with two real blurred blobs (top-right and
  bottom-left, 120px blur, pointer-events: none, behind the card).
- Dark: page #0f0f0f, card #1a1a1a, 8% white border.
- Phone: 24px heading, 15px subtitle, wider card padding, 40px corner
  buttons with safe-area-aware offsets, robust 100dvh centering.
- Modal: 480px wide with text-pretty steps, stronger tinted free-tier
  callout, handle removed from the phone sheet.
- Tests: gate suite now 49 checks incl. disabled opacity, glow pseudo,
  card separation and help-link placement; new state screenshots for
  invalid/offline/validating in both themes.
2026-10-07 06:09:13 +00:00
fee59b9577 Polish the API-key gate
- Add a theme toggle to the gate (sun/moon ghost button next to the help
  button); it switches instantly and persists to localStorage.
- Strengthen the corner glows so the stage reads atmospheric in both themes
  (bigger, slightly stronger radial gradients behind the card).
- Center the logo, heading, subtitle and footnote inside the card.
- Dark mode: replace the solid white logo tile with a subtle raised tile
  (thin border, light snowflake); make the card only slightly lighter than
  the near-black page with a low-opacity border and soft shadow.
- Disabled Continue keeps the primary fill at ~45% opacity with a crisp
  label instead of a faded muddy one; fully solid once text is entered.
- Bigger 36px corner buttons and a visible "How do I get a key?" text link
  under the input that opens the same modal.
- Shorten and balance the reassurance line.
- Help modal: step circles aligned to the first text line, emphasised
  free-tier callout with a check icon, underlined links with an
  external-link icon, muted medium-weight UI labels, 36px close button
  aligned to the title.
- Gate test extended to 44 checks (theme toggle, text link, modal polish).
2026-10-07 05:34:32 +00:00
dfa7b7502a Add full-screen API-key gate
In user-key mode a browser with no stored Ollama key now gets a polished
gate instead of the app shell: one masked input with a show/hide toggle,
a single Continue button, inline validation errors, and a "?" help sheet
with the steps to a free key.

- Validates the draft key through /api/health (real 1-token probe) before
  saving; invalid-key and network failures read differently.
- Key still lives only in this browser's localStorage; nothing server-side.
- Gate wraps /, /create and deck study; server-key and local-daemon modes
  skip it; removing the key in Settings brings it back.
- Removes the now-redundant dashboard/create API-key banner.
- Monochrome corner glows (dark near-black / light near-white), 24px card,
  48px field and button, gentle fade/slide-in, reduced-motion aware.
- Help modal: centred dialog on desktop, bottom sheet on mobile, closes via
  X, Escape or backdrop.
- README and browser suites updated (34 gate checks, 15 key-flow checks,
  12 shipped-default checks; ui/folders/docs/pwa/security/contrast green).
2026-10-06 21:20:22 +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
3baa0fa8ce Initial commit from Create Next App 2026-10-05 07:05:33 +00:00