Sunil Prasad c48a11b13c feat: Add smooth 5-second auto-rotation to Hero stories and ProductShowcase cards
- Hero auto-rotates story backdrops every 5 seconds with 500ms fade transitions
- ProductShowcase cards rotate with mint ring highlight on the active card
- Inactive cards fade to 60% opacity on mobile, full opacity on desktop
- New components: StoryBackdrop, StoryOnboarding, CountUp, lib/stories
- Manual Another-story control still works

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:10:48 -07:00

127 lines
6.2 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { ArrowRight, RefreshCcw, Sparkles } from "lucide-react";
import Link from "next/link";
import { StoryBackdrop } from "@/components/marketing/StoryBackdrop";
import { randomStory, type GoalStory } from "@/lib/stories";
const askAnswers: [string, string][] = [
["Can I afford a $2,400 vacation in December?", "Yes — set aside $400/month starting now and your flex budget stays green the whole way."],
["When can I buy my first home?", "At your current savings pace, a 10% down payment on a $380k home is ~34 months out. Two changes could make it 26."],
["Which card should I use for groceries?", "Sapphire Preferred — 3x points beats your flat 2% card. Worth about $18 more this month."],
["When can I retire?", "Holding your 14% savings rate, a comfortable retirement at 63 is on track. Bump to 17% and it's 61."],
];
export function Hero() {
const [story, setStory] = useState<GoalStory | null>(null);
const [askIndex, setAskIndex] = useState(0);
const [asked, setAsked] = useState<[string, string] | null>(null);
const [input, setInput] = useState("");
const [fadeOut, setFadeOut] = useState(false);
useEffect(() => {
setStory(randomStory());
const t = setInterval(() => setAskIndex((i) => (i + 1) % askAnswers.length), 3500);
return () => clearInterval(t);
}, []);
useEffect(() => {
const storyInterval = setInterval(() => {
setFadeOut(true);
setTimeout(() => {
setStory((s) => randomStory(s?.id));
setFadeOut(false);
}, 500);
}, 5000);
return () => clearInterval(storyInterval);
}, []);
const ask = () => {
const typed = input.trim().toLowerCase();
const match =
askAnswers.find(([q]) => typed && q.toLowerCase().includes(typed.slice(0, 12))) ??
askAnswers[askIndex];
setAsked(match);
setInput("");
};
return (
<section className="relative isolate flex min-h-screen flex-col overflow-hidden bg-[#0A0A09]">
{story && <div className={`transition-opacity duration-500 ${fadeOut ? 'opacity-0' : 'opacity-100'}`}><StoryBackdrop story={story} dim="soft" /></div>}
{/* extra cinematic vignette */}
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(85%_70%_at_50%_35%,transparent_40%,rgba(10,10,9,.75)_100%)]" />
<div className="relative mx-auto flex w-full max-w-4xl flex-1 flex-col items-center justify-center px-5 pb-40 pt-32 text-center">
<p className="font-mono text-[11px] uppercase tracking-[.3em] text-mint">
Subscription-only · Zero ads · Zero referral bias
</p>
<h1 className="mt-6 font-display text-6xl leading-[1.02] text-paper md:text-[84px]">
<em className="text-mint">Own</em> your story.
</h1>
<p className="mt-5 max-w-xl text-lg leading-8 text-paper/80">
Aartha is the personal finance app that works only for you budgets, credit,
rewards, and an AI coach pointed at the life you&apos;re building.
</p>
{/* Ask Aartha — interactive, right in the hero */}
<div className="mt-9 w-full max-w-xl">
<div className="flex items-center gap-2 rounded-full border border-white/15 bg-[#0A0A09]/60 p-2 pl-5 backdrop-blur-md transition focus-within:border-mint/60">
<Sparkles size={16} className="flex-none text-mint" />
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && ask()}
placeholder={askAnswers[askIndex][0]}
className="w-full bg-transparent text-sm text-paper placeholder:text-paper/45 focus:outline-none"
/>
<button
onClick={ask}
className="flex-none rounded-full bg-paper px-4 py-2.5 font-mono text-[11px] font-bold uppercase tracking-[.15em] text-ink transition hover:bg-mint"
>
Ask
</button>
</div>
<p className="mt-3 font-mono text-[10px] uppercase tracking-[.25em] text-paper/40">
Track everything · Ask anything · Trust the answer
</p>
{asked && (
<div className="mt-4 animate-fadeUp rounded-lg border border-white/12 bg-[#0A0A09]/70 p-4 text-left backdrop-blur-md">
<p className="font-mono text-[10px] uppercase tracking-[.2em] text-paper/45">{asked[0]}</p>
<p className="mt-2 text-sm leading-6 text-paper/90">{asked[1]}</p>
<p className="mt-2 text-[10px] text-paper/35">Sample answer · Educational guidance only, not regulated financial advice.</p>
</div>
)}
</div>
<div className="mt-9 flex items-center gap-3">
<Link href="/dashboard" className="inline-flex items-center gap-2 rounded-full bg-mint px-6 py-3.5 text-sm font-bold text-ink transition hover:bg-paper">
Get started <ArrowRight size={16} />
</Link>
<a href="#product" className="rounded-full border border-white/20 px-6 py-3.5 text-sm font-semibold text-paper/85 transition hover:border-white/50 hover:text-paper">
See the product
</a>
</div>
</div>
{/* Story caption — the human moment behind this visit */}
{story && (
<div className={`absolute inset-x-0 bottom-0 z-10 border-t border-white/10 bg-gradient-to-t from-[#0A0A09] to-transparent transition-opacity duration-500 ${fadeOut ? 'opacity-0' : 'opacity-100'}`}>
<div className="mx-auto flex max-w-7xl flex-wrap items-end justify-between gap-3 px-5 py-5">
<div>
<p className="font-mono text-[10px] uppercase tracking-[.25em] text-mint">{story.emoji} {story.tag} a different story every visit</p>
<p className="mt-1 font-display text-xl text-paper">{story.headline}</p>
</div>
<button
onClick={() => setStory((s) => randomStory(s?.id))}
className="inline-flex items-center gap-2 rounded-full border border-white/15 px-4 py-2 font-mono text-[10px] uppercase tracking-[.2em] text-paper/60 transition hover:border-white/40 hover:text-paper"
>
<RefreshCcw size={11} /> Another story
</button>
</div>
</div>
)}
</section>
);
}