feat(ui): add visual asset pack and documentation (T03)

This commit is contained in:
Paul Huliganga 2026-03-26 17:47:12 -04:00
parent 469b4b78a1
commit 3931455e64
15 changed files with 213 additions and 0 deletions

View File

@ -0,0 +1,78 @@
# Visual Asset Audit — T03 (Asset Pack)
Date: 2026-03-26
Task: T03 — Visual Asset Pack
Owner: agent-assets
## Summary
Implemented a legal-safe asset pack under `frontend/public/assets/` to support the redesign with:
1. Food imagery placeholders
2. Category illustrations/icons
3. Empty-state graphics
4. Placeholder strategy for missing images
All new visual files are simple, in-repo SVG illustrations generated specifically for this project.
No copyrighted third-party photos or icons were imported.
## Asset Inventory
### Food placeholders
- `/assets/food/placeholder-recipe.svg` (wide hero fallback)
- `/assets/food/placeholder-recipe-4x3.svg` (list/card ratio fallback)
- `/assets/food/placeholder-recipe-1x1.svg` (square thumb fallback)
- `/assets/food/placeholder-upload-dropzone.svg` (upload UI placeholder)
### Category icons
- `/assets/category/icon-breakfast.svg`
- `/assets/category/icon-lunch.svg`
- `/assets/category/icon-dinner.svg`
- `/assets/category/icon-dessert.svg`
- `/assets/category/icon-snack.svg`
### Empty-state graphics
- `/assets/empty-state/no-recipes.svg`
- `/assets/empty-state/no-favorites.svg`
- `/assets/empty-state/no-results-search.svg`
## Source & Attribution Notes
- **Source:** Created in-house (manual SVG composition)
- **Attribution required:** None
- **Third-party dependencies:** None for these files
- **Legal status:** Safe to use and modify within this repository
## Icon Strategy
Current strategy for redesign wave:
- Use lightweight, project-owned SVG icons for category visuals and illustrative states
- Keep semantic UI icons in code (emoji/text) where already present to avoid functional churn during visual phase
- Optionally standardize on one icon library in a later task (e.g., Lucide React) if/when design system tokenization introduces centralized icon components
## Placeholder Strategy (Missing Images)
When recipe images are missing or fail to load:
1. **Primary display:** Use `/assets/food/placeholder-recipe-4x3.svg` for list cards
2. **Detail hero fallback:** Use `/assets/food/placeholder-recipe.svg`
3. **Square contexts (avatars/thumbs):** Use `/assets/food/placeholder-recipe-1x1.svg`
4. **Upload workflows:** Show `/assets/food/placeholder-upload-dropzone.svg` before image selection
5. **Error fallback:** On image load error, swap `src` to matching placeholder and set descriptive `alt` text (e.g., `"Recipe image placeholder"`)
Recommended accessibility behavior:
- Keep `alt` text contextual to the recipe title when available
- For decorative empty-state graphics, use empty alt (`alt=""`) plus nearby descriptive copy
## Usage Guidance
- Public URL pattern: `/assets/<group>/<file>.svg`
- Prefer SVG scaling with CSS `object-fit: cover` for card contexts
- Keep placeholders as deterministic defaults so UI is never image-empty
## Follow-up Suggestions
- Wire fallback logic in card/detail components (T04T06)
- Add visual regression screenshots once integrated
- If future photography is required, only use assets with explicit commercial licenses and record attribution here

View File

@ -0,0 +1,21 @@
# Visual Asset Pack (T03)
This directory contains **copyright-safe, generated-in-project assets** for the visual redesign.
## Structure
- `food/` — recipe/photo placeholders for cards, detail hero, and upload dropzone states
- `category/` — category icon SVGs (breakfast, lunch, dinner, dessert, snack)
- `empty-state/` — illustrations for no recipes, no favorites, and no search results
- `ui/` — reserved for additional decorative UI assets
## Usage Notes
- Reference from frontend as absolute public URLs, e.g. `/assets/food/placeholder-recipe.svg`
- Prefer SVG placeholders over external stock images until approved imagery is available
- Keep files lightweight and editable (plain SVG)
## License Safety
All assets in this folder were authored for this project and are safe for internal/commercial use under repository ownership.
No third-party copyrighted graphics are included.

View File

@ -0,0 +1,6 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Breakfast icon">
<circle cx="64" cy="64" r="60" fill="#FEF3C7"/>
<circle cx="64" cy="64" r="28" fill="#F59E0B"/>
<circle cx="64" cy="64" r="16" fill="#FDE68A"/>
<path d="M24 96H104" stroke="#92400E" stroke-width="8" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 378 B

View File

