← back to IWasCute
src/app/v2/page.tsx
507 lines
'use client'
import Link from 'next/link'
import { motion } from 'framer-motion'
import { Camera, Shield, DollarSign, ArrowRight, Sparkles } from 'lucide-react'
import clsx from 'clsx'
// ---------------------------------------------------------------------------
// AnimationStyles — injects CSS keyframes for blob motion + glass utilities.
// Content is a static compile-time string with NO user-supplied data, so
// rendering it via innerHTML carries zero XSS risk (same as any <style> tag
// in a CSS file or styled-component). This is a standard Next.js / React
// pattern for scoped global keyframes in a Client Component.
// ---------------------------------------------------------------------------
function AnimationStyles() {
const css = [
'@keyframes blob-float-a{0%{transform:translate(0px,0px) scale(1)}100%{transform:translate(40px,-60px) scale(1.08)}}',
'@keyframes blob-float-b{0%{transform:translate(0px,0px) scale(1)}100%{transform:translate(-50px,40px) scale(1.05)}}',
'@keyframes blob-float-c{0%{transform:translate(0px,0px) scale(1.02)}100%{transform:translate(30px,50px) scale(0.96)}}',
'@keyframes blob-float-d{0%{transform:translate(0px,0px) scale(1)}100%{transform:translate(-30px,-40px) scale(1.06)}}',
'@keyframes blob-float-e{0%{transform:translate(0px,0px) scale(0.98)}100%{transform:translate(20px,30px) scale(1.04)}}',
'@keyframes shimmer{0%{background-position:-200% center}100%{background-position:200% center}}',
'.blob-a{animation:blob-float-a 18s ease-in-out infinite alternate}',
'.blob-b{animation:blob-float-b 22s ease-in-out infinite alternate}',
'.blob-c{animation:blob-float-c 16s ease-in-out infinite alternate}',
'.blob-d{animation:blob-float-d 20s ease-in-out infinite alternate}',
'.blob-e{animation:blob-float-e 14s ease-in-out infinite alternate}',
'.glass{background:rgba(255,255,255,0.38);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid rgba(255,255,255,0.32)}',
'.glass-dark{background:rgba(255,255,255,0.22);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border:1px solid rgba(255,255,255,0.24)}',
'.shimmer-text{background:linear-gradient(90deg,#3D405B 0%,#F4846F 30%,#8BBCAA 55%,#3D405B 80%);background-size:200% auto;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;animation:shimmer 5s linear infinite}',
'.btn-primary{background:linear-gradient(135deg,#FFB5A7 0%,#F4846F 100%);transition:transform 0.2s ease,box-shadow 0.2s ease,filter 0.2s ease}',
'.btn-primary:hover{transform:translateY(-2px) scale(1.02);box-shadow:0 12px 40px rgba(244,132,111,0.45);filter:brightness(1.05)}',
'.btn-secondary{transition:transform 0.2s ease,box-shadow 0.2s ease,background 0.2s ease}',
'.btn-secondary:hover{transform:translateY(-2px) scale(1.02);box-shadow:0 12px 32px rgba(139,188,170,0.35);background:rgba(255,255,255,0.55)}',
'.divider-v{width:1px;background:rgba(61,64,91,0.12);align-self:stretch}',
].join('\n')
// React renders <style> tags safely — this is static content, not user data.
return <style>{css}</style>
}
// ---------------------------------------------------------------------------
// Animated gradient mesh — five large blobs using pure CSS keyframes.
// Fixed so it covers the entire viewport regardless of page scroll length.
// ---------------------------------------------------------------------------
function GradientMeshBackground() {
return (
<div
aria-hidden="true"
className="pointer-events-none fixed inset-0 overflow-hidden"
style={{ zIndex: 0 }}
>
{/* Blob A — top-left, peach */}
<div
className="blob-a absolute -top-24 -left-24 w-[520px] h-[520px] rounded-full blur-3xl"
style={{ background: '#FFB5A7', opacity: 0.42 }}
/>
{/* Blob B — top-right, sage */}
<div
className="blob-b absolute -top-12 right-0 w-[480px] h-[480px] rounded-full blur-3xl"
style={{ background: '#B5D5C5', opacity: 0.38 }}
/>
{/* Blob C — center, cream-dark */}
<div
className="blob-c absolute top-1/3 left-1/4 w-[400px] h-[400px] rounded-full blur-3xl"
style={{ background: '#FFE0CB', opacity: 0.50 }}
/>
{/* Blob D — bottom-left, peach-dark */}
<div
className="blob-d absolute bottom-0 -left-12 w-[440px] h-[440px] rounded-full blur-3xl"
style={{ background: '#F4846F', opacity: 0.28 }}
/>
{/* Blob E — bottom-right, sage-dark */}
<div
className="blob-e absolute -bottom-16 right-8 w-[460px] h-[460px] rounded-full blur-3xl"
style={{ background: '#8BBCAA', opacity: 0.35 }}
/>
</div>
)
}
// ---------------------------------------------------------------------------
// NavBar
// ---------------------------------------------------------------------------
function NavBar() {
return (
<nav
className="glass relative z-20 flex items-center justify-between px-6 py-4 md:px-12 mx-4 mt-4 rounded-2xl"
style={{ color: 'var(--color-brown)' }}
>
<div className="flex items-center gap-2">
<Sparkles
size={18}
strokeWidth={1.8}
style={{ color: 'var(--color-peach-dark)' }}
/>
<span
className="text-xl font-black tracking-tighter"
style={{ color: 'var(--color-brown)' }}
>
i was cute
</span>
</div>
<div
className="hidden sm:flex items-center gap-6 text-sm font-medium"
style={{ color: 'var(--color-warm-gray)' }}
>
<Link href="/browse" className="hover:opacity-70 transition-opacity">
Browse
</Link>
<Link href="/licensor" className="hover:opacity-70 transition-opacity">
Upload
</Link>
<Link href="/login" className="hover:opacity-70 transition-opacity">
Login
</Link>
</div>
<Link
href="/licensor"
className="btn-primary inline-flex items-center gap-1.5 px-5 py-2.5 rounded-xl text-sm font-semibold"
style={{ color: 'white' }}
>
Get Started
<ArrowRight size={14} strokeWidth={2.5} />
</Link>
</nav>
)
}
// ---------------------------------------------------------------------------
// Hero Card — glassmorphism panel, centered on the page
// ---------------------------------------------------------------------------
function HeroCard() {
return (
<section className="relative z-10 flex flex-col items-center justify-center px-4 pt-16 pb-8 md:pt-24">
<motion.div
whileHover={{ scale: 1.005 }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
className="glass w-full max-w-3xl rounded-3xl px-8 py-14 md:px-16 md:py-20 flex flex-col items-center text-center gap-7"
style={{
boxShadow:
'0 32px 80px rgba(61,64,91,0.10), 0 0 0 1px rgba(255,255,255,0.30)',
}}
>
{/* Badge */}
<div
className="glass-dark inline-flex items-center gap-2 px-4 py-1.5 rounded-full text-xs font-semibold tracking-widest uppercase"
style={{ color: 'var(--color-warm-gray)' }}
>
<span
className="w-1.5 h-1.5 rounded-full"
style={{ background: 'var(--color-peach-dark)' }}
/>
Childhood Photo Marketplace
</div>
{/* Headline */}
<h1 className="text-6xl md:text-8xl font-black tracking-tighter leading-[0.9] select-none">
<span className="shimmer-text">i was</span>
<br />
<span style={{ color: 'var(--color-peach-dark)' }}>cute</span>
</h1>
{/* Tagline */}
<p
className="text-xl md:text-2xl font-light tracking-wide max-w-md leading-relaxed"
style={{ color: 'var(--color-warm-gray)' }}
>
Your childhood photos, legally licensed and earning for you.
</p>
{/* Description */}
<p
className="text-base max-w-lg leading-relaxed"
style={{ color: 'var(--color-warm-gray)', opacity: 0.85 }}
>
Turn nostalgic memories into royalties. Upload, verify rights, and
let advertisers, filmmakers, and designers celebrate the real you —
at your most adorable.
</p>
{/* CTA buttons */}
<div className="flex flex-col sm:flex-row gap-3 mt-2 w-full sm:w-auto">
<Link
href="/licensor"
className="btn-primary inline-flex items-center justify-center gap-2 px-8 py-4 rounded-2xl text-base font-bold tracking-wide"
style={{ color: 'white' }}
>
<Camera size={18} strokeWidth={2} />
Upload Your Photos
</Link>
<Link
href="/browse"
className="btn-secondary glass-dark inline-flex items-center justify-center gap-2 px-8 py-4 rounded-2xl text-base font-semibold tracking-wide"
style={{ color: 'var(--color-brown)' }}
>
Browse Library
<ArrowRight size={16} strokeWidth={2} />
</Link>
</div>
{/* Trust micro-copy */}
<p
className="text-xs font-medium tracking-wide"
style={{ color: 'var(--color-warm-gray)', opacity: 0.6 }}
>
No credit card required · Free to upload · You
keep all rights
</p>
</motion.div>
</section>
)
}
// ---------------------------------------------------------------------------
// Stats Bar — glassmorphism strip with 3 metrics
// ---------------------------------------------------------------------------
const STATS = [
{ value: '10K+', label: 'Photos Licensed' },
{ value: '500+', label: 'Verified Licensors' },
{ value: '100%', label: 'Rights Verified' },
] as const
function StatsBar() {
return (
<section className="relative z-10 px-4 py-4">
<div
className="glass mx-auto max-w-2xl rounded-2xl px-6 py-5 flex items-center justify-around gap-4"
style={{ boxShadow: '0 8px 32px rgba(61,64,91,0.08)' }}
>
{STATS.map((stat, i) => (
<div key={stat.label} className="flex items-center gap-4">
<div className="flex flex-col items-center gap-0.5">
<span
className="text-2xl md:text-3xl font-black tracking-tight"
style={{ color: 'var(--color-peach-dark)' }}
>
{stat.value}
</span>
<span
className="text-xs font-semibold tracking-widest uppercase"
style={{ color: 'var(--color-warm-gray)', opacity: 0.75 }}
>
{stat.label}
</span>
</div>
{i < STATS.length - 1 && (
<div className="divider-v hidden sm:block" />
)}
</div>
))}
</div>
</section>
)
}
// ---------------------------------------------------------------------------
// Feature Grid — three glassmorphism cards
// ---------------------------------------------------------------------------
interface Feature {
icon: React.ReactNode
title: string
description: string
accent: string
step: string
}
const FEATURES: Feature[] = [
{
icon: <Camera size={28} strokeWidth={1.6} />,
title: 'Upload Photos',
description:
'Drag and drop your childhood photos in any format. We handle compression, metadata extraction, and secure storage.',
accent: '#FFB5A7',
step: '01',
},
{
icon: <Shield size={28} strokeWidth={1.6} />,
title: 'Verify Rights',
description:
'Our guided rights-clearance wizard walks you through copyright and likeness release in under five minutes.',
accent: '#B5D5C5',
step: '02',
},
{
icon: <DollarSign size={28} strokeWidth={1.6} />,
title: 'Earn Royalties',
description:
'When your photo is licensed by a brand or filmmaker, royalties land directly in your account. Automatically.',
accent: '#FFE0CB',
step: '03',
},
]
function FeatureGrid() {
return (
<section className="relative z-10 px-4 py-10 md:py-16">
<div className="max-w-4xl mx-auto">
{/* Section header */}
<div className="text-center mb-10">
<h2
className="text-3xl md:text-4xl font-black tracking-tight mb-3"
style={{ color: 'var(--color-brown)' }}
>
How it works
</h2>
<p
className="text-base max-w-md mx-auto leading-relaxed"
style={{ color: 'var(--color-warm-gray)', opacity: 0.8 }}
>
Three steps from photo to paycheck.
</p>
</div>
{/* Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-5">
{FEATURES.map((feature) => (
<motion.div
key={feature.title}
whileHover={{ y: -6, scale: 1.01 }}
transition={{ type: 'spring', stiffness: 400, damping: 28 }}
className={clsx(
'glass rounded-3xl p-8 flex flex-col gap-5 cursor-default',
)}
style={{ boxShadow: '0 8px 32px rgba(61,64,91,0.07)' }}
>
{/* Icon bubble */}
<div
className="w-14 h-14 rounded-2xl flex items-center justify-center flex-shrink-0"
style={{ background: feature.accent, color: 'var(--color-brown)' }}
>
{feature.icon}
</div>
{/* Step label */}
<span
className="text-xs font-bold tracking-widest uppercase"
style={{ color: 'var(--color-warm-gray)', opacity: 0.45 }}
>
Step {feature.step}
</span>
<div className="flex flex-col gap-2">
<h3
className="text-lg font-bold tracking-tight"
style={{ color: 'var(--color-brown)' }}
>
{feature.title}
</h3>
<p
className="text-sm leading-relaxed"
style={{ color: 'var(--color-warm-gray)', opacity: 0.85 }}
>
{feature.description}
</p>
</div>
</motion.div>
))}
</div>
</div>
</section>
)
}
// ---------------------------------------------------------------------------
// CTA Banner — second conversion touchpoint
// ---------------------------------------------------------------------------
function CallToActionBanner() {
return (
<section className="relative z-10 px-4 py-8 md:py-12">
<motion.div
whileHover={{ scale: 1.008 }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
className="glass max-w-3xl mx-auto rounded-3xl px-8 py-12 md:px-16 flex flex-col items-center text-center gap-6"
style={{
boxShadow:
'0 20px 60px rgba(244,132,111,0.14), 0 0 0 1px rgba(255,255,255,0.28)',
background: 'rgba(255,241,230,0.45)',
}}
>
<h2
className="text-3xl md:text-4xl font-black tracking-tight"
style={{ color: 'var(--color-brown)' }}
>
Ready to be remembered?
</h2>
<p
className="text-base max-w-md leading-relaxed"
style={{ color: 'var(--color-warm-gray)' }}
>
Join hundreds of people who have already turned their most charming
memories into a passive income stream.
</p>
<Link
href="/licensor"
className="btn-primary inline-flex items-center gap-2 px-10 py-4 rounded-2xl text-base font-bold"
style={{ color: 'white' }}
>
Start Uploading — It's Free
<ArrowRight size={16} strokeWidth={2.5} />
</Link>
</motion.div>
</section>
)
}
// ---------------------------------------------------------------------------
// Footer
// ---------------------------------------------------------------------------
const FOOTER_LINKS = [
{ href: '/browse', label: 'Browse' },
{ href: '/licensor', label: 'Upload' },
{ href: '/login', label: 'Login' },
{ href: '#', label: 'Privacy' },
{ href: '#', label: 'Terms' },
] as const
function Footer() {
return (
<footer className="relative z-10 px-6 py-10 flex flex-col items-center gap-4">
<div className="flex items-center gap-2 mb-1">
<Sparkles
size={14}
strokeWidth={1.8}
style={{ color: 'var(--color-peach-dark)' }}
/>
<span
className="text-sm font-black tracking-tighter"
style={{ color: 'var(--color-brown)' }}
>
i was cute
</span>
</div>
<nav className="flex flex-wrap items-center justify-center gap-5">
{FOOTER_LINKS.map((link) => (
<Link
key={link.label}
href={link.href}
className="text-xs font-medium hover:opacity-70 transition-opacity"
style={{ color: 'var(--color-warm-gray)' }}
>
{link.label}
</Link>
))}
</nav>
<p
className="text-xs"
style={{ color: 'var(--color-warm-gray)', opacity: 0.55 }}
>
© {new Date().getFullYear()} I was Cute. All rights reserved.
</p>
</footer>
)
}
// ---------------------------------------------------------------------------
// Page — root export
// ---------------------------------------------------------------------------
/**
* /v2 — Glassmorphism + Animated Gradient Mesh landing page.
*
* Usage: navigate to /v2 in the running Next.js dev server.
*
* Architecture notes:
* - 'use client' is required for framer-motion whileHover.
* - Blob animations are pure CSS (@keyframes), zero JS runtime cost.
* - Glass utility classes are injected via <style>{css}</style> — static
* string, no XSS surface — using the same mechanism as styled-jsx.
* - framer-motion is used ONLY for whileHover on interactive cards.
* No initial/animate props to avoid SSR hydration mismatches.
* - Fixed-position mesh background sits at z-index 0; page content at z-10+.
*/
export default function V2Page() {
return (
<>
<AnimationStyles />
{/*
Root wrapper: `position: relative` establishes a stacking context so
the fixed blobs render behind page content. overflow-x-hidden prevents
the translated blobs from creating a horizontal scrollbar.
*/}
<div
className="relative min-h-screen flex flex-col overflow-x-hidden"
style={{ background: 'var(--color-cream)' }}
>
<GradientMeshBackground />
{/* Page content — stacks above the fixed mesh */}
<div className="relative z-10 flex flex-col flex-1">
<NavBar />
<HeroCard />
<StatsBar />
<FeatureGrid />
<CallToActionBanner />
<Footer />
</div>
</div>
</>
)
}