// pricing.jsx — full pricing page: packages, addons, comparison, guarantees, FAQ snippet const { useState: useStateP, useMemo: useMemoP } = React; function Pricing({ setRoute }) { const [billing, setBilling] = useStateP("once"); // for visual flourish only const mobile = useMobile(); return (
{/* ─── header ─── */}
— Pricing

Pay once.
Yours forever.

Pick a package, get hand-keyed animation in days, walk away with the files.

{/* package cards */}
{VM_PACKAGES.map((p, i) => setRoute("order")} /> )}
{/* guarantee strip */}
{/* ─── add-ons ─── */} {/* ─── comparison table ─── */} {/* ─── custom project ─── */} {/* ─── pricing faq ─── */}
); } /* ─── big package card (richer than home snapshot) ─── */ function BigPackageCard({ p, onClick }) { const [hover, setHover] = useStateP(false); const allFeatures = { starter: ["Custom animation (~5s)", "Custom colours & text", "MP4 export", "Commercial rights", "1 revision"], standard: ["Detailed animation (~8s)", "Music + sound design", "MP4 + transparent MOV", "Commercial rights", "1 revision"], pro: ["Cinematic animation (~10s)", "Custom sound design", "Perfect loop version", "All file formats", "2 revisions", "Priority queue"] }; return (
setHover(true)} onMouseLeave={() => setHover(false)} className="glass" style={{ padding: "36px 32px 28px", cursor: "pointer", transition: "transform 240ms cubic-bezier(.2,.7,.2,1), border-color 240ms", transform: hover ? "translateY(-4px)" : "translateY(0)", borderColor: p.popular ? "var(--line-2)" : hover ? "var(--line-2)" : "var(--line)", background: p.popular ? "linear-gradient(180deg, rgba(138,77,255,0.06), rgba(255,255,255,0.02))" : "var(--glass)", minHeight: 540, display: "flex", flexDirection: "column", position: "relative", overflow: "hidden" }}> {p.popular &&
MOST PICKED
}
{p.id.toUpperCase()}
{p.name}
${p.price}
DELIVERED IN {p.delivery.toUpperCase()} · {p.revisions} REVISION{p.revisions > 1 ? "S" : ""}
); } /* ─── guarantee strip icons ─── */ function GuaranteeStrip({ icon, k, v }) { const I = ({ kind }) => { if (kind === "check") return ; if (kind === "hand") return ; if (kind === "lock") return ; if (kind === "bolt") return ; return null; }; return (
{k}
{v}
); } /* ─── add-ons ─── */ function Addons({ setRoute }) { const mobile = useMobile(); return (
— Optional extras

Mix and match.
No upsell, no pressure.

Add anything below to any package. Prices are flat — what you see is what you pay.

{VM_ADDONS.map((a, i) => )}
); } function AddonCard({ a }) { const descs = { transparency: "Transparent MOV file. Paste your logo over any background, including video.", soundfx: "Layered sound design that hits with the animation. Whooshes, impacts, the works.", loop: "Seamless loop, so it can play in the background of your homepage forever.", revision: "An extra round of tweaks after the second cut — change anything, no questions.", rush: "Move your project to the top of the queue. Delivered in 24 hours.", vectorize: "Don't have a clean SVG? I'll redraw your logo as vectors before animating.", source: "The .aep project file. For agencies that want to keep tweaking internally." }; return (
{a.label}
+${a.price}
{descs[a.id]}
{a.excludes.length > 0 &&
INCLUDED IN: {a.excludes.map((x) => x.toUpperCase()).join(", ")}
}
); } function AddonGlyph({ kind }) { const c = "currentColor"; const sw = 1.4; if (kind === "transparency") return ( ); if (kind === "soundfx") return ( ); if (kind === "loop") return ( ); if (kind === "revision") return ( ); if (kind === "rush") return ( ); if (kind === "vectorize") return ( ); if (kind === "source") return ( AEP ); return null; } /* ─── comparison table ─── */ function ComparisonTable() { const mobile = useMobile(); const rows = [ { feat: "Custom animation", starter: true, standard: true, pro: true }, { feat: "Animation length", starter: "~5s", standard: "~8s", pro: "~10s" }, { feat: "Custom colours & text", starter: true, standard: true, pro: true }, { feat: "MP4 export", starter: true, standard: true, pro: true }, { feat: "Music + sound design", starter: false, standard: true, pro: true }, { feat: "Transparent (MOV/PNG)", starter: false, standard: true, pro: true }, { feat: "Perfect loop", starter: false, standard: false, pro: true }, { feat: "Source file (.aep)", starter: "add-on", standard: "add-on", pro: "add-on" }, { feat: "Delivery", starter: "5 days", standard: "4 days", pro: "2 days" }, { feat: "Revisions", starter: "1", standard: "1", pro: "2" }, { feat: "Priority queue", starter: false, standard: false, pro: true }, { feat: "Commercial rights", starter: true, standard: true, pro: true }]; return (
— Side by side

Compare.

{/* head */}
FEATURE
{VM_PACKAGES.map((p) =>
{p.id.toUpperCase()}
${p.price}
)}
{/* rows */} {rows.map((r, i) =>
{r.feat}
{["starter", "standard", "pro"].map((k) =>
)}
)}
); } function Cell({ v }) { if (v === true) return ( ); if (v === false) return ; if (v === "add-on") return ADD-ON; return {v}; } /* ─── custom project ─── */ function CustomProject({ setRoute }) { const mobile = useMobile(); return (
{/* prism background */}
— Bigger than a logo?

Custom projects
start at $500

Explainer animations, intros and outros for a content series. Anything that isn't a single logo reveal. Quoted per project.

); } /* ─── pricing FAQ ─── */ function PricingFAQ() { const items = [ { q: "What if I don't like the first cut?", a: "You get one round of revisions in every package, and Pro gets two. If the first cut is genuinely off-brief, I'll redo it — and if it still isn't right after that, you get a full refund within seven days. No drama." }, { q: "Do you take rush jobs?", a: "Yes, for an extra $50 — bumped to the top of the queue and delivered in 24 hours. Subject to whether I have a slot open that week; I'll tell you yes/no within an hour of getting the brief." }, { q: "Can I commission something outside the listed packages?", a: "Of course. Custom projects start at $1,500. Drop me a note via the contact form with what you're imagining and I'll quote it within a day." }, { q: "Do you charge in EUR?", a: "Prices are USD because most of my clients are US-based, but Stripe converts at checkout so you can pay in your local currency. The total in EUR/GBP/etc. matches what you see, give or take the FX spread." }, { q: "Will you sign an NDA?", a: "Always happy to. Send me yours before the brief, or I'll send mine — a standard mutual NDA, no surprises." }]; return (
— Pricing questions

The small print in plain language.

); } Object.assign(window, { Pricing });