Impeccable

This commit is contained in:
Timo Knuth
2026-04-29 20:34:09 +02:00
parent 9b31e77daa
commit aab808c553
22 changed files with 1827 additions and 684 deletions

View File

@@ -1004,49 +1004,51 @@ export default function CreatePage() {
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Foreground Color
</label>
<div className="flex items-center space-x-2">
<input
type="color"
value={foregroundColor}
onChange={(e) => setForegroundColor(e.target.value)}
className="w-12 h-10 rounded border border-gray-300"
disabled={!canCustomizeColors}
/>
<Input
value={foregroundColor}
onChange={(e) => setForegroundColor(e.target.value)}
className="flex-1"
disabled={!canCustomizeColors}
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Background Color
</label>
<div className="flex items-center space-x-2">
<input
type="color"
value={backgroundColor}
onChange={(e) => setBackgroundColor(e.target.value)}
className="w-12 h-10 rounded border border-gray-300"
disabled={!canCustomizeColors}
/>
<Input
value={backgroundColor}
onChange={(e) => setBackgroundColor(e.target.value)}
className="flex-1"
disabled={!canCustomizeColors}
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Foreground Color
</label>
<div className="flex items-center space-x-2">
<input
type="color"
value={foregroundColor}
onChange={(e) => setForegroundColor(e.target.value)}
className="w-12 h-10 rounded border border-gray-300"
disabled={!canCustomizeColors}
/>
<Input
value={foregroundColor}
onChange={(e) => setForegroundColor(e.target.value)}
className="flex-1"
disabled={!canCustomizeColors}
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Background Color
</label>
<div className="flex items-center space-x-2">
<input
type="color"
value={backgroundColor}
onChange={(e) => setBackgroundColor(e.target.value)}
className="w-12 h-10 rounded border border-gray-300"
disabled={!canCustomizeColors}
/>
<Input
value={backgroundColor}
onChange={(e) => setBackgroundColor(e.target.value)}
className="flex-1"
disabled={!canCustomizeColors}
/>
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<Select

View File

@@ -65,7 +65,10 @@ export default function RootLayout({
{children}
</Providers>
</Suspense>
</body>
{/* impeccable-live-start */}
<script src="http://localhost:8400/live.js"></script>
{/* impeccable-live-end */}
</body>
</html>
);
}

View File

@@ -1,280 +1,124 @@
'use client';
import React from 'react';
import Link from 'next/link';
import { motion, Variants } from 'framer-motion';
import {
Link as LinkIcon,
User,
Mail,
Calendar,
Facebook,
Instagram,
Phone,
MessageSquare,
Type,
Music,
Twitter,
MessageCircle,
Wifi,
Youtube,
Bitcoin,
MapPin,
CreditCard,
Video,
Users,
Barcode,
Star
} from 'lucide-react';
const TOOLS = [
{
icon: LinkIcon,
name: 'URL',
description: 'Open any website',
href: '/tools/url-qr-code',
color: 'text-blue-500',
bg: 'bg-blue-50'
},
{
icon: User,
name: 'vCard',
description: 'Share contact details',
href: '/tools/vcard-qr-code',
color: 'text-rose-500',
bg: 'bg-rose-50'
},
{
icon: Type,
name: 'Text',
description: 'Display plain text',
href: '/tools/text-qr-code',
color: 'text-slate-600',
bg: 'bg-slate-50'
},
{
icon: Mail,
name: 'Email',
description: 'Send an email',
href: '/tools/email-qr-code',
color: 'text-red-500',
bg: 'bg-red-50'
},
{
icon: MessageSquare,
name: 'SMS',
description: 'Send a text message',
href: '/tools/sms-qr-code',
color: 'text-green-500',
bg: 'bg-green-50'
},
{
icon: Wifi,
name: 'WiFi',
description: 'Connect to WiFi',
href: '/tools/wifi-qr-code',
color: 'text-indigo-500',
bg: 'bg-indigo-50'
},
{
icon: Bitcoin,
name: 'Crypto',
description: 'Receive payments',
href: '/tools/crypto-qr-code',
color: 'text-orange-500',
bg: 'bg-orange-50'
},
{
icon: Calendar,
name: 'Event',
description: 'Save calendar event',
href: '/tools/event-qr-code',
color: 'text-violet-500',
bg: 'bg-violet-50'
},
{
icon: Facebook,
name: 'Facebook',
description: 'Open Facebook page',
href: '/tools/facebook-qr-code',
color: 'text-blue-600',
bg: 'bg-blue-50'
},
{
icon: Instagram,
name: 'Instagram',
description: 'Open Instagram profile',
href: '/tools/instagram-qr-code',
color: 'text-pink-500',
bg: 'bg-pink-50'
},
{
icon: Twitter,
name: 'Twitter',
description: 'Open Twitter profile',
href: '/tools/twitter-qr-code',
color: 'text-sky-500',
bg: 'bg-sky-50'
},
{
icon: Youtube,
name: 'YouTube',
description: 'Open YouTube video',
href: '/tools/youtube-qr-code',
color: 'text-red-600',
bg: 'bg-red-50'
},
{
icon: MessageCircle,
name: 'WhatsApp',
description: 'Send WhatsApp message',
href: '/tools/whatsapp-qr-code',
color: 'text-green-600',
bg: 'bg-green-50'
},
{
icon: Music,
name: 'TikTok',
description: 'Open TikTok profile',
href: '/tools/tiktok-qr-code',
color: 'text-pink-600',
bg: 'bg-pink-50'
},
{
icon: MapPin,
name: 'Location',
description: 'Share GPS coordinates',
href: '/tools/geolocation-qr-code',
color: 'text-emerald-500',
bg: 'bg-emerald-50'
},
{
icon: Phone,
name: 'Call',
description: 'Start a phone call',
href: '/tools/call-qr-code-generator',
color: 'text-violet-500',
bg: 'bg-violet-50'
},
{
icon: CreditCard,
name: 'PayPal',
description: 'Receive PayPal payments',
href: '/tools/paypal-qr-code',
color: 'text-blue-700',
bg: 'bg-blue-50'
},
{
icon: Video,
name: 'Zoom',
description: 'Join Zoom meeting',
href: '/tools/zoom-qr-code',
color: 'text-sky-500',
bg: 'bg-sky-50'
},
{
icon: Users,
name: 'Teams',
description: 'Join Teams meeting',
href: '/tools/teams-qr-code',
color: 'text-violet-500',
bg: 'bg-violet-50'
},
{
icon: Barcode,
name: 'Barcode',
description: 'Create standard barcodes',
href: '/tools/barcode-generator',
color: 'text-slate-900',
bg: 'bg-slate-100'
},
{
icon: Star,
name: 'Google Review',
description: 'Get more Google reviews',
href: '/tools/google-review-qr-code',
color: 'text-yellow-500',
bg: 'bg-yellow-50'
}
];
// Animation variants
const containerVariants: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.05
}
}
};
const itemVariants: Variants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.4
}
}
};
export function FreeToolsGrid() {
return (
<section id="tools" className="py-24 bg-slate-50/50 border-t border-slate-100">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.5 }}
className="text-center mb-16"
>
<div className="flex flex-col md:flex-row items-center justify-center gap-3 mb-4">
<h2 className="text-3xl lg:text-4xl font-bold text-slate-900">
More Free QR Code Tools
</h2>
<div className="bg-gradient-to-r from-emerald-500 to-green-500 text-white px-3 py-1 rounded-full text-xs md:text-sm font-semibold shadow-lg shadow-emerald-500/20 flex items-center gap-2">
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-white opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-white"></span>
</span>
Free Forever
</div>
</div>
<p className="text-lg text-slate-600 max-w-2xl mx-auto">
Create specialized QR codes for every need. Completely free and no signup required.
</p>
</motion.div>
<motion.div
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-50px" }}
className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-4 gap-4 md:gap-6"
>
{TOOLS.map((tool) => (
<motion.div key={tool.name} variants={itemVariants}>
<Link
href={tool.href}
className="group flex flex-col items-center p-5 md:p-6 rounded-2xl border border-slate-200/80 bg-white hover:border-primary-200 hover:shadow-xl hover:shadow-primary-500/10 transition-all duration-300"
>
<div className={`w-12 h-12 md:w-14 md:h-14 rounded-xl ${tool.bg} flex items-center justify-center mb-3 md:mb-4 group-hover:scale-110 transition-transform duration-300`}>
<tool.icon className={`w-6 h-6 md:w-7 md:h-7 ${tool.color}`} />
</div>
<h3 className="text-base md:text-lg font-semibold text-slate-900 mb-0.5">
{tool.name}
</h3>
<p className="text-xs md:text-sm text-slate-600 text-center">
{tool.description}
</p>
</Link>
</motion.div>
))}
</motion.div>
</div>
</section>
);
}
'use client';
import React from 'react';
import Link from 'next/link';
import { motion, Variants } from 'framer-motion';
import {
Link as LinkIcon,
User,
Mail,
Calendar,
Facebook,
Instagram,
Phone,
MessageSquare,
Type,
Music,
Twitter,
MessageCircle,
Wifi,
Youtube,
Bitcoin,
MapPin,
CreditCard,
Video,
Users,
Barcode,
Star
} from 'lucide-react';
const TOOLS = [
{ icon: LinkIcon, name: 'URL', description: 'Open any website', href: '/tools/url-qr-code', color: 'text-blue-500', bg: 'bg-blue-50' },
{ icon: User, name: 'vCard', description: 'Share contact details', href: '/tools/vcard-qr-code', color: 'text-rose-500', bg: 'bg-rose-50' },
{ icon: Type, name: 'Text', description: 'Display plain text', href: '/tools/text-qr-code', color: 'text-slate-600', bg: 'bg-slate-50' },
{ icon: Mail, name: 'Email', description: 'Send an email', href: '/tools/email-qr-code', color: 'text-red-500', bg: 'bg-red-50' },
{ icon: MessageSquare, name: 'SMS', description: 'Send a text message', href: '/tools/sms-qr-code', color: 'text-green-500', bg: 'bg-green-50' },
{ icon: Wifi, name: 'WiFi', description: 'Connect to WiFi', href: '/tools/wifi-qr-code', color: 'text-indigo-500', bg: 'bg-indigo-50' },
{ icon: Bitcoin, name: 'Crypto', description: 'Receive payments', href: '/tools/crypto-qr-code', color: 'text-orange-500', bg: 'bg-orange-50' },
{ icon: Calendar, name: 'Event', description: 'Save calendar event', href: '/tools/event-qr-code', color: 'text-violet-500', bg: 'bg-violet-50' },
{ icon: Facebook, name: 'Facebook', description: 'Open Facebook page', href: '/tools/facebook-qr-code', color: 'text-blue-600', bg: 'bg-blue-50' },
{ icon: Instagram, name: 'Instagram', description: 'Open Instagram profile', href: '/tools/instagram-qr-code', color: 'text-pink-500', bg: 'bg-pink-50' },
{ icon: Twitter, name: 'Twitter', description: 'Open Twitter profile', href: '/tools/twitter-qr-code', color: 'text-sky-500', bg: 'bg-sky-50' },
{ icon: Youtube, name: 'YouTube', description: 'Open YouTube video', href: '/tools/youtube-qr-code', color: 'text-red-600', bg: 'bg-red-50' },
{ icon: MessageCircle, name: 'WhatsApp', description: 'Send WhatsApp message', href: '/tools/whatsapp-qr-code', color: 'text-green-600', bg: 'bg-green-50' },
{ icon: Music, name: 'TikTok', description: 'Open TikTok profile', href: '/tools/tiktok-qr-code', color: 'text-slate-900', bg: 'bg-slate-100' },
{ icon: MapPin, name: 'Location', description: 'Share GPS coordinates', href: '/tools/location-qr-code', color: 'text-emerald-500', bg: 'bg-emerald-50' },
{ icon: Phone, name: 'Call', description: 'Start a phone call', href: '/tools/phone-qr-code', color: 'text-teal-500', bg: 'bg-teal-50' },
];
const containerVariants: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.05 }
}
};
const itemVariants: Variants = {
hidden: { opacity: 0, y: 16 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.4 }
}
};
export function FreeToolsGrid() {
return (
<section id="tools" className="py-24 bg-slate-50/50 border-t border-slate-100">
<style dangerouslySetInnerHTML={{__html:`
.ftg-card { transition: transform 0.22s ease-out, box-shadow 0.22s ease-out, border-color 0.22s ease-out; }
.ftg-card:hover { transform: translateY(-8px); box-shadow: rgba(83,58,253,0.2) 0px 20px 40px -12px, rgba(0,0,0,0.08) 0px 8px 16px -8px; border-color: #533afd; }
.ftg-icon { transition: transform 0.25s ease-out; }
.ftg-card:hover .ftg-icon { transform: rotate(12deg) scale(1.1); }
@media (prefers-reduced-motion: reduce) { .ftg-card, .ftg-icon { transition: none !important; } }
`}}/>
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.5 }}
className="text-center mb-16"
>
<div className="flex flex-col md:flex-row items-center justify-center gap-3 mb-4">
<h2 className="text-3xl lg:text-4xl font-bold text-slate-900">More Free QR Code Tools</h2>
<div className="bg-gradient-to-r from-emerald-500 to-green-500 text-white px-3 py-1 rounded-full text-xs md:text-sm font-semibold shadow-lg shadow-emerald-500/20 flex items-center gap-2">
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-white opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-white"></span>
</span>
Free Forever
</div>
</div>
<p className="text-lg text-slate-600 max-w-2xl mx-auto">
Create specialized QR codes for every need. Completely free and no signup required.
</p>
</motion.div>
<motion.div
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-50px" }}
className="grid grid-cols-2 md:grid-cols-4 gap-4 md:gap-6"
>
{TOOLS.map((tool) => (
<motion.div key={tool.name} variants={itemVariants}>
<Link
href={tool.href}
className="ftg-card group flex flex-col items-center p-5 md:p-6 rounded-2xl border border-[#e5edf5] bg-white"
>
<div className={`ftg-icon w-12 h-12 md:w-14 md:h-14 rounded-xl ${tool.bg} flex items-center justify-center mb-3 md:mb-4`}>
<tool.icon className={`w-6 h-6 md:w-7 md:h-7 ${tool.color}`} aria-hidden="true" />
</div>
<h3 className="text-base md:text-lg font-semibold text-[#061b31] mb-0.5">{tool.name}</h3>
<p className="text-xs md:text-sm text-[#64748d] text-center">{tool.description}</p>
</Link>
</motion.div>
))}
</motion.div>
</div>
</section>
);
}

