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:
sebastjanartic
2026-02-28 18:26:40 +00:00
parent 8ef815824a
commit d28c36131c
12 changed files with 1847 additions and 174 deletions
+141
View File
@@ -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)} />}
</>
);
}