feat(ui): redesign homepage visuals and hierarchy (T04)
This commit is contained in:
parent
4af20eaf91
commit
b5a2588bc4
|
|
@ -62,3 +62,43 @@ Severity legend: **High** = materially harms UX/clarity, **Medium** = noticeable
|
|||
- **P1**: Introduce consistent, high-quality food imagery strategy (home + detail + cards).
|
||||
- **P1**: Reduce action clutter in detail and establish one primary action per context.
|
||||
- **P2**: Replace ad-hoc emoji iconography with a coherent icon system.
|
||||
|
||||
---
|
||||
|
||||
# T04 Homepage Redesign (After)
|
||||
|
||||
Date: 2026-03-26
|
||||
Task: T04 — Homepage Redesign
|
||||
Owner: agent-core-ui
|
||||
|
||||
## What changed
|
||||
|
||||
- Introduced a stronger home hero with clear action hierarchy:
|
||||
- primary CTA: **Start a Recipe**
|
||||
- secondary CTA: **Browse Library**
|
||||
- responsive image panel with safe fallback chain (`hero.png` → `/images/hero-fallback.svg`)
|
||||
- Added feature-highlight blocks using T03 asset icons:
|
||||
- `/assets/category/icon-dinner.svg`
|
||||
- `/assets/category/icon-lunch.svg`
|
||||
- `/assets/category/icon-breakfast.svg`
|
||||
- Improved visual hierarchy and spacing for the recipe discovery area:
|
||||
- renamed section to **Recipe Library**
|
||||
- improved search input and action sizing for touch targets
|
||||
- retained filter behavior with clearer card grouping
|
||||
- Polished footer structure in app shell:
|
||||
- multi-column footer content
|
||||
- quick links and stronger brand/utility hierarchy
|
||||
- Empty state now uses T03 illustration (`/assets/empty-state/no-recipes.svg`) for better visual clarity.
|
||||
|
||||
## Token alignment note (T02 dependency)
|
||||
|
||||
- T02 token outputs are available (`frontend/src/theme.ts` + CSS custom properties in `frontend/src/index.css`).
|
||||
- T04 consumed existing token-friendly values (radius + CSS vars via global styles) and avoided introducing breaking token assumptions.
|
||||
- If T02 token contracts evolve, align CTA/card semantic classes first before changing component behavior.
|
||||
|
||||
## After screenshots
|
||||
|
||||
Saved under `docs/visual-audit/after/`:
|
||||
|
||||
- `home-desktop.png`
|
||||
- `home-mobile.png`
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 596 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 245 KiB |
|
|
@ -9,7 +9,6 @@ import { ToastContainer } from './components/Toast';
|
|||
import { useToast } from './hooks/useToast';
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
// Create toast context to share toast functionality across the app
|
||||
interface ToastContextType {
|
||||
success: (message: string, duration?: number) => string;
|
||||
error: (message: string, duration?: number) => string;
|
||||
|
|
@ -79,15 +78,15 @@ function App() {
|
|||
<ToastContainer messages={toast.messages} onClose={toast.removeToast} />
|
||||
|
||||
<header className="sticky top-0 z-20 border-b border-slate-200/70 bg-white/85 shadow-sm backdrop-blur-md dark:border-slate-700/60 dark:bg-slate-900/70">
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<div className="flex items-center justify-between h-16 gap-3">
|
||||
<div className="mx-auto max-w-7xl px-4">
|
||||
<div className="flex h-16 items-center justify-between gap-3">
|
||||
<div className="flex items-center">
|
||||
<Link
|
||||
to="/"
|
||||
className="group inline-flex items-center gap-2 rounded-lg px-2 py-1.5 outline-none transition-colors duration-200 hover:text-blue-700 focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2"
|
||||
>
|
||||
<span className="text-xl" aria-hidden="true">🍽️</span>
|
||||
<h1 className="text-xl sm:text-2xl font-bold text-gray-900 tracking-tight">Recipe Manager</h1>
|
||||
<h1 className="text-xl font-bold tracking-tight text-gray-900 sm:text-2xl">Recipe Manager</h1>
|
||||
</Link>
|
||||
</div>
|
||||
<nav aria-label="Primary" className="flex flex-wrap items-center justify-end gap-2 sm:gap-3">
|
||||
|
|
@ -108,7 +107,7 @@ function App() {
|
|||
</div>
|
||||
</header>
|
||||
|
||||
<main className="max-w-7xl mx-auto py-8 px-4 min-h-[70vh]">
|
||||
<main className="mx-auto min-h-[70vh] max-w-7xl px-4 py-8">
|
||||
<Routes>
|
||||
<Route path="/" element={<RecipeListPage />} />
|
||||
<Route path="/recipe/new" element={<RecipeDetailPage />} />
|
||||
|
|
@ -119,11 +118,24 @@ function App() {
|
|||
</Routes>
|
||||
</main>
|
||||
|
||||
<footer className="mt-12 border-t border-slate-200/70 bg-white/70 backdrop-blur-sm dark:border-slate-700/60 dark:bg-slate-900/45">
|
||||
<div className="max-w-7xl mx-auto py-6 px-4">
|
||||
<p className="text-center text-sm text-gray-500">
|
||||
Recipe Manager MVP - Built with React + Vite + TypeScript
|
||||
</p>
|
||||
<footer className="mt-10 border-t border-slate-200/80 bg-gradient-to-br from-white/90 to-slate-100/90 backdrop-blur-sm dark:border-slate-700/60 dark:from-slate-900/60 dark:to-slate-900/30">
|
||||
<div className="mx-auto grid max-w-7xl grid-cols-1 gap-6 px-4 py-8 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<div>
|
||||
<p className="text-sm font-semibold uppercase tracking-wide text-blue-700">Recipe Manager</p>
|
||||
<p className="mt-2 text-sm text-slate-600">Save recipes, organize by tags, and keep your kitchen workflow simple.</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-semibold uppercase tracking-wide text-slate-700">Quick Links</p>
|
||||
<div className="mt-2 flex flex-wrap gap-2 text-sm text-slate-600">
|
||||
<Link to="/" className="rounded-full bg-white px-3 py-1 hover:bg-slate-100">Browse</Link>
|
||||
<Link to="/recipe/new" className="rounded-full bg-white px-3 py-1 hover:bg-slate-100">Add Recipe</Link>
|
||||
<Link to="/import/url" className="rounded-full bg-white px-3 py-1 hover:bg-slate-100">Import URL</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sm:col-span-2 lg:col-span-1">
|
||||
<p className="text-sm font-semibold uppercase tracking-wide text-slate-700">Built for everyday cooking</p>
|
||||
<p className="mt-2 text-sm text-slate-600">React + Vite + TypeScript · Visual redesign in progress</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,24 @@ const emptyStatus: HarnessStatus = {
|
|||
uptime: 0,
|
||||
};
|
||||
|
||||
const featureHighlights = [
|
||||
{
|
||||
title: 'Organize with tags',
|
||||
copy: 'Group your meals by cuisine, prep style, or dietary preference so finding dinner is instant.',
|
||||
icon: '/assets/category/icon-dinner.svg',
|
||||
},
|
||||
{
|
||||
title: 'Capture from the web',
|
||||
copy: 'Import recipe links and keep your best discoveries in one place instead of scattered bookmarks.',
|
||||
icon: '/assets/category/icon-lunch.svg',
|
||||
},
|
||||
{
|
||||
title: 'Cook with confidence',
|
||||
copy: 'Use clear recipe cards and streamlined actions while planning, prepping, and cooking.',
|
||||
icon: '/assets/category/icon-breakfast.svg',
|
||||
},
|
||||
] as const;
|
||||
|
||||
export function RecipeListPage() {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
|
@ -55,36 +73,36 @@ export function RecipeListPage() {
|
|||
<MissionControlPanel status={emptyStatus} />
|
||||
|
||||
<section
|
||||
className="relative mt-8 overflow-hidden border border-slate-200/80 bg-white/85 p-0 shadow-card"
|
||||
style={{ borderRadius: radius.lg }}
|
||||
className="relative mt-6 overflow-hidden border border-slate-200/80 bg-white/90 p-0 shadow-card"
|
||||
style={{ borderRadius: radius.xl }}
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2">
|
||||
<div className="flex flex-col justify-center gap-4 px-6 py-8 md:px-8 md:py-10">
|
||||
<p className="text-sm font-semibold uppercase tracking-wider text-orange-500">Kitchen Companion</p>
|
||||
<h1 className="text-3xl font-extrabold leading-tight text-slate-900 md:text-4xl">Cook smarter with your favorite recipes in one place</h1>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2">
|
||||
<div className="flex flex-col justify-center gap-4 px-6 py-8 md:px-10 md:py-12">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-orange-500 md:text-sm">Your kitchen companion</p>
|
||||
<h1 className="text-3xl font-extrabold leading-tight text-slate-900 md:text-5xl">Plan meals faster and keep every favorite recipe organized</h1>
|
||||
<p className="max-w-lg text-sm text-slate-600 md:text-base">
|
||||
Build your personal cookbook, tag meals by mood or diet, and find what you need fast.
|
||||
Build your personal cookbook with better structure, quick tag filters, and easy capture from web links.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<div className="flex flex-wrap gap-3 pt-1">
|
||||
<Link
|
||||
to="/recipe/new"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-5 py-2.5 font-semibold text-white shadow transition-all duration-200 hover:-translate-y-0.5 hover:bg-blue-700 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
className="inline-flex min-h-11 items-center gap-2 rounded-lg bg-blue-600 px-5 py-2.5 font-semibold text-white shadow transition-all duration-200 hover:-translate-y-0.5 hover:bg-blue-700 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
style={{ borderRadius: radius.md }}
|
||||
>
|
||||
<span aria-hidden="true">➕</span>
|
||||
Add Recipe
|
||||
<span aria-hidden="true">✨</span>
|
||||
Start a Recipe
|
||||
</Link>
|
||||
<a
|
||||
href="#recipes-grid"
|
||||
className="inline-flex items-center gap-2 rounded-lg border border-slate-200 bg-white px-5 py-2.5 font-semibold text-slate-700 transition-all duration-200 hover:-translate-y-0.5 hover:bg-slate-50 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
className="inline-flex min-h-11 items-center gap-2 rounded-lg border border-slate-200 bg-white px-5 py-2.5 font-semibold text-slate-700 transition-all duration-200 hover:-translate-y-0.5 hover:bg-slate-50 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
style={{ borderRadius: radius.md }}
|
||||
>
|
||||
<span aria-hidden="true">📚</span>
|
||||
Browse Recipes
|
||||
Browse Library
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative min-h-[220px] bg-slate-100 md:min-h-full">
|
||||
<div className="relative min-h-[260px] bg-slate-100 md:min-h-[320px] lg:min-h-full">
|
||||
<img
|
||||
src={heroSrc}
|
||||
alt={visualAssets.hero.alt}
|
||||
|
|
@ -95,28 +113,37 @@ export function RecipeListPage() {
|
|||
}
|
||||
}}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-slate-900/20 via-transparent to-transparent" />
|
||||
<div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-slate-900/45 via-slate-900/15 to-transparent" />
|
||||
<div className="absolute bottom-4 left-4 rounded-full bg-white/90 px-4 py-1.5 text-sm font-semibold text-slate-800 shadow">
|
||||
{filteredRecipes.length} saved recipes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-6 grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
{featureHighlights.map((feature) => (
|
||||
<article
|
||||
key={feature.title}
|
||||
className="rounded-xl border border-slate-200/80 bg-white/85 p-4 shadow-card"
|
||||
style={{ borderRadius: radius.lg }}
|
||||
>
|
||||
<img src={feature.icon} alt="" aria-hidden="true" className="h-10 w-10" />
|
||||
<h2 className="mt-3 text-lg font-bold text-slate-900">{feature.title}</h2>
|
||||
<p className="mt-1 text-sm text-slate-600">{feature.copy}</p>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<section
|
||||
className="mt-8 mb-10 flex flex-col gap-4 rounded-xl border border-slate-200/80 bg-white/90 px-5 py-6 shadow-card md:px-6"
|
||||
className="mb-10 mt-7 flex flex-col gap-4 rounded-xl border border-slate-200/80 bg-white/90 px-5 py-6 shadow-card md:px-6"
|
||||
style={{ borderRadius: radius.lg, boxShadow: '0 2px 8px 0 rgba(28,30,34,0.07)' }}
|
||||
>
|
||||
<div className="flex flex-col justify-between gap-4 md:flex-row md:items-end">
|
||||
<div>
|
||||
<h2 className="mb-0 text-2xl font-extrabold text-gray-900">My Recipes</h2>
|
||||
<p className="mt-1 text-sm text-gray-500">Browse and search your recipe collection</p>
|
||||
<h2 className="mb-0 text-2xl font-extrabold text-gray-900">Recipe Library</h2>
|
||||
<p className="mt-1 text-sm text-gray-500">Search, filter, and jump back into your most-used meals</p>
|
||||
</div>
|
||||
<Link
|
||||
to="/recipe/new"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 font-semibold text-white shadow transition-all duration-200 hover:-translate-y-0.5 hover:bg-blue-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
style={{ borderRadius: radius.md }}
|
||||
>
|
||||
<span aria-hidden="true">✨</span>
|
||||
New Recipe
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSearch} className="mt-3 flex flex-col items-stretch gap-3 md:mt-0 md:flex-row">
|
||||
|
|
@ -126,8 +153,8 @@ export function RecipeListPage() {
|
|||
placeholder="Search recipes by title, ingredients, or tags..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full rounded-lg border border-gray-300 px-4 py-2 text-base focus:border-transparent focus:ring-2 focus:ring-blue-500"
|
||||
style={{ borderRadius: radius.md }}
|
||||
className="w-full rounded-lg border border-gray-300 px-4 py-2.5 text-base focus:border-transparent focus:ring-2 focus:ring-blue-500"
|
||||
style={{ borderRadius: radius.md, minHeight: 44 }}
|
||||
/>
|
||||
{!!searchQuery && (
|
||||
<button
|
||||
|
|
@ -142,7 +169,7 @@ export function RecipeListPage() {
|
|||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="rounded-lg border border-gray-200 bg-gray-100 px-6 py-2 font-semibold text-gray-700 transition-all duration-200 hover:-translate-y-0.5 hover:bg-gray-200 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
className="min-h-11 rounded-lg border border-gray-200 bg-gray-100 px-6 py-2 font-semibold text-gray-700 transition-all duration-200 hover:-translate-y-0.5 hover:bg-gray-200 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
style={{ borderRadius: radius.md }}
|
||||
>
|
||||
Search
|
||||
|
|
@ -187,7 +214,7 @@ export function RecipeListPage() {
|
|||
{selectedTagId !== null && (
|
||||
<span className="rounded bg-blue-50 px-2 py-1 text-blue-700">Tag: {tags.find((t) => t.id === selectedTagId)?.name}</span>
|
||||
)}
|
||||
<button onClick={handleClearFilters} className="font-medium text-blue-600 transition-colors hover:text-blue-700 focus-visible:ring-2 focus-visible:ring-blue-500 rounded-sm">
|
||||
<button onClick={handleClearFilters} className="rounded-sm font-medium text-blue-600 transition-colors hover:text-blue-700 focus-visible:ring-2 focus-visible:ring-blue-500">
|
||||
Clear all filters
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -217,13 +244,13 @@ export function RecipeListPage() {
|
|||
className="mx-auto flex max-w-xl flex-col items-center gap-2 rounded-xl border border-dashed border-orange-200 bg-gradient-to-br from-white to-orange-50 p-14 text-center shadow-card"
|
||||
style={{ borderRadius: radius.lg }}
|
||||
>
|
||||
<div className="mb-2 text-6xl">🧑🍳</div>
|
||||
<img src="/assets/empty-state/no-recipes.svg" alt="" aria-hidden="true" className="mb-2 h-28 w-28" />
|
||||
<h3 className="mb-2 text-xl font-bold text-gray-800">{searchQuery ? 'No recipes found' : 'No recipes yet'}</h3>
|
||||
<p className="mb-4 text-gray-600">{searchQuery ? 'Try another keyword or clear filters.' : 'Start your cookbook with a first delicious recipe.'}</p>
|
||||
{!searchQuery && (
|
||||
<Link
|
||||
to="/recipe/new"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-6 py-3 font-semibold text-white shadow transition-all duration-200 hover:-translate-y-0.5 hover:bg-blue-700 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
className="inline-flex min-h-11 items-center gap-2 rounded-lg bg-blue-600 px-6 py-3 font-semibold text-white shadow transition-all duration-200 hover:-translate-y-0.5 hover:bg-blue-700 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
style={{ borderRadius: radius.md }}
|
||||
>
|
||||
<span aria-hidden="true">🍳</span>
|
||||
|
|
|
|||
Loading…
Reference in New Issue