Wealth Landing Page New
A warm, editorial light-theme fintech landing page with bento features, sticky steps, pricing toggle and story carousel.
Installation
01
Install with the shadcn CLI. The NiceUI theme comes along the first time.
$ npx shadcn@latest add https://niceui.dev/r/landing-wealth-landing.json02
Or copy the source below (8 files). Dependencies: lucide-react.
Source
components/niceui/pages/landing/LandingPageAurelia.tsx
import { AureliaFooter, Faq, SignupCta } from './aurelia/Closing';
import { BentoFeatures, PressMarquee } from './aurelia/Features';
import { AureliaHero, AureliaNav } from './aurelia/Hero';
import { HowItWorks, StatsBand } from './aurelia/HowItWorks';
import { Pricing, Testimonials } from './aurelia/Pricing';
import { paperGrain } from './aurelia/primitives';
export function LandingPageAurelia() {
return (
<div className="relative min-h-screen bg-[#f5f1ea] font-sans text-stone-900 antialiased selection:bg-orange-200 selection:text-stone-900">
<div className="pointer-events-none absolute inset-0" style={paperGrain} />
<div className="relative">
<AureliaNav />
<AureliaHero />
<PressMarquee />
<BentoFeatures />
<HowItWorks />
<StatsBand />
<Pricing />
<Testimonials />
<Faq />
<SignupCta />
<AureliaFooter />
</div>
</div>
);
}components/niceui/pages/landing/aurelia/Closing.tsx
import { useState, type FormEvent } from 'react';
import { ArrowRight, ArrowUp, Check, Leaf, Loader2, Plus } from 'lucide-react';
import { cn } from '@/lib/cn';
import { Reveal } from '@/components/niceui/pages/landing/shared/motion';
import { Eyebrow, Pill, paperGrain, scrollToId } from './primitives';
const faqs = [
{ q: 'Is my money actually safe?', a: 'Investment accounts are held at a qualified custodian and protected by SIPC up to $500k, plus excess insurance up to $2M. Cash is swept to FDIC-insured partner banks with coverage up to $5M.' },
{ q: 'What does a named advisor actually do?', a: 'Your advisor reviews your plan quarterly, helps with big moments like a home purchase or inheritance, and is reachable by message or video. They are fiduciaries and never paid on commission.' },
{ q: 'Can I move an existing account?', a: 'Yes. We support ACATS transfers from every major brokerage and will cover transfer fees up to $150. Most transfers complete in five to seven business days.' },
{ q: 'Are there any hidden fees?', a: 'None. Your membership is the only thing you pay. No trading commissions, no advisory percentage, no account minimums and no withdrawal fees.' },
{ q: 'Can I cancel any time?', a: 'Of course. Downgrade to Essential or close your account from settings in two taps. Yearly memberships are refunded pro rata.' },
];
export function Faq() {
const [open, setOpen] = useState<number | null>(0);
return (
<section className="px-6 py-28">
<div className="mx-auto grid max-w-6xl gap-12 lg:grid-cols-[1fr_1.4fr]">
<Reveal>
<Eyebrow>Questions</Eyebrow>
<h2 className="mt-5 font-serif text-5xl leading-[1.05] tracking-tight text-stone-900">Asked, and <em className="text-orange-700">answered.</em></h2>
<p className="mt-6 max-w-sm text-stone-600">Still curious? Our advisors answer within the hour, every day of the year.</p>
</Reveal>
<div className="divide-y divide-stone-900/10 border-y border-stone-900/10">
{faqs.map((f, i) => {
const isOpen = open === i;
return (
<div key={f.q}>
<button onClick={() => setOpen(isOpen ? null : i)} aria-expanded={isOpen} className="group flex w-full items-center justify-between gap-6 py-6 text-left">
<span className={cn('font-serif text-2xl transition-colors', isOpen ? 'text-stone-900' : 'text-stone-700 group-hover:text-stone-900')}>{f.q}</span>
<span className={cn('grid h-9 w-9 shrink-0 place-items-center rounded-full border transition-all duration-500 ease-spring', isOpen ? 'rotate-45 border-orange-700 bg-orange-700 text-white' : 'border-stone-900/15 text-stone-700')}>
<Plus className="h-4 w-4" />
</span>
</button>
<div className={cn('grid transition-all duration-500 ease-smooth', isOpen ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0')}>
<div className="overflow-hidden">
<p className="max-w-xl pb-6 leading-relaxed text-stone-600">{f.a}</p>
</div>
</div>
</div>
);
})}
</div>
</div>
</section>
);
}
export function SignupCta() {
const [email, setEmail] = useState('');
const [status, setStatus] = useState<'idle' | 'loading' | 'done' | 'error'>('idle');
const submit = (e: FormEvent) => {
e.preventDefault();
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
setStatus('error');
return;
}
setStatus('loading');
window.setTimeout(() => setStatus('done'), 1200);
};
return (
<section id="aurelia-cta" className="scroll-mt-24 px-6 pb-28">
<Reveal>
<div className="relative mx-auto max-w-6xl overflow-hidden rounded-[40px] bg-orange-700 px-8 py-20 text-center md:px-16 md:py-28">
<div className="pointer-events-none absolute inset-0 opacity-20" style={{ backgroundImage: 'radial-gradient(#fff 1px, transparent 1px)', backgroundSize: '7px 7px' }} />
<div className="pointer-events-none absolute -bottom-32 left-1/2 h-80 w-[700px] -translate-x-1/2 rounded-full bg-amber-300/40 blur-[100px]" />
<div className="relative">
<h2 className="mx-auto max-w-3xl font-serif text-5xl leading-[1.02] tracking-tight text-white md:text-7xl">
Your future self <em>is already grateful.</em>
</h2>
<p className="mx-auto mt-6 max-w-md text-orange-50">Open an account in four minutes. Start with any amount. First year of Reserve is on us.</p>
{status === 'done' ? (
<div className="mx-auto mt-10 inline-flex items-center gap-3 rounded-full bg-white px-6 py-4 text-stone-900 animate-scale-in">
<span className="grid h-7 w-7 place-items-center rounded-full bg-emerald-700 text-white"><Check className="h-4 w-4" /></span>
Check your inbox, we have saved you a seat.
</div>
) : (
<form onSubmit={submit} noValidate className="mx-auto mt-10 flex max-w-md flex-col gap-2 rounded-full bg-white/10 p-1.5 ring-1 ring-white/25 backdrop-blur sm:flex-row">
<input
type="email"
value={email}
onChange={(e) => { setEmail(e.target.value); if (status === 'error') setStatus('idle'); }}
placeholder="you@example.com"
aria-label="Email address"
className="h-12 flex-1 rounded-full bg-transparent px-5 text-white placeholder:text-orange-100/70 focus:outline-none"
/>
<Pill type="submit" disabled={status === 'loading'} className="disabled:opacity-80">
{status === 'loading' ? <Loader2 className="h-4 w-4 animate-spin" /> : <>Get started <ArrowRight className="h-4 w-4" /></>}
</Pill>
</form>
)}
<p className={cn('mt-4 text-sm transition-opacity', status === 'error' ? 'text-white opacity-100' : 'opacity-0')} role="alert">
Please enter a valid email address.
</p>
</div>
</div>
</Reveal>
</section>
);
}
const columns = [
{ title: 'Products', links: ['Invest', 'Save', 'Card', 'Private'] },
{ title: 'Company', links: ['About', 'Careers', 'Press', 'Letters'] },
{ title: 'Support', links: ['Help centre', 'Security', 'Contact', 'Status'] },
];
export function AureliaFooter() {
return (
<footer className="relative overflow-hidden border-t border-stone-900/10 px-6 pt-20">
<div className="pointer-events-none absolute inset-0" style={paperGrain} />
<div className="relative mx-auto max-w-6xl">
<div className="grid gap-12 md:grid-cols-[1.5fr_repeat(3,1fr)]">
<div>
<div className="flex items-center gap-2">
<span className="grid h-9 w-9 place-items-center rounded-full bg-stone-900 text-[#f5f1ea]"><Leaf className="h-4 w-4" /></span>
<span className="font-serif text-3xl text-stone-900">Aurelia</span>
</div>
<p className="mt-4 max-w-xs text-sm leading-relaxed text-stone-600">A private wealth studio for people who would rather be living.</p>
<button onClick={() => scrollToId('top')} className="group mt-8 inline-flex items-center gap-2 text-sm text-stone-700 hover:text-stone-900">
<span className="grid h-8 w-8 place-items-center rounded-full border border-stone-900/15 transition-all group-hover:-translate-y-0.5 group-hover:bg-stone-900 group-hover:text-[#f5f1ea]"><ArrowUp className="h-3.5 w-3.5" /></span>
Back to top
</button>
</div>
{columns.map((c) => (
<div key={c.title}>
<h4 className="text-[11px] uppercase tracking-[0.22em] text-stone-500">{c.title}</h4>
<ul className="mt-5 space-y-3">
{c.links.map((l) => (
<li key={l}>
<button className="group relative text-sm text-stone-700 transition-colors hover:text-stone-900">
{l}
<span className="absolute -bottom-0.5 left-0 h-px w-0 bg-orange-700 transition-all duration-300 group-hover:w-full" />
</button>
</li>
))}
</ul>
</div>
))}
</div>
<div className="mt-20 select-none font-serif text-[22vw] leading-[0.8] tracking-tighter text-stone-900/[0.07] lg:text-[260px]" aria-hidden>
Aurelia
</div>
<div className="flex flex-col justify-between gap-4 border-t border-stone-900/10 py-6 text-xs text-stone-500 md:flex-row">
<span>© 2026 Aurelia Wealth, LLC. A NiceUI template.</span>
<span className="max-w-xl md:text-right">Advisory services offered by Aurelia Advisers, an SEC-registered investment adviser. Investing involves risk, including loss of principal.</span>
</div>
</div>
</footer>
);
}components/niceui/pages/landing/shared/motion.tsx
import { type ReactNode, useEffect, useRef, useState } from 'react';
import { cn } from '@/lib/cn';
export function useInView<T extends Element>(threshold = 0.2) {
const ref = useRef<T>(null);
const [inView, setInView] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setInView(true);
io.disconnect();
}
},
{ threshold },
);
io.observe(el);
return () => io.disconnect();
}, [threshold]);
return { ref, inView };
}
export function Reveal({ children, delay = 0, className }: { children: ReactNode; delay?: number; className?: string }) {
const { ref, inView } = useInView<HTMLDivElement>(0.15);
return (
<div
ref={ref}
className={cn('transition-all duration-1000 ease-smooth', inView ? 'translate-y-0 opacity-100 blur-0' : 'translate-y-6 opacity-0 blur-[2px]', className)}
style={{ transitionDelay: `${delay}ms` }}
>
{children}
</div>
);
}
export function useCountUp(target: number, active: boolean, duration = 1800) {
const [val, setVal] = useState(0);
useEffect(() => {
if (!active) return;
let raf = 0;
const start = performance.now();
const loop = (now: number) => {
const t = Math.min(1, (now - start) / duration);
setVal(target * (1 - Math.pow(2, -10 * t)));
if (t < 1) raf = requestAnimationFrame(loop);
else setVal(target);
};
raf = requestAnimationFrame(loop);
return () => cancelAnimationFrame(raf);
}, [target, active, duration]);
return val;
}components/niceui/pages/landing/aurelia/primitives.tsx
import type { ButtonHTMLAttributes, ReactNode } from 'react';
import { cn } from '@/lib/cn';
export function Eyebrow({ children, tone = 'dark' }: { children: ReactNode; tone?: 'dark' | 'light' }) {
return (
<span
className={cn(
'inline-flex items-center gap-2 text-[11px] font-medium uppercase tracking-[0.22em]',
tone === 'dark' ? 'text-stone-500' : 'text-stone-400',
)}
>
<span className="h-px w-6 bg-current" />
{children}
</span>
);
}
type PillProps = ButtonHTMLAttributes<HTMLButtonElement> & { variant?: 'ink' | 'paper' | 'clay' };
export function Pill({ variant = 'ink', className, children, ...rest }: PillProps) {
return (
<button
{...rest}
className={cn(
'group relative inline-flex h-12 items-center justify-center gap-2 overflow-hidden rounded-full px-6 text-sm font-medium transition-all duration-300 ease-smooth active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-orange-600/50 focus-visible:ring-offset-2 focus-visible:ring-offset-[#f5f1ea]',
variant === 'ink' && 'bg-stone-900 text-[#f5f1ea] hover:bg-stone-800',
variant === 'paper' && 'bg-white/70 text-stone-900 ring-1 ring-stone-900/10 backdrop-blur hover:bg-white',
variant === 'clay' && 'bg-orange-700 text-white hover:bg-orange-600',
className,
)}
>
<span className="relative z-10 flex items-center gap-2">{children}</span>
<span className="absolute inset-0 -translate-x-full bg-gradient-to-r from-transparent via-white/15 to-transparent transition-transform duration-700 group-hover:translate-x-full" />
</button>
);
}
export const paperGrain = {
backgroundImage: 'radial-gradient(rgba(41,37,36,0.06) 0.6px, transparent 0.6px)',
backgroundSize: '5px 5px',
};
export function scrollToId(id: string) {
document.getElementById(`aurelia-${id}`)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}components/niceui/pages/landing/aurelia/Features.tsx
import { useEffect, useState, type MouseEvent, type ReactNode } from 'react';
import { Globe2, Lock, Sparkles, Target, Wifi } from 'lucide-react';
import { cn } from '@/lib/cn';
import { Reveal, useInView } from '@/components/niceui/pages/landing/shared/motion';
import { Eyebrow } from './primitives';
const press = ['The Financial Ledger', 'Monocle Weekly', 'Kinfolk Money', 'The Quiet Times', 'Wealth & Form', 'Morning Brief', 'Atlas Review'];
export function PressMarquee() {
return (
<section className="border-y border-stone-900/10 py-8">
<p className="mb-6 text-center text-[11px] uppercase tracking-[0.22em] text-stone-500">As written about in</p>
<div className="relative flex overflow-hidden mask-fade-x">
<div className="flex shrink-0 animate-marquee items-center gap-16 pr-16 [animation-duration:40s]">
{[...press, ...press].map((p, i) => (
<span key={i} className="whitespace-nowrap font-serif text-2xl italic text-stone-400 transition-colors hover:text-stone-900">{p}</span>
))}
</div>
</div>
</section>
);
}
function Tile({ className, children, delay = 0 }: { className?: string; children: ReactNode; delay?: number }) {
return (
<Reveal delay={delay} className={className}>
<div className="group relative h-full overflow-hidden rounded-[28px] border border-stone-900/10 bg-white/70 p-7 transition-all duration-500 ease-smooth hover:-translate-y-1 hover:shadow-[0_30px_60px_-30px_rgba(41,37,36,0.35)]">
{children}
</div>
</Reveal>
);
}
function GoalRing() {
const { ref, inView } = useInView<HTMLDivElement>(0.4);
const r = 52;
const c = 2 * Math.PI * r;
return (
<div ref={ref} className="relative mx-auto mt-6 h-36 w-36">
<svg viewBox="0 0 120 120" className="h-full w-full -rotate-90">
<circle cx="60" cy="60" r={r} fill="none" stroke="#e7e5e4" strokeWidth="8" />
<circle
cx="60" cy="60" r={r} fill="none" stroke="#047857" strokeWidth="8" strokeLinecap="round"
strokeDasharray={c}
strokeDashoffset={inView ? c * 0.28 : c}
className="transition-[stroke-dashoffset] duration-[1800ms] ease-smooth"
/>
</svg>
<div className="absolute inset-0 grid place-items-center text-center">
<div>
<div className="font-serif text-4xl text-stone-900">72%</div>
<div className="text-xs text-stone-500">Kyoto, 2027</div>
</div>
</div>
</div>
);
}
function MetalCard() {
const [tilt, setTilt] = useState({ x: 0, y: 0 });
const onMove = (e: MouseEvent<HTMLDivElement>) => {
const b = e.currentTarget.getBoundingClientRect();
setTilt({ x: ((e.clientY - b.top) / b.height - 0.5) * -14, y: ((e.clientX - b.left) / b.width - 0.5) * 14 });
};
return (
<div className="mt-8 [perspective:1000px]" onMouseMove={onMove} onMouseLeave={() => setTilt({ x: 0, y: 0 })}>
<div
className="relative mx-auto aspect-[1.586] w-full max-w-sm overflow-hidden rounded-2xl bg-gradient-to-br from-stone-800 via-stone-900 to-black p-6 text-[#f5f1ea] shadow-2xl transition-transform duration-200 ease-out"
style={{ transform: `rotateX(${tilt.x}deg) rotateY(${tilt.y}deg)` }}
>
<div
className="pointer-events-none absolute inset-0 opacity-60 mix-blend-overlay"
style={{ background: `radial-gradient(circle at ${50 + tilt.y * 3}% ${50 - tilt.x * 3}%, rgba(255,255,255,0.35), transparent 50%)` }}
/>
<div className="relative flex h-full flex-col justify-between">
<div className="flex items-start justify-between">
<span className="font-serif text-2xl">Aurelia</span>
<Wifi className="h-5 w-5 rotate-90 text-stone-400" />
</div>
<div className="h-8 w-11 rounded-md bg-gradient-to-br from-amber-200 to-amber-500 opacity-90" />
<div className="flex items-end justify-between">
<span className="font-mono text-sm tracking-[0.2em] text-stone-300">•••• 4821</span>
<span className="text-xs uppercase tracking-[0.2em] text-stone-400">Reserve</span>
</div>
</div>
</div>
</div>
);
}
const currencies = [
{ code: 'EUR', rate: '0.9213', delta: '+0.12%' },
{ code: 'GBP', rate: '0.7841', delta: '-0.04%' },
{ code: 'JPY', rate: '149.82', delta: '+0.31%' },
{ code: 'CHF', rate: '0.8812', delta: '+0.08%' },
];
function CurrencyTicker() {
const [active, setActive] = useState(0);
useEffect(() => {
const id = setInterval(() => setActive((a) => (a + 1) % currencies.length), 2000);
return () => clearInterval(id);
}, []);
return (
<div className="mt-6 space-y-1">
{currencies.map((c, i) => (
<div key={c.code} className={cn('flex items-center justify-between rounded-xl px-3 py-2.5 text-sm transition-all duration-500', i === active ? 'bg-stone-900 text-[#f5f1ea]' : 'text-stone-700')}>
<span className="font-medium">USD / {c.code}</span>
<span className="flex gap-4 tabular-nums">
<span>{c.rate}</span>
<span className={cn(i === active ? 'text-orange-300' : c.delta.startsWith('+') ? 'text-emerald-700' : 'text-orange-700')}>{c.delta}</span>
</span>
</div>
))}
</div>
);
}
export function BentoFeatures() {
return (
<section id="aurelia-invest" className="scroll-mt-24 px-6 py-28">
<div className="mx-auto max-w-6xl">
<Reveal className="max-w-2xl">
<Eyebrow>The studio</Eyebrow>
<h2 className="mt-5 font-serif text-5xl leading-[1.05] tracking-tight text-stone-900 md:text-6xl">
Every part of your money, <em className="text-orange-700">considered.</em>
</h2>
</Reveal>
<div className="mt-16 grid gap-4 md:grid-cols-6">
<Tile className="md:col-span-4" delay={0}>
<Sparkles className="h-5 w-5 text-orange-700" />
<h3 className="mt-4 font-serif text-3xl text-stone-900">Portfolios that rebalance themselves</h3>
<p className="mt-2 max-w-md text-stone-600">Built by our investment committee, tuned to your goals, and kept on course every single day.</p>
<div className="mt-8 flex h-32 items-end gap-2">
{[40, 64, 52, 78, 60, 88, 72, 96, 84, 100, 90, 70].map((h, i) => (
<div key={i} className="flex-1 rounded-t-md bg-stone-200 transition-colors duration-300 group-hover:bg-orange-200" style={{ height: `${h}%`, transitionDelay: `${i * 30}ms` }}>
<div className="h-1/3 rounded-t-md bg-orange-700/80" />
</div>
))}
</div>
</Tile>
<Tile className="md:col-span-2" delay={100}>
<Target className="h-5 w-5 text-emerald-700" />
<h3 className="mt-4 font-serif text-3xl text-stone-900">Goals with a horizon</h3>
<GoalRing />
</Tile>
<Tile className="md:col-span-3" delay={150}>
<h3 className="font-serif text-3xl text-stone-900" id="aurelia-card">The Reserve card</h3>
<p className="mt-2 text-stone-600">18g of brushed steel. 1.5% back, invested automatically.</p>
<MetalCard />
</Tile>
<Tile className="md:col-span-3" delay={200}>
<Globe2 className="h-5 w-5 text-orange-700" />
<h3 className="mt-4 font-serif text-3xl text-stone-900">Borderless by default</h3>
<p className="mt-2 text-stone-600">Hold 40 currencies at the real exchange rate. No markups, ever.</p>
<CurrencyTicker />
</Tile>
<Tile className="md:col-span-6" delay={250}>
<div className="flex flex-col items-start justify-between gap-6 md:flex-row md:items-center">
<div className="flex items-center gap-5">
<span className="relative grid h-14 w-14 place-items-center rounded-2xl bg-stone-900 text-[#f5f1ea]">
<Lock className="h-6 w-6" />
<span className="absolute inset-0 rounded-2xl ring-2 ring-orange-600/40 animate-pulse-ring" />
</span>
<div>
<h3 className="font-serif text-3xl text-stone-900">Vault-grade security</h3>
<p className="text-stone-600">SIPC + private insurance up to $2M. SOC 2 Type II. Biometric everything.</p>
</div>
</div>
<div className="flex flex-wrap gap-2">
{['256-bit AES', 'Hardware keys', 'Cold storage', 'Audit trail'].map((t) => (
<span key={t} className="rounded-full border border-stone-900/10 bg-[#f5f1ea] px-3.5 py-1.5 text-sm text-stone-700">{t}</span>
))}
</div>
</div>
</Tile>
</div>
</div>
</section>
);
}components/niceui/pages/landing/aurelia/Hero.tsx
import { useEffect, useState } from 'react';
import { ArrowRight, ArrowUpRight, Leaf, Menu, Play, Repeat, ShieldCheck, X } from 'lucide-react';
import { cn } from '@/lib/cn';
import { useCountUp } from '@/components/niceui/pages/landing/shared/motion';
import { Pill, scrollToId } from './primitives';
const links = ['Invest', 'Save', 'Card', 'Pricing', 'Journal'];
export function AureliaNav() {
const [scrolled, setScrolled] = useState(false);
const [open, setOpen] = useState(false);
useEffect(() => {
const main = document.querySelector('main');
const onScroll = () => setScrolled((main?.scrollTop ?? window.scrollY) > 24);
main?.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('scroll', onScroll, { passive: true });
return () => {
main?.removeEventListener('scroll', onScroll);
window.removeEventListener('scroll', onScroll);
};
}, []);
return (
<div className="sticky top-0 z-40 flex justify-center px-4 pt-4">
<nav
className={cn(
'flex w-full max-w-5xl items-center justify-between rounded-full border px-3 py-2 transition-all duration-500 ease-smooth',
scrolled ? 'border-stone-900/10 bg-[#f5f1ea]/85 shadow-[0_8px_30px_-12px_rgba(41,37,36,0.25)] backdrop-blur-xl' : 'border-transparent bg-transparent',
)}
>
<button onClick={() => scrollToId('top')} className="flex items-center gap-2 pl-2">
<span className="grid h-8 w-8 place-items-center rounded-full bg-stone-900 text-[#f5f1ea]">
<Leaf className="h-4 w-4" />
</span>
<span className="font-serif text-2xl leading-none text-stone-900">Aurelia</span>
</button>
<div className="hidden items-center gap-1 md:flex">
{links.map((l) => (
<button key={l} onClick={() => scrollToId(l.toLowerCase())} className="rounded-full px-4 py-2 text-sm text-stone-600 transition-colors hover:bg-stone-900/5 hover:text-stone-900">
{l}
</button>
))}
</div>
<div className="flex items-center gap-2">
<button onClick={() => scrollToId('cta')} className="hidden px-3 text-sm text-stone-700 hover:text-stone-900 sm:block">Sign in</button>
<Pill className="h-10 px-5" onClick={() => scrollToId('cta')}>Open account</Pill>
<button
onClick={() => setOpen((o) => !o)}
aria-label="Toggle menu"
className="grid h-10 w-10 place-items-center rounded-full text-stone-900 hover:bg-stone-900/5 md:hidden"
>
{open ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
</button>
</div>
</nav>
<div
className={cn(
'absolute inset-x-4 top-20 grid rounded-3xl border border-stone-900/10 bg-[#f5f1ea]/95 backdrop-blur-xl transition-all duration-500 ease-smooth md:hidden',
open ? 'grid-rows-[1fr] opacity-100' : 'pointer-events-none grid-rows-[0fr] opacity-0',
)}
>
<div className="overflow-hidden">
<div className="flex flex-col p-3">
{links.map((l) => (
<button key={l} onClick={() => { setOpen(false); scrollToId(l.toLowerCase()); }} className="rounded-2xl text-left px-4 py-3 font-serif text-2xl text-stone-900 hover:bg-stone-900/5">
{l}
</button>
))}
</div>
</div>
</div>
</div>
);
}
const chartPoints = [12, 18, 15, 24, 22, 30, 28, 36, 34, 44, 41, 52, 50, 60];
function PortfolioCard() {
const balance = useCountUp(248_391.42, true, 2200);
const max = Math.max(...chartPoints);
const path = chartPoints.map((p, i) => `${i === 0 ? 'M' : 'L'} ${(i / (chartPoints.length - 1)) * 300} ${100 - (p / max) * 88}`).join(' ');
const [range, setRange] = useState('1Y');
return (
<div className="relative mx-auto w-full max-w-md">
<div className="absolute -inset-8 rounded-[48px] bg-gradient-to-br from-orange-200/60 via-transparent to-emerald-200/60 blur-3xl" />
<div className="relative rounded-[32px] border border-stone-900/10 bg-white/80 p-6 shadow-[0_40px_80px_-30px_rgba(41,37,36,0.35)] backdrop-blur-xl animate-fade-up">
<div className="flex items-center justify-between">
<span className="text-xs uppercase tracking-[0.2em] text-stone-500">Total wealth</span>
<span className="flex items-center gap-1 rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-medium text-emerald-800 ring-1 ring-emerald-700/15">
<ArrowUpRight className="h-3 w-3" /> 18.4%
</span>
</div>
<div className="mt-3 font-serif text-5xl tabular-nums tracking-tight text-stone-900">
${balance.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div>
<svg viewBox="0 0 300 110" className="mt-6 h-32 w-full overflow-visible">
<defs>
<linearGradient id="aurelia-fill" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stopColor="#c2410c" stopOpacity="0.22" />
<stop offset="100%" stopColor="#c2410c" stopOpacity="0" />
</linearGradient>
</defs>
<path d={`${path} L 300 110 L 0 110 Z`} fill="url(#aurelia-fill)" className="animate-fade-in" />
<path
d={path}
fill="none"
stroke="#c2410c"
strokeWidth="2"
strokeLinecap="round"
strokeDasharray="600"
strokeDashoffset="600"
style={{ animation: 'aurelia-draw 2.4s cubic-bezier(.22,1,.36,1) forwards' }}
/>
<circle cx="300" cy={100 - (chartPoints[chartPoints.length - 1] / max) * 88} r="4" fill="#c2410c" className="animate-fade-in" style={{ animationDelay: '2s', animationFillMode: 'both' }} />
</svg>
<div className="mt-4 flex gap-1 rounded-full bg-stone-100 p-1">
{['1W', '1M', '1Y', 'All'].map((r) => (
<button
key={r}
onClick={() => setRange(r)}
className={cn('flex-1 rounded-full py-1.5 text-xs font-medium transition-all duration-300', range === r ? 'bg-white text-stone-900 shadow-sm' : 'text-stone-500 hover:text-stone-800')}
>
{r}
</button>
))}
</div>
<div className="mt-5 space-y-3">
{[
{ name: 'Global Equities', pct: 62, color: 'bg-orange-700' },
{ name: 'Treasury Bonds', pct: 24, color: 'bg-emerald-700' },
{ name: 'Cash Reserve', pct: 14, color: 'bg-stone-400' },
].map((a, i) => (
<div key={a.name}>
<div className="flex justify-between text-sm">
<span className="text-stone-700">{a.name}</span>
<span className="tabular-nums text-stone-900">{a.pct}%</span>
</div>
<div className="mt-1.5 h-1.5 overflow-hidden rounded-full bg-stone-100">
<div className={cn('h-full origin-left rounded-full', a.color)} style={{ width: `${a.pct}%`, animation: `aurelia-grow 1.4s ${0.6 + i * 0.15}s cubic-bezier(.22,1,.36,1) both` }} />
</div>
</div>
))}
</div>
</div>
<div className="absolute -left-6 top-24 hidden items-center gap-3 rounded-2xl border border-stone-900/10 bg-white/90 p-3 pr-4 shadow-xl backdrop-blur animate-float sm:flex">
<span className="grid h-9 w-9 place-items-center rounded-xl bg-emerald-700 text-white"><Repeat className="h-4 w-4" /></span>
<div>
<div className="text-xs text-stone-500">Auto-invest</div>
<div className="text-sm font-medium text-stone-900">$500 every Friday</div>
</div>
</div>
<div className="absolute -right-4 bottom-16 hidden items-center gap-3 rounded-2xl border border-stone-900/10 bg-stone-900 p-3 pr-4 text-[#f5f1ea] shadow-xl animate-float sm:flex" style={{ animationDelay: '1.5s' }}>
<ShieldCheck className="h-5 w-5 text-orange-300" />
<span className="text-sm">Insured up to $2M</span>
</div>
</div>
);
}
export function AureliaHero() {
return (
<section id="aurelia-top" className="relative overflow-hidden px-6 pb-24 pt-16 md:pt-24">
<style>{`@keyframes aurelia-draw{to{stroke-dashoffset:0}}@keyframes aurelia-grow{from{transform:scaleX(0)}to{transform:scaleX(1)}}`}</style>
<div className="pointer-events-none absolute -top-40 left-1/2 h-[520px] w-[900px] -translate-x-1/2 rounded-full bg-[radial-gradient(closest-side,rgba(234,88,12,0.14),transparent)]" />
<div className="relative mx-auto grid max-w-6xl items-center gap-16 lg:grid-cols-[1.1fr_1fr]">
<div>
<button onClick={() => scrollToId('invest')} className="group inline-flex items-center gap-3 rounded-full border border-stone-900/10 bg-white/60 py-1 pl-1 pr-4 text-sm text-stone-700 backdrop-blur animate-fade-down">
<span className="rounded-full bg-orange-700 px-2.5 py-0.5 text-xs font-medium text-white">New</span>
Tax-loss harvesting is here
<ArrowRight className="h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" />
</button>
<h1 className="mt-8 font-serif text-6xl leading-[1.02] tracking-tight text-stone-900 sm:text-7xl lg:text-[88px]">
{['Wealth,', 'quietly'].map((w, i) => (
<span key={w} className="mr-4 inline-block animate-fade-up" style={{ animationDelay: `${150 + i * 120}ms`, animationFillMode: 'both' }}>{w}</span>
))}
<br />
<em className="inline-block animate-fade-up text-orange-700" style={{ animationDelay: '400ms', animationFillMode: 'both' }}>compounded.</em>
</h1>
<p className="mt-8 max-w-lg text-lg leading-relaxed text-stone-600 animate-fade-up" style={{ animationDelay: '550ms', animationFillMode: 'both' }}>
Aurelia is the private wealth studio for people who would rather be living. Automated portfolios, high-yield savings and a metal card, all in one calm place.
</p>
<div className="mt-10 flex flex-wrap items-center gap-3 animate-fade-up" style={{ animationDelay: '700ms', animationFillMode: 'both' }}>
<Pill onClick={() => scrollToId('cta')}>Start investing <ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-0.5" /></Pill>
<Pill variant="paper" onClick={() => scrollToId('save')}><Play className="h-3.5 w-3.5 fill-current" /> Watch the film</Pill>
</div>
<div className="mt-12 flex items-center gap-4 animate-fade-up" style={{ animationDelay: '850ms', animationFillMode: 'both' }}>
<div className="flex -space-x-2">
{['bg-orange-200', 'bg-emerald-200', 'bg-stone-300', 'bg-amber-200'].map((c, i) => (
<span key={c} className={cn('grid h-9 w-9 place-items-center rounded-full text-xs font-medium text-stone-800 ring-2 ring-[#f5f1ea]', c)}>{'MJKS'[i]}</span>
))}
</div>
<p className="text-sm text-stone-600"><span className="font-medium text-stone-900">140,000+</span> members growing $4.2B</p>
</div>
</div>
<PortfolioCard />
</div>
</section>
);
}components/niceui/pages/landing/aurelia/HowItWorks.tsx
import { useEffect, useRef, useState } from 'react';
import { ArrowUpRight, BadgeCheck, Coins, Landmark, UserRound } from 'lucide-react';
import { cn } from '@/lib/cn';
import { Reveal, useCountUp, useInView } from '@/components/niceui/pages/landing/shared/motion';
import { Eyebrow, scrollToId } from './primitives';
const steps = [
{ icon: UserRound, title: 'Tell us about your life', body: 'Ten thoughtful questions. No jargon. We learn your horizon, your appetite for risk and what you are saving toward.', stat: '4 min', statLabel: 'average setup' },
{ icon: Landmark, title: 'Connect your accounts', body: 'Link any bank in 11,000+ institutions. Roll over old 401(k)s with one signature, and we handle the paperwork.', stat: '11k+', statLabel: 'institutions' },
{ icon: Coins, title: 'Set it, then forget it', body: 'Recurring deposits, round-ups and dividend reinvestment run quietly in the background while you get on with things.', stat: '$0', statLabel: 'trading fees' },
{ icon: BadgeCheck, title: 'Meet your advisor', body: 'Every member has a named, fiduciary advisor. Real humans, on call, legally bound to act in your interest.', stat: '24/7', statLabel: 'human support' },
];
export function HowItWorks() {
const [active, setActive] = useState(0);
const refs = useRef<Array<HTMLDivElement | null>>([]);
useEffect(() => {
const io = new IntersectionObserver(
(entries) => {
entries.forEach((e) => {
if (e.isIntersecting) setActive(Number((e.target as HTMLElement).dataset.index));
});
},
{ rootMargin: '-45% 0px -45% 0px' },
);
refs.current.forEach((el) => el && io.observe(el));
return () => io.disconnect();
}, []);
return (
<section id="aurelia-save" className="scroll-mt-24 bg-[#ede7dd] px-6 py-28">
<div className="mx-auto grid max-w-6xl gap-16 lg:grid-cols-2">
<div className="lg:sticky lg:top-32 lg:h-fit">
<Eyebrow>How it works</Eyebrow>
<h2 className="mt-5 font-serif text-5xl leading-[1.05] tracking-tight text-stone-900 md:text-6xl">
Four steps to a <em className="text-emerald-800">calmer</em> balance sheet.
</h2>
<div className="mt-12 hidden space-y-2 lg:block">
{steps.map((s, i) => (
<button
key={s.title}
onClick={() => refs.current[i]?.scrollIntoView({ behavior: 'smooth', block: 'center' })}
className="flex w-full items-center gap-4 rounded-2xl px-4 py-3 text-left transition-all duration-500"
>
<span className={cn('font-mono text-sm transition-colors duration-500', active === i ? 'text-orange-700' : 'text-stone-400')}>0{i + 1}</span>
<span className="relative h-px flex-1 overflow-hidden bg-stone-900/10">
<span className={cn('absolute inset-y-0 left-0 bg-orange-700 transition-all duration-700 ease-smooth', active === i ? 'w-full' : active > i ? 'w-full opacity-30' : 'w-0')} />
</span>
<span className={cn('w-56 text-sm transition-colors duration-500', active === i ? 'text-stone-900' : 'text-stone-500')}>{s.title}</span>
</button>
))}
</div>
</div>
<div className="space-y-6">
{steps.map((s, i) => (
<div
key={s.title}
ref={(el) => { refs.current[i] = el; }}
data-index={i}
className={cn(
'rounded-[28px] border bg-white p-8 transition-all duration-700 ease-smooth md:p-10',
active === i ? 'border-orange-700/30 shadow-[0_30px_60px_-30px_rgba(194,65,12,0.35)] lg:scale-100 lg:opacity-100' : 'border-stone-900/10 lg:scale-[0.97] lg:opacity-50',
)}
>
<div className="flex items-start justify-between">
<span className={cn('grid h-12 w-12 place-items-center rounded-2xl transition-colors duration-500', active === i ? 'bg-orange-700 text-white' : 'bg-stone-100 text-stone-700')}>
<s.icon className="h-5 w-5" />
</span>
<span className="font-mono text-sm text-stone-400">Step 0{i + 1}</span>
</div>
<h3 className="mt-8 font-serif text-3xl text-stone-900">{s.title}</h3>
<p className="mt-3 leading-relaxed text-stone-600">{s.body}</p>
<div className="mt-8 flex items-baseline gap-2 border-t border-stone-900/10 pt-6">
<span className="font-serif text-4xl text-stone-900">{s.stat}</span>
<span className="text-sm text-stone-500">{s.statLabel}</span>
</div>
</div>
))}
</div>
</div>
</section>
);
}
function Stat({ value, prefix = '', suffix = '', decimals = 0, label, active }: { value: number; prefix?: string; suffix?: string; decimals?: number; label: string; active: boolean }) {
const v = useCountUp(value, active, 2000);
return (
<div className="border-t border-white/15 pt-6">
<div className="font-serif text-6xl tabular-nums tracking-tight text-[#f5f1ea] md:text-7xl">
{prefix}{v.toFixed(decimals)}{suffix}
</div>
<p className="mt-3 text-sm text-stone-400">{label}</p>
</div>
);
}
export function StatsBand() {
const { ref, inView } = useInView<HTMLDivElement>(0.3);
return (
<section className="relative overflow-hidden bg-stone-950 px-6 py-28">
<div className="pointer-events-none absolute inset-0 opacity-[0.07]" style={{ backgroundImage: 'radial-gradient(#f5f1ea 1px, transparent 1px)', backgroundSize: '6px 6px' }} />
<div className="pointer-events-none absolute -right-40 -top-40 h-[480px] w-[480px] rounded-full bg-orange-700/25 blur-[120px]" />
<div ref={ref} className="relative mx-auto max-w-6xl">
<Reveal className="flex flex-col justify-between gap-8 md:flex-row md:items-end">
<h2 className="max-w-xl font-serif text-5xl leading-[1.05] tracking-tight text-[#f5f1ea] md:text-6xl">
Patience, <em className="text-orange-300">measured.</em>
</h2>
<button onClick={() => scrollToId('journal')} className="inline-flex items-center gap-2 text-sm text-stone-300 transition-colors hover:text-white">
Hear from our members <ArrowUpRight className="h-4 w-4" />
</button>
</Reveal>
<div className="mt-16 grid gap-10 sm:grid-cols-2 lg:grid-cols-4">
<Stat value={4.2} prefix="$" suffix="B" decimals={1} label="Assets under care" active={inView} />
<Stat value={9.8} suffix="%" decimals={1} label="Avg. 10-yr annualized return*" active={inView} />
<Stat value={140} suffix="k" label="Members in 38 countries" active={inView} />
<Stat value={4.9} decimals={1} label="App Store rating, 32k reviews" active={inView} />
</div>
<p className="mt-12 text-xs text-stone-500">*Illustrative figures for template purposes. Past performance does not guarantee future results.</p>
</div>
</section>
);
}components/niceui/pages/landing/aurelia/Pricing.tsx
import { useEffect, useRef, useState } from 'react';
import { ArrowLeft, ArrowRight, Check, Quote } from 'lucide-react';
import { cn } from '@/lib/cn';
import { Reveal } from '@/components/niceui/pages/landing/shared/motion';
import { Eyebrow, Pill, scrollToId } from './primitives';
const plans = [
{ name: 'Essential', monthly: 0, yearly: 0, blurb: 'For your first portfolio.', features: ['Automated portfolios', '4.10% APY savings', 'Round-ups', 'Email support'] },
{ name: 'Reserve', monthly: 12, yearly: 9, blurb: 'Our most loved membership.', features: ['Everything in Essential', 'Tax-loss harvesting', 'Reserve metal card', 'Named advisor', 'Priority support'], featured: true },
{ name: 'Private', monthly: 48, yearly: 39, blurb: 'For complex, multi-generational wealth.', features: ['Everything in Reserve', 'Direct indexing', 'Trust & estate planning', 'Private markets access', 'Dedicated wealth team'] },
];
function AnimatedPrice({ value }: { value: number }) {
return (
<span key={value} className="inline-block animate-fade-up font-serif text-6xl tabular-nums text-inherit">
${value}
</span>
);
}
export function Pricing() {
const [yearly, setYearly] = useState(true);
return (
<section id="aurelia-pricing" className="scroll-mt-24 px-6 py-28">
<div className="mx-auto max-w-6xl">
<Reveal className="flex flex-col items-center text-center">
<Eyebrow>Membership</Eyebrow>
<h2 className="mt-5 max-w-2xl font-serif text-5xl leading-[1.05] tracking-tight text-stone-900 md:text-6xl">
One honest price. <em className="text-orange-700">No basis points.</em>
</h2>
<div className="relative mt-10 flex rounded-full border border-stone-900/10 bg-white/70 p-1">
<span className={cn('absolute inset-y-1 w-[calc(50%-4px)] rounded-full bg-stone-900 transition-transform duration-500 ease-spring', yearly ? 'translate-x-full' : 'translate-x-0')} />
{['Monthly', 'Yearly'].map((l, i) => (
<button
key={l}
onClick={() => setYearly(i === 1)}
className={cn('relative z-10 w-32 rounded-full py-2 text-sm font-medium transition-colors duration-300', yearly === (i === 1) ? 'text-[#f5f1ea]' : 'text-stone-600 hover:text-stone-900')}
>
{l}
{i === 1 && <span className={cn('ml-1.5 text-xs', yearly ? 'text-orange-300' : 'text-orange-700')}>-25%</span>}
</button>
))}
</div>
</Reveal>
<div className="mt-16 grid gap-4 lg:grid-cols-3">
{plans.map((p, i) => (
<Reveal key={p.name} delay={i * 100}>
<div
className={cn(
'relative flex h-full flex-col rounded-[28px] border p-8 transition-all duration-500 ease-smooth hover:-translate-y-1',
p.featured ? 'border-stone-900 bg-stone-900 text-[#f5f1ea] shadow-[0_40px_80px_-30px_rgba(41,37,36,0.6)]' : 'border-stone-900/10 bg-white/70 text-stone-900',
)}
>
{p.featured && <span className="absolute right-6 top-6 rounded-full bg-orange-700 px-3 py-1 text-xs font-medium text-white">Most chosen</span>}
<h3 className="font-serif text-3xl">{p.name}</h3>
<p className={cn('mt-1 text-sm', p.featured ? 'text-stone-400' : 'text-stone-500')}>{p.blurb}</p>
<div className="mt-8 flex items-baseline gap-2">
<AnimatedPrice value={yearly ? p.yearly : p.monthly} />
<span className={cn('text-sm', p.featured ? 'text-stone-400' : 'text-stone-500')}>/ month</span>
</div>
<ul className="mt-8 flex-1 space-y-3">
{p.features.map((f) => (
<li key={f} className="flex items-center gap-3 text-sm">
<span className={cn('grid h-5 w-5 place-items-center rounded-full', p.featured ? 'bg-orange-700 text-white' : 'bg-emerald-50 text-emerald-800')}>
<Check className="h-3 w-3" />
</span>
<span className={p.featured ? 'text-stone-200' : 'text-stone-700'}>{f}</span>
</li>
))}
</ul>
<Pill variant={p.featured ? 'clay' : 'ink'} className="mt-10 w-full" onClick={() => scrollToId('cta')}>
{p.monthly === 0 ? 'Start free' : `Choose ${p.name}`}
</Pill>
</div>
</Reveal>
))}
</div>
</div>
</section>
);
}
const stories = [
{ quote: 'I used to check my portfolio daily and lose sleep over it. With Aurelia I look once a quarter, and it is always further along than I expected.', name: 'Margot Ellery', role: 'Architect, Lisbon', tone: 'bg-orange-200' },
{ quote: 'Our advisor helped us set up a trust for the kids in an afternoon. It felt like having a family office without the family-office invoice.', name: 'Daniel & Priya Rao', role: 'Founders, Austin', tone: 'bg-emerald-200' },
{ quote: 'The card is beautiful, but the real magic is the cashback quietly turning into index funds. I have never thought about it once.', name: 'Jonah Whitlock', role: 'Film editor, Brooklyn', tone: 'bg-amber-200' },
];
export function Testimonials() {
const [i, setI] = useState(0);
const [paused, setPaused] = useState(false);
const timer = useRef<number>();
useEffect(() => {
if (paused) return;
timer.current = window.setTimeout(() => setI((n) => (n + 1) % stories.length), 6000);
return () => window.clearTimeout(timer.current);
}, [i, paused]);
const go = (d: number) => setI((n) => (n + d + stories.length) % stories.length);
const s = stories[i];
return (
<section id="aurelia-journal" className="scroll-mt-24 bg-[#ede7dd] px-6 py-28" onMouseEnter={() => setPaused(true)} onMouseLeave={() => setPaused(false)}>
<div className="mx-auto max-w-5xl">
<Eyebrow>Member journal</Eyebrow>
<div className="relative mt-10 min-h-[320px]">
<Quote className="h-10 w-10 text-orange-700/40" />
<blockquote key={i} className="mt-6 animate-fade-up font-serif text-3xl leading-[1.25] text-stone-900 md:text-5xl">
{s.quote}
</blockquote>
</div>
<div className="mt-12 flex flex-col justify-between gap-8 border-t border-stone-900/10 pt-8 sm:flex-row sm:items-center">
<div key={`a-${i}`} className="flex items-center gap-4 animate-fade-in">
<span className={cn('grid h-12 w-12 place-items-center rounded-full font-serif text-xl text-stone-900', s.tone)}>{s.name[0]}</span>
<div>
<div className="font-medium text-stone-900">{s.name}</div>
<div className="text-sm text-stone-500">{s.role}</div>
</div>
</div>
<div className="flex items-center gap-6">
<div className="flex gap-2">
{stories.map((_, n) => (
<button key={n} onClick={() => setI(n)} aria-label={`Story ${n + 1}`} className="relative h-1 w-10 overflow-hidden rounded-full bg-stone-900/10">
<span
key={`${i}-${paused}`}
className={cn('absolute inset-y-0 left-0 bg-stone-900', n < i ? 'w-full' : n > i ? 'w-0' : '')}
style={n === i ? { animation: paused ? 'none' : 'aurelia-progress 6s linear forwards', width: paused ? '100%' : undefined } : undefined}
/>
</button>
))}
</div>
<div className="flex gap-2">
<button onClick={() => go(-1)} aria-label="Previous" className="grid h-11 w-11 place-items-center rounded-full border border-stone-900/15 text-stone-900 transition-all hover:bg-stone-900 hover:text-[#f5f1ea]">
<ArrowLeft className="h-4 w-4" />
</button>
<button onClick={() => go(1)} aria-label="Next" className="grid h-11 w-11 place-items-center rounded-full border border-stone-900/15 text-stone-900 transition-all hover:bg-stone-900 hover:text-[#f5f1ea]">
<ArrowRight className="h-4 w-4" />
</button>
</div>
</div>
</div>
</div>
<style>{`@keyframes aurelia-progress{from{width:0}to{width:100%}}`}</style>
</section>
);
}