Pocket: mobile money app
A bold five-page landing site for a money app: a working app inside a phone frame, live feature demos (freeze a card, round-up calculator, bill splitting, currency conversion), pricing with a yearly switch, security and download.
// Ballmac UI: Pocket template fonts. https://ui.ballmac.com/templates/template-pocket
import { Gabarito } from "next/font/google"
export const pocketSans = Gabarito({ variable: "--pocket-sans", subsets: ["latin"], display: "swap", preload: false })// Ballmac UI: Pocket template data. https://ui.ballmac.com/templates/template-pocket
export const transactions = [
{ name: "Corner Café", detail: "Today, 8:12", amount: -4.8, tone: 1, emoji: "☕" },
{ name: "From Sam", detail: "Yesterday", amount: 120, tone: 0, emoji: "↓" },
{ name: "City Transit", detail: "Yesterday", amount: -2.75, tone: 3, emoji: "🚆" },
{ name: "Market Hall", detail: "Monday", amount: -38.12, tone: 2, emoji: "🥬" },
{ name: "Pilates Studio", detail: "Sunday", amount: -22, tone: 4, emoji: "🧘" },
]
export type Plan = { id: string; name: string; monthly: number; annual: number; blurb: string; features: string[]; featured?: boolean }
export const plans: Plan[] = [
{ id: "free", name: "Free", monthly: 0, annual: 0, blurb: "Everything you need to stop using a spreadsheet.", features: ["Free virtual card", "Instant transfers to friends", "Round-ups to one pot", "Spending insights"] },
{ id: "plus", name: "Plus", monthly: 6, annual: 5, blurb: "For people who travel, save and split things.", features: ["Everything in Free", "Fee-free spending in 40 currencies", "Unlimited savings pots", "Bill splitting with reminders", "Priority chat support"], featured: true },
{ id: "metal", name: "Metal", monthly: 16, annual: 13, blurb: "A heavy card and a lot of perks.", features: ["Everything in Plus", "Metal card, engraved", "Airport lounge passes", "Travel and phone insurance", "Cashback on every purchase"] },
]
export const compare = [
["Virtual card", true, true, true],
["Physical card", "$5", true, true],
["Savings pots", "1", "Unlimited", "Unlimited"],
["Foreign spending fee", "1.5%", "None", "None"],
["ATM withdrawals a month", "$200", "$800", "Unlimited"],
["Bill splitting", false, true, true],
["Cashback", false, false, "1%"],
["Support", "Chat", "Priority chat", "24/7 phone"],
] as const
export const faqs = [
{ q: "Is Pocket a bank?", a: "Pocket is a money app. Accounts are held at Harbor Bank, N.A., Member FDIC, and your deposits are insured up to $250,000." },
{ q: "How do round-ups work?", a: "Every card purchase is rounded up to the next dollar and the difference moves to your pot. Spend $4.20 and $0.80 is saved." },
{ q: "Are there hidden fees?", a: "No. The Free plan has no monthly fee, no overdraft fee and no minimum balance. Paid plans show their price on this page and nowhere else." },
{ q: "What if I lose my phone?", a: "Sign in on another device, freeze the card in one tap and revoke the old phone. Nothing is stored on the device." },
{ q: "Which countries does it work in?", a: "The card works anywhere Visa does. Sending money to friends is available in 31 countries today." },
{ q: "Can I close my account?", a: "Yes, any time, in the app. Your balance goes to the bank account you choose within one working day." },
]
export const currencies = [["USD", 1, "$"], ["EUR", 0.92, "€"], ["GBP", 0.79, "£"], ["JPY", 156.4, "¥"], ["MXN", 17.1, "MX$"]] as const
export const money = (n: number, digits = 2) => `${n < 0 ? "−" : ""}$${Math.abs(n).toLocaleString("en-US", { minimumFractionDigits: digits, maximumFractionDigits: digits })}`// Ballmac UI: Pocket template shell. https://ui.ballmac.com/templates/template-pocket
"use client"
import * as React from "react"
import { Apple, Bell, CreditCard, Home as HomeIcon, Menu, PiggyBank, Play, QrCode, Send, X } from "lucide-react"
import { PhoneFrame } from "@/components/ballmac/phone-frame"
import { Switch } from "@/components/ballmac/switch"
import { money, transactions } from "@/components/ballmac/templates/pocket/pocket-data"
import { pocketSans } from "@/components/ballmac/templates/pocket/pocket-fonts"
import { cn } from "@/lib/utils"
type PocketPage = "home" | "features" | "pricing" | "security" | "download"
type PocketHrefs = Record<PocketPage, string>
const defaultHrefs: PocketHrefs = { home: "/pocket", features: "/pocket/features", pricing: "/pocket/pricing", security: "/pocket/security", download: "/pocket/download" }
/** Pocket's palette: mint paper, navy ink, cobalt and one loud lime. Dark mode is midnight with the lime turned up. */
const pocketCss = `
.pocket-theme,body:not(:has([data-gallery])):has(.pocket-theme){--background:oklch(0.985 0.012 165);--foreground:oklch(0.2 0.06 265);--card:oklch(1 0 0);--card-foreground:oklch(0.2 0.06 265);--popover:oklch(1 0 0);--popover-foreground:oklch(0.2 0.06 265);--primary:oklch(0.47 0.22 267);--primary-foreground:oklch(0.99 0.005 165);--secondary:oklch(0.95 0.025 165);--secondary-foreground:oklch(0.2 0.06 265);--muted:oklch(0.95 0.02 165);--muted-foreground:oklch(0.45 0.05 265);--accent:oklch(0.93 0.04 150);--accent-foreground:oklch(0.2 0.06 265);--border:oklch(0.2 0.06 265 / 12%);--input:oklch(0.2 0.06 265 / 20%);--ring:oklch(0.55 0.22 267);--surface:oklch(0.965 0.02 165);--destructive:oklch(0.52 0.21 27);--chart-1:oklch(0.9 0.2 125);--chart-2:oklch(0.72 0.17 30);--chart-3:oklch(0.62 0.2 300);--chart-4:oklch(0.78 0.13 200);--chart-5:oklch(0.85 0.14 85);--pocket-ink:oklch(0.2 0.06 265);--pocket-on-lime:oklch(0.2 0.06 265);--pocket-navy:oklch(0.2 0.07 265);--pocket-on-navy:oklch(0.98 0.01 165);--radius:1.5rem}
.dark .pocket-theme,.dark body:not(:has([data-gallery])):has(.pocket-theme){--background:oklch(0.16 0.045 265);--foreground:oklch(0.96 0.015 165);--card:oklch(0.21 0.05 265);--card-foreground:oklch(0.96 0.015 165);--popover:oklch(0.23 0.05 265);--popover-foreground:oklch(0.96 0.015 165);--primary:oklch(0.9 0.2 125);--primary-foreground:oklch(0.2 0.06 265);--secondary:oklch(0.26 0.05 265);--secondary-foreground:oklch(0.96 0.015 165);--muted:oklch(0.25 0.05 265);--muted-foreground:oklch(0.76 0.04 250);--accent:oklch(0.3 0.06 265);--accent-foreground:oklch(0.96 0.015 165);--border:oklch(1 0 0 / 11%);--input:oklch(1 0 0 / 16%);--ring:oklch(0.9 0.2 125);--surface:oklch(0.19 0.047 265);--destructive:oklch(0.7 0.19 27);--chart-1:oklch(0.9 0.2 125);--chart-2:oklch(0.76 0.15 30);--chart-3:oklch(0.74 0.17 300);--chart-4:oklch(0.8 0.12 200);--chart-5:oklch(0.86 0.13 85);--pocket-navy:oklch(0.12 0.045 265)}
body:not(:has([data-gallery])):has(.pocket-theme){font-family:var(--pocket-sans),ui-sans-serif,system-ui,sans-serif}
@keyframes pocket-bob{0%,100%{transform:translateY(0) rotate(var(--r,0deg))}50%{transform:translateY(-8px) rotate(var(--r,0deg))}}
.pocket-bob{animation:pocket-bob 5s ease-in-out infinite}
@media (prefers-reduced-motion:reduce){.pocket-bob{animation:none}}
`
const display = "font-black tracking-[-0.035em]"
const onLime = "text-[var(--pocket-on-lime)]"
const txTone = ["bg-chart-1", "bg-chart-5", "bg-chart-4", "bg-chart-2", "bg-chart-3"]
/** The Pocket app inside a phone: balance, quick actions and three tabs (Home, Card, Save). Tap around. */
function PocketApp({ className, defaultTab = "home" }: { className?: string; defaultTab?: "home" | "card" | "save" }) {
const [tab, setTab] = React.useState<"home" | "card" | "save">(defaultTab)
const [frozen, setFrozen] = React.useState(false)
const id = React.useId()
const tabs = [["home", "Home", HomeIcon], ["card", "Card", CreditCard], ["save", "Save", PiggyBank]] as const
return (
<PhoneFrame screenWidth={390} className={cn("w-[min(100%,290px)]", className)} screenClassName="pocket-theme bg-background text-foreground">
<div className="flex h-full flex-col text-[15px]" style={{ fontFamily: "var(--pocket-sans), ui-sans-serif, system-ui, sans-serif" }}>
<div className="flex items-center justify-between px-5 pt-2">
<div className="flex items-center gap-3"><span aria-hidden="true" className="bg-chart-3 flex size-10 items-center justify-center rounded-full font-black text-white">M</span><div><p className="text-muted-foreground text-xs">Good morning</p><p className="font-extrabold">Maya</p></div></div>
<span aria-hidden="true" className="bg-secondary flex size-10 items-center justify-center rounded-full"><Bell className="size-5" /></span>
</div>
<div role="tabpanel" id={`${id}-panel`} aria-labelledby={`${id}-${tab}`} className="min-h-0 flex-1 overflow-hidden px-5 pt-4">
{tab === "home" && (
<>
<div className="bg-primary text-primary-foreground rounded-3xl p-5">
<p className="text-sm font-semibold">Total balance</p>
<p className={cn("mt-1 text-[40px] leading-none tabular-nums", display)}>$12,480<span className="text-2xl opacity-100">.62</span></p>
<p className="bg-card text-card-foreground mt-3 inline-block rounded-full px-3 py-1 text-xs font-extrabold">+$212.40 this week</p>
</div>
<div className="mt-4 grid grid-cols-4 gap-2 text-center text-xs font-bold">{[["Send", Send], ["Request", Apple], ["Top up", QrCode], ["More", Play]].map(([l, Icon]) => { const I = Icon as typeof Send; return <div key={l as string}><span aria-hidden="true" className="bg-secondary mx-auto flex size-12 items-center justify-center rounded-2xl"><I className="size-5" /></span><span className="mt-1 block">{l as string}</span></div> })}</div>
<p className="mt-4 text-sm font-extrabold">Recent</p>
<ul className="mt-2 grid gap-2.5">{transactions.slice(0, 4).map((t) => <li key={t.name} className="flex items-center gap-3"><span aria-hidden="true" className={cn("flex size-10 items-center justify-center rounded-full text-base text-[var(--pocket-on-lime)]", txTone[t.tone])}>{t.emoji}</span><span className="min-w-0 flex-1"><span className="block truncate font-bold">{t.name}</span><span className="text-muted-foreground block text-xs">{t.detail}</span></span><span className={cn("font-extrabold tabular-nums", t.amount > 0 && "text-primary")}>{t.amount > 0 ? "+" : ""}{money(t.amount)}</span></li>)}</ul>
</>
)}
{tab === "card" && (
<>
<div className={cn("relative aspect-[1.6] overflow-hidden rounded-3xl p-5 text-[var(--pocket-on-navy)] transition-opacity motion-reduce:transition-none", "bg-[var(--pocket-navy)]", frozen && "opacity-50")}>
<div aria-hidden="true" className="bg-chart-1 absolute -end-10 -bottom-10 aspect-square w-40 rounded-full" /><div aria-hidden="true" className="bg-chart-3 absolute -end-2 -bottom-16 aspect-square w-32 rounded-full" />
<p className={cn("relative text-xl", display)}>pocket</p><p className="relative mt-8 text-lg font-bold tracking-widest tabular-nums">•••• 4821</p><p className="relative mt-1 text-xs">MAYA OKAFOR · 09/29</p>
{frozen && <span className="bg-chart-4 absolute top-4 end-4 rounded-full px-3 py-1 text-xs font-extrabold text-[var(--pocket-on-lime)]">Frozen</span>}
</div>
<div className="bg-secondary mt-4 flex items-center justify-between rounded-2xl p-4"><span><span className="block font-extrabold">Freeze card</span><span className="text-muted-foreground text-xs">Stops every payment instantly</span></span><Switch checked={frozen} onCheckedChange={setFrozen} aria-label="Freeze card" /></div>
<dl className="mt-3 grid gap-2 text-sm">{[["Daily limit", "$2,000"], ["Online payments", "On"], ["Contactless", "On"]].map(([k, v]) => <div key={k} className="bg-card flex justify-between rounded-xl border px-4 py-3"><dt className="text-muted-foreground">{k}</dt><dd className="font-extrabold">{v}</dd></div>)}</dl>
</>
)}
{tab === "save" && (
<>
<div className="bg-chart-1 rounded-3xl p-5 text-[var(--pocket-on-lime)]"><p className="text-sm font-bold">Holiday fund</p><p className={cn("mt-1 text-4xl tabular-nums", display)}>$1,240</p><div className="mt-3 h-3 overflow-hidden rounded-full bg-black/15" role="progressbar" aria-label="Holiday fund progress" aria-valuemin={0} aria-valuemax={100} aria-valuenow={62}><div className="h-full w-[62%] rounded-full bg-[var(--pocket-ink)]" /></div><p className="mt-2 text-xs font-bold">62% of $2,000 · Lisbon, June</p></div>
<p className="mt-4 text-sm font-extrabold">Round-ups this month</p>
<p className={cn("text-3xl tabular-nums", display)}>$37.20</p>
<ul className="mt-3 grid gap-2">{[["Corner Café", "+$0.20"], ["City Transit", "+$0.25"], ["Market Hall", "+$0.88"]].map(([n, a]) => <li key={n} className="bg-card flex justify-between rounded-xl border px-4 py-3"><span className="font-bold">{n}</span><span className="text-primary font-extrabold tabular-nums">{a}</span></li>)}</ul>
</>
)}
</div>
<div role="tablist" aria-label="App sections" className="bg-card mx-4 mb-7 mt-2 grid grid-cols-3 rounded-full border p-1">
{tabs.map(([k, label, Icon]) => <button key={k} type="button" role="tab" id={`${id}-${k}`} aria-selected={tab === k} aria-controls={`${id}-panel`} onClick={() => setTab(k)} className="aria-selected:bg-primary aria-selected:text-primary-foreground focus-visible:ring-ring/50 flex h-10 items-center justify-center gap-1.5 rounded-full text-sm font-extrabold outline-none focus-visible:ring-2"><Icon className="size-4" aria-hidden="true" />{label}</button>)}
</div>
</div>
</PhoneFrame>
)
}
/** App Store and Google Play buttons that go to the download page. */
function StoreButtons({ href, className }: { href: string; className?: string }) {
const b = "bg-[var(--pocket-navy)] text-[var(--pocket-on-navy)] focus-visible:ring-ring inline-flex h-14 items-center gap-3 rounded-2xl px-5 outline-none transition-transform hover:-translate-y-0.5 focus-visible:ring-[3px] motion-reduce:transition-none motion-reduce:hover:translate-y-0"
return (
<div className={cn("flex flex-wrap gap-3", className)}>
<a href={href} className={b}><Apple className="size-7" aria-hidden="true" /><span className="text-start leading-tight"><span className="block text-[11px] font-medium">Download on the</span><span className="block text-lg font-extrabold">App Store</span></span></a>
<a href={href} className={b}><Play className="size-7" aria-hidden="true" /><span className="text-start leading-tight"><span className="block text-[11px] font-medium">Get it on</span><span className="block text-lg font-extrabold">Google Play</span></span></a>
</div>
)
}
const links: { key: PocketPage; label: string }[] = [{ key: "features", label: "Features" }, { key: "pricing", label: "Pricing" }, { key: "security", label: "Security" }]
type PocketShellProps = React.ComponentProps<"div"> & {
/** The page being shown, so its nav link is marked current. */
page: PocketPage
/** Override where pages live (used by previews). */
hrefs?: Partial<PocketHrefs>
}
/** Pocket's frame: a floating pill header, and a navy footer with a giant wordmark. */
function PocketShell({ page, hrefs: overrides, className, style, children, ...props }: PocketShellProps) {
const hrefs = { ...defaultHrefs, ...overrides }
const [open, setOpen] = React.useState(false)
React.useEffect(() => {
const classes = [pocketSans.variable].filter(Boolean)
document.body.classList.add(...classes)
return () => document.body.classList.remove(...classes)
}, [])
return (
<div
data-slot="pocket"
className={cn("pocket-theme bg-background text-foreground relative min-h-dvh overflow-x-clip", pocketSans.variable, className)}
style={{ fontFamily: "var(--pocket-sans), ui-sans-serif, system-ui, sans-serif", ...style }}
{...props}
>
<style>{pocketCss}</style>
<header className="sticky top-3 z-40 px-3 sm:px-6">
<div className="bg-card/90 mx-auto flex h-16 max-w-6xl items-center justify-between rounded-full border pe-2 ps-5 shadow-sm backdrop-blur-xl">
<a href={hrefs.home} className={cn("focus-visible:ring-ring/50 flex items-center gap-2 rounded-full text-2xl outline-none focus-visible:ring-[3px]", display)}><span aria-hidden="true" className="bg-primary text-primary-foreground flex size-8 items-center justify-center rounded-xl text-lg">p</span>pocket</a>
<nav aria-label="Main" className="hidden items-center gap-1 md:flex">{links.map((l) => <a key={l.key} href={hrefs[l.key]} aria-current={page === l.key ? "page" : undefined} className="hover:bg-accent aria-[current=page]:bg-accent focus-visible:ring-ring/50 rounded-full px-4 py-2 text-sm font-extrabold outline-none transition-colors focus-visible:ring-[3px] motion-reduce:transition-none">{l.label}</a>)}</nav>
<div className="flex items-center gap-1">
<a href={hrefs.download} className="bg-primary text-primary-foreground focus-visible:ring-ring/50 inline-flex h-11 items-center rounded-full px-5 text-sm font-extrabold outline-none transition-transform hover:scale-[1.03] focus-visible:ring-[3px] motion-reduce:transition-none motion-reduce:hover:scale-100">Get the app</a>
<button type="button" aria-label={open ? "Close menu" : "Open menu"} aria-expanded={open} aria-controls="pocket-mobile-menu" onClick={() => setOpen((v) => !v)} className="hover:bg-accent focus-visible:ring-ring/50 inline-flex size-11 items-center justify-center rounded-full outline-none focus-visible:ring-[3px] md:hidden">{open ? <X className="size-5" aria-hidden="true" /> : <Menu className="size-5" aria-hidden="true" />}</button>
</div>
</div>
{open && <nav id="pocket-mobile-menu" aria-label="Mobile" className="bg-card mx-auto mt-2 max-w-6xl rounded-3xl border p-2 md:hidden">{links.map((l) => <a key={l.key} href={hrefs[l.key]} className="hover:bg-accent block rounded-2xl px-4 py-3 text-lg font-extrabold">{l.label}</a>)}</nav>}
</header>
{children}
<footer className="mt-24 overflow-hidden bg-[var(--pocket-navy)] text-[var(--pocket-on-navy)]">
<div className="mx-auto grid max-w-6xl gap-10 px-4 pt-16 pb-6 sm:px-6 md:grid-cols-[1.4fr_1fr_1fr_1fr]">
<div><p className="max-w-xs text-xl font-bold text-pretty">Money that keeps up with you.</p><StoreButtons href={hrefs.download} className="mt-6 [&>a]:bg-white/10" /></div>
{[["Product", [["Features", hrefs.features], ["Pricing", hrefs.pricing], ["Download", hrefs.download]]], ["Trust", [["Security", hrefs.security], ["Status", hrefs.security], ["Bug bounty", hrefs.security]]], ["Company", [["About", hrefs.home], ["Careers", hrefs.home], ["Press", hrefs.home]]]].map(([t, items]) => <div key={t as string}><h2 className="text-xs font-extrabold tracking-[0.14em] uppercase">{t as string}</h2><ul className="mt-4 grid gap-2.5">{(items as string[][]).map(([l, h]) => <li key={l}><a href={h} className="hover:underline">{l}</a></li>)}</ul></div>)}
</div>
<p aria-hidden="true" className={cn("text-chart-1 -mb-[0.18em] select-none text-center text-[clamp(6rem,26vw,22rem)] leading-none", display)}>pocket</p>
<p className="mx-auto max-w-6xl px-4 pb-6 text-xs sm:px-6">Pocket is a financial technology company, not a bank. Banking services by Harbor Bank, N.A., Member FDIC. © 2026 Pocket Labs.</p>
</footer>
</div>
)
}
export { PocketApp, PocketShell, StoreButtons, defaultHrefs as pocketDefaultHrefs, display as pocketDisplayClass, onLime as pocketOnLime, type PocketHrefs, type PocketPage, type PocketShellProps }// Ballmac UI: Pocket home page. https://ui.ballmac.com/templates/template-pocket
"use client"
import * as React from "react"
import { ArrowRight, Globe2, PiggyBank, ShieldCheck, Snowflake, Users } from "lucide-react"
import { PocketApp, PocketShell, StoreButtons, pocketDisplayClass, type PocketHrefs } from "@/components/ballmac/templates/pocket/pocket-theme"
import { cn } from "@/lib/utils"
const cards = [
{ icon: PiggyBank, title: "Round-ups that add up", body: "Every purchase rounds up to the next dollar and the change lands in a pot. Most people save $40 a month without noticing.", tone: "bg-chart-1" },
{ icon: Snowflake, title: "Freeze it in one tap", body: "Lost your card? Tap once and every payment stops. Tap again and you are back.", tone: "bg-chart-4" },
{ icon: Globe2, title: "Spend anywhere", body: "Pay in 40 currencies at the real exchange rate with no fee. The rate you see is the rate you get.", tone: "bg-chart-5" },
{ icon: Users, title: "Split without the awkward", body: "Send a request for your share of dinner, with a gentle reminder so you do not have to be the one who asks.", tone: "bg-chart-2" },
]
const reviews = [
["It’s the first money app I open on purpose.", "Priya N.", "App Store"],
["Round-ups paid for our entire holiday. I did nothing.", "Tom & Lucia", "Google Play"],
["Froze my card from a taxi in Lisbon. Unfroze it at the hotel.", "Dev R.", "App Store"],
]
type PocketHomeProps = React.ComponentProps<"div"> & { hrefs?: Partial<PocketHrefs> }
/** The Pocket home page: a hero with a working app in a phone, four feature blocks, proof and the download buttons. */
function PocketHome({ hrefs, ...props }: PocketHomeProps) {
const link = { features: "/pocket/features", pricing: "/pocket/pricing", download: "/pocket/download", security: "/pocket/security", ...hrefs }
return (
<PocketShell page="home" hrefs={hrefs} {...props}>
<main>
<section aria-labelledby="ph-title" className="relative isolate mx-auto grid max-w-6xl items-center gap-12 px-4 pt-14 pb-20 sm:px-6 lg:grid-cols-[1.15fr_1fr] lg:pt-24">
<div aria-hidden="true" className="bg-chart-1/50 absolute top-10 end-0 -z-10 aspect-square w-[min(80vw,34rem)] rounded-full blur-3xl" />
<div>
<p className="bg-card inline-flex items-center gap-2 rounded-full border px-4 py-1.5 text-sm font-extrabold"><span className="bg-chart-1 size-2.5 rounded-full" aria-hidden="true" />2.4 million pockets and counting</p>
<h1 id="ph-title" className={cn("mt-6 text-[clamp(3.4rem,10vw,8rem)] leading-[0.88] text-balance", pocketDisplayClass)}>Money that <span className="bg-chart-1 inline-block -rotate-2 rounded-2xl px-3 text-[var(--pocket-on-lime)]">keeps up.</span></h1>
<p className="text-muted-foreground mt-6 max-w-lg text-xl font-medium text-pretty">One app for spending, saving and splitting. No fees to open, no fees to leave, and a card you can freeze from the back of a taxi.</p>
<StoreButtons href={link.download} className="mt-8" />
<p className="text-muted-foreground mt-4 flex items-center gap-2 text-sm font-semibold"><ShieldCheck className="size-4" aria-hidden="true" />FDIC insured up to $250,000 · Rated 4.8 by 61,000 people</p>
</div>
<div className="relative mx-auto w-full max-w-sm">
<PocketApp className="mx-auto w-[min(100%,300px)]" />
<p aria-hidden="true" className="bg-chart-1 pocket-bob absolute top-16 -start-2 rounded-2xl px-4 py-2 text-sm font-extrabold text-[var(--pocket-on-lime)] shadow-lg [--r:-6deg] sm:-start-10" style={{ transform: "rotate(-6deg)" }}>+$0.20 saved</p>
<p aria-hidden="true" className="bg-card pocket-bob absolute end-0 bottom-28 flex items-center gap-2 rounded-2xl border px-4 py-2 text-sm font-extrabold shadow-lg [--r:5deg] sm:-end-8" style={{ transform: "rotate(5deg)", animationDelay: "1.2s" }}><Snowflake className="text-primary size-4" />Card frozen</p>
</div>
</section>
<section aria-label="Featured in" className="border-y">
<div className="mx-auto flex max-w-6xl flex-wrap items-center justify-center gap-x-12 gap-y-3 px-4 py-6 sm:px-6"><span className="text-muted-foreground text-xs font-extrabold tracking-[0.14em] uppercase">As seen in</span>{["The Daily Ledger", "Fast Money", "Wired Weekly", "Product Hunt"].map((n) => <span key={n} className={cn("text-muted-foreground text-xl", pocketDisplayClass)}>{n}</span>)}</div>
</section>
<section aria-labelledby="ph-feat" className="mx-auto max-w-6xl px-4 py-20 sm:px-6">
<h2 id="ph-feat" className={cn("max-w-2xl text-[clamp(2.4rem,6vw,4.5rem)] leading-[0.95] text-balance", pocketDisplayClass)}>Everything your money app should have done years ago.</h2>
<ul className="mt-12 grid gap-4 md:grid-cols-2">{cards.map((c) => <li key={c.title} className={cn("rounded-[2rem] p-8 text-[var(--pocket-on-lime)] sm:p-10", c.tone)}><c.icon className="size-9" aria-hidden="true" /><h3 className={cn("mt-10 text-3xl", pocketDisplayClass)}>{c.title}</h3><p className="mt-3 max-w-md text-lg font-medium text-pretty">{c.body}</p></li>)}</ul>
<a href={link.features} className="bg-primary text-primary-foreground focus-visible:ring-ring/50 mt-8 inline-flex h-14 items-center gap-2 rounded-full px-8 text-lg font-extrabold outline-none focus-visible:ring-[3px]">See every feature <ArrowRight className="size-5 rtl:rotate-180" aria-hidden="true" /></a>
</section>
<section aria-label="Pocket in numbers" className="bg-[var(--pocket-navy)] text-[var(--pocket-on-navy)]">
<ul className="mx-auto grid max-w-6xl gap-8 px-4 py-16 sm:grid-cols-3 sm:px-6">{[["$0", "to open, hold or close an account"], ["40", "currencies at the real rate"], ["11 min", "average wait for a human reply"]].map(([n, l]) => <li key={l}><span className={cn("text-chart-1 block text-[clamp(3.5rem,8vw,6rem)] leading-none", pocketDisplayClass)}>{n}</span><span className="mt-2 block text-lg font-medium">{l}</span></li>)}</ul>
</section>
<section aria-labelledby="ph-rev" className="mx-auto max-w-6xl px-4 py-20 sm:px-6">
<h2 id="ph-rev" className={cn("text-[clamp(2.4rem,6vw,4rem)] leading-none", pocketDisplayClass)}>People say nice things</h2>
<ul className="mt-10 grid gap-4 md:grid-cols-3">{reviews.map(([q, n, s], i) => <li key={n} className={cn("bg-card rounded-[2rem] border p-8", i === 1 && "md:translate-y-6")}><p className="text-2xl leading-snug font-extrabold text-balance">“{q}”</p><p className="text-muted-foreground mt-6 text-sm font-bold">{n} · {s}</p></li>)}</ul>
</section>
<section aria-labelledby="ph-cta" className="mx-auto max-w-6xl px-4 pb-8 sm:px-6">
<div className="bg-primary text-primary-foreground rounded-[2.5rem] px-6 py-16 text-center sm:px-14">
<h2 id="ph-cta" className={cn("mx-auto max-w-2xl text-[clamp(2.4rem,6vw,4.5rem)] leading-[0.95] text-balance", pocketDisplayClass)}>Open an account in about four minutes.</h2>
<p className="mx-auto mt-4 max-w-md text-lg font-medium text-pretty">All you need is your phone and a photo ID. We will do the rest.</p>
<div className="mt-8 flex justify-center"><a href={link.download} className="bg-[var(--pocket-navy)] text-[var(--pocket-on-navy)] focus-visible:ring-ring inline-flex h-14 items-center gap-2 rounded-full px-8 text-lg font-extrabold outline-none focus-visible:ring-[3px]">Get Pocket <ArrowRight className="size-5 rtl:rotate-180" aria-hidden="true" /></a></div>
</div>
</section>
</main>
</PocketShell>
)
}
export { PocketHome, type PocketHomeProps }// Ballmac UI: Pocket features page. https://ui.ballmac.com/templates/template-pocket
"use client"
import * as React from "react"
import { Send, Snowflake } from "lucide-react"
import { Slider } from "@/components/ballmac/slider"
import { Switch } from "@/components/ballmac/switch"
import { currencies, money } from "@/components/ballmac/templates/pocket/pocket-data"
import { PocketShell, pocketDisplayClass, type PocketHrefs } from "@/components/ballmac/templates/pocket/pocket-theme"
import { cn } from "@/lib/utils"
const bars = [["Food", 340, "bg-chart-1"], ["Travel", 215, "bg-chart-4"], ["Fun", 180, "bg-chart-5"], ["Home", 120, "bg-chart-2"]] as const
const people = [["Sam", "bg-chart-1", true], ["Ana", "bg-chart-4", true], ["Joe", "bg-chart-5", false], ["Mei", "bg-chart-2", false]] as const
type PocketFeaturesProps = React.ComponentProps<"div"> & { hrefs?: Partial<PocketHrefs> }
/** The features page: five live demos instead of five screenshots. Freeze a card, tune round-ups, split a bill, convert money. */
function PocketFeatures({ hrefs, ...props }: PocketFeaturesProps) {
const [frozen, setFrozen] = React.useState(false)
const [spend, setSpend] = React.useState(900)
const [paid, setPaid] = React.useState<string[]>(["Sam", "Ana"])
const [amount, setAmount] = React.useState("100")
const [cur, setCur] = React.useState<(typeof currencies)[number][0]>("EUR")
const savedPerMonth = Math.round(spend * 0.045)
const rate = currencies.find((c) => c[0] === cur)!
const converted = (Number(amount) || 0) * rate[1]
const bill = 128
const share = bill / people.length
const total = bars.reduce((n, b) => n + b[1], 0)
const card = "rounded-[2rem] border p-8 sm:p-10"
return (
<PocketShell page="features" hrefs={hrefs} {...props}>
<main className="mx-auto max-w-6xl px-4 pt-14 pb-8 sm:px-6 sm:pt-20">
<h1 className={cn("max-w-3xl text-[clamp(3rem,9vw,7rem)] leading-[0.9] text-balance", pocketDisplayClass)}>Try it right here.</h1>
<p className="text-muted-foreground mt-5 max-w-xl text-xl font-medium text-pretty">Everything below works. Freeze the card, drag the slider, split the bill.</p>
<div className="mt-14 grid gap-4 lg:grid-cols-6">
<section aria-labelledby="pf-card" className={cn(card, "bg-[var(--pocket-navy)] text-[var(--pocket-on-navy)] lg:col-span-3")}>
<h2 id="pf-card" className={cn("text-3xl", pocketDisplayClass)}>Freeze it in one tap</h2>
<p className="mt-2 max-w-sm font-medium text-pretty">Lost it? Misplaced it? Stop every payment instantly, then undo it when you find it in a coat.</p>
<div className={cn("relative mt-8 aspect-[1.6] max-w-sm overflow-hidden rounded-3xl bg-primary p-6 text-primary-foreground transition-all duration-500 motion-reduce:transition-none", frozen && "scale-[0.97] opacity-60 saturate-0")}>
<p className={cn("text-2xl", pocketDisplayClass)}>pocket</p><p className="mt-12 text-xl font-bold tracking-widest tabular-nums">•••• •••• •••• 4821</p>
{frozen && <span className="bg-card text-card-foreground absolute top-5 end-5 flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-extrabold"><Snowflake className="size-4" aria-hidden="true" />Frozen</span>}
</div>
<label className="mt-6 flex max-w-sm cursor-pointer items-center justify-between gap-4 rounded-2xl bg-white/10 p-4"><span className="font-extrabold">Freeze card</span><Switch checked={frozen} onCheckedChange={setFrozen} /></label>
<p role="status" className="mt-3 text-sm font-semibold">{frozen ? "Payments are off. Nothing can be charged." : "Your card is live."}</p>
</section>
<section aria-labelledby="pf-round" className={cn(card, "bg-chart-1 text-[var(--pocket-on-lime)] lg:col-span-3")}>
<h2 id="pf-round" className={cn("text-3xl", pocketDisplayClass)}>Round-ups, calculated</h2>
<p className="mt-2 max-w-sm font-medium text-pretty">Tell us roughly what you spend on your card each month.</p>
<p id="pf-spend" className="mt-8 flex items-baseline justify-between font-extrabold"><span>Monthly card spending</span><span className="text-2xl tabular-nums">{money(spend, 0)}</span></p>
<Slider aria-labelledby="pf-spend" thumbLabels={["Monthly spending"]} min={200} max={3000} step={50} value={[spend]} onValueChange={(v) => setSpend(v[0]!)} className="mt-4 [&_[data-slot=slider-track]]:bg-black/20 [&_[data-slot=slider-range]]:bg-[var(--pocket-ink)] [&_[data-slot=slider-thumb]]:border-[var(--pocket-ink)] [&_[data-slot=slider-thumb]]:bg-white" />
<div className="mt-10"><p className="font-extrabold">You would save about</p><p className={cn("text-[clamp(3.5rem,8vw,6rem)] leading-none tabular-nums", pocketDisplayClass)}><output aria-live="polite">{money(savedPerMonth, 0)}</output></p><p className="mt-1 font-bold">a month · {money(savedPerMonth * 12, 0)} a year</p></div>
</section>
<section aria-labelledby="pf-ins" className={cn(card, "bg-card lg:col-span-2")}>
<h2 id="pf-ins" className={cn("text-3xl", pocketDisplayClass)}>Where it went</h2>
<p className="text-muted-foreground mt-2 text-lg font-medium">{money(total, 0)} in August</p>
<ul className="mt-6 grid gap-4">{bars.map(([n, v, c]) => <li key={n}><div className="flex justify-between text-sm font-extrabold"><span>{n}</span><span className="tabular-nums">{money(v, 0)}</span></div><div className="bg-muted mt-1.5 h-4 overflow-hidden rounded-full" aria-hidden="true"><div className={cn("h-full rounded-full", c)} style={{ width: `${(v / 340) * 100}%` }} /></div></li>)}</ul>
</section>
<section aria-labelledby="pf-split" className={cn(card, "bg-card lg:col-span-2")}>
<h2 id="pf-split" className={cn("text-3xl", pocketDisplayClass)}>Split the bill</h2>
<p className="text-muted-foreground mt-2 font-medium">Dinner, {money(bill, 0)} · {money(share, 0)} each</p>
<ul className="mt-5 grid gap-2">{people.map(([n, c]) => { const isPaid = paid.includes(n); return <li key={n} className="flex items-center justify-between gap-3"><span className="flex items-center gap-3"><span aria-hidden="true" className={cn("flex size-10 items-center justify-center rounded-full font-black text-[var(--pocket-on-lime)]", c)}>{n[0]}</span><span className="font-bold">{n}</span></span>{isPaid ? <span className="bg-secondary rounded-full px-3 py-1 text-sm font-extrabold">Paid</span> : <button type="button" onClick={() => setPaid((p) => [...p, n])} className="bg-primary text-primary-foreground focus-visible:ring-ring/50 inline-flex h-9 items-center gap-1.5 rounded-full px-4 text-sm font-extrabold outline-none focus-visible:ring-[3px]"><Send className="size-3.5 rtl:-scale-x-100" aria-hidden="true" />Remind {n}</button>}</li> })}</ul>
<p role="status" className="text-muted-foreground mt-4 text-sm font-semibold">{paid.length === people.length ? "Everyone has paid." : `${people.length - paid.length} still to pay.`}</p>
</section>
<section aria-labelledby="pf-fx" className={cn(card, "bg-chart-4 text-[var(--pocket-on-lime)] lg:col-span-2")}>
<h2 id="pf-fx" className={cn("text-3xl", pocketDisplayClass)}>Real exchange rate</h2>
<p className="mt-2 font-medium">No fee, no markup.</p>
<div className="mt-6 grid gap-3">
<div className="grid gap-1.5"><label htmlFor="pf-amount" className="text-sm font-extrabold">You send (USD)</label><input id="pf-amount" inputMode="decimal" value={amount} onChange={(e) => setAmount(e.target.value.replace(/[^\d.]/g, ""))} className="h-12 rounded-2xl border-2 border-[var(--pocket-ink)] bg-white/60 px-4 text-lg font-extrabold tabular-nums outline-none focus-visible:ring-[3px] focus-visible:ring-[var(--pocket-ink)]/40" /></div>
<div role="group" aria-label="Currency" className="flex flex-wrap gap-2">{currencies.slice(1).map(([c]) => <button key={c} type="button" aria-pressed={cur === c} onClick={() => setCur(c)} className="aria-pressed:bg-[var(--pocket-ink)] aria-pressed:text-white focus-visible:ring-[var(--pocket-ink)]/50 h-10 rounded-full border-2 border-[var(--pocket-ink)] px-4 text-sm font-extrabold outline-none focus-visible:ring-[3px]">{c}</button>)}</div>
<p className="mt-2 font-extrabold">They receive</p>
<p className={cn("text-4xl tabular-nums", pocketDisplayClass)}><output aria-live="polite">{rate[2]}{converted.toLocaleString("en-US", { maximumFractionDigits: cur === "JPY" ? 0 : 2, minimumFractionDigits: cur === "JPY" ? 0 : 2 })}</output></p>
<p className="text-sm font-bold">1 USD = {rate[1]} {cur}</p>
</div>
</section>
</div>
</main>
</PocketShell>
)
}
export { PocketFeatures, type PocketFeaturesProps }// Ballmac UI: Pocket pricing page. https://ui.ballmac.com/templates/template-pocket
"use client"
import * as React from "react"
import { Check, Minus } from "lucide-react"
import { compare, faqs, plans } from "@/components/ballmac/templates/pocket/pocket-data"
import { PocketShell, pocketDisplayClass, type PocketHrefs } from "@/components/ballmac/templates/pocket/pocket-theme"
import { cn } from "@/lib/utils"
type PocketPricingProps = React.ComponentProps<"div"> & { hrefs?: Partial<PocketHrefs> }
/** Pricing: a monthly or yearly switch, three plans, a real comparison table and the questions people ask. */
function PocketPricing({ hrefs, ...props }: PocketPricingProps) {
const link = { download: "/pocket/download", ...hrefs }
const [yearly, setYearly] = React.useState(true)
const cell = (v: string | boolean) => (v === true ? <><Check className="mx-auto size-5" aria-hidden="true" /><span className="sr-only">Included</span></> : v === false ? <><Minus className="text-muted-foreground mx-auto size-5" aria-hidden="true" /><span className="sr-only">Not included</span></> : v)
return (
<PocketShell page="pricing" hrefs={hrefs} {...props}>
<main className="mx-auto max-w-6xl px-4 pt-14 pb-8 sm:px-6 sm:pt-20">
<div className="text-center">
<h1 className={cn("mx-auto max-w-3xl text-[clamp(3rem,9vw,7rem)] leading-[0.9] text-balance", pocketDisplayClass)}>Pay for perks, not for banking.</h1>
<p className="text-muted-foreground mx-auto mt-5 max-w-xl text-xl font-medium text-pretty">The Free plan is free forever. Upgrade only when you want the extras.</p>
<div role="group" aria-label="Billing period" className="bg-secondary mt-8 inline-flex rounded-full p-1">
{([[false, "Monthly"], [true, "Yearly"]] as const).map(([v, l]) => <button key={l} type="button" aria-pressed={yearly === v} onClick={() => setYearly(v)} className="aria-pressed:bg-primary aria-pressed:text-primary-foreground focus-visible:ring-ring/50 inline-flex h-11 items-center gap-2 rounded-full px-6 text-sm font-extrabold outline-none focus-visible:ring-[3px]">{l}{v && <span className="bg-chart-1 rounded-full px-2 py-0.5 text-[11px] text-[var(--pocket-on-lime)]">Save 20%</span>}</button>)}
</div>
</div>
<ul className="mt-12 grid gap-4 lg:grid-cols-3">
{plans.map((p) => {
const price = yearly ? p.annual : p.monthly
return (
<li key={p.id} className={cn("relative flex flex-col rounded-[2rem] border p-8", p.featured ? "bg-primary text-primary-foreground border-transparent lg:-my-4 lg:py-12" : "bg-card")}>
{p.featured && <span className="bg-chart-1 absolute -top-3 start-8 rounded-full px-3 py-1 text-xs font-extrabold text-[var(--pocket-on-lime)]">Most popular</span>}
<h2 className={cn("text-2xl", pocketDisplayClass)}>{p.name}</h2>
<p className="mt-1 text-sm font-medium">{p.blurb}</p>
<p className="mt-6 flex items-baseline gap-1"><span className={cn("text-6xl leading-none tabular-nums", pocketDisplayClass)}>${price}</span><span className="text-sm font-bold">{price === 0 ? "forever" : "a month"}</span></p>
<p className="mt-1 min-h-5 text-xs font-semibold">{price > 0 && yearly ? `Billed $${price * 12} a year` : price > 0 ? "Billed monthly" : "No card needed"}</p>
<ul className="mt-6 grid flex-1 gap-3">{p.features.map((f) => <li key={f} className="flex gap-3 text-sm font-semibold"><Check className="mt-0.5 size-4 shrink-0" aria-hidden="true" />{f}</li>)}</ul>
<a href={link.download} className={cn("focus-visible:ring-ring mt-8 inline-flex h-12 items-center justify-center rounded-full px-6 font-extrabold outline-none focus-visible:ring-[3px]", p.featured ? "bg-card text-card-foreground" : "bg-primary text-primary-foreground")}>{p.monthly === 0 ? "Get Pocket Free" : `Try ${p.name} free for 30 days`}</a>
</li>
)
})}
</ul>
<section aria-labelledby="pp-compare" className="mt-24">
<h2 id="pp-compare" className={cn("text-[clamp(2rem,5vw,3.5rem)] leading-none", pocketDisplayClass)}>Compare plans</h2>
<div role="region" aria-label="Plan comparison" tabIndex={0} className="focus-visible:ring-ring/50 mt-8 overflow-x-auto rounded-3xl border outline-none focus-visible:ring-[3px]">
<table className="w-full min-w-[34rem] text-start text-sm">
<thead className="bg-secondary"><tr><th scope="col" className="p-4 font-extrabold">Feature</th>{plans.map((p) => <th key={p.id} scope="col" className="p-4 text-center font-extrabold">{p.name}</th>)}</tr></thead>
<tbody className="divide-y">{compare.map((r) => <tr key={r[0]}><th scope="row" className="p-4 font-bold">{r[0]}</th>{[r[1], r[2], r[3]].map((v, i) => <td key={i} className="p-4 text-center font-semibold">{cell(v)}</td>)}</tr>)}</tbody>
</table>
</div>
</section>
<section aria-labelledby="pp-faq" className="mt-24 grid gap-8 lg:grid-cols-[1fr_1.6fr]">
<h2 id="pp-faq" className={cn("text-[clamp(2rem,5vw,3.5rem)] leading-none", pocketDisplayClass)}>Questions</h2>
<div className="divide-y border-y">{faqs.map((f) => <details key={f.q} className="py-5"><summary className="focus-visible:ring-ring/50 cursor-pointer list-none rounded-md text-lg font-extrabold outline-none focus-visible:ring-[3px]">{f.q}</summary><p className="text-muted-foreground mt-3 text-pretty">{f.a}</p></details>)}</div>
</section>
</main>
</PocketShell>
)
}
export { PocketPricing, type PocketPricingProps }// Ballmac UI: Pocket security page. https://ui.ballmac.com/templates/template-pocket
"use client"
import * as React from "react"
import { Fingerprint, KeyRound, LockKeyhole, ShieldCheck, Smartphone, Snowflake } from "lucide-react"
import { PocketShell, pocketDisplayClass, type PocketHrefs } from "@/components/ballmac/templates/pocket/pocket-theme"
import { cn } from "@/lib/utils"
const pillars = [
{ icon: Fingerprint, title: "Only you get in", body: "Face or fingerprint on your phone, plus a passkey that never leaves the device. No passwords to leak.", tone: "bg-chart-1" },
{ icon: LockKeyhole, title: "Encrypted end to end", body: "Everything is encrypted in transit and at rest. Card numbers live in a hardware vault, not in a database.", tone: "bg-chart-4" },
{ icon: KeyRound, title: "You decide every payment", body: "Set limits, switch off online or contactless, and approve unusual payments with a tap.", tone: "bg-chart-5" },
{ icon: ShieldCheck, title: "Insured and audited", body: "Deposits are FDIC insured up to $250,000, and independent auditors test us every quarter.", tone: "bg-chart-2" },
]
const steps = [
{ icon: Smartphone, t: "0:00", title: "You notice it’s gone", body: "Open Pocket on any other device and sign in with your passkey." },
{ icon: Snowflake, t: "0:20", title: "Freeze the card", body: "One tap stops every payment. The old phone is signed out at the same time." },
{ icon: LockKeyhole, t: "1:30", title: "Nothing was on the phone", body: "No balances or card numbers are stored on the device, so there is nothing to find." },
{ icon: ShieldCheck, t: "5:00", title: "A new card is on its way", body: "Order a replacement and it arrives in three days. The virtual card works in minutes." },
]
type PocketSecurityProps = React.ComponentProps<"div"> & { hrefs?: Partial<PocketHrefs> }
/** The security page: four pillars, a minute-by-minute lost-phone timeline, live status and a bug bounty. */
function PocketSecurity({ hrefs, ...props }: PocketSecurityProps) {
return (
<PocketShell page="security" hrefs={hrefs} {...props}>
<main className="mx-auto max-w-6xl px-4 pt-14 pb-8 sm:px-6 sm:pt-20">
<section aria-labelledby="pse-title" className="grid items-center gap-10 lg:grid-cols-[1.3fr_1fr]">
<div>
<p className="bg-card inline-flex items-center gap-2 rounded-full border px-4 py-1.5 text-sm font-extrabold"><span className="bg-chart-1 size-2.5 rounded-full" aria-hidden="true" />All systems normal · 99.99% uptime this year</p>
<h1 id="pse-title" className={cn("mt-6 text-[clamp(3rem,9vw,7rem)] leading-[0.9] text-balance", pocketDisplayClass)}>Boring on purpose.</h1>
<p className="text-muted-foreground mt-5 max-w-xl text-xl font-medium text-pretty">Money should be the least exciting thing about your phone. This is how we make sure it is.</p>
</div>
<div aria-hidden="true" className="relative mx-auto aspect-square w-full max-w-sm">
<div className="bg-chart-1 absolute inset-[6%] rounded-full" /><div className="bg-[var(--pocket-navy)] absolute inset-[22%] rounded-full" /><ShieldCheck className="text-chart-1 absolute inset-[36%] size-[28%]" strokeWidth={1.6} />
<span className="bg-card absolute top-[8%] end-[2%] rotate-6 rounded-2xl border px-3 py-1.5 text-sm font-extrabold shadow-lg">256-bit</span><span className="bg-card absolute bottom-[10%] start-[0%] -rotate-6 rounded-2xl border px-3 py-1.5 text-sm font-extrabold shadow-lg">Passkeys</span>
</div>
</section>
<ul className="mt-20 grid gap-4 md:grid-cols-2">{pillars.map((p) => <li key={p.title} className={cn("rounded-[2rem] p-8 text-[var(--pocket-on-lime)] sm:p-10", p.tone)}><p.icon className="size-9" aria-hidden="true" /><h2 className={cn("mt-8 text-3xl", pocketDisplayClass)}>{p.title}</h2><p className="mt-3 text-lg font-medium text-pretty">{p.body}</p></li>)}</ul>
<section aria-labelledby="pse-lost" className="mt-24">
<h2 id="pse-lost" className={cn("max-w-2xl text-[clamp(2.2rem,5.5vw,4rem)] leading-[0.95] text-balance", pocketDisplayClass)}>What happens when you lose your phone</h2>
<ol className="mt-12 grid gap-4 md:grid-cols-4">{steps.map((s, i) => <li key={s.title} className="bg-card relative rounded-3xl border p-6"><span className="text-primary text-sm font-extrabold tabular-nums">{s.t}</span><s.icon className="mt-6 size-7" aria-hidden="true" /><h3 className="mt-4 text-xl font-extrabold text-balance">{s.title}</h3><p className="text-muted-foreground mt-2 text-sm font-medium text-pretty">{s.body}</p><span className="sr-only">Step {i + 1} of {steps.length}</span></li>)}</ol>
</section>
<section aria-labelledby="pse-bounty" className="mt-24 grid gap-4 lg:grid-cols-2">
<div className="bg-[var(--pocket-navy)] rounded-[2rem] p-8 text-[var(--pocket-on-navy)] sm:p-12"><h2 id="pse-bounty" className={cn("text-4xl", pocketDisplayClass)}>Find a bug, get paid.</h2><p className="mt-3 text-lg font-medium text-pretty">Our bug bounty pays up to $50,000 for serious findings. We publish every fixed report and credit the researcher.</p><p className={cn("text-chart-1 mt-8 text-7xl leading-none tabular-nums", pocketDisplayClass)}>$50k</p></div>
<div className="bg-card rounded-[2rem] border p-8 sm:p-12"><h2 className={cn("text-4xl", pocketDisplayClass)}>Checked by others</h2><ul className="mt-6 grid grid-cols-2 gap-3">{["SOC 2 Type II", "ISO 27001", "PCI DSS Level 1", "FDIC insured"].map((c) => <li key={c} className="bg-secondary flex items-center gap-2 rounded-2xl p-4 text-sm font-extrabold"><ShieldCheck className="size-5 shrink-0" aria-hidden="true" />{c}</li>)}</ul><p className="text-muted-foreground mt-6 text-sm font-medium">Reports are available to customers on request. Last audit: August 2026.</p></div>
</section>
</main>
</PocketShell>
)
}
export { PocketSecurity, type PocketSecurityProps }// Ballmac UI: Pocket download page. https://ui.ballmac.com/templates/template-pocket
"use client"
import * as React from "react"
import { Check, Smartphone } from "lucide-react"
import { PocketApp, PocketShell, StoreButtons, pocketDisplayClass, type PocketHrefs } from "@/components/ballmac/templates/pocket/pocket-theme"
import { cn } from "@/lib/utils"
/** A decorative QR-style code: three finder squares and a fixed pseudo-random fill, so server and client agree. */
function QrArt({ className }: { className?: string }) {
const n = 21
const finder = (x: number, y: number) => (x < 7 && y < 7) || (x >= n - 7 && y < 7) || (x < 7 && y >= n - 7)
const cells: React.ReactNode[] = []
let seed = 7
for (let y = 0; y < n; y++)
for (let x = 0; x < n; x++) {
seed = (seed * 1103515245 + 12345) & 0x7fffffff
if (finder(x, y)) continue
if ((seed >> 8) % 100 < 46) cells.push(<rect key={`${x}-${y}`} x={x} y={y} width="1" height="1" />)
}
const eye = (x: number, y: number) => (
<g key={`${x}${y}`}><rect x={x} y={y} width="7" height="7" /><rect x={x + 1} y={y + 1} width="5" height="5" className="fill-card" /><rect x={x + 2} y={y + 2} width="3" height="3" /></g>
)
return (
<svg viewBox={`-1 -1 ${n + 2} ${n + 2}`} role="img" aria-label="QR code that opens the download page on your phone" className={cn("bg-card fill-[var(--pocket-ink)] dark:fill-foreground rounded-2xl p-1", className)}>
{cells}{eye(0, 0)}{eye(n - 7, 0)}{eye(0, n - 7)}
</svg>
)
}
type PocketDownloadProps = React.ComponentProps<"div"> & { hrefs?: Partial<PocketHrefs> }
/** The download page: store buttons, a QR code, and a “text me the link” form that validates and confirms. */
function PocketDownload({ hrefs, ...props }: PocketDownloadProps) {
const link = { download: "/pocket/download", ...hrefs }
const [value, setValue] = React.useState("")
const [device, setDevice] = React.useState<"iphone" | "android">("iphone")
const [error, setError] = React.useState("")
const [sent, setSent] = React.useState<string | null>(null)
function submit(e: React.FormEvent) {
e.preventDefault()
const v = value.trim()
const ok = /^\S+@\S+\.\S+$/.test(v) || v.replace(/\D/g, "").length >= 10
if (!ok) return setError("Enter a mobile number or an email address.")
setError("")
setSent(v)
}
return (
<PocketShell page="download" hrefs={hrefs} {...props}>
<main className="mx-auto max-w-6xl px-4 pt-14 pb-8 sm:px-6 sm:pt-20">
<div className="grid items-center gap-12 lg:grid-cols-[1.2fr_1fr]">
<div>
<h1 className={cn("text-[clamp(3rem,9vw,7rem)] leading-[0.9] text-balance", pocketDisplayClass)}>Get Pocket in your pocket.</h1>
<p className="text-muted-foreground mt-5 max-w-lg text-xl font-medium text-pretty">Free on iPhone and Android. Open an account in about four minutes, with a photo ID and your phone.</p>
<StoreButtons href={link.download} className="mt-8" />
<section aria-labelledby="pd-text" className="bg-card mt-10 rounded-[2rem] border p-6 sm:p-8">
<h2 id="pd-text" className={cn("flex items-center gap-2 text-2xl", pocketDisplayClass)}><Smartphone className="size-6" aria-hidden="true" />On your computer? Text yourself the link.</h2>
{sent ? (
<p role="status" className="bg-chart-1 mt-5 flex items-center gap-2 rounded-2xl p-4 font-extrabold text-[var(--pocket-on-lime)]"><Check className="size-5 shrink-0" aria-hidden="true" />Sent to {sent}. Open it on your {device === "iphone" ? "iPhone" : "Android"}.</p>
) : (
<form onSubmit={submit} noValidate className="mt-5 grid gap-4">
<fieldset><legend className="mb-2 text-sm font-extrabold">Your phone</legend>
<div className="flex gap-2">{([["iphone", "iPhone"], ["android", "Android"]] as const).map(([v, l]) => <label key={v} className={cn("focus-within:ring-ring/50 cursor-pointer rounded-full border-2 px-5 py-2.5 text-sm font-extrabold transition-colors focus-within:ring-[3px] motion-reduce:transition-none", device === v ? "border-primary bg-primary text-primary-foreground" : "hover:border-primary/40")}><input type="radio" name="device" value={v} checked={device === v} onChange={() => setDevice(v)} className="sr-only" />{l}</label>)}</div>
</fieldset>
<div className="grid gap-2"><label htmlFor="pd-contact" className="text-sm font-extrabold">Mobile number or email</label>
<div className="flex flex-col gap-2 sm:flex-row"><input id="pd-contact" value={value} onChange={(e) => setValue(e.target.value)} aria-invalid={!!error} aria-describedby={error ? "pd-err" : undefined} placeholder="(555) 010-2030" className="bg-background focus-visible:ring-ring/50 aria-invalid:border-destructive h-14 min-w-0 flex-1 rounded-full border-2 px-6 text-lg font-bold outline-none focus-visible:ring-[3px]" /><button type="submit" className="bg-primary text-primary-foreground focus-visible:ring-ring/50 h-14 rounded-full px-8 text-lg font-extrabold outline-none focus-visible:ring-[3px]">Text me the link</button></div>
{error && <p id="pd-err" role="alert" className="text-destructive text-sm font-bold">{error}</p>}
</div>
<p className="text-muted-foreground text-xs font-medium">We send one message and do not keep your number.</p>
</form>
)}
</section>
</div>
<div className="relative mx-auto grid w-full max-w-sm justify-items-center gap-6">
<PocketApp defaultTab="save" className="w-[min(100%,290px)]" />
<div className="bg-chart-1 relative z-10 -mt-20 flex items-center gap-4 self-end rounded-3xl p-4 text-[var(--pocket-on-lime)] shadow-xl sm:-me-8"><QrArt className="size-24" /><p className="max-w-[9rem] text-sm font-extrabold">Point your camera here to download</p></div>
</div>
</div>
<section aria-labelledby="pd-need" className="mt-24 grid gap-4 md:grid-cols-3">
<h2 id="pd-need" className="sr-only">What you need</h2>
{[["A phone", "iOS 16 or later, or Android 10 or later. Face or fingerprint unlock turned on."], ["A photo ID", "A passport or driver’s licence. We check it in the app, usually in under a minute."], ["Four minutes", "That is the median. You can fund the account straight away from a bank account or card."]].map(([t, b]) => <div key={t} className="bg-secondary rounded-3xl p-7"><h3 className={cn("text-2xl", pocketDisplayClass)}>{t}</h3><p className="text-muted-foreground mt-2 font-medium text-pretty">{b}</p></div>)}
</section>
</main>
</PocketShell>
)
}
export { PocketDownload, QrArt, type PocketDownloadProps }// Ballmac UI: Pocket template route. https://ui.ballmac.com/templates/template-pocket
import type { Metadata } from "next"
import { PocketHome } from "@/components/ballmac/templates/pocket/pocket-home"
export const metadata: Metadata = {
title: "Pocket: money that keeps up",
description: "One app for spending, saving and splitting. No fees to open or to leave.",
}
export default function Page() {
return <PocketHome />
}// Ballmac UI: Pocket template route. https://ui.ballmac.com/templates/template-pocket
import type { Metadata } from "next"
import { PocketFeatures } from "@/components/ballmac/templates/pocket/pocket-features"
export const metadata: Metadata = {
title: "Features · Pocket",
description: "Freeze a card, tune round-ups, split a bill and convert money, live.",
}
export default function Page() {
return <PocketFeatures />
}// Ballmac UI: Pocket template route. https://ui.ballmac.com/templates/template-pocket
import type { Metadata } from "next"
import { PocketPricing } from "@/components/ballmac/templates/pocket/pocket-pricing"
export const metadata: Metadata = {
title: "Pricing · Pocket",
description: "Free forever, with Plus and Metal for extras.",
}
export default function Page() {
return <PocketPricing />
}// Ballmac UI: Pocket template route. https://ui.ballmac.com/templates/template-pocket
import type { Metadata } from "next"
import { PocketSecurity } from "@/components/ballmac/templates/pocket/pocket-security"
export const metadata: Metadata = {
title: "Security · Pocket",
description: "How Pocket keeps your money safe, and what happens if you lose your phone.",
}
export default function Page() {
return <PocketSecurity />
}// Ballmac UI: Pocket template route. https://ui.ballmac.com/templates/template-pocket
import type { Metadata } from "next"
import { PocketDownload } from "@/components/ballmac/templates/pocket/pocket-download"
export const metadata: Metadata = {
title: "Download · Pocket",
description: "Free on iPhone and Android.",
}
export default function Page() {
return <PocketDownload />
}Installation
$ pnpm dlx shadcn@latest add @ballmac/template-pocketInstall the dependencies.
$ pnpm add lucide-reactAdd the Ballmac items it builds on.
$ pnpm dlx shadcn@latest add @ballmac/phone-frame @ballmac/switch @ballmac/sliderCopy the source into your project.
components/ballmac/templates/pocket/pocket-fonts.ts// Ballmac UI: Pocket template fonts. https://ui.ballmac.com/templates/template-pocket import { Gabarito } from "next/font/google" export const pocketSans = Gabarito({ variable: "--pocket-sans", subsets: ["latin"], display: "swap", preload: false })components/ballmac/templates/pocket/pocket-data.ts// Ballmac UI: Pocket template data. https://ui.ballmac.com/templates/template-pocket export const transactions = [ { name: "Corner Café", detail: "Today, 8:12", amount: -4.8, tone: 1, emoji: "☕" }, { name: "From Sam", detail: "Yesterday", amount: 120, tone: 0, emoji: "↓" }, { name: "City Transit", detail: "Yesterday", amount: -2.75, tone: 3, emoji: "🚆" }, { name: "Market Hall", detail: "Monday", amount: -38.12, tone: 2, emoji: "🥬" }, { name: "Pilates Studio", detail: "Sunday", amount: -22, tone: 4, emoji: "🧘" }, ] export type Plan = { id: string; name: string; monthly: number; annual: number; blurb: string; features: string[]; featured?: boolean } export const plans: Plan[] = [ { id: "free", name: "Free", monthly: 0, annual: 0, blurb: "Everything you need to stop using a spreadsheet.", features: ["Free virtual card", "Instant transfers to friends", "Round-ups to one pot", "Spending insights"] }, { id: "plus", name: "Plus", monthly: 6, annual: 5, blurb: "For people who travel, save and split things.", features: ["Everything in Free", "Fee-free spending in 40 currencies", "Unlimited savings pots", "Bill splitting with reminders", "Priority chat support"], featured: true }, { id: "metal", name: "Metal", monthly: 16, annual: 13, blurb: "A heavy card and a lot of perks.", features: ["Everything in Plus", "Metal card, engraved", "Airport lounge passes", "Travel and phone insurance", "Cashback on every purchase"] }, ] export const compare = [ ["Virtual card", true, true, true], ["Physical card", "$5", true, true], ["Savings pots", "1", "Unlimited", "Unlimited"], ["Foreign spending fee", "1.5%", "None", "None"], ["ATM withdrawals a month", "$200", "$800", "Unlimited"], ["Bill splitting", false, true, true], ["Cashback", false, false, "1%"], ["Support", "Chat", "Priority chat", "24/7 phone"], ] as const export const faqs = [ { q: "Is Pocket a bank?", a: "Pocket is a money app. Accounts are held at Harbor Bank, N.A., Member FDIC, and your deposits are insured up to $250,000." }, { q: "How do round-ups work?", a: "Every card purchase is rounded up to the next dollar and the difference moves to your pot. Spend $4.20 and $0.80 is saved." }, { q: "Are there hidden fees?", a: "No. The Free plan has no monthly fee, no overdraft fee and no minimum balance. Paid plans show their price on this page and nowhere else." }, { q: "What if I lose my phone?", a: "Sign in on another device, freeze the card in one tap and revoke the old phone. Nothing is stored on the device." }, { q: "Which countries does it work in?", a: "The card works anywhere Visa does. Sending money to friends is available in 31 countries today." }, { q: "Can I close my account?", a: "Yes, any time, in the app. Your balance goes to the bank account you choose within one working day." }, ] export const currencies = [["USD", 1, "$"], ["EUR", 0.92, "€"], ["GBP", 0.79, "£"], ["JPY", 156.4, "¥"], ["MXN", 17.1, "MX$"]] as const export const money = (n: number, digits = 2) => `${n < 0 ? "−" : ""}$${Math.abs(n).toLocaleString("en-US", { minimumFractionDigits: digits, maximumFractionDigits: digits })}`components/ballmac/templates/pocket/pocket-theme.tsx// Ballmac UI: Pocket template shell. https://ui.ballmac.com/templates/template-pocket "use client" import * as React from "react" import { Apple, Bell, CreditCard, Home as HomeIcon, Menu, PiggyBank, Play, QrCode, Send, X } from "lucide-react" import { PhoneFrame } from "@/components/ballmac/phone-frame" import { Switch } from "@/components/ballmac/switch" import { money, transactions } from "@/components/ballmac/templates/pocket/pocket-data" import { pocketSans } from "@/components/ballmac/templates/pocket/pocket-fonts" import { cn } from "@/lib/utils" type PocketPage = "home" | "features" | "pricing" | "security" | "download" type PocketHrefs = Record<PocketPage, string> const defaultHrefs: PocketHrefs = { home: "/pocket", features: "/pocket/features", pricing: "/pocket/pricing", security: "/pocket/security", download: "/pocket/download" } /** Pocket's palette: mint paper, navy ink, cobalt and one loud lime. Dark mode is midnight with the lime turned up. */ const pocketCss = ` .pocket-theme,body:not(:has([data-gallery])):has(.pocket-theme){--background:oklch(0.985 0.012 165);--foreground:oklch(0.2 0.06 265);--card:oklch(1 0 0);--card-foreground:oklch(0.2 0.06 265);--popover:oklch(1 0 0);--popover-foreground:oklch(0.2 0.06 265);--primary:oklch(0.47 0.22 267);--primary-foreground:oklch(0.99 0.005 165);--secondary:oklch(0.95 0.025 165);--secondary-foreground:oklch(0.2 0.06 265);--muted:oklch(0.95 0.02 165);--muted-foreground:oklch(0.45 0.05 265);--accent:oklch(0.93 0.04 150);--accent-foreground:oklch(0.2 0.06 265);--border:oklch(0.2 0.06 265 / 12%);--input:oklch(0.2 0.06 265 / 20%);--ring:oklch(0.55 0.22 267);--surface:oklch(0.965 0.02 165);--destructive:oklch(0.52 0.21 27);--chart-1:oklch(0.9 0.2 125);--chart-2:oklch(0.72 0.17 30);--chart-3:oklch(0.62 0.2 300);--chart-4:oklch(0.78 0.13 200);--chart-5:oklch(0.85 0.14 85);--pocket-ink:oklch(0.2 0.06 265);--pocket-on-lime:oklch(0.2 0.06 265);--pocket-navy:oklch(0.2 0.07 265);--pocket-on-navy:oklch(0.98 0.01 165);--radius:1.5rem} .dark .pocket-theme,.dark body:not(:has([data-gallery])):has(.pocket-theme){--background:oklch(0.16 0.045 265);--foreground:oklch(0.96 0.015 165);--card:oklch(0.21 0.05 265);--card-foreground:oklch(0.96 0.015 165);--popover:oklch(0.23 0.05 265);--popover-foreground:oklch(0.96 0.015 165);--primary:oklch(0.9 0.2 125);--primary-foreground:oklch(0.2 0.06 265);--secondary:oklch(0.26 0.05 265);--secondary-foreground:oklch(0.96 0.015 165);--muted:oklch(0.25 0.05 265);--muted-foreground:oklch(0.76 0.04 250);--accent:oklch(0.3 0.06 265);--accent-foreground:oklch(0.96 0.015 165);--border:oklch(1 0 0 / 11%);--input:oklch(1 0 0 / 16%);--ring:oklch(0.9 0.2 125);--surface:oklch(0.19 0.047 265);--destructive:oklch(0.7 0.19 27);--chart-1:oklch(0.9 0.2 125);--chart-2:oklch(0.76 0.15 30);--chart-3:oklch(0.74 0.17 300);--chart-4:oklch(0.8 0.12 200);--chart-5:oklch(0.86 0.13 85);--pocket-navy:oklch(0.12 0.045 265)} body:not(:has([data-gallery])):has(.pocket-theme){font-family:var(--pocket-sans),ui-sans-serif,system-ui,sans-serif} @keyframes pocket-bob{0%,100%{transform:translateY(0) rotate(var(--r,0deg))}50%{transform:translateY(-8px) rotate(var(--r,0deg))}} .pocket-bob{animation:pocket-bob 5s ease-in-out infinite} @media (prefers-reduced-motion:reduce){.pocket-bob{animation:none}} ` const display = "font-black tracking-[-0.035em]" const onLime = "text-[var(--pocket-on-lime)]" const txTone = ["bg-chart-1", "bg-chart-5", "bg-chart-4", "bg-chart-2", "bg-chart-3"] /** The Pocket app inside a phone: balance, quick actions and three tabs (Home, Card, Save). Tap around. */ function PocketApp({ className, defaultTab = "home" }: { className?: string; defaultTab?: "home" | "card" | "save" }) { const [tab, setTab] = React.useState<"home" | "card" | "save">(defaultTab) const [frozen, setFrozen] = React.useState(false) const id = React.useId() const tabs = [["home", "Home", HomeIcon], ["card", "Card", CreditCard], ["save", "Save", PiggyBank]] as const return ( <PhoneFrame screenWidth={390} className={cn("w-[min(100%,290px)]", className)} screenClassName="pocket-theme bg-background text-foreground"> <div className="flex h-full flex-col text-[15px]" style={{ fontFamily: "var(--pocket-sans), ui-sans-serif, system-ui, sans-serif" }}> <div className="flex items-center justify-between px-5 pt-2"> <div className="flex items-center gap-3"><span aria-hidden="true" className="bg-chart-3 flex size-10 items-center justify-center rounded-full font-black text-white">M</span><div><p className="text-muted-foreground text-xs">Good morning</p><p className="font-extrabold">Maya</p></div></div> <span aria-hidden="true" className="bg-secondary flex size-10 items-center justify-center rounded-full"><Bell className="size-5" /></span> </div> <div role="tabpanel" id={`${id}-panel`} aria-labelledby={`${id}-${tab}`} className="min-h-0 flex-1 overflow-hidden px-5 pt-4"> {tab === "home" && ( <> <div className="bg-primary text-primary-foreground rounded-3xl p-5"> <p className="text-sm font-semibold">Total balance</p> <p className={cn("mt-1 text-[40px] leading-none tabular-nums", display)}>$12,480<span className="text-2xl opacity-100">.62</span></p> <p className="bg-card text-card-foreground mt-3 inline-block rounded-full px-3 py-1 text-xs font-extrabold">+$212.40 this week</p> </div> <div className="mt-4 grid grid-cols-4 gap-2 text-center text-xs font-bold">{[["Send", Send], ["Request", Apple], ["Top up", QrCode], ["More", Play]].map(([l, Icon]) => { const I = Icon as typeof Send; return <div key={l as string}><span aria-hidden="true" className="bg-secondary mx-auto flex size-12 items-center justify-center rounded-2xl"><I className="size-5" /></span><span className="mt-1 block">{l as string}</span></div> })}</div> <p className="mt-4 text-sm font-extrabold">Recent</p> <ul className="mt-2 grid gap-2.5">{transactions.slice(0, 4).map((t) => <li key={t.name} className="flex items-center gap-3"><span aria-hidden="true" className={cn("flex size-10 items-center justify-center rounded-full text-base text-[var(--pocket-on-lime)]", txTone[t.tone])}>{t.emoji}</span><span className="min-w-0 flex-1"><span className="block truncate font-bold">{t.name}</span><span className="text-muted-foreground block text-xs">{t.detail}</span></span><span className={cn("font-extrabold tabular-nums", t.amount > 0 && "text-primary")}>{t.amount > 0 ? "+" : ""}{money(t.amount)}</span></li>)}</ul> </> )} {tab === "card" && ( <> <div className={cn("relative aspect-[1.6] overflow-hidden rounded-3xl p-5 text-[var(--pocket-on-navy)] transition-opacity motion-reduce:transition-none", "bg-[var(--pocket-navy)]", frozen && "opacity-50")}> <div aria-hidden="true" className="bg-chart-1 absolute -end-10 -bottom-10 aspect-square w-40 rounded-full" /><div aria-hidden="true" className="bg-chart-3 absolute -end-2 -bottom-16 aspect-square w-32 rounded-full" /> <p className={cn("relative text-xl", display)}>pocket</p><p className="relative mt-8 text-lg font-bold tracking-widest tabular-nums">•••• 4821</p><p className="relative mt-1 text-xs">MAYA OKAFOR · 09/29</p> {frozen && <span className="bg-chart-4 absolute top-4 end-4 rounded-full px-3 py-1 text-xs font-extrabold text-[var(--pocket-on-lime)]">Frozen</span>} </div> <div className="bg-secondary mt-4 flex items-center justify-between rounded-2xl p-4"><span><span className="block font-extrabold">Freeze card</span><span className="text-muted-foreground text-xs">Stops every payment instantly</span></span><Switch checked={frozen} onCheckedChange={setFrozen} aria-label="Freeze card" /></div> <dl className="mt-3 grid gap-2 text-sm">{[["Daily limit", "$2,000"], ["Online payments", "On"], ["Contactless", "On"]].map(([k, v]) => <div key={k} className="bg-card flex justify-between rounded-xl border px-4 py-3"><dt className="text-muted-foreground">{k}</dt><dd className="font-extrabold">{v}</dd></div>)}</dl> </> )} {tab === "save" && ( <> <div className="bg-chart-1 rounded-3xl p-5 text-[var(--pocket-on-lime)]"><p className="text-sm font-bold">Holiday fund</p><p className={cn("mt-1 text-4xl tabular-nums", display)}>$1,240</p><div className="mt-3 h-3 overflow-hidden rounded-full bg-black/15" role="progressbar" aria-label="Holiday fund progress" aria-valuemin={0} aria-valuemax={100} aria-valuenow={62}><div className="h-full w-[62%] rounded-full bg-[var(--pocket-ink)]" /></div><p className="mt-2 text-xs font-bold">62% of $2,000 · Lisbon, June</p></div> <p className="mt-4 text-sm font-extrabold">Round-ups this month</p> <p className={cn("text-3xl tabular-nums", display)}>$37.20</p> <ul className="mt-3 grid gap-2">{[["Corner Café", "+$0.20"], ["City Transit", "+$0.25"], ["Market Hall", "+$0.88"]].map(([n, a]) => <li key={n} className="bg-card flex justify-between rounded-xl border px-4 py-3"><span className="font-bold">{n}</span><span className="text-primary font-extrabold tabular-nums">{a}</span></li>)}</ul> </> )} </div> <div role="tablist" aria-label="App sections" className="bg-card mx-4 mb-7 mt-2 grid grid-cols-3 rounded-full border p-1"> {tabs.map(([k, label, Icon]) => <button key={k} type="button" role="tab" id={`${id}-${k}`} aria-selected={tab === k} aria-controls={`${id}-panel`} onClick={() => setTab(k)} className="aria-selected:bg-primary aria-selected:text-primary-foreground focus-visible:ring-ring/50 flex h-10 items-center justify-center gap-1.5 rounded-full text-sm font-extrabold outline-none focus-visible:ring-2"><Icon className="size-4" aria-hidden="true" />{label}</button>)} </div> </div> </PhoneFrame> ) } /** App Store and Google Play buttons that go to the download page. */ function StoreButtons({ href, className }: { href: string; className?: string }) { const b = "bg-[var(--pocket-navy)] text-[var(--pocket-on-navy)] focus-visible:ring-ring inline-flex h-14 items-center gap-3 rounded-2xl px-5 outline-none transition-transform hover:-translate-y-0.5 focus-visible:ring-[3px] motion-reduce:transition-none motion-reduce:hover:translate-y-0" return ( <div className={cn("flex flex-wrap gap-3", className)}> <a href={href} className={b}><Apple className="size-7" aria-hidden="true" /><span className="text-start leading-tight"><span className="block text-[11px] font-medium">Download on the</span><span className="block text-lg font-extrabold">App Store</span></span></a> <a href={href} className={b}><Play className="size-7" aria-hidden="true" /><span className="text-start leading-tight"><span className="block text-[11px] font-medium">Get it on</span><span className="block text-lg font-extrabold">Google Play</span></span></a> </div> ) } const links: { key: PocketPage; label: string }[] = [{ key: "features", label: "Features" }, { key: "pricing", label: "Pricing" }, { key: "security", label: "Security" }] type PocketShellProps = React.ComponentProps<"div"> & { /** The page being shown, so its nav link is marked current. */ page: PocketPage /** Override where pages live (used by previews). */ hrefs?: Partial<PocketHrefs> } /** Pocket's frame: a floating pill header, and a navy footer with a giant wordmark. */ function PocketShell({ page, hrefs: overrides, className, style, children, ...props }: PocketShellProps) { const hrefs = { ...defaultHrefs, ...overrides } const [open, setOpen] = React.useState(false) React.useEffect(() => { const classes = [pocketSans.variable].filter(Boolean) document.body.classList.add(...classes) return () => document.body.classList.remove(...classes) }, []) return ( <div data-slot="pocket" className={cn("pocket-theme bg-background text-foreground relative min-h-dvh overflow-x-clip", pocketSans.variable, className)} style={{ fontFamily: "var(--pocket-sans), ui-sans-serif, system-ui, sans-serif", ...style }} {...props} > <style>{pocketCss}</style> <header className="sticky top-3 z-40 px-3 sm:px-6"> <div className="bg-card/90 mx-auto flex h-16 max-w-6xl items-center justify-between rounded-full border pe-2 ps-5 shadow-sm backdrop-blur-xl"> <a href={hrefs.home} className={cn("focus-visible:ring-ring/50 flex items-center gap-2 rounded-full text-2xl outline-none focus-visible:ring-[3px]", display)}><span aria-hidden="true" className="bg-primary text-primary-foreground flex size-8 items-center justify-center rounded-xl text-lg">p</span>pocket</a> <nav aria-label="Main" className="hidden items-center gap-1 md:flex">{links.map((l) => <a key={l.key} href={hrefs[l.key]} aria-current={page === l.key ? "page" : undefined} className="hover:bg-accent aria-[current=page]:bg-accent focus-visible:ring-ring/50 rounded-full px-4 py-2 text-sm font-extrabold outline-none transition-colors focus-visible:ring-[3px] motion-reduce:transition-none">{l.label}</a>)}</nav> <div className="flex items-center gap-1"> <a href={hrefs.download} className="bg-primary text-primary-foreground focus-visible:ring-ring/50 inline-flex h-11 items-center rounded-full px-5 text-sm font-extrabold outline-none transition-transform hover:scale-[1.03] focus-visible:ring-[3px] motion-reduce:transition-none motion-reduce:hover:scale-100">Get the app</a> <button type="button" aria-label={open ? "Close menu" : "Open menu"} aria-expanded={open} aria-controls="pocket-mobile-menu" onClick={() => setOpen((v) => !v)} className="hover:bg-accent focus-visible:ring-ring/50 inline-flex size-11 items-center justify-center rounded-full outline-none focus-visible:ring-[3px] md:hidden">{open ? <X className="size-5" aria-hidden="true" /> : <Menu className="size-5" aria-hidden="true" />}</button> </div> </div> {open && <nav id="pocket-mobile-menu" aria-label="Mobile" className="bg-card mx-auto mt-2 max-w-6xl rounded-3xl border p-2 md:hidden">{links.map((l) => <a key={l.key} href={hrefs[l.key]} className="hover:bg-accent block rounded-2xl px-4 py-3 text-lg font-extrabold">{l.label}</a>)}</nav>} </header> {children} <footer className="mt-24 overflow-hidden bg-[var(--pocket-navy)] text-[var(--pocket-on-navy)]"> <div className="mx-auto grid max-w-6xl gap-10 px-4 pt-16 pb-6 sm:px-6 md:grid-cols-[1.4fr_1fr_1fr_1fr]"> <div><p className="max-w-xs text-xl font-bold text-pretty">Money that keeps up with you.</p><StoreButtons href={hrefs.download} className="mt-6 [&>a]:bg-white/10" /></div> {[["Product", [["Features", hrefs.features], ["Pricing", hrefs.pricing], ["Download", hrefs.download]]], ["Trust", [["Security", hrefs.security], ["Status", hrefs.security], ["Bug bounty", hrefs.security]]], ["Company", [["About", hrefs.home], ["Careers", hrefs.home], ["Press", hrefs.home]]]].map(([t, items]) => <div key={t as string}><h2 className="text-xs font-extrabold tracking-[0.14em] uppercase">{t as string}</h2><ul className="mt-4 grid gap-2.5">{(items as string[][]).map(([l, h]) => <li key={l}><a href={h} className="hover:underline">{l}</a></li>)}</ul></div>)} </div> <p aria-hidden="true" className={cn("text-chart-1 -mb-[0.18em] select-none text-center text-[clamp(6rem,26vw,22rem)] leading-none", display)}>pocket</p> <p className="mx-auto max-w-6xl px-4 pb-6 text-xs sm:px-6">Pocket is a financial technology company, not a bank. Banking services by Harbor Bank, N.A., Member FDIC. © 2026 Pocket Labs.</p> </footer> </div> ) } export { PocketApp, PocketShell, StoreButtons, defaultHrefs as pocketDefaultHrefs, display as pocketDisplayClass, onLime as pocketOnLime, type PocketHrefs, type PocketPage, type PocketShellProps }components/ballmac/templates/pocket/pocket-home.tsx// Ballmac UI: Pocket home page. https://ui.ballmac.com/templates/template-pocket "use client" import * as React from "react" import { ArrowRight, Globe2, PiggyBank, ShieldCheck, Snowflake, Users } from "lucide-react" import { PocketApp, PocketShell, StoreButtons, pocketDisplayClass, type PocketHrefs } from "@/components/ballmac/templates/pocket/pocket-theme" import { cn } from "@/lib/utils" const cards = [ { icon: PiggyBank, title: "Round-ups that add up", body: "Every purchase rounds up to the next dollar and the change lands in a pot. Most people save $40 a month without noticing.", tone: "bg-chart-1" }, { icon: Snowflake, title: "Freeze it in one tap", body: "Lost your card? Tap once and every payment stops. Tap again and you are back.", tone: "bg-chart-4" }, { icon: Globe2, title: "Spend anywhere", body: "Pay in 40 currencies at the real exchange rate with no fee. The rate you see is the rate you get.", tone: "bg-chart-5" }, { icon: Users, title: "Split without the awkward", body: "Send a request for your share of dinner, with a gentle reminder so you do not have to be the one who asks.", tone: "bg-chart-2" }, ] const reviews = [ ["It’s the first money app I open on purpose.", "Priya N.", "App Store"], ["Round-ups paid for our entire holiday. I did nothing.", "Tom & Lucia", "Google Play"], ["Froze my card from a taxi in Lisbon. Unfroze it at the hotel.", "Dev R.", "App Store"], ] type PocketHomeProps = React.ComponentProps<"div"> & { hrefs?: Partial<PocketHrefs> } /** The Pocket home page: a hero with a working app in a phone, four feature blocks, proof and the download buttons. */ function PocketHome({ hrefs, ...props }: PocketHomeProps) { const link = { features: "/pocket/features", pricing: "/pocket/pricing", download: "/pocket/download", security: "/pocket/security", ...hrefs } return ( <PocketShell page="home" hrefs={hrefs} {...props}> <main> <section aria-labelledby="ph-title" className="relative isolate mx-auto grid max-w-6xl items-center gap-12 px-4 pt-14 pb-20 sm:px-6 lg:grid-cols-[1.15fr_1fr] lg:pt-24"> <div aria-hidden="true" className="bg-chart-1/50 absolute top-10 end-0 -z-10 aspect-square w-[min(80vw,34rem)] rounded-full blur-3xl" /> <div> <p className="bg-card inline-flex items-center gap-2 rounded-full border px-4 py-1.5 text-sm font-extrabold"><span className="bg-chart-1 size-2.5 rounded-full" aria-hidden="true" />2.4 million pockets and counting</p> <h1 id="ph-title" className={cn("mt-6 text-[clamp(3.4rem,10vw,8rem)] leading-[0.88] text-balance", pocketDisplayClass)}>Money that <span className="bg-chart-1 inline-block -rotate-2 rounded-2xl px-3 text-[var(--pocket-on-lime)]">keeps up.</span></h1> <p className="text-muted-foreground mt-6 max-w-lg text-xl font-medium text-pretty">One app for spending, saving and splitting. No fees to open, no fees to leave, and a card you can freeze from the back of a taxi.</p> <StoreButtons href={link.download} className="mt-8" /> <p className="text-muted-foreground mt-4 flex items-center gap-2 text-sm font-semibold"><ShieldCheck className="size-4" aria-hidden="true" />FDIC insured up to $250,000 · Rated 4.8 by 61,000 people</p> </div> <div className="relative mx-auto w-full max-w-sm"> <PocketApp className="mx-auto w-[min(100%,300px)]" /> <p aria-hidden="true" className="bg-chart-1 pocket-bob absolute top-16 -start-2 rounded-2xl px-4 py-2 text-sm font-extrabold text-[var(--pocket-on-lime)] shadow-lg [--r:-6deg] sm:-start-10" style={{ transform: "rotate(-6deg)" }}>+$0.20 saved</p> <p aria-hidden="true" className="bg-card pocket-bob absolute end-0 bottom-28 flex items-center gap-2 rounded-2xl border px-4 py-2 text-sm font-extrabold shadow-lg [--r:5deg] sm:-end-8" style={{ transform: "rotate(5deg)", animationDelay: "1.2s" }}><Snowflake className="text-primary size-4" />Card frozen</p> </div> </section> <section aria-label="Featured in" className="border-y"> <div className="mx-auto flex max-w-6xl flex-wrap items-center justify-center gap-x-12 gap-y-3 px-4 py-6 sm:px-6"><span className="text-muted-foreground text-xs font-extrabold tracking-[0.14em] uppercase">As seen in</span>{["The Daily Ledger", "Fast Money", "Wired Weekly", "Product Hunt"].map((n) => <span key={n} className={cn("text-muted-foreground text-xl", pocketDisplayClass)}>{n}</span>)}</div> </section> <section aria-labelledby="ph-feat" className="mx-auto max-w-6xl px-4 py-20 sm:px-6"> <h2 id="ph-feat" className={cn("max-w-2xl text-[clamp(2.4rem,6vw,4.5rem)] leading-[0.95] text-balance", pocketDisplayClass)}>Everything your money app should have done years ago.</h2> <ul className="mt-12 grid gap-4 md:grid-cols-2">{cards.map((c) => <li key={c.title} className={cn("rounded-[2rem] p-8 text-[var(--pocket-on-lime)] sm:p-10", c.tone)}><c.icon className="size-9" aria-hidden="true" /><h3 className={cn("mt-10 text-3xl", pocketDisplayClass)}>{c.title}</h3><p className="mt-3 max-w-md text-lg font-medium text-pretty">{c.body}</p></li>)}</ul> <a href={link.features} className="bg-primary text-primary-foreground focus-visible:ring-ring/50 mt-8 inline-flex h-14 items-center gap-2 rounded-full px-8 text-lg font-extrabold outline-none focus-visible:ring-[3px]">See every feature <ArrowRight className="size-5 rtl:rotate-180" aria-hidden="true" /></a> </section> <section aria-label="Pocket in numbers" className="bg-[var(--pocket-navy)] text-[var(--pocket-on-navy)]"> <ul className="mx-auto grid max-w-6xl gap-8 px-4 py-16 sm:grid-cols-3 sm:px-6">{[["$0", "to open, hold or close an account"], ["40", "currencies at the real rate"], ["11 min", "average wait for a human reply"]].map(([n, l]) => <li key={l}><span className={cn("text-chart-1 block text-[clamp(3.5rem,8vw,6rem)] leading-none", pocketDisplayClass)}>{n}</span><span className="mt-2 block text-lg font-medium">{l}</span></li>)}</ul> </section> <section aria-labelledby="ph-rev" className="mx-auto max-w-6xl px-4 py-20 sm:px-6"> <h2 id="ph-rev" className={cn("text-[clamp(2.4rem,6vw,4rem)] leading-none", pocketDisplayClass)}>People say nice things</h2> <ul className="mt-10 grid gap-4 md:grid-cols-3">{reviews.map(([q, n, s], i) => <li key={n} className={cn("bg-card rounded-[2rem] border p-8", i === 1 && "md:translate-y-6")}><p className="text-2xl leading-snug font-extrabold text-balance">“{q}”</p><p className="text-muted-foreground mt-6 text-sm font-bold">{n} · {s}</p></li>)}</ul> </section> <section aria-labelledby="ph-cta" className="mx-auto max-w-6xl px-4 pb-8 sm:px-6"> <div className="bg-primary text-primary-foreground rounded-[2.5rem] px-6 py-16 text-center sm:px-14"> <h2 id="ph-cta" className={cn("mx-auto max-w-2xl text-[clamp(2.4rem,6vw,4.5rem)] leading-[0.95] text-balance", pocketDisplayClass)}>Open an account in about four minutes.</h2> <p className="mx-auto mt-4 max-w-md text-lg font-medium text-pretty">All you need is your phone and a photo ID. We will do the rest.</p> <div className="mt-8 flex justify-center"><a href={link.download} className="bg-[var(--pocket-navy)] text-[var(--pocket-on-navy)] focus-visible:ring-ring inline-flex h-14 items-center gap-2 rounded-full px-8 text-lg font-extrabold outline-none focus-visible:ring-[3px]">Get Pocket <ArrowRight className="size-5 rtl:rotate-180" aria-hidden="true" /></a></div> </div> </section> </main> </PocketShell> ) } export { PocketHome, type PocketHomeProps }components/ballmac/templates/pocket/pocket-features.tsx// Ballmac UI: Pocket features page. https://ui.ballmac.com/templates/template-pocket "use client" import * as React from "react" import { Send, Snowflake } from "lucide-react" import { Slider } from "@/components/ballmac/slider" import { Switch } from "@/components/ballmac/switch" import { currencies, money } from "@/components/ballmac/templates/pocket/pocket-data" import { PocketShell, pocketDisplayClass, type PocketHrefs } from "@/components/ballmac/templates/pocket/pocket-theme" import { cn } from "@/lib/utils" const bars = [["Food", 340, "bg-chart-1"], ["Travel", 215, "bg-chart-4"], ["Fun", 180, "bg-chart-5"], ["Home", 120, "bg-chart-2"]] as const const people = [["Sam", "bg-chart-1", true], ["Ana", "bg-chart-4", true], ["Joe", "bg-chart-5", false], ["Mei", "bg-chart-2", false]] as const type PocketFeaturesProps = React.ComponentProps<"div"> & { hrefs?: Partial<PocketHrefs> } /** The features page: five live demos instead of five screenshots. Freeze a card, tune round-ups, split a bill, convert money. */ function PocketFeatures({ hrefs, ...props }: PocketFeaturesProps) { const [frozen, setFrozen] = React.useState(false) const [spend, setSpend] = React.useState(900) const [paid, setPaid] = React.useState<string[]>(["Sam", "Ana"]) const [amount, setAmount] = React.useState("100") const [cur, setCur] = React.useState<(typeof currencies)[number][0]>("EUR") const savedPerMonth = Math.round(spend * 0.045) const rate = currencies.find((c) => c[0] === cur)! const converted = (Number(amount) || 0) * rate[1] const bill = 128 const share = bill / people.length const total = bars.reduce((n, b) => n + b[1], 0) const card = "rounded-[2rem] border p-8 sm:p-10" return ( <PocketShell page="features" hrefs={hrefs} {...props}> <main className="mx-auto max-w-6xl px-4 pt-14 pb-8 sm:px-6 sm:pt-20"> <h1 className={cn("max-w-3xl text-[clamp(3rem,9vw,7rem)] leading-[0.9] text-balance", pocketDisplayClass)}>Try it right here.</h1> <p className="text-muted-foreground mt-5 max-w-xl text-xl font-medium text-pretty">Everything below works. Freeze the card, drag the slider, split the bill.</p> <div className="mt-14 grid gap-4 lg:grid-cols-6"> <section aria-labelledby="pf-card" className={cn(card, "bg-[var(--pocket-navy)] text-[var(--pocket-on-navy)] lg:col-span-3")}> <h2 id="pf-card" className={cn("text-3xl", pocketDisplayClass)}>Freeze it in one tap</h2> <p className="mt-2 max-w-sm font-medium text-pretty">Lost it? Misplaced it? Stop every payment instantly, then undo it when you find it in a coat.</p> <div className={cn("relative mt-8 aspect-[1.6] max-w-sm overflow-hidden rounded-3xl bg-primary p-6 text-primary-foreground transition-all duration-500 motion-reduce:transition-none", frozen && "scale-[0.97] opacity-60 saturate-0")}> <p className={cn("text-2xl", pocketDisplayClass)}>pocket</p><p className="mt-12 text-xl font-bold tracking-widest tabular-nums">•••• •••• •••• 4821</p> {frozen && <span className="bg-card text-card-foreground absolute top-5 end-5 flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-extrabold"><Snowflake className="size-4" aria-hidden="true" />Frozen</span>} </div> <label className="mt-6 flex max-w-sm cursor-pointer items-center justify-between gap-4 rounded-2xl bg-white/10 p-4"><span className="font-extrabold">Freeze card</span><Switch checked={frozen} onCheckedChange={setFrozen} /></label> <p role="status" className="mt-3 text-sm font-semibold">{frozen ? "Payments are off. Nothing can be charged." : "Your card is live."}</p> </section> <section aria-labelledby="pf-round" className={cn(card, "bg-chart-1 text-[var(--pocket-on-lime)] lg:col-span-3")}> <h2 id="pf-round" className={cn("text-3xl", pocketDisplayClass)}>Round-ups, calculated</h2> <p className="mt-2 max-w-sm font-medium text-pretty">Tell us roughly what you spend on your card each month.</p> <p id="pf-spend" className="mt-8 flex items-baseline justify-between font-extrabold"><span>Monthly card spending</span><span className="text-2xl tabular-nums">{money(spend, 0)}</span></p> <Slider aria-labelledby="pf-spend" thumbLabels={["Monthly spending"]} min={200} max={3000} step={50} value={[spend]} onValueChange={(v) => setSpend(v[0]!)} className="mt-4 [&_[data-slot=slider-track]]:bg-black/20 [&_[data-slot=slider-range]]:bg-[var(--pocket-ink)] [&_[data-slot=slider-thumb]]:border-[var(--pocket-ink)] [&_[data-slot=slider-thumb]]:bg-white" /> <div className="mt-10"><p className="font-extrabold">You would save about</p><p className={cn("text-[clamp(3.5rem,8vw,6rem)] leading-none tabular-nums", pocketDisplayClass)}><output aria-live="polite">{money(savedPerMonth, 0)}</output></p><p className="mt-1 font-bold">a month · {money(savedPerMonth * 12, 0)} a year</p></div> </section> <section aria-labelledby="pf-ins" className={cn(card, "bg-card lg:col-span-2")}> <h2 id="pf-ins" className={cn("text-3xl", pocketDisplayClass)}>Where it went</h2> <p className="text-muted-foreground mt-2 text-lg font-medium">{money(total, 0)} in August</p> <ul className="mt-6 grid gap-4">{bars.map(([n, v, c]) => <li key={n}><div className="flex justify-between text-sm font-extrabold"><span>{n}</span><span className="tabular-nums">{money(v, 0)}</span></div><div className="bg-muted mt-1.5 h-4 overflow-hidden rounded-full" aria-hidden="true"><div className={cn("h-full rounded-full", c)} style={{ width: `${(v / 340) * 100}%` }} /></div></li>)}</ul> </section> <section aria-labelledby="pf-split" className={cn(card, "bg-card lg:col-span-2")}> <h2 id="pf-split" className={cn("text-3xl", pocketDisplayClass)}>Split the bill</h2> <p className="text-muted-foreground mt-2 font-medium">Dinner, {money(bill, 0)} · {money(share, 0)} each</p> <ul className="mt-5 grid gap-2">{people.map(([n, c]) => { const isPaid = paid.includes(n); return <li key={n} className="flex items-center justify-between gap-3"><span className="flex items-center gap-3"><span aria-hidden="true" className={cn("flex size-10 items-center justify-center rounded-full font-black text-[var(--pocket-on-lime)]", c)}>{n[0]}</span><span className="font-bold">{n}</span></span>{isPaid ? <span className="bg-secondary rounded-full px-3 py-1 text-sm font-extrabold">Paid</span> : <button type="button" onClick={() => setPaid((p) => [...p, n])} className="bg-primary text-primary-foreground focus-visible:ring-ring/50 inline-flex h-9 items-center gap-1.5 rounded-full px-4 text-sm font-extrabold outline-none focus-visible:ring-[3px]"><Send className="size-3.5 rtl:-scale-x-100" aria-hidden="true" />Remind {n}</button>}</li> })}</ul> <p role="status" className="text-muted-foreground mt-4 text-sm font-semibold">{paid.length === people.length ? "Everyone has paid." : `${people.length - paid.length} still to pay.`}</p> </section> <section aria-labelledby="pf-fx" className={cn(card, "bg-chart-4 text-[var(--pocket-on-lime)] lg:col-span-2")}> <h2 id="pf-fx" className={cn("text-3xl", pocketDisplayClass)}>Real exchange rate</h2> <p className="mt-2 font-medium">No fee, no markup.</p> <div className="mt-6 grid gap-3"> <div className="grid gap-1.5"><label htmlFor="pf-amount" className="text-sm font-extrabold">You send (USD)</label><input id="pf-amount" inputMode="decimal" value={amount} onChange={(e) => setAmount(e.target.value.replace(/[^\d.]/g, ""))} className="h-12 rounded-2xl border-2 border-[var(--pocket-ink)] bg-white/60 px-4 text-lg font-extrabold tabular-nums outline-none focus-visible:ring-[3px] focus-visible:ring-[var(--pocket-ink)]/40" /></div> <div role="group" aria-label="Currency" className="flex flex-wrap gap-2">{currencies.slice(1).map(([c]) => <button key={c} type="button" aria-pressed={cur === c} onClick={() => setCur(c)} className="aria-pressed:bg-[var(--pocket-ink)] aria-pressed:text-white focus-visible:ring-[var(--pocket-ink)]/50 h-10 rounded-full border-2 border-[var(--pocket-ink)] px-4 text-sm font-extrabold outline-none focus-visible:ring-[3px]">{c}</button>)}</div> <p className="mt-2 font-extrabold">They receive</p> <p className={cn("text-4xl tabular-nums", pocketDisplayClass)}><output aria-live="polite">{rate[2]}{converted.toLocaleString("en-US", { maximumFractionDigits: cur === "JPY" ? 0 : 2, minimumFractionDigits: cur === "JPY" ? 0 : 2 })}</output></p> <p className="text-sm font-bold">1 USD = {rate[1]} {cur}</p> </div> </section> </div> </main> </PocketShell> ) } export { PocketFeatures, type PocketFeaturesProps }components/ballmac/templates/pocket/pocket-pricing.tsx// Ballmac UI: Pocket pricing page. https://ui.ballmac.com/templates/template-pocket "use client" import * as React from "react" import { Check, Minus } from "lucide-react" import { compare, faqs, plans } from "@/components/ballmac/templates/pocket/pocket-data" import { PocketShell, pocketDisplayClass, type PocketHrefs } from "@/components/ballmac/templates/pocket/pocket-theme" import { cn } from "@/lib/utils" type PocketPricingProps = React.ComponentProps<"div"> & { hrefs?: Partial<PocketHrefs> } /** Pricing: a monthly or yearly switch, three plans, a real comparison table and the questions people ask. */ function PocketPricing({ hrefs, ...props }: PocketPricingProps) { const link = { download: "/pocket/download", ...hrefs } const [yearly, setYearly] = React.useState(true) const cell = (v: string | boolean) => (v === true ? <><Check className="mx-auto size-5" aria-hidden="true" /><span className="sr-only">Included</span></> : v === false ? <><Minus className="text-muted-foreground mx-auto size-5" aria-hidden="true" /><span className="sr-only">Not included</span></> : v) return ( <PocketShell page="pricing" hrefs={hrefs} {...props}> <main className="mx-auto max-w-6xl px-4 pt-14 pb-8 sm:px-6 sm:pt-20"> <div className="text-center"> <h1 className={cn("mx-auto max-w-3xl text-[clamp(3rem,9vw,7rem)] leading-[0.9] text-balance", pocketDisplayClass)}>Pay for perks, not for banking.</h1> <p className="text-muted-foreground mx-auto mt-5 max-w-xl text-xl font-medium text-pretty">The Free plan is free forever. Upgrade only when you want the extras.</p> <div role="group" aria-label="Billing period" className="bg-secondary mt-8 inline-flex rounded-full p-1"> {([[false, "Monthly"], [true, "Yearly"]] as const).map(([v, l]) => <button key={l} type="button" aria-pressed={yearly === v} onClick={() => setYearly(v)} className="aria-pressed:bg-primary aria-pressed:text-primary-foreground focus-visible:ring-ring/50 inline-flex h-11 items-center gap-2 rounded-full px-6 text-sm font-extrabold outline-none focus-visible:ring-[3px]">{l}{v && <span className="bg-chart-1 rounded-full px-2 py-0.5 text-[11px] text-[var(--pocket-on-lime)]">Save 20%</span>}</button>)} </div> </div> <ul className="mt-12 grid gap-4 lg:grid-cols-3"> {plans.map((p) => { const price = yearly ? p.annual : p.monthly return ( <li key={p.id} className={cn("relative flex flex-col rounded-[2rem] border p-8", p.featured ? "bg-primary text-primary-foreground border-transparent lg:-my-4 lg:py-12" : "bg-card")}> {p.featured && <span className="bg-chart-1 absolute -top-3 start-8 rounded-full px-3 py-1 text-xs font-extrabold text-[var(--pocket-on-lime)]">Most popular</span>} <h2 className={cn("text-2xl", pocketDisplayClass)}>{p.name}</h2> <p className="mt-1 text-sm font-medium">{p.blurb}</p> <p className="mt-6 flex items-baseline gap-1"><span className={cn("text-6xl leading-none tabular-nums", pocketDisplayClass)}>${price}</span><span className="text-sm font-bold">{price === 0 ? "forever" : "a month"}</span></p> <p className="mt-1 min-h-5 text-xs font-semibold">{price > 0 && yearly ? `Billed $${price * 12} a year` : price > 0 ? "Billed monthly" : "No card needed"}</p> <ul className="mt-6 grid flex-1 gap-3">{p.features.map((f) => <li key={f} className="flex gap-3 text-sm font-semibold"><Check className="mt-0.5 size-4 shrink-0" aria-hidden="true" />{f}</li>)}</ul> <a href={link.download} className={cn("focus-visible:ring-ring mt-8 inline-flex h-12 items-center justify-center rounded-full px-6 font-extrabold outline-none focus-visible:ring-[3px]", p.featured ? "bg-card text-card-foreground" : "bg-primary text-primary-foreground")}>{p.monthly === 0 ? "Get Pocket Free" : `Try ${p.name} free for 30 days`}</a> </li> ) })} </ul> <section aria-labelledby="pp-compare" className="mt-24"> <h2 id="pp-compare" className={cn("text-[clamp(2rem,5vw,3.5rem)] leading-none", pocketDisplayClass)}>Compare plans</h2> <div role="region" aria-label="Plan comparison" tabIndex={0} className="focus-visible:ring-ring/50 mt-8 overflow-x-auto rounded-3xl border outline-none focus-visible:ring-[3px]"> <table className="w-full min-w-[34rem] text-start text-sm"> <thead className="bg-secondary"><tr><th scope="col" className="p-4 font-extrabold">Feature</th>{plans.map((p) => <th key={p.id} scope="col" className="p-4 text-center font-extrabold">{p.name}</th>)}</tr></thead> <tbody className="divide-y">{compare.map((r) => <tr key={r[0]}><th scope="row" className="p-4 font-bold">{r[0]}</th>{[r[1], r[2], r[3]].map((v, i) => <td key={i} className="p-4 text-center font-semibold">{cell(v)}</td>)}</tr>)}</tbody> </table> </div> </section> <section aria-labelledby="pp-faq" className="mt-24 grid gap-8 lg:grid-cols-[1fr_1.6fr]"> <h2 id="pp-faq" className={cn("text-[clamp(2rem,5vw,3.5rem)] leading-none", pocketDisplayClass)}>Questions</h2> <div className="divide-y border-y">{faqs.map((f) => <details key={f.q} className="py-5"><summary className="focus-visible:ring-ring/50 cursor-pointer list-none rounded-md text-lg font-extrabold outline-none focus-visible:ring-[3px]">{f.q}</summary><p className="text-muted-foreground mt-3 text-pretty">{f.a}</p></details>)}</div> </section> </main> </PocketShell> ) } export { PocketPricing, type PocketPricingProps }components/ballmac/templates/pocket/pocket-security.tsx// Ballmac UI: Pocket security page. https://ui.ballmac.com/templates/template-pocket "use client" import * as React from "react" import { Fingerprint, KeyRound, LockKeyhole, ShieldCheck, Smartphone, Snowflake } from "lucide-react" import { PocketShell, pocketDisplayClass, type PocketHrefs } from "@/components/ballmac/templates/pocket/pocket-theme" import { cn } from "@/lib/utils" const pillars = [ { icon: Fingerprint, title: "Only you get in", body: "Face or fingerprint on your phone, plus a passkey that never leaves the device. No passwords to leak.", tone: "bg-chart-1" }, { icon: LockKeyhole, title: "Encrypted end to end", body: "Everything is encrypted in transit and at rest. Card numbers live in a hardware vault, not in a database.", tone: "bg-chart-4" }, { icon: KeyRound, title: "You decide every payment", body: "Set limits, switch off online or contactless, and approve unusual payments with a tap.", tone: "bg-chart-5" }, { icon: ShieldCheck, title: "Insured and audited", body: "Deposits are FDIC insured up to $250,000, and independent auditors test us every quarter.", tone: "bg-chart-2" }, ] const steps = [ { icon: Smartphone, t: "0:00", title: "You notice it’s gone", body: "Open Pocket on any other device and sign in with your passkey." }, { icon: Snowflake, t: "0:20", title: "Freeze the card", body: "One tap stops every payment. The old phone is signed out at the same time." }, { icon: LockKeyhole, t: "1:30", title: "Nothing was on the phone", body: "No balances or card numbers are stored on the device, so there is nothing to find." }, { icon: ShieldCheck, t: "5:00", title: "A new card is on its way", body: "Order a replacement and it arrives in three days. The virtual card works in minutes." }, ] type PocketSecurityProps = React.ComponentProps<"div"> & { hrefs?: Partial<PocketHrefs> } /** The security page: four pillars, a minute-by-minute lost-phone timeline, live status and a bug bounty. */ function PocketSecurity({ hrefs, ...props }: PocketSecurityProps) { return ( <PocketShell page="security" hrefs={hrefs} {...props}> <main className="mx-auto max-w-6xl px-4 pt-14 pb-8 sm:px-6 sm:pt-20"> <section aria-labelledby="pse-title" className="grid items-center gap-10 lg:grid-cols-[1.3fr_1fr]"> <div> <p className="bg-card inline-flex items-center gap-2 rounded-full border px-4 py-1.5 text-sm font-extrabold"><span className="bg-chart-1 size-2.5 rounded-full" aria-hidden="true" />All systems normal · 99.99% uptime this year</p> <h1 id="pse-title" className={cn("mt-6 text-[clamp(3rem,9vw,7rem)] leading-[0.9] text-balance", pocketDisplayClass)}>Boring on purpose.</h1> <p className="text-muted-foreground mt-5 max-w-xl text-xl font-medium text-pretty">Money should be the least exciting thing about your phone. This is how we make sure it is.</p> </div> <div aria-hidden="true" className="relative mx-auto aspect-square w-full max-w-sm"> <div className="bg-chart-1 absolute inset-[6%] rounded-full" /><div className="bg-[var(--pocket-navy)] absolute inset-[22%] rounded-full" /><ShieldCheck className="text-chart-1 absolute inset-[36%] size-[28%]" strokeWidth={1.6} /> <span className="bg-card absolute top-[8%] end-[2%] rotate-6 rounded-2xl border px-3 py-1.5 text-sm font-extrabold shadow-lg">256-bit</span><span className="bg-card absolute bottom-[10%] start-[0%] -rotate-6 rounded-2xl border px-3 py-1.5 text-sm font-extrabold shadow-lg">Passkeys</span> </div> </section> <ul className="mt-20 grid gap-4 md:grid-cols-2">{pillars.map((p) => <li key={p.title} className={cn("rounded-[2rem] p-8 text-[var(--pocket-on-lime)] sm:p-10", p.tone)}><p.icon className="size-9" aria-hidden="true" /><h2 className={cn("mt-8 text-3xl", pocketDisplayClass)}>{p.title}</h2><p className="mt-3 text-lg font-medium text-pretty">{p.body}</p></li>)}</ul> <section aria-labelledby="pse-lost" className="mt-24"> <h2 id="pse-lost" className={cn("max-w-2xl text-[clamp(2.2rem,5.5vw,4rem)] leading-[0.95] text-balance", pocketDisplayClass)}>What happens when you lose your phone</h2> <ol className="mt-12 grid gap-4 md:grid-cols-4">{steps.map((s, i) => <li key={s.title} className="bg-card relative rounded-3xl border p-6"><span className="text-primary text-sm font-extrabold tabular-nums">{s.t}</span><s.icon className="mt-6 size-7" aria-hidden="true" /><h3 className="mt-4 text-xl font-extrabold text-balance">{s.title}</h3><p className="text-muted-foreground mt-2 text-sm font-medium text-pretty">{s.body}</p><span className="sr-only">Step {i + 1} of {steps.length}</span></li>)}</ol> </section> <section aria-labelledby="pse-bounty" className="mt-24 grid gap-4 lg:grid-cols-2"> <div className="bg-[var(--pocket-navy)] rounded-[2rem] p-8 text-[var(--pocket-on-navy)] sm:p-12"><h2 id="pse-bounty" className={cn("text-4xl", pocketDisplayClass)}>Find a bug, get paid.</h2><p className="mt-3 text-lg font-medium text-pretty">Our bug bounty pays up to $50,000 for serious findings. We publish every fixed report and credit the researcher.</p><p className={cn("text-chart-1 mt-8 text-7xl leading-none tabular-nums", pocketDisplayClass)}>$50k</p></div> <div className="bg-card rounded-[2rem] border p-8 sm:p-12"><h2 className={cn("text-4xl", pocketDisplayClass)}>Checked by others</h2><ul className="mt-6 grid grid-cols-2 gap-3">{["SOC 2 Type II", "ISO 27001", "PCI DSS Level 1", "FDIC insured"].map((c) => <li key={c} className="bg-secondary flex items-center gap-2 rounded-2xl p-4 text-sm font-extrabold"><ShieldCheck className="size-5 shrink-0" aria-hidden="true" />{c}</li>)}</ul><p className="text-muted-foreground mt-6 text-sm font-medium">Reports are available to customers on request. Last audit: August 2026.</p></div> </section> </main> </PocketShell> ) } export { PocketSecurity, type PocketSecurityProps }components/ballmac/templates/pocket/pocket-download.tsx// Ballmac UI: Pocket download page. https://ui.ballmac.com/templates/template-pocket "use client" import * as React from "react" import { Check, Smartphone } from "lucide-react" import { PocketApp, PocketShell, StoreButtons, pocketDisplayClass, type PocketHrefs } from "@/components/ballmac/templates/pocket/pocket-theme" import { cn } from "@/lib/utils" /** A decorative QR-style code: three finder squares and a fixed pseudo-random fill, so server and client agree. */ function QrArt({ className }: { className?: string }) { const n = 21 const finder = (x: number, y: number) => (x < 7 && y < 7) || (x >= n - 7 && y < 7) || (x < 7 && y >= n - 7) const cells: React.ReactNode[] = [] let seed = 7 for (let y = 0; y < n; y++) for (let x = 0; x < n; x++) { seed = (seed * 1103515245 + 12345) & 0x7fffffff if (finder(x, y)) continue if ((seed >> 8) % 100 < 46) cells.push(<rect key={`${x}-${y}`} x={x} y={y} width="1" height="1" />) } const eye = (x: number, y: number) => ( <g key={`${x}${y}`}><rect x={x} y={y} width="7" height="7" /><rect x={x + 1} y={y + 1} width="5" height="5" className="fill-card" /><rect x={x + 2} y={y + 2} width="3" height="3" /></g> ) return ( <svg viewBox={`-1 -1 ${n + 2} ${n + 2}`} role="img" aria-label="QR code that opens the download page on your phone" className={cn("bg-card fill-[var(--pocket-ink)] dark:fill-foreground rounded-2xl p-1", className)}> {cells}{eye(0, 0)}{eye(n - 7, 0)}{eye(0, n - 7)} </svg> ) } type PocketDownloadProps = React.ComponentProps<"div"> & { hrefs?: Partial<PocketHrefs> } /** The download page: store buttons, a QR code, and a “text me the link” form that validates and confirms. */ function PocketDownload({ hrefs, ...props }: PocketDownloadProps) { const link = { download: "/pocket/download", ...hrefs } const [value, setValue] = React.useState("") const [device, setDevice] = React.useState<"iphone" | "android">("iphone") const [error, setError] = React.useState("") const [sent, setSent] = React.useState<string | null>(null) function submit(e: React.FormEvent) { e.preventDefault() const v = value.trim() const ok = /^\S+@\S+\.\S+$/.test(v) || v.replace(/\D/g, "").length >= 10 if (!ok) return setError("Enter a mobile number or an email address.") setError("") setSent(v) } return ( <PocketShell page="download" hrefs={hrefs} {...props}> <main className="mx-auto max-w-6xl px-4 pt-14 pb-8 sm:px-6 sm:pt-20"> <div className="grid items-center gap-12 lg:grid-cols-[1.2fr_1fr]"> <div> <h1 className={cn("text-[clamp(3rem,9vw,7rem)] leading-[0.9] text-balance", pocketDisplayClass)}>Get Pocket in your pocket.</h1> <p className="text-muted-foreground mt-5 max-w-lg text-xl font-medium text-pretty">Free on iPhone and Android. Open an account in about four minutes, with a photo ID and your phone.</p> <StoreButtons href={link.download} className="mt-8" /> <section aria-labelledby="pd-text" className="bg-card mt-10 rounded-[2rem] border p-6 sm:p-8"> <h2 id="pd-text" className={cn("flex items-center gap-2 text-2xl", pocketDisplayClass)}><Smartphone className="size-6" aria-hidden="true" />On your computer? Text yourself the link.</h2> {sent ? ( <p role="status" className="bg-chart-1 mt-5 flex items-center gap-2 rounded-2xl p-4 font-extrabold text-[var(--pocket-on-lime)]"><Check className="size-5 shrink-0" aria-hidden="true" />Sent to {sent}. Open it on your {device === "iphone" ? "iPhone" : "Android"}.</p> ) : ( <form onSubmit={submit} noValidate className="mt-5 grid gap-4"> <fieldset><legend className="mb-2 text-sm font-extrabold">Your phone</legend> <div className="flex gap-2">{([["iphone", "iPhone"], ["android", "Android"]] as const).map(([v, l]) => <label key={v} className={cn("focus-within:ring-ring/50 cursor-pointer rounded-full border-2 px-5 py-2.5 text-sm font-extrabold transition-colors focus-within:ring-[3px] motion-reduce:transition-none", device === v ? "border-primary bg-primary text-primary-foreground" : "hover:border-primary/40")}><input type="radio" name="device" value={v} checked={device === v} onChange={() => setDevice(v)} className="sr-only" />{l}</label>)}</div> </fieldset> <div className="grid gap-2"><label htmlFor="pd-contact" className="text-sm font-extrabold">Mobile number or email</label> <div className="flex flex-col gap-2 sm:flex-row"><input id="pd-contact" value={value} onChange={(e) => setValue(e.target.value)} aria-invalid={!!error} aria-describedby={error ? "pd-err" : undefined} placeholder="(555) 010-2030" className="bg-background focus-visible:ring-ring/50 aria-invalid:border-destructive h-14 min-w-0 flex-1 rounded-full border-2 px-6 text-lg font-bold outline-none focus-visible:ring-[3px]" /><button type="submit" className="bg-primary text-primary-foreground focus-visible:ring-ring/50 h-14 rounded-full px-8 text-lg font-extrabold outline-none focus-visible:ring-[3px]">Text me the link</button></div> {error && <p id="pd-err" role="alert" className="text-destructive text-sm font-bold">{error}</p>} </div> <p className="text-muted-foreground text-xs font-medium">We send one message and do not keep your number.</p> </form> )} </section> </div> <div className="relative mx-auto grid w-full max-w-sm justify-items-center gap-6"> <PocketApp defaultTab="save" className="w-[min(100%,290px)]" /> <div className="bg-chart-1 relative z-10 -mt-20 flex items-center gap-4 self-end rounded-3xl p-4 text-[var(--pocket-on-lime)] shadow-xl sm:-me-8"><QrArt className="size-24" /><p className="max-w-[9rem] text-sm font-extrabold">Point your camera here to download</p></div> </div> </div> <section aria-labelledby="pd-need" className="mt-24 grid gap-4 md:grid-cols-3"> <h2 id="pd-need" className="sr-only">What you need</h2> {[["A phone", "iOS 16 or later, or Android 10 or later. Face or fingerprint unlock turned on."], ["A photo ID", "A passport or driver’s licence. We check it in the app, usually in under a minute."], ["Four minutes", "That is the median. You can fund the account straight away from a bank account or card."]].map(([t, b]) => <div key={t} className="bg-secondary rounded-3xl p-7"><h3 className={cn("text-2xl", pocketDisplayClass)}>{t}</h3><p className="text-muted-foreground mt-2 font-medium text-pretty">{b}</p></div>)} </section> </main> </PocketShell> ) } export { PocketDownload, QrArt, type PocketDownloadProps }app/pocket/page.tsx// Ballmac UI: Pocket template route. https://ui.ballmac.com/templates/template-pocket import type { Metadata } from "next" import { PocketHome } from "@/components/ballmac/templates/pocket/pocket-home" export const metadata: Metadata = { title: "Pocket: money that keeps up", description: "One app for spending, saving and splitting. No fees to open or to leave.", } export default function Page() { return <PocketHome /> }app/pocket/features/page.tsx// Ballmac UI: Pocket template route. https://ui.ballmac.com/templates/template-pocket import type { Metadata } from "next" import { PocketFeatures } from "@/components/ballmac/templates/pocket/pocket-features" export const metadata: Metadata = { title: "Features · Pocket", description: "Freeze a card, tune round-ups, split a bill and convert money, live.", } export default function Page() { return <PocketFeatures /> }app/pocket/pricing/page.tsx// Ballmac UI: Pocket template route. https://ui.ballmac.com/templates/template-pocket import type { Metadata } from "next" import { PocketPricing } from "@/components/ballmac/templates/pocket/pocket-pricing" export const metadata: Metadata = { title: "Pricing · Pocket", description: "Free forever, with Plus and Metal for extras.", } export default function Page() { return <PocketPricing /> }app/pocket/security/page.tsx// Ballmac UI: Pocket template route. https://ui.ballmac.com/templates/template-pocket import type { Metadata } from "next" import { PocketSecurity } from "@/components/ballmac/templates/pocket/pocket-security" export const metadata: Metadata = { title: "Security · Pocket", description: "How Pocket keeps your money safe, and what happens if you lose your phone.", } export default function Page() { return <PocketSecurity /> }app/pocket/download/page.tsx// Ballmac UI: Pocket template route. https://ui.ballmac.com/templates/template-pocket import type { Metadata } from "next" import { PocketDownload } from "@/components/ballmac/templates/pocket/pocket-download" export const metadata: Metadata = { title: "Download · Pocket", description: "Free on iPhone and Android.", } export default function Page() { return <PocketDownload /> }Update the import paths to match your project setup.
The page is added as a route in your app, with every block it uses.
What's inside
- Pages
- Home/pocket
- Features/pocket/features
- Pricing/pocket/pricing
- Security/pocket/security
- Download/pocket/download
- Fonts
- Gabarito (Google Fonts, loaded with next/font)
- Files
- 13 files · 3 Ballmac blocks and components
Built with
Dependencies
- npm
- lucide-react
Use with AI
Installs a five-page mobile-app landing site with a working app in a phone frame, live feature demos, pricing with a yearly toggle, a security page and a download page. Edit pocket-data.ts and pocketCss. With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI Pocket: mobile money app (@ballmac/template-pocket) to this project with the shadcn MCP, then use it where it fits.
Use it for
- Mobile app and fintech landing sites
- Any product that benefits from a live demo inside a phone
Not for
- Real banking flows (this is marketing; connect your own auth and APIs)
Registry JSON: https://ui.ballmac.com/r/template-pocket.json
Free to use in personal and commercial projects.