Add new content widgets and a dynamic bento grid homepage
Integrates photo gallery, horoscope, recipe, and news widgets into a dynamic MSN-style bento grid homepage. Adds new routes for gallery and updates navigation. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 413891e8-d784-4bea-b9f5-91a5a68316b4 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: ac196a32-ec08-4953-9df7-633cb142cc48 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/413891e8-d784-4bea-b9f5-91a5a68316b4/RVXhOPb Replit-Helium-Checkpoint-Created: true
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
import { useState } from "react";
|
||||
import { ChefHat, X, Clock, Users } from "lucide-react";
|
||||
|
||||
interface Recipe {
|
||||
title: string;
|
||||
image: string;
|
||||
time: string;
|
||||
servings: string;
|
||||
ingredients: string[];
|
||||
steps: string[];
|
||||
}
|
||||
|
||||
const RECIPES: Recipe[] = [
|
||||
{
|
||||
title: "Kaiserschmarrn",
|
||||
image: "https://images.unsplash.com/photo-1621939514649-280e2ee25f60?w=400&h=300&fit=crop",
|
||||
time: "25 Min.",
|
||||
servings: "2 Portionen",
|
||||
ingredients: ["3 Eier", "200 ml Milch", "120 g Mehl", "30 g Zucker", "1 Prise Salz", "50 g Butter", "Rosinen nach Belieben", "Puderzucker"],
|
||||
steps: ["Eigelb, Milch, Mehl und Salz verrühren.", "Eiweiß mit Zucker steif schlagen und unterheben.", "Butter in der Pfanne erhitzen, Teig eingießen.", "Rosinen darüber streuen, von unten goldbraun backen.", "Mit zwei Gabeln in Stücke reißen.", "Mit Puderzucker bestreut servieren."],
|
||||
},
|
||||
{
|
||||
title: "Wiener Schnitzel",
|
||||
image: "https://images.unsplash.com/photo-1599921841143-819065a55cc6?w=400&h=300&fit=crop",
|
||||
time: "30 Min.",
|
||||
servings: "4 Portionen",
|
||||
ingredients: ["4 Kalbsschnitzel", "2 Eier", "Mehl", "Semmelbrösel", "Butterschmalz", "Salz", "Zitrone"],
|
||||
steps: ["Schnitzel dünn klopfen, salzen.", "In Mehl, verquirltem Ei und Semmelbröseln panieren.", "In reichlich heißem Butterschmalz goldbraun backen.", "Auf Küchenpapier abtropfen lassen.", "Mit Zitrone und Petersilienkartoffeln servieren."],
|
||||
},
|
||||
{
|
||||
title: "Apfelstrudel",
|
||||
image: "https://images.unsplash.com/photo-1535920527002-b35e96722eb9?w=400&h=300&fit=crop",
|
||||
time: "60 Min.",
|
||||
servings: "6 Portionen",
|
||||
ingredients: ["250 g Mehl", "1 Ei", "2 EL Öl", "125 ml Wasser", "1 kg Äpfel", "100 g Zucker", "Zimt", "80 g Semmelbrösel", "80 g Butter"],
|
||||
steps: ["Strudelteig kneten, 30 Min. ruhen lassen.", "Äpfel schälen, in dünne Scheiben schneiden.", "Mit Zucker, Zimt und Rosinen mischen.", "Teig dünn ausziehen, Brösel verteilen.", "Füllung auflegen, einrollen.", "Bei 180°C 40 Min. goldbraun backen."],
|
||||
},
|
||||
{
|
||||
title: "Tiroler Knödel",
|
||||
image: "https://images.unsplash.com/photo-1548940740-204726a19be3?w=400&h=300&fit=crop",
|
||||
time: "40 Min.",
|
||||
servings: "4 Portionen",
|
||||
ingredients: ["300 g Knödelbrot", "200 ml Milch", "3 Eier", "150 g Speck", "1 Zwiebel", "Petersilie", "Salz, Pfeffer", "Mehl"],
|
||||
steps: ["Knödelbrot in eine Schüssel geben, warme Milch darüber.", "Speck und Zwiebel anbraten.", "Eier, Speck, Petersilie zum Brot geben, mischen.", "30 Min. rasten lassen.", "Knödel formen, in Salzwasser 15 Min. kochen.", "Mit Butter und Schnittlauch servieren."],
|
||||
},
|
||||
{
|
||||
title: "Sachertorte",
|
||||
image: "https://images.unsplash.com/photo-1578985545062-69928b1d9587?w=400&h=300&fit=crop",
|
||||
time: "90 Min.",
|
||||
servings: "8 Portionen",
|
||||
ingredients: ["150 g Butter", "110 g Zucker", "6 Eier", "130 g Zartbitterschokolade", "130 g Mehl", "Marillenmarmelade", "200 g Kuvertüre"],
|
||||
steps: ["Butter und Zucker schaumig rühren.", "Geschmolzene Schokolade und Eigelb unterrühren.", "Eiweiß steif schlagen, mit Mehl unterheben.", "Bei 170°C 50 Min. backen.", "Auskühlen lassen, mit Marmelade bestreichen.", "Mit Schokoladen-Glasur überziehen."],
|
||||
},
|
||||
{
|
||||
title: "Kärntner Kasnudeln",
|
||||
image: "https://images.unsplash.com/photo-1551183053-bf91a1d81141?w=400&h=300&fit=crop",
|
||||
time: "50 Min.",
|
||||
servings: "4 Portionen",
|
||||
ingredients: ["400 g Mehl", "2 Eier", "Wasser", "500 g Topfen", "200 g Kartoffeln", "Minze", "Kerbel", "Butter", "Salz"],
|
||||
steps: ["Nudelteig aus Mehl, Eiern und Wasser herstellen.", "Kartoffeln kochen und stampfen.", "Mit Topfen, Minze und Kerbel mischen.", "Teig ausrollen, Kreise ausstechen.", "Füllung aufsetzen, Ränder krendeln.", "In Salzwasser kochen, mit Butter servieren."],
|
||||
},
|
||||
];
|
||||
|
||||
function RecipeModal({ recipe, onClose }: { recipe: Recipe; onClose: () => void }) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 bg-black/80 flex items-center justify-center p-4" onClick={onClose} data-testid="modal-recipe">
|
||||
<div
|
||||
className="bg-card rounded-xl border border-card-border max-w-lg w-full max-h-[80vh] overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="relative">
|
||||
<img src={recipe.image} alt={recipe.title} className="w-full h-48 object-cover rounded-t-xl" />
|
||||
<button onClick={onClose} className="absolute top-3 right-3 bg-black/60 rounded-full p-1.5 text-white hover:bg-black/80" data-testid="button-recipe-close">
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-5">
|
||||
<h2 className="text-xl font-bold text-foreground mb-2">{recipe.title}</h2>
|
||||
<div className="flex items-center gap-4 text-sm text-muted-foreground mb-4">
|
||||
<span className="flex items-center gap-1"><Clock className="w-4 h-4" />{recipe.time}</span>
|
||||
<span className="flex items-center gap-1"><Users className="w-4 h-4" />{recipe.servings}</span>
|
||||
</div>
|
||||
|
||||
<h3 className="font-semibold text-foreground text-sm mb-2">Zutaten</h3>
|
||||
<ul className="text-sm text-foreground/80 mb-4 space-y-1">
|
||||
{recipe.ingredients.map((ing, i) => (
|
||||
<li key={i} className="flex items-start gap-2">
|
||||
<span className="text-primary mt-1">•</span>{ing}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<h3 className="font-semibold text-foreground text-sm mb-2">Zubereitung</h3>
|
||||
<ol className="text-sm text-foreground/80 space-y-2">
|
||||
{recipe.steps.map((step, i) => (
|
||||
<li key={i} className="flex gap-2">
|
||||
<span className="font-bold text-primary flex-shrink-0">{i + 1}.</span>{step}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function RecipeWidget() {
|
||||
const [showRecipe, setShowRecipe] = useState<Recipe | null>(null);
|
||||
const dayIndex = Math.floor(Date.now() / 86400000) % RECIPES.length;
|
||||
const recipe = RECIPES[dayIndex];
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setShowRecipe(recipe)}
|
||||
className="bg-card rounded-lg border border-card-border overflow-hidden h-full w-full text-left cursor-pointer group hover:border-primary/50 transition-colors"
|
||||
data-testid="widget-recipe"
|
||||
>
|
||||
<div className="p-3 flex items-center gap-2 border-b border-card-border">
|
||||
<ChefHat className="w-4 h-4 text-primary" />
|
||||
<h3 className="font-bold text-card-foreground text-sm">Rezept des Tages</h3>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<img
|
||||
src={recipe.image}
|
||||
alt={recipe.title}
|
||||
className="w-full aspect-[16/10] object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent" />
|
||||
<div className="absolute bottom-0 left-0 right-0 p-3">
|
||||
<h4 className="text-white font-semibold text-sm">{recipe.title}</h4>
|
||||
<p className="text-white/60 text-xs mt-0.5">{recipe.time} · {recipe.servings}</p>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{showRecipe && <RecipeModal recipe={showRecipe} onClose={() => setShowRecipe(null)} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user