diff --git a/frontend/README.md b/frontend/README.md index 633f56f..57d4ee9 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -52,3 +52,11 @@ src/ ## Architecture See `/ARCHITECTURE.md` for full system architecture and patterns. + + +## Visual Assets + Fallbacks + +- Visual asset definitions live in `src/assets/visualAssets.ts`. +- The homepage hero uses a fallback chain: bundled `hero.png` first, then `/images/hero-fallback.svg`. +- Keep fallback assets lightweight (SVG preferred) and store browser-served fallbacks under `public/images/`. +- Any new UI-critical image should follow the same fallback pattern to avoid broken-image regressions in production. diff --git a/frontend/public/images/hero-fallback.svg b/frontend/public/images/hero-fallback.svg new file mode 100644 index 0000000..cec6798 --- /dev/null +++ b/frontend/public/images/hero-fallback.svg @@ -0,0 +1,26 @@ + + Recipe hero fallback illustration + Soft gradient with simple food-themed shapes used when the hero image is unavailable. + + + + + + + + + + + + + + + Recipe Manager + Fresh meals. Organized beautifully. + + + 🥗 + 🍲 + 🍰 + + diff --git a/frontend/src/assets/visualAssets.ts b/frontend/src/assets/visualAssets.ts new file mode 100644 index 0000000..45f4513 --- /dev/null +++ b/frontend/src/assets/visualAssets.ts @@ -0,0 +1,13 @@ +import heroImage from './hero.png'; + +export const visualAssets = { + hero: { + primary: heroImage, + fallbacks: ['/images/hero-fallback.svg'], + alt: 'Fresh ingredients and plated food', + }, +} as const; + +export function heroImageChain(): string[] { + return [visualAssets.hero.primary, ...visualAssets.hero.fallbacks]; +} diff --git a/frontend/src/pages/RecipeListPage.tsx b/frontend/src/pages/RecipeListPage.tsx index b512def..63c8df6 100644 --- a/frontend/src/pages/RecipeListPage.tsx +++ b/frontend/src/pages/RecipeListPage.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { Link } from 'react-router-dom'; -import heroImage from '../assets/hero.png'; +import { heroImageChain, visualAssets } from '../assets/visualAssets'; import { useRecipes } from '../hooks/useRecipes'; import { useTags } from '../hooks/useTags'; import { RecipeCard } from '../components/RecipeCard'; @@ -18,6 +18,10 @@ export function RecipeListPage() { const [searchTerm, setSearchTerm] = useState(''); const [searchQuery, setSearchQuery] = useState(''); const [selectedTagId, setSelectedTagId] = useState(null); + const [heroCandidateIndex, setHeroCandidateIndex] = useState(0); + + const heroCandidates = heroImageChain(); + const heroSrc = heroCandidates[Math.min(heroCandidateIndex, heroCandidates.length - 1)]; const { recipes, loading, error, hasMore, loadMore } = useRecipes({ search: searchQuery, @@ -81,7 +85,16 @@ export function RecipeListPage() { - + { + if (heroCandidateIndex < heroCandidates.length - 1) { + setHeroCandidateIndex((current) => current + 1); + } + }} + />