@ -0,0 +1,6 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Dessert icon">
<circle cx="64" cy="64" r="56" fill="#FCE7F3"/>
<path d="M34 84H94L86 50H42L34 84Z" fill="#F472B6"/>
<circle cx="64" cy="44" r="12" fill="#FB7185"/>
<rect x="61" y="22" width="6" height="14" rx="3" fill="#BE123C"/>
</svg>

After

Width:  |  Height:  |  Size: 367 B

View File

@ -0,0 +1,5 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Dinner icon">
<circle cx="64" cy="64" r="52" fill="#E0E7FF"/>
<circle cx="64" cy="64" r="30" stroke="#4F46E5" stroke-width="10"/>
<path d="M38 92C46 84 54 80 64 80C74 80 82 84 90 92" stroke="#4F46E5" stroke-width="8" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 377 B

View File

@ -0,0 +1,5 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Lunch icon">
<rect x="14" y="20" width="100" height="88" rx="18" fill="#DCFCE7"/>
<rect x="36" y="34" width="56" height="60" rx="12" fill="#22C55E"/>
<path d="M64 12V26" stroke="#166534" stroke-width="8" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 364 B

View File

@ -0,0 +1,7 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Snack icon">
<circle cx="64" cy="64" r="56" fill="#FFEDD5"/>
<rect x="36" y="30" width="56" height="68" rx="16" fill="#FB923C"/>
<circle cx="64" cy="50" r="8" fill="#FDBA74"/>
<circle cx="50" cy="68" r="6" fill="#FDBA74"/>
<circle cx="78" cy="68" r="6" fill="#FDBA74"/>
</svg>

After

Width:  |  Height:  |  Size: 409 B

View File

@ -0,0 +1,6 @@
<svg width="960" height="640" viewBox="0 0 960 640" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="No favorite recipes">
<rect width="960" height="640" rx="32" fill="#FFF1F2"/>
<path d="M480 418L309 247C267 205 267 137 309 95C351 53 419 53 461 95L480 114L499 95C541 53 609 53 651 95C693 137 693 205 651 247L480 418Z" fill="#FB7185"/>
<circle cx="480" cy="260" r="82" fill="#FECDD3"/>
<text x="480" y="530" text-anchor="middle" font-family="Inter, sans-serif" font-size="36" fill="#9F1239">No favorites yet</text>
</svg>

After

Width:  |  Height:  |  Size: 551 B

View File

@ -0,0 +1,12 @@
<svg width="960" height="640" viewBox="0 0 960 640" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
<title id="title">No recipes found</title>
<desc id="desc">Illustration of a cookbook and magnifier to show empty search results.</desc>
<rect width="960" height="640" rx="32" fill="#F8FAFC"/>
<rect x="210" y="170" width="300" height="320" rx="24" fill="#E2E8F0"/>
<rect x="238" y="208" width="244" height="24" rx="12" fill="#94A3B8"/>
<rect x="238" y="256" width="188" height="18" rx="9" fill="#CBD5E1"/>
<rect x="238" y="288" width="224" height="18" rx="9" fill="#CBD5E1"/>
<circle cx="612" cy="320" r="88" stroke="#F97316" stroke-width="20"/>
<path d="M672 382L742 452" stroke="#F97316" stroke-width="20" stroke-linecap="round"/>
<text x="480" y="560" text-anchor="middle" font-family="Inter, sans-serif" font-size="38" fill="#334155">No recipes match your filters</text>
</svg>

After

Width:  |  Height:  |  Size: 942 B

View File

@ -0,0 +1,10 @@
<svg width="960" height="640" viewBox="0 0 960 640" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Search returned no results">
<rect width="960" height="640" rx="32" fill="#EFF6FF"/>
<rect x="170" y="160" width="620" height="90" rx="45" fill="#DBEAFE"/>
<circle cx="250" cy="205" r="24" stroke="#3B82F6" stroke-width="10"/>
<path d="M266 221L287 242" stroke="#3B82F6" stroke-width="10" stroke-linecap="round"/>
<rect x="310" y="186" width="350" height="38" rx="19" fill="#BFDBFE"/>
<circle cx="480" cy="380" r="120" stroke="#93C5FD" stroke-width="16" stroke-dasharray="14 12"/>
<path d="M432 380L468 416L536 348" stroke="#1D4ED8" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
<text x="480" y="560" text-anchor="middle" font-family="Inter, sans-serif" font-size="36" fill="#1E3A8A">Try a different keyword</text>
</svg>

After

Width:  |  Height:  |  Size: 880 B

View File

