diff --git a/package-lock.json b/package-lock.json
index 212d284..a6c7161 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,6 +21,7 @@
"dayjs": "^1.11.10",
"exceljs": "^4.4.0",
"file-saver": "^2.0.5",
+ "framer-motion": "^12.24.10",
"i18next": "^23.7.6",
"ioredis": "^5.3.2",
"jszip": "^3.10.1",
@@ -5098,6 +5099,33 @@
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -6607,6 +6635,21 @@
"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": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
diff --git a/package.json b/package.json
index 193ee1c..8305a0e 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"dayjs": "^1.11.10",
"exceljs": "^4.4.0",
"file-saver": "^2.0.5",
+ "framer-motion": "^12.24.10",
"i18next": "^23.7.6",
"ioredis": "^5.3.2",
"jszip": "^3.10.1",
diff --git a/src/app/(marketing)/layout.tsx b/src/app/(marketing)/layout.tsx
index 2790aff..cb38211 100644
--- a/src/app/(marketing)/layout.tsx
+++ b/src/app/(marketing)/layout.tsx
@@ -3,6 +3,7 @@
import React, { useState } from 'react';
import Link from 'next/link';
import { Button } from '@/components/ui/Button';
+import { Footer } from '@/components/ui/Footer';
import en from '@/i18n/en.json';
export default function MarketingLayout({
@@ -11,6 +12,16 @@ export default function MarketingLayout({
children: React.ReactNode;
}) {
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
const t = en;
@@ -25,7 +36,7 @@ export default function MarketingLayout({
return (
{/* Header */}
-
+
{/* Main Content */}
- {children}
+ {children}
{/* Footer */}
-
+
);
}
\ No newline at end of file
diff --git a/src/components/marketing/AIComingSoonBanner.tsx b/src/components/marketing/AIComingSoonBanner.tsx
index 04a5c40..32be33d 100644
--- a/src/components/marketing/AIComingSoonBanner.tsx
+++ b/src/components/marketing/AIComingSoonBanner.tsx
@@ -3,6 +3,7 @@
import React, { useState } from 'react';
import { Sparkles, Brain, TrendingUp, MessageSquare, Palette, ArrowRight, Mail, CheckCircle2, Lock } from 'lucide-react';
import Link from 'next/link';
+import { motion } from 'framer-motion';
const AIComingSoonBanner = () => {
const [email, setEmail] = useState('');
@@ -93,8 +94,15 @@ const AIComingSoonBanner = () => {
{/* Header */}
-
-
+ {/* Header */}
+
+
Coming Soon
@@ -111,14 +119,18 @@ const AIComingSoonBanner = () => {
Revolutionary AI features to transform how you create, manage, and optimize QR codes
-
+
{/* Features Grid */}
{features.map((feature, index) => (
-
@@ -136,12 +148,18 @@ const AIComingSoonBanner = () => {
))}
-
+
))}
{/* Email Capture */}
-
)}
-
+
);
diff --git a/src/components/marketing/FAQ.tsx b/src/components/marketing/FAQ.tsx
index dec7e35..3fa7b6b 100644
--- a/src/components/marketing/FAQ.tsx
+++ b/src/components/marketing/FAQ.tsx
@@ -1,6 +1,7 @@
'use client';
import React, { useState } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
import { Card } from '@/components/ui/Card';
interface FAQProps {
@@ -21,38 +22,62 @@ export const FAQ: React.FC
= ({ t }) => {
return (
-
+
{t.faq.title}
-
+
{questions.map((key, index) => (
-
setOpenIndex(openIndex === index ? null : index)}>
-
-
-
- {t.faq.questions[key].question}
-
-
-
-
- {openIndex === index && (
-
- {t.faq.questions[key].answer}
+
+ setOpenIndex(openIndex === index ? null : index)}>
+
+
+
+ {t.faq.questions[key].question}
+
+
- )}
-
-
+
+
+ {openIndex === index && (
+
+
+ {t.faq.questions[key].answer}
+
+
+ )}
+
+
+
+
))}
diff --git a/src/components/marketing/Features.tsx b/src/components/marketing/Features.tsx
index 5ad53a1..0a7b532 100644
--- a/src/components/marketing/Features.tsx
+++ b/src/components/marketing/Features.tsx
@@ -1,6 +1,7 @@
'use client';
import React from 'react';
+import { motion } from 'framer-motion';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
interface FeaturesProps {
@@ -41,27 +42,41 @@ export const Features: React.FC = ({ t }) => {
return (
-
+
{t.features.title}
-
+
- {features.map((feature) => (
-
-
-
- {feature.icon}
-
- {t.features[feature.key].title}
-
-
-
- {t.features[feature.key].description}
-
-
-
+ {features.map((feature, index) => (
+
+
+
+
+ {feature.icon}
+
+ {t.features[feature.key].title}
+
+
+
+ {t.features[feature.key].description}
+
+
+
+
))}
diff --git a/src/components/marketing/Hero.tsx b/src/components/marketing/Hero.tsx
index 3cf45db..b5bceb7 100644
--- a/src/components/marketing/Hero.tsx
+++ b/src/components/marketing/Hero.tsx
@@ -5,6 +5,8 @@ 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 } from 'lucide-react';
interface HeroProps {
t: any; // i18n translation function
@@ -12,12 +14,27 @@ interface HeroProps {
export const Hero: React.FC = ({ t }) => {
const templateCards = [
- { title: 'URL/Website', color: 'bg-blue-100', icon: '🌐' },
- { title: 'Contact Card', color: 'bg-purple-100', icon: '👤' },
- { title: 'Location', color: 'bg-green-100', icon: '📍' },
- { title: 'Phone Number', color: 'bg-pink-100', icon: '📞' },
+ { title: 'URL/Website', color: 'bg-blue-500/10 text-blue-600', icon: Globe },
+ { title: 'Contact Card', color: 'bg-purple-500/10 text-purple-600', icon: User },
+ { title: 'Location', color: 'bg-green-500/10 text-green-600', icon: MapPin },
+ { 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 (
{/* Animated Background Orbs */}
@@ -43,64 +60,96 @@ export const Hero: React.FC = ({ t }) => {
{t.hero.badge}
-
+
{t.hero.title}
-
+
{t.hero.subtitle}
-
+
{t.hero.features.map((feature: string, index: number) => (
-
-
-
+
+
+
- {feature}
-
+
{feature}
+
))}
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
{/* Right Preview Widget */}
-
+
{templateCards.map((card, index) => (
-
- {card.icon}
- {card.title}
-
+
+
+
+
+
+ {card.title}
+
+
))}
-
+
{/* Floating Badge */}
-
+
+
+
+
+
{t.hero.engagement_badge}
-
+
{/* Smooth Gradient Fade Transition */}
-
+
);
};
\ No newline at end of file
diff --git a/src/components/marketing/HomePageClient.tsx b/src/components/marketing/HomePageClient.tsx
index 633ce57..b857be1 100644
--- a/src/components/marketing/HomePageClient.tsx
+++ b/src/components/marketing/HomePageClient.tsx
@@ -30,8 +30,14 @@ export default function HomePageClient() {
return (
<>
-
+
+ {/* Main Interaction: Generator */}
+
+
+
diff --git a/src/components/marketing/InstantGenerator.tsx b/src/components/marketing/InstantGenerator.tsx
index eaeb985..fdab8e0 100644
--- a/src/components/marketing/InstantGenerator.tsx
+++ b/src/components/marketing/InstantGenerator.tsx
@@ -2,6 +2,7 @@
import React, { useState } from 'react';
import { QRCodeSVG } from 'qrcode.react';
+import { motion } from 'framer-motion';
import { Card } from '@/components/ui/Card';
import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button';
@@ -73,139 +74,183 @@ export const InstantGenerator: React.FC
= ({ t }) => {
};
return (
-
+
-
+
{t.generator.title}
-
+
{/* Left Form */}
-
- setUrl(e.target.value)}
- placeholder={t.generator.url_placeholder}
- />
+
+
+ setUrl(e.target.value)}
+ placeholder={t.generator.url_placeholder}
+ className="transition-all focus:ring-2 focus:ring-primary-500/20"
+ />
-
-
-
-
-
setForegroundColor(e.target.value)}
- className="w-12 h-10 rounded border border-gray-300"
- aria-label="Foreground color picker"
- />
-
setForegroundColor(e.target.value)}
- className="flex-1"
- aria-label="Foreground color hex value"
- />
+
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+ {hasGoodContrast ? t.generator.contrast_good : 'Low contrast'}
+
+
+ Contrast: {contrast.toFixed(1)}:1
+
-
-
-
setSize(Number(e.target.value))}
- className="w-full"
- aria-label={`QR code size: ${size} pixels`}
- />
-
{size}px
+
+
+
-
-
-
- {hasGoodContrast ? t.generator.contrast_good : 'Low contrast'}
-
-
- Contrast: {contrast.toFixed(1)}:1
-
-
-
-
-
-
-
window.location.href = '/login'}>
- {t.generator.save_track}
-
-
+
+
{/* Right Preview */}
-
-
- {t.generator.live_preview}
-
+
+ {/* Artistic Curved Lines Background */}
+
+
+
+
+
+ {/* Decorative Orbs */}
+
+
+
+
+
{t.generator.live_preview}
+
{url ? (
-
+
= ({ t }) => {
) : (
Enter URL
)}
-
URL
-
{t.generator.demo_note}
-
-
+
+ {url || 'https://example.com'}
+
+
{t.generator.demo_note}
+
+
diff --git a/src/components/marketing/Pricing.tsx b/src/components/marketing/Pricing.tsx
index b7be3fa..911b8fc 100644
--- a/src/components/marketing/Pricing.tsx
+++ b/src/components/marketing/Pricing.tsx
@@ -1,6 +1,7 @@
'use client';
import React, { useState } from 'react';
+import { motion } from 'framer-motion';
import Link from 'next/link';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
@@ -32,89 +33,106 @@ export const Pricing: React.FC
= ({ t }) => {
return (
-
+
{t.pricing.title}
{t.pricing.subtitle}
-
+
- {plans.map((plan) => (
-
(
+
- {plan.popular && (
-
-
- {t.pricing[plan.key].badge}
-
-
- )}
-
-
-
- {t.pricing[plan.key].title}
-
-
-
-
- {plan.key === 'free'
- ? t.pricing[plan.key].price
- : billingPeriod === 'month'
- ? t.pricing[plan.key].price
- : plan.key === 'pro'
- ? '€90'
- : '€290'}
-
-
- {plan.key === 'free'
- ? t.pricing[plan.key].period
- : billingPeriod === 'month'
- ? t.pricing[plan.key].period
- : 'per year'}
-
-
- {billingPeriod === 'year' && plan.key !== 'free' && (
-
- Save 16%
+
+ {plan.popular && (
+
+
+ {t.pricing[plan.key].badge}
- )}
-
-
+
+ )}
-
-
- {t.pricing[plan.key].features.map((feature: string, index: number) => (
- -
-
- {feature}
-
- ))}
-
+
+
+ {t.pricing[plan.key].title}
+
+
+
+
+ {plan.key === 'free'
+ ? t.pricing[plan.key].price
+ : billingPeriod === 'month'
+ ? t.pricing[plan.key].price
+ : plan.key === 'pro'
+ ? '€90'
+ : '€290'}
+
+
+ {plan.key === 'free'
+ ? t.pricing[plan.key].period
+ : billingPeriod === 'month'
+ ? t.pricing[plan.key].period
+ : 'per year'}
+
+
+ {billingPeriod === 'year' && plan.key !== 'free' && (
+
+ Save 16%
+
+ )}
+
+
-
-
-
- Get Started
-
-
-
-
-
+
+
+ {t.pricing[plan.key].features.map((feature: string, index: number) => (
+ -
+
+ {feature}
+
+ ))}
+
+
+
+
+
+ Get Started
+
+
+
+
+
+
))}
diff --git a/src/components/marketing/StaticVsDynamic.tsx b/src/components/marketing/StaticVsDynamic.tsx
index 2208f01..2579cf7 100644
--- a/src/components/marketing/StaticVsDynamic.tsx
+++ b/src/components/marketing/StaticVsDynamic.tsx
@@ -1,6 +1,8 @@
'use client';
import React from 'react';
+import { motion } from 'framer-motion';
+import { CheckCircle2 } from 'lucide-react';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
@@ -12,56 +14,83 @@ export const StaticVsDynamic: React.FC = ({ t }) => {
return (
+
+
+ {t.static_vs_dynamic.title}
+
+
+ {t.static_vs_dynamic.description}
+
+
+
{/* Static QR Codes */}
-
-
-
- {t.static_vs_dynamic.static.title}
- {t.static_vs_dynamic.static.subtitle}
-
- {t.static_vs_dynamic.static.description}
-
-
-
- {t.static_vs_dynamic.static.features.map((feature: string, index: number) => (
- -
-
- {feature}
-
- ))}
-
-
-
+
+
+
+
+ {t.static_vs_dynamic.static.title}
+ {t.static_vs_dynamic.static.subtitle}
+
+ {t.static_vs_dynamic.static.description}
+
+
+
+ {t.static_vs_dynamic.static.features.map((feature: string, index: number) => (
+ -
+
+
+
+ {feature}
+
+ ))}
+
+
+
+
{/* Dynamic QR Codes */}
-
-
-
-
{t.static_vs_dynamic.dynamic.title}
-
{t.static_vs_dynamic.dynamic.subtitle}
+
+
+
- {t.static_vs_dynamic.dynamic.description}
-
-
-
- {t.static_vs_dynamic.dynamic.features.map((feature: string, index: number) => (
- -
-
- {feature}
-
- ))}
-
-
-
+
+
+ {t.static_vs_dynamic.dynamic.title}
+ {t.static_vs_dynamic.dynamic.subtitle}
+
+ {t.static_vs_dynamic.dynamic.description}
+
+
+
+ {t.static_vs_dynamic.dynamic.features.map((feature: string, index: number) => (
+ -
+
+
+
+ {feature}
+
+ ))}
+
+
+
+
diff --git a/src/components/marketing/StatsStrip.tsx b/src/components/marketing/StatsStrip.tsx
index fc5cebc..82aa87f 100644
--- a/src/components/marketing/StatsStrip.tsx
+++ b/src/components/marketing/StatsStrip.tsx
@@ -8,22 +8,22 @@ interface StatsStripProps {
export const StatsStrip: React.FC
= ({ t }) => {
const stats = [
- { key: 'users', value: '10,000+', label: t.trust.users },
- { key: 'codes', value: '500,000+', label: t.trust.codes },
- { key: 'scans', value: '50M+', label: t.trust.scans },
+ { key: 'users', value: '1,240+', label: t.trust.users },
+ { key: 'codes', value: '8,500+', label: t.trust.codes },
+ { key: 'scans', value: '1.2M+', label: t.trust.scans },
{ key: 'countries', value: '120+', label: t.trust.countries },
];
return (
-
+
{stats.map((stat, index) => (
-
-
+
+
{stat.value}
-
diff --git a/src/components/ui/Footer.tsx b/src/components/ui/Footer.tsx
index e9837f8..cceeaf3 100644
--- a/src/components/ui/Footer.tsx
+++ b/src/components/ui/Footer.tsx
@@ -26,7 +26,7 @@ export function Footer({ variant = 'marketing' }: FooterProps) {
- Features
- Pricing
- - FAQ
+ - FAQ
- Blog
@@ -34,7 +34,7 @@ export function Footer({ variant = 'marketing' }: FooterProps) {
Resources
- - Full Pricing
+ - Full Pricing
- All Questions
- Blog
- Get Started
diff --git a/src/i18n/en.json b/src/i18n/en.json
index e3437ef..1d34218 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -26,10 +26,10 @@
"engagement_badge": "Free Forever"
},
"trust": {
- "users": "Trusted by small businesses",
- "codes": "Simple QR code creation",
- "scans": "Track every scan",
- "countries": "Works worldwide"
+ "users": "Happy Users",
+ "codes": "Active QR Codes",
+ "scans": "Total Scans",
+ "countries": "Countries"
},
"industries": {
"restaurant": "Restaurant Chain",
@@ -62,6 +62,8 @@
"demo_note": "This is a demo QR code"
},
"static_vs_dynamic": {
+ "title": "Why Dynamic QR Codes Save You Money",
+ "description": "Stop re-printing materials. Switch destinations instantly and track every scan.",
"static": {
"title": "Static QR Codes",
"subtitle": "Always Free",