View File

@@ -1,212 +1,456 @@
'use client';
import React from 'react';
import Link from 'next/link';
import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge';
import { Card } from '@/components/ui/Card';
import { motion } from 'framer-motion';
import { Globe, User, MapPin, Phone, CheckCircle2, ArrowRight, FileText, Ticket, Smartphone, Star } from 'lucide-react';
import { useState, useEffect } from 'react';
// Sub-component for the flipping effect
const FlippingCard = ({ front, back, delay }: { front: any, back: any, delay: number }) => {
const [isFlipped, setIsFlipped] = useState(false);
useEffect(() => {
// Initial delay
const initialTimeout = setTimeout(() => {
setIsFlipped(true); // First flip
// Setup interval for subsequent flips
const interval = setInterval(() => {
setIsFlipped(prev => !prev);
}, 8000); // Toggle every 8 seconds to prevent overlap (4 cards * 2s gap)
return () => clearInterval(interval);
}, delay * 1000);
return () => clearTimeout(initialTimeout);
}, [delay]);
return (
<div className="relative h-32 w-full perspective-[1000px] group cursor-pointer">
<motion.div
animate={{ rotateY: isFlipped ? 180 : 0 }}
transition={{ duration: 0.6, type: "spring", stiffness: 260, damping: 20 }}
className="relative w-full h-full preserve-3d"
style={{ transformStyle: 'preserve-3d' }}
>
{/* Front Face */}
<div
className="absolute inset-0 backface-hidden"
style={{ backfaceVisibility: 'hidden', WebkitBackfaceVisibility: 'hidden' }}
>
<Card className="w-full h-full backdrop-blur-xl bg-white/70 border-white/50 shadow-xl shadow-gray-200/50 p-4 flex flex-col items-center justify-center hover:scale-105 transition-all duration-300">
<div className={`w-10 h-10 mb-3 rounded-xl ${front.color} flex items-center justify-center`}>
<front.icon className="w-5 h-5" />
</div>
<p className="font-semibold text-gray-800 text-sm">{front.title}</p>
</Card>
</div>
{/* Back Face */}
<div
className="absolute inset-0 backface-hidden"
style={{
backfaceVisibility: 'hidden',
WebkitBackfaceVisibility: 'hidden',
transform: 'rotateY(180deg)'
}}
>
<Card className="w-full h-full backdrop-blur-xl bg-white/80 border-white/60 shadow-xl shadow-blue-200/50 p-4 flex flex-col items-center justify-center hover:scale-105 transition-all duration-300">
<div className={`w-10 h-10 mb-3 rounded-xl ${back.color} flex items-center justify-center`}>
<back.icon className="w-5 h-5" />
</div>
<p className="font-semibold text-gray-900 text-sm">{back.title}</p>
</Card>
</div>
</motion.div>
</div>
);
};
'use client';
import React from 'react';
import Link from 'next/link';
import { Button } from '@/components/ui/Button';
import { Card } from '@/components/ui/Card';
import { motion } from 'framer-motion';
import { Globe, User, MapPin, Phone, FileText, Ticket, Smartphone, Star } from 'lucide-react';
import { useState, useEffect } from 'react';
const FlippingCard = ({ front, back, delay }: { front: any, back: any, delay: number }) => {
const [isFlipped, setIsFlipped] = useState(false);
useEffect(() => {
const initialTimeout = setTimeout(() => {
setIsFlipped(true);
const interval = setInterval(() => {
setIsFlipped(prev => !prev);
}, 8000);
return () => clearInterval(interval);
}, delay * 1000);
return () => clearTimeout(initialTimeout);
}, [delay]);
return (
<div className="relative h-32 w-full perspective-[1000px] group cursor-pointer">
<motion.div
animate={{ rotateY: isFlipped ? 180 : 0 }}
transition={{ duration: 0.6, type: "spring", stiffness: 260, damping: 20 }}
className="relative w-full h-full preserve-3d"
style={{ transformStyle: 'preserve-3d' }}
>
<div
className="absolute inset-0 backface-hidden"
style={{ backfaceVisibility: 'hidden', WebkitBackfaceVisibility: 'hidden' }}
>
<Card className="w-full h-full backdrop-blur-xl bg-white/70 border-white/50 shadow-xl shadow-gray-200/50 p-4 flex flex-col items-center justify-center hover:scale-105 transition-all duration-300">
<div className={`w-10 h-10 mb-3 rounded-xl ${front.color} flex items-center justify-center`}>
<front.icon className="w-5 h-5" />
</div>
<p className="font-semibold text-gray-800 text-sm">{front.title}</p>
</Card>
</div>
<div
className="absolute inset-0 backface-hidden"
style={{
backfaceVisibility: 'hidden',
WebkitBackfaceVisibility: 'hidden',
transform: 'rotateY(180deg)'
}}
>
<Card className="w-full h-full backdrop-blur-xl bg-white/80 border-white/60 shadow-xl shadow-blue-200/50 p-4 flex flex-col items-center justify-center hover:scale-105 transition-all duration-300">
<div className={`w-10 h-10 mb-3 rounded-xl ${back.color} flex items-center justify-center`}>
<back.icon className="w-5 h-5" />
</div>
<p className="font-semibold text-gray-900 text-sm">{back.title}</p>
</Card>
</div>
</motion.div>
</div>
);
};
interface HeroProps {
t: any; // i18n translation function
t: any;
headingAs?: 'h1' | 'div';
}
export const Hero: React.FC<HeroProps> = ({ t, headingAs = 'h1' }) => {
const HeadingTag = headingAs;
return (
<section className="relative overflow-hidden bg-gradient-to-br from-blue-50 via-white to-purple-50 pt-12 pb-20">
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<style dangerouslySetInnerHTML={{__html:`
@keyframes dotFloat{0%{transform:translateY(0) translateX(0);opacity:0}20%{opacity:1}80%{opacity:1}100%{transform:translateY(calc(var(--dy)*1px)) translateX(calc(var(--dx)*1px));opacity:0}}
.hero-dot{position:absolute;border-radius:50%;animation:dotFloat linear infinite;}
@media(prefers-reduced-motion:reduce){.hero-dot{animation:none}}
`}}/>
{([
{size:4,x:15,y:80,dx:-30,dy:-120,dur:12,delay:0,color:'rgba(96,165,250,0.5)'},
{size:3,x:35,y:90,dx:20,dy:-100,dur:15,delay:-3,color:'rgba(167,139,250,0.4)'},
{size:5,x:55,y:85,dx:-10,dy:-130,dur:10,delay:-6,color:'rgba(96,165,250,0.3)'},
{size:2,x:70,y:95,dx:30,dy:-110,dur:18,delay:-2,color:'rgba(192,132,252,0.5)'},
{size:4,x:85,y:80,dx:-20,dy:-90,dur:14,delay:-8,color:'rgba(147,197,253,0.4)'},
{size:3,x:25,y:70,dx:15,dy:-140,dur:11,delay:-5,color:'rgba(216,180,254,0.4)'},
{size:6,x:60,y:75,dx:-25,dy:-100,dur:16,delay:-1,color:'rgba(96,165,250,0.25)'},
{size:2,x:45,y:88,dx:10,dy:-120,dur:13,delay:-9,color:'rgba(167,139,250,0.5)'},
{size:5,x:80,y:60,dx:-15,dy:-80,dur:20,delay:-4,color:'rgba(99,102,241,0.3)'},
{size:3,x:10,y:50,dx:25,dy:-110,dur:17,delay:-7,color:'rgba(192,132,252,0.35)'},
] as {size:number,x:number,y:number,dx:number,dy:number,dur:number,delay:number,color:string}[]).map((p,i)=>(
<div key={i} className="hero-dot" style={{
width:p.size,height:p.size,
left:`${p.x}%`,top:`${p.y}%`,
background:p.color,
'--dx':p.dx,'--dy':p.dy,
animationDuration:`${p.dur}s`,
animationDelay:`${p.delay}s`,
} as React.CSSProperties}/>
))}
</div>
const containerjs = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.1
}
}
};
const itemjs = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0 }
};
return (
<section className="relative overflow-hidden bg-gradient-to-br from-blue-50 via-white to-purple-50 pt-12 pb-20">
{/* Animated Background Orbs */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
{/* Orb 1 - Blue (top-left) */}
<div className="absolute -top-24 -left-24 w-96 h-96 bg-blue-400/30 rounded-full blur-3xl animate-blob" />
{/* Orb 2 - Purple (top-right) */}
<div className="absolute -top-12 -right-12 w-96 h-96 bg-purple-400/30 rounded-full blur-3xl animate-blob animation-delay-2000" />
{/* Orb 3 - Pink (bottom-left) */}
<div className="absolute -bottom-24 -left-12 w-96 h-96 bg-pink-400/20 rounded-full blur-3xl animate-blob animation-delay-4000" />
{/* Orb 4 - Cyan (center-right) */}
<div className="absolute top-1/2 -right-24 w-80 h-80 bg-cyan-400/20 rounded-full blur-3xl animate-blob animation-delay-6000" />
</div>
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl relative z-10">
<div className="grid lg:grid-cols-2 gap-12 items-center">
{/* Left Content */}
<div className="space-y-8">
<Badge variant="info" className="inline-flex items-center space-x-2">
<span>{t.hero.badge}</span>
</Badge>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="space-y-6"
>
<HeadingTag className="text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl relative z-10">
<div className="grid lg:grid-cols-2 gap-12 items-center">
{/* Left Content */}
<div className="space-y-6">
<div className="flex items-center gap-2.5">
<div className="w-6 h-0.5 bg-[#533afd] shrink-0" />
<span className="text-[10px] font-semibold tracking-[0.1em] uppercase text-[#533afd]">{t.hero.badge}</span>
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="space-y-4"
>
<HeadingTag
className="font-extrabold text-[#061b31] leading-[0.92]"
style={{ fontSize: 'clamp(2.75rem, 6vw, 5rem)' }}
>
{t.hero.title}
</HeadingTag>
<p className="text-xl text-gray-600 leading-relaxed max-w-2xl">
<p className="text-[0.9375rem] text-[#64748d] leading-[1.55] max-w-[44ch]">
{t.hero.subtitle}
</p>
<div className="space-y-3 pt-2">
{t.hero.features.map((feature: string, index: number) => (
<motion.div
key={index}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.2 + (index * 0.1) }}
className="flex items-center space-x-3"
>
<div className="flex-shrink-0 w-6 h-6 bg-emerald-100 rounded-full flex items-center justify-center">
<CheckCircle2 className="w-4 h-4 text-emerald-600" />
</div>
<span className="text-gray-700 font-medium">{feature}</span>
</motion.div>
))}
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5 }}
className="flex flex-col sm:flex-row gap-4 pt-4"
>
<Link href="/signup">
<Button size="lg" className="text-lg px-8 py-6 w-full sm:w-auto shadow-lg shadow-blue-500/25 hover:shadow-blue-500/40 transition-all duration-300">
{t.hero.cta_primary}
</Button>
</Link>
<Link href="/#pricing">
<Button variant="outline" size="lg" className="text-lg px-8 py-6 w-full sm:w-auto backdrop-blur-sm bg-white/50 border-gray-200 hover:bg-white/80 transition-all duration-300">
{t.hero.cta_secondary}
</Button>
</Link>
</motion.div>
</div>
{/* Right Preview Widget */}
<div className="relative">
<div className="relative perspective-[1000px]">
<div className="grid grid-cols-2 gap-4">
{[
{
front: { title: 'URL/Website', color: 'bg-blue-500/10 text-blue-600', icon: Globe },
back: { title: 'PDF / Menu', color: 'bg-orange-500/10 text-orange-600', icon: FileText },
delay: 3 // Starts at 3s
},
{
front: { title: 'Contact Card', color: 'bg-purple-500/10 text-purple-600', icon: User },
back: { title: 'Coupon / Deals', color: 'bg-red-500/10 text-red-600', icon: Ticket },
delay: 5 // +2s
},
{
front: { title: 'Location', color: 'bg-green-500/10 text-green-600', icon: MapPin },
back: { title: 'App Store', color: 'bg-sky-500/10 text-sky-600', icon: Smartphone },
delay: 7 // +2s
},
{
front: { title: 'Phone Number', color: 'bg-pink-500/10 text-pink-600', icon: Phone },
back: { title: 'Feedback', color: 'bg-yellow-500/10 text-yellow-600', icon: Star },
delay: 9 // +2s
},
].map((card, index) => (
<FlippingCard key={index} {...card} />
))}
</div>
</div>
</div>
</div>
</div>
{/* Smooth Gradient Fade Transition */}
<div className="absolute bottom-0 left-0 w-full h-32 bg-gradient-to-b from-transparent to-gray-50 pointer-events-none" />
</section >
);
</p>
<div className="flex flex-col gap-1.5 pt-1">
{t.hero.features.map((feature: string, index: number) => (
<motion.div
key={index}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.2 + (index * 0.1) }}
className="flex items-center gap-2"
>
<div className="w-1 h-1 rounded-full bg-[#533afd] shrink-0" />
<span className="text-[13px] text-[#273951] font-medium">{feature}</span>
</motion.div>
))}
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5 }}
className="flex gap-4 items-center flex-wrap pt-1"
>
<Link href="/signup">
<Button size="lg" style={{ padding: '14px 36px', fontSize: '1.0625rem', boxShadow: 'rgba(83,58,253,0.3) 0px 10px 28px -8px' }}>
{t.hero.cta_primary}
</Button>
</Link>
<Link
href="/#pricing"
style={{ fontSize: '0.875rem', fontWeight: 500, color: '#533afd', textDecoration: 'underline', textUnderlineOffset: '3px' }}
>
{t.hero.cta_secondary}
</Link>
</motion.div>
</div>
{/* Right Preview Widget */}
<div className="relative">
<div className="relative perspective-[1000px]">
{/* impeccable-variants-start eb49f55c */}
<div data-impeccable-variants="eb49f55c" data-impeccable-variant-count="2" style={{ display: "contents" }}>
{/* Original */}
<div data-impeccable-variant="original">
<div className="grid grid-cols-2 gap-4">
{[
{
front: { title: 'URL/Website', color: 'bg-blue-500/10 text-blue-600', icon: Globe },
back: { title: 'PDF / Menu', color: 'bg-orange-500/10 text-orange-600', icon: FileText },
delay: 3
},
{
front: { title: 'Contact Card', color: 'bg-purple-500/10 text-purple-600', icon: User },
back: { title: 'Coupon / Deals', color: 'bg-red-500/10 text-red-600', icon: Ticket },
delay: 5
},
{
front: { title: 'Location', color: 'bg-green-500/10 text-green-600', icon: MapPin },
back: { title: 'App Store', color: 'bg-sky-500/10 text-sky-600', icon: Smartphone },
delay: 7
},
{
front: { title: 'Phone Number', color: 'bg-pink-500/10 text-pink-600', icon: Phone },
back: { title: 'Feedback', color: 'bg-yellow-500/10 text-yellow-600', icon: Star },
delay: 9
},
].map((card, index) => (
<FlippingCard key={index} {...card} />
))}
</div>
</div>
{/* Variants: insert below this line */}
<style data-impeccable-css="eb49f55c">{`
@scope ([data-impeccable-variant="1"]) {
.qr-instrument-grid {
--accent-strength: var(--p-accent-strength, 0.58);
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
padding: 10px;
border: 1px solid oklch(91% 0.018 260);
border-radius: 8px;
background:
linear-gradient(135deg, oklch(99% 0.006 260), oklch(96% 0.018 278)),
repeating-linear-gradient(90deg, oklch(93% 0.012 260) 0 1px, transparent 1px 44px);
box-shadow:
rgba(50, 50, 93, 0.24) 0 30px 45px -30px,
rgba(3, 3, 39, 0.10) 0 18px 36px -18px;
}
.qr-instrument-tile {
position: relative;
min-height: 126px;
overflow: hidden;
border: 1px solid oklch(88% 0.018 260);
border-radius: 6px;
background: oklch(99% 0.004 260 / 0.92);
color: oklch(22% 0.07 260);
box-shadow: rgba(23, 23, 23, 0.06) 0 3px 6px;
transition: transform 220ms cubic-bezier(0.25, 1, 0.5, 1), border-color 220ms cubic-bezier(0.25, 1, 0.5, 1), box-shadow 220ms cubic-bezier(0.25, 1, 0.5, 1);
}
.qr-instrument-tile:hover {
transform: translateY(-3px);
border-color: color-mix(in oklch, oklch(55% 0.25 280) calc(var(--accent-strength) * 100%), oklch(88% 0.018 260));
box-shadow:
rgba(50, 50, 93, 0.26) 0 24px 40px -26px,
rgba(83, 58, 253, calc(var(--accent-strength) * 0.16)) 0 10px 24px -16px;
}
.qr-instrument-face {
position: absolute;
inset: 0;
display: grid;
align-content: space-between;
padding: 14px;
}
.qr-instrument-code {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 10px;
line-height: 1;
color: oklch(55% 0.04 260);
font-variant-numeric: tabular-nums;
}
.qr-instrument-icon {
width: 36px;
height: 36px;
display: grid;
place-items: center;
border-radius: 6px;
color: oklch(52% 0.24 280);
background: color-mix(in oklch, oklch(62% 0.25 280) calc(var(--accent-strength) * 16%), oklch(97% 0.01 260));
}
.qr-instrument-label {
display: flex;
align-items: end;
justify-content: space-between;
gap: 10px;
font-size: 13px;
line-height: 1.15;
font-weight: 600;
color: oklch(20% 0.065 260);
}
.qr-instrument-spark {
width: 34px;
height: 12px;
background: linear-gradient(90deg, transparent 0 12%, oklch(58% 0.24 280 / calc(var(--accent-strength) * 0.55)) 12% 18%, transparent 18% 34%, oklch(58% 0.24 280 / calc(var(--accent-strength) * 0.75)) 34% 44%, transparent 44% 100%);
transform: skewX(-18deg);
}
.qr-instrument-tile:hover .qr-instrument-spark {
animation: qrInstrumentPulse 720ms cubic-bezier(0.25, 1, 0.5, 1);
}
@keyframes qrInstrumentPulse {
from { opacity: 0.25; transform: translateX(-4px) skewX(-18deg); }
to { opacity: 1; transform: translateX(0) skewX(-18deg); }
}
:scope[data-p-density="compact"] .qr-instrument-grid { gap: 8px; padding: 8px; }
:scope[data-p-density="compact"] .qr-instrument-tile { min-height: 114px; }
:scope[data-p-density="open"] .qr-instrument-grid { gap: 14px; padding: 12px; }
:scope[data-p-density="open"] .qr-instrument-tile { min-height: 136px; }
}
@scope ([data-impeccable-variant="2"]) {
.qr-rhythm-grid {
--motion-depth: var(--p-motion-depth, 0.45);
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.qr-rhythm-tile {
position: relative;
min-height: 126px;
overflow: hidden;
border: 1px solid oklch(90% 0.018 260);
border-radius: 8px;
background: oklch(99% 0.004 260 / 0.86);
box-shadow:
rgba(50, 50, 93, 0.18) 0 24px 34px -28px,
rgba(0, 0, 0, 0.08) 0 14px 24px -18px;
transition: transform 260ms cubic-bezier(0.25, 1, 0.5, 1), box-shadow 260ms cubic-bezier(0.25, 1, 0.5, 1);
}
.qr-rhythm-tile:hover {
transform: translateY(calc(var(--motion-depth) * -8px));
box-shadow:
rgba(50, 50, 93, 0.26) 0 30px 45px -30px,
rgba(83, 58, 253, 0.14) 0 16px 30px -22px;
}
.qr-rhythm-tile::before {
content: "";
position: absolute;
inset: 10px;
border-radius: 6px;
background:
linear-gradient(90deg, oklch(55% 0.25 280 / 0.10), transparent 42%),
repeating-linear-gradient(90deg, oklch(68% 0.16 280 / 0.32) 0 3px, transparent 3px 8px);
clip-path: inset(72% 0 10% 0);
opacity: calc(0.36 + var(--motion-depth) * 0.5);
}
.qr-rhythm-content {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 11px;
padding: 16px;
text-align: center;
}
.qr-rhythm-icon {
width: 42px;
height: 42px;
display: grid;
place-items: center;
border-radius: 8px;
color: oklch(48% 0.24 280);
background: oklch(96% 0.025 280);
box-shadow: inset oklch(100% 0.004 260 / 0.82) 0 1px 0;
transition: transform 260ms cubic-bezier(0.25, 1, 0.5, 1);
}
.qr-rhythm-tile:hover .qr-rhythm-icon {
transform: translateY(calc(var(--motion-depth) * -5px)) scale(calc(1 + var(--motion-depth) * 0.05));
}
.qr-rhythm-label {
color: oklch(20% 0.065 260);
font-size: 13px;
line-height: 1.12;
font-weight: 600;
}
.qr-rhythm-meta {
position: absolute;
left: 12px;
top: 10px;
color: oklch(58% 0.04 260);
font-size: 9px;
line-height: 1;
font-variant-numeric: tabular-nums;
letter-spacing: 0;
}
:scope[data-p-signal="quiet"] .qr-rhythm-tile::before { opacity: 0.18; }
:scope[data-p-signal="bright"] .qr-rhythm-tile::before { opacity: 0.78; }
@media (prefers-reduced-motion: reduce) {
.qr-rhythm-tile,
.qr-rhythm-icon,
.qr-instrument-tile,
.qr-instrument-spark {
animation: none;
transition: none;
}
}
}
`}</style>
<div
data-impeccable-variant="1"
data-impeccable-params='[
{"id":"accent-strength","kind":"range","min":0.25,"max":0.9,"step":0.05,"default":0.58,"label":"Accent strength"},
{"id":"density","kind":"steps","default":"balanced","label":"Density","options":[
{"value":"compact","label":"Compact"},
{"value":"balanced","label":"Balanced"},
{"value":"open","label":"Open"}
]}
]'
>
<div className="qr-instrument-grid">
{[
{ code: '01', title: 'URL/Website', icon: Globe },
{ code: '02', title: 'PDF / Menu', icon: FileText },
{ code: '03', title: 'Contact Card', icon: User },
{ code: '04', title: 'Coupon / Deals', icon: Ticket },
].map((card) => (
<div className="qr-instrument-tile" key={card.title}>
<div className="qr-instrument-face">
<div className="qr-instrument-code">
<span>QR-{card.code}</span>
<span>READY</span>
</div>
<div className="qr-instrument-icon">
<card.icon className="w-5 h-5" />
</div>
<div className="qr-instrument-label">
<span>{card.title}</span>
<span className="qr-instrument-spark" />
</div>
</div>
</div>
))}
</div>
</div>
<div
data-impeccable-variant="2"
style={{ display: 'none' }}
data-impeccable-params='[
{"id":"motion-depth","kind":"range","min":0,"max":1,"step":0.05,"default":0.45,"label":"Motion depth"},
{"id":"signal","kind":"steps","default":"calm","label":"Signal","options":[
{"value":"quiet","label":"Quiet"},
{"value":"calm","label":"Calm"},
{"value":"bright","label":"Bright"}
]}
]'
>
<div className="qr-rhythm-grid">
{[
{ code: 'URL', title: 'URL/Website', icon: Globe },
{ code: 'PDF', title: 'PDF / Menu', icon: FileText },
{ code: 'LOC', title: 'Location', icon: MapPin },
{ code: 'APP', title: 'App Store', icon: Smartphone },
].map((card) => (
<div className="qr-rhythm-tile" key={card.title}>
<span className="qr-rhythm-meta">{card.code}</span>
<div className="qr-rhythm-content">
<div className="qr-rhythm-icon">
<card.icon className="w-5 h-5" />
</div>
<span className="qr-rhythm-label">{card.title}</span>
</div>
</div>
))}
</div>
</div>
</div>
{/* impeccable-variants-end eb49f55c */}
</div>
</div>
</div>
</div>
<div className="absolute bottom-0 left-0 w-full h-32 bg-gradient-to-b from-transparent to-gray-50 pointer-events-none" />
</section>
);
};