@ -0,0 +1,11 @@
<svg width="900" height="900" viewBox="0 0 900 900" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Recipe placeholder square">
<rect width="900" height="900" rx="40" fill="#F8FAFC"/>
<rect x="90" y="90" width="720" height="720" rx="32" fill="#ECFEFF"/>
<ellipse cx="450" cy="460" rx="220" ry="126" fill="#E2E8F0"/>
<ellipse cx="450" cy="445" rx="200" ry="110" fill="#FFF"/>
<circle cx="366" cy="430" r="30" fill="#F97316"/>
<circle cx="425" cy="398" r="26" fill="#22C55E"/>
<circle cx="485" cy="442" r="24" fill="#FB7185"/>
<circle cx="546" cy="410" r="20" fill="#EAB308"/>
<text x="450" y="650" text-anchor="middle" font-family="Inter, sans-serif" font-size="40" fill="#334155">Recipe Image</text>
</svg>

After

Width:  |  Height:  |  Size: 747 B

View File

@ -0,0 +1,10 @@
<svg width="800" height="600" viewBox="0 0 800 600" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Recipe placeholder 4 by 3">
<rect width="800" height="600" rx="28" fill="#FFF7ED"/>
<rect x="70" y="70" width="660" height="460" rx="24" fill="#FFEDD5"/>
<ellipse cx="400" cy="330" rx="170" ry="98" fill="#F8FAFC"/>
<circle cx="330" cy="300" r="24" fill="#F97316"/>
<circle cx="390" cy="282" r="22" fill="#22C55E"/>
<circle cx="446" cy="308" r="20" fill="#EAB308"/>
<circle cx="500" cy="287" r="18" fill="#FB7185"/>
<text x="400" y="470" text-anchor="middle" font-family="Inter, sans-serif" font-size="28" fill="#475569">No photo yet</text>
</svg>

After

Width:  |  Height:  |  Size: 685 B

View File

@ -0,0 +1,21 @@
<svg width="1200" height="800" viewBox="0 0 1200 800" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
<title id="title">Recipe image placeholder</title>
<desc id="desc">Abstract food bowl illustration used as a safe placeholder image.</desc>
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1200" y2="800" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFF7ED"/>
<stop offset="1" stop-color="#FEF3C7"/>
</linearGradient>
</defs>
<rect width="1200" height="800" rx="36" fill="url(#bg)"/>
<ellipse cx="600" cy="470" rx="280" ry="150" fill="#E2E8F0"/>
<ellipse cx="600" cy="450" rx="250" ry="125" fill="#F8FAFC"/>
<circle cx="500" cy="430" r="28" fill="#F97316"/>
<circle cx="560" cy="405" r="34" fill="#84CC16"/>
<circle cx="630" cy="445" r="30" fill="#22C55E"/>
<circle cx="690" cy="410" r="26" fill="#FB7185"/>
<circle cx="740" cy="445" r="22" fill="#F59E0B"/>
<rect x="410" y="560" width="380" height="20" rx="10" fill="#CBD5E1"/>
<text x="600" y="662" text-anchor="middle" font-family="Inter, system-ui, -apple-system, Segoe UI, Roboto, sans-serif" font-size="46" fill="#475569">Recipe Photo Placeholder</text>
<text x="600" y="712" text-anchor="middle" font-family="Inter, system-ui, -apple-system, Segoe UI, Roboto, sans-serif" font-size="28" fill="#64748B">Generated in-project, copyright-safe</text>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,8 @@
<svg width="960" height="540" viewBox="0 0 960 540" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Upload photo placeholder">
<rect width="960" height="540" rx="24" fill="#F8FAFC"/>
<rect x="40" y="40" width="880" height="460" rx="20" fill="#EEF2FF" stroke="#94A3B8" stroke-width="4" stroke-dasharray="10 10"/>
<path d="M480 198V302" stroke="#475569" stroke-width="16" stroke-linecap="round"/>
<path d="M428 250L480 198L532 250" stroke="#475569" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
<text x="480" y="360" text-anchor="middle" font-family="Inter, sans-serif" font-size="36" fill="#334155">Drop image or click to upload</text>
<text x="480" y="408" text-anchor="middle" font-family="Inter, sans-serif" font-size="24" fill="#64748B">JPG, PNG, WebP — placeholder graphic</text>
</svg>

After

Width:  |  Height:  |  Size: 848 B

View File

@ -0,0 +1,7 @@
<svg width="1600" height="900" viewBox="0 0 1600 900" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Soft decorative background pattern">
<rect width="1600" height="900" fill="#F8FAFC"/>
<circle cx="180" cy="120" r="180" fill="#DBEAFE"/>
<circle cx="1440" cy="180" r="220" fill="#FFEDD5"/>
<circle cx="320" cy="780" r="240" fill="#DCFCE7"/>
<circle cx="1320" cy="760" r="200" fill="#FCE7F3"/>
</svg>

After

Width:  |  Height:  |  Size: 433 B