chore: normalize line endings
This commit is contained in:
@@ -1,248 +1,248 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Footer } from '@/components/ui/Footer';
|
||||
import de from '@/i18n/de.json';
|
||||
import { ChevronDown, Wifi, Contact, MessageCircle, QrCode, Link2, Type, Mail, MessageSquare, Phone, Calendar, MapPin, Facebook, Instagram, Twitter, Youtube, Music, Bitcoin, CreditCard, Video, Users } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
|
||||
export default function MarketingLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
const [toolsOpen, setToolsOpen] = useState(false);
|
||||
const [mobileToolsOpen, setMobileToolsOpen] = useState(false);
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setScrolled(window.scrollY > 20);
|
||||
};
|
||||
|
||||
// Check immediately on mount
|
||||
handleScroll();
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
// Close simple menus when path changes
|
||||
useEffect(() => {
|
||||
setMobileMenuOpen(false);
|
||||
setToolsOpen(false);
|
||||
}, [pathname]);
|
||||
|
||||
// Always German for this layout
|
||||
const t = de;
|
||||
|
||||
const tools = [
|
||||
{ name: 'URL / Link', description: 'Link to any website', href: '/tools/url-qr-code', icon: Link2, color: 'text-blue-500', bgColor: 'bg-blue-50' },
|
||||
{ name: 'Text', description: 'Plain text message', href: '/tools/text-qr-code', icon: Type, color: 'text-slate-500', bgColor: 'bg-slate-50' },
|
||||
{ name: 'WiFi', description: 'Share WiFi credentials', href: '/tools/wifi-qr-code', icon: Wifi, color: 'text-indigo-500', bgColor: 'bg-indigo-50' },
|
||||
{ name: 'VCard', description: 'Digital business card', href: '/tools/vcard-qr-code', icon: Contact, color: 'text-pink-500', bgColor: 'bg-pink-50' },
|
||||
{ name: 'WhatsApp', description: 'Start a chat', href: '/tools/whatsapp-qr-code', icon: MessageCircle, color: 'text-green-500', bgColor: 'bg-green-50' },
|
||||
{ name: 'Email', description: 'Compose an email', href: '/tools/email-qr-code', icon: Mail, color: 'text-amber-500', bgColor: 'bg-amber-50' },
|
||||
{ name: 'SMS', description: 'Send a text message', href: '/tools/sms-qr-code', icon: MessageSquare, color: 'text-cyan-500', bgColor: 'bg-cyan-50' },
|
||||
{ name: 'Phone', description: 'Start a call', href: '/tools/phone-qr-code', icon: Phone, color: 'text-violet-500', bgColor: 'bg-violet-50' },
|
||||
{ name: 'Event', description: 'Add calendar event', href: '/tools/event-qr-code', icon: Calendar, color: 'text-red-500', bgColor: 'bg-red-50' },
|
||||
{ name: 'Location', description: 'Share a place', href: '/tools/geolocation-qr-code', icon: MapPin, color: 'text-emerald-500', bgColor: 'bg-emerald-50' },
|
||||
{ name: 'Facebook', description: 'Facebook profile/page', href: '/tools/facebook-qr-code', icon: Facebook, color: 'text-blue-600', bgColor: 'bg-blue-50' },
|
||||
{ name: 'Instagram', description: 'Instagram profile', href: '/tools/instagram-qr-code', icon: Instagram, color: 'text-pink-600', bgColor: 'bg-pink-50' },
|
||||
{ name: 'Twitter / X', description: 'Twitter profile', href: '/tools/twitter-qr-code', icon: Twitter, color: 'text-sky-500', bgColor: 'bg-sky-50' },
|
||||
{ name: 'YouTube', description: 'YouTube video/channel', href: '/tools/youtube-qr-code', icon: Youtube, color: 'text-red-600', bgColor: 'bg-red-50' },
|
||||
{ name: 'TikTok', description: 'TikTok profile', href: '/tools/tiktok-qr-code', icon: Music, color: 'text-slate-800', bgColor: 'bg-slate-100' },
|
||||
{ name: 'Crypto', description: 'Share wallet address', href: '/tools/crypto-qr-code', icon: Bitcoin, color: 'text-orange-500', bgColor: 'bg-orange-50' },
|
||||
{ name: 'PayPal', description: 'Receive payments', href: '/tools/paypal-qr-code', icon: CreditCard, color: 'text-blue-700', bgColor: 'bg-blue-50' },
|
||||
{ name: 'Zoom', description: 'Join Zoom meeting', href: '/tools/zoom-qr-code', icon: Video, color: 'text-sky-500', bgColor: 'bg-sky-50' },
|
||||
{ name: 'Teams', description: 'Join Teams meeting', href: '/tools/teams-qr-code', icon: Users, color: 'text-violet-500', bgColor: 'bg-violet-50' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Header */}
|
||||
<header
|
||||
className="fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-md border-b border-slate-200 shadow-sm"
|
||||
>
|
||||
<nav className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl h-20 flex items-center justify-between">
|
||||
{/* Logo */}
|
||||
<Link href="/" className="flex items-center space-x-2.5 group">
|
||||
<div className="relative w-9 h-9 flex items-center justify-center bg-indigo-600 rounded-lg shadow-indigo-200 shadow-lg group-hover:scale-105 transition-transform duration-200">
|
||||
<QrCode className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<span className="text-xl font-bold text-slate-900 tracking-tight group-hover:text-indigo-600 transition-colors">QR Master</span>
|
||||
</Link>
|
||||
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<div className="hidden md:flex items-center space-x-1">
|
||||
|
||||
{/* Tools Dropdown */}
|
||||
<div
|
||||
className="relative group px-3 py-2"
|
||||
onMouseEnter={() => setToolsOpen(true)}
|
||||
onMouseLeave={() => setToolsOpen(false)}
|
||||
>
|
||||
<button className="flex items-center space-x-1 text-sm font-medium text-slate-600 group-hover:text-slate-900 transition-colors">
|
||||
<span>{t.nav.tools}</span>
|
||||
<ChevronDown className={cn("w-4 h-4 transition-transform duration-200", toolsOpen && "rotate-180")} />
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{toolsOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 10 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
className="absolute left-1/2 -translate-x-1/2 top-full mt-2 w-[750px] bg-white rounded-2xl shadow-lg border border-slate-100 p-4 overflow-hidden"
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-1">
|
||||
{tools.map((tool) => (
|
||||
<Link
|
||||
key={tool.name}
|
||||
href={tool.href}
|
||||
className="flex items-center space-x-3 p-2.5 rounded-xl transition-colors hover:bg-slate-50"
|
||||
>
|
||||
<div className={cn("p-2 rounded-lg shrink-0", tool.bgColor, tool.color)}>
|
||||
<tool.icon className="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-slate-900">{tool.name}</div>
|
||||
<p className="text-xs text-slate-500 leading-snug">{tool.description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-3 pt-3 border-t border-slate-100 -mx-4 -mb-4 px-4 py-3 text-center bg-slate-50/50">
|
||||
<p className="text-xs text-slate-500 font-medium">{t.nav.all_free}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
<Link href="/#features" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.features}
|
||||
</Link>
|
||||
<Link href="/#pricing" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.pricing}
|
||||
</Link>
|
||||
<Link href="/blog" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.blog}
|
||||
</Link>
|
||||
<Link href="/#faq" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.faq}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="hidden md:flex items-center space-x-4">
|
||||
<Link href="/login" className="text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.login}
|
||||
</Link>
|
||||
|
||||
<Link href="/signup">
|
||||
<Button className={cn(
|
||||
"font-semibold shadow-lg shadow-indigo-500/20 transition-all hover:scale-105",
|
||||
scrolled ? "bg-blue-600 text-white hover:bg-blue-700" : "bg-blue-600 text-white hover:bg-blue-700"
|
||||
)}>
|
||||
{t.nav.cta || "Get Started Free"}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button - Always dark */}
|
||||
<button
|
||||
className="md:hidden p-2 text-slate-900"
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{mobileMenuOpen ? (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
) : (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||
)}
|
||||
</svg>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
<AnimatePresence>
|
||||
{mobileMenuOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
className="md:hidden bg-white border-b border-slate-100 overflow-hidden"
|
||||
>
|
||||
<div className="container mx-auto px-4 py-6 space-y-2">
|
||||
{/* Free Tools Accordion */}
|
||||
<button
|
||||
onClick={() => setMobileToolsOpen(!mobileToolsOpen)}
|
||||
className="flex items-center justify-between w-full px-4 py-3 rounded-xl hover:bg-slate-50 text-slate-700 font-semibold"
|
||||
>
|
||||
<span>{t.nav.tools}</span>
|
||||
<ChevronDown className={cn("w-5 h-5 transition-transform", mobileToolsOpen && "rotate-180")} />
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{mobileToolsOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="max-h-[50vh] overflow-y-auto pl-4 space-y-1 border-l-2 border-slate-100 ml-4">
|
||||
{tools.map((tool) => (
|
||||
<Link
|
||||
key={tool.name}
|
||||
href={tool.href}
|
||||
className="flex items-center gap-3 px-4 py-2.5 rounded-lg hover:bg-slate-50 text-slate-600 text-sm"
|
||||
onClick={() => { setMobileMenuOpen(false); setMobileToolsOpen(false); }}
|
||||
>
|
||||
<tool.icon className={cn("w-4 h-4", tool.color)} />
|
||||
{tool.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<div className="h-px bg-slate-100 my-2"></div>
|
||||
|
||||
<Link href="/#features" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.features}</Link>
|
||||
<Link href="/#pricing" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.pricing}</Link>
|
||||
<Link href="/blog" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.blog}</Link>
|
||||
<Link href="/#faq" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.faq}</Link>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 pt-4">
|
||||
<Link href="/login" onClick={() => setMobileMenuOpen(false)}>
|
||||
<Button variant="outline" className="w-full justify-center">{t.nav.login}</Button>
|
||||
</Link>
|
||||
<Link href="/signup" onClick={() => setMobileMenuOpen(false)}>
|
||||
<Button className="w-full justify-center bg-indigo-600 hover:bg-indigo-700">{t.nav.cta}</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="pt-20">{children}</main>
|
||||
|
||||
{/* Footer */}
|
||||
<Footer t={t} />
|
||||
</div >
|
||||
);
|
||||
}
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Footer } from '@/components/ui/Footer';
|
||||
import de from '@/i18n/de.json';
|
||||
import { ChevronDown, Wifi, Contact, MessageCircle, QrCode, Link2, Type, Mail, MessageSquare, Phone, Calendar, MapPin, Facebook, Instagram, Twitter, Youtube, Music, Bitcoin, CreditCard, Video, Users } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
|
||||
export default function MarketingLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
const [toolsOpen, setToolsOpen] = useState(false);
|
||||
const [mobileToolsOpen, setMobileToolsOpen] = useState(false);
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setScrolled(window.scrollY > 20);
|
||||
};
|
||||
|
||||
// Check immediately on mount
|
||||
handleScroll();
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
// Close simple menus when path changes
|
||||
useEffect(() => {
|
||||
setMobileMenuOpen(false);
|
||||
setToolsOpen(false);
|
||||
}, [pathname]);
|
||||
|
||||
// Always German for this layout
|
||||
const t = de;
|
||||
|
||||
const tools = [
|
||||
{ name: 'URL / Link', description: 'Link to any website', href: '/tools/url-qr-code', icon: Link2, color: 'text-blue-500', bgColor: 'bg-blue-50' },
|
||||
{ name: 'Text', description: 'Plain text message', href: '/tools/text-qr-code', icon: Type, color: 'text-slate-500', bgColor: 'bg-slate-50' },
|
||||
{ name: 'WiFi', description: 'Share WiFi credentials', href: '/tools/wifi-qr-code', icon: Wifi, color: 'text-indigo-500', bgColor: 'bg-indigo-50' },
|
||||
{ name: 'VCard', description: 'Digital business card', href: '/tools/vcard-qr-code', icon: Contact, color: 'text-pink-500', bgColor: 'bg-pink-50' },
|
||||
{ name: 'WhatsApp', description: 'Start a chat', href: '/tools/whatsapp-qr-code', icon: MessageCircle, color: 'text-green-500', bgColor: 'bg-green-50' },
|
||||
{ name: 'Email', description: 'Compose an email', href: '/tools/email-qr-code', icon: Mail, color: 'text-amber-500', bgColor: 'bg-amber-50' },
|
||||
{ name: 'SMS', description: 'Send a text message', href: '/tools/sms-qr-code', icon: MessageSquare, color: 'text-cyan-500', bgColor: 'bg-cyan-50' },
|
||||
{ name: 'Phone', description: 'Start a call', href: '/tools/phone-qr-code', icon: Phone, color: 'text-violet-500', bgColor: 'bg-violet-50' },
|
||||
{ name: 'Event', description: 'Add calendar event', href: '/tools/event-qr-code', icon: Calendar, color: 'text-red-500', bgColor: 'bg-red-50' },
|
||||
{ name: 'Location', description: 'Share a place', href: '/tools/geolocation-qr-code', icon: MapPin, color: 'text-emerald-500', bgColor: 'bg-emerald-50' },
|
||||
{ name: 'Facebook', description: 'Facebook profile/page', href: '/tools/facebook-qr-code', icon: Facebook, color: 'text-blue-600', bgColor: 'bg-blue-50' },
|
||||
{ name: 'Instagram', description: 'Instagram profile', href: '/tools/instagram-qr-code', icon: Instagram, color: 'text-pink-600', bgColor: 'bg-pink-50' },
|
||||
{ name: 'Twitter / X', description: 'Twitter profile', href: '/tools/twitter-qr-code', icon: Twitter, color: 'text-sky-500', bgColor: 'bg-sky-50' },
|
||||
{ name: 'YouTube', description: 'YouTube video/channel', href: '/tools/youtube-qr-code', icon: Youtube, color: 'text-red-600', bgColor: 'bg-red-50' },
|
||||
{ name: 'TikTok', description: 'TikTok profile', href: '/tools/tiktok-qr-code', icon: Music, color: 'text-slate-800', bgColor: 'bg-slate-100' },
|
||||
{ name: 'Crypto', description: 'Share wallet address', href: '/tools/crypto-qr-code', icon: Bitcoin, color: 'text-orange-500', bgColor: 'bg-orange-50' },
|
||||
{ name: 'PayPal', description: 'Receive payments', href: '/tools/paypal-qr-code', icon: CreditCard, color: 'text-blue-700', bgColor: 'bg-blue-50' },
|
||||
{ name: 'Zoom', description: 'Join Zoom meeting', href: '/tools/zoom-qr-code', icon: Video, color: 'text-sky-500', bgColor: 'bg-sky-50' },
|
||||
{ name: 'Teams', description: 'Join Teams meeting', href: '/tools/teams-qr-code', icon: Users, color: 'text-violet-500', bgColor: 'bg-violet-50' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Header */}
|
||||
<header
|
||||
className="fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-md border-b border-slate-200 shadow-sm"
|
||||
>
|
||||
<nav className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl h-20 flex items-center justify-between">
|
||||
{/* Logo */}
|
||||
<Link href="/" className="flex items-center space-x-2.5 group">
|
||||
<div className="relative w-9 h-9 flex items-center justify-center bg-indigo-600 rounded-lg shadow-indigo-200 shadow-lg group-hover:scale-105 transition-transform duration-200">
|
||||
<QrCode className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<span className="text-xl font-bold text-slate-900 tracking-tight group-hover:text-indigo-600 transition-colors">QR Master</span>
|
||||
</Link>
|
||||
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<div className="hidden md:flex items-center space-x-1">
|
||||
|
||||
{/* Tools Dropdown */}
|
||||
<div
|
||||
className="relative group px-3 py-2"
|
||||
onMouseEnter={() => setToolsOpen(true)}
|
||||
onMouseLeave={() => setToolsOpen(false)}
|
||||
>
|
||||
<button className="flex items-center space-x-1 text-sm font-medium text-slate-600 group-hover:text-slate-900 transition-colors">
|
||||
<span>{t.nav.tools}</span>
|
||||
<ChevronDown className={cn("w-4 h-4 transition-transform duration-200", toolsOpen && "rotate-180")} />
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{toolsOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 10 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
className="absolute left-1/2 -translate-x-1/2 top-full mt-2 w-[750px] bg-white rounded-2xl shadow-lg border border-slate-100 p-4 overflow-hidden"
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-1">
|
||||
{tools.map((tool) => (
|
||||
<Link
|
||||
key={tool.name}
|
||||
href={tool.href}
|
||||
className="flex items-center space-x-3 p-2.5 rounded-xl transition-colors hover:bg-slate-50"
|
||||
>
|
||||
<div className={cn("p-2 rounded-lg shrink-0", tool.bgColor, tool.color)}>
|
||||
<tool.icon className="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-slate-900">{tool.name}</div>
|
||||
<p className="text-xs text-slate-500 leading-snug">{tool.description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-3 pt-3 border-t border-slate-100 -mx-4 -mb-4 px-4 py-3 text-center bg-slate-50/50">
|
||||
<p className="text-xs text-slate-500 font-medium">{t.nav.all_free}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
<Link href="/#features" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.features}
|
||||
</Link>
|
||||
<Link href="/#pricing" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.pricing}
|
||||
</Link>
|
||||
<Link href="/blog" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.blog}
|
||||
</Link>
|
||||
<Link href="/#faq" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.faq}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="hidden md:flex items-center space-x-4">
|
||||
<Link href="/login" className="text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.login}
|
||||
</Link>
|
||||
|
||||
<Link href="/signup">
|
||||
<Button className={cn(
|
||||
"font-semibold shadow-lg shadow-indigo-500/20 transition-all hover:scale-105",
|
||||
scrolled ? "bg-blue-600 text-white hover:bg-blue-700" : "bg-blue-600 text-white hover:bg-blue-700"
|
||||
)}>
|
||||
{t.nav.cta || "Get Started Free"}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button - Always dark */}
|
||||
<button
|
||||
className="md:hidden p-2 text-slate-900"
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{mobileMenuOpen ? (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
) : (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||
)}
|
||||
</svg>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
<AnimatePresence>
|
||||
{mobileMenuOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
className="md:hidden bg-white border-b border-slate-100 overflow-hidden"
|
||||
>
|
||||
<div className="container mx-auto px-4 py-6 space-y-2">
|
||||
{/* Free Tools Accordion */}
|
||||
<button
|
||||
onClick={() => setMobileToolsOpen(!mobileToolsOpen)}
|
||||
className="flex items-center justify-between w-full px-4 py-3 rounded-xl hover:bg-slate-50 text-slate-700 font-semibold"
|
||||
>
|
||||
<span>{t.nav.tools}</span>
|
||||
<ChevronDown className={cn("w-5 h-5 transition-transform", mobileToolsOpen && "rotate-180")} />
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{mobileToolsOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="max-h-[50vh] overflow-y-auto pl-4 space-y-1 border-l-2 border-slate-100 ml-4">
|
||||
{tools.map((tool) => (
|
||||
<Link
|
||||
key={tool.name}
|
||||
href={tool.href}
|
||||
className="flex items-center gap-3 px-4 py-2.5 rounded-lg hover:bg-slate-50 text-slate-600 text-sm"
|
||||
onClick={() => { setMobileMenuOpen(false); setMobileToolsOpen(false); }}
|
||||
>
|
||||
<tool.icon className={cn("w-4 h-4", tool.color)} />
|
||||
{tool.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<div className="h-px bg-slate-100 my-2"></div>
|
||||
|
||||
<Link href="/#features" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.features}</Link>
|
||||
<Link href="/#pricing" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.pricing}</Link>
|
||||
<Link href="/blog" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.blog}</Link>
|
||||
<Link href="/#faq" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.faq}</Link>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 pt-4">
|
||||
<Link href="/login" onClick={() => setMobileMenuOpen(false)}>
|
||||
<Button variant="outline" className="w-full justify-center">{t.nav.login}</Button>
|
||||
</Link>
|
||||
<Link href="/signup" onClick={() => setMobileMenuOpen(false)}>
|
||||
<Button className="w-full justify-center bg-indigo-600 hover:bg-indigo-700">{t.nav.cta}</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="pt-20">{children}</main>
|
||||
|
||||
{/* Footer */}
|
||||
<Footer t={t} />
|
||||
</div >
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,108 +1,108 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
||||
import { Hero } from '@/components/marketing/Hero';
|
||||
import { InstantGenerator } from '@/components/marketing/InstantGenerator';
|
||||
import { StaticVsDynamic } from '@/components/marketing/StaticVsDynamic';
|
||||
import { Features } from '@/components/marketing/Features';
|
||||
import { Pricing } from '@/components/marketing/Pricing';
|
||||
import { FAQ } from '@/components/marketing/FAQ';
|
||||
import { ScrollToTop } from '@/components/ui/ScrollToTop';
|
||||
import de from '@/i18n/de.json';
|
||||
|
||||
function truncateAtWord(text: string, maxLength: number): string {
|
||||
if (text.length <= maxLength) return text;
|
||||
const truncated = text.slice(0, maxLength);
|
||||
const lastSpace = truncated.lastIndexOf(' ');
|
||||
return lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated;
|
||||
}
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const title = truncateAtWord('QR Code Erstellen – Kostenlos & Sofort | QR Master', 60);
|
||||
const description = truncateAtWord(
|
||||
'Erstellen Sie QR Codes kostenlos in Sekunden. Statische und dynamische QR-Codes mit Tracking, individuellem Branding und Massen-Erstellung. Für immer kostenlos.',
|
||||
160
|
||||
);
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
keywords: [
|
||||
'qr code erstellen',
|
||||
'qr code generator',
|
||||
'qr code kostenlos',
|
||||
'qr-code-generatoren',
|
||||
'qr codes erstellen',
|
||||
'qr code erstellen kostenlos',
|
||||
'dynamischer qr code',
|
||||
'qr code mit logo'
|
||||
],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/qr-code-erstellen',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/',
|
||||
en: 'https://www.qrmaster.net/',
|
||||
de: 'https://www.qrmaster.net/qr-code-erstellen',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'QR Code Erstellen – Kostenlos & Sofort | QR Master',
|
||||
description: 'Erstellen Sie QR Codes kostenlos in Sekunden. Mit Tracking, Branding und Massen-Erstellung.',
|
||||
url: 'https://www.qrmaster.net/qr-code-erstellen',
|
||||
type: 'website',
|
||||
locale: 'de_DE',
|
||||
},
|
||||
twitter: {
|
||||
title: 'QR Code Erstellen – Kostenlos | QR Master',
|
||||
description: 'QR Codes erstellen in Sekunden. Kostenlos, mit Tracking und individuellem Branding.',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function QRCodeErstellenPage() {
|
||||
// Use German translations
|
||||
const t = de;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[organizationSchema(), websiteSchema()]} />
|
||||
|
||||
{/* Server-rendered SEO content for crawlers - GERMAN */}
|
||||
<div className="sr-only" aria-hidden="false">
|
||||
<h1>QR Code Erstellen – Kostenloser QR Code Generator mit Tracking & Statistiken</h1>
|
||||
<p>
|
||||
Erstellen Sie professionelle QR Codes für Ihr Unternehmen mit QR Master. Unser dynamischer QR Code Generator
|
||||
ermöglicht es Ihnen, trackbare QR Codes zu erstellen, Ziel-URLs jederzeit zu ändern und detaillierte Statistiken einzusehen.
|
||||
Perfekt für Restaurants, Einzelhandel, Events und Marketing-Kampagnen.
|
||||
</p>
|
||||
<p>
|
||||
Funktionen: Dynamische QR Codes mit Echtzeit-Tracking, Massen-QR-Code-Erstellung aus Excel/CSV,
|
||||
individuelles Branding mit Farben und Logos, erweiterte Scan-Statistiken mit Gerätetypen und Standorten,
|
||||
vCard QR Codes für digitale Visitenkarten und QR Codes für Restaurant-Speisekarten.
|
||||
</p>
|
||||
<p>
|
||||
Starten Sie kostenlos mit 3 dynamischen QR Codes und unbegrenzten statischen Codes. Upgrade auf Pro für 50 Codes
|
||||
mit erweiterten Statistiken, oder Business für 500 Codes mit Massen-Erstellung und Prioritäts-Support.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Hero t={t} />
|
||||
|
||||
{/* Main Interaction: Generator */}
|
||||
<InstantGenerator t={t} />
|
||||
|
||||
<StaticVsDynamic t={t} />
|
||||
<Features t={t} />
|
||||
|
||||
{/* Pricing Section */}
|
||||
<Pricing t={t} />
|
||||
|
||||
{/* FAQ Section */}
|
||||
<FAQ t={t} />
|
||||
|
||||
{/* Scroll to Top Button */}
|
||||
<ScrollToTop />
|
||||
</>
|
||||
);
|
||||
}
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
||||
import { Hero } from '@/components/marketing/Hero';
|
||||
import { InstantGenerator } from '@/components/marketing/InstantGenerator';
|
||||
import { StaticVsDynamic } from '@/components/marketing/StaticVsDynamic';
|
||||
import { Features } from '@/components/marketing/Features';
|
||||
import { Pricing } from '@/components/marketing/Pricing';
|
||||
import { FAQ } from '@/components/marketing/FAQ';
|
||||
import { ScrollToTop } from '@/components/ui/ScrollToTop';
|
||||
import de from '@/i18n/de.json';
|
||||
|
||||
function truncateAtWord(text: string, maxLength: number): string {
|
||||
if (text.length <= maxLength) return text;
|
||||
const truncated = text.slice(0, maxLength);
|
||||
const lastSpace = truncated.lastIndexOf(' ');
|
||||
return lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated;
|
||||
}
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const title = truncateAtWord('QR Code Erstellen – Kostenlos & Sofort | QR Master', 60);
|
||||
const description = truncateAtWord(
|
||||
'Erstellen Sie QR Codes kostenlos in Sekunden. Statische und dynamische QR-Codes mit Tracking, individuellem Branding und Massen-Erstellung. Für immer kostenlos.',
|
||||
160
|
||||
);
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
keywords: [
|
||||
'qr code erstellen',
|
||||
'qr code generator',
|
||||
'qr code kostenlos',
|
||||
'qr-code-generatoren',
|
||||
'qr codes erstellen',
|
||||
'qr code erstellen kostenlos',
|
||||
'dynamischer qr code',
|
||||
'qr code mit logo'
|
||||
],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/qr-code-erstellen',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/',
|
||||
en: 'https://www.qrmaster.net/',
|
||||
de: 'https://www.qrmaster.net/qr-code-erstellen',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'QR Code Erstellen – Kostenlos & Sofort | QR Master',
|
||||
description: 'Erstellen Sie QR Codes kostenlos in Sekunden. Mit Tracking, Branding und Massen-Erstellung.',
|
||||
url: 'https://www.qrmaster.net/qr-code-erstellen',
|
||||
type: 'website',
|
||||
locale: 'de_DE',
|
||||
},
|
||||
twitter: {
|
||||
title: 'QR Code Erstellen – Kostenlos | QR Master',
|
||||
description: 'QR Codes erstellen in Sekunden. Kostenlos, mit Tracking und individuellem Branding.',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function QRCodeErstellenPage() {
|
||||
// Use German translations
|
||||
const t = de;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[organizationSchema(), websiteSchema()]} />
|
||||
|
||||
{/* Server-rendered SEO content for crawlers - GERMAN */}
|
||||
<div className="sr-only" aria-hidden="false">
|
||||
<h1>QR Code Erstellen – Kostenloser QR Code Generator mit Tracking & Statistiken</h1>
|
||||
<p>
|
||||
Erstellen Sie professionelle QR Codes für Ihr Unternehmen mit QR Master. Unser dynamischer QR Code Generator
|
||||
ermöglicht es Ihnen, trackbare QR Codes zu erstellen, Ziel-URLs jederzeit zu ändern und detaillierte Statistiken einzusehen.
|
||||
Perfekt für Restaurants, Einzelhandel, Events und Marketing-Kampagnen.
|
||||
</p>
|
||||
<p>
|
||||
Funktionen: Dynamische QR Codes mit Echtzeit-Tracking, Massen-QR-Code-Erstellung aus Excel/CSV,
|
||||
individuelles Branding mit Farben und Logos, erweiterte Scan-Statistiken mit Gerätetypen und Standorten,
|
||||
vCard QR Codes für digitale Visitenkarten und QR Codes für Restaurant-Speisekarten.
|
||||
</p>
|
||||
<p>
|
||||
Starten Sie kostenlos mit 3 dynamischen QR Codes und unbegrenzten statischen Codes. Upgrade auf Pro für 50 Codes
|
||||
mit erweiterten Statistiken, oder Business für 500 Codes mit Massen-Erstellung und Prioritäts-Support.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Hero t={t} />
|
||||
|
||||
{/* Main Interaction: Generator */}
|
||||
<InstantGenerator t={t} />
|
||||
|
||||
<StaticVsDynamic t={t} />
|
||||
<Features t={t} />
|
||||
|
||||
{/* Pricing Section */}
|
||||
<Pricing t={t} />
|
||||
|
||||
{/* FAQ Section */}
|
||||
<FAQ t={t} />
|
||||
|
||||
{/* Scroll to Top Button */}
|
||||
<ScrollToTop />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user