View File

@@ -9,48 +9,41 @@ export const ReprintCalculatorTeaser: React.FC = () => {
return (
<section className="py-24 bg-white relative overflow-hidden">
<div className="container mx-auto px-4 relative z-10">
<div className="bg-gradient-to-br from-indigo-50 to-white border border-indigo-100 rounded-3xl p-8 md:p-12 lg:p-16 flex flex-col md:flex-row items-center justify-between gap-12 shadow-sm hover:shadow-md transition-shadow duration-500">
<div className="flex flex-col md:flex-row items-center justify-between gap-12 bg-white border border-[#e5edf5] rounded-lg p-8 md:p-12 lg:p-16" style={{boxShadow:'rgba(50,50,93,0.12) 0px 8px 20px -8px'}}>
<div className="flex-1 text-center md:text-left">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-indigo-100 text-indigo-700 text-sm font-medium mb-6">
<TrendingUp className="w-4 h-4" />
<div className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded border border-[rgba(83,58,253,0.15)] mb-5" style={{background:'oklch(0.94 0.02 270)',color:'#533afd',fontSize:11,fontWeight:500,letterSpacing:'0.06em',textTransform:'uppercase'}}>
<TrendingUp style={{width:12,height:12}} aria-hidden="true" />
<span>ROI Calculator</span>
</div>
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-slate-900 mb-6 leading-tight">
Are you burning budget on <br />
<span className="text-transparent bg-clip-text bg-gradient-to-r from-indigo-600 to-blue-600">Static Reprints?</span>
<h2 style={{fontSize:'clamp(1.875rem,4vw,3rem)',fontWeight:700,lineHeight:1.1,color:'#061b31',marginBottom:'1.25rem',letterSpacing:'-0.02em'}}>
Are you burning budget on{' '}
<span style={{color:'#533afd'}}>Static Reprints?</span>
</h2>
<p className="text-slate-600 text-lg md:text-xl max-w-xl mx-auto md:mx-0 leading-relaxed mb-8">
<p style={{color:'#64748d',fontSize:'1.0625rem',lineHeight:1.6,maxWidth:'52ch',margin:'0 auto 2rem'}}>
Find out exactly how much you can save by switching to dynamic QR codes. Our calculator reveals your savings potential in seconds.
</p>
<Link href="/reprint-calculator">
<Button
size="lg"
variant="primary"
className="h-14 px-8 text-lg hover:translate-x-1 transition-transform"
>
Calculate Savings <ArrowRight className="w-5 h-5 ml-2" />
<Button size="lg" className="h-14 px-8">
Calculate Savings <ArrowRight className="w-4 h-4 ml-2" aria-hidden="true" />
</Button>
</Link>
</div>
<div className="flex-shrink-0 w-full md:w-auto flex justify-center md:justify-end">
<div className="relative group">
<div className="absolute -inset-1 bg-gradient-to-r from-indigo-500 to-blue-500 rounded-2xl blur opacity-20 group-hover:opacity-40 transition duration-1000 group-hover:duration-200"></div>
<div className="relative bg-white rounded-xl p-8 border border-slate-100 w-full max-w-xs text-center shadow-lg">
<div className="w-16 h-16 bg-indigo-50 rounded-2xl flex items-center justify-center mx-auto mb-6 text-indigo-600">
<Calculator className="w-8 h-8" />
</div>
<h3 className="text-lg font-bold text-slate-900 mb-2">Cost Analysis</h3>
<p className="text-slate-500 text-sm mb-6">
Enter your print volume and update frequency to see your hidden costs.
</p>
<div className="h-1.5 w-full bg-slate-100 rounded-full overflow-hidden">
<div className="h-full w-2/3 bg-indigo-500 rounded-full animate-pulse"></div>
</div>
<div className="bg-white border border-[#e5edf5] rounded-lg p-7 w-full max-w-[260px] text-center" style={{boxShadow:'rgba(50,50,93,0.25) 0px 20px 40px -20px,rgba(0,0,0,0.08) 0px 10px 20px -10px'}}>
<div className="flex items-center justify-center mx-auto mb-4 text-[#533afd]" style={{width:44,height:44,background:'oklch(0.94 0.02 270)',borderRadius:6}}>
<Calculator style={{width:22,height:22}} aria-hidden="true" />
</div>
<h3 className="font-semibold text-[#061b31] mb-2" style={{fontSize:'0.9375rem'}}>Cost Analysis</h3>
<p className="text-[#64748d] leading-relaxed" style={{fontSize:'0.8125rem'}}>
Enter your print volume and update frequency to see your hidden costs.
</p>
<div className="mt-5 h-0.5 w-full bg-[#e5edf5] rounded-sm overflow-hidden">
<div className="h-full w-2/3 bg-[#533afd] rounded-sm" />
</div>
</div>
</div>