20 images generated via gemini-3-pro-image-preview (Nano Banana Pro): - Documentary street photography style, British-diverse subjects - 12 square (1:1), 8 landscape (16:9) matching placeholder aspect ratios - Replaced all ImagePlaceholder components with LandingImage + next/image - Images in public/images/landing/, served statically Pages updated: /, /for/charities, /for/fundraisers, /for/volunteers, /for/organisations New component: LandingImage (next/image with fill + object-cover)
118 lines
5.2 KiB
TypeScript
118 lines
5.2 KiB
TypeScript
import Link from "next/link"
|
|
import Image from "next/image"
|
|
|
|
/* ── Nav ── */
|
|
export function Nav() {
|
|
return (
|
|
<header className="sticky top-0 z-40 border-b border-gray-100 bg-white/90 backdrop-blur-lg">
|
|
<div className="max-w-5xl mx-auto flex h-14 items-center justify-between px-6">
|
|
<Link href="/" className="flex items-center gap-2.5">
|
|
<div className="h-7 w-7 bg-gray-900 flex items-center justify-center">
|
|
<span className="text-white text-xs font-black">P</span>
|
|
</div>
|
|
<span className="font-black text-sm tracking-tight">Pledge Now, Pay Later</span>
|
|
</Link>
|
|
<div className="flex items-center gap-4">
|
|
<Link href="/login" className="text-sm font-medium text-gray-500 hover:text-gray-900 transition-colors">Sign In</Link>
|
|
<Link href="/signup" className="rounded-lg bg-gray-900 px-4 py-2 text-sm font-semibold text-white hover:bg-gray-800 transition-colors">Get Started</Link>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
)
|
|
}
|
|
|
|
/* ── Footer ── */
|
|
export function Footer({ active }: { active?: string }) {
|
|
const links = [
|
|
{ href: "/for/charities", label: "Charities" },
|
|
{ href: "/for/fundraisers", label: "Fundraisers" },
|
|
{ href: "/for/volunteers", label: "Volunteers" },
|
|
{ href: "/for/organisations", label: "Organisations" },
|
|
]
|
|
return (
|
|
<footer className="py-10 px-6 border-t border-gray-100">
|
|
<div className="max-w-5xl mx-auto flex flex-col md:flex-row items-center justify-between gap-6 text-xs text-gray-400">
|
|
<Link href="/" className="flex items-center gap-2">
|
|
<div className="h-5 w-5 bg-gray-900 flex items-center justify-center">
|
|
<span className="text-white text-[9px] font-black">P</span>
|
|
</div>
|
|
<span className="text-gray-500">Pledge Now, Pay Later</span>
|
|
</Link>
|
|
<div className="flex flex-wrap justify-center gap-x-6 gap-y-1">
|
|
{links.map((l) => (
|
|
<Link
|
|
key={l.href}
|
|
href={l.href}
|
|
className={`hover:text-gray-900 transition-colors ${active && l.href.includes(active) ? "text-gray-900 font-semibold" : ""}`}
|
|
>
|
|
{l.label}
|
|
</Link>
|
|
))}
|
|
<Link href="/login" className="hover:text-gray-900 transition-colors">Sign In</Link>
|
|
</div>
|
|
<span>© {new Date().getFullYear()} QuikCue Ltd</span>
|
|
</div>
|
|
</footer>
|
|
)
|
|
}
|
|
|
|
/* ── Bottom CTA ── */
|
|
export function BottomCta({ headline, sub }: { headline?: string; sub?: string }) {
|
|
return (
|
|
<section className="py-24 bg-gray-950 px-6">
|
|
<div className="max-w-2xl mx-auto text-center space-y-8">
|
|
<h2 className="text-3xl md:text-4xl font-black text-white leading-tight tracking-tight">
|
|
{headline || "Every day without this, you're losing pledges."}
|
|
</h2>
|
|
<p className="text-gray-500 max-w-md mx-auto">
|
|
{sub || "Free forever. Two-minute setup. Works tonight."}
|
|
</p>
|
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
|
<Link href="/signup" className="inline-flex items-center justify-center bg-white px-8 py-4 text-base font-bold text-gray-900 hover:bg-gray-100 transition-colors">
|
|
Create free account
|
|
</Link>
|
|
<Link href="/login?demo=1" className="inline-flex items-center justify-center border border-gray-700 px-8 py-4 text-base font-bold text-white hover:bg-white/5 transition-colors">
|
|
See live demo
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|
|
|
|
/* ── Landing Image ── */
|
|
export function LandingImage({ src, alt, aspect = "video" }: { src: string; alt: string; aspect?: "video" | "square" | "wide" }) {
|
|
const aspectClass = aspect === "square" ? "aspect-square" : aspect === "wide" ? "aspect-[2/1]" : "aspect-video"
|
|
return (
|
|
<div className={`${aspectClass} w-full relative overflow-hidden bg-gray-100`}>
|
|
<Image
|
|
src={src}
|
|
alt={alt}
|
|
fill
|
|
className="object-cover"
|
|
sizes="(max-width: 768px) 100vw, 50vw"
|
|
quality={85}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/* ── Image Placeholder (kept as fallback) ── */
|
|
export function ImagePlaceholder({ aspect = "video", label }: { aspect?: "video" | "square" | "wide"; label?: string }) {
|
|
const aspectClass = aspect === "square" ? "aspect-square" : aspect === "wide" ? "aspect-[2/1]" : "aspect-video"
|
|
return (
|
|
<div className={`${aspectClass} w-full bg-gray-100 flex items-center justify-center`}>
|
|
{label && (
|
|
<div className="text-center px-6">
|
|
<div className="w-10 h-10 bg-gray-200 mx-auto mb-3 flex items-center justify-center">
|
|
<svg className="w-5 h-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909M3.75 21h16.5A2.25 2.25 0 0 0 22.5 18.75V5.25A2.25 2.25 0 0 0 20.25 3H3.75A2.25 2.25 0 0 0 1.5 5.25v13.5A2.25 2.25 0 0 0 3.75 21Z" />
|
|
</svg>
|
|
</div>
|
|
<p className="text-xs text-gray-400">{label}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|