- 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>
127 lines
6.2 KiB
TypeScript
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'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>
|
|
);
|
|
}
|