Enhance the horoscope page with cosmic events and detailed sign information
Refactor client/src/pages/horoscope.tsx to introduce AstroEventsSection and improve sign detail display with carousel navigation. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 413891e8-d784-4bea-b9f5-91a5a68316b4 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: c5d5d9ec-0385-4ba4-9d22-4d8e94460720 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:
parent
a2e323122b
commit
774a72bc22
@ -1,12 +1,28 @@
|
|||||||
import { useState } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { Link } from "wouter";
|
import { Link, useParams } from "wouter";
|
||||||
import { Star, Heart, Briefcase, TrendingUp, ChevronLeft, Calendar, CalendarDays, Sparkles, Lightbulb } from "lucide-react";
|
import {
|
||||||
|
Star,
|
||||||
|
Heart,
|
||||||
|
Briefcase,
|
||||||
|
TrendingUp,
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
Calendar,
|
||||||
|
CalendarDays,
|
||||||
|
Sparkles,
|
||||||
|
Lightbulb,
|
||||||
|
AlertTriangle,
|
||||||
|
Moon,
|
||||||
|
Sun,
|
||||||
|
ArrowRight,
|
||||||
|
} from "lucide-react";
|
||||||
import Header from "@/components/header";
|
import Header from "@/components/header";
|
||||||
import Footer from "@/components/footer";
|
import Footer from "@/components/footer";
|
||||||
import { InArticleAd } from "@/components/adsense";
|
import { InArticleAd } from "@/components/adsense";
|
||||||
import {
|
import {
|
||||||
SIGNS,
|
SIGNS,
|
||||||
ELEMENT_COLORS,
|
ELEMENT_COLORS,
|
||||||
|
ASTRO_EVENTS,
|
||||||
getHoroscope,
|
getHoroscope,
|
||||||
getRating,
|
getRating,
|
||||||
getLuckyNumbers,
|
getLuckyNumbers,
|
||||||
@ -23,9 +39,335 @@ function StarRating({ count }: { count: number }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HoroscopePage() {
|
function getEventIcon(icon: string) {
|
||||||
const [selected, setSelected] = useState<number | null>(null);
|
switch (icon) {
|
||||||
|
case "mercury": return <AlertTriangle className="w-5 h-5 text-amber-400" />;
|
||||||
|
case "moon": return <Moon className="w-5 h-5 text-blue-300" />;
|
||||||
|
case "venus": return <Heart className="w-5 h-5 text-pink-400" />;
|
||||||
|
case "sun": return <Sun className="w-5 h-5 text-amber-300" />;
|
||||||
|
case "saturn": return <Briefcase className="w-5 h-5 text-slate-400" />;
|
||||||
|
case "newmoon": return <Moon className="w-5 h-5 text-violet-400" />;
|
||||||
|
default: return <Star className="w-5 h-5 text-primary" />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEventColor(type: string) {
|
||||||
|
switch (type) {
|
||||||
|
case "retrograde": return { bg: "bg-amber-500/10", border: "border-amber-500/20", accent: "text-amber-400" };
|
||||||
|
case "moon": return { bg: "bg-blue-500/10", border: "border-blue-500/20", accent: "text-blue-400" };
|
||||||
|
case "transit": return { bg: "bg-pink-500/10", border: "border-pink-500/20", accent: "text-pink-400" };
|
||||||
|
case "season": return { bg: "bg-emerald-500/10", border: "border-emerald-500/20", accent: "text-emerald-400" };
|
||||||
|
default: return { bg: "bg-primary/10", border: "border-primary/20", accent: "text-primary" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function AstroEventsSection() {
|
||||||
|
return (
|
||||||
|
<section className="mb-10" data-testid="section-astro-events">
|
||||||
|
<h2 className="text-lg font-bold text-foreground flex items-center gap-2 mb-4">
|
||||||
|
<Sparkles className="w-5 h-5 text-primary" />
|
||||||
|
Aktuelle kosmische Ereignisse
|
||||||
|
</h2>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{ASTRO_EVENTS.map((event, i) => {
|
||||||
|
const ec = getEventColor(event.type);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className={`${ec.bg} border ${ec.border} rounded-xl p-4 transition-all hover:scale-[1.02]`}
|
||||||
|
data-testid={`card-astro-event-${i}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-3 mb-2">
|
||||||
|
{getEventIcon(event.icon)}
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<h3 className={`font-semibold text-sm ${ec.accent}`}>{event.title}</h3>
|
||||||
|
<p className="text-[11px] text-muted-foreground">{event.dateRange}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-foreground/70 leading-relaxed mb-3">{event.description}</p>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{event.affectedSigns.map((s) => {
|
||||||
|
const sign = SIGNS.find((x) => x.name === s);
|
||||||
|
return (
|
||||||
|
<span key={s} className="text-[10px] bg-white/5 border border-white/10 rounded px-1.5 py-0.5 text-muted-foreground">
|
||||||
|
{sign?.symbol} {s}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SignGrid({ onSelect, selectedIndex }: { onSelect: (i: number) => void; selectedIndex: number | null }) {
|
||||||
|
return (
|
||||||
|
<section className="mb-10" data-testid="section-sign-grid">
|
||||||
|
<h2 className="text-lg font-bold text-foreground flex items-center gap-2 mb-4">
|
||||||
|
<Star className="w-5 h-5 text-primary" />
|
||||||
|
Ihr Sternzeichen wählen
|
||||||
|
</h2>
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-3">
|
||||||
|
{SIGNS.map((sign, i) => {
|
||||||
|
const ec = ELEMENT_COLORS[sign.element];
|
||||||
|
const horoscope = getHoroscope(i);
|
||||||
|
const isSelected = selectedIndex === i;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={sign.name}
|
||||||
|
onClick={() => onSelect(i)}
|
||||||
|
className={`text-left rounded-xl p-4 transition-all duration-200 border ${
|
||||||
|
isSelected
|
||||||
|
? `${ec.bg} ${ec.border} shadow-lg ${ec.glow} ring-1 ring-primary/30`
|
||||||
|
: "bg-card border-card-border hover:border-primary/40 hover:shadow-md"
|
||||||
|
}`}
|
||||||
|
data-testid={`button-sign-grid-${sign.name.toLowerCase()}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<span className="text-2xl">{sign.symbol}</span>
|
||||||
|
<div>
|
||||||
|
<span className={`text-sm font-semibold block ${isSelected ? ec.text : "text-foreground"}`}>{sign.name}</span>
|
||||||
|
<span className="text-[10px] text-muted-foreground">{sign.date}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-[11px] text-foreground/60 leading-relaxed line-clamp-3">{horoscope.general.substring(0, 100)}...</p>
|
||||||
|
<div className="flex items-center gap-1 mt-2 text-[10px] text-primary">
|
||||||
|
Lesen <ArrowRight className="w-3 h-3" />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SignDetail({ signIndex, onNavigate }: { signIndex: number; onNavigate: (i: number) => void }) {
|
||||||
const [tab, setTab] = useState<"daily" | "weekly" | "monthly">("daily");
|
const [tab, setTab] = useState<"daily" | "weekly" | "monthly">("daily");
|
||||||
|
const sign = SIGNS[signIndex];
|
||||||
|
const ec = ELEMENT_COLORS[sign.element];
|
||||||
|
const horoscope = getHoroscope(signIndex);
|
||||||
|
const luckyNums = getLuckyNumbers(signIndex);
|
||||||
|
const dailyColor = getDailyColor(signIndex);
|
||||||
|
const detailRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const prevIndex = (signIndex - 1 + SIGNS.length) % SIGNS.length;
|
||||||
|
const nextIndex = (signIndex + 1) % SIGNS.length;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTab("daily");
|
||||||
|
}, [signIndex]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section ref={detailRef} data-testid={`section-sign-detail-${sign.name.toLowerCase()}`}>
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<button
|
||||||
|
onClick={() => onNavigate(prevIndex)}
|
||||||
|
className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors bg-card border border-card-border rounded-lg px-3 py-2"
|
||||||
|
data-testid="button-sign-prev"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-4 h-4" />
|
||||||
|
<span className="text-lg">{SIGNS[prevIndex].symbol}</span>
|
||||||
|
<span className="hidden sm:inline">{SIGNS[prevIndex].name}</span>
|
||||||
|
</button>
|
||||||
|
<h2 className="text-lg font-bold text-foreground flex items-center gap-2">
|
||||||
|
<span className="text-3xl">{sign.symbol}</span>
|
||||||
|
{sign.name}
|
||||||
|
</h2>
|
||||||
|
<button
|
||||||
|
onClick={() => onNavigate(nextIndex)}
|
||||||
|
className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors bg-card border border-card-border rounded-lg px-3 py-2"
|
||||||
|
data-testid="button-sign-next"
|
||||||
|
>
|
||||||
|
<span className="hidden sm:inline">{SIGNS[nextIndex].name}</span>
|
||||||
|
<span className="text-lg">{SIGNS[nextIndex].symbol}</span>
|
||||||
|
<ChevronRight className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</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-${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>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className={`text-2xl font-bold ${ec.text}`}>{sign.name}</h3>
|
||||||
|
<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(signIndex, "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(signIndex, "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(signIndex, "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 !== signIndex).map((s) => {
|
||||||
|
const origIdx = SIGNS.findIndex((x) => x.name === s.name);
|
||||||
|
const sEc = ELEMENT_COLORS[s.element];
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={s.name}
|
||||||
|
onClick={() => onNavigate(origIdx)}
|
||||||
|
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>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function HoroscopePage() {
|
||||||
|
const params = useParams<{ sign?: string }>();
|
||||||
|
const [selected, setSelected] = useState<number | null>(null);
|
||||||
|
const detailRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (params.sign) {
|
||||||
|
const idx = SIGNS.findIndex((s) => s.name.toLowerCase() === params.sign?.toLowerCase());
|
||||||
|
if (idx >= 0) setSelected(idx);
|
||||||
|
}
|
||||||
|
}, [params.sign]);
|
||||||
|
|
||||||
|
const handleSelect = (i: number) => {
|
||||||
|
setSelected(i);
|
||||||
|
setTimeout(() => {
|
||||||
|
detailRef.current?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen bg-background">
|
||||||
@ -39,212 +381,28 @@ export default function HoroscopePage() {
|
|||||||
</Link>
|
</Link>
|
||||||
<h1 className="text-2xl font-bold text-foreground flex items-center gap-2" data-testid="text-horoscope-title">
|
<h1 className="text-2xl font-bold text-foreground flex items-center gap-2" data-testid="text-horoscope-title">
|
||||||
<Star className="w-6 h-6 text-primary" />
|
<Star className="w-6 h-6 text-primary" />
|
||||||
Tageshoroskop
|
Horoskop
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-muted-foreground text-sm mb-8 ml-8" data-testid="text-horoscope-subtitle">
|
<p className="text-muted-foreground text-sm mb-8 ml-8" data-testid="text-horoscope-subtitle">
|
||||||
Was sagen die Sterne heute? Wählen Sie Ihr Sternzeichen für Ihr persönliches Tageshoroskop.
|
Entdecken Sie, was die Sterne für Sie bereithalten. Aktuelle kosmische Ereignisse und Ihr persönliches Tageshoroskop.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 lg:grid-cols-12 gap-3 mb-10">
|
<AstroEventsSection />
|
||||||
{SIGNS.map((sign, i) => {
|
|
||||||
const ec = ELEMENT_COLORS[sign.element];
|
<SignGrid onSelect={handleSelect} selectedIndex={selected} />
|
||||||
return (
|
|
||||||
<button
|
<div ref={detailRef}>
|
||||||
key={sign.name}
|
{selected !== null ? (
|
||||||
onClick={() => { setSelected(i); setTab("daily"); }}
|
<SignDetail signIndex={selected} onNavigate={handleSelect} />
|
||||||
className={`flex flex-col items-center p-3 rounded-xl transition-all duration-200 border ${
|
) : (
|
||||||
selected === i
|
<div className="text-center py-16 bg-card rounded-xl border border-card-border" data-testid="text-horoscope-prompt">
|
||||||
? `${ec.bg} ${ec.border} shadow-lg ${ec.glow} scale-105`
|
<Sparkles className="w-16 h-16 text-primary mx-auto mb-4" />
|
||||||
: "bg-card border-card-border hover:border-primary/50 hover:bg-card/80"
|
<p className="text-muted-foreground text-lg">Wählen Sie oben Ihr Sternzeichen</p>
|
||||||
}`}
|
<p className="text-muted-foreground/60 text-sm mt-1">für Ihr persönliches Tages-, Wochen- und Monatshoroskop</p>
|
||||||
data-testid={`button-sign-select-${sign.name.toLowerCase()}`}
|
</div>
|
||||||
>
|
)}
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
{selected === null ? (
|
|
||||||
<div className="text-center py-16" data-testid="text-horoscope-prompt">
|
|
||||||
<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">
|
|
||||||
{(() => {
|
|
||||||
const sign = SIGNS[selected];
|
|
||||||
const ec = ELEMENT_COLORS[sign.element];
|
|
||||||
const horoscope = getHoroscope(selected);
|
|
||||||
const luckyNums = getLuckyNumbers(selected);
|
|
||||||
const dailyColor = getDailyColor(selected);
|
|
||||||
|
|
||||||
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>
|
|
||||||
<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>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user