Add new regional recipes and enhance the horoscope page functionality

Introduce a new /rezepte page with 21 regional recipes featuring AI-generated images. Enhance the /horoskop page with element colors, star ratings, and navigation. Update navigation, routing, and widgets to support these new features.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 413891e8-d784-4bea-b9f5-91a5a68316b4
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ca8c12e6-1e00-476d-86cc-c529591779ac
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/413891e8-d784-4bea-b9f5-91a5a68316b4/oYq1Msd
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
sebastjanartic
2026-02-28 19:58:23 +00:00
parent d0ee926b35
commit 4963edd636
34 changed files with 970 additions and 534 deletions
+23 -65
View File
@@ -3,11 +3,11 @@ import { Link } from "wouter";
import { type Article } from "@shared/schema";
import { format } from "date-fns";
import { de } from "date-fns/locale";
import { Eye, Play, ChevronRight, Images } from "lucide-react";
import { Eye, Play, Images } from "lucide-react";
import { Skeleton } from "@/components/ui/skeleton";
import Header from "@/components/header";
import Footer from "@/components/footer";
import AdSense, { ArticleCardAd } from "@/components/adsense";
import AdSense from "@/components/adsense";
import { PhotoGalleryWidget } from "@/components/photo-gallery";
import { HoroscopeWidget } from "@/components/horoscope-widget";
import { RecipeWidget } from "@/components/recipe-widget";
@@ -129,37 +129,6 @@ function MediumCard({ article }: { article: Article }) {
);
}
function GalleryMediumCard({ images }: { images: GalleryImage[] }) {
const [idx, setIdx] = useState(0);
useEffect(() => {
const timer = setInterval(() => setIdx((i) => (i + 1) % images.length), 5000);
return () => clearInterval(timer);
}, [images.length]);
return (
<Link href="/gallery">
<div className="relative group cursor-pointer rounded-lg overflow-hidden h-full bg-card border border-card-border" data-testid="card-medium-gallery">
<div className="relative">
<div className="overflow-hidden">
<img src={images[idx].large || images[idx].thumb} alt={images[idx].fileName} className="w-full aspect-video object-cover transition-opacity duration-700" style={{ objectPosition: "center 25%" }} loading="lazy" />
</div>
<div className="absolute bottom-2 right-2 bg-black/60 rounded px-1.5 py-0.5 flex items-center gap-1">
<Images className="w-3 h-3 text-white/80" />
<span className="text-[10px] text-white/80">{images.length}</span>
</div>
</div>
<div className="p-3.5">
<div className="flex items-center gap-2 mb-1.5">
<span className="text-[10px] font-medium text-primary">Fotogalerie</span>
</div>
<h3 className="font-semibold text-card-foreground text-sm leading-snug line-clamp-2 group-hover:text-primary transition-colors">Exklusive Backstage-Fotos</h3>
</div>
</div>
</Link>
);
}
function NativeAdCard() {
return (
<div className="relative rounded-lg overflow-hidden h-full bg-card border border-card-border" data-testid="card-native-ad">
@@ -269,16 +238,6 @@ function FeaturedCarousel({ articles, popular, galleryImages }: { articles: Arti
);
}
function SectionLink({ href, label }: { href: string; label: string }) {
return (
<Link href={href}>
<span className="flex items-center gap-1 text-xs text-primary hover:underline cursor-pointer mt-2">
{label} <ChevronRight className="w-3 h-3" />
</span>
</Link>
);
}
function BentoSkeleton() {
return (
<div className="space-y-4">
@@ -318,9 +277,10 @@ export default function Home() {
);
}
const row2Articles = articles.slice(0, 4);
const row3Articles = articles.slice(4, 7);
const row4Articles = articles.slice(7, 10);
const row2Left = articles.slice(0, 2);
const row3Middle = articles.slice(2, 4);
const row4Articles = articles.slice(4, 7);
const row5Articles = articles.slice(7, 10);
return (
<div className="min-h-screen bg-background">
@@ -329,44 +289,42 @@ export default function Home() {
<FeaturedCarousel articles={articles} popular={popular} galleryImages={galleryImages} />
{row2Articles.length > 0 && (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{row2Articles.map((a) => (
<MediumCard key={a.id} article={a} />
))}
</div>
)}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 auto-rows-fr">
<div className="aspect-[4/5]">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{row2Left.map((a) => (
<MediumCard key={a.id} article={a} />
))}
<div className="aspect-[4/5] sm:aspect-auto">
<PhotoGalleryWidget />
</div>
<div className="aspect-[4/5]">
<div className="aspect-[4/5] sm:aspect-auto">
<RecipeWidget />
</div>
</div>
<div className="aspect-[4/5]">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="aspect-[4/5] sm:aspect-auto">
<HoroscopeWidget />
</div>
<div className="aspect-[4/5]">
{row3Middle.map((a) => (
<MediumCard key={a.id} article={a} />
))}
<div className="aspect-[4/5] sm:aspect-auto">
<NewsWidget />
</div>
</div>
{row3Articles.length > 0 && (
{row4Articles.length > 0 && (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{row3Articles.map((a) => (
{row4Articles.map((a) => (
<MediumCard key={a.id} article={a} />
))}
<NativeAdCard />
</div>
)}
{row4Articles.length > 0 && (
{row5Articles.length > 0 && (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{row4Articles.slice(0, 3).map((a) => (
{row5Articles.map((a) => (
<MediumCard key={a.id} article={a} />
))}
<NativeAdCard />
+196 -234
View File
@@ -1,126 +1,17 @@
import { useState } from "react";
import { Link } from "wouter";
import { Star, Heart, Briefcase, TrendingUp, ChevronLeft } from "lucide-react";
import { Star, Heart, Briefcase, TrendingUp, ChevronLeft, Calendar, CalendarDays, Sparkles, Lightbulb } from "lucide-react";
import Header from "@/components/header";
import Footer from "@/components/footer";
import { InArticleAd } from "@/components/adsense";
const SIGNS = [
{ name: "Widder", symbol: "♈", date: "21.03 – 19.04", element: "Feuer", planet: "Mars", color: "Rot", stone: "Diamant" },
{ name: "Stier", symbol: "♉", date: "20.04 – 20.05", element: "Erde", planet: "Venus", color: "Grün", stone: "Smaragd" },
{ name: "Zwillinge", symbol: "♊", date: "21.05 – 20.06", element: "Luft", planet: "Merkur", color: "Gelb", stone: "Achat" },
{ name: "Krebs", symbol: "♋", date: "21.06 – 22.07", element: "Wasser", planet: "Mond", color: "Silber", stone: "Mondstein" },
{ name: "Löwe", symbol: "♌", date: "23.07 – 22.08", element: "Feuer", planet: "Sonne", color: "Gold", stone: "Rubin" },
{ name: "Jungfrau", symbol: "♍", date: "23.08 – 22.09", element: "Erde", planet: "Merkur", color: "Braun", stone: "Saphir" },
{ name: "Waage", symbol: "♎", date: "23.09 – 22.10", element: "Luft", planet: "Venus", color: "Rosa", stone: "Opal" },
{ name: "Skorpion", symbol: "♏", date: "23.10 – 21.11", element: "Wasser", planet: "Pluto", color: "Dunkelrot", stone: "Topas" },
{ name: "Schütze", symbol: "♐", date: "22.11 – 21.12", element: "Feuer", planet: "Jupiter", color: "Violett", stone: "Türkis" },
{ name: "Steinbock", symbol: "♑", date: "22.12 – 19.01", element: "Erde", planet: "Saturn", color: "Schwarz", stone: "Granat" },
{ name: "Wassermann", symbol: "♒", date: "20.01 – 18.02", element: "Luft", planet: "Uranus", color: "Blau", stone: "Amethyst" },
{ name: "Fische", symbol: "♓", date: "19.02 – 20.03", element: "Wasser", planet: "Neptun", color: "Meeresblau", stone: "Aquamarin" },
];
const DAILY_HOROSCOPES = [
{
general: "Ein wunderbarer Tag für neue Begegnungen. Die Sterne stehen günstig für spontane Entscheidungen. Sie spüren eine besondere Energie, die Sie antreibt, Neues zu wagen. Nutzen Sie diese Kraft und gehen Sie auf Menschen zu – unerwartete Gespräche könnten Ihr Leben bereichern. Ihr natürlicher Optimismus strahlt heute besonders hell und zieht positive Situationen an.",
love: "In der Liebe zeigt sich heute eine besondere Verbindung. Singles könnten jemand Besonderen treffen – halten Sie die Augen offen! Paare erleben einen harmonischen Tag voller Zärtlichkeit. Ein romantisches Abendessen oder ein gemeinsamer Spaziergang stärkt Ihre Beziehung.",
career: "Beruflich stehen die Zeichen auf Erfolg. Ein neues Projekt könnte sich anbieten, das Ihre Fähigkeiten perfekt zur Geltung bringt. Zeigen Sie Initiative und Ihr Vorgesetzter wird beeindruckt sein. Finanzielle Entscheidungen sollten heute wohlüberlegt getroffen werden.",
health: "Ihre Vitalität ist heute auf einem Höhepunkt. Nutzen Sie die Energie für sportliche Aktivitäten oder einen langen Spaziergang in der Natur. Achten Sie darauf, ausreichend Wasser zu trinken und gönnen Sie sich am Abend eine entspannende Auszeit.",
tip: "Tragen Sie heute etwas Rotes – es verstärkt Ihre natürliche Ausstrahlung und zieht positive Energie an."
},
{
general: "Heute ist Geduld gefragt. Lassen Sie sich Zeit und genießen Sie die kleinen Freuden des Alltags. Nicht alles muss sofort erledigt werden – manchmal bringt ein langsameres Tempo bessere Ergebnisse. Die Sterne empfehlen Ihnen, sich auf das Wesentliche zu konzentrieren und unnötigen Stress zu vermeiden. Am Nachmittag lichtet sich der Nebel und Sie sehen klarer.",
love: "Die Liebe braucht heute besondere Aufmerksamkeit. Nehmen Sie sich Zeit für tiefgründige Gespräche mit Ihrem Partner. Singles sollten nicht zu schnell urteilen – der erste Eindruck kann täuschen. Ein zweiter Blick lohnt sich, denn wahre Schönheit zeigt sich erst bei näherem Kennenlernen.",
career: "Im Beruf ist Beharrlichkeit der Schlüssel zum Erfolg. Ein schwieriges Projekt erfordert Ihre volle Konzentration. Lassen Sie sich nicht von Rückschlägen entmutigen – Ihr Durchhaltevermögen wird sich langfristig auszahlen. Finanziell ist Sparsamkeit angesagt.",
health: "Achten Sie heute besonders auf Ihre Ernährung. Frisches Obst und Gemüse geben Ihrem Körper die nötigen Vitamine. Eine kurze Meditation am Morgen oder Abend hilft Ihnen, innere Ruhe zu finden und den Alltagsstress abzubauen.",
tip: "Kochen Sie heute Ihr Lieblingsgericht – es nährt nicht nur den Körper, sondern auch die Seele."
},
{
general: "Kommunikation steht im Vordergrund. Ein gutes Gespräch kann heute Wunder wirken. Ihre Worte haben besondere Kraft und können Brücken bauen. Nutzen Sie diese Gabe, um Missverständnisse zu klären oder neue Kontakte zu knüpfen. Am Abend erwartet Sie eine überraschende Nachricht, die Ihre Stimmung hebt.",
love: "Reden Sie über Ihre Gefühle! Heute ist der perfekte Tag, um Ihrem Partner oder einer besonderen Person mitzuteilen, was Sie empfinden. Offenheit wird belohnt und kann Ihre Beziehung auf ein neues Level heben. Singles sollten gesellige Orte aufsuchen.",
career: "Ein wichtiges Meeting oder Telefonat könnte heute den Durchbruch bringen. Bereiten Sie sich gut vor und präsentieren Sie Ihre Ideen selbstbewusst. Teamarbeit steht unter einem guten Stern – gemeinsam erreichen Sie mehr als allein.",
health: "Soziale Kontakte sind heute Ihre beste Medizin. Ein Treffen mit Freunden oder ein anregendes Gespräch mit einem Kollegen hebt Ihre Stimmung und stärkt Ihr Wohlbefinden. Achten Sie auf ausreichend Schlaf – Ihr Geist braucht Erholung.",
tip: "Schreiben Sie jemandem, den Sie schon lange nicht mehr kontaktiert haben – es wird beiden guttun."
},
{
general: "Vertrauen Sie auf Ihre Intuition. Ihr Bauchgefühl weist Ihnen den richtigen Weg. Heute spüren Sie besonders deutlich, was richtig und was falsch ist. Lassen Sie sich nicht von der Meinung anderer beirren – Ihre innere Stimme kennt die Antwort. Am Abend finden Sie Ruhe und Klarheit.",
love: "Emotionale Tiefe prägt heute Ihre Beziehungen. Sie spüren intuitiv, was Ihr Partner braucht, und können liebevoll darauf eingehen. Singles erkennen sofort, ob jemand zu ihnen passt. Vertrauen Sie diesem Gefühl – es wird Sie nicht täuschen.",
career: "Kreative Lösungen sind heute gefragt. Verlassen Sie ausgetretene Pfade und denken Sie um die Ecke. Ihre Intuition führt Sie zu innovativen Ansätzen, die andere beeindrucken werden. Finanzielle Entscheidungen sollten aus dem Bauch heraus getroffen werden.",
health: "Hören Sie heute besonders auf Ihren Körper. Wenn er Ruhe fordert, gönnen Sie sie ihm. Ein warmes Bad, beruhigende Musik oder eine Tasse Kräutertee können Wunder wirken. Yoga oder leichte Dehnübungen harmonisieren Körper und Geist.",
tip: "Zünden Sie am Abend eine Kerze an und nehmen Sie sich zehn Minuten nur für sich – Meditation oder einfaches Stillsein."
},
{
general: "Kreativität und Mut werden heute belohnt. Zeigen Sie, was in Ihnen steckt! Die kosmische Energie unterstützt alle künstlerischen und kreativen Vorhaben. Ob Musik, Malerei oder ein neues Projekt – jetzt ist die Zeit, Ihre Talente zu zeigen. Ihre Begeisterung ist ansteckend und inspiriert auch andere.",
love: "Romantik liegt in der Luft! Überraschen Sie Ihren Partner mit einer liebevollen Geste oder einem spontanen Ausflug. Singles strahlen heute eine besondere Anziehungskraft aus – nutzen Sie das! Ein Flirt beim Konzert oder Festival könnte der Beginn von etwas Schönem sein.",
career: "Ihre kreative Seite ist heute Ihr größtes Kapital. Neue Ideen sprudeln nur so aus Ihnen heraus und finden Anerkennung bei Kollegen und Vorgesetzten. Ein Moment im Rampenlicht steht bevor – genießen Sie ihn! Finanziell könnten sich neue Möglichkeiten ergeben.",
health: "Bewegung und Aktivität tun Ihnen heute besonders gut. Tanzen Sie zu Ihrer Lieblingsmusik, gehen Sie schwimmen oder machen Sie einen Ausflug in die Berge. Ihre Energie ist grenzenlos – nutzen Sie sie für Aktivitäten, die Ihnen Freude bereiten.",
tip: "Hören Sie heute bewusst Volksmusik oder besuchen Sie ein Konzert – Musik nährt die Seele."
},
{
general: "Ordnung und Struktur bringen heute den Erfolg. Nutzen Sie die Energie für wichtige Aufgaben. Ein gut organisierter Tag bringt mehr Ergebnisse als hektisches Multitasking. Erstellen Sie eine Prioritätenliste und arbeiten Sie sie Schritt für Schritt ab. Am Ende des Tages werden Sie stolz auf das Erreichte sein.",
love: "Kleine Aufmerksamkeiten machen heute den Unterschied in der Liebe. Ein liebevoll zubereitetes Frühstück, eine handgeschriebene Nachricht oder ein unerwartetes Kompliment – es sind die Details, die zählen. Singles sollten ihre Ansprüche nicht zu hoch schrauben.",
career: "Perfektion ist heute Ihr Markenzeichen. Nutzen Sie Ihren Blick fürs Detail, um ein wichtiges Projekt zu vervollständigen. Kollegen schätzen Ihre Zuverlässigkeit und Gründlichkeit. Ein Gespräch mit dem Chef könnte positive Entwicklungen anstoßen.",
health: "Strukturieren Sie Ihre Mahlzeiten und achten Sie auf regelmäßige Pausen. Ein aufgeräumter Arbeitsplatz und ein ordentliches Zuhause tragen zu Ihrem Wohlbefinden bei. Am Abend sorgt ein gutes Buch für die nötige Entspannung.",
tip: "Räumen Sie heute eine Schublade oder einen Schrank auf – äußere Ordnung schafft innere Klarheit."
},
{
general: "Harmonie und Ausgeglichenheit bestimmen den Tag. Genießen Sie die Zeit mit Ihren Liebsten. Die Sterne schenken Ihnen heute ein besonderes Gespür für Schönheit und Ästhetik. Umgeben Sie sich mit angenehmen Dingen und Menschen, die Ihnen guttun. Konflikte lassen sich heute leicht lösen.",
love: "Ein Tag wie geschaffen für die Liebe! Paare erleben eine Phase tiefer Verbundenheit und gegenseitigen Verständnisses. Gemeinsame Aktivitäten stärken die Beziehung. Singles sollten sich schick machen und ausgehen – Venus begünstigt neue Begegnungen.",
career: "Diplomatie ist heute Ihre Stärke. Vermitteln Sie geschickt zwischen verschiedenen Positionen und finden Sie Kompromisse, mit denen alle zufrieden sind. Ihre soziale Kompetenz wird anerkannt und könnte zu einer Beförderung führen.",
health: "Gönnen Sie sich heute etwas Besonderes – einen Wellness-Tag, ein entspannendes Bad oder eine Massage. Ihr Körper und Geist verdienen Verwöhnung. Schöne Musik und gutes Essen tragen zu Ihrem Wohlbefinden bei.",
tip: "Besuchen Sie heute ein Museum, eine Ausstellung oder einen schönen Garten – Schönheit nährt die Seele."
},
{
general: "Tiefgründige Erkenntnisse warten auf Sie. Nehmen Sie sich Zeit für Reflexion. Heute können Sie unter die Oberfläche schauen und verborgene Wahrheiten entdecken. Ihre analytische Kraft ist besonders stark – nutzen Sie sie, um wichtige Lebensfragen zu beantworten. Transformation und Wandel liegen in der Luft.",
love: "Intensive Emotionen prägen heute Ihre Beziehungen. Tiefe Gespräche über Wünsche und Sehnsüchte bringen Sie und Ihren Partner näher zusammen. Singles könnten eine magnetische Anziehung zu einer bestimmten Person spüren – lassen Sie sich darauf ein.",
career: "Forschen Sie heute tiefer – ob bei der Arbeit oder in persönlichen Projekten. Ihre Fähigkeit, Zusammenhänge zu erkennen, die anderen verborgen bleiben, macht Sie heute unverzichtbar. Finanzielle Angelegenheiten erfordern gründliche Analyse.",
health: "Emotionale Reinigung steht an. Lassen Sie alte Verletzungen los und vergeben Sie – sich selbst und anderen. Ein Tagebucheintrag oder ein Gespräch mit einer Vertrauensperson kann befreiend wirken. Achten Sie auf ausreichend Schlaf.",
tip: "Schreiben Sie heute Ihre Gedanken und Gefühle auf – das Aufschreiben wirkt klärend und befreiend."
},
{
general: "Abenteuerlust liegt in der Luft! Planen Sie etwas Besonderes für heute. Die Sterne wecken Ihre Reiselust und den Wunsch nach neuen Erfahrungen. Ob ein Tagesausflug in die Berge, ein neues Restaurant oder ein spannendes Buch – erweitern Sie Ihren Horizont! Optimismus und Lebensfreude begleiten Sie durch den Tag.",
love: "Abenteuer zu zweit stärken die Beziehung. Planen Sie etwas Ungewöhnliches mit Ihrem Partner – ein spontaner Ausflug oder ein gemeinsames Hobby. Singles finden die Liebe an unerwarteten Orten – vielleicht bei einer Wanderung oder auf einem Volksfest.",
career: "Denken Sie heute groß! Neue berufliche Chancen könnten sich am Horizont zeigen. Internationale Kontakte oder Weiterbildungen stehen unter einem guten Stern. Ihre Begeisterungsfähigkeit öffnet Türen, die anderen verschlossen bleiben.",
health: "Bewegung an der frischen Luft ist heute das beste Rezept für Körper und Seele. Eine Wanderung, Radtour oder ein Spaziergang durch die Natur laden Ihre Batterien auf. Probieren Sie ein neues Gericht aus einer anderen Kultur – es bereichert Ihren Speiseplan.",
tip: "Machen Sie heute etwas, das Sie noch nie gemacht haben – neue Erfahrungen halten jung und vital."
},
{
general: "Disziplin und Ausdauer zahlen sich aus. Bleiben Sie an Ihren Zielen dran. Die kosmische Energie unterstützt heute alle langfristigen Pläne und ambitionierten Vorhaben. Setzen Sie sich realistische Ziele und arbeiten Sie konsequent darauf hin. Geduld ist Ihre Stärke – Erfolg kommt Schritt für Schritt.",
love: "Stabilität und Verlässlichkeit sind heute die Pfeiler Ihrer Beziehung. Zeigen Sie Ihrem Partner, dass er sich auf Sie verlassen kann. Gemeinsame Zukunftspläne zu schmieden stärkt Ihre Verbindung. Singles sollten nach jemandem Ausschau halten, der ihre Werte teilt.",
career: "Heute ist der Tag für wichtige berufliche Entscheidungen. Ihre Erfahrung und Kompetenz werden anerkannt. Führungsqualitäten kommen zum Vorschein und ebnen den Weg für einen Aufstieg. Finanzielle Disziplin zahlt sich langfristig aus.",
health: "Regelmäßigkeit ist heute der Schlüssel zu Ihrem Wohlbefinden. Halten Sie sich an Ihre Routinen – regelmäßige Mahlzeiten, Bewegung und ausreichend Schlaf. Ihre Knochen und Gelenke freuen sich über sanfte Dehnübungen.",
tip: "Setzen Sie sich heute ein kleines Ziel und erreichen Sie es – Erfolgserlebnisse stärken das Selbstvertrauen."
},
{
general: "Originelle Ideen kommen heute von ganz allein. Lassen Sie Ihrer Fantasie freien Lauf. Die Sterne begünstigen unkonventionelles Denken und innovative Lösungen. Sie sehen Dinge aus einer einzigartigen Perspektive, die andere inspiriert. Gemeinschaftliche Aktivitäten und Freundschaften stehen unter einem besonders guten Stern.",
love: "Freiheit und Nähe in Balance zu halten ist heute das Thema in der Liebe. Geben Sie Ihrem Partner Raum und genießen Sie gleichzeitig die gemeinsame Zeit. Singles treffen heute interessante Menschen in Gruppen oder bei Vereinsaktivitäten.",
career: "Innovation ist heute gefragt! Ihre unkonventionellen Ideen fallen auf fruchtbaren Boden. Teamarbeit bringt heute die besten Ergebnisse – tauschen Sie sich mit Gleichgesinnten aus. Technologische Neuerungen könnten Ihre Arbeit revolutionieren.",
health: "Gruppenaktivitäten wie Sport im Verein, Wandergruppen oder gemeinsames Kochen tun Ihnen heute besonders gut. Der soziale Aspekt stärkt nicht nur den Körper, sondern auch die Seele. Probieren Sie eine neue Sportart oder Entspannungstechnik aus.",
tip: "Engagieren Sie sich heute für eine gute Sache – Altruismus macht glücklich und erfüllt."
},
{
general: "Einfühlungsvermögen und Mitgefühl stärken heute Ihre Beziehungen. Die Sterne schärfen Ihre Sensibilität und Ihr Gespür für die Gefühle anderer. Kreative und spirituelle Aktivitäten stehen unter einem besonders guten Stern. Musik, Kunst und Natur sind heute Ihre Kraftquellen.",
love: "Romantische Träume können heute wahr werden. Ihre einfühlsame Art berührt andere Menschen tief. Paare erleben Momente tiefer emotionaler Verbundenheit. Singles könnten eine fast schicksalhafte Begegnung haben – bei einem Konzert, am Wasser oder in einer kreativen Umgebung.",
career: "Kreative Berufe profitieren heute besonders von der kosmischen Energie. Musiker, Künstler und alle, die mit Menschen arbeiten, haben einen glücklichen Tag. Lassen Sie sich von Ihrer Intuition leiten – sie führt Sie zu den richtigen Entscheidungen.",
health: "Wasser ist heute Ihr Element – ob ein Besuch am See, ein langes Bad oder einfach genug trinken. Meditation und Entspannungsübungen harmonisieren Körper und Geist. Gönnen Sie sich Musik, die Ihre Seele berührt – Volksmusik oder klassische Klänge wirken heilsam.",
tip: "Setzen Sie sich heute an einen ruhigen Ort in der Natur und lauschen Sie – die Stille hat heilende Kraft."
},
];
function getDayOfYear(): number {
return Math.floor((Date.now() - new Date(new Date().getFullYear(), 0, 0).getTime()) / 86400000);
}
function getHoroscope(signIndex: number) {
const day = getDayOfYear();
return DAILY_HOROSCOPES[(signIndex + day) % DAILY_HOROSCOPES.length];
}
function getRating(signIndex: number, category: string): number {
const day = getDayOfYear();
const hash = (signIndex * 7 + day * 3 + category.length) % 5;
return Math.max(3, hash + 3);
}
import {
SIGNS,
ELEMENT_COLORS,
getHoroscope,
getRating,
getLuckyNumbers,
getDailyColor,
} from "@/lib/horoscope-data";
function StarRating({ count }: { count: number }) {
return (
@@ -134,6 +25,7 @@ function StarRating({ count }: { count: number }) {
export default function HoroscopePage() {
const [selected, setSelected] = useState<number | null>(null);
const [tab, setTab] = useState<"daily" | "weekly" | "monthly">("daily");
return (
<div className="min-h-screen bg-background">
@@ -155,132 +47,202 @@ export default function HoroscopePage() {
</p>
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 lg:grid-cols-12 gap-3 mb-10">
{SIGNS.map((sign, i) => (
<button
key={sign.name}
onClick={() => setSelected(i)}
className={`flex flex-col items-center p-3 rounded-xl transition-all duration-200 border ${
selected === i
? "bg-primary/20 border-primary shadow-lg shadow-primary/10 scale-105"
: "bg-card border-card-border hover:border-primary/50 hover:bg-card/80"
}`}
data-testid={`button-sign-select-${sign.name.toLowerCase()}`}
>
<span className="text-3xl mb-1">{sign.symbol}</span>
<span className="text-[10px] font-medium text-foreground">{sign.name}</span>
<span className="text-[8px] text-muted-foreground">{sign.date.split(" – ")[0]}</span>
</button>
))}
{SIGNS.map((sign, i) => {
const ec = ELEMENT_COLORS[sign.element];
return (
<button
key={sign.name}
onClick={() => { setSelected(i); setTab("daily"); }}
className={`flex flex-col items-center p-3 rounded-xl transition-all duration-200 border ${
selected === i
? `${ec.bg} ${ec.border} shadow-lg ${ec.glow} scale-105`
: "bg-card border-card-border hover:border-primary/50 hover:bg-card/80"
}`}
data-testid={`button-sign-select-${sign.name.toLowerCase()}`}
>
<span className="text-3xl mb-1">{sign.symbol}</span>
<span className={`text-[10px] font-medium ${selected === i ? ec.text : "text-foreground"}`}>{sign.name}</span>
<span className="text-[8px] text-muted-foreground">{sign.date.split(" – ")[0]}</span>
</button>
);
})}
</div>
{selected === null ? (
<div className="text-center py-16" data-testid="text-horoscope-prompt">
<span className="text-6xl block mb-4">✨</span>
<Sparkles className="w-16 h-16 text-primary mx-auto mb-4" />
<p className="text-muted-foreground text-lg">Wählen Sie Ihr Sternzeichen oben aus</p>
</div>
) : (
<div className="space-y-0">
<div className="bg-card rounded-xl border border-card-border p-6 md:p-8" data-testid={`card-horoscope-${SIGNS[selected].name.toLowerCase()}`}>
<div className="flex items-center gap-4 mb-6">
<span className="text-6xl">{SIGNS[selected].symbol}</span>
<div>
<h2 className="text-2xl font-bold text-foreground">{SIGNS[selected].name}</h2>
<p className="text-sm text-muted-foreground">{SIGNS[selected].date}</p>
<div className="flex flex-wrap gap-3 mt-2">
<span className="text-xs bg-muted px-2 py-0.5 rounded text-muted-foreground">Element: {SIGNS[selected].element}</span>
<span className="text-xs bg-muted px-2 py-0.5 rounded text-muted-foreground">Planet: {SIGNS[selected].planet}</span>
<span className="text-xs bg-muted px-2 py-0.5 rounded text-muted-foreground">Farbe: {SIGNS[selected].color}</span>
<span className="text-xs bg-muted px-2 py-0.5 rounded text-muted-foreground">Stein: {SIGNS[selected].stone}</span>
</div>
</div>
</div>
{(() => {
const sign = SIGNS[selected];
const ec = ELEMENT_COLORS[sign.element];
const horoscope = getHoroscope(selected);
const luckyNums = getLuckyNumbers(selected);
const dailyColor = getDailyColor(selected);
<div className="grid grid-cols-3 gap-4 mb-6">
<div className="bg-muted/30 rounded-lg p-3 text-center">
<Heart className="w-4 h-4 text-primary mx-auto mb-1" />
<p className="text-[10px] text-muted-foreground mb-1">Liebe</p>
<StarRating count={getRating(selected, "love")} />
</div>
<div className="bg-muted/30 rounded-lg p-3 text-center">
<Briefcase className="w-4 h-4 text-primary mx-auto mb-1" />
<p className="text-[10px] text-muted-foreground mb-1">Beruf</p>
<StarRating count={getRating(selected, "career")} />
</div>
<div className="bg-muted/30 rounded-lg p-3 text-center">
<TrendingUp className="w-4 h-4 text-primary mx-auto mb-1" />
<p className="text-[10px] text-muted-foreground mb-1">Gesundheit</p>
<StarRating count={getRating(selected, "health")} />
</div>
</div>
<div className="space-y-5">
<div>
<h3 className="font-semibold text-foreground flex items-center gap-2 mb-2">
<Star className="w-4 h-4 text-primary" /> Allgemein
</h3>
<p className="text-sm text-foreground/80 leading-relaxed">{getHoroscope(selected).general}</p>
</div>
<div>
<h3 className="font-semibold text-foreground flex items-center gap-2 mb-2">
<Heart className="w-4 h-4 text-primary" /> Liebe & Partnerschaft
</h3>
<p className="text-sm text-foreground/80 leading-relaxed">{getHoroscope(selected).love}</p>
</div>
</div>
</div>
<InArticleAd />
<div className="bg-card rounded-xl border border-card-border p-6 md:p-8" data-testid="card-horoscope-detail-2">
<div className="space-y-5">
<div>
<h3 className="font-semibold text-foreground flex items-center gap-2 mb-2">
<Briefcase className="w-4 h-4 text-primary" /> Beruf & Finanzen
</h3>
<p className="text-sm text-foreground/80 leading-relaxed">{getHoroscope(selected).career}</p>
</div>
<div>
<h3 className="font-semibold text-foreground flex items-center gap-2 mb-2">
<TrendingUp className="w-4 h-4 text-primary" /> Gesundheit & Wohlbefinden
</h3>
<p className="text-sm text-foreground/80 leading-relaxed">{getHoroscope(selected).health}</p>
</div>
<div className="bg-primary/10 border border-primary/20 rounded-lg p-4">
<p className="text-sm text-foreground/90 font-medium flex items-start gap-2">
<span className="text-primary text-lg leading-none">💡</span>
<span><strong>Tipp des Tages:</strong> {getHoroscope(selected).tip}</span>
</p>
</div>
</div>
</div>
<InArticleAd />
<div className="bg-card rounded-xl border border-card-border p-6" data-testid="card-horoscope-others">
<h3 className="font-semibold text-foreground mb-4">Weitere Sternzeichen entdecken</h3>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3">
{SIGNS.filter((_, i) => i !== selected).map((sign, i) => {
const origIdx = SIGNS.findIndex(s => s.name === sign.name);
return (
<button
key={sign.name}
onClick={() => { setSelected(origIdx); window.scrollTo({ top: 0, behavior: "smooth" }); }}
className="bg-muted/30 hover:bg-muted/50 border border-card-border hover:border-primary/50 rounded-lg p-3 transition-all text-left"
data-testid={`button-sign-other-${sign.name.toLowerCase()}`}
>
<div className="flex items-center gap-2 mb-1">
<span className="text-xl">{sign.symbol}</span>
<span className="font-medium text-sm text-foreground">{sign.name}</span>
return (
<>
<div className="bg-card rounded-xl border border-card-border p-6 md:p-8" data-testid={`card-horoscope-${sign.name.toLowerCase()}`}>
<div className="flex items-start gap-4 mb-6">
<div className={`w-20 h-20 rounded-2xl ${ec.bg} border ${ec.border} flex items-center justify-center flex-shrink-0`}>
<span className="text-5xl">{sign.symbol}</span>
</div>
<p className="text-[10px] text-muted-foreground line-clamp-2">{getHoroscope(origIdx).general.slice(0, 80)}...</p>
</button>
);
})}
</div>
</div>
<div className="flex-1">
<h2 className={`text-2xl font-bold ${ec.text}`}>{sign.name}</h2>
<p className="text-sm text-muted-foreground">{sign.date}</p>
<div className="flex flex-wrap gap-2 mt-2">
<span className={`text-[10px] font-medium px-2 py-0.5 rounded ${ec.bg} ${ec.text} border ${ec.border}`}>{sign.element}</span>
<span className="text-[10px] bg-muted px-2 py-0.5 rounded text-muted-foreground">Planet: {sign.planet}</span>
<span className="text-[10px] bg-muted px-2 py-0.5 rounded text-muted-foreground">Farbe: {sign.color}</span>
<span className="text-[10px] bg-muted px-2 py-0.5 rounded text-muted-foreground">Stein: {sign.stone}</span>
</div>
</div>
</div>
<div className="grid grid-cols-3 sm:grid-cols-6 gap-3 mb-6">
<div className="bg-muted/30 rounded-lg p-3 text-center">
<Heart className="w-4 h-4 text-red-400 mx-auto mb-1" />
<p className="text-[10px] text-muted-foreground mb-1">Liebe</p>
<StarRating count={getRating(selected, "love")} />
</div>
<div className="bg-muted/30 rounded-lg p-3 text-center">
<Briefcase className="w-4 h-4 text-amber-400 mx-auto mb-1" />
<p className="text-[10px] text-muted-foreground mb-1">Beruf</p>
<StarRating count={getRating(selected, "career")} />
</div>
<div className="bg-muted/30 rounded-lg p-3 text-center">
<TrendingUp className="w-4 h-4 text-emerald-400 mx-auto mb-1" />
<p className="text-[10px] text-muted-foreground mb-1">Gesundheit</p>
<StarRating count={getRating(selected, "health")} />
</div>
<div className="bg-muted/30 rounded-lg p-3 text-center">
<Sparkles className="w-4 h-4 text-violet-400 mx-auto mb-1" />
<p className="text-[10px] text-muted-foreground mb-1">Glückszahlen</p>
<p className="text-xs font-bold text-foreground" data-testid="text-lucky-numbers">{luckyNums.join(", ")}</p>
</div>
<div className="bg-muted/30 rounded-lg p-3 text-center">
<div className="w-4 h-4 rounded-full bg-primary/60 mx-auto mb-1" />
<p className="text-[10px] text-muted-foreground mb-1">Tagesfarbe</p>
<p className="text-xs font-bold text-foreground" data-testid="text-daily-color">{dailyColor}</p>
</div>
<div className="bg-muted/30 rounded-lg p-3 text-center">
<Heart className="w-4 h-4 text-pink-400 mx-auto mb-1" />
<p className="text-[10px] text-muted-foreground mb-1">Kompatibel</p>
<p className="text-[10px] font-medium text-foreground leading-tight" data-testid="text-compatible-signs">{sign.compatible.join(", ")}</p>
</div>
</div>
<div className="flex gap-1 mb-6 bg-muted/30 rounded-lg p-1">
{(["daily", "weekly", "monthly"] as const).map((t) => (
<button
key={t}
onClick={() => setTab(t)}
className={`flex-1 text-xs font-medium py-2 px-3 rounded-md transition-all flex items-center justify-center gap-1.5 ${
tab === t ? "bg-primary text-white shadow" : "text-muted-foreground hover:text-foreground"
}`}
data-testid={`button-tab-${t}`}
>
{t === "daily" && <Star className="w-3 h-3" />}
{t === "weekly" && <Calendar className="w-3 h-3" />}
{t === "monthly" && <CalendarDays className="w-3 h-3" />}
{t === "daily" ? "Heute" : t === "weekly" ? "Woche" : "Monat"}
</button>
))}
</div>
{tab === "daily" && (
<div className="space-y-5" data-testid="section-horoscope-daily">
<div>
<h3 className="font-semibold text-foreground flex items-center gap-2 mb-2">
<Star className="w-4 h-4 text-primary" /> Allgemein
</h3>
<p className="text-sm text-foreground/80 leading-relaxed" data-testid="text-horoscope-general">{horoscope.general}</p>
</div>
<div>
<h3 className="font-semibold text-foreground flex items-center gap-2 mb-2">
<Heart className="w-4 h-4 text-red-400" /> Liebe & Partnerschaft
</h3>
<p className="text-sm text-foreground/80 leading-relaxed" data-testid="text-horoscope-love">{horoscope.love}</p>
</div>
</div>
)}
{tab === "weekly" && (
<div data-testid="section-horoscope-weekly">
<h3 className="font-semibold text-foreground flex items-center gap-2 mb-2">
<Calendar className="w-4 h-4 text-primary" /> Wochenhoroskop
</h3>
<p className="text-sm text-foreground/80 leading-relaxed" data-testid="text-horoscope-weekly">{horoscope.weekly}</p>
</div>
)}
{tab === "monthly" && (
<div data-testid="section-horoscope-monthly">
<h3 className="font-semibold text-foreground flex items-center gap-2 mb-2">
<CalendarDays className="w-4 h-4 text-primary" /> Monatshoroskop
</h3>
<p className="text-sm text-foreground/80 leading-relaxed" data-testid="text-horoscope-monthly">{horoscope.monthly}</p>
</div>
)}
</div>
<InArticleAd />
{tab === "daily" && (
<div className="bg-card rounded-xl border border-card-border p-6 md:p-8" data-testid="card-horoscope-detail-2">
<div className="space-y-5">
<div>
<h3 className="font-semibold text-foreground flex items-center gap-2 mb-2">
<Briefcase className="w-4 h-4 text-amber-400" /> Beruf & Finanzen
</h3>
<p className="text-sm text-foreground/80 leading-relaxed">{horoscope.career}</p>
</div>
<div>
<h3 className="font-semibold text-foreground flex items-center gap-2 mb-2">
<TrendingUp className="w-4 h-4 text-emerald-400" /> Gesundheit & Wohlbefinden
</h3>
<p className="text-sm text-foreground/80 leading-relaxed">{horoscope.health}</p>
</div>
<div className="bg-primary/10 border border-primary/20 rounded-lg p-4" data-testid="card-horoscope-tip">
<p className="text-sm text-foreground/90 font-medium flex items-start gap-2">
<Lightbulb className="w-5 h-5 text-primary flex-shrink-0 mt-0.5" />
<span><strong>Tipp des Tages:</strong> {horoscope.tip}</span>
</p>
</div>
</div>
</div>
)}
<InArticleAd />
<div className="bg-card rounded-xl border border-card-border p-6" data-testid="card-horoscope-others">
<h3 className="font-semibold text-foreground mb-4">Weitere Sternzeichen entdecken</h3>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3">
{SIGNS.filter((_, i) => i !== selected).map((s) => {
const origIdx = SIGNS.findIndex((x) => x.name === s.name);
const sEc = ELEMENT_COLORS[s.element];
return (
<button
key={s.name}
onClick={() => { setSelected(origIdx); setTab("daily"); window.scrollTo({ top: 0, behavior: "smooth" }); }}
className={`${sEc.bg} hover:opacity-80 border ${sEc.border} rounded-lg p-3 transition-all text-left`}
data-testid={`button-sign-other-${s.name.toLowerCase()}`}
>
<div className="flex items-center gap-2 mb-1">
<span className="text-xl">{s.symbol}</span>
<span className={`font-medium text-sm ${sEc.text}`}>{s.name}</span>
</div>
<p className="text-[10px] text-muted-foreground">{s.date}</p>
</button>
);
})}
</div>
</div>
</>
);
})()}
</div>
)}
</main>
+365
View File
@@ -0,0 +1,365 @@
import { useState } from "react";
import { Link } from "wouter";
import { ChefHat, Clock, Users, X, ChevronLeft } from "lucide-react";
import Header from "@/components/header";
import Footer from "@/components/footer";
import { InArticleAd } from "@/components/adsense";
interface Recipe {
title: string;
image: string;
time: string;
servings: string;
ingredients: string[];
steps: string[];
description: string;
category: string;
}
interface RecipeRegion {
name: string;
recipes: Recipe[];
}
const RECIPE_REGIONS: RecipeRegion[] = [
{
name: "Österreich",
recipes: [
{
title: "Kaiserschmarrn",
image: "/uploads/recipe-kaiserschmarrn.png",
time: "25 Min.",
servings: "2 Portionen",
category: "Nachspeise",
description: "Der Klassiker der österreichischen Küche -- fluffig, süß und einfach unwiderstehlich.",
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: "/uploads/recipe-wiener-schnitzel.png",
time: "30 Min.",
servings: "4 Portionen",
category: "Hauptspeise",
description: "Das berühmteste Gericht Wiens -- goldbraun paniert und herrlich knusprig.",
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: "/uploads/recipe-apfelstrudel.png",
time: "60 Min.",
servings: "6 Portionen",
category: "Nachspeise",
description: "Dünn ausgezogener Strudelteig mit saftiger Apfelfüllung -- ein Traum aus Omas Küche.",
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: "/uploads/recipe-tiroler-knoedel.png",
time: "40 Min.",
servings: "4 Portionen",
category: "Hauptspeise",
description: "Herzhafte Speckknödel aus Tirol -- perfekt zu einer kräftigen Suppe oder Sauerkraut.",
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: "/uploads/recipe-sachertorte.png",
time: "90 Min.",
servings: "8 Portionen",
category: "Nachspeise",
description: "Die weltberühmte Wiener Schokoladentorte mit Marillenmarmelade und Glasur.",
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: "/uploads/recipe-kaerntner-kasnudeln.png",
time: "50 Min.",
servings: "4 Portionen",
category: "Hauptspeise",
description: "Handgemachte Teigtaschen aus Kärnten mit Topfen-Kartoffel-Füllung und frischer Minze.",
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."],
},
{
title: "Germknödel",
image: "/uploads/recipe-germknoedel.png",
time: "120 Min.",
servings: "4 Portionen",
category: "Nachspeise",
description: "Fluffige Hefeteig-Knödel mit Powidl-Füllung, Vanillesoße und Mohn-Zucker-Topping.",
ingredients: ["500 g Mehl", "250 ml Milch", "1 Würfel Hefe", "60 g Zucker", "60 g Butter", "2 Eigelb", "Powidl (Pflaumenmus)", "Mohn", "Vanillesoße"],
steps: ["Hefeteig zubereiten und 1 Stunde gehen lassen.", "Teig in Portionen teilen, flach drücken.", "Je 1 TL Powidl in die Mitte geben.", "Zu Knödeln formen, nochmals 20 Min. gehen lassen.", "In Salzwasser oder über Dampf 15 Min. garen.", "Mit Mohn-Zucker und Vanillesoße servieren."],
},
{
title: "Tafelspitz",
image: "/uploads/recipe-tafelspitz.png",
time: "180 Min.",
servings: "6 Portionen",
category: "Hauptspeise",
description: "Zart gekochtes Rindfleisch mit Schnittlauchsoße und Apfelkren -- ein Festmahl der Wiener Küche.",
ingredients: ["1,5 kg Tafelspitz", "Suppengemüse", "Lorbeerblatt", "Pfefferkörner", "Schnittlauch", "Sauerrahm", "Äpfel", "Kren (Meerrettich)"],
steps: ["Fleisch in kaltes Wasser legen, langsam erhitzen.", "Schaum abschöpfen, Gemüse und Gewürze zugeben.", "3 Stunden sanft köcheln lassen.", "Schnittlauchsoße aus Sauerrahm zubereiten.", "Apfelkren frisch reiben und mischen.", "Fleisch aufschneiden, mit Soßen und Gemüse servieren."],
},
],
},
{
name: "Bayern",
recipes: [
{
title: "Schweinshaxe",
image: "/uploads/recipe-schweinshaxe.png",
time: "180 Min.",
servings: "4 Portionen",
category: "Hauptspeise",
description: "Knusprig gebratene Schweinshaxe mit krachender Kruste -- das Herzstück jeder bayerischen Wirtschaft.",
ingredients: ["4 Schweinshaxen", "2 Zwiebeln", "2 Karotten", "500 ml Dunkelbier", "Kümmel", "Knoblauch", "Salz, Pfeffer", "Majoran"],
steps: ["Haxen waschen, Schwarte rautenförmig einschneiden.", "Mit Salz, Kümmel und Knoblauch einreiben.", "Mit Gemüse und Bier in die Bratpfanne geben.", "Bei 180°C ca. 2,5 Stunden braten.", "Regelmäßig mit Bratensud übergießen.", "Zum Schluss Grill zuschalten für knusprige Kruste."],
},
{
title: "Obatzda",
image: "/uploads/recipe-obatzda.png",
time: "15 Min.",
servings: "4 Portionen",
category: "Vorspeise",
description: "Der bayerische Biergarten-Klassiker -- würziger Camembert-Aufstrich mit Brezn.",
ingredients: ["200 g reifer Camembert", "100 g Frischkäse", "1 kleine Zwiebel", "1 EL Butter", "Paprikapulver", "Kümmel", "Salz, Pfeffer", "Schnittlauch"],
steps: ["Camembert mit einer Gabel zerdrücken.", "Frischkäse und weiche Butter untermischen.", "Fein gehackte Zwiebel dazugeben.", "Mit Paprika, Kümmel, Salz und Pfeffer würzen.", "Mindestens 1 Stunde durchziehen lassen.", "Mit Schnittlauch bestreut zu frischen Brezn servieren."],
},
{
title: "Dampfnudeln",
image: "/uploads/recipe-dampfnudeln.png",
time: "90 Min.",
servings: "4 Portionen",
category: "Nachspeise",
description: "Bayerische Dampfnudeln mit goldener Kruste und Vanillesoße -- wie bei der Oma auf dem Bauernhof.",
ingredients: ["500 g Mehl", "250 ml Milch", "1 Würfel Hefe", "80 g Zucker", "80 g Butter", "2 Eier", "1 Prise Salz", "Vanillesoße"],
steps: ["Hefe in lauwarmer Milch auflösen.", "Mit Mehl, Zucker, Eiern und Butter zu einem Teig kneten.", "1 Stunde gehen lassen, bis sich das Volumen verdoppelt.", "Kugeln formen und nochmals 20 Min. ruhen lassen.", "In einem geschlossenen Topf mit Milch-Butter-Gemisch dämpfen.", "Servieren, wenn die Kruste goldbraun knistert."],
},
{
title: "Käsespätzle",
image: "/uploads/recipe-kaesespaetzle.png",
time: "45 Min.",
servings: "4 Portionen",
category: "Hauptspeise",
description: "Handgeschabte Spätzle mit würzigem Bergkäse und knusprigen Röstzwiebeln -- ein Allgäuer Traum.",
ingredients: ["400 g Mehl", "4 Eier", "200 ml Wasser", "300 g Bergkäse", "3 große Zwiebeln", "Butter", "Salz, Pfeffer", "Muskatnuss"],
steps: ["Mehl, Eier, Wasser und Salz zu einem zähen Teig schlagen.", "Durch ein Spätzlebrett in kochendes Salzwasser schaben.", "Wenn die Spätzle oben schwimmen, abschöpfen.", "Zwiebeln in Butter goldbraun rösten.", "Spätzle und geriebenen Käse schichtweise in eine Form geben.", "Mit Röstzwiebeln krönen und kurz überbacken."],
},
],
},
{
name: "Schwaben & Baden",
recipes: [
{
title: "Maultaschen",
image: "/uploads/recipe-maultaschen.png",
time: "90 Min.",
servings: "4 Portionen",
category: "Hauptspeise",
description: "Schwäbische Herrgottsbscheißerle -- gefüllte Teigtaschen, die einst das Fleisch vor Gott versteckten.",
ingredients: ["Nudelteig (400 g Mehl, 4 Eier)", "300 g Hackfleisch", "200 g Spinat", "1 Brötchen", "1 Zwiebel", "2 Eier", "Petersilie", "Muskatnuss, Salz, Pfeffer"],
steps: ["Nudelteig kneten und 30 Min. ruhen lassen.", "Brötchen einweichen, Spinat blanchieren und hacken.", "Hackfleisch, Spinat, Brötchen, Eier und Gewürze mischen.", "Teig dünn ausrollen, Füllung in Häufchen setzen.", "Zuklappen, Taschen ausschneiden, Ränder fest andrücken.", "In kräftiger Brühe 10 Min. ziehen lassen oder anbraten."],
},
{
title: "Zwiebelrostbraten",
image: "/uploads/recipe-zwiebelrostbraten.png",
time: "40 Min.",
servings: "4 Portionen",
category: "Hauptspeise",
description: "Saftiges Rostbraten-Stück mit einem Berg goldener Röstzwiebeln -- der schwäbische Sonntagsklassiker.",
ingredients: ["4 Scheiben Roastbeef (je 200 g)", "6 große Zwiebeln", "3 EL Mehl", "Butterschmalz", "200 ml Brühe", "Salz, Pfeffer", "1 Schuss Rotwein"],
steps: ["Zwiebeln in Ringe schneiden, in Mehl wenden.", "In heißem Butterschmalz goldbraun und knusprig frittieren.", "Fleisch salzen, pfeffern und scharf anbraten.", "Fleisch herausnehmen und warm halten.", "Bratensatz mit Rotwein und Brühe ablöschen.", "Fleisch mit Soße und einem Berg Röstzwiebeln servieren."],
},
{
title: "Flammkuchen",
image: "/uploads/recipe-flammkuchen.png",
time: "30 Min.",
servings: "4 Portionen",
category: "Hauptspeise",
description: "Hauchdünner elsässisch-badischer Flammkuchen mit Crème fraîche, Speck und Zwiebeln.",
ingredients: ["300 g Mehl", "150 ml Wasser", "3 EL Öl", "200 g Crème fraîche", "200 g Speck", "3 Zwiebeln", "Salz, Pfeffer", "Muskatnuss"],
steps: ["Mehl, Wasser, Öl und Salz zu einem dünnen Teig kneten.", "Sehr dünn ausrollen auf einem bemehlten Blech.", "Crème fraîche gleichmäßig verstreichen.", "Mit dünnen Zwiebelringen und Speckwürfeln belegen.", "Bei 250°C auf unterster Schiene 12-15 Min. backen.", "Sofort heiß servieren, am besten mit einem Glas Weißwein."],
},
],
},
{
name: "Südtirol & Alpen",
recipes: [
{
title: "Schlutzkrapfen",
image: "/uploads/recipe-schlutzkrapfen.png",
time: "60 Min.",
servings: "4 Portionen",
category: "Hauptspeise",
description: "Südtiroler Halbmond-Teigtaschen mit Spinat-Ricotta-Füllung und brauner Butter.",
ingredients: ["300 g Roggenmehl", "100 g Weizenmehl", "3 Eier", "Wasser", "400 g Spinat", "200 g Ricotta", "1 Zwiebel", "Butter, Parmesan, Salbei"],
steps: ["Roggen- und Weizenmehl mit Eiern und Wasser verkneten.", "30 Min. ruhen lassen.", "Spinat dünsten, hacken, mit Ricotta und Zwiebel mischen.", "Teig dünn ausrollen, Kreise ausstechen.", "Füllung aufsetzen, zu Halbmonden falten, Ränder fest drücken.", "In Salzwasser kochen, mit brauner Salbeibutter und Parmesan servieren."],
},
{
title: "Sauerbraten",
image: "/uploads/recipe-sauerbraten.png",
time: "240 Min.",
servings: "6 Portionen",
category: "Hauptspeise",
description: "Tagelang marinierter Schmorbraten mit dunkler Soße -- das Geduldsgericht der deutschen Küche.",
ingredients: ["1,5 kg Rindfleisch", "500 ml Rotweinessig", "500 ml Wasser", "2 Zwiebeln", "Lorbeer, Nelken, Piment", "Wacholderbeeren", "3 EL Lebkuchengewürz", "Rosinen"],
steps: ["Fleisch 3-5 Tage in Essig-Wasser-Marinade mit Gewürzen einlegen.", "Fleisch herausnehmen, trocken tupfen, scharf anbraten.", "Mit gefilterter Marinade ablöschen.", "Bei 160°C 3 Stunden schmoren.", "Soße mit Lebkuchen-Bröseln und Rosinen binden.", "Mit Kartoffelklößen und Rotkraut servieren."],
},
{
title: "Schwarzwälder Kirschtorte",
image: "/uploads/recipe-schwarzwaelder-kirschtorte.png",
time: "120 Min.",
servings: "12 Portionen",
category: "Nachspeise",
description: "Die Königin der deutschen Torten -- Schokolade, Kirschen, Sahne und ein Schuss Kirschwasser.",
ingredients: ["6 Eier", "200 g Zucker", "150 g Mehl", "50 g Kakao", "80 g Butter", "1 Glas Sauerkirschen", "600 ml Sahne", "Kirschwasser", "Schokoraspeln"],
steps: ["Biskuit aus Eiern, Zucker, Mehl, Kakao und Butter backen.", "Auskühlen lassen und in 3 Böden schneiden.", "Böden mit Kirschwasser tränken.", "Sahne steif schlagen, zwischen die Böden streichen.", "Kirschen auf die Schichten verteilen.", "Torte mit Sahne, Schokoraspeln und Kirschen dekorieren."],
},
],
},
{
name: "Norddeutschland",
recipes: [
{
title: "Grünkohl mit Pinkel",
image: "/uploads/recipe-gruenkohl.png",
time: "120 Min.",
servings: "6 Portionen",
category: "Hauptspeise",
description: "Norddeutscher Winterklassiker -- deftiger Grünkohl mit geräucherter Pinkelwurst nach dem ersten Frost.",
ingredients: ["1,5 kg frischer Grünkohl", "4 Pinkelwürste", "250 g Kasseler", "250 g durchwachsener Speck", "2 Zwiebeln", "Schmalz", "Haferflocken", "Senf"],
steps: ["Grünkohl waschen, Stiele entfernen und grob hacken.", "Zwiebeln in Schmalz anbraten, Grünkohl zugeben.", "Mit Brühe aufgießen, Speck und Kasseler einlegen.", "90 Min. köcheln lassen, gelegentlich umrühren.", "Pinkelwürste in den letzten 30 Min. mitgaren.", "Mit Haferflocken binden, mit Senf und Bratkartoffeln servieren."],
},
{
title: "Kartoffelpuffer",
image: "/uploads/recipe-kartoffelpuffer.png",
time: "30 Min.",
servings: "4 Portionen",
category: "Hauptspeise",
description: "Goldbraun gebratene Kartoffelreibekuchen -- ob süß mit Apfelmus oder herzhaft mit Lachs.",
ingredients: ["1 kg festkochende Kartoffeln", "1 Zwiebel", "2 Eier", "3 EL Mehl", "Salz, Pfeffer", "Muskatnuss", "Öl zum Braten", "Apfelmus"],
steps: ["Kartoffeln und Zwiebel fein reiben.", "Überschüssige Flüssigkeit gut ausdrücken.", "Mit Eiern, Mehl, Salz und Muskatnuss verrühren.", "In heißem Öl löffelweise flache Puffer braten.", "Von beiden Seiten goldbraun und knusprig backen.", "Auf Küchenpapier abtropfen, mit Apfelmus 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-page">
<div
className="bg-card rounded-xl border border-card-border max-w-lg w-full max-h-[85vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="relative">
<img src={recipe.image} alt={recipe.title} className="w-full h-56 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-modal-close">
<X className="w-4 h-4" />
</button>
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-transparent p-4">
<span className="text-[10px] font-medium text-primary bg-primary/20 px-2 py-0.5 rounded">{recipe.category}</span>
</div>
</div>
<div className="p-5">
<h2 className="text-xl font-bold text-foreground mb-1">{recipe.title}</h2>
<p className="text-sm text-muted-foreground mb-3">{recipe.description}</p>
<div className="flex items-center gap-4 text-sm text-muted-foreground mb-5">
<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-5 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 default function RecipesPage() {
const [selectedRecipe, setSelectedRecipe] = useState<Recipe | null>(null);
return (
<div className="min-h-screen bg-background">
<Header />
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="flex items-center gap-3 mb-2">
<Link href="/">
<button className="text-muted-foreground hover:text-foreground transition-colors" data-testid="button-recipes-back">
<ChevronLeft className="w-5 h-5" />
</button>
</Link>
<h1 className="text-2xl font-bold text-foreground flex items-center gap-2" data-testid="text-recipes-title">
<ChefHat className="w-6 h-6 text-primary" />
Legendäre Bauernküche
</h1>
</div>
<p className="text-muted-foreground text-sm mb-8 ml-8" data-testid="text-recipes-subtitle">
Traditionelle Rezepte aus den germanischen Regionen -- von der Alm bis zur Küste, von der Oma überliefert.
</p>
{RECIPE_REGIONS.map((region, ri) => (
<div key={region.name} data-testid={`section-region-${ri}`}>
<div className="flex items-center gap-3 mb-4 mt-8 first:mt-0">
<div className="h-px flex-1 bg-card-border" />
<h2 className="text-lg font-bold text-foreground px-3" data-testid={`text-region-${ri}`}>{region.name}</h2>
<div className="h-px flex-1 bg-card-border" />
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5 mb-4">
{region.recipes.map((recipe, i) => (
<button
key={recipe.title}
onClick={() => setSelectedRecipe(recipe)}
className="bg-card rounded-xl border border-card-border overflow-hidden text-left group hover:border-primary/50 transition-all hover:shadow-lg hover:shadow-primary/5"
data-testid={`card-recipe-${ri}-${i}`}
>
<div className="relative overflow-hidden">
<img src={recipe.image} alt={recipe.title} className="w-full aspect-video object-cover transition-transform duration-500 group-hover:scale-105" loading="lazy" />
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent" />
<div className="absolute bottom-2 left-2">
<span className="text-[10px] font-medium text-primary bg-primary/20 px-2 py-0.5 rounded">{recipe.category}</span>
</div>
</div>
<div className="p-4">
<h3 className="font-bold text-card-foreground text-base group-hover:text-primary transition-colors">{recipe.title}</h3>
<p className="text-xs text-muted-foreground mt-1 line-clamp-2">{recipe.description}</p>
<div className="flex items-center gap-3 mt-3 text-xs text-muted-foreground">
<span className="flex items-center gap-1"><Clock className="w-3 h-3" />{recipe.time}</span>
<span className="flex items-center gap-1"><Users className="w-3 h-3" />{recipe.servings}</span>
</div>
</div>
</button>
))}
</div>
{ri % 2 === 0 && <InArticleAd />}
</div>
))}
</main>
<Footer />
{selectedRecipe && <RecipeModal recipe={selectedRecipe} onClose={() => setSelectedRecipe(null)} />}
</div>
);
}