Files
Epicure/apps/web/lib/recipe-placeholder.ts
T
Arnaud 955c4bd9dd fix: varied gradient+icon placeholders for recipes without a cover photo (v0.50.2)
Replaces the single flat emoji-on-muted-bg fallback (same 🍽️/🍹 everywhere)
with a deterministic gradient + food/drink icon per recipe id — pulled from
a small curated palette/icon pool via a string hash, so it's stable across
re-renders but visually varied across a grid of photo-less recipes.

New shared lib/recipe-placeholder.ts + components/recipe/recipe-cover-
placeholder.tsx, applied everywhere the old inline emoji fallback lived:
recipe-grid-card.tsx, recipe-card.tsx, recipes-grid.tsx, and the public
profile page's recipe grid.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 09:47:22 +02:00

48 lines
1.8 KiB
TypeScript

import {
Soup, Salad, Pizza, Sandwich, IceCream, Cookie, Beef, Fish, Cherry,
Carrot, Egg, Drumstick, Croissant, Donut, Cake, ChefHat,
Coffee, Wine, Beer, GlassWater, Martini, CupSoda,
type LucideIcon,
} from "lucide-react";
const GRADIENTS = [
"from-orange-200 to-rose-200 dark:from-orange-950 dark:to-rose-950",
"from-amber-200 to-lime-200 dark:from-amber-950 dark:to-lime-950",
"from-sky-200 to-indigo-200 dark:from-sky-950 dark:to-indigo-950",
"from-emerald-200 to-teal-200 dark:from-emerald-950 dark:to-teal-950",
"from-fuchsia-200 to-purple-200 dark:from-fuchsia-950 dark:to-purple-950",
"from-yellow-200 to-orange-200 dark:from-yellow-950 dark:to-orange-950",
"from-rose-200 to-pink-200 dark:from-rose-950 dark:to-pink-950",
"from-teal-200 to-cyan-200 dark:from-teal-950 dark:to-cyan-950",
];
const DISH_ICONS: LucideIcon[] = [
ChefHat, Soup, Salad, Pizza, Sandwich, IceCream, Cookie, Beef, Fish,
Cherry, Carrot, Egg, Drumstick, Croissant, Donut, Cake,
];
const DRINK_ICONS: LucideIcon[] = [Coffee, Wine, Beer, GlassWater, Martini, CupSoda];
/** Small, fast, deterministic string hash (djb2) — same recipe always gets the same placeholder. */
function hashString(s: string): number {
let hash = 5381;
for (let i = 0; i < s.length; i++) {
hash = (hash * 33) ^ s.charCodeAt(i);
}
return hash >>> 0;
}
export function getRecipePlaceholder(recipe: { id: string; recipeType?: "dish" | "drink" }): {
gradient: string;
Icon: LucideIcon;
} {
const hash = hashString(recipe.id);
const icons = recipe.recipeType === "drink" ? DRINK_ICONS : DISH_ICONS;
return {
gradient: GRADIENTS[hash % GRADIENTS.length]!,
// Different modulus base than gradient's so icon/color pairing doesn't
// repeat in lockstep across ids that happen to share a gradient.
Icon: icons[Math.floor(hash / GRADIENTS.length) % icons.length]!,
};
}