4 Commits

20 changed files with 890 additions and 628 deletions

41
ideen.md Normal file
View File

@@ -0,0 +1,41 @@
🚀 Neue Content-Typen
Feature Beschreibung
WiFi QR SSID, Passwort, Verschlüsselungstyp perfekt für Cafés/Hotels
Event (VEVENT) Kalendereinträge direkt ins Handy importieren
App Store Links Smart-Links die iOS/Android erkennen
PayPal/Bitcoin Zahlungsaufforderungen per QR
WhatsApp/Telegram Direkt-Chat mit vordefinierter Nachricht
📊 Analytics-Erweiterungen
Feature Beschreibung
UTM-Parameter Automatische Kampagnen-Tags für Google Analytics
Conversion Tracking Ziel-URLs definieren und Conversion messen
A/B Testing Zwei Ziel-URLs testen, welche besser performt
Scheduled Reports Wöchentliche/monatliche E-Mail-Reports
Export (CSV/PDF) Analytics-Daten exportieren
🎨 QR Design & Styling
Feature Beschreibung
Design Templates Vorgefertigte Farb-/Logo-Kombinationen
Frames & CTA "Scan me!" Rahmen um den QR Code
Dot Styles Runde Punkte, Diamanten, etc.
Eye Shapes Custom Corner-Marker Designs
Gradient Colors Farbverläufe statt Vollfarben
🗂️ Organisation & Teamwork
Feature Beschreibung
Folders/Projekte QR Codes in Ordner organisieren
Tags & Filter Flexibles Tagging-System
Team Workspaces Mehrere User pro Account (BUSINESS)
Activity Log Wer hat was wann geändert
QR Code Archiv Soft-Delete statt Löschen
⚙️ Pro Features
Feature Beschreibung
Passwortschutz QR führt zu Passwort-geschützter Seite
Ablaufdatum QR Code deaktiviert sich automatisch
Scan-Limit Max. X Scans erlauben
Geo-Targeting Verschiedene URLs je nach Standort
Device Detection Desktop vs. Mobile unterschiedliche URLs
🔌 Integrationen
Feature Beschreibung
Zapier/Make Webhooks bei Scans triggern
Google Sheets Scan-Daten automatisch exportieren
Slack Notifications Benachrichtigung bei X Scans
API für Entwickler Public API mit Token-Auth

43
package-lock.json generated
View File

