Ledger
Business banking page with a 3D card, live transaction feed and security section.
app/page.tsx
"use client";
import { AnimatePresence, motion, useMotionValue, useSpring, useTransform } from "motion/react";
import {
ArrowDownLeft,
ArrowRight,
ArrowUpRight,
Building2,
Check,
CreditCard,
Fingerprint,
Globe2,
Landmark,
Lock,
PiggyBank,
Receipt,
ShieldCheck,
Wallet,
} from "lucide-react";
import { useEffect, useState } from "react";
const ease = [0.22, 1, 0.36, 1] as const;
const up = {
initial: { opacity: 0, y: 24 },
whileInView: { opacity: 1, y: 0 },
viewport: { once: true, amount: 0.3 },
transition: { duration: 0.7, ease },
};
function Card3D() {
const px = useMotionValue(0.5);
const py = useMotionValue(0.5);
const rx = useSpring(useTransform(py, [0, 1], [14, -14]), { stiffness: 150, damping: 16 });
const ry = useSpring(useTransform(px, [0, 1], [-14, 14]), { stiffness: 150, damping: 16 });
return (
<motion.div
onMouseMove={(e) => {
const r = e.currentTarget.getBoundingClientRect();
px.set((e.clientX - r.left) / r.width);
py.set((e.clientY - r.top) / r.height);
}}
onMouseLeave={() => {
px.set(0.5);
py.set(0.5);
}}
style={{ rotateX: rx, rotateY: ry, transformPerspective: 1000 }}
className="relative aspect-[1.586] w-full max-w-sm overflow-hidden rounded-3xl bg-gradient-to-br from-emerald-900 via-emerald-800 to-zinc-900 p-7 text-white shadow-[0_40px_80px_-20px_rgba(6,78,59,0.55)]"
>
<div aria-hidden className="absolute -right-16 -top-16 size-56 rounded-full border-[28px] border-white/5" />
<div className="relative flex h-full flex-col justify-between">
<div className="flex items-center justify-between">
<span className="font-semibold tracking-tight">Ledger</span>
<span className="text-xs uppercase tracking-widest text-emerald-200/70">Business</span>
</div>
<div className="h-9 w-12 rounded-md bg-gradient-to-br from-amber-100 to-amber-300" />
<div>
<p className="font-mono text-lg tracking-[0.2em]">5412 •••• •••• 7720</p>
<div className="mt-2 flex justify-between text-xs uppercase tracking-wider text-white/60">
<span>Northwind Labs</span>
<span>11/30</span>
</div>
</div>
</div>
</motion.div>
);
}
const txs = [
{ icon: Building2, name: "AWS", cat: "Infrastructure", amount: -2840.12 },
{ icon: ArrowDownLeft, name: "Stripe payout", cat: "Revenue", amount: 18420.0 },
{ icon: Receipt, name: "Figma", cat: "Software", amount: -144.0 },
{ icon: ArrowDownLeft, name: "Invoice #1042", cat: "Revenue", amount: 6500.0 },
{ icon: Landmark, name: "Payroll", cat: "Team", amount: -42300.0 },
];
function LiveFeed() {
const [items, setItems] = useState(() => txs.slice(0, 3).map((t, uid) => ({ ...t, uid })));
useEffect(() => {
let i = 3;
const id = setInterval(() => {
const uid = i;
setItems((prev) => [{ ...txs[uid % txs.length], uid }, ...prev].slice(0, 4));
i++;
}, 2400);
return () => clearInterval(id);
}, []);
return (
<div className="w-full max-w-sm rounded-3xl border border-zinc-200 bg-white p-5 shadow-xl shadow-zinc-900/5">
<div className="mb-4 flex items-center justify-between">
<p className="text-sm font-medium text-zinc-900">Live activity</p>
<span className="flex items-center gap-1.5 text-xs text-emerald-600"><span className="size-1.5 animate-pulse rounded-full bg-emerald-500" />Real-time</span>
</div>
<ul className="space-y-1">
<AnimatePresence initial={false}>
{items.map((t) => (
<motion.li
key={t.uid}
layout
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.4, ease }}
className="overflow-hidden"
>
<div className="flex items-center gap-3 rounded-xl px-2 py-2.5">
<span className={`grid size-9 place-items-center rounded-full ${t.amount > 0 ? "bg-emerald-50 text-emerald-600" : "bg-zinc-100 text-zinc-600"}`}>
<t.icon className="size-4" />
</span>
<div className="flex-1">
<p className="text-sm font-medium text-zinc-900">{t.name}</p>
<p className="text-xs text-zinc-500">{t.cat}</p>
</div>
<p className={`text-sm font-medium tabular-nums ${t.amount > 0 ? "text-emerald-600" : "text-zinc-900"}`}>
{t.amount > 0 ? "+" : "-"}${Math.abs(t.amount).toLocaleString("en-US", { minimumFractionDigits: 2 })}
</p>
</div>
</motion.li>
))}
</AnimatePresence>
</ul>
</div>
);
}
function Hero() {
return (
<section className="relative isolate overflow-hidden px-6 pb-24 pt-16">
<div aria-hidden className="absolute inset-0 -z-10 bg-[radial-gradient(ellipse_at_top_right,#d1fae5,transparent_55%)]" />
<div className="mx-auto grid max-w-6xl items-center gap-16 lg:grid-cols-[1.1fr_1fr]">
<div>
<motion.p initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="text-sm font-semibold text-emerald-700">Banking for modern businesses</motion.p>
<motion.h1 initial={{ opacity: 0, y: 24 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.9, ease }} className="mt-5 text-balance text-5xl font-semibold leading-[1.05] tracking-tight text-zinc-900 sm:text-6xl">
Your company's finances, finally in one place
</motion.h1>
<motion.p initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.2, duration: 0.8, ease }} className="mt-6 max-w-lg text-lg text-zinc-500">
Business accounts, corporate cards, bill pay and accounting automation — with 4.1% APY on idle cash.
</motion.p>
<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.35, duration: 0.8, ease }} className="mt-9 flex flex-wrap gap-3">
<a href="#" className="group inline-flex items-center gap-2 rounded-full bg-emerald-700 px-6 py-3 text-sm font-medium text-white shadow-lg shadow-emerald-700/25 transition hover:-translate-y-0.5 hover:bg-emerald-800">
Open an account <ArrowRight className="size-4 transition-transform group-hover:translate-x-0.5" />
</a>
<a href="#" className="rounded-full border border-zinc-200 bg-white px-6 py-3 text-sm font-medium text-zinc-900 hover:bg-zinc-50">Talk to sales</a>
</motion.div>
<ul className="mt-10 grid max-w-md grid-cols-2 gap-3 text-sm text-zinc-600">
{["No monthly fees", "FDIC insured to $5M", "Unlimited virtual cards", "Opens in 10 minutes"].map((f) => (
<li key={f} className="flex items-center gap-2"><Check className="size-4 text-emerald-600" />{f}</li>
))}
</ul>
</div>
<motion.div initial={{ opacity: 0, y: 40 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.3, duration: 1, ease }} className="flex flex-col items-center gap-6">
<Card3D />
<LiveFeed />
</motion.div>
</div>
</section>
);
}
const products = [
{ icon: Wallet, title: "Business accounts", text: "Checking and treasury in one dashboard, with multi-user permissions." },
{ icon: CreditCard, title: "Corporate cards", text: "Physical and virtual cards with per-card limits and instant receipts." },
{ icon: Receipt, title: "Bill pay", text: "Upload an invoice, approve it, and we schedule ACH or wire for you." },
{ icon: PiggyBank, title: "Treasury", text: "Earn up to 4.1% on idle cash through money-market funds." },
{ icon: Globe2, title: "Global payments", text: "Send to 140+ countries at real exchange rates with no markups." },
{ icon: Landmark, title: "Accounting sync", text: "Two-way sync with QuickBooks, Xero and NetSuite. Month-end in minutes." },
];
function Products() {
return (
<section className="px-6 py-24">
<div className="mx-auto max-w-6xl">
<motion.h2 {...up} className="max-w-2xl text-4xl font-semibold tracking-tight text-zinc-900 sm:text-5xl">One platform replaces five finance tools</motion.h2>
<div className="mt-14 grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
{products.map((p, i) => (
<motion.div key={p.title} {...up} transition={{ ...up.transition, delay: i * 0.06 }} whileHover={{ y: -6 }} className="rounded-3xl border border-zinc-200 bg-white p-8 transition-shadow hover:shadow-xl hover:shadow-emerald-900/5">
<span className="grid size-12 place-items-center rounded-2xl bg-emerald-50 text-emerald-700"><p.icon className="size-5" /></span>
<h3 className="mt-6 text-lg font-semibold text-zinc-900">{p.title}</h3>
<p className="mt-2 text-sm leading-relaxed text-zinc-500">{p.text}</p>
</motion.div>
))}
</div>
</div>
</section>
);
}
function Security() {
const items = [
{ icon: ShieldCheck, title: "FDIC insured", text: "Up to $5M through our partner bank sweep network." },
{ icon: Fingerprint, title: "Biometric approvals", text: "Every large payment requires Face ID or a hardware key." },
{ icon: Lock, title: "Bank-grade encryption", text: "AES-256 at rest, TLS 1.3 in transit, SOC 2 Type II audited." },
];
return (
<section className="bg-emerald-950 px-6 py-24 text-white">
<div className="mx-auto grid max-w-6xl gap-14 lg:grid-cols-2 lg:items-center">
<motion.div {...up}>
<p className="text-sm font-semibold text-emerald-400">Security</p>
<h2 className="mt-3 text-4xl font-semibold tracking-tight sm:text-5xl">Protected at every layer</h2>
<p className="mt-5 max-w-md text-emerald-100/70">Your money is held at regulated partner banks. We add the controls and monitoring modern finance teams expect.</p>
</motion.div>
<div className="space-y-4">
{items.map((it, i) => (
<motion.div key={it.title} {...up} transition={{ ...up.transition, delay: i * 0.1 }} className="flex gap-5 rounded-2xl border border-white/10 bg-white/5 p-6">
<it.icon className="size-6 shrink-0 text-emerald-400" />
<div>
<h3 className="font-semibold">{it.title}</h3>
<p className="mt-1 text-sm text-emerald-100/70">{it.text}</p>
</div>
</motion.div>
))}
</div>
</div>
</section>
);
}
function Quote() {
return (
<section className="px-6 py-24">
<motion.figure {...up} className="mx-auto max-w-3xl text-center">
<blockquote className="text-balance text-3xl font-medium leading-snug tracking-tight text-zinc-900 sm:text-4xl">
“We closed our books in two days instead of two weeks. Ledger paid for itself in the first month.”
</blockquote>
<figcaption className="mt-8 text-sm text-zinc-500">
<span className="font-medium text-zinc-900">Maya Johansson</span> · Head of Finance, Northwind Labs
</figcaption>
</motion.figure>
</section>
);
}
function Cta() {
return (
<section className="px-6 pb-24">
<motion.div {...up} className="mx-auto flex max-w-6xl flex-col items-start justify-between gap-8 rounded-[2rem] bg-emerald-50 p-10 sm:p-14 md:flex-row md:items-center">
<div>
<h2 className="text-3xl font-semibold tracking-tight text-zinc-900 sm:text-4xl">Open your account in 10 minutes</h2>
<p className="mt-3 text-zinc-600">No minimums. No monthly fees. Cancel anytime.</p>
</div>
<a href="#" className="group inline-flex shrink-0 items-center gap-2 rounded-full bg-emerald-700 px-7 py-3.5 text-sm font-medium text-white transition hover:bg-emerald-800">
Get started <ArrowUpRight className="size-4 transition-transform group-hover:rotate-45" />
</a>
</motion.div>
</section>
);
}
export default function LedgerTemplate() {
return (
<div className="min-h-screen bg-white text-zinc-900 antialiased">
<header className="sticky top-0 z-50 border-b border-zinc-100 bg-white/80 backdrop-blur-lg">
<div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-6">
<span className="flex items-center gap-2 font-semibold">
<span className="grid size-8 place-items-center rounded-lg bg-emerald-700 text-white"><Landmark className="size-4" /></span>
Ledger
</span>
<nav className="hidden gap-8 text-sm text-zinc-600 md:flex">
<a href="#" className="hover:text-zinc-900">Accounts</a>
<a href="#" className="hover:text-zinc-900">Cards</a>
<a href="#" className="hover:text-zinc-900">Treasury</a>
<a href="#" className="hover:text-zinc-900">Pricing</a>
</nav>
<div className="flex items-center gap-3">
<a href="#" className="hidden text-sm text-zinc-600 sm:block">Sign in</a>
<a href="#" className="rounded-full bg-zinc-900 px-4 py-2 text-sm font-medium text-white">Open account</a>
</div>
</div>
</header>
<main>
<Hero />
<Products />
<Security />
<Quote />
<Cta />
</main>
<footer className="border-t border-zinc-100 px-6 py-10 text-xs leading-relaxed text-zinc-400">
<div className="mx-auto max-w-6xl">
<p>© {new Date().getFullYear()} Ledger Financial Technologies. Ledger is a financial technology company, not a bank. Banking services are provided by partner banks, Members FDIC.</p>
</div>
</footer>
</div>
);
}
What's included
- Navbar
- Hero + 3D card
- Live feed
- Products
- Security
- Testimonial
- CTA
Use it in 3 steps
1. Install dependencies in your Next.js + Tailwind project:
$ npm i motion lucide-react- 2. Copy the code into
app/page.tsx. - 3. Replace the copy, colors and links with your own — then deploy.