import React, { useMemo, useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Moon, Stars, Sparkles, Wand2, Flame, Eye, Orbit, Gem, CalendarDays } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
const SIGNS = [
{ name: "Aries", start: [3, 21], end: [4, 19], icon: Flame, tone: "reckless fire", element: "Fire" },
{ name: "Taurus", start: [4, 20], end: [5, 20], icon: Gem, tone: "slow power", element: "Earth" },
{ name: "Gemini", start: [5, 21], end: [6, 20], icon: Stars, tone: "electric duality", element: "Air" },
{ name: "Cancer", start: [6, 21], end: [7, 22], icon: Moon, tone: "deep tide", element: "Water" },
{ name: "Leo", start: [7, 23], end: [8, 22], icon: Sparkles, tone: "royal heat", element: "Fire" },
{ name: "Virgo", start: [8, 23], end: [9, 22], icon: Eye, tone: "clean precision", element: "Earth" },
{ name: "Libra", start: [9, 23], end: [10, 22], icon: Orbit, tone: "silk and steel", element: "Air" },
{ name: "Scorpio", start: [10, 23], end: [11, 21], icon: Wand2, tone: "shadow magnetism", element: "Water" },
{ name: "Sagittarius", start: [11, 22], end: [12, 21], icon: Flame, tone: "wild expansion", element: "Fire" },
{ name: "Capricorn", start: [12, 22], end: [1, 19], icon: Gem, tone: "iron ambition", element: "Earth" },
{ name: "Aquarius", start: [1, 20], end: [2, 18], icon: Stars, tone: "future signal", element: "Air" },
{ name: "Pisces", start: [2, 19], end: [3, 20], icon: Moon, tone: "dream current", element: "Water" },
] as const;
type SignName = (typeof SIGNS)[number]["name"];
type Category = "love" | "career" | "energy" | "warning" | "ritual";
type HoroscopeResult = {
sign: SignName;
cosmicWeather: string;
headline: string;
vibe: string;
categories: Record<Category, string>;
powerColor: string;
luckyNumbers: number[];
intensity: number;
mantra: string;
};
const cosmicOpeners = [
"The sky is not being subtle with you today.",
"Something in your life wants honesty, not performance.",
"You are standing in a pressure point where change gets real.",
"This energy is sharp, seductive, and impossible to ignore.",
"What looks like tension is really an invitation to move smarter.",
"The universe is dragging hidden motives into the light.",
"You are being pushed toward alignment, not comfort.",
"There is raw momentum under the surface right now.",
];
const signHeadlines: Record<SignName, string[]> = {
Aries: ["Stop waiting. Strike clean.", "Your fire works best with direction.", "Movement breaks the curse."],
Taurus: ["Hold the line, but not the dead weight.", "Luxury means peace, not excess.", "Your power is in what you refuse."],
Gemini: ["Say the dangerous truth beautifully.", "Your mind is a live wire today.", "Pick the signal, not the noise."],
Cancer: ["Feel it fully, then choose wisely.", "Your softness is not weakness.", "Protect your energy without hiding."],
Leo: ["Take the room without begging for it.", "Your glow gets brighter when it’s real.", "Lead with heart, not ego."],
Virgo: ["Precision is your superpower, not your prison.", "Fix what matters. Release the rest.", "Discernment cuts cleaner than doubt."],
Libra: ["Beauty means balance with backbone.", "Harmony without honesty is fake peace.", "Choose what feels aligned, not just pretty."],
Scorpio: ["You already know what’s rotting. Cut it off.", "Magnetism rises when you stop pretending.", "Power moves in silence first."],
Sagittarius: ["Freedom needs aim or it turns into drift.", "Go farther, but mean it.", "Your next answer lives outside routine."],
Capricorn: ["Build the empire, not the costume.", "Discipline is sexy when it serves a vision.", "Respect your pace. Then raise it."],
Aquarius: ["You were not made for stale rooms.", "Innovation begins where approval ends.", "Protect the weird spark."],
Pisces: ["Dreams need a door to walk through.", "Your sensitivity is picking up real signals.", "Stay open, but don’t drown."],
};
const colorMap: Record<string, string> = {
Fire: "Crimson Ember",
Earth: "Obsidian Moss",
Air: "Silver Static",
Water: "Midnight Tide",
};
function pad(n: number) {
return n < 10 ? `0${n}` : `${n}`;
}
function signFromDate(dateStr: string): SignName | "" {
if (!dateStr) return "";
const d = new Date(dateStr + "T00:00:00");
if (Number.isNaN(d.getTime())) return "";
const month = d.getMonth() + 1;
const day = d.getDate();
for (const sign of SIGNS) {
const [sm, sd] = sign.start;
const [em, ed] = sign.end;
const wrapsYear = sm > em;
const afterStart = month > sm || (month === sm && day >= sd);
const beforeEnd = month < em || (month === em && day <= ed);
if ((!wrapsYear && afterStart && beforeEnd) || (wrapsYear && (afterStart || beforeEnd))) {
return sign.name;
}
}
return "";
}
function hashSeed(input: string) {
let hash = 0;
for (let i = 0; i < input.length; i++) {
hash = (hash << 5) - hash + input.charCodeAt(i);
hash |= 0;
}
return Math.abs(hash);
}
function choose<T>(arr: T[], seed: number, offset = 0): T {
return arr[(seed + offset) % arr.length];
}
function buildSentence(seed: number, parts: string[][]) {
return parts.map((group, i) => choose(group, seed, i * 7)).join(" ");
}
function generateHoroscope(signName: SignName, focus: string, dateStr: string): HoroscopeResult {
const sign = SIGNS.find((s) => s.name === signName)!;
const seed = hashSeed(`${signName}-${focus}-${dateStr || "today"}`);
const cosmicWeather = buildSentence(seed, [
cosmicOpeners,
[
`Your ${sign.tone} energy is turning heads for a reason.`,
`That ${sign.tone} mood around you is not random.`,
`Your ${sign.element.toLowerCase()} element is hitting harder than usual.`,
`There is a visible charge around your choices right now.`,
],
[
"The right move is bold, but not sloppy.",
"This is a day for intentional moves, not emotional flinching.",
"You do not need permission to correct the course.",
"A cleaner boundary changes everything faster than another explanation.",
],
]);
const love = buildSentence(seed + 3, [
[
"In love, desire is asking for clarity.",
"In love, mixed signals are a waste of your magic.",
"In love, chemistry means nothing without consistency.",
"In love, you are reading the room correctly.",
],
[
"Say what you need without dressing it up.",
"Pull closer only where effort matches effort.",
"Stop romanticizing people who only show up halfway.",
"The truth gets hotter when it stops hiding.",
],
]);
const career = buildSentence(seed + 11, [
[
"Work wants cleaner focus.",
"Your career lane is begging for sharper strategy.",
"Money responds to discipline today.",
"Professional momentum is available, but not to scattered energy.",
],
[
"Finish the thing that actually matters.",
"A serious move now creates breathing room later.",
"Cut distractions that wear a fake urgency mask.",
"Your authority rises when your output gets cleaner.",
],
]);
const energy = buildSentence(seed + 17, [
[
"Your body is tracking more than your mouth admits.",
"Your energy field is loud right now.",
"Your nervous system wants less chaos and more rhythm.",
"You are more intuitive than usual today.",
],
[
"Protect your mornings if you want the whole day to improve.",
"Silence is medicine for you today.",
"Move your body before your thoughts start running wild.",
"Hydrate, reset, and stop entertaining what drains you.",
],
]);
const warning = buildSentence(seed + 23, [
[
"Watch for ego disguised as certainty.",
"Watch for emotional overspending.",
"Watch for wasting time on people who love confusion.",
"Watch for overexplaining when one clear sentence would do.",
],
[
"You do not owe chaos a front-row seat.",
"Not every trigger deserves a reaction.",
"Silence can be a winning move.",
"Delay the impulse; keep the power.",
],
]);
const ritual = buildSentence(seed + 29, [
[
"Tonight’s ritual: light a candle and write down what is leaving your life.",
"Tonight’s ritual: clean one corner of your space and call your energy back.",
"Tonight’s ritual: take ten quiet minutes and ask what your next real move is.",
"Tonight’s ritual: stand in the mirror and speak one truth you’ve been dodging.",
],
[
`Wear ${colorMap[sign.element]}.`,
"Leave your phone alone for one hour.",
"Play music that reminds you who the hell you are.",
"Do something beautiful on purpose.",
],
]);
const mantra = choose([
"I choose power without panic.",
"I move with instinct and precision.",
"I stop shrinking to fit broken rooms.",
"I protect my energy like it prints gold.",
"I make cleaner choices and get cleaner results.",
"I trust what my spirit already knows.",
], seed, 41);
const luckyNumbers = [
(seed % 9) + 1,
((seed >> 2) % 9) + 1,
((seed >> 4) % 9) + 1,
];
const intensity = (seed % 5) + 6;
const headline = choose(signHeadlines[signName], seed, 5);
const vibe = `${sign.element} sign • ${sign.tone} • focus: ${focus || "general"}`;
return {
sign: signName,
cosmicWeather,
headline,
vibe,
categories: { love, career, energy, warning, ritual },
powerColor: colorMap[sign.element],
luckyNumbers,
intensity,
mantra,
};
}
function todayString() {
const now = new Date();
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
}
const focusOptions = [
"general",
"love",
"career",
"money",
"healing",
"shadow work",
"intuition",
"power moves",
];
function meterLabel(n: number) {
if (n <= 6) return "steady";
if (n <= 8) return "charged";
return "feral";
}
export default function DesecratedHoroscopeGenerator() {
const [birthdate, setBirthdate] = useState("");
const [manualSign, setManualSign] = useState<string>("");
const [focus, setFocus] = useState("general");
const [readingDate, setReadingDate] = useState(todayString());
const [generatedAt, setGeneratedAt] = useState(Date.now());
const autoSign = useMemo(() => signFromDate(birthdate), [birthdate]);
const activeSign = (manualSign || autoSign) as SignName | "";
const result = useMemo(() => {
if (!activeSign) return null;
return generateHoroscope(activeSign, focus, `${readingDate}-${generatedAt}`);
}, [activeSign, focus, readingDate, generatedAt]);
return (
<div className="min-h-screen bg-black text-white relative overflow-hidden">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(255,0,0,.16),transparent_28%),radial-gradient(circle_at_top_right,rgba(255,255,255,.12),transparent_22%),radial-gradient(circle_at_bottom_left,rgba(120,0,255,.18),transparent_32%)]" />
<div className="absolute inset-0 opacity-20 bg-[linear-gradient(rgba(255,255,255,.04)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,.04)_1px,transparent_1px)] bg-[size:52px_52px]" />
<div className="relative max-w-7xl mx-auto px-4 py-8 md:py-12">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="grid lg:grid-cols-[1.05fr_.95fr] gap-6 items-start"
>
<Card className="border-white/10 bg-white/5 backdrop-blur-xl rounded-3xl shadow-2xl overflow-hidden">
<CardHeader className="pb-4">
<div className="inline-flex w-fit items-center gap-2 rounded-full border border-white/10 bg-white/5 px-3 py-1 text-xs uppercase tracking-[0.25em] text-zinc-300">
<Stars className="h-3.5 w-3.5" /> Desecrated Horoscope Generator
</div>
<CardTitle className="text-4xl md:text-6xl uppercase tracking-[0.08em] leading-none mt-4">
Dark. Detailed. <span className="text-red-500">Alive.</span>
</CardTitle>
<CardDescription className="text-zinc-300 text-base md:text-lg max-w-2xl leading-7 mt-3">
This is built to feel cinematic and sharp, not like watered-down horoscope filler. Pick a sign or drop in a birthdate, choose your focus, and pull a reading that actually sounds like it has a pulse.
</CardDescription>
</CardHeader>
<CardContent className="grid md:grid-cols-2 gap-4">
<div className="space-y-3">
<label className="text-xs uppercase tracking-[0.22em] text-zinc-400 flex items-center gap-2">
<CalendarDays className="h-4 w-4" /> Birthdate
</label>
<Input
type="date"
value={birthdate}
onChange={(e) => setBirthdate(e.target.value)}
className="bg-black/50 border-white/10 rounded-2xl h-12 text-white"
/>
<p className="text-sm text-zinc-400 min-h-6">
{autoSign ? `Auto-detected sign: ${autoSign}` : "Enter a birthdate to auto-detect your sign."}
</p>
</div>
<div className="space-y-3">
<label className="text-xs uppercase tracking-[0.22em] text-zinc-400 flex items-center gap-2">
<Moon className="h-4 w-4" /> Or choose a sign
</label>
<Select value={manualSign} onValueChange={setManualSign}>
<SelectTrigger className="bg-black/50 border-white/10 rounded-2xl h-12 text-white">
<SelectValue placeholder="Select your sign" />
</SelectTrigger>
<SelectContent className="bg-zinc-950 border-white/10 text-white">
{SIGNS.map((sign) => (
<SelectItem key={sign.name} value={sign.name}>
{sign.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<label className="text-xs uppercase tracking-[0.22em] text-zinc-400 flex items-center gap-2">
<Eye className="h-4 w-4" /> Focus
</label>
<Select value={focus} onValueChange={setFocus}>
<SelectTrigger className="bg-black/50 border-white/10 rounded-2xl h-12 text-white capitalize">
<SelectValue placeholder="Choose a focus" />
</SelectTrigger>
<SelectContent className="bg-zinc-950 border-white/10 text-white">
{focusOptions.map((option) => (
<SelectItem key={option} value={option} className="capitalize">
{option}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<label className="text-xs uppercase tracking-[0.22em] text-zinc-400 flex items-center gap-2">
<Orbit className="h-4 w-4" /> Reading date
</label>
<Input
type="date"
value={readingDate}
onChange={(e) => setReadingDate(e.target.value)}
className="bg-black/50 border-white/10 rounded-2xl h-12 text-white"
/>
</div>
<div className="md:col-span-2 flex flex-wrap gap-3 pt-2">
<Button
onClick={() => setGeneratedAt(Date.now())}
disabled={!activeSign}
className="rounded-full px-6 h-12 uppercase tracking-[0.22em] bg-red-600 hover:bg-red-500 text-white shadow-[0_0_32px_rgba(255,0,0,.35)] disabled:opacity-50"
>
Generate Reading
</Button>
<Button
variant="outline"
onClick={() => {
setBirthdate("");
setManualSign("");
setFocus("general");
setReadingDate(todayString());
setGeneratedAt(Date.now());
}}
className="rounded-full px-6 h-12 uppercase tracking-[0.22em] border-white/15 bg-white/5 text-white hover:bg-white/10"
>
Reset
</Button>
</div>
</CardContent>
</Card>
<Card className="border-white/10 bg-white/5 backdrop-blur-xl rounded-3xl shadow-2xl overflow-hidden min-h-[420px]">
<CardContent className="p-0 h-full">
<div className="relative h-full min-h-[420px] flex items-end overflow-hidden rounded-3xl">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_15%,rgba(255,255,255,.18),transparent_12%),linear-gradient(180deg,rgba(255,255,255,.06),rgba(0,0,0,.15)),linear-gradient(180deg,#160707_0%,#050505_60%,#000_100%)]" />
<div className="absolute inset-0 opacity-70 bg-[radial-gradient(circle_at_center,transparent_30%,rgba(0,0,0,.55)_100%)]" />
<div className="absolute inset-0 flex items-center justify-center">
<div className="grid grid-cols-3 gap-4 rotate-[-10deg] scale-95 md:scale-100">
{[0, 1, 2].map((n) => (
<motion.div
key={n}
initial={{ opacity: 0, y: 18, rotate: -6 + n * 3 }}
animate={{ opacity: 1, y: 0, rotate: -8 + n * 6 }}
transition={{ duration: 0.5, delay: n * 0.1 }}
className="aspect-[2/3] w-24 md:w-32 rounded-[1.6rem] border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,.13),rgba(255,255,255,.02)),radial-gradient(circle_at_top,rgba(255,0,0,.22),transparent_30%),#050505] shadow-[0_0_30px_rgba(255,0,0,.18)] flex items-center justify-center"
>
<Sparkles className="h-8 w-8 text-zinc-200 opacity-90" />
</motion.div>
))}
</div>
</div>
<div className="relative z-10 p-6 md:p-8 w-full">
<div className="max-w-md rounded-3xl border border-white/10 bg-black/40 backdrop-blur-md p-5 shadow-xl">
<div className="text-xs uppercase tracking-[0.25em] text-zinc-400 mb-3">Generator Preview</div>
<div className="text-2xl md:text-3xl uppercase tracking-[0.08em]">Premium sign readings</div>
<p className="text-zinc-300 leading-7 mt-3">
Love, career, energy, warning, ritual, power color, lucky numbers, and a mantra — all in one polished interface.
</p>
</div>
</div>
</div>
</CardContent>
</Card>
</motion.div>
<AnimatePresence mode="wait">
{result ? (
<motion.div
key={`${result.sign}-${generatedAt}`}
initial={{ opacity: 0, y: 18 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.45 }}
className="mt-6 grid gap-6"
>
<Card className="border-white/10 bg-white/5 backdrop-blur-xl rounded-3xl shadow-2xl overflow-hidden">
<CardContent className="p-6 md:p-8">
<div className="flex flex-col lg:flex-row lg:items-end lg:justify-between gap-5">
<div>
<div className="inline-flex items-center gap-2 rounded-full border border-red-500/30 bg-red-500/10 px-3 py-1 text-xs uppercase tracking-[0.25em] text-red-300">
<Moon className="h-3.5 w-3.5" /> {result.sign}
</div>
<h2 className="text-3xl md:text-5xl uppercase tracking-[0.08em] mt-4 leading-none">{result.headline}</h2>
<p className="text-zinc-300 max-w-3xl mt-4 text-lg leading-8">{result.cosmicWeather}</p>
</div>
<div className="grid grid-cols-2 gap-3 min-w-[280px]">
<div className="rounded-2xl border border-white/10 bg-black/30 p-4">
<div className="text-xs uppercase tracking-[0.25em] text-zinc-500">Intensity</div>
<div className="mt-2 text-3xl font-semibold">{result.intensity}/10</div>
<div className="text-sm text-zinc-400 uppercase tracking-[0.18em] mt-1">{meterLabel(result.intensity)}</div>
</div>
<div className="rounded-2xl border border-white/10 bg-black/30 p-4">
<div className="text-xs uppercase tracking-[0.25em] text-zinc-500">Power Color</div>
<div className="mt-2 text-xl font-semibold">{result.powerColor}</div>
<div className="text-sm text-zinc-400 uppercase tracking-[0.18em] mt-1">Wear the mood</div>
</div>
</div>
</div>
<div className="grid lg:grid-cols-5 gap-4 mt-8">
{([
["Love", result.categories.love, Sparkles],
["Career", result.categories.career, Gem],
["Energy", result.categories.energy, Orbit],
["Warning", result.categories.warning, Eye],
["Ritual", result.categories.ritual, Wand2],
] as const).map(([title, text, Icon]) => (
<motion.div
key={title}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
className="rounded-3xl border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,.06),rgba(255,255,255,.02))] p-5"
>
<div className="flex items-center gap-2 text-zinc-300 uppercase tracking-[0.18em] text-xs mb-3">
<Icon className="h-4 w-4" /> {title}
</div>
<p className="text-zinc-100 leading-7 text-sm">{text}</p>
</motion.div>
))}
</div>
<div className="grid md:grid-cols-[1.25fr_.75fr] gap-4 mt-6">
<div className="rounded-3xl border border-white/10 bg-black/30 p-5">
<div className="text-xs uppercase tracking-[0.24em] text-zinc-500">Mantra</div>
<div className="text-2xl md:text-3xl leading-tight mt-3">“{result.mantra}”</div>
<div className="text-sm text-zinc-400 uppercase tracking-[0.18em] mt-4">{result.vibe}</div>
</div>
<div className="rounded-3xl border border-white/10 bg-black/30 p-5">
<div className="text-xs uppercase tracking-[0.24em] text-zinc-500">Lucky Numbers</div>
<div className="flex gap-3 mt-4">
{result.luckyNumbers.map((n, idx) => (
<div
key={idx}
className="h-14 w-14 rounded-2xl border border-red-500/25 bg-red-500/10 flex items-center justify-center text-xl font-semibold shadow-[0_0_22px_rgba(255,0,0,.15)]"
>
{n}
</div>
))}
</div>
</div>
</div>
</CardContent>
</Card>
</motion.div>
) : (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="mt-6"
>
<Card className="border-white/10 bg-white/5 backdrop-blur-xl rounded-3xl shadow-2xl">
<CardContent className="p-8 text-center">
<div className="mx-auto h-16 w-16 rounded-full border border-white/10 bg-white/5 flex items-center justify-center mb-4">
<Moon className="h-7 w-7 text-zinc-300" />
</div>
<h3 className="text-2xl uppercase tracking-[0.1em]">Choose a sign to wake it up</h3>
<p className="text-zinc-400 max-w-2xl mx-auto mt-3 leading-7">
Enter a birthdate or manually choose a sign, then hit generate. The whole thing is designed to feel like a premium mystic feature, not a bargain-bin widget.
</p>
</CardContent>
</Card>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
);
}
Like this:
Like Loading...