@@ -21,6 +21,7 @@
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"exceljs": "^4.4.0", "exceljs": "^4.4.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"framer-motion": "^12.24.10",
"i18next": "^23.7.6", "i18next": "^23.7.6",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
"jszip": "^3.10.1", "jszip": "^3.10.1",
@@ -5098,6 +5099,33 @@
"url": "https://github.com/sponsors/rawify" "url": "https://github.com/sponsors/rawify"
} }
}, },
"node_modules/framer-motion": {
"version": "12.24.10",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.24.10.tgz",
"integrity": "sha512-8yoyMkCn2RmV9UB9mfmMuzKyenQe909hRQRl0yGBhbZJjZZ9bSU87NIGAruqCXCuTNCA0qHw2LWLrcXLL9GF6A==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.24.10",
"motion-utils": "^12.24.10",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/fs-constants": { "node_modules/fs-constants": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -6607,6 +6635,21 @@
"mkdirp": "bin/cmd.js" "mkdirp": "bin/cmd.js"
} }
}, },
"node_modules/motion-dom": {
"version": "12.24.10",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.24.10.tgz",
"integrity": "sha512-H3HStYaJ6wANoZVNT0ZmYZHGvrpvi9pKJRzsgNEHkdITR4Qd9FFu2e9sH4e2Phr4tKCmyyloex6SOSmv0Tlq+g==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.24.10"
}
},
"node_modules/motion-utils": {
"version": "12.24.10",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.24.10.tgz",
"integrity": "sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww==",
"license": "MIT"
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",

View File

@@ -37,6 +37,7 @@
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"exceljs": "^4.4.0", "exceljs": "^4.4.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"framer-motion": "^12.24.10",
"i18next": "^23.7.6", "i18next": "^23.7.6",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
"jszip": "^3.10.1", "jszip": "^3.10.1",

BIN
public/hero-fluid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

View File

@@ -141,13 +141,13 @@ export default function PricingPage() {
'50 dynamic QR codes', '50 dynamic QR codes',
'Unlimited static QR codes', 'Unlimited static QR codes',
'Advanced analytics (scans, devices, locations)', 'Advanced analytics (scans, devices, locations)',
'Custom branding (colors)', 'Custom branding (colors & logos)',
], ],
buttonText: isCurrentPlanWithInterval('PRO', selectedInterval) buttonText: isCurrentPlanWithInterval('PRO', selectedInterval)
? 'Current Plan' ? 'Current Plan'
: hasPlanDifferentInterval('PRO') : hasPlanDifferentInterval('PRO')
? `Switch to ${billingPeriod === 'month' ? 'Monthly' : 'Yearly'}` ? `Switch to ${billingPeriod === 'month' ? 'Monthly' : 'Yearly'}`
: 'Upgrade to Pro', : 'Upgrade to Pro',
buttonVariant: 'primary' as const, buttonVariant: 'primary' as const,
disabled: isCurrentPlanWithInterval('PRO', selectedInterval), disabled: isCurrentPlanWithInterval('PRO', selectedInterval),
popular: true, popular: true,
@@ -170,8 +170,8 @@ export default function PricingPage() {
buttonText: isCurrentPlanWithInterval('BUSINESS', selectedInterval) buttonText: isCurrentPlanWithInterval('BUSINESS', selectedInterval)
? 'Current Plan' ? 'Current Plan'
: hasPlanDifferentInterval('BUSINESS') : hasPlanDifferentInterval('BUSINESS')
? `Switch to ${billingPeriod === 'month' ? 'Monthly' : 'Yearly'}` ? `Switch to ${billingPeriod === 'month' ? 'Monthly' : 'Yearly'}`
: 'Upgrade to Business', : 'Upgrade to Business',
buttonVariant: 'primary' as const, buttonVariant: 'primary' as const,
disabled: isCurrentPlanWithInterval('BUSINESS', selectedInterval), disabled: isCurrentPlanWithInterval('BUSINESS', selectedInterval),
popular: false, popular: false,

View File

@@ -3,6 +3,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { Footer } from '@/components/ui/Footer';
import en from '@/i18n/en.json'; import en from '@/i18n/en.json';
export default function MarketingLayout({ export default function MarketingLayout({
@@ -11,6 +12,16 @@ export default function MarketingLayout({
children: React.ReactNode; children: React.ReactNode;
}) { }) {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [showStickyCTA, setShowStickyCTA] = useState(false);
React.useEffect(() => {
const handleScroll = () => {
setShowStickyCTA(window.scrollY > 400);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
// Always use English for marketing pages // Always use English for marketing pages
const t = en; const t = en;
@@ -25,7 +36,7 @@ export default function MarketingLayout({
return ( return (
<div className="min-h-screen bg-white"> <div className="min-h-screen bg-white">
{/* Header */} {/* Header */}
<header className="sticky top-0 z-50 bg-white border-b border-gray-200"> <header className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${showStickyCTA ? 'bg-white/60 backdrop-blur-xl shadow-sm border-b border-gray-200/40 supports-[backdrop-filter]:bg-white/60' : 'bg-transparent border-b border-transparent'}`}>
<nav className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl py-4"> <nav className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl py-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
{/* Logo */} {/* Logo */}
@@ -54,7 +65,11 @@ export default function MarketingLayout({
</Link> </Link>
<Link href="/signup"> <Link href="/signup">
<Button>Get Started Free</Button> {showStickyCTA ? (
<Button className="animate-in fade-in zoom-in duration-300">Create QR Code</Button>
) : (
<Button>Get Started Free</Button>
)}
</Link> </Link>
</div> </div>
@@ -102,63 +117,10 @@ export default function MarketingLayout({
</header> </header>
{/* Main Content */} {/* Main Content */}
<main>{children}</main> <main className="pt-20">{children}</main>
{/* Footer */} {/* Footer */}
<footer className="bg-gray-900 text-white py-12 mt-20"> <Footer />
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
<div className="grid md:grid-cols-4 gap-8">
<div>
<Link href="/" className="flex items-center space-x-2 mb-4 hover:opacity-80 transition-opacity">
<img src="/logo.svg" alt="QR Master" className="w-10 h-10" />
<span className="text-xl font-bold">QR Master</span>
</Link>
<p className="text-gray-400">
Create custom QR codes in seconds with advanced tracking and analytics.
</p>
</div>
<div>
<h3 className="font-semibold mb-4">Product</h3>
<ul className="space-y-2 text-gray-400">
<li><Link href="/#features" className="hover:text-white">Features</Link></li>
<li><Link href="/#pricing" className="hover:text-white">Pricing</Link></li>
<li><Link href="/#faq" className="hover:text-white">FAQ</Link></li>
<li><Link href="/blog" className="hover:text-white">Blog</Link></li>
</ul>
</div>
<div>
<h3 className="font-semibold mb-4">Resources</h3>
<ul className="space-y-2 text-gray-400">
<li><Link href="/#pricing" className="hover:text-white">Full Pricing</Link></li>
<li><Link href="/faq" className="hover:text-white">All Questions</Link></li>
<li><Link href="/blog" className="hover:text-white">Blog</Link></li>
<li><Link href="/signup" className="hover:text-white">Get Started</Link></li>
</ul>
</div>
<div>
<h3 className="font-semibold mb-4">Legal</h3>
<ul className="space-y-2 text-gray-400">
<li><Link href="/privacy" className="hover:text-white">Privacy Policy</Link></li>
</ul>
</div>
</div>
<div className="border-t border-gray-800 mt-8 pt-8 flex items-center justify-between text-gray-400">
<Link
href="/newsletter"
className="text-[6px] text-gray-700 opacity-[0.25] hover:opacity-100 hover:text-white transition-opacity duration-300"
>
</Link>
<p>&copy; 2025 QR Master. All rights reserved.</p>
<div className="w-12"></div>
</div>
</div>
</footer>
</div> </div>
); );
} }

View File

@@ -51,7 +51,7 @@ export default function CookieBanner() {
<p className="text-gray-600 text-sm leading-relaxed mb-3"> <p className="text-gray-600 text-sm leading-relaxed mb-3">
We use essential cookies for authentication and analytics cookies to improve your experience.{' '} We use essential cookies for authentication and analytics cookies to improve your experience.{' '}
<Link href="/privacy" className="text-primary-600 hover:text-primary-700 font-medium underline"> <Link href="/privacy" className="text-primary-600 hover:text-primary-700 font-medium underline">
Learn more Learn more about our privacy policy
</Link> </Link>
</p> </p>

View File

@@ -3,6 +3,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Sparkles, Brain, TrendingUp, MessageSquare, Palette, ArrowRight, Mail, CheckCircle2, Lock } from 'lucide-react'; import { Sparkles, Brain, TrendingUp, MessageSquare, Palette, ArrowRight, Mail, CheckCircle2, Lock } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
import { motion } from 'framer-motion';
const AIComingSoonBanner = () => { const AIComingSoonBanner = () => {
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
@@ -83,7 +84,7 @@ const AIComingSoonBanner = () => {
]; ];
return ( return (
<section className="relative overflow-hidden py-20 px-4 sm:px-6 lg:px-8 bg-gradient-to-br from-blue-50 via-white to-purple-50"> <section className="relative overflow-hidden pt-12 pb-20 px-4 sm:px-6 lg:px-8 bg-gradient-to-br from-blue-50 via-white to-purple-50">
{/* Animated Background Orbs (matching Hero) */} {/* Animated Background Orbs (matching Hero) */}
<div className="absolute inset-0 overflow-hidden pointer-events-none"> <div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute top-0 left-1/4 w-96 h-96 bg-blue-400/20 rounded-full blur-3xl animate-blob" /> <div className="absolute top-0 left-1/4 w-96 h-96 bg-blue-400/20 rounded-full blur-3xl animate-blob" />
@@ -93,8 +94,15 @@ const AIComingSoonBanner = () => {
<div className="max-w-6xl mx-auto relative z-10"> <div className="max-w-6xl mx-auto relative z-10">
{/* Header */} {/* Header */}
<div className="text-center mb-12"> {/* Header */}
<div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-blue-100 mb-4"> <motion.div
initial={{ opacity: 0, scale: 0.95 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="text-center mb-12"
>
<div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-blue-100 mb-4 animate-pulse">
<Sparkles className="w-4 h-4 text-blue-600" /> <Sparkles className="w-4 h-4 text-blue-600" />
<span className="text-sm font-medium text-blue-700"> <span className="text-sm font-medium text-blue-700">
Coming Soon Coming Soon
@@ -111,14 +119,18 @@ const AIComingSoonBanner = () => {
<p className="text-gray-600 text-lg max-w-2xl mx-auto"> <p className="text-gray-600 text-lg max-w-2xl mx-auto">
Revolutionary AI features to transform how you create, manage, and optimize QR codes Revolutionary AI features to transform how you create, manage, and optimize QR codes
</p> </p>
</div> </motion.div>
{/* Features Grid */} {/* Features Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
{features.map((feature, index) => ( {features.map((feature, index) => (
<div <motion.div
key={index} key={index}
className="bg-white/80 backdrop-blur rounded-xl p-6 border border-gray-100 hover:shadow-lg transition-all" initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
className="bg-white/80 backdrop-blur rounded-xl p-6 border border-gray-100 hover:shadow-lg transition-all hover:scale-105"
> >
<div className="w-12 h-12 bg-gradient-to-br from-blue-100 to-purple-100 rounded-lg flex items-center justify-center mb-4"> <div className="w-12 h-12 bg-gradient-to-br from-blue-100 to-purple-100 rounded-lg flex items-center justify-center mb-4">
<feature.icon className="w-6 h-6 text-blue-600" /> <feature.icon className="w-6 h-6 text-blue-600" />
@@ -136,12 +148,18 @@ const AIComingSoonBanner = () => {
</li> </li>
))} ))}
</ul> </ul>
</div> </motion.div>
))} ))}
</div> </div>
{/* Email Capture */} {/* Email Capture */}
<div className="max-w-2xl mx-auto bg-gradient-to-br from-blue-50 to-purple-50 rounded-2xl p-8 border border-gray-100"> <motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.4 }}
className="max-w-2xl mx-auto bg-gradient-to-br from-blue-50 to-purple-50 rounded-2xl p-8 border border-gray-100"
>
{!submitted ? ( {!submitted ? (
<> <>
<form onSubmit={handleSubmit} className="flex flex-col sm:flex-row gap-3 mb-3"> <form onSubmit={handleSubmit} className="flex flex-col sm:flex-row gap-3 mb-3">
@@ -163,12 +181,12 @@ const AIComingSoonBanner = () => {
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-semibold rounded-xl transition-all disabled:opacity-50 whitespace-nowrap flex items-center justify-center gap-2" className="px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-semibold rounded-xl transition-all disabled:opacity-50 whitespace-nowrap flex items-center justify-center gap-2 group"
> >
{loading ? 'Subscribing...' : ( {loading ? 'Subscribing...' : (
<> <>
Notify Me Notify Me
<ArrowRight className="w-4 h-4" /> <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
</> </>
)} )}
</button> </button>
@@ -188,8 +206,9 @@ const AIComingSoonBanner = () => {
</span> </span>
</div> </div>
)} )}
</div> </motion.div>
</div> </div>
<div className="absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-b from-transparent to-white pointer-events-none" />
</section> </section>
); );
}; };

View File

@@ -1,6 +1,7 @@
'use client'; 'use client';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Card } from '@/components/ui/Card'; import { Card } from '@/components/ui/Card';
interface FAQProps { interface FAQProps {
@@ -21,38 +22,62 @@ export const FAQ: React.FC<FAQProps> = ({ t }) => {
return ( return (
<section id="faq" className="py-16 bg-gray-50"> <section id="faq" className="py-16 bg-gray-50">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl"> <div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
<div className="text-center mb-12"> <motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="text-center mb-12"
>
<h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4"> <h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4">
{t.faq.title} {t.faq.title}
</h2> </h2>
</div> </motion.div>
<div className="max-w-3xl mx-auto space-y-4"> <div className="max-w-3xl mx-auto space-y-4">
{questions.map((key, index) => ( {questions.map((key, index) => (
<Card key={key} className="cursor-pointer" onClick={() => setOpenIndex(openIndex === index ? null : index)}> <motion.div
<div className="p-6"> key={key}
<div className="flex items-center justify-between"> initial={{ opacity: 0, x: -20 }}
<h3 className="text-lg font-semibold text-gray-900"> whileInView={{ opacity: 1, x: 0 }}
{t.faq.questions[key].question} viewport={{ once: true }}
</h3> transition={{ duration: 0.5, delay: index * 0.1 }}
<svg >
className={`w-5 h-5 text-gray-500 transition-transform ${openIndex === index ? 'rotate-180' : ''}`} <Card className="cursor-pointer border-gray-200 hover:border-gray-300 transition-colors" onClick={() => setOpenIndex(openIndex === index ? null : index)}>
fill="none" <div className="p-6">
stroke="currentColor" <div className="flex items-center justify-between">
viewBox="0 0 24 24" <h3 className="text-lg font-semibold text-gray-900">
aria-hidden="true" {t.faq.questions[key].question}
> </h3>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /> <svg
</svg> className={`w-5 h-5 text-gray-500 transition-transform duration-300 ${openIndex === index ? 'rotate-180' : ''}`}
</div> fill="none"
stroke="currentColor"
{openIndex === index && ( viewBox="0 0 24 24"
<div className="mt-4 text-gray-600"> aria-hidden="true"
{t.faq.questions[key].answer} >
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div> </div>
)}
</div> <AnimatePresence>
</Card> {openIndex === index && (
<motion.div
initial={{ height: 0, opacity: 0, marginTop: 0 }}
animate={{ height: 'auto', opacity: 1, marginTop: 16 }}
exit={{ height: 0, opacity: 0, marginTop: 0 }}
transition={{ duration: 0.3 }}
className="overflow-hidden"
>
<div className="text-gray-600">
{t.faq.questions[key].answer}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</Card>
</motion.div>
))} ))}
</div> </div>

View File

@@ -1,6 +1,7 @@
'use client'; 'use client';
import React from 'react'; import React from 'react';
import { motion } from 'framer-motion';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
interface FeaturesProps { interface FeaturesProps {
@@ -41,27 +42,41 @@ export const Features: React.FC<FeaturesProps> = ({ t }) => {
return ( return (
<section className="py-16 bg-gray-50"> <section className="py-16 bg-gray-50">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl"> <div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
<div className="text-center mb-12"> <motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="text-center mb-12"
>
<h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4"> <h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4">
{t.features.title} {t.features.title}
</h2> </h2>
</div> </motion.div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto"> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
{features.map((feature) => ( {features.map((feature, index) => (
<Card key={feature.key} hover> <motion.div
<CardHeader> key={feature.key}
<div className={`w-12 h-12 rounded-lg ${feature.color} flex items-center justify-center mb-4`}> initial={{ opacity: 0, y: 20 }}
{feature.icon} whileInView={{ opacity: 1, y: 0 }}
</div> viewport={{ once: true }}
<CardTitle>{t.features[feature.key].title}</CardTitle> transition={{ duration: 0.5, delay: index * 0.1 }}
</CardHeader> >
<CardContent> <Card hover className="h-full border-gray-100 hover:border-primary-100 hover:shadow-lg transition-all">
<p className="text-gray-600"> <CardHeader>
{t.features[feature.key].description} <div className={`w-12 h-12 rounded-lg ${feature.color} flex items-center justify-center mb-4`}>
</p> {feature.icon}
</CardContent> </div>
</Card> <CardTitle>{t.features[feature.key].title}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-gray-600">
{t.features[feature.key].description}
</p>
</CardContent>
</Card>
</motion.div>
))} ))}
</div> </div>
</div> </div>

View File

@@ -5,6 +5,8 @@ import Link from 'next/link';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge'; import { Badge } from '@/components/ui/Badge';
import { Card } from '@/components/ui/Card'; import { Card } from '@/components/ui/Card';
import { motion } from 'framer-motion';
import { Globe, User, MapPin, Phone, CheckCircle2, ArrowRight } from 'lucide-react';
interface HeroProps { interface HeroProps {
t: any; // i18n translation function t: any; // i18n translation function
@@ -12,12 +14,27 @@ interface HeroProps {
export const Hero: React.FC<HeroProps> = ({ t }) => { export const Hero: React.FC<HeroProps> = ({ t }) => {
const templateCards = [ const templateCards = [
{ title: 'URL/Website', color: 'bg-blue-100', icon: '🌐' }, { title: 'URL/Website', color: 'bg-blue-500/10 text-blue-600', icon: Globe },
{ title: 'Contact Card', color: 'bg-purple-100', icon: '👤' }, { title: 'Contact Card', color: 'bg-purple-500/10 text-purple-600', icon: User },
{ title: 'Location', color: 'bg-green-100', icon: '📍' }, { title: 'Location', color: 'bg-green-500/10 text-green-600', icon: MapPin },
{ title: 'Phone Number', color: 'bg-pink-100', icon: '📞' }, { title: 'Phone Number', color: 'bg-pink-500/10 text-pink-600', icon: Phone },
]; ];
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 ( return (
<section className="relative overflow-hidden bg-gradient-to-br from-blue-50 via-white to-purple-50 pt-12 pb-20"> <section className="relative overflow-hidden bg-gradient-to-br from-blue-50 via-white to-purple-50 pt-12 pb-20">
{/* Animated Background Orbs */} {/* Animated Background Orbs */}
@@ -43,64 +60,96 @@ export const Hero: React.FC<HeroProps> = ({ t }) => {
<span>{t.hero.badge}</span> <span>{t.hero.badge}</span>
</Badge> </Badge>
<div className="space-y-6"> <motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="space-y-6"
>
<h1 className="text-5xl lg:text-6xl font-bold text-gray-900 leading-tight"> <h1 className="text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
{t.hero.title} {t.hero.title}
</h1> </h1>
<p className="text-xl text-gray-600 leading-relaxed"> <p className="text-xl text-gray-600 leading-relaxed max-w-2xl">
{t.hero.subtitle} {t.hero.subtitle}
</p> </p>
<div className="space-y-3"> <div className="space-y-3 pt-2">
{t.hero.features.map((feature: string, index: number) => ( {t.hero.features.map((feature: string, index: number) => (
<div key={index} className="flex items-center space-x-3"> <motion.div
<div className="flex-shrink-0 w-5 h-5 bg-success-500 rounded-full flex items-center justify-center"> key={index}
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20"> initial={{ opacity: 0, x: -20 }}
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" /> animate={{ opacity: 1, x: 0 }}
</svg> 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> </div>
<span className="text-gray-700">{feature}</span> <span className="text-gray-700 font-medium">{feature}</span>
</div> </motion.div>
))} ))}
</div> </div>
</motion.div>
<div className="flex flex-col sm:flex-row gap-4"> <motion.div
<Link href="/signup"> initial={{ opacity: 0, y: 20 }}
<Button size="lg" className="text-lg px-8 py-4 w-full sm:w-auto"> animate={{ opacity: 1, y: 0 }}
{t.hero.cta_primary} transition={{ delay: 0.5 }}
</Button> className="flex flex-col sm:flex-row gap-4 pt-4"
</Link> >
<Link href="/#pricing"> <Link href="/signup">
<Button variant="outline" size="lg" className="text-lg px-8 py-4 w-full sm:w-auto"> <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_secondary} {t.hero.cta_primary}
</Button> </Button>
</Link> </Link>
</div> <Link href="/#pricing">
</div> <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> </div>
{/* Right Preview Widget */} {/* Right Preview Widget */}
<div className="relative"> <div className="relative">
<div className="grid grid-cols-2 gap-4"> <motion.div
variants={containerjs}
initial="hidden"
animate="show"
className="grid grid-cols-2 gap-4"
>
{templateCards.map((card, index) => ( {templateCards.map((card, index) => (
<Card key={index} className={`${card.color} border-0 p-6 text-center hover:scale-105 transition-transform`}> <motion.div key={index} variants={itemjs}>
<div className="text-3xl mb-2">{card.icon}</div> <Card className={`backdrop-blur-xl bg-white/70 border-white/50 shadow-xl shadow-gray-200/50 p-6 text-center hover:scale-105 transition-all duration-300 group cursor-pointer`}>
<p className="font-semibold text-gray-800">{card.title}</p> <div className={`w-12 h-12 mx-auto mb-4 rounded-xl ${card.color} flex items-center justify-center group-hover:scale-110 transition-transform duration-300`}>
</Card> <card.icon className="w-6 h-6" />
</div>
<p className="font-semibold text-gray-800 group-hover:text-gray-900">{card.title}</p>
</Card>
</motion.div>
))} ))}
</div> </motion.div>
{/* Floating Badge */} {/* Floating Badge */}
<div className="absolute -top-4 -right-4 bg-success-500 text-white px-4 py-2 rounded-full text-sm font-semibold shadow-lg"> <motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.8 }}
className="absolute -top-4 -right-4 bg-gradient-to-r from-success-500 to-emerald-500 text-white px-4 py-2 rounded-full text-sm font-semibold shadow-lg shadow-success-500/30 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>
{t.hero.engagement_badge} {t.hero.engagement_badge}
</div> </motion.div>
</div> </div>
</div> </div>
</div> </div>
{/* Smooth Gradient Fade Transition */} {/* 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" /> <div className="absolute bottom-0 left-0 w-full h-32 bg-gradient-to-b from-transparent to-gray-50 pointer-events-none" />
</section> </section >
); );
}; };

View File

@@ -30,8 +30,12 @@ export default function HomePageClient() {
return ( return (
<> <>
<Hero t={t} /> <Hero t={t} />
<AIComingSoonBanner />
{/* Main Interaction: Generator */}
<InstantGenerator t={t} /> <InstantGenerator t={t} />
<AIComingSoonBanner />
<StaticVsDynamic t={t} /> <StaticVsDynamic t={t} />
<Features t={t} /> <Features t={t} />

View File

@@ -2,6 +2,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { QRCodeSVG } from 'qrcode.react'; import { QRCodeSVG } from 'qrcode.react';
import { motion } from 'framer-motion';
import { Card } from '@/components/ui/Card'; import { Card } from '@/components/ui/Card';
import { Input } from '@/components/ui/Input'; import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
@@ -73,139 +74,187 @@ export const InstantGenerator: React.FC<InstantGeneratorProps> = ({ t }) => {
}; };
return ( return (
<section className="py-16 bg-gray-50"> <section className="pt-16 pb-32 bg-gray-50 border-t border-gray-100 relative">
<div
className="absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-r from-blue-50 to-white pointer-events-none"
style={{ maskImage: 'linear-gradient(to bottom, transparent, black)', WebkitMaskImage: 'linear-gradient(to bottom, transparent, black)' }}
/>
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl"> <div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
<div className="text-center mb-12"> <motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="text-center mb-12"
>
<h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4"> <h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4">
{t.generator.title} {t.generator.title}
</h2> </h2>
</div> </motion.div>
<div className="grid lg:grid-cols-2 gap-12 max-w-6xl mx-auto"> <div className="grid lg:grid-cols-2 gap-12 max-w-6xl mx-auto">
{/* Left Form */} {/* Left Form */}
<Card className="space-y-6"> <motion.div
<Input initial={{ opacity: 0, x: -20 }}
label="URL" whileInView={{ opacity: 1, x: 0 }}
value={url} viewport={{ once: true }}
onChange={(e) => setUrl(e.target.value)} transition={{ duration: 0.5, delay: 0.2 }}
placeholder={t.generator.url_placeholder} >
/> <Card className="space-y-6 shadow-xl shadow-gray-200/50 border-gray-100">
<Input
label="URL"
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder={t.generator.url_placeholder}
className="transition-all focus:ring-2 focus:ring-primary-500/20"
/>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label htmlFor="foreground-color" className="block text-sm font-medium text-gray-700 mb-2"> <label htmlFor="foreground-color" className="block text-sm font-medium text-gray-700 mb-2">
{t.generator.foreground} {t.generator.foreground}
</label> </label>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<input <input
id="foreground-color" id="foreground-color"
type="color" type="color"
value={foregroundColor} value={foregroundColor}
onChange={(e) => setForegroundColor(e.target.value)} onChange={(e) => setForegroundColor(e.target.value)}
className="w-12 h-10 rounded border border-gray-300" className="w-14 h-12 rounded border border-gray-300 cursor-pointer"
aria-label="Foreground color picker" aria-label="Foreground color picker"
/> />
<Input <Input
id="foreground-color-text" id="foreground-color-text"
value={foregroundColor} value={foregroundColor}
onChange={(e) => setForegroundColor(e.target.value)} onChange={(e) => setForegroundColor(e.target.value)}
className="flex-1" className="flex-1"
aria-label="Foreground color hex value" aria-label="Foreground color hex value"
/> />
</div>
</div>
<div>
<label htmlFor="background-color" className="block text-sm font-medium text-gray-700 mb-2">
{t.generator.background}
</label>
<div className="flex items-center space-x-2">
<input
id="background-color"
type="color"
value={backgroundColor}
onChange={(e) => setBackgroundColor(e.target.value)}
className="w-14 h-12 rounded border border-gray-300 cursor-pointer"
aria-label="Background color picker"
/>
<Input
id="background-color-text"
value={backgroundColor}
onChange={(e) => setBackgroundColor(e.target.value)}
className="flex-1"
aria-label="Background color hex value"
/>
</div>
</div> </div>
</div> </div>
<div> <div className="grid grid-cols-2 gap-4">
<label htmlFor="background-color" className="block text-sm font-medium text-gray-700 mb-2"> <div>
{t.generator.background} <label htmlFor="corner-style" className="block text-sm font-medium text-gray-700 mb-2">
</label> {t.generator.corners}
<div className="flex items-center space-x-2"> </label>
<select
id="corner-style"
value={cornerStyle}
onChange={(e) => setCornerStyle(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
>
<option value="square">Square</option>
<option value="rounded">Rounded</option>
</select>
</div>
<div>
<label htmlFor="qr-size" className="block text-sm font-medium text-gray-700 mb-2">
{t.generator.size}
</label>
<input <input
id="background-color" id="qr-size"
type="color" type="range"
value={backgroundColor} min="100"
onChange={(e) => setBackgroundColor(e.target.value)} max="400"
className="w-12 h-10 rounded border border-gray-300" value={size}
aria-label="Background color picker" onChange={(e) => setSize(Number(e.target.value))}
/> className="w-full accent-primary-600"
<Input aria-label={`QR code size: ${size} pixels`}
id="background-color-text"
value={backgroundColor}
onChange={(e) => setBackgroundColor(e.target.value)}
className="flex-1"
aria-label="Background color hex value"
/> />
<div className="text-sm text-gray-500 text-center mt-1" aria-hidden="true">{size}px</div>
</div> </div>
</div> </div>
</div>
<div className="grid grid-cols-2 gap-4"> <div className="flex items-center justify-between">
<div> <Badge variant={hasGoodContrast ? 'success' : 'warning'}>
<label htmlFor="corner-style" className="block text-sm font-medium text-gray-700 mb-2"> {hasGoodContrast ? t.generator.contrast_good : 'Low contrast'}
{t.generator.corners} </Badge>
</label> <div className="text-sm text-gray-500">
<select Contrast: {contrast.toFixed(1)}:1
id="corner-style" </div>
value={cornerStyle}
onChange={(e) => setCornerStyle(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
>
<option value="square">Square</option>
<option value="rounded">Rounded</option>
</select>
</div> </div>
<div> <div className="flex space-x-3">
<label htmlFor="qr-size" className="block text-sm font-medium text-gray-700 mb-2"> <Button variant="outline" className="flex-1 hover:bg-gray-50" onClick={() => downloadQR('svg')}>
{t.generator.size} {t.generator.download_svg}
</label> </Button>
<input <Button variant="outline" className="flex-1 hover:bg-gray-50" onClick={() => downloadQR('png')}>
id="qr-size" {t.generator.download_png}
type="range" </Button>
min="100"
max="400"
value={size}
onChange={(e) => setSize(Number(e.target.value))}
className="w-full"
aria-label={`QR code size: ${size} pixels`}
/>
<div className="text-sm text-gray-500 text-center mt-1" aria-hidden="true">{size}px</div>
</div> </div>
</div>
<div className="flex items-center justify-between"> <Button className="w-full text-lg py-6 shadow-lg shadow-primary-500/20 hover:shadow-primary-500/40 transition-all" onClick={() => window.location.href = '/login'}>
<Badge variant={hasGoodContrast ? 'success' : 'warning'}> {t.generator.save_track}
{hasGoodContrast ? t.generator.contrast_good : 'Low contrast'}
</Badge>
<div className="text-sm text-gray-500">
Contrast: {contrast.toFixed(1)}:1
</div>
</div>
<div className="flex space-x-3">
<Button variant="outline" className="flex-1" onClick={() => downloadQR('svg')}>
{t.generator.download_svg}
</Button> </Button>
<Button variant="outline" className="flex-1" onClick={() => downloadQR('png')}> </Card>
{t.generator.download_png} </motion.div>
</Button>
</div>
<Button className="w-full" onClick={() => window.location.href = '/login'}>
{t.generator.save_track}
</Button>
</Card>
{/* Right Preview */} {/* Right Preview */}
<div className="flex flex-col items-center justify-center"> <motion.div
<Card className="text-center p-8"> initial={{ opacity: 0, x: 20 }}
<h3 className="text-lg font-semibold mb-4">{t.generator.live_preview}</h3> whileInView={{ opacity: 1, x: 0 }}
<div id="instant-qr-preview" className="flex justify-center mb-4"> viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.4 }}
className="flex flex-col items-center justify-center p-8 bg-gradient-to-br from-blue-50/50 to-purple-50/50 rounded-2xl border border-blue-100/50 shadow-lg shadow-blue-500/5 relative overflow-hidden backdrop-blur-sm"
>
{/* Artistic Curved Lines Background */}
<div className="absolute inset-0 opacity-[0.4]">
<svg className="h-full w-full" viewBox="0 0 100 100" preserveAspectRatio="none">
<defs>
<linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#60a5fa" stopOpacity="0.4" />
<stop offset="100%" stopColor="#c084fc" stopOpacity="0.4" />
</linearGradient>
<linearGradient id="gradient2" x1="100%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor="#818cf8" stopOpacity="0.4" />
<stop offset="100%" stopColor="#38bdf8" stopOpacity="0.4" />
</linearGradient>
</defs>
<path d="M0 100 Q 25 30 50 70 T 100 0" fill="none" stroke="url(#gradient1)" strokeWidth="0.8" className="opacity-60" />
<path d="M0 50 Q 40 80 70 30 T 100 50" fill="none" stroke="url(#gradient2)" strokeWidth="0.8" className="opacity-60" />
<path d="M0 0 Q 30 60 60 20 T 100 80" fill="none" stroke="url(#gradient1)" strokeWidth="0.6" className="opacity-40" />
</svg>
<div className="absolute inset-0 bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-15 brightness-100 contrast-150 mix-blend-overlay"></div>
</div>
{/* Decorative Orbs */}
<div className="absolute -top-20 -right-20 w-64 h-64 bg-purple-200/30 rounded-full blur-3xl animate-blob"></div>
<div className="absolute -bottom-20 -left-20 w-64 h-64 bg-blue-200/30 rounded-full blur-3xl animate-blob animation-delay-2000"></div>
<div className="text-center w-full relative z-10">
<h3 className="text-xl font-bold mb-8 text-gray-800">{t.generator.live_preview}</h3>
<div id="instant-qr-preview" className="flex justify-center mb-8 transform hover:scale-105 transition-transform duration-300">
{url ? ( {url ? (
<div className={`${cornerStyle === 'rounded' ? 'rounded-lg overflow-hidden' : ''}`}> <div className={`${cornerStyle === 'rounded' ? 'rounded-lg overflow-hidden' : ''} p-4 bg-white shadow-lg rounded-xl`}>
<QRCodeSVG <QRCodeSVG
value={url} value={url}
size={Math.min(size, 200)} size={size}
fgColor={foregroundColor} fgColor={foregroundColor}
bgColor={backgroundColor} bgColor={backgroundColor}
level="M" level="M"
@@ -213,17 +262,19 @@ export const InstantGenerator: React.FC<InstantGeneratorProps> = ({ t }) => {
</div> </div>
) : ( ) : (
<div <div
className="bg-gray-200 flex items-center justify-center text-gray-500" className="bg-gray-100 rounded-xl flex items-center justify-center text-gray-500 animate-pulse"
style={{ width: 200, height: 200 }} style={{ width: 200, height: 200 }}
> >
Enter URL Enter URL
</div> </div>
)} )}
</div> </div>
<div className="text-sm text-gray-600 mb-2">URL</div> <div className="text-sm font-medium text-gray-600 mb-2 bg-gray-50 py-2 px-4 rounded-full inline-block">
<div className="text-xs text-gray-500">{t.generator.demo_note}</div> {url || 'https://example.com'}
</Card> </div>
</div> <div className="text-xs text-gray-400 mt-2">{t.generator.demo_note}</div>
</div>
</motion.div>
</div> </div>
</div> </div>
</section> </section>

View File

@@ -1,6 +1,7 @@
'use client'; 'use client';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { motion } from 'framer-motion';
import Link from 'next/link'; import Link from 'next/link';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
@@ -32,87 +33,106 @@ export const Pricing: React.FC<PricingProps> = ({ t }) => {
return ( return (
<section id="pricing" className="py-16"> <section id="pricing" className="py-16">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl"> <div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
<div className="text-center mb-12"> <motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="text-center mb-12"
>
<h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4"> <h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4">
{t.pricing.title} {t.pricing.title}
</h2> </h2>
<p className="text-xl text-gray-600"> <p className="text-xl text-gray-600">
{t.pricing.subtitle} {t.pricing.subtitle}
</p> </p>
</div> </motion.div>
<div className="flex justify-center mb-8"> <div className="flex justify-center mb-8">
<BillingToggle value={billingPeriod} onChange={setBillingPeriod} /> <BillingToggle value={billingPeriod} onChange={setBillingPeriod} />
</div> </div>
<div className="grid md:grid-cols-3 gap-8 max-w-5xl mx-auto"> <div className="grid md:grid-cols-3 gap-8 max-w-5xl mx-auto">
{plans.map((plan) => ( {plans.map((plan, index) => (
<Card <motion.div
key={plan.key} key={plan.key}
className={plan.popular ? 'border-primary-500 shadow-xl relative' : ''} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
className="h-full"
> >
{plan.popular && ( <Card
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2"> className={`h-full flex flex-col ${plan.popular
<Badge variant="info" className="px-3 py-1"> ? 'border-primary-500 shadow-xl relative scale-105 z-10'
{t.pricing[plan.key].badge} : 'border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all'
</Badge> }`}
</div> >
)} {plan.popular && (
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2 w-full text-center">
<CardHeader className="text-center pb-8"> <Badge variant="info" className="px-4 py-1.5 shadow-sm">
<CardTitle className="text-2xl mb-4"> {t.pricing[plan.key].badge}
{t.pricing[plan.key].title}
</CardTitle>
<div className="flex flex-col items-center">
<div className="flex items-baseline justify-center">
<span className="text-4xl font-bold">
{plan.key === 'free'
? t.pricing[plan.key].price
: billingPeriod === 'month'
? t.pricing[plan.key].price
: plan.key === 'pro'
? '€90'
: '€290'}
</span>
<span className="text-gray-600 ml-2">
{plan.key === 'free'
? t.pricing[plan.key].period
: billingPeriod === 'month'
? t.pricing[plan.key].period
: 'per year'}
</span>
</div>
{billingPeriod === 'year' && plan.key !== 'free' && (
<Badge variant="success" className="mt-2">
Save 16%
</Badge> </Badge>
)} </div>
</div> )}
</CardHeader>
<CardContent className="space-y-4"> <CardHeader className="text-center pb-8">
<ul className="space-y-3"> <CardTitle className="text-2xl mb-4">
{t.pricing[plan.key].features.map((feature: string, index: number) => ( {t.pricing[plan.key].title}
<li key={index} className="flex items-start space-x-3"> </CardTitle>
<svg className="w-5 h-5 text-success-500 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20"> <div className="flex flex-col items-center">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" /> <div className="flex items-baseline justify-center">
</svg> <span className="text-4xl font-bold">
<span className="text-gray-700">{feature}</span> {plan.key === 'free'
</li> ? t.pricing[plan.key].price
))} : billingPeriod === 'month'
</ul> ? t.pricing[plan.key].price
: plan.key === 'pro'
? '€90'
: '€290'}
</span>
<span className="text-gray-600 ml-2">
{plan.key === 'free'
? t.pricing[plan.key].period
: billingPeriod === 'month'
? t.pricing[plan.key].period
: 'per year'}
</span>
</div>
{billingPeriod === 'year' && plan.key !== 'free' && (
<Badge variant="success" className="mt-2">
Save 16%
</Badge>
)}
</div>
</CardHeader>
<Link href="/signup"> <CardContent className="space-y-8 flex-1 flex flex-col">
<Button <ul className="space-y-3 flex-1">
variant={plan.popular ? 'primary' : 'outline'} {t.pricing[plan.key].features.map((feature: string, index: number) => (
className="w-full" <li key={index} className="flex items-start space-x-3">
size="lg" <svg className="w-5 h-5 text-success-500 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
> <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
Get Started </svg>
</Button> <span className="text-gray-700">{feature}</span>
</Link> </li>
</CardContent> ))}
</Card> </ul>
<div className="mt-8 pt-8 border-t border-gray-100">
<Link href="/signup">
<Button
variant={plan.popular ? 'primary' : 'outline'}
className="w-full"
size="lg"
>
Get Started
</Button>
</Link>
</div>
</CardContent>
</Card>
</motion.div>
))} ))}
</div> </div>
</div> </div>

View File

@@ -1,6 +1,8 @@
'use client'; 'use client';
import React from 'react'; import React from 'react';
import { motion } from 'framer-motion';
import { CheckCircle2 } from 'lucide-react';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge'; import { Badge } from '@/components/ui/Badge';
@@ -12,56 +14,83 @@ export const StaticVsDynamic: React.FC<StaticVsDynamicProps> = ({ t }) => {
return ( return (
<section className="py-16"> <section className="py-16">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl"> <div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold text-gray-900 sm:text-4xl mb-4">
{t.static_vs_dynamic.title}
</h2>
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
{t.static_vs_dynamic.description}
</p>
</div>
<div className="grid lg:grid-cols-2 gap-8 max-w-6xl mx-auto"> <div className="grid lg:grid-cols-2 gap-8 max-w-6xl mx-auto">
{/* Static QR Codes */} {/* Static QR Codes */}
<Card className="relative"> <motion.div
<CardHeader> initial={{ opacity: 0, x: -20 }}
<div className="flex items-center justify-between"> whileInView={{ opacity: 1, x: 0 }}
<CardTitle className="text-2xl">{t.static_vs_dynamic.static.title}</CardTitle> viewport={{ once: true }}
<Badge variant="success">{t.static_vs_dynamic.static.subtitle}</Badge> transition={{ duration: 0.5, delay: 0.2 }}
</div> >
<p className="text-gray-600">{t.static_vs_dynamic.static.description}</p> <Card className="relative h-full border-gray-200 shadow-sm hover:shadow-lg transition-all duration-300">
</CardHeader> <CardHeader>
<CardContent> <div className="flex items-center justify-between mb-2">
<ul className="space-y-3"> <CardTitle className="text-2xl font-bold text-gray-700">{t.static_vs_dynamic.static.title}</CardTitle>
{t.static_vs_dynamic.static.features.map((feature: string, index: number) => ( <Badge variant="success" className="bg-gray-100 text-gray-700 hover:bg-gray-200">{t.static_vs_dynamic.static.subtitle}</Badge>
<li key={index} className="flex items-center space-x-3"> </div>
<div className="flex-shrink-0 w-5 h-5 bg-gray-400 rounded-full flex items-center justify-center"> <p className="text-gray-500">{t.static_vs_dynamic.static.description}</p>
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20"> </CardHeader>
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" /> <CardContent>
</svg> <ul className="space-y-4">
</div> {t.static_vs_dynamic.static.features.map((feature: string, index: number) => (
<span className="text-gray-700">{feature}</span> <li key={index} className="flex items-start space-x-3">
</li> <div className="flex-shrink-0 w-6 h-6 bg-gray-100 rounded-full flex items-center justify-center mt-0.5">
))} <CheckCircle2 className="w-4 h-4 text-gray-500" />
</ul> </div>
</CardContent> <span className="text-gray-600">{feature}</span>
</Card> </li>
))}
</ul>
</CardContent>
</Card>
</motion.div>
{/* Dynamic QR Codes */} {/* Dynamic QR Codes */}
<Card className="relative border-primary-200 bg-primary-50"> <motion.div
<CardHeader> initial={{ opacity: 0, x: 20 }}
<div className="flex items-center justify-between"> whileInView={{ opacity: 1, x: 0 }}
<CardTitle className="text-2xl">{t.static_vs_dynamic.dynamic.title}</CardTitle> viewport={{ once: true }}
<Badge variant="info">{t.static_vs_dynamic.dynamic.subtitle}</Badge> transition={{ duration: 0.5, delay: 0.2 }}
>
<Card className="relative h-full border-2 border-primary-500/20 bg-gradient-to-br from-white to-primary-50/50 shadow-xl shadow-primary-500/10 hover:shadow-2xl hover:shadow-primary-500/20 transition-all duration-300">
<div className="absolute top-0 right-0 p-4">
<div className="absolute -top-3 -right-3">
<span className="relative flex h-4 w-4">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-4 w-4 bg-primary-500"></span>
</span>
</div>
</div> </div>
<p className="text-gray-600">{t.static_vs_dynamic.dynamic.description}</p> <CardHeader>
</CardHeader> <div className="flex items-center justify-between mb-2">
<CardContent> <CardTitle className="text-2xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-primary-600 to-purple-600">{t.static_vs_dynamic.dynamic.title}</CardTitle>
<ul className="space-y-3"> <Badge variant="info" className="bg-primary-100 text-primary-700">{t.static_vs_dynamic.dynamic.subtitle}</Badge>
{t.static_vs_dynamic.dynamic.features.map((feature: string, index: number) => ( </div>
<li key={index} className="flex items-center space-x-3"> <p className="text-gray-600 font-medium">{t.static_vs_dynamic.dynamic.description}</p>
<div className="flex-shrink-0 w-5 h-5 bg-primary-500 rounded-full flex items-center justify-center"> </CardHeader>
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20"> <CardContent>
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" /> <ul className="space-y-4">
</svg> {t.static_vs_dynamic.dynamic.features.map((feature: string, index: number) => (
</div> <li key={index} className="flex items-start space-x-3">
<span className="text-gray-700">{feature}</span> <div className="flex-shrink-0 w-6 h-6 bg-primary-100 rounded-full flex items-center justify-center mt-0.5">
</li> <CheckCircle2 className="w-4 h-4 text-primary-600" />
))} </div>
</ul> <span className="text-gray-900 font-medium">{feature}</span>
</CardContent> </li>
</Card> ))}
</ul>
</CardContent>
</Card>
</motion.div>
</div> </div>
</div> </div>
</section> </section>

View File

@@ -8,22 +8,22 @@ interface StatsStripProps {
export const StatsStrip: React.FC<StatsStripProps> = ({ t }) => { export const StatsStrip: React.FC<StatsStripProps> = ({ t }) => {
const stats = [ const stats = [
{ key: 'users', value: '10,000+', label: t.trust.users }, { key: 'users', value: '1,240+', label: t.trust.users },
{ key: 'codes', value: '500,000+', label: t.trust.codes }, { key: 'codes', value: '8,500+', label: t.trust.codes },
{ key: 'scans', value: '50M+', label: t.trust.scans }, { key: 'scans', value: '1.2M+', label: t.trust.scans },
{ key: 'countries', value: '120+', label: t.trust.countries }, { key: 'countries', value: '120+', label: t.trust.countries },
]; ];
return ( return (
<section className="py-16 bg-gray-50"> <section className="py-20 bg-white border-y border-gray-100">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl"> <div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-8"> <div className="grid grid-cols-2 lg:grid-cols-4 gap-8">
{stats.map((stat, index) => ( {stats.map((stat, index) => (
<div key={stat.key} className="text-center"> <div key={stat.key} className="text-center group hover:-translate-y-1 transition-transform duration-300">
<div className="text-3xl lg:text-4xl font-bold text-primary-600 mb-2"> <div className="text-4xl lg:text-5xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-purple-600 mb-2">
{stat.value} {stat.value}
</div> </div>
<div className="text-gray-600 font-medium"> <div className="text-gray-500 font-medium uppercase tracking-wider text-sm">
{stat.label} {stat.label}
</div> </div>
</div> </div>

View File

@@ -26,7 +26,7 @@ export function Footer({ variant = 'marketing' }: FooterProps) {
<ul className={`space-y-2 ${isDashboard ? 'text-gray-500' : 'text-gray-400'}`}> <ul className={`space-y-2 ${isDashboard ? 'text-gray-500' : 'text-gray-400'}`}>
<li><Link href="/#features" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Features</Link></li> <li><Link href="/#features" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Features</Link></li>
<li><Link href="/#pricing" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Pricing</Link></li> <li><Link href="/#pricing" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Pricing</Link></li>
<li><Link href="/#faq" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>FAQ</Link></li> <li><Link href="/faq" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>FAQ</Link></li>
<li><Link href="/blog" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Blog</Link></li> <li><Link href="/blog" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Blog</Link></li>
</ul> </ul>
</div> </div>
@@ -34,9 +34,9 @@ export function Footer({ variant = 'marketing' }: FooterProps) {
<div> <div>
<h3 className={`font-semibold mb-4 ${isDashboard ? 'text-gray-900' : ''}`}>Resources</h3> <h3 className={`font-semibold mb-4 ${isDashboard ? 'text-gray-900' : ''}`}>Resources</h3>
<ul className={`space-y-2 ${isDashboard ? 'text-gray-500' : 'text-gray-400'}`}> <ul className={`space-y-2 ${isDashboard ? 'text-gray-500' : 'text-gray-400'}`}>
<li><Link href="/pricing" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Full Pricing</Link></li> <li><Link href="/#pricing" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Full Pricing</Link></li>
<li><Link href="/faq" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>All Questions</Link></li> <li><Link href="/faq" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>All Questions</Link></li>
<li><Link href="/blog" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Blog</Link></li> <li><Link href="/blog" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>All Articles</Link></li>
<li><Link href="/signup" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Get Started</Link></li> <li><Link href="/signup" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Get Started</Link></li>
</ul> </ul>
</div> </div>
@@ -56,6 +56,7 @@ export function Footer({ variant = 'marketing' }: FooterProps) {
href="/newsletter" href="/newsletter"
className="text-[6px] text-gray-700 opacity-[0.03] hover:opacity-100 hover:text-white transition-opacity duration-300" className="text-[6px] text-gray-700 opacity-[0.03] hover:opacity-100 hover:text-white transition-opacity duration-300"
> >
<span className="sr-only">Newsletter signup</span>
</Link> </Link>
) : ( ) : (

View File

@@ -32,7 +32,7 @@ export function ScrollToTop() {
{isVisible && ( {isVisible && (
<button <button
onClick={scrollToTop} onClick={scrollToTop}
className="fixed bottom-8 right-8 z-50 p-3 bg-primary-600 hover:bg-primary-700 text-white rounded-full shadow-lg transition-all duration-300 hover:scale-110 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2" className="fixed bottom-8 right-8 z-50 p-4 bg-primary-600 hover:bg-primary-700 text-white rounded-full shadow-lg transition-all duration-300 hover:scale-110 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2"
aria-label="Scroll to top" aria-label="Scroll to top"
> >
<svg <svg

View File

@@ -26,10 +26,10 @@
"engagement_badge": "Free Forever" "engagement_badge": "Free Forever"
}, },
"trust": { "trust": {
"users": "Trusted by small businesses", "users": "Happy Users",
"codes": "Simple QR code creation", "codes": "Active QR Codes",
"scans": "Track every scan", "scans": "Total Scans",
"countries": "Works worldwide" "countries": "Countries"
}, },
"industries": { "industries": {
"restaurant": "Restaurant Chain", "restaurant": "Restaurant Chain",
@@ -62,6 +62,8 @@
"demo_note": "This is a demo QR code" "demo_note": "This is a demo QR code"
}, },
"static_vs_dynamic": { "static_vs_dynamic": {
"title": "Why Dynamic QR Codes Save You Money",
"description": "Stop re-printing materials. Switch destinations instantly and track every scan.",
"static": { "static": {
"title": "Static QR Codes", "title": "Static QR Codes",
"subtitle": "Always Free", "subtitle": "Always Free",
@@ -149,7 +151,7 @@
"50 dynamic QR codes", "50 dynamic QR codes",
"Unlimited static QR codes", "Unlimited static QR codes",
"Advanced analytics (scans, devices, locations)", "Advanced analytics (scans, devices, locations)",
"Custom branding (colors)" "Custom branding (colors & logos)"
] ]
}, },
"business": { "business": {