Redesign the quick-action CTAs: two ways in (study deck vs. homework)
Library empty state ("Your icebox")
- Replace the single "Make your first deck" button with two quick-action
cards: "Create interactive test" (primary, ice-gradient icon + subtle
accent border) and "Help me with my homework" (glass card, sparkles
icon). Stacked on phones, side by side from 640px up; both >=44px tall.
/create
- Replace the "Got homework instead of notes?" banner with a two-card mode
switcher above "1. Add your notes": "Study Deck & Quizzes" (the current
view — aria-current, accent tint, ice icon) and "Homework Solver" (a
shortcut to /homework). Icon-on-top tiles on phones, inline rows from
640px up; glass tokens + backdrop blur in both themes.
Strings
- New keys in both dictionaries: emptyStateCreateDeck, emptyStateHomework,
actionStudyDeck, actionHomework. Drop the now-unused homework.entry and
library.makeFirst keys.
Drive-by AA fix (light mode, /create only)
- The "New folder" dashed button and the difficulty blurbs move from
frost-500 to frost-600 (4.0:1 -> ~6:1). Dark mode is unchanged.
Service worker: freezmo-v8.
Verified: redesign suite 32/32 (stacked/side-by-side layouts, 360/390/1280,
dark+light, en+cs, >=44px targets, both cards navigate, no overflow, axe
contrast), mobile 49/49, i18n 27/27, desktop regression 73/73. Lint + build
clean.
This commit is contained in:
parent
4ac2f33e3f
commit
22af888ec0
4 changed files with 81 additions and 28 deletions
|
|
@ -5,7 +5,7 @@ import { useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|||
|
||||
import { DeckCard } from "@/components/DeckCard";
|
||||
import { FolderBar } from "@/components/FolderBar";
|
||||
import { FolderIcon, UploadIcon } from "@/components/icons";
|
||||
import { FolderIcon, SnowflakeIcon, SparklesIcon, UploadIcon } from "@/components/icons";
|
||||
import { Button, EmptyState } from "@/components/ui";
|
||||
import { folderStore, renameFolder } from "@/lib/folders";
|
||||
import { useI18n } from "@/lib/i18n";
|
||||
|
|
@ -224,9 +224,38 @@ export function LibraryScreen() {
|
|||
<EmptyState
|
||||
title={t("library.noDecks")}
|
||||
action={
|
||||
<Link href="/create" className="btn-primary touch-target h-10 px-4 text-sm">
|
||||
{t("library.makeFirst")}
|
||||
</Link>
|
||||
/* Two ways in: build a study deck from notes, or hand over an
|
||||
assignment. Stacked on phones, side by side from 640px up. */
|
||||
<div className="grid w-[min(24rem,calc(100vw_-_3rem))] gap-3 sm:w-[36rem] sm:grid-cols-2">
|
||||
<Link
|
||||
href="/create"
|
||||
className="card card-hover touch-target flex items-center gap-3 border-accent-400/60 p-3.5 text-left sm:p-4"
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className="grad-ice grid size-11 shrink-0 place-items-center rounded-2xl text-[#06111f]"
|
||||
>
|
||||
<SnowflakeIcon className="size-5" />
|
||||
</span>
|
||||
<span className="min-w-0 text-[14px] font-semibold leading-snug text-[var(--text)]">
|
||||
{t("emptyStateCreateDeck")}
|
||||
</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/homework"
|
||||
className="card card-hover touch-target flex items-center gap-3 p-3.5 text-left sm:p-4"
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className="grid size-11 shrink-0 place-items-center rounded-2xl bg-[var(--surface-sunken)] text-[var(--text-muted)]"
|
||||
>
|
||||
<SparklesIcon className="size-5" />
|
||||
</span>
|
||||
<span className="min-w-0 text-[14px] font-semibold leading-snug text-[var(--text)]">
|
||||
{t("emptyStateHomework")}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{t("library.emptyBody")}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import { UNORGANIZED_ID, type Difficulty } from "@/lib/schemas";
|
|||
import { upsertDeck } from "@/lib/storage";
|
||||
import { openSettings } from "@/lib/ui-events";
|
||||
import { cn, uid } from "@/lib/utils";
|
||||
import { CameraIcon, ChevronRightIcon, FolderIcon, IceCubeIcon, PlusIcon, SnowflakeIcon, SparklesIcon, SunIcon } from "@/components/icons";
|
||||
import { CameraIcon, FolderIcon, IceCubeIcon, PlusIcon, SnowflakeIcon, SparklesIcon, SunIcon } from "@/components/icons";
|
||||
import { Button, ProgressBar } from "@/components/ui";
|
||||
|
||||
type Phase = "idle" | "preparing" | "loading" | "error";
|
||||
|
|
@ -321,23 +321,39 @@ export function UploadScreen() {
|
|||
</p>
|
||||
</section>
|
||||
|
||||
{/* The other thing you can bring here: an assignment. */}
|
||||
<Link
|
||||
href="/homework"
|
||||
className="card card-hover flex items-center gap-3 p-4 text-left"
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className="grid size-10 shrink-0 place-items-center rounded-xl bg-[var(--surface-sunken)] text-[var(--text-muted)]"
|
||||
{/* Mode switcher: this page builds a study deck from notes; the other
|
||||
card hands the same intake over to the homework flow. */}
|
||||
<div className="grid grid-cols-2 gap-2.5 sm:gap-3">
|
||||
<Link
|
||||
href="/create"
|
||||
aria-current="page"
|
||||
className="card touch-target flex flex-col items-start gap-2 rounded-2xl border-accent-400/60 bg-accent-50/60 p-3 ring-2 ring-accent-400/30 sm:flex-row sm:items-center sm:gap-3 sm:p-3.5 dark:bg-accent-500/15"
|
||||
>
|
||||
<SparklesIcon className="size-5" />
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block text-[14px] font-semibold text-[var(--text)]">{t("homework.entry")}</span>
|
||||
<span className="block text-[12.5px] text-[var(--text-subtle)]">{t("homework.nav")} →</span>
|
||||
</span>
|
||||
<ChevronRightIcon className="size-4 shrink-0 text-[var(--text-subtle)]" />
|
||||
</Link>
|
||||
<span
|
||||
aria-hidden
|
||||
className="grad-ice grid size-9 shrink-0 place-items-center rounded-xl text-[#06111f]"
|
||||
>
|
||||
<SnowflakeIcon className="size-5" />
|
||||
</span>
|
||||
<span className="min-w-0 text-[13px] font-semibold leading-snug text-[var(--text)] sm:text-[13.5px]">
|
||||
{t("actionStudyDeck")}
|
||||
</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/homework"
|
||||
className="card card-hover touch-target flex flex-col items-start gap-2 rounded-2xl p-3 sm:flex-row sm:items-center sm:gap-3 sm:p-3.5"
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className="grid size-9 shrink-0 place-items-center rounded-xl bg-[var(--surface-sunken)] text-[var(--text-muted)]"
|
||||
>
|
||||
<SparklesIcon className="size-5" />
|
||||
</span>
|
||||
<span className="min-w-0 text-[13px] font-semibold leading-snug text-[var(--text)] sm:text-[13.5px]">
|
||||
{t("actionHomework")}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
|
||||
{error ? (
|
||||
|
|
@ -676,7 +692,7 @@ export function UploadScreen() {
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => setCreatingFolder(true)}
|
||||
className="inline-flex items-center gap-1.5 rounded-xl border border-dashed border-[var(--hairline)] px-3 py-1.5 text-sm font-semibold text-frost-500 transition-colors hover:text-accent-700 dark:text-frost-400 dark:hover:text-accent-200"
|
||||
className="inline-flex items-center gap-1.5 rounded-xl border border-dashed border-[var(--hairline)] px-3 py-1.5 text-sm font-semibold text-frost-600 transition-colors hover:text-accent-700 dark:text-frost-400 dark:hover:text-accent-200"
|
||||
>
|
||||
<PlusIcon className="size-4" /> {t("common.newFolder")}
|
||||
</button>
|
||||
|
|
@ -705,7 +721,7 @@ export function UploadScreen() {
|
|||
{option.icon}
|
||||
</span>
|
||||
<span className="text-sm font-bold text-[var(--text)]">{t(option.labelKey)}</span>
|
||||
<span className="hidden text-[11px] text-frost-500 sm:block dark:text-frost-400">{t(option.blurbKey)}</span>
|
||||
<span className="hidden text-[11px] text-frost-600 sm:block dark:text-frost-400">{t(option.blurbKey)}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -172,7 +172,6 @@ const en: Record<string, string> = {
|
|||
"homework.title": "Make my 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.",
|
||||
"homework.entry": "Got homework instead of notes?",
|
||||
"homework.source.title": "1. The assignment",
|
||||
"homework.source.help": "Paste its text, drop photos, or add a document.",
|
||||
"homework.source.textLabel": "Assignment text",
|
||||
|
|
@ -224,6 +223,12 @@ const en: Record<string, string> = {
|
|||
"homework.history.openTitle": "Open “{title}”",
|
||||
"homework.history.deleteConfirm": "Delete this result?",
|
||||
|
||||
/* quick actions (library empty state + /create switcher) ----------- */
|
||||
"emptyStateCreateDeck": "Create interactive test",
|
||||
"emptyStateHomework": "Help me with my homework",
|
||||
"actionStudyDeck": "Study Deck & Quizzes",
|
||||
"actionHomework": "Homework Solver",
|
||||
|
||||
/* library --------------------------------------------------------- */
|
||||
"library.title": "Your icebox",
|
||||
"library.subtitle": "Decks frozen from your notes, saved in this browser.",
|
||||
|
|
@ -235,7 +240,6 @@ const en: Record<string, string> = {
|
|||
"library.deckMoved": "Deck moved.",
|
||||
"library.deckMovedUnorganized": "Deck moved to Unorganized.",
|
||||
"library.noDecks": "No decks yet",
|
||||
"library.makeFirst": "Make your first deck",
|
||||
"library.emptyBody": "Freeze a page of notes into a feed of flashcards and quizzes.",
|
||||
"library.emptyHint": "Using your own Ollama models —",
|
||||
"library.emptySettings": "check the connection in Settings",
|
||||
|
|
@ -752,7 +756,6 @@ const cs: Record<string, string> = {
|
|||
"homework.title": "Udělej mi ú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.",
|
||||
"homework.entry": "Máš místo poznámek úkol?",
|
||||
"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í",
|
||||
|
|
@ -804,6 +807,12 @@ const cs: Record<string, string> = {
|
|||
"homework.history.openTitle": "Otevřít „{title}“",
|
||||
"homework.history.deleteConfirm": "Smazat tento výsledek?",
|
||||
|
||||
/* quick actions (library empty state + /create switcher) ----------- */
|
||||
"emptyStateCreateDeck": "Vytvořit interaktivní test",
|
||||
"emptyStateHomework": "Pomoct s úkolem",
|
||||
"actionStudyDeck": "Studijní balíček a testy",
|
||||
"actionHomework": "Řešitel úkolů",
|
||||
|
||||
/* library --------------------------------------------------------- */
|
||||
"library.title": "Tvoje mraznička",
|
||||
"library.subtitle": "Sady zmrazené z tvých poznámek, uložené v tomto prohlížeči.",
|
||||
|
|
@ -815,7 +824,6 @@ const cs: Record<string, string> = {
|
|||
"library.deckMoved": "Sada přesunuta.",
|
||||
"library.deckMovedUnorganized": "Sada přesunuta do Nezařazených.",
|
||||
"library.noDecks": "Zatím žádné sady",
|
||||
"library.makeFirst": "Vytvoř si první sadu",
|
||||
"library.emptyBody": "Zmraz stránku poznámek do feedu kartiček a kvízů.",
|
||||
"library.emptyHint": "S tvými vlastními modely Ollama —",
|
||||
"library.emptySettings": "zkontroluj připojení v Nastavení",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
*
|
||||
* Bumping CACHE_VERSION is how a new deploy invalidates old assets.
|
||||
*/
|
||||
const CACHE_VERSION = "freezmo-v7";
|
||||
const CACHE_VERSION = "freezmo-v8";
|
||||
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
|
||||
const ASSET_CACHE = `${CACHE_VERSION}-assets